Skip to content

Commit

Permalink
[webview] compliance to vscode webview api tests 1.40.0
Browse files Browse the repository at this point in the history
Signed-off-by: Anton Kosyakov <anton.kosyakov@typefox.io>
  • Loading branch information
akosyakov committed Nov 13, 2019
1 parent 9e0d246 commit b961ceb
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 30 deletions.
21 changes: 12 additions & 9 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
image:
file: .gitpod.dockerfile
ports:
- port: 3000
- port: 6080
onOpen: ignore
- port: 5900
onOpen: ignore
- port: 3000 # Theia
- port: 3030 # VS Code extension tests
- port: 9339 # Node.js debug port
onOpen: ignore
- port: 6080
onOpen: ignore
- port: 5900
onOpen: ignore
tasks:
- init: yarn
command: >
jwm &
yarn --cwd examples/browser start ../..
- init: yarn
command: >
jwm &
yarn --cwd examples/browser start ../..
github:
prebuilds:
pullRequestsFromForks: true
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ import { WebviewWidget, WebviewWidgetIdentifier, WebviewWidgetExternalEndpoint }
import { WebviewEnvironment } from './webview/webview-environment';
import { WebviewThemeDataProvider } from './webview/webview-theme-data-provider';
import { PluginCommandOpenHandler } from './plugin-command-open-handler';
import { bindWebviewPreferences } from './webview/webview-preferences';

