-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Consider separating Terminal into different layers to prevent DOM access exceptions #1380
Comments
Some rough options below. Delegation seems like the cleanest to me, probably needs a little more boilerplate though. Delegation
interface ITerminal {
open(element: HTMLElement): void;
reset(): void;
}
class Terminal implements ITerminal {
private _delegate: ITerminal;
open(element: HTMLElement): void {
// Special case for this method
if (this._delegate instanceof HeadlessTerminal) {
this._delegate = new AttachedTerminal(this._delegate, element);
}
}
reset(): void {
this._delegate.reset();
}
}
class HeadlessTerminal implements ITerminal {
// buffer, options, etc. anything that does not need the DOM
open(element: HTMLElement): void { /* noop */ }
reset(): void { console.log('reset'); }
}
class AttachedTerminal implements ITerminal {
// viewport, textarea, etc. anything that needs the DOM
// Use HeadlessTerminal here so that we don't need to expose a bunch of stuff on ITerminal
constructor(private _headlessTerminal: HeadlessTerminal, element: HTMLElement) {
}
open(element: HTMLElement): void {
this._headlessTerminal.open();
// Attach to DOM
}
reset(): void {
this._headlessTerminal.open();
// DOM access
}
} Decorator (sort of)
interface ITerminal {
open(element: HTMLElement): void;
reset(): void;
}
class Terminal implements ITerminal {
// buffer, options, etc. anything that does not need the DOM
private attachedTerminal: AttachedTerminal;
open(element: HTMLElement): void {
if (!attachedTerminal) {
attachedTerminal = new AttachedTerminal(this, element);
}
}
reset(): void {
console.log('reset');
// We would need to put this on every single method that needs DOM access
if (attachedTerminal) {
attachedTerminal.reset();
}
}
}
class AttachedTerminal implements ITerminal {
constructor(private _headlessTerminal: Terminal, element: HTMLElement) {
this.open(element);
}
open(element: HTMLElement): void {
// Attach to DOM
}
reset(): void {
// DOM access
}
} Events
interface ITerminal {
open(element: HTMLElement): void;
reset(): void;
}
class Terminal implements ITerminal {
// buffer, options, etc. anything that does not need the DOM
private attachedTerminal: AttachedTerminal;
open(element: HTMLElement): void {
if (!attachedTerminal) {
attachedTerminal = new AttachedTerminal(this, element);
}
}
reset(): void {
console.log('reset');
this.emit('postreset');
}
}
class AttachedTerminal {
constructor(private _terminal: ITerminal, element: HTMLElement) {
this.on('postreset', reset);
}
reset(): void {
// DOM access
}
} |
How about separating it into |
@mofux isn't extracting all the model information a separate (still useful) refactor aiming at solving a different problem? I don't think it would solve the problem of ensuring bad DOM access doesn't happen. Also just now I needed to disable a new test because open in a limited test DOM environment is horrible, was wishing the selection code was separate too microsoft/vscode@43edf97 |
If I understand the problem correctly, the terminal currently assumes that it is attached to the DOM once the This is bad because these services have a direct dependency to the DOM. It would be much better if things like selection, links etc would be tracked on the model and then would be drawn during the render phase using the information stored with the model. That would also mean that things would continue to work if no renderer is attached (because only the renderer would then have a dependency to the DOM, but not the model). The monaco editor uses a very similar approach, where all the state is saved with the TextModel, and the editor itself is only a rendering component that draws the model. Things like linkifiers or syntax highlighters add "decorations" to the model. The renderer later on takes those decorations and transform them into spans with classes that have the styling instructions for those decoration types. In our case, decorations could look like this (every buffer has its own decorations): {
// range: [x1, y1, x2, y2]
decorations: [
// foreground color decoration
{ type: 'fg', range: [0,0,0,10], color: 'red' },
// selection decoration
{ type: 'selection', range: [0,2,12,4] },
// background color decoration
{ type: 'bg', range: [0,0,0,84], color: 'blue' },
// link decoration
{ type: 'link', range: [10,0,5,1] }
]
} Based on these decorations it is very easy to write a generic renderer that doesn't need to know about the linkifier or the selection manager at all, it only renders the decorations. We'll want to maintain these decorations for the whole scrollback + screen area, so we only have to calculate them once (and every time that particular buffer line is altered). Hope you can see where I'm going with this 😅 |
Duplicate #1507 |
These happen periodically #1379 and are difficult to catch. A better way to handle this would be to completely isolate the headless terminal and DOM-access terminal.
The text was updated successfully, but these errors were encountered: