Skip to content

Commit dc49e2d

Browse files
vidortegDevtools-frontend LUCI CQ
authored andcommitted
Adding attachSourceMapURL to Resource in ExtensionAPI
This CL adds the APIs required for attaching a sourcemap to a resource via Extension APIs. This CL considers that the External Developer will subscribe to some of the existing Devtools Extension API (chrome.runtime.onConnect, port.OnMessage & inspectedWindow.onResourceAdded) This “subscribed“ extension, will be notified every time a resource is loaded. This includes images and any other resources This CL introduces: - APIs shape and implementation (attachSourceMapURL) - ExtensionServer implementation - ExtensionServer tests Full spec can be found here (Implementation): https://docs.google.com/document/d/1x9_MTMVr213ApruMK6ryc4JpPHahvOBlExP6sHkMv8Y/edit?pli=1 Bug: 364508694 Change-Id: I35a53b58fae1ba41b733bc4bcb8ed6b8022dad54 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6166854 Reviewed-by: Danil Somsikov <[email protected]> Commit-Queue: Vidal Diazleal <[email protected]> Reviewed-by: Philip Pfaffe <[email protected]>
1 parent f271e0d commit dc49e2d

File tree

4 files changed

+130
-3
lines changed

4 files changed

+130
-3
lines changed

extension-api/ExtensionAPI.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export namespace Chrome {
2525
* there must be no "straddling" (i.e. partially overlapping ranges).
2626
*/
2727
setFunctionRangesForScript(ranges: NamedFunctionRange[]): Promise<void>;
28+
attachSourceMapURL(sourceMapURL: string): Promise<void>;
2829
}
2930

