-
Notifications
You must be signed in to change notification settings - Fork 2.5k
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
Do not store webview if there is no corresponding serializer #8680
Do not store webview if there is no corresponding serializer #8680
Conversation
a7bf3ba
to
5927c71
Compare
24a52b8
to
9b05789
Compare
@akosyakov what do you think about not to store the webview at all in a case when there is no webview serializer. Instead of saving it with |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should handle the case where widgets cannot be recreated. For example, what if a plugin is updated and no longer contributes a webview serializer?
ShellLayoutRestorer#convertToWidget
can return undefined
and we need to handle that.
Agree with you. I think thew we need to handle both cases:
|
@tsmaeder done. I've handled a case when the widget cannot be recreated. |
@@ -636,6 +636,12 @@ export class ApplicationShell extends Widget { | |||
// Proceed with the main panel once all others are set up | |||
await this.bottomPanelState.pendingUpdate; | |||
if (mainPanel) { | |||
// Array of widgets must not contain items with `undefined` value as it breaks the restoring of the layout |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
T.b.h I liked the previous version better: can't we just filter the array in ShellLayoutRestorer#parse()
after we've created all the widgets?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I like the old version better because the filtering is done closer to where the widget is created (or not created). Simpler to understand when looking at ShellLayoutRestorere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This what I wanted to do first, but in this case it will not work.
In ShellLayoutRestorer#parse()
each widget is created in a separate promise, which is called already after returning array of widgets.
theia/packages/core/src/browser/shell/shell-layout-restorer.ts
Lines 272 to 279 in b54e9eb
const widgets: (Widget | undefined)[] = []; | |
const descs = (value as WidgetDescription[]); | |
for (let i = 0; i < descs.length; i++) { | |
parseContext.push(async context => { | |
widgets[i] = await this.convertToWidget(descs[i], context); | |
}); | |
} | |
return widgets; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We wait can for all promises to be fullfilled: we should not return before the widgets
array is fully populated anyway:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In previous version we had a problem with order of opened editors. It's because the time for restoring/creating widgets for different files is different and the created widget was always inserted the first.
Layout data is created in ShellLayoutRestorer#inflate
function.
At the beginning of this function we parse layout string representation to have LayoutData
.
const layout = this.parse<ApplicationShell.LayoutData>(layoutData, parseContext); |
But at that moment arrays of widgets are empty for all panels.
At the end we call parseContext.inflate(context)
. It runs all the promises, which create the widgets and insert them in the array.
await parseContext.inflate(context); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tsmaeder @spoenemann I have another idea is to move filtering at the end of ShellLayoutRestorer.ParseContext#inflate
.
ParseContext
has all the arrays to be filtered. So, it will work for all the widget panels.
export class ParseContext {
protected readonly toInflate: Inflate[] = [];
protected readonly toFilter: Widgets[] = [];
newArray(): Widgets {
const array: Widgets = [];
this.toFilter.push(array);
return array;
}
async inflate(context: InflateContext): Promise<void> {
const pending: Promise<void>[] = [];
while (this.toInflate.length) {
pending.push(this.toInflate.pop()!(context));
}
await Promise.all(pending);
if (this.toFilter.length) {
this.toFilter.forEach(array => {
const filtered = array.filter(value => value !== undefined);
array.splice(0, array.length, ...filtered);
});
}
}
}
export type WidgetArray = (Widget | undefined)[];
Then ShellLayoutRestorer#parse
can ask for a widget array, which will be filtered after creating the widgets
const widgets = parseContext.newArray();
const descs = (value as WidgetDescription[]);
for (let i = 0; i < descs.length; i++) {
parseContext.push(async context => {
widgets[i] = await this.convertToWidget(descs[i], context);
});
}
return widgets;
I have just checked this approach, it works well.
If it's OK for you, I will update the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sounds good.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reworked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I really don't understand why this needs to be so complicated: just wait for all the promises to resolve and then filter out the undefined values from the array in ShellLayoutRestorer#parse(). It's better code-quality-wise because it does the filtering closer to where the nulls are created and there is no if conditional expression around the filtering. Please convince me!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's such confusing because promises for the widgets are created in one place and then resolved in another.
Why do we need so?
It could happen that your Theia is updated or downgraded and the layout cannot be restored properly.
That requires to do some checks for the layout version and probably transform the layout or update the state of some widgets before creating their instances.
Just take a look at ShellLayoutRestorer#inflate()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've tested both use-cases on the provided plug-in https://github.com/vitaliy-guliy/greeting-extension:
- when a contributed WebView has a related registered serializer
- when a contributed WebView without a registered serializer
All widgets restore works well. In both use cases, the behavior is the same as in VS Code 👍
So, I'm +1 for this patch.
It would be great to have another pair of eyes look at it as the proposed changes affect one of the essential part - the application shell.
cc @eclipse-theia/core
if (this.toFilter.length) { | ||
this.toFilter.forEach(array => { | ||
const filtered = array.filter(value => value !== undefined); | ||
array.splice(0, array.length, ...filtered); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance: Do this splice
only if filtered.length
is smaller than array.length
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Performance: Do this
splice
only iffiltered.length
is smaller thanarray.length
Interesting, I didn't know about it before. In fact the array will not have so much items to work very slow.
But thinking about performance. To avoid checking for an array length, maybe it's better to reset the array at all and then push all the items..
array.length = 0;
array.push(...filtered);
WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That might be an alternative to splice
, but checking length
for sure has lower cost than resetting the array. And resetting is only necessary if the length has changed.
Another alternative would be this:
for (let i = 0; i < array.length; i++) {
if (array[i] === undefined) {
array.splice(i--, 1);
}
}
If there are only few undefined elements, this variant might be even better because it does not duplicate the array.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In 99% array will not contain undefined elements, so this alternative looks much better than rebuilding the array each time when restoring the layout.
Thanks for this.
I've updated and squashed the PR.
Signed-off-by: Vitaliy Gulyy <[email protected]>
611ee92
to
e25d8d9
Compare
I'm going to merge this PR tomorrow if there are no objections. |
Signed-off-by: Vitaliy Gulyy [email protected]
This PR is a duplicate of previously closed #8473
What it does
Video demonstrating bug https://www.youtube.com/watch?v=9ir1hTov350
Fixes #8620 and #6877
This is another approach in comparing with #8621
In #8621 Theia stores the empty state for the webview and tries to find the serializer when restoring the state.
In this PR, Theia checks for the theia.WebviewPanelSerializer, and does not store the webview to the layout at all if the serializer is not found.
How to test
To test it you need to have a plugin which opens a webview, but don't register
vscode.WebviewPanelSerializer
for this webview.I used this one https://github.com/vitaliy-guliy/greeting-extension, which opens a Greeting webview each time when Theia starts.
Steps to reproduce:
Opened flies must be restored, the order of files must be saved. As the webview is not persisted, after reloading it will be opened again next to active tab.
Follow 'Steps to reproduce' in the original issue #8620
Review checklist
Reminder for reviewers