Skip to content
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

complete support of webviews #6465

Merged
merged 21 commits into from
Nov 22, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
15c06a8
[webview] delete 'vscode.previewHtml' command
akosyakov Oct 28, 2019
95b9691
[webview] fix #5648: integrate webviews with the application shell
akosyakov Oct 28, 2019
5a1b062
[webview] secure webviews
akosyakov Oct 29, 2019
6f4879e
[webview] fix #5647: restore webviews
akosyakov Oct 30, 2019
0b43c51
[webview] open links via OpenerService
akosyakov Oct 30, 2019
a000a27
[webview] fix #5521: emulate webview focus when something is focused …
akosyakov Oct 30, 2019
64c49f8
[webview] fix #5786: unify the icon path resolution
akosyakov Oct 31, 2019
93e23c2
[wbview] fix #5518: apply theming
akosyakov Oct 31, 2019
766dfd7
[theming] #4831: color contribution point
akosyakov Oct 31, 2019
2e7fca7
[webview] retain iframe when widget is hidden only when `retainContex…
akosyakov Nov 6, 2019
ff9a58c
[plugin]: support webview port mapping and external URIs
akosyakov Nov 7, 2019
6e84132
[plugin] register command open handler
akosyakov Nov 7, 2019
ef478b4
[webview] compliance to vscode webview api tests 1.40.0
akosyakov Nov 8, 2019
e462ae7
[webivew] clarify breaking changes for adopters
akosyakov Nov 14, 2019
4a89eff
[maximized] fix #6453: send attach/detach messages to widgets
akosyakov Nov 14, 2019
cabe495
[webview] cross instance browser based resource caching
akosyakov Nov 16, 2019
73f7c56
[plugin] move vscode built-ins translation to manifest loading
akosyakov Nov 18, 2019
fa9e20e
[core] open mailto with an external window
akosyakov Nov 18, 2019
7d3856f
[webview] treat vscode-resource equally to theia-resource
akosyakov Nov 18, 2019
6831b91
[webview] translate http vscode-resource links to file links
akosyakov Nov 19, 2019
98e8bd9
[webview] fix computation of view columns
akosyakov Nov 19, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions .gitpod.yml
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
image:
file: .gitpod.dockerfile
ports:
- port: 3000
- port: 3000 # Theia
- port: 3030 # VS Code extension tests
- port: 9339 # Node.js debug port
onOpen: ignore
- port: 6080
onOpen: ignore
- port: 5900
Expand All @@ -10,7 +13,7 @@ tasks:
- init: yarn
command: >
jwm &
yarn --cwd examples/browser start ../..
yarn --cwd examples/browser start ../.. --hostname=0.0.0.0
github:
prebuilds:
pullRequestsFromForks: true
12 changes: 8 additions & 4 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
"--no-app-auto-install"
],
"env": {
"NODE_ENV": "development"
"NODE_ENV": "development",
"THEIA_WEBVIEW_EXTERNAL_ENDPOINT": "${env:THEIA_WEBVIEW_EXTERNAL_ENDPOINT}"
},
"sourceMaps": true,
"outFiles": [
Expand Down Expand Up @@ -63,7 +64,8 @@
"--hosted-plugin-inspect=9339"
],
"env": {
"NODE_ENV": "development"
"NODE_ENV": "development",
"THEIA_WEBVIEW_EXTERNAL_ENDPOINT": "${env:THEIA_WEBVIEW_EXTERNAL_ENDPOINT}"
},
"sourceMaps": true,
"outFiles": [
Expand Down Expand Up @@ -104,7 +106,8 @@
"--no-app-auto-install"
],
"env": {
"NODE_ENV": "development"
"NODE_ENV": "development",
"THEIA_WEBVIEW_EXTERNAL_ENDPOINT": "${env:THEIA_WEBVIEW_EXTERNAL_ENDPOINT}"
},
"sourceMaps": true,
"outFiles": [
Expand Down Expand Up @@ -166,7 +169,8 @@
"--hosted-plugin-inspect=9339"
],
"env": {
"THEIA_DEFAULT_PLUGINS": "local-dir:${workspaceFolder}/plugins"
"THEIA_DEFAULT_PLUGINS": "local-dir:${workspaceFolder}/plugins",
"THEIA_WEBVIEW_EXTERNAL_ENDPOINT": "${env:THEIA_WEBVIEW_EXTERNAL_ENDPOINT}"
},
"stopOnEntry": false,
"sourceMaps": true,
Expand Down
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,29 @@ Breaking changes:
- [core] renamed preference `list.openMode` to `workbench.list.openMode` [#6481](https://github.com/eclipse-theia/theia/pull/6481)
- [task] changed `TaskSchemaUpdater.update()` from asynchronous to synchronous [#6483](https://github.com/eclipse-theia/theia/pull/6483)
- [monaco] monaco prefix has been removed from commands [#5590](https://github.com/eclipse-theia/theia/pull/5590)
- [plugin] webviews are reimplemented to align with [VS Code browser implementation](https://blog.mattbierner.com/vscode-webview-web-learnings/) [#6465](https://github.com/eclipse-theia/theia/pull/6465)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@benoitf @olexii4 @evidolob @mmorhun I've tried to capture breaking changes required to align with VS Code here. I hope it would be helpful to adopt them in Che. If some APIs don't have enough information please do follow-up PRs during migration.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@amiramw @idoprz I'm not sure how it is important for you. Just pinging, since you run plugins in sidecar containers as well.

- Security: `vscode.previewHTML` is removed, see https://code.visualstudio.com/updates/v1_33#_removing-the-vscodepreviewhtml-command
- Security: Before all webviews were deployed on [the same origin](https://developer.mozilla.org/en-US/docs/Web/Security/Same-origin_policy)
allowing them to break out and manipulate shared data as cookies, local storage or even start service workers
for the main window as well as for each other. Now each webview will be deployed on own origin by default.
- Webview origin pattern can be configured with `THEIA_WEBVIEW_EXTERNAL_ENDPOINT` env variable. The default value is `{{uuid}}.webview.{{hostname}}`.
Here `{{uuid}}` and `{{hostname}}` are placeholders which get replaced at runtime with proper webview uuid
and [hostname](https://developer.mozilla.org/en-US/docs/Web/API/HTMLHyperlinkElementUtils/hostname) correspondingly.
- To switch to unsecure mode as before configure `THEIA_WEBVIEW_EXTERNAL_ENDPOINT` with `{{hostname}}` as a value.
You can also drop `{{uuid}}.` prefix, in this case, webviews still will be able to access each other but not the main window.
- Remote: Local URIs are resolved by default to the host serving Theia.
If you want to resolve to another host or change how remote URIs are constructed then
implement [ExternalUriService.resolve](./packages/core/src/browser/external-uri-service.ts) in a frontend module.
- Content loading: Webview HTTP endpoint is removed. Content loaded via [WebviewResourceLoader](./packages/plugin-ext/src/main/common/webview-protocol.ts) JSON-RPC service
with properly preserved resource URIs. Content is only loaded if it's allowed by WebviewOptions.localResourceRoots, otherwise, the service won't be called.
If you want to customize content loading then implement [WebviewResourceLoaderImpl](packages/plugin-ext/src/main/node/webview-resource-loader-impl.ts) in a backend module.
- Theming: Theia styles are not applied to webviews anymore
instead [VS Code way of styling](https://code.visualstudio.com/api/extension-guides/webview#theming-webview-content) should be used.
VS Code color variables also available with `--theia` prefix.
- Testing: Webview can work only in secure context because they rely on service workers to load local content and redirect local to remote requests.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@AndrienkoAleksandr added a section here about local testing

Most browsers define a page as served from secure context if its url has `https` scheme. For local testing `localhost` is treated as a secure context as well.
Unfortunately, it does not work nicely in FireFox, since it does not treat subdomains of localhost as secure as well, compare to Chrome.
If you want to test with FireFox you can configure it as described [here](https://github.com/eclipse-theia/theia/pull/6465#issuecomment-556443218).

## v0.12.0

Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"typings": "lib/common/index.d.ts",
"dependencies": {
"@babel/runtime": "^7.5.5",
"@phosphor/widgets": "^1.5.0",
"@phosphor/widgets": "^1.9.3",
"@primer/octicons-react": "^9.0.0",
"@theia/application-package": "^0.12.0",
"@types/body-parser": "^1.16.4",
Expand Down
75 changes: 75 additions & 0 deletions packages/core/src/browser/color-application-contribution.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
/********************************************************************************
* 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 { injectable, inject, named } from 'inversify';
import { ColorRegistry } from './color-registry';
import { Emitter } from '../common/event';
import { ThemeService } from './theming';
import { FrontendApplicationContribution } from './frontend-application';
import { ContributionProvider } from '../common/contribution-provider';
import { Disposable, DisposableCollection } from '../common/disposable';

export const ColorContribution = Symbol('ColorContribution');
export interface ColorContribution {
registerColors(colors: ColorRegistry): void;
}

@injectable()
export class ColorApplicationContribution implements FrontendApplicationContribution {

protected readonly onDidChangeEmitter = new Emitter<void>();
readonly onDidChange = this.onDidChangeEmitter.event;

@inject(ColorRegistry)
protected readonly colors: ColorRegistry;

@inject(ContributionProvider) @named(ColorContribution)
protected readonly colorContributions: ContributionProvider<ColorContribution>;

onStart(): void {
for (const contribution of this.colorContributions.getContributions()) {
contribution.registerColors(this.colors);
}

this.update();
ThemeService.get().onThemeChange(() => this.update());
}

protected readonly toUpdate = new DisposableCollection();
protected update(): void {
if (!document) {
return;
}
this.toUpdate.dispose();
const theme = 'theia-' + ThemeService.get().getCurrentTheme().type;
document.body.classList.add(theme);
this.toUpdate.push(Disposable.create(() => document.body.classList.remove(theme)));

const documentElement = document.documentElement;
if (documentElement) {
for (const id of this.colors.getColors()) {
const color = this.colors.getCurrentColor(id);
if (color) {
const propertyName = `--theia-${id.replace('.', '-')}`;
documentElement.style.setProperty(propertyName, color);
this.toUpdate.push(Disposable.create(() => documentElement.style.removeProperty(propertyName)));
}
}
}
this.onDidChangeEmitter.fire(undefined);
}

}
47 changes: 47 additions & 0 deletions packages/core/src/browser/color-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/********************************************************************************
* 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 { injectable } from 'inversify';
import { Disposable } from '../common/disposable';

export interface ColorDefaults {
light?: string
dark?: string
hc?: string
}

export interface ColorOptions {
defaults?: ColorDefaults
description: string
}

/**
* It should be implemented by an extension, e.g. by the monaco extension.
*/
@injectable()
export class ColorRegistry {

*getColors(): IterableIterator<string> { }

getCurrentColor(id: string): string | undefined {
return undefined;
}

register(id: string, options: ColorOptions): Disposable {
return Disposable.NULL;
}

}
6 changes: 6 additions & 0 deletions packages/core/src/browser/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export class Endpoint {
}

protected get host(): string {
if (this.options.host) {
return this.options.host;
}
if (this.location.host) {
return this.location.host;
}
Expand All @@ -77,6 +80,9 @@ export class Endpoint {
}

protected get wsScheme(): string {
if (this.options.wsScheme) {
return this.options.wsScheme;
}
return this.httpScheme === Endpoint.PROTO_HTTPS ? Endpoint.PROTO_WSS : Endpoint.PROTO_WS;
}

Expand Down
64 changes: 64 additions & 0 deletions packages/core/src/browser/external-uri-service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/********************************************************************************
* 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 { injectable } from 'inversify';
import URI from '../common/uri';
import { MaybePromise } from '../common/types';
import { Endpoint } from './endpoint';

@injectable()
export class ExternalUriService {

/**
* Maps local to remote URLs.
* Should be no-op if the given URL is not a localhost URL.
*
* By default maps to an origin serving Theia.
*
* Use `parseLocalhost` to retrive localhost address and port information.
*/
resolve(uri: URI): MaybePromise<URI> {
const localhost = this.parseLocalhost(uri);
if (localhost) {
return this.toRemoteUrl(uri, localhost);
}
return uri;
}

protected toRemoteUrl(uri: URI, localhost: { address: string, port: number }): URI {
paul-marechal marked this conversation as resolved.
Show resolved Hide resolved
const host = this.toRemoteHost(localhost);
return new Endpoint({ host }).getRestUrl().withPath(uri.path).withFragment(uri.fragment).withQuery(uri.query);
}

protected toRemoteHost(localhost: { address: string, port: number }): string {
paul-marechal marked this conversation as resolved.
Show resolved Hide resolved
return `${window.location.hostname}:${localhost.port}`;
}

parseLocalhost(uri: URI): { address: string, port: number } | undefined {
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return undefined;
}
const localhostMatch = /^(localhost|127\.0\.0\.1|0\.0\.0\.0):(\d+)$/.exec(uri.authority);
if (!localhostMatch) {
return undefined;
}
return {
address: localhostMatch[1],
port: +localhostMatch[2],
};
}

}
10 changes: 10 additions & 0 deletions packages/core/src/browser/frontend-application-module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,9 @@ import { ProgressStatusBarItem } from './progress-status-bar-item';
import { TabBarDecoratorService, TabBarDecorator } from './shell/tab-bar-decorator';
import { ContextMenuContext } from './menu/context-menu-context';
import { bindResourceProvider, bindMessageService, bindPreferenceService } from './frontend-application-bindings';
import { ColorRegistry } from './color-registry';
import { ColorContribution, ColorApplicationContribution } from './color-application-contribution';
import { ExternalUriService } from './external-uri-service';

export { bindResourceProvider, bindMessageService, bindPreferenceService };

Expand All @@ -91,6 +94,11 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
themeService.register(...BuiltinThemeProvider.themes);
themeService.startupTheme();

bind(ColorRegistry).toSelf().inSingletonScope();
bindContributionProvider(bind, ColorContribution);
bind(ColorApplicationContribution).toSelf().inSingletonScope();
bind(FrontendApplicationContribution).toService(ColorApplicationContribution);

bind(FrontendApplication).toSelf().inSingletonScope();
bind(FrontendApplicationStateService).toSelf().inSingletonScope();
bind(DefaultFrontendApplicationContribution).toSelf();
Expand Down Expand Up @@ -131,6 +139,8 @@ export const frontendApplicationModule = new ContainerModule((bind, unbind, isBo
bindContributionProvider(bind, OpenHandler);
bind(DefaultOpenerService).toSelf().inSingletonScope();
bind(OpenerService).toService(DefaultOpenerService);

bind(ExternalUriService).toSelf().inSingletonScope();
bind(HttpOpenHandler).toSelf().inSingletonScope();
bind(OpenHandler).toService(HttpOpenHandler);

Expand Down
11 changes: 8 additions & 3 deletions packages/core/src/browser/http-open-handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { injectable, inject } from 'inversify';
import URI from '../common/uri';
import { OpenHandler } from './opener-service';
import { WindowService } from './window/window-service';
import { ExternalUriService } from './external-uri-service';

@injectable()
export class HttpOpenHandler implements OpenHandler {
Expand All @@ -27,12 +28,16 @@ export class HttpOpenHandler implements OpenHandler {
@inject(WindowService)
protected readonly windowService: WindowService;

@inject(ExternalUriService)
protected readonly externalUriService: ExternalUriService;

canHandle(uri: URI): number {
return uri.scheme.startsWith('http') ? 500 : 0;
return (uri.scheme.startsWith('http') || uri.scheme.startsWith('mailto')) ? 500 : 0;
}

open(uri: URI): Window | undefined {
return this.windowService.openNewWindow(uri.toString(true), { external: true });
async open(uri: URI): Promise<Window | undefined> {
const resolvedUri = await this.externalUriService.resolve(uri);
return this.windowService.openNewWindow(resolvedUri.toString(true), { external: true });
}

}
5 changes: 5 additions & 0 deletions packages/core/src/browser/shell/application-shell.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1005,6 +1005,11 @@ export class ApplicationShell extends Widget {
private readonly toDisposeOnActivationCheck = new DisposableCollection();
private assertActivated(widget: Widget): void {
this.toDisposeOnActivationCheck.dispose();

const onDispose = () => this.toDisposeOnActivationCheck.dispose();
widget.disposed.connect(onDispose);
this.toDisposeOnActivationCheck.push(Disposable.create(() => widget.disposed.disconnect(onDispose)));

let start = 0;
const step: FrameRequestCallback = timestamp => {
if (document.activeElement && widget.node.contains(document.activeElement)) {
Expand Down
3 changes: 3 additions & 0 deletions packages/core/src/browser/shell/shell-layout-restorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -333,6 +333,9 @@ export class ShellLayoutRestorer implements CommandContribution {
this.logger.warn(`Couldn't restore widget state for ${widget.id}. Error: ${e} `);
}
}
if (widget.isDisposed) {
return undefined;
}
return widget;
} catch (e) {
if (ApplicationShellLayoutMigrationError.is(e)) {
Expand Down
Loading