-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Remove dependency on code/browser/workbench/workbench (#137)
* remove dependency on code/browser/workbench/workbench * complete * polish
- Loading branch information
Showing
14 changed files
with
2,342 additions
and
72 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,272 @@ | ||
/*--------------------------------------------------------------------------------------------- | ||
* Copyright (c) Microsoft Corporation. All rights reserved. | ||
* Licensed under the MIT License. See License.txt in the project root for license information. | ||
*--------------------------------------------------------------------------------------------*/ | ||
///<amd-module name='vscode-web-browser-main'/> | ||
|
||
import { create, IWorkspaceProvider, IWorkbenchConstructionOptions, UriComponents, IWorkspace, URI, IURLCallbackProvider, Emitter, IDisposable} from './workbench.api'; | ||
|
||
class WorkspaceProvider implements IWorkspaceProvider { | ||
|
||
private static QUERY_PARAM_EMPTY_WINDOW = 'ew'; | ||
private static QUERY_PARAM_FOLDER = 'folder'; | ||
private static QUERY_PARAM_WORKSPACE = 'workspace'; | ||
|
||
private static QUERY_PARAM_PAYLOAD = 'payload'; | ||
|
||
static create(config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents }) { | ||
let foundWorkspace = false; | ||
let workspace: IWorkspace; | ||
let payload = Object.create(null); | ||
|
||
const query = new URL(document.location.href).searchParams; | ||
query.forEach((value, key) => { | ||
switch (key) { | ||
|
||
// Folder | ||
case WorkspaceProvider.QUERY_PARAM_FOLDER: | ||
workspace = { folderUri: URI.parse(value) }; | ||
foundWorkspace = true; | ||
break; | ||
|
||
// Workspace | ||
case WorkspaceProvider.QUERY_PARAM_WORKSPACE: | ||
workspace = { workspaceUri: URI.parse(value) }; | ||
foundWorkspace = true; | ||
break; | ||
|
||
// Empty | ||
case WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW: | ||
workspace = undefined; | ||
foundWorkspace = true; | ||
break; | ||
|
||
// Payload | ||
case WorkspaceProvider.QUERY_PARAM_PAYLOAD: | ||
try { | ||
payload = JSON.parse(value); | ||
} catch (error) { | ||
console.error(error); // possible invalid JSON | ||
} | ||
break; | ||
} | ||
}); | ||
|
||
// If no workspace is provided through the URL, check for config | ||
// attribute from server | ||
if (!foundWorkspace) { | ||
if (config.folderUri) { | ||
workspace = { folderUri: URI.revive(config.folderUri) }; | ||
} else if (config.workspaceUri) { | ||
workspace = { workspaceUri: URI.revive(config.workspaceUri) }; | ||
} | ||
} | ||
|
||
return new WorkspaceProvider(workspace, payload); | ||
} | ||
|
||
readonly trusted = true; | ||
|
||
private constructor( | ||
readonly workspace: IWorkspace, | ||
readonly payload: object, | ||
) { | ||
} | ||
|
||
async open(workspace: IWorkspace, options?: { reuse?: boolean; payload?: object }): Promise<boolean> { | ||
if (options?.reuse && !options.payload && this.isSame(this.workspace, workspace)) { | ||
return true; // return early if workspace and environment is not changing and we are reusing window | ||
} | ||
|
||
const targetHref = this.createTargetUrl(workspace, options); | ||
if (targetHref) { | ||
if (options?.reuse) { | ||
window.location.href = targetHref; | ||
return true; | ||
} else { | ||
return !!window.open(targetHref); | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
private createTargetUrl(workspace: IWorkspace, options?: { reuse?: boolean; payload?: object }): string | undefined { | ||
|
||
// Empty | ||
let targetHref: string | undefined = undefined; | ||
if (!workspace) { | ||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_EMPTY_WINDOW}=true`; | ||
} | ||
|
||
// Folder | ||
else if ('folderUri' in workspace) { | ||
const queryParamFolder = encodeURIComponent(workspace.folderUri.toString(true)); | ||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_FOLDER}=${queryParamFolder}`; | ||
} | ||
|
||
// Workspace | ||
else if ('workspaceUri' in workspace) { | ||
const queryParamWorkspace = encodeURIComponent(workspace.workspaceUri.toString(true)); | ||
targetHref = `${document.location.origin}${document.location.pathname}?${WorkspaceProvider.QUERY_PARAM_WORKSPACE}=${queryParamWorkspace}`; | ||
} | ||
|
||
// Append payload if any | ||
if (options?.payload) { | ||
targetHref += `&${WorkspaceProvider.QUERY_PARAM_PAYLOAD}=${encodeURIComponent(JSON.stringify(options.payload))}`; | ||
} | ||
|
||
return targetHref; | ||
} | ||
|
||
private isSame(workspaceA: IWorkspace, workspaceB: IWorkspace): boolean { | ||
if (!workspaceA || !workspaceB) { | ||
return workspaceA === workspaceB; // both empty | ||
} | ||
|
||
if ('folderUri' in workspaceA && 'folderUri' in workspaceB) { | ||
return isEqual(workspaceA.folderUri, workspaceB.folderUri); // same workspace | ||
} | ||
|
||
if ('workspaceUri' in workspaceA && 'workspaceUri' in workspaceB) { | ||
return isEqual(workspaceA.workspaceUri, workspaceB.workspaceUri); // same workspace | ||
} | ||
|
||
return false; | ||
} | ||
|
||
} | ||
|
||
class LocalStorageURLCallbackProvider implements IURLCallbackProvider, IDisposable { | ||
|
||
private static REQUEST_ID = 0; | ||
|
||
private static QUERY_KEYS: ('scheme' | 'authority' | 'path' | 'query' | 'fragment')[] = [ | ||
'scheme', | ||
'authority', | ||
'path', | ||
'query', | ||
'fragment' | ||
]; | ||
|
||
private readonly _onCallback = new Emitter<URI>(); | ||
readonly onCallback = this._onCallback.event; | ||
|
||
private pendingCallbacks = new Set<number>(); | ||
private lastTimeChecked = Date.now(); | ||
private checkCallbacksTimeout: unknown | undefined = undefined; | ||
private onDidChangeLocalStorageDisposable: IDisposable | undefined; | ||
|
||
constructor(private readonly _callbackRoute: string) { | ||
} | ||
|
||
create(options: Partial<UriComponents> = {}): URI { | ||
const id = ++LocalStorageURLCallbackProvider.REQUEST_ID; | ||
const queryParams: string[] = [`vscode-reqid=${id}`]; | ||
|
||
for (const key of LocalStorageURLCallbackProvider.QUERY_KEYS) { | ||
const value = options[key]; | ||
|
||
if (value) { | ||
queryParams.push(`vscode-${key}=${encodeURIComponent(value)}`); | ||
} | ||
} | ||
|
||
// TODO@joao remove eventually | ||
// https://github.com/microsoft/vscode-dev/issues/62 | ||
// https://github.com/microsoft/vscode/blob/159479eb5ae451a66b5dac3c12d564f32f454796/extensions/github-authentication/src/githubServer.ts#L50-L50 | ||
if (!(options.authority === 'vscode.github-authentication' && options.path === '/dummy')) { | ||
const key = `vscode-web.url-callbacks[${id}]`; | ||
localStorage.removeItem(key); | ||
|
||
this.pendingCallbacks.add(id); | ||
this.startListening(); | ||
} | ||
|
||
return URI.parse(window.location.href).with({ path: this._callbackRoute, query: queryParams.join('&') }); | ||
} | ||
|
||
private startListening(): void { | ||
if (this.onDidChangeLocalStorageDisposable) { | ||
return; | ||
} | ||
|
||
const fn = () => this.onDidChangeLocalStorage(); | ||
window.addEventListener('storage', fn); | ||
this.onDidChangeLocalStorageDisposable = { dispose: () => window.removeEventListener('storage', fn) }; | ||
} | ||
|
||
private stopListening(): void { | ||
this.onDidChangeLocalStorageDisposable?.dispose(); | ||
this.onDidChangeLocalStorageDisposable = undefined; | ||
} | ||
|
||
// this fires every time local storage changes, but we | ||
// don't want to check more often than once a second | ||
private async onDidChangeLocalStorage(): Promise<void> { | ||
const ellapsed = Date.now() - this.lastTimeChecked; | ||
|
||
if (ellapsed > 1000) { | ||
this.checkCallbacks(); | ||
} else if (this.checkCallbacksTimeout === undefined) { | ||
this.checkCallbacksTimeout = setTimeout(() => { | ||
this.checkCallbacksTimeout = undefined; | ||
this.checkCallbacks(); | ||
}, 1000 - ellapsed); | ||
} | ||
} | ||
|
||
private checkCallbacks(): void { | ||
let pendingCallbacks: Set<number> | undefined; | ||
|
||
for (const id of this.pendingCallbacks) { | ||
const key = `vscode-web.url-callbacks[${id}]`; | ||
const result = localStorage.getItem(key); | ||
|
||
if (result !== null) { | ||
try { | ||
this._onCallback.fire(URI.revive(JSON.parse(result))); | ||
} catch (error) { | ||
console.error(error); | ||
} | ||
|
||
pendingCallbacks = pendingCallbacks ?? new Set(this.pendingCallbacks); | ||
pendingCallbacks.delete(id); | ||
localStorage.removeItem(key); | ||
} | ||
} | ||
|
||
if (pendingCallbacks) { | ||
this.pendingCallbacks = pendingCallbacks; | ||
|
||
if (this.pendingCallbacks.size === 0) { | ||
this.stopListening(); | ||
} | ||
} | ||
|
||
this.lastTimeChecked = Date.now(); | ||
} | ||
|
||
dispose(): void { | ||
this._onCallback.dispose(); | ||
} | ||
} | ||
|
||
function isEqual(a: UriComponents, b: UriComponents): boolean { | ||
return a.scheme === b.scheme && a.authority === b.authority && a.path === b.path; | ||
} | ||
|
||
(function () { | ||
const configElement = window.document.getElementById('vscode-workbench-web-configuration'); | ||
const configElementAttribute = configElement ? configElement.getAttribute('data-settings') : undefined; | ||
if (!configElement || !configElementAttribute) { | ||
throw new Error('Missing web configuration element'); | ||
} | ||
const config: IWorkbenchConstructionOptions & { folderUri?: UriComponents; workspaceUri?: UriComponents; callbackRoute: string } = JSON.parse(configElementAttribute); | ||
|
||
create(window.document.body, { | ||
...config, | ||
workspaceProvider: WorkspaceProvider.create(config), | ||
urlCallbackProvider: new LocalStorageURLCallbackProvider(config.callbackRoute) | ||
}); | ||
|
||
})(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2022", | ||
"module": "AMD", | ||
"lib": [ | ||
"ES2022", | ||
"DOM", | ||
], | ||
"outDir": "../../out/browser/amd", | ||
"declaration": true, | ||
"strict": true, | ||
"noImplicitAny": false, | ||
"noImplicitThis": true, | ||
"noUnusedLocals": true, | ||
"alwaysStrict": true, | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"sourceMap": false, | ||
"newLine": "lf", | ||
"removeComments": true | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"compilerOptions": { | ||
"target": "ES2022", | ||
"module": "ES2022", | ||
"lib": [ | ||
"ES2022", | ||
"DOM", | ||
], | ||
"outDir": "../../out/browser/esm", | ||
"declaration": true, | ||
"strict": true, | ||
"noImplicitAny": false, | ||
"noImplicitThis": true, | ||
"noUnusedLocals": true, | ||
"alwaysStrict": true, | ||
"skipLibCheck": true, | ||
"forceConsistentCasingInFileNames": true, | ||
"sourceMap": false, | ||
"newLine": "lf", | ||
"removeComments": true | ||
} | ||
} |
Oops, something went wrong.