export default new ContainerModule((bind, unbind, isBound, rebind) => {

Expand Down Expand Up @@ -153,6 +154,7 @@ export default new ContainerModule((bind, unbind, isBound, rebind) => {
bind(PluginCommandOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(PluginCommandOpenHandler);

bindWebviewPreferences(bind);
bind(WebviewEnvironment).toSelf().inSingletonScope();
bind(WebviewThemeDataProvider).toSelf().inSingletonScope();
bind(WebviewWidget).toSelf();
Expand Down
5 changes: 5 additions & 0 deletions packages/plugin-ext/src/main/browser/style/webview.css
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

.theia-webview.p-mod-hidden {
visibility: hidden;
display: flex !important;
}

.theia-webview {
display: flex;
flex-direction: column;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/********************************************************************************
* Copyright (C) 2019 TypeFox and others.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* This Source Code may also be made available under the following Secondary
* Licenses when the conditions for such availability set forth in the Eclipse
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
* with the GNU Classpath Exception which is available at
* https://www.gnu.org/software/classpath/license.html.
*
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
********************************************************************************/

import { interfaces } from 'inversify';
import {
createPreferenceProxy,
PreferenceProxy,
PreferenceService,
PreferenceContribution,
PreferenceSchema
} from '@theia/core/lib/browser/preferences';

export const WebviewConfigSchema: PreferenceSchema = {
'type': 'object',
'properties': {
'webview.trace': {
'type': 'string',
'enum': ['off', 'on', 'verbose'],
'description': 'Controls communication tracing with webviews.',
'default': 'off'
}
}
};

export interface WebviewConfiguration {
'webview.trace': 'off' | 'on' | 'verbose'
}

export const WebviewPreferences = Symbol('WebviewPreferences');
export type WebviewPreferences = PreferenceProxy<WebviewConfiguration>;

export function createWebviewPreferences(preferences: PreferenceService): WebviewPreferences {
return createPreferenceProxy(preferences, WebviewConfigSchema);
}

export function bindWebviewPreferences(bind: interfaces.Bind): void {
bind(WebviewPreferences).toDynamicValue(ctx => {
const preferences = ctx.container.get<PreferenceService>(PreferenceService);
return createWebviewPreferences(preferences);
});

bind(PreferenceContribution).toConstantValue({ schema: WebviewConfigSchema });
}
61 changes: 57 additions & 4 deletions packages/plugin-ext/src/main/browser/webview/webview.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ import { PluginSharedStyle } from '../plugin-shared-style';
import { BuiltinThemeProvider } from '@theia/core/lib/browser/theming';
import { WebviewThemeDataProvider } from './webview-theme-data-provider';
import { ExternalUriService } from '@theia/core/lib/browser/external-uri-service';
import { OutputChannelManager } from '@theia/output/lib/common/output-channel';
import { WebviewPreferences } from './webview-preferences';

// tslint:disable:no-any

Expand Down Expand Up @@ -123,6 +125,12 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
@inject(ExternalUriService)
protected readonly externalUriService: ExternalUriService;

@inject(OutputChannelManager)
protected readonly outputManager: OutputChannelManager;

@inject(WebviewPreferences)
protected readonly preferences: WebviewPreferences;

viewState: WebviewPanelViewState = {
visible: false,
active: false,
Expand All @@ -148,8 +156,10 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {

protected readonly onMessageEmitter = new Emitter<any>();
readonly onMessage = this.onMessageEmitter.event;
protected readonly pendingMessages: any[] = [];

protected readonly toHide = new DisposableCollection();
protected hideTimeout: any | number | undefined;

@postConstruct()
protected init(): void {
Expand Down Expand Up @@ -180,6 +190,8 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
protected onBeforeAttach(msg: Message): void {
super.onBeforeAttach(msg);
this.doShow();
// iframe has to be reloaded when moved to another DOM element
this.toDisposeOnDetach.push(Disposable.create(() => this.forceHide()));
}

protected onBeforeShow(msg: Message): void {
Expand All @@ -194,11 +206,22 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {

protected doHide(): void {
if (this.options.retainContextWhenHidden !== true) {
this.toHide.dispose();
if (this.hideTimeout === undefined) {
// avoid removing iframe if a widget moved quickly
this.hideTimeout = setTimeout(() => this.forceHide(), 50);
}
}
}

protected forceHide(): void {
clearTimeout(this.hideTimeout);
this.hideTimeout = undefined;
this.toHide.dispose();
}

protected doShow(): void {
clearTimeout(this.hideTimeout);
this.hideTimeout = undefined;
if (!this.toHide.disposed) {
return;
}
Expand All @@ -224,7 +247,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
const ready = new Deferred<void>();
ready.promise.then(() => oldReady.resolve());
this.ready = ready;
this.doUpdateContent();
this.toHide.push(Disposable.create(() => this.ready = new Deferred<void>()));
const subscription = this.on(WebviewMessageChannels.webviewReady, () => {
subscription.dispose();
ready.resolve();
Expand Down Expand Up @@ -262,6 +285,11 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {

this.style();
this.toHide.push(this.themeDataProvider.onDidChangeThemeData(() => this.style()));

this.doUpdateContent();
while (this.pendingMessages.length) {
this.sendMessage(this.pendingMessages.shift());
}
}

protected async loadLocalhost(origin: string): Promise<void> {
Expand Down Expand Up @@ -394,7 +422,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
if (!new URI(root).path.isEqualOrParent(normalizedUri.path)) {
continue;
}
const { content } = await this.fileSystem.resolveContent(normalizedUri.toString());
const { content } = await this.fileSystem.resolveContent(normalizedUri.toString(), { encoding: 'ISO-8859-1' });
return this.doSend('did-load-resource', {
status: 200,
path: requestPath,
Expand Down Expand Up @@ -429,7 +457,11 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
}

sendMessage(data: any): void {
this.doSend('message', data);
if (this.element) {
this.doSend('message', data);
} else {
this.pendingMessages.push(data);
}
}

protected doUpdateContent(): void {
Expand Down Expand Up @@ -462,6 +494,9 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
}

protected async doSend(channel: string, data?: any): Promise<void> {
if (!this.element) {
return;
}
try {
await this.ready.promise;
this.postMessage(channel, data);
Expand All @@ -472,6 +507,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {

protected postMessage(channel: string, data?: any): void {
if (this.element) {
this.trace('out', channel, data);
this.element.contentWindow!.postMessage({ channel, args: data }, '*');
}
}
Expand All @@ -482,6 +518,7 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
return;
}
if (e.data.channel === channel) {
this.trace('in', e.data.channel, e.data.data);
handler(e.data.data);
}
};
Expand All @@ -491,6 +528,22 @@ export class WebviewWidget extends BaseWidget implements StatefulWidget {
);
}

protected trace(kind: 'in' | 'out', channel: string, data?: any): void {
const value = this.preferences['webview.trace'];
if (value === 'off') {
return;
}
const output = this.outputManager.getChannel('webviews');
output.append('\n' + this.identifier.id);
output.append(kind === 'out' ? ' => ' : ' <= ');
output.append(channel);
if (value === 'verbose') {
if (data) {
output.append('\n' + JSON.stringify(data, undefined, 2));
}
}
}

}
export namespace WebviewWidget {
export namespace Styles {
Expand Down
12 changes: 3 additions & 9 deletions packages/plugin-ext/src/main/browser/webviews-main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
localResourceRoots: localResourceRoots && localResourceRoots.map(root => root.toString()),
...contentOptions
});
this.addOrReattachWidget(panelId, showOptions);
this.addOrReattachWidget(view, showOptions);
}

protected hookWebview(view: WebviewWidget): void {
Expand All @@ -86,11 +86,7 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
});
}

private async addOrReattachWidget(handle: string, showOptions: WebviewPanelShowOptions): Promise<void> {
const widget = await this.tryGetWebview(handle);
if (!widget) {
return;
}
private addOrReattachWidget(widget: WebviewWidget, showOptions: WebviewPanelShowOptions): void {
const widgetOptions: ApplicationShell.WidgetOptions = { area: showOptions.area ? showOptions.area : 'main' };

let mode = 'open-to-right';
Expand Down Expand Up @@ -123,8 +119,6 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
} else {
this.shell.activateWidget(widget.id);
}
this.updateViewState(widget, showOptions.viewColumn);
this.updateViewStates();
}

async $disposeWebview(handle: string): Promise<void> {
Expand All @@ -144,7 +138,7 @@ export class WebviewsMainImpl implements WebviewsMain, Disposable {
const columnIds = showOptions.viewColumn ? this.viewColumnService.getViewColumnIds(showOptions.viewColumn) : [];
const area = this.shell.getAreaFor(widget);
if (columnIds.indexOf(widget.id) === -1 || area !== showOptions.area) {
this.addOrReattachWidget(widget.identifier.id, showOptions);
this.addOrReattachWidget(widget, showOptions);
return;
}
}
Expand Down
20 changes: 12 additions & 8 deletions packages/plugin-ext/src/plugin/webviews.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class WebviewsExtImpl implements WebviewsExt {
}
const webviewShowOptions = toWebviewPanelShowOptions(showOptions);
const viewId = v4();
this.proxy.$createWebviewPanel(viewId, viewType, title, webviewShowOptions, options);
this.proxy.$createWebviewPanel(viewId, viewType, title, webviewShowOptions, WebviewImpl.toWebviewOptions(options, this.workspace, plugin));

const webview = new WebviewImpl(viewId, this.proxy, options, this.initData, this.workspace, plugin);
const panel = new WebviewPanelImpl(viewId, this.proxy, viewType, title, webviewShowOptions, options, webview);
Expand Down Expand Up @@ -204,13 +204,7 @@ export class WebviewImpl implements theia.Webview {

set options(newOptions: theia.WebviewOptions) {
this.checkIsDisposed();
this.proxy.$setOptions(this.viewId, {
...newOptions,
localResourceRoots: newOptions.localResourceRoots || [
...(this.workspace.workspaceFolders || []).map(x => x.uri),
URI.file(this.plugin.pluginPath)
]
});
this.proxy.$setOptions(this.viewId, WebviewImpl.toWebviewOptions(newOptions, this.workspace, this.plugin));
this._options = newOptions;
}

Expand All @@ -225,6 +219,16 @@ export class WebviewImpl implements theia.Webview {
throw new Error('This Webview is disposed!');
}
}

static toWebviewOptions(options: theia.WebviewOptions, workspace: WorkspaceExtImpl, plugin: Plugin): theia.WebviewOptions {
return {
...options,
localResourceRoots: options.localResourceRoots || [
...(workspace.workspaceFolders || []).map(x => x.uri),
URI.file(plugin.pluginPath)
]
};
}
}

export class WebviewPanelImpl implements theia.WebviewPanel {
Expand Down

0 comments on commit b961ceb

Please sign in to comment.