Skip to content

Commit d0481dc

Browse files
committed
web - move selfhost pieces out of workbench
1 parent 0030e6e commit d0481dc

File tree

8 files changed

+342
-211
lines changed

8 files changed

+342
-211
lines changed

build/gulpfile.vscode.web.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,8 @@ const nodeModules = Object.keys(product.dependencies || {})
3434
const vscodeWebResources = [
3535

3636
// Workbench
37-
'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png,html}',
37+
'out-build/vs/{base,platform,editor,workbench}/**/*.{svg,png}',
38+
'out-build/vs/code/browser/workbench/workbench.html',
3839
'out-build/vs/base/browser/ui/octiconLabel/octicons/**',
3940
'out-build/vs/**/markdown.css',
4041

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { IWorkbenchConstructionOptions, create } from 'vs/workbench/workbench.web.api';
7+
import { IURLCallbackProvider } from 'vs/workbench/services/url/browser/urlService';
8+
import { Event, Emitter } from 'vs/base/common/event';
9+
import { URI, UriComponents } from 'vs/base/common/uri';
10+
import { generateUuid } from 'vs/base/common/uuid';
11+
import { CancellationToken } from 'vs/base/common/cancellation';
12+
import { streamToBuffer } from 'vs/base/common/buffer';
13+
import { Disposable } from 'vs/base/common/lifecycle';
14+
import { request } from 'vs/base/parts/request/browser/request';
15+
import { ICredentialsProvider } from 'vs/workbench/services/credentials/browser/credentialsService';
16+
17+
export function main(): void {
18+
const options: IWorkbenchConstructionOptions = JSON.parse(document.getElementById('vscode-workbench-web-configuration')!.getAttribute('data-settings')!);
19+
options.urlCallbackProvider = new PollingURLCallbackProvider();
20+
options.credentialsProvider = new LocalStorageCredentialsProvider();
21+
22+
create(document.body, options);
23+
}
24+
25+
interface ICredential {
26+
service: string;
27+
account: string;
28+
password: string;
29+
}
30+
31+
class LocalStorageCredentialsProvider implements ICredentialsProvider {
32+
33+
static readonly CREDENTIALS_OPENED_KEY = 'credentials.provider';
34+
35+
private _credentials: ICredential[];
36+
private get credentials(): ICredential[] {
37+
if (!this._credentials) {
38+
try {
39+
const serializedCredentials = window.localStorage.getItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY);
40+
if (serializedCredentials) {
41+
this._credentials = JSON.parse(serializedCredentials);
42+
}
43+
} catch (error) {
44+
// ignore
45+
}
46+
47+
if (!Array.isArray(this._credentials)) {
48+
this._credentials = [];
49+
}
50+
}
51+
52+
return this._credentials;
53+
}
54+
55+
private save(): void {
56+
window.localStorage.setItem(LocalStorageCredentialsProvider.CREDENTIALS_OPENED_KEY, JSON.stringify(this.credentials));
57+
}
58+
59+
async getPassword(service: string, account: string): Promise<string | null> {
60+
return this.doGetPassword(service, account);
61+
}
62+
63+
private async doGetPassword(service: string, account?: string): Promise<string | null> {
64+
for (const credential of this.credentials) {
65+
if (credential.service === service) {
66+
if (typeof account !== 'string' || account === credential.account) {
67+
return credential.password;
68+
}
69+
}
70+
}
71+
72+
return null;
73+
}
74+
75+
async setPassword(service: string, account: string, password: string): Promise<void> {
76+
this.deletePassword(service, account);
77+
78+
this.credentials.push({ service, account, password });
79+
80+
this.save();
81+
}
82+
83+
async deletePassword(service: string, account: string): Promise<boolean> {
84+
let found = false;
85+
86+
this._credentials = this.credentials.filter(credential => {
87+
if (credential.service === service && credential.account === account) {
88+
found = true;
89+
90+
return false;
91+
}
92+
93+
return true;
94+
});
95+
96+
if (found) {
97+
this.save();
98+
}
99+
100+
return found;
101+
}
102+
103+
async findPassword(service: string): Promise<string | null> {
104+
return this.doGetPassword(service);
105+
}
106+
107+
async findCredentials(service: string): Promise<Array<{ account: string, password: string }>> {
108+
return this.credentials
109+
.filter(credential => credential.service === service)
110+
.map(({ account, password }) => ({ account, password }));
111+
}
112+
}
113+
114+
class PollingURLCallbackProvider extends Disposable implements IURLCallbackProvider {
115+
116+
static FETCH_INTERVAL = 500; // fetch every 500ms
117+
static FETCH_TIMEOUT = 5 * 60 * 1000; // ...but stop after 5min
118+
119+
static QUERY_KEYS = {
120+
REQUEST_ID: 'vscode-requestId',
121+
SCHEME: 'vscode-scheme',
122+
AUTHORITY: 'vscode-authority',
123+
PATH: 'vscode-path',
124+
QUERY: 'vscode-query',
125+
FRAGMENT: 'vscode-fragment'
126+
};
127+
128+
private readonly _onCallback: Emitter<URI> = this._register(new Emitter<URI>());
129+
readonly onCallback: Event<URI> = this._onCallback.event;
130+
131+
create(options?: Partial<UriComponents>): URI {
132+
const queryValues: Map<string, string> = new Map();
133+
134+
const requestId = generateUuid();
135+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
136+
137+
const { scheme, authority, path, query, fragment } = options ? options : { scheme: undefined, authority: undefined, path: undefined, query: undefined, fragment: undefined };
138+
139+
if (scheme) {
140+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.SCHEME, scheme);
141+
}
142+
143+
if (authority) {
144+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.AUTHORITY, authority);
145+
}
146+
147+
if (path) {
148+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.PATH, path);
149+
}
150+
151+
if (query) {
152+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.QUERY, query);
153+
}
154+
155+
if (fragment) {
156+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.FRAGMENT, fragment);
157+
}
158+
159+
// Start to poll on the callback being fired
160+
this.periodicFetchCallback(requestId, Date.now());
161+
162+
return this.doCreateUri('/callback', queryValues);
163+
}
164+
165+
private async periodicFetchCallback(requestId: string, startTime: number): Promise<void> {
166+
167+
// Ask server for callback results
168+
const queryValues: Map<string, string> = new Map();
169+
queryValues.set(PollingURLCallbackProvider.QUERY_KEYS.REQUEST_ID, requestId);
170+
171+
const result = await request({
172+
url: this.doCreateUri('/fetch-callback', queryValues).toString(true)
173+
}, CancellationToken.None);
174+
175+
// Check for callback results
176+
const content = await streamToBuffer(result.stream);
177+
if (content.byteLength > 0) {
178+
try {
179+
this._onCallback.fire(URI.revive(JSON.parse(content.toString())));
180+
} catch (error) {
181+
console.error(error);
182+
}
183+
184+
return; // done
185+
}
186+
187+
// Continue fetching unless we hit the timeout
188+
if (Date.now() - startTime < PollingURLCallbackProvider.FETCH_TIMEOUT) {
189+
setTimeout(() => this.periodicFetchCallback(requestId, startTime), PollingURLCallbackProvider.FETCH_INTERVAL);
190+
}
191+
}
192+
193+
private doCreateUri(path: string, queryValues: Map<string, string>): URI {
194+
let query: string | undefined = undefined;
195+
196+
if (queryValues) {
197+
let index = 0;
198+
queryValues.forEach((value, key) => {
199+
if (!query) {
200+
query = '';
201+
}
202+
203+
const prefix = (index++ === 0) ? '' : '&';
204+
query += `${prefix}${key}=${encodeURIComponent(value)}`;
205+
});
206+
}
207+
208+
return URI.parse(window.location.href).with({ path, query });
209+
}
210+
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!-- Copyright (C) Microsoft Corporation. All rights reserved. -->
2+
<!DOCTYPE html>
3+
<html>
4+
<head>
5+
<meta charset="utf-8" />
6+
7+
<!-- Disable pinch zooming -->
8+
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no">
9+
10+
<!-- Content Security Policy -->
11+
<meta
12+
http-equiv="Content-Security-Policy"
13+
content="
14+
default-src 'self';
15+
img-src 'self' https: data: blob:;
16+
media-src 'none';
17+
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-4DqvCTjCHj2KW4QxC/Yt6uBwMRyYiEg7kOoykSEkonQ=' 'sha256-g94DXzh59qc37AZjL7sV1lYN7OX4K1fgKl4ts5tAQDw=';
18+
child-src 'self';
19+
frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com;
20+
worker-src 'self';
21+
style-src 'self' 'unsafe-inline';
22+
connect-src 'self' ws: wss: https:;
23+
font-src 'self' blob:;
24+
manifest-src 'self';
25+
">
26+
27+
<!-- Workbench Configuration -->
28+
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONGIGURATION}}">
29+
30+
<!-- Workarounds/Hacks (remote user data uri) -->
31+
<meta id="vscode-remote-user-data-uri" data-settings="{{REMOTE_USER_DATA_URI}}">
32+
33+
<!-- Workbench Icon/Manifest/CSS -->
34+
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
35+
<link rel="manifest" href="/manifest.json">
36+
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static/out/vs/workbench/workbench.web.api.css">
37+
</head>
38+
39+
<body aria-label="">
40+
</body>
41+
42+
<!-- Startup (do not modify order of script tags!) -->
43+
<script>
44+
// NOTE: Changes to inline scripts require update of content security policy
45+
self.require = {
46+
baseUrl: `${window.location.origin}/static/out`,
47+
paths: {
48+
'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`,
49+
'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`,
50+
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
51+
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
52+
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
53+
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
54+
'@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
55+
}
56+
};
57+
</script>
58+
<script src="./static/out/vs/loader.js"></script>
59+
<script>
60+
// NOTE: Changes to inline scripts require update of content security policy
61+
require(['vs/code/browser/workbench/web.main'], function (web) {
62+
web.main();
63+
});
64+
</script>
65+
</html>

