Skip to content

Commit ba27193

Browse files
committed
Web extensions support
- Add package nls url to scanned extension - Introduce scanner service for builtin extensions - Introduce scanner service for web extensions - Use web extensions scanner service in management and runtime - Apply default translation inside management service
1 parent 8a7c490 commit ba27193

12 files changed

Lines changed: 183 additions & 101 deletions

File tree

resources/serverless/code-web.js

Lines changed: 10 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -91,13 +91,13 @@ async function initialize() {
9191
const packageJSONPath = path.join(EXTENSIONS_ROOT, folderName, 'package.json');
9292
if (await exists(packageJSONPath)) {
9393
try {
94-
const manifest = JSON.parse((await readFile(packageJSONPath)).toString());
95-
if (manifest.main && !manifest.browser) {
94+
const packageJSON = JSON.parse((await readFile(packageJSONPath)).toString());
95+
if (packageJSON.main && !packageJSON.browser) {
9696
return; // unsupported
9797
}
9898

99-
if (manifest.browser) {
100-
manifest.main = manifest.browser;
99+
if (packageJSON.browser) {
100+
packageJSON.main = packageJSON.browser;
101101

102102
const webpackConfigLocations = await util.promisify(glob)(
103103
path.join(EXTENSIONS_ROOT, folderName, '**', 'extension-browser.webpack.config.js'),
@@ -117,31 +117,14 @@ async function initialize() {
117117
}
118118
}
119119

120-
const packageNlsPath = path.join(EXTENSIONS_ROOT, folderName, 'package.nls.json');
121-
if (await exists(packageNlsPath)) {
122-
const packageNls = JSON.parse((await readFile(packageNlsPath)).toString());
123-
const translate = (obj) => {
124-
for (let key in obj) {
125-
const val = obj[key];
126-
if (Array.isArray(val)) {
127-
val.forEach(translate);
128-
} else if (val && typeof val === 'object') {
129-
translate(val);
130-
} else if (typeof val === 'string' && val.charCodeAt(0) === CharCode_PC && val.charCodeAt(val.length - 1) === CharCode_PC) {
131-
const translated = packageNls[val.substr(1, val.length - 2)];
132-
if (translated) {
133-
obj[key] = translated;
134-
}
135-
}
136-
}
137-
};
138-
translate(manifest);
139-
}
140-
manifest.extensionKind = ['web']; // enable for Web
120+
packageJSON.extensionKind = ['web']; // enable for Web
121+
122+
const packageNLSPath = path.join(folderName, 'package.nls.json');
123+
const packageNLSExists = await exists(path.join(EXTENSIONS_ROOT, packageNLSPath));
141124
builtinExtensions.push({
142-
identifier: { id: `${manifest.publisher}.${manifest.name}`},
143-
manifest,
125+
packageJSON,
144126
location: toStaticExtensionUri(folderName),
127+
packageNLSUrl: packageNLSExists ? toStaticExtensionUri(packageNLSPath) : undefined,
145128
readmeUrl,
146129
changelogUrl
147130
});
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
7+
import { isWeb } from 'vs/base/common/platform';
8+
import { URI } from 'vs/base/common/uri';
9+
10+
export class BuiltinExtensionsScannerService implements IBuiltinExtensionsScannerService {
11+
12+
declare readonly _serviceBrand: undefined;
13+
14+
private readonly builtinExtensions: IScannedExtension[] = [];
15+
16+
constructor(
17+
) {
18+
if (isWeb) {
19+
// Find builtin extensions by checking for DOM
20+
const builtinExtensionsElement = document.getElementById('vscode-workbench-builtin-extensions');
21+
const builtinExtensionsElementAttribute = builtinExtensionsElement ? builtinExtensionsElement.getAttribute('data-settings') : undefined;
22+
if (builtinExtensionsElementAttribute) {
23+
try {
24+
const builtinExtensions: IScannedExtension[] = JSON.parse(builtinExtensionsElementAttribute);
25+
this.builtinExtensions = builtinExtensions.map(e => <IScannedExtension>{
26+
location: URI.revive(e.location),
27+
type: ExtensionType.System,
28+
packageJSON: e.packageJSON,
29+
packageNLSUrl: URI.revive(e.packageNLSUrl),
30+
readmeUrl: URI.revive(e.readmeUrl),
31+
changelogUrl: URI.revive(e.changelogUrl),
32+
});
33+
} catch (error) { /* ignore error*/ }
34+
}
35+
}
36+
}
37+
38+
async scanBuiltinExtensions(): Promise<IScannedExtension[]> {
39+
if (isWeb) {
40+
return this.builtinExtensions;
41+
}
42+
throw new Error('not supported');
43+
}
44+
}

src/vs/platform/extensions/common/extensions.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import * as strings from 'vs/base/common/strings';
77
import { ILocalization } from 'vs/platform/localizations/common/localizations';
88
import { URI } from 'vs/base/common/uri';
9+
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
910

1011
export const MANIFEST_CACHE_FOLDER = 'CachedExtensions';
1112
export const USER_MANIFEST_CACHE_FILE = 'user';
@@ -246,3 +247,18 @@ export interface IExtensionDescription extends IExtensionManifest {
246247
export function isLanguagePackExtension(manifest: IExtensionManifest): boolean {
247248
return manifest.contributes && manifest.contributes.localizations ? manifest.contributes.localizations.length > 0 : false;
248249
}
250+
251+
export interface IScannedExtension {
252+
readonly location: URI;
253+
readonly type: ExtensionType;
254+
readonly packageJSON: IExtensionManifest
255+
readonly packageNLSUrl?: URI;
256+
readonly readmeUrl?: URI;
257+
readonly changelogUrl?: URI;
258+
}
259+
260+
export const IBuiltinExtensionsScannerService = createDecorator<IBuiltinExtensionsScannerService>('IBuiltinExtensionsScannerService');
261+
export interface IBuiltinExtensionsScannerService {
262+
readonly _serviceBrand: undefined;
263+
scanBuiltinExtensions(): Promise<IScannedExtension[]>;
264+
}

src/vs/workbench/services/extensionManagement/common/extensionManagement.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
import { Event } from 'vs/base/common/event';
77
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
88
import { URI } from 'vs/base/common/uri';
9-
import { IExtension } from 'vs/platform/extensions/common/extensions';
9+
import { IExtension, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
1010
import { IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
1111
import { IWorkspace, IWorkspaceFolder } from 'vs/platform/workspace/common/workspace';
1212
import { IStringDictionary } from 'vs/base/common/collections';
@@ -137,3 +137,9 @@ export interface IExtensionRecommendationsService {
137137
getIgnoredRecommendations(): ReadonlyArray<string>;
138138
onRecommendationChange: Event<RecommendationChangeNotification>;
139139
}
140+
141+
export const IWebExtensionsScannerService = createDecorator<IWebExtensionsScannerService>('IWebExtensionsScannerService');
142+
export interface IWebExtensionsScannerService {
143+
readonly _serviceBrand: undefined;
144+
scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]>;
145+
}

src/vs/workbench/services/extensionManagement/browser/extensionManagementServerService.ts renamed to src/vs/workbench/services/extensionManagement/common/extensionManagementServerService.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
1313
import { ILabelService } from 'vs/platform/label/common/label';
1414
import { isWeb } from 'vs/base/common/platform';
1515
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
16-
import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/browser/webExtensionManagementService';
16+
import { WebExtensionManagementService } from 'vs/workbench/services/extensionManagement/common/webExtensionManagementService';
1717
import { IExtension } from 'vs/platform/extensions/common/extensions';
1818

1919
export class ExtensionManagementServerService implements IExtensionManagementServerService {

src/vs/workbench/services/extensionManagement/browser/webExtensionManagementService.ts renamed to src/vs/workbench/services/extensionManagement/common/webExtensionManagementService.ts

Lines changed: 33 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -3,42 +3,15 @@
33
* Licensed under the MIT License. See License.txt in the project root for license information.
44
*--------------------------------------------------------------------------------------------*/
55

6-
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, IExtension } from 'vs/platform/extensions/common/extensions';
6+
import { ExtensionType, IExtensionIdentifier, IExtensionManifest, IScannedExtension } from 'vs/platform/extensions/common/extensions';
77
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, DidUninstallExtensionEvent, IGalleryExtension, IReportedExtension, IGalleryMetadata } from 'vs/platform/extensionManagement/common/extensionManagement';
88
import { Event } from 'vs/base/common/event';
99
import { URI } from 'vs/base/common/uri';
10-
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
11-
import { isWeb } from 'vs/base/common/platform';
12-
13-
let builtinExtensions: IExtension[] = [];
14-
15-
// Web
16-
if (isWeb) {
17-
18-
// Running out of sources
19-
if (Object.keys(builtinExtensions).length === 0) {
20-
// Find builtin extensions by checking for DOM
21-
const builtinExtensionsElement = document.getElementById('vscode-workbench-builtin-extensions');
22-
const builtinExtensionsElementAttribute = builtinExtensionsElement ? builtinExtensionsElement.getAttribute('data-settings') : undefined;
23-
if (builtinExtensionsElementAttribute) {
24-
try {
25-
builtinExtensions = JSON.parse(builtinExtensionsElementAttribute);
26-
} catch (error) { /* ignore error*/ }
27-
}
28-
}
29-
}
30-
31-
// Unknown
32-
else {
33-
throw new Error('Unable to resolve builtin extensions');
34-
}
35-
36-
builtinExtensions = builtinExtensions.map(extension => ({
37-
...extension,
38-
location: URI.revive(extension.location),
39-
readmeUrl: URI.revive(extension.readmeUrl),
40-
changelogUrl: URI.revive(extension.changelogUrl),
41-
}));
10+
import { IRequestService, isSuccess, asText } from 'vs/platform/request/common/request';
11+
import { CancellationToken } from 'vs/base/common/cancellation';
12+
import { localizeManifest } from 'vs/platform/extensionManagement/common/extensionNls';
13+
import { getGalleryExtensionId } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
14+
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
4215

4316
export class WebExtensionManagementService implements IExtensionManagementService {
4417

@@ -49,35 +22,39 @@ export class WebExtensionManagementService implements IExtensionManagementServic
4922
onUninstallExtension: Event<IExtensionIdentifier> = Event.None;
5023
onDidUninstallExtension: Event<DidUninstallExtensionEvent> = Event.None;
5124

52-
private readonly systemExtensions: ILocalExtension[];
53-
private readonly staticExtensions: ILocalExtension[];
54-
5525
constructor(
56-
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
26+
@IWebExtensionsScannerService private readonly webExtensionsScannerService: IWebExtensionsScannerService,
27+
@IRequestService private readonly requestService: IRequestService,
5728
) {
58-
this.systemExtensions = builtinExtensions.map(e => ({ ...e, type: ExtensionType.System, isMachineScoped: false, publisherId: null, publisherDisplayName: null }));
59-
const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : [];
60-
61-
this.staticExtensions = staticExtensions.map(data => <ILocalExtension>{
62-
type: ExtensionType.User,
63-
identifier: { id: `${data.packageJSON.publisher}.${data.packageJSON.name}` },
64-
manifest: data.packageJSON,
65-
location: data.extensionLocation,
66-
isMachineScoped: false,
67-
publisherId: null,
68-
publisherDisplayName: null
69-
});
7029
}
7130

7231
async getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
73-
const extensions = [];
74-
if (type === undefined || type === ExtensionType.System) {
75-
extensions.push(...this.systemExtensions);
76-
}
77-
if (type === undefined || type === ExtensionType.User) {
78-
extensions.push(...this.staticExtensions);
32+
const extensions = await this.webExtensionsScannerService.scanExtensions(type);
33+
return Promise.all(extensions.map(e => this.toLocalExtension(e)));
34+
}
35+
36+
private async toLocalExtension(scannedExtension: IScannedExtension): Promise<ILocalExtension> {
37+
let manifest = scannedExtension.packageJSON;
38+
if (scannedExtension.packageNLSUrl) {
39+
try {
40+
const context = await this.requestService.request({ type: 'GET', url: scannedExtension.packageNLSUrl.toString() }, CancellationToken.None);
41+
if (isSuccess(context)) {
42+
const content = await asText(context);
43+
if (content) {
44+
manifest = localizeManifest(manifest, JSON.parse(content));
45+
}
46+
}
47+
} catch (error) { /* ignore */ }
7948
}
80-
return extensions;
49+
return <ILocalExtension>{
50+
type: ExtensionType.System,
51+
identifier: { id: getGalleryExtensionId(manifest.publisher, manifest.name) },
52+
manifest,
53+
location: scannedExtension.location,
54+
isMachineScoped: false,
55+
publisherId: null,
56+
publisherDisplayName: null
57+
};
8158
}
8259

8360
zip(extension: ILocalExtension): Promise<URI> { throw new Error('unsupported'); }
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
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 { IBuiltinExtensionsScannerService, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
7+
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
8+
import { IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
9+
import { isWeb } from 'vs/base/common/platform';
10+
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
11+
12+
export class WebExtensionsScannerService implements IWebExtensionsScannerService {
13+
14+
declare readonly _serviceBrand: undefined;
15+
16+
private readonly systemExtensionsPromise: Promise<IScannedExtension[]>;
17+
private readonly userExtensions: IScannedExtension[];
18+
19+
constructor(
20+
@IBuiltinExtensionsScannerService private readonly builtinExtensionsScannerService: IBuiltinExtensionsScannerService,
21+
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService
22+
) {
23+
this.systemExtensionsPromise = isWeb ? this.builtinExtensionsScannerService.scanBuiltinExtensions() : Promise.resolve([]);
24+
const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : [];
25+
this.userExtensions = staticExtensions.map(data => <IScannedExtension>{
26+
location: data.extensionLocation,
27+
type: ExtensionType.User,
28+
packageJSON: data.packageJSON,
29+
});
30+
}
31+
32+
async scanExtensions(type?: ExtensionType): Promise<IScannedExtension[]> {
33+
const extensions = [];
34+
if (type === undefined || type === ExtensionType.System) {
35+
const systemExtensions = await this.systemExtensionsPromise;
36+
extensions.push(...systemExtensions);
37+
}
38+
if (type === undefined || type === ExtensionType.User) {
39+
extensions.push(...this.userExtensions);
40+
}
41+
return extensions;
42+
}
43+
44+
}
45+
46+
registerSingleton(IWebExtensionsScannerService, WebExtensionsScannerService);

src/vs/workbench/services/extensions/browser/extensionService.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,15 @@
55

66
import * as nls from 'vs/nls';
77
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
8-
import { IWorkbenchExtensionEnablementService, IExtensionManagementServerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
8+
import { IWorkbenchExtensionEnablementService, IWebExtensionsScannerService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
99
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
1010
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
1111
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
12-
import { IExtensionService, IExtensionHost, toExtensionDescription } from 'vs/workbench/services/extensions/common/extensions';
12+
import { IExtensionService, IExtensionHost } from 'vs/workbench/services/extensions/common/extensions';
1313
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
1414
import { IFileService } from 'vs/platform/files/common/files';
1515
import { IProductService } from 'vs/platform/product/common/productService';
16-
import { AbstractExtensionService } from 'vs/workbench/services/extensions/common/abstractExtensionService';
16+
import { AbstractExtensionService, parseScannedExtension } from 'vs/workbench/services/extensions/common/abstractExtensionService';
1717
import { RemoteExtensionHost, IInitDataProvider } from 'vs/workbench/services/extensions/common/remoteExtensionHost';
1818
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
1919
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
@@ -44,7 +44,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
4444
@IRemoteAuthorityResolverService private readonly _remoteAuthorityResolverService: IRemoteAuthorityResolverService,
4545
@IRemoteAgentService private readonly _remoteAgentService: IRemoteAgentService,
4646
@IConfigurationService private readonly _configService: IConfigurationService,
47-
@IExtensionManagementServerService private readonly _extensionManagementServerService: IExtensionManagementServerService,
47+
@IWebExtensionsScannerService private readonly _webExtensionsScannerService: IWebExtensionsScannerService,
4848
) {
4949
super(
5050
instantiationService,
@@ -104,9 +104,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
104104
// fetch the remote environment
105105
let [remoteEnv, localExtensions] = await Promise.all([
106106
this._remoteAgentService.getEnvironment(),
107-
this._extensionManagementServerService.webExtensionManagementServer
108-
? this._extensionManagementServerService.webExtensionManagementServer.extensionManagementService.getInstalled().then(extensions => extensions.map(toExtensionDescription))
109-
: Promise.resolve([])
107+
this._webExtensionsScannerService.scanExtensions().then(extensions => extensions.map(parseScannedExtension))
110108
]);
111109

112110
let result: DeltaExtensionsResult;

src/vs/workbench/services/extensions/common/abstractExtensionService.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ import { ExtensionMessageCollector, ExtensionPoint, ExtensionsRegistry, IExtensi
2020
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
2121
import { ResponsiveState } from 'vs/workbench/services/extensions/common/rpcProtocol';
2222
import { ExtensionHostManager } from 'vs/workbench/services/extensions/common/extensionHostManager';
23-
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
23+
import { ExtensionIdentifier, IExtensionDescription, IScannedExtension, ExtensionType } from 'vs/platform/extensions/common/extensions';
2424
import { IFileService } from 'vs/platform/files/common/files';
2525
import { parseExtensionDevOptions } from 'vs/workbench/services/extensions/common/extensionDevOptions';
2626
import { IProductService } from 'vs/platform/product/common/productService';
@@ -29,6 +29,16 @@ import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtens
2929
const hasOwnProperty = Object.hasOwnProperty;
3030
const NO_OP_VOID_PROMISE = Promise.resolve<void>(undefined);
3131

32+
export function parseScannedExtension(extension: IScannedExtension): IExtensionDescription {
33+
return {
34+
identifier: new ExtensionIdentifier(`${extension.packageJSON.publisher}.${extension.packageJSON.name}`),
35+
isBuiltin: extension.type === ExtensionType.System,
36+
isUnderDevelopment: false,
37+
extensionLocation: extension.location,
38+
...extension.packageJSON,
39+
};
40+
}
41+
3242
export abstract class AbstractExtensionService extends Disposable implements IExtensionService {
3343

3444
public _serviceBrand: undefined;

0 commit comments

Comments
 (0)