mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-09-28 13:13:37 +08:00
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 <dsv@chromium.org> Commit-Queue: Vidal Diazleal <vidorteg@microsoft.com> Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
f271e0d644
commit
dc49e2d266
Vendored
+1
@@ -25,6 +25,7 @@ export namespace Chrome {
|
||||
* there must be no "straddling" (i.e. partially overlapping ranges).
|
||||
*/
|
||||
setFunctionRangesForScript(ranges: NamedFunctionRange[]): Promise<void>;
|
||||
attachSourceMapURL(sourceMapURL: string): Promise<void>;
|
||||
}
|
||||
|
||||
export interface InspectedWindow {
|
||||
|
||||
@@ -82,6 +82,7 @@ export namespace PrivateAPI {
|
||||
ShowPanel = 'showPanel',
|
||||
Unsubscribe = 'unsubscribe',
|
||||
UpdateButton = 'updateButton',
|
||||
AttachSourceMapToResource = 'attachSourceMapToResource',
|
||||
RegisterLanguageExtensionPlugin = 'registerLanguageExtensionPlugin',
|
||||
GetWasmLinearMemory = 'getWasmLinearMemory',
|
||||
GetWasmLocal = 'getWasmLocal',
|
||||
@@ -255,6 +256,11 @@ export namespace PrivateAPI {
|
||||
command: Commands.GetResourceContent;
|
||||
url: string;
|
||||
}
|
||||
interface AttachSourceMapToResourceRequest {
|
||||
command: Commands.AttachSourceMapToResource;
|
||||
contentUrl: string;
|
||||
sourceMapURL: string;
|
||||
}
|
||||
interface SetResourceContentRequest {
|
||||
command: Commands.SetResourceContent;
|
||||
url: string;
|
||||
@@ -314,8 +320,9 @@ export namespace PrivateAPI {
|
||||
SetSidebarHeightRequest|SetSidebarContentRequest|SetSidebarPageRequest|OpenResourceRequest|
|
||||
SetOpenResourceHandlerRequest|SetThemeChangeHandlerRequest|ReloadRequest|EvaluateOnInspectedPageRequest|
|
||||
GetRequestContentRequest|GetResourceContentRequest|SetResourceContentRequest|SetFunctionRangesForScriptRequest|
|
||||
ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest|GetWasmLinearMemoryRequest|GetWasmLocalRequest|
|
||||
GetWasmGlobalRequest|GetWasmOpRequest|ShowNetworkPanelRequest|ReportResourceLoadRequest;
|
||||
AttachSourceMapToResourceRequest|ForwardKeyboardEventRequest|GetHARRequest|GetPageResourcesRequest|
|
||||
GetWasmLinearMemoryRequest|GetWasmLocalRequest|GetWasmGlobalRequest|GetWasmOpRequest|ShowNetworkPanelRequest|
|
||||
ReportResourceLoadRequest;
|
||||
export type ExtensionServerRequestMessage = PrivateAPI.ServerRequests&{requestId?: number};
|
||||
|
||||
interface AddRawModuleRequest {
|
||||
@@ -1331,7 +1338,7 @@ self.injectedExtensionAPI = function(
|
||||
}
|
||||
|
||||
(ResourceImpl.prototype as
|
||||
Pick<APIImpl.Resource, 'url'|'type'|'getContent'|'setContent'|'setFunctionRangesForScript'>) = {
|
||||
Pick<APIImpl.Resource, 'url'|'type'|'getContent'|'setContent'|'setFunctionRangesForScript'|'attachSourceMapURL'>) = {
|
||||
get url(): string {
|
||||
return (this as APIImpl.Resource)._url;
|
||||
},
|
||||
@@ -1380,6 +1387,25 @@ self.injectedExtensionAPI = function(
|
||||
}
|
||||
}));
|
||||
},
|
||||
|
||||
attachSourceMapURL: function(this: APIImpl.Resource, sourceMapURL: string): Promise<void> {
|
||||
return new Promise(
|
||||
(resolve, reject) => extensionServer.sendRequest(
|
||||
{command: PrivateAPI.Commands.AttachSourceMapToResource, contentUrl: this._url, sourceMapURL},
|
||||
(response: unknown) => {
|
||||
const result = response as {
|
||||
code: string,
|
||||
description: string,
|
||||
details: unknown[],
|
||||
isError?: boolean,
|
||||
};
|
||||
if (result.isError) {
|
||||
reject(new Error(result.description));
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
}));
|
||||
},
|
||||
};
|
||||
|
||||
function getTabId(): string {
|
||||
|
||||
@@ -12,7 +12,9 @@ import {
|
||||
describeWithDevtoolsExtension,
|
||||
getExtensionOrigin,
|
||||
} from '../../testing/ExtensionHelpers.js';
|
||||
import {MockProtocolBackend} from '../../testing/MockScopeChain.js';
|
||||
import {addChildFrame, FRAME_URL, getMainFrame} from '../../testing/ResourceTreeHelpers.js';
|
||||
import {encodeSourceMap} from '../../testing/SourceMapEncoder.js';
|
||||
import * as UI from '../../ui/legacy/legacy.js';
|
||||
import * as Bindings from '../bindings/bindings.js';
|
||||
import * as Extensions from '../extensions/extensions.js';
|
||||
@@ -959,3 +961,72 @@ for (const allowFileAccess of [true, false]) {
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
describeWithDevtoolsExtension('validate attachSourceMapURL ', {}, context => {
|
||||
it('correctly attaches a source map to a registered script', async () => {
|
||||
const sourceRoot = 'http://example.com';
|
||||
const scriptName = 'script.ts';
|
||||
const scriptInfo = {
|
||||
url: urlString`${sourceRoot}/script.js`,
|
||||
content: 'function f(x) { console.log(x); } function ignore(y){ console.log(y); }',
|
||||
};
|
||||
const sourceMap = encodeSourceMap(
|
||||
[
|
||||
`0:9 => ${scriptName}:0:1`,
|
||||
`1:0 => ${scriptName}:4:0`,
|
||||
`1:2 => ${scriptName}:4:2`,
|
||||
`2:0 => ${scriptName}:2:0`,
|
||||
],
|
||||
sourceRoot);
|
||||
|
||||
const sourceMapString = {
|
||||
version: 3,
|
||||
names: ['f', 'console', 'log', 'ignore'],
|
||||
sources: [scriptInfo.url],
|
||||
mappings: sourceMap.mappings,
|
||||
file: `${scriptInfo.url}.map`,
|
||||
};
|
||||
|
||||
const target = createTarget({type: SDK.Target.Type.FRAME});
|
||||
const targetManager = target.targetManager();
|
||||
const workspace = Workspace.Workspace.WorkspaceImpl.instance();
|
||||
const resourceMapping = new Bindings.ResourceMapping.ResourceMapping(targetManager, workspace);
|
||||
const debuggerWorkspaceBinding = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance(
|
||||
{forceNew: false, resourceMapping, targetManager});
|
||||
const backend = new MockProtocolBackend();
|
||||
Bindings.IgnoreListManager.IgnoreListManager.instance({forceNew: false, debuggerWorkspaceBinding});
|
||||
|
||||
// Before any script is registered, there shouldn't be any uiSourceCodes.
|
||||
assert.isNull(Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(scriptInfo.url));
|
||||
|
||||
// Create promise to await the uiSourceCode given the url and its target.
|
||||
const uiSourceCodePromise = debuggerWorkspaceBinding.waitForUISourceCodeAdded(scriptInfo.url, target);
|
||||
|
||||
// Register the script.
|
||||
const currentScript = await backend.addScript(target, scriptInfo, null);
|
||||
|
||||
// Await the promise for sourceCode to be added.
|
||||
await uiSourceCodePromise;
|
||||
|
||||
assert.exists(context.chrome.devtools);
|
||||
|
||||
const resources = await new Promise<Chrome.DevTools.Resource[]>(r => {
|
||||
context.chrome.devtools?.inspectedWindow.getResources(r);
|
||||
});
|
||||
|
||||
// Validate that resource is registered.
|
||||
assert.isTrue(resources && resources.length > 0);
|
||||
|
||||
// Script should not have a source map url attached yet.
|
||||
assert.notExists(currentScript.sourceMapURL);
|
||||
|
||||
// Call attachSourceMapURL with encoded source map as a dataURL
|
||||
const scriptResource = resources.find(item => item.url === scriptInfo.url.toString());
|
||||
const encodedSourceMap = `data:text/plain;base64,${btoa(JSON.stringify(sourceMapString))}`;
|
||||
|
||||
await scriptResource?.attachSourceMapURL(encodedSourceMap);
|
||||
|
||||
// Validate that the script has the sourcemap dataURL attached.
|
||||
assert.deepEqual(currentScript.sourceMapURL, encodedSourceMap);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -195,6 +195,7 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
this.registerHandler(PrivateAPI.Commands.SetOpenResourceHandler, this.onSetOpenResourceHandler.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.SetThemeChangeHandler, this.onSetThemeChangeHandler.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.SetResourceContent, this.onSetResourceContent.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.AttachSourceMapToResource, this.onAttachSourceMapToResource.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.SetSidebarHeight, this.onSetSidebarHeight.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.SetSidebarContent, this.onSetSidebarContent.bind(this));
|
||||
this.registerHandler(PrivateAPI.Commands.SetSidebarPage, this.onSetSidebarPage.bind(this));
|
||||
@@ -998,6 +999,34 @@ export class ExtensionServer extends Common.ObjectWrapper.ObjectWrapper<EventTyp
|
||||
return undefined;
|
||||
}
|
||||
|
||||
private onAttachSourceMapToResource(message: PrivateAPI.ExtensionServerRequestMessage): Record|undefined {
|
||||
if (message.command !== PrivateAPI.Commands.AttachSourceMapToResource) {
|
||||
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.GetResourceContent}`);
|
||||
}
|
||||
|
||||
if (!message.sourceMapURL) {
|
||||
return this.status.E_FAILED('Expected a source map URL but got null');
|
||||
}
|
||||
|
||||
const url = message.contentUrl as Platform.DevToolsPath.UrlString;
|
||||
const contentProvider = Workspace.Workspace.WorkspaceImpl.instance().uiSourceCodeForURL(url);
|
||||
if (!contentProvider) {
|
||||
return this.status.E_NOTFOUND(url);
|
||||
}
|
||||
|
||||
const debuggerBindingsInstance = Bindings.DebuggerWorkspaceBinding.DebuggerWorkspaceBinding.instance();
|
||||
const scriptFiles = debuggerBindingsInstance.scriptsForUISourceCode(contentProvider);
|
||||
if (scriptFiles.length > 0) {
|
||||
for (const script of scriptFiles) {
|
||||
const resourceFile = debuggerBindingsInstance.scriptFile(
|
||||
contentProvider as Workspace.UISourceCode.UISourceCode, script.debuggerModel);
|
||||
resourceFile?.addSourceMapURL(message.sourceMapURL as Platform.DevToolsPath.UrlString);
|
||||
}
|
||||
}
|
||||
|
||||
return this.status.OK();
|
||||
}
|
||||
|
||||
private onSetResourceContent(message: PrivateAPI.ExtensionServerRequestMessage, port: MessagePort): Record|undefined {
|
||||
if (message.command !== PrivateAPI.Commands.SetResourceContent) {
|
||||
return this.status.E_BADARG('command', `expected ${PrivateAPI.Commands.SetResourceContent}`);
|
||||
|
||||
Reference in New Issue
Block a user