src/vs/code/browser/workbench/workbench.html

Lines changed: 33 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@
1010
<!-- Content Security Policy -->
1111
<meta
1212
http-equiv="Content-Security-Policy"
13-
content="default-src 'none';
13+
content="
14+
default-src 'self';
1415
img-src 'self' https: data: blob:;
1516
media-src 'none';
16-
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https:;
17+
script-src 'self' https://az416426.vo.msecnd.net 'unsafe-eval' https: 'sha256-4DqvCTjCHj2KW4QxC/Yt6uBwMRyYiEg7kOoykSEkonQ=' 'sha256-kXwJWoOluR7vyWhuqykdzYEHvOuOu2ZZhnBm0EBbYvU=';
1718
child-src 'self';
1819
frame-src 'self' {{WEBVIEW_ENDPOINT}} https://*.vscode-webview-test.com;
1920
worker-src 'self';
2021
style-src 'self' 'unsafe-inline';
2122
connect-src 'self' ws: wss: https:;
2223
font-src 'self' blob:;
23-
manifest-src 'self';"
24-
>
24+
manifest-src 'self';
25+
">
2526

2627
<!-- Workbench Configuration -->
2728
<meta id="vscode-workbench-web-configuration" data-settings="{{WORKBENCH_WEB_CONGIGURATION}}">
@@ -33,13 +34,40 @@
3334
<link rel="icon" href="/favicon.ico" type="image/x-icon" />
3435
<link rel="manifest" href="/manifest.json">
3536
<link data-name="vs/workbench/workbench.web.api" rel="stylesheet" href="./static/out/vs/workbench/workbench.web.api.css">
37+
38+
<!-- Prefetch to avoid waterfall -->
39+
<link rel="prefetch" href="./static/node_modules/semver-umd/lib/semver-umd.js">
40+
<link rel="prefetch" href="./static/node_modules/onigasm-umd/release/main.js"> <!-- TODO@ben TODO@alex: should be lazy -->
41+
<link rel="prefetch" href="./static/out/vs/code/browser/workbench/web.main.js"> <!--TODO@ben: Why is it not built -->
42+
<link rel="prefetch" href="./static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js">
3643
</head>
3744