3031
export interface InspectedWindow {

front_end/models/extensions/ExtensionAPI.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ export namespace PrivateAPI {
8282
ShowPanel = 'showPanel',
8383
Unsubscribe = 'unsubscribe',
8484
UpdateButton = 'updateButton',
85+
AttachSourceMapToResource = 'attachSourceMapToResource',
8586
RegisterLanguageExtensionPlugin = 'registerLanguageExtensionPlugin',
8687
GetWasmLinearMemory = 'getWasmLinearMemory',
8788
GetWasmLocal = 'getWasmLocal',
@@ -255,6 +256,11 @@ export namespace PrivateAPI {
255256
command: Commands.GetResourceContent;
256257
url: string;
257258
}
259+
interface AttachSourceMapToResourceRequest {
260+
command: Commands.AttachSourceMapToResource;
261+
contentUrl: string;
262+
sourceMapURL: string;
263+
}
258264
interface SetResourceContentRequest {
259265
command: Commands.SetResourceContent;
260266
url: string;
@@ -314,8 +320,9 @@ export namespace PrivateAPI {
314320
SetSidebarHeightRequest|SetSidebarContentRequest|SetSidebarPageRequest|OpenResourceRequest|
315321
SetOpenResourceHandlerRequest|SetThemeChangeHandlerRequest|ReloadRequest|EvaluateOnInspectedPageRequest|
316322
GetRequestContentRequest|GetResourceContentRequest|SetResourceContentRequest|SetFunctionRangesForScriptRequest|
317-
ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest|GetWasmLinearMemoryRequest|GetWasmLocalRequest|
318-
GetWasmGlobalRequest|GetWasmOpRequest|ShowNetworkPanelRequest|ReportResourceLoadRequest;
323+
AttachSourceMapToResourceRequest|ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest|
324+
GetWasmLinearMemoryRequest|GetWasmLocalRequest|GetWasmGlobalRequest|GetWasmOpRequest|ShowNetworkPanelRequest|
325+
ReportResourceLoadRequest;
319326
export type ExtensionServerRequestMessage = PrivateAPI.ServerRequests&{requestId?: number};
320327

321328
interface AddRawModuleRequest {
@@ -1331,7 +1338,7 @@ self.injectedExtensionAPI = function(
13311338
}
13321339

13331340
(ResourceImpl.prototype as
1334-
Pick<APIImpl.Resource, 'url'|'type'|'getContent'|'setContent'|'setFunctionRangesForScript'>) = {
1341+
Pick<APIImpl.Resource, 'url'|'type'|'getContent'|'setContent'|'setFunctionRangesForScript'|'attachSourceMapURL'>) = {
13351342
get url(): string {
13361343
return (this as APIImpl.Resource)._url;
13371344
},
@@ -1380,6 +1387,25 @@ self.injectedExtensionAPI = function(
13801387
}
13811388
}));
13821389
},
1390+
1391+
attachSourceMapURL: function(this: APIImpl.Resource, sourceMapURL: string): Promise<void> {
1392+
return new Promise(
1393+
(resolve, reject) => extensionServer.sendRequest(
1394+
{command: PrivateAPI.Commands.AttachSourceMapToResource, contentUrl: this._url, sourceMapURL},
1395+
(response: unknown) => {
1396+
const result = response as {
1397+
code: string,
1398+
description: string,
1399+
details: unknown[],
1400+
isError?: boolean,
1401+
};
1402+
if (result.isError) {
1403+
reject(new Error(result.description));
1404+
} else {
1405+
resolve();
1406+
}
1407+
}));
1408+
},
13831409
};
13841410

13851411
function getTabId(): string {

front_end/models/extensions/ExtensionServer.test.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import {
1212
describeWithDevtoolsExtension,
1313
getExtensionOrigin,
1414
} from '../../testing/ExtensionHelpers.js';
15+
import {MockProtocolBackend} from '../../testing/MockScopeChain.js';
1516
import {addChildFrame, FRAME_URL, getMainFrame} from '../../testing/ResourceTreeHelpers.js';
17+
import {encodeSourceMap} from '../../testing/SourceMapEncoder.js';
1618
import * as UI from '../../ui/legacy/legacy.js';
1719
import * as Bindings from '../bindings/bindings.js';
1820
import * as Extensions from '../extensions/extensions.js';
@@ -959,3 +961,72 @@ for (const allowFileAccess of [true, false]) {
959961
});
960962
});
961963
}
964+
965+
describeWithDevtoolsExtension('validate attachSourceMapURL ', {}, context => {
966+
it('correctly attaches a source map to a registered script', async () => {
967+
const sourceRoot = 'http://example.com';
968+
const scriptName = 'script.ts';
969+
const scriptInfo = {
970+
url: urlString`${sourceRoot}/script.js`,
971+
content: 'function f(x) { console.log(x); } function ignore(y){ console.log(y); }',
972+
};
973+
const sourceMap = encodeSourceMap(
974+
[
975+
`0:9 => ${scriptName}:0:1`,
976+
`1:0 => ${scriptName}:4:0`,
977+
`1:2 => ${scriptName}:4:2`,
978+
`2:0 => ${scriptName}:2:0`,
979+
],
980+
sourceRoot);
981+
982+
const sourceMapString = {
983+
version: 3,
984+
names: ['f', 'console', 'log', 'ignore'],
985+
sources: [scriptInfo.url],
986+
mappings: sourceMap.mappings,
987+
file: `${scriptInfo.url}.map`,
988+
};
989+
990+
const target = createTarget({type: SDK.Target.Type.FRAME});
991+
const targetManager = target.targetManager();
992+
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
993+
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
994+
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(
995+
{forceNew: false, resourceMapping, targetManager});
996+
const backend = new MockProtocolBackend();
997+
Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: false, debuggerWorkspaceBinding});
998+
999+
// Before any script is registered, there shouldn't be any uiSourceCodes.
1000+
assert.isNull(Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(scriptInfo.url));
1001+
1002+
// Create promise to await the uiSourceCode given the url and its target.
1003+
const uiSourceCodePromise = debuggerWorkspaceBinding.waitForUISourceCodeAdded(scriptInfo.url, target);
1004+
1005+
// Register the script.
1006+
const currentScript = await backend.addScript(target, scriptInfo, null);
1007+
1008+
// Await the promise for sourceCode to be added.
1009+
await uiSourceCodePromise;
1010+
1011+
assert.exists(context.chrome.devtools);
1012+
1013+
const resources = await new Promise<Chrome.DevTools.Resource[]>(r => {
1014+
context.chrome.devtools?.inspectedWindow.getResources(r);
1015+
});
1016+
1017+
// Validate that resource is registered.
1018+
assert.isTrue(resources && resources.length > 0);
1019+
1020+
// Script should not have a source map url attached yet.
1021+
assert.notExists(currentScript.sourceMapURL);
1022+
1023+
// Call attachSourceMapURL with encoded source map as a dataURL
1024+
const scriptResource = resources.find(item => item.url === scriptInfo.url.toString());
1025+
const encodedSourceMap = `data:text/plain;base64,${btoa(JSON.stringify(sourceMapString))}`;
1026+
1027+
await scriptResource?.attachSourceMapURL(encodedSourceMap);
1028+
1029+
// Validate that the script has the sourcemap dataURL attached.
1030+
assert.deepEqual(currentScript.sourceMapURL, encodedSourceMap);
1031+
});
1032+
});

front_end/models/extensions/ExtensionServer.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
195195
this.registerHandler(PrivateAPI.Commands.SetOpenResourceHandler, this.onSetOpenResourceHandler.bind(this));
196196
this.registerHandler(PrivateAPI.Commands.SetThemeChangeHandler, this.onSetThemeChangeHandler.bind(this));
197197
this.registerHandler(PrivateAPI.Commands.SetResourceContent, this.onSetResourceContent.bind(this));
198+
this.registerHandler(PrivateAPI.Commands.AttachSourceMapToResource, this.onAttachSourceMapToResource.bind(this));
198199
this.registerHandler(PrivateAPI.Commands.SetSidebarHeight, this.onSetSidebarHeight.bind(this));
199200
this.registerHandler(PrivateAPI.Commands.SetSidebarContent, this.onSetSidebarContent.bind(this));
200201
this.registerHandler(PrivateAPI.Commands.SetSidebarPage, this.onSetSidebarPage.bind(this));
@@ -998,6 +999,34 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
998999
return undefined;
9991000
}
10001001

1002+
private onAttachSourceMapToResource(message: PrivateAPI.ExtensionServerRequestMessage): Record|undefined {
1003+
if (message.command !== PrivateAPI.Commands.AttachSourceMapToResource) {
1004+
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.GetResourceContent}`);
1005+
}
1006+
1007+
if (!message.sourceMapURL) {
1008+
return this.status.E_FAILED('Expected a source map URL but got null');
1009+
}
1010+
1011+
const url = message.contentUrl as Platform.DevToolsPath.UrlString;
1012+
const contentProvider = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url);
1013+
if (!contentProvider) {
1014+
return this.status.E_NOTFOUND(url);
1015+
}
1016+
1017+
const debuggerBindingsInstance = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
1018+
const scriptFiles = debuggerBindingsInstance.scriptsForUISourceCode(contentProvider);
1019+
if (scriptFiles.length > 0) {
1020+
for (const script of scriptFiles) {
1021+
const resourceFile = debuggerBindingsInstance.scriptFile(
1022+
contentProvider as Workspace.UISourceCode.UISourceCode, script.debuggerModel);
1023+
resourceFile?.addSourceMapURL(message.sourceMapURL as Platform.DevToolsPath.UrlString);
1024+
}
1025+
}
1026+
1027+
return this.status.OK();
1028+
}
1029+
10011030
private onSetResourceContent(message: PrivateAPI.ExtensionServerRequestMessage, port: MessagePort): Record|undefined {
10021031
if (message.command !== PrivateAPI.Commands.SetResourceContent) {
10031032
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.SetResourceContent}`);

0 commit comments

Comments
 (0)