3845
<body aria-label="">
3946
</body>
4047

4148
<!-- Startup (do not modify order of script tags!) -->
49+
<script>
50+
// NOTE: Changes to inline scripts require update of content security policy
51+
self.require = {
52+
baseUrl: `${window.location.origin}/static/out`,
53+
paths: {
54+
'vscode-textmate': `${window.location.origin}/static/node_modules/vscode-textmate/release/main`,
55+
'onigasm-umd': `${window.location.origin}/static/node_modules/onigasm-umd/release/main`,
56+
'xterm': `${window.location.origin}/static/node_modules/xterm/lib/xterm.js`,
57+
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
58+
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
59+
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
60+
'@microsoft/applicationinsights-web': `${window.location.origin}/static/node_modules/@microsoft/applicationinsights-web/dist/applicationinsights-web.js`,
61+
}
62+
};
63+
</script>
4264
<script src="./static/out/vs/loader.js"></script>
4365
<script src="./static/out/vs/workbench/workbench.web.api.nls.js"></script>
44-
<script src="./static/out/vs/code/browser/workbench/workbench.js"></script>
66+
<script src="./static/out/vs/workbench/workbench.web.api.js"></script>
67+
<!-- <script src="./static/out/vs/code/browser/workbench/web.main.js"></script> TODO@ben: Why is it not built-->
68+
<script>
69+
require(['vs/code/browser/workbench/web.main'], function (web) {
70+
web.main();
71+
});
72+
</script>
4573
</html>

src/vs/code/browser/workbench/workbench.js

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/vs/workbench/buildfile.web.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,6 @@ function createModuleDescription(name, exclude) {
2020
exports.collectModules = function () {
2121
return [
2222
createModuleDescription('vs/workbench/contrib/output/common/outputLinkComputer', ['vs/base/common/worker/simpleWorker', 'vs/editor/common/services/editorSimpleWorker']),
23+
createModuleDescription('vs/code/browser/workbench/web.main', ['vs/workbench/workbench.web.api']),
2324
];
2425
};

0 commit comments

Comments
 (0)