blob: 5c6fa8f56504018414d90ef3a9bf041f8a7ae9f1 [file] [log] [blame]
Blink Reformat4c46d092018-04-07 15:32:371/*
2 * Copyright (C) 2012 Google Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
7 *
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29 */
30
Paul Lewisaf066612020-07-28 15:32:4231// @ts-nocheck
32// TODO(crbug.com/1011811): Enable TypeScript compiler checks
33
Tim van der Lippea6110922020-01-09 15:38:3934export function defineCommonExtensionSymbols(apiPrivate) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3435 if (!apiPrivate.panels) {
Blink Reformat4c46d092018-04-07 15:32:3736 apiPrivate.panels = {};
Tim van der Lippe1d6e57a2019-09-30 11:55:3437 }
Blink Reformat4c46d092018-04-07 15:32:3738 apiPrivate.panels.SearchAction = {
39 CancelSearch: 'cancelSearch',
40 PerformSearch: 'performSearch',
41 NextSearchResult: 'nextSearchResult',
42 PreviousSearchResult: 'previousSearchResult'
43 };
44
45 /** @enum {string} */
46 apiPrivate.Events = {
47 ButtonClicked: 'button-clicked-',
48 PanelObjectSelected: 'panel-objectSelected-',
49 NetworkRequestFinished: 'network-request-finished',
50 OpenResource: 'open-resource',
51 PanelSearch: 'panel-search-',
52 RecordingStarted: 'trace-recording-started-',
53 RecordingStopped: 'trace-recording-stopped-',
54 ResourceAdded: 'resource-added',
55 ResourceContentCommitted: 'resource-content-committed',
56 ViewShown: 'view-shown-',
57 ViewHidden: 'view-hidden-'
58 };
59
60 /** @enum {string} */
61 apiPrivate.Commands = {
62 AddRequestHeaders: 'addRequestHeaders',
63 AddTraceProvider: 'addTraceProvider',
64 ApplyStyleSheet: 'applyStyleSheet',
65 CompleteTraceSession: 'completeTraceSession',
66 CreatePanel: 'createPanel',
67 CreateSidebarPane: 'createSidebarPane',
68 CreateToolbarButton: 'createToolbarButton',
69 EvaluateOnInspectedPage: 'evaluateOnInspectedPage',
70 ForwardKeyboardEvent: '_forwardKeyboardEvent',
71 GetHAR: 'getHAR',
72 GetPageResources: 'getPageResources',
73 GetRequestContent: 'getRequestContent',
74 GetResourceContent: 'getResourceContent',
75 InspectedURLChanged: 'inspectedURLChanged',
76 OpenResource: 'openResource',
77 Reload: 'Reload',
78 Subscribe: 'subscribe',
79 SetOpenResourceHandler: 'setOpenResourceHandler',
80 SetResourceContent: 'setResourceContent',
81 SetSidebarContent: 'setSidebarContent',
82 SetSidebarHeight: 'setSidebarHeight',
83 SetSidebarPage: 'setSidebarPage',
84 ShowPanel: 'showPanel',
85 Unsubscribe: 'unsubscribe',
Philip Pfaffeedad8322020-07-20 10:24:2586 UpdateButton: 'updateButton',
87 RegisterLanguageExtensionPlugin: 'registerLanguageExtensionPlugin'
88 };
89
90 /** @enum {string} */
91 apiPrivate.LanguageExtensionPluginCommands = {
92 AddRawModule: 'addRawModule',
93 RemoveRawModule: 'removeRawModule',
94 SourceLocationToRawLocation: 'sourceLocationToRawLocation',
95 RawLocationToSourceLocation: 'rawLocationToSourceLocation',
Benedikt Meurer723d5432020-10-09 09:02:4296 GetScopeInfo: 'getScopeInfo',
Philip Pfaffeedad8322020-07-20 10:24:2597 ListVariablesInScope: 'listVariablesInScope',
Eric Leese5aaa2222020-10-01 11:49:3598 EvaluateVariable: 'evaluateVariable',
Philip Pfaffe480fa882020-10-22 09:38:3699 GetTypeInfo: 'getTypeInfo',
100 GetFormatter: 'getFormatter',
Eric Leese553115e2020-10-19 12:09:04101 GetFunctionInfo: 'getFunctionInfo',
102 GetInlinedFunctionRanges: 'getInlinedFunctionRanges',
103 GetInlinedCalleesRanges: 'getInlinedCalleesRanges'
Blink Reformat4c46d092018-04-07 15:32:37104 };
Benedikt Meurer929fc7c2020-11-20 14:21:06105
106 /** @enum {string} */
107 apiPrivate.LanguageExtensionPluginEvents = {
108 UnregisteredLanguageExtensionPlugin: 'unregisteredLanguageExtensionPlugin'
109 };
Blink Reformat4c46d092018-04-07 15:32:37110}
111
112/**
113 * @param {!ExtensionDescriptor} extensionInfo
114 * @param {string} inspectedTabId
115 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47116 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37117 * @param {number} injectedScriptId
118 * @param {function(!Object, !Object)} testHook
Blink Reformat4c46d092018-04-07 15:32:37119 */
Tim van der Lippe226fc222019-10-10 12:17:12120self.injectedExtensionAPI = function(
121 extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
Joel Einbinder67f28fb2018-08-02 00:33:47122 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:37123 const chrome = window.chrome || {};
124 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
Tim van der Lippe1d6e57a2019-09-30 11:55:34125 if (devtools_descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37126 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34127 }
Blink Reformat4c46d092018-04-07 15:32:37128
129 const apiPrivate = {};
130
131 defineCommonExtensionSymbols(apiPrivate);
132
133 const commands = apiPrivate.Commands;
Philip Pfaffeedad8322020-07-20 10:24:25134 const languageExtensionPluginCommands = apiPrivate.LanguageExtensionPluginCommands;
Benedikt Meurer929fc7c2020-11-20 14:21:06135 const languageExtensionPluginEvents = apiPrivate.LanguageExtensionPluginEvents;
Blink Reformat4c46d092018-04-07 15:32:37136 const events = apiPrivate.Events;
137 let userAction = false;
138
139 // Here and below, all constructors are private to API implementation.
140 // For a public type Foo, if internal fields are present, these are on
141 // a private FooImpl type, an instance of FooImpl is used in a closure
142 // by Foo consutrctor to re-bind publicly exported members to an instance
143 // of Foo.
144
145 /**
146 * @constructor
147 */
148 function EventSinkImpl(type, customDispatch) {
149 this._type = type;
150 this._listeners = [];
151 this._customDispatch = customDispatch;
152 }
153
154 EventSinkImpl.prototype = {
155 addListener: function(callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34156 if (typeof callback !== 'function') {
Blink Reformat4c46d092018-04-07 15:32:37157 throw 'addListener: callback is not a function';
Tim van der Lippe1d6e57a2019-09-30 11:55:34158 }
159 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37160 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34161 }
Blink Reformat4c46d092018-04-07 15:32:37162 this._listeners.push(callback);
163 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
164 },
165
166 removeListener: function(callback) {
167 const listeners = this._listeners;
168
169 for (let i = 0; i < listeners.length; ++i) {
170 if (listeners[i] === callback) {
171 listeners.splice(i, 1);
172 break;
173 }
174 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34175 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37176 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34177 }
Blink Reformat4c46d092018-04-07 15:32:37178 },
179
180 /**
181 * @param {...} vararg
182 */
183 _fire: function(vararg) {
184 const listeners = this._listeners.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34185 for (let i = 0; i < listeners.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37186 listeners[i].apply(null, arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34187 }
Blink Reformat4c46d092018-04-07 15:32:37188 },
189
190 _dispatch: function(request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34191 if (this._customDispatch) {
Blink Reformat4c46d092018-04-07 15:32:37192 this._customDispatch.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34193 } else {
Blink Reformat4c46d092018-04-07 15:32:37194 this._fire.apply(this, request.arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34195 }
Blink Reformat4c46d092018-04-07 15:32:37196 }
197 };
198
199 /**
200 * @constructor
201 */
202 function InspectorExtensionAPI() {
203 this.inspectedWindow = new InspectedWindow();
204 this.panels = new Panels();
205 this.network = new Network();
206 this.timeline = new Timeline();
Philip Pfaffeedad8322020-07-20 10:24:25207 this.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37208 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
209 }
210
211 /**
212 * @constructor
213 */
214 function Network() {
215 /**
216 * @this {EventSinkImpl}
217 */
218 function dispatchRequestEvent(message) {
219 const request = message.arguments[1];
220 request.__proto__ = new Request(message.arguments[0]);
221 this._fire(request);
222 }
223 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
224 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
225 this.onNavigated = new EventSink(events.InspectedURLChanged);
226 }
227
228 Network.prototype = {
229 getHAR: function(callback) {
230 function callbackWrapper(result) {
231 const entries = (result && result.entries) || [];
232 for (let i = 0; i < entries.length; ++i) {
233 entries[i].__proto__ = new Request(entries[i]._requestId);
234 delete entries[i]._requestId;
235 }
236 callback(result);
237 }
238 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
239 },
240
241 addRequestHeaders: function(headers) {
242 extensionServer.sendRequest(
243 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
244 }
245 };
246
247 /**
248 * @constructor
249 */
250 function RequestImpl(id) {
251 this._id = id;
252 }
253
254 RequestImpl.prototype = {
255 getContent: function(callback) {
256 function callbackWrapper(response) {
257 callback(response.content, response.encoding);
258 }
259 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
260 }
261 };
262
263 /**
264 * @constructor
265 */
266 function Panels() {
267 const panels = {
268 elements: new ElementsPanel(),
269 sources: new SourcesPanel(),
270 };
271
272 function panelGetter(name) {
273 return panels[name];
274 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34275 for (const panel in panels) {
Tim van der Lippeffa78622019-09-16 12:07:12276 Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
Tim van der Lippe1d6e57a2019-09-30 11:55:34277 }
Blink Reformat4c46d092018-04-07 15:32:37278 this.applyStyleSheet = function(styleSheet) {
279 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
280 };
281 }
282
283 Panels.prototype = {
284 create: function(title, icon, page, callback) {
285 const id = 'extension-panel-' + extensionServer.nextObjectId();
286 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
287 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
288 },
289
290 setOpenResourceHandler: function(callback) {
291 const hadHandler = extensionServer.hasHandler(events.OpenResource);
292
293 function callbackWrapper(message) {
294 // Allow the panel to show itself when handling the event.
295 userAction = true;
296 try {
297 callback.call(null, new Resource(message.resource), message.lineNumber);
298 } finally {
299 userAction = false;
300 }
301 }
302
Tim van der Lippe1d6e57a2019-09-30 11:55:34303 if (!callback) {
Blink Reformat4c46d092018-04-07 15:32:37304 extensionServer.unregisterHandler(events.OpenResource);
Tim van der Lippe1d6e57a2019-09-30 11:55:34305 } else {
Blink Reformat4c46d092018-04-07 15:32:37306 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
Tim van der Lippe1d6e57a2019-09-30 11:55:34307 }
Blink Reformat4c46d092018-04-07 15:32:37308
309 // Only send command if we either removed an existing handler or added handler and had none before.
Tim van der Lippe1d6e57a2019-09-30 11:55:34310 if (hadHandler === !callback) {
Blink Reformat4c46d092018-04-07 15:32:37311 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
Tim van der Lippe1d6e57a2019-09-30 11:55:34312 }
Blink Reformat4c46d092018-04-07 15:32:37313 },
314
315 openResource: function(url, lineNumber, callback) {
316 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
317 },
318
319 get SearchAction() {
320 return apiPrivate.panels.SearchAction;
321 }
322 };
323
324 /**
325 * @constructor
326 */
327 function ExtensionViewImpl(id) {
328 this._id = id;
329
330 /**
331 * @this {EventSinkImpl}
332 */
333 function dispatchShowEvent(message) {
334 const frameIndex = message.arguments[0];
Tim van der Lippe1d6e57a2019-09-30 11:55:34335 if (typeof frameIndex === 'number') {
Blink Reformat4c46d092018-04-07 15:32:37336 this._fire(window.parent.frames[frameIndex]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34337 } else {
Blink Reformat4c46d092018-04-07 15:32:37338 this._fire();
Tim van der Lippe1d6e57a2019-09-30 11:55:34339 }
Blink Reformat4c46d092018-04-07 15:32:37340 }
341
342 if (id) {
343 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
344 this.onHidden = new EventSink(events.ViewHidden + id);
345 }
346 }
347
348 /**
349 * @constructor
350 * @extends {ExtensionViewImpl}
351 * @param {string} hostPanelName
352 */
353 function PanelWithSidebarImpl(hostPanelName) {
354 ExtensionViewImpl.call(this, null);
355 this._hostPanelName = hostPanelName;
356 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
357 }
358
359 PanelWithSidebarImpl.prototype = {
360 createSidebarPane: function(title, callback) {
361 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
362 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
363 function callbackWrapper() {
364 callback(new ExtensionSidebarPane(id));
365 }
366 extensionServer.sendRequest(request, callback && callbackWrapper);
367 },
368
369 __proto__: ExtensionViewImpl.prototype
370 };
371
Philip Pfaffeedad8322020-07-20 10:24:25372 /**
373 * @constructor
374 */
375 function LanguageServicesAPIImpl() {
Benedikt Meurer929fc7c2020-11-20 14:21:06376 /** @type {!Map<*, !MessagePort>} */
Philip Pfaffeedad8322020-07-20 10:24:25377 this._plugins = new Map();
378 }
379
380 LanguageServicesAPIImpl.prototype = {
381 /**
382 * @param {*} plugin The language plugin instance to register.
383 * @param {string} pluginName The plugin name
384 * @param {{language: string, symbol_types: !Array<string>}} supportedScriptTypes Script language and debug symbol types supported by this extension.
Benedikt Meurer929fc7c2020-11-20 14:21:06385 * @return {!Promise<void>}
Philip Pfaffeedad8322020-07-20 10:24:25386 */
Benedikt Meurer929fc7c2020-11-20 14:21:06387 registerLanguageExtensionPlugin: async function(plugin, pluginName, supportedScriptTypes) {
Philip Pfaffeedad8322020-07-20 10:24:25388 if (this._plugins.has(plugin)) {
Benedikt Meurer929fc7c2020-11-20 14:21:06389 throw new Error(`Tried to register plugin '${pluginName}' twice`);
Philip Pfaffeedad8322020-07-20 10:24:25390 }
391 const channel = new MessageChannel();
392 const port = channel.port1;
393 this._plugins.set(plugin, port);
394 port.onmessage = ({data: {requestId, method, parameters}}) => {
Benedikt Meurer99b14a92020-12-09 07:44:07395 console.time(`${requestId}: ${method}`);
Philip Pfaffeedad8322020-07-20 10:24:25396 dispatchMethodCall(method, parameters)
397 .then(result => port.postMessage({requestId, result}))
Benedikt Meurer99b14a92020-12-09 07:44:07398 .catch(error => port.postMessage({requestId, error: {message: error.message}}))
399 .finally(() => console.timeEnd(`${requestId}: ${method}`));
Philip Pfaffeedad8322020-07-20 10:24:25400 };
401
402 /**
403 * @param {string} method
404 * @param {*} parameters
405 * @return {!Promise<*>}
406 */
407 function dispatchMethodCall(method, parameters) {
408 switch (method) {
409 case languageExtensionPluginCommands.AddRawModule:
410 return plugin.addRawModule(parameters.rawModuleId, parameters.symbolsURL, parameters.rawModule);
411 case languageExtensionPluginCommands.RemoveRawModule:
412 return plugin.removeRawModule(parameters.rawModuleId);
413 case languageExtensionPluginCommands.SourceLocationToRawLocation:
414 return plugin.sourceLocationToRawLocation(parameters.sourceLocation);
415 case languageExtensionPluginCommands.RawLocationToSourceLocation:
416 return plugin.rawLocationToSourceLocation(parameters.rawLocation);
Benedikt Meurer723d5432020-10-09 09:02:42417 case languageExtensionPluginCommands.GetScopeInfo:
418 return plugin.getScopeInfo(parameters.type);
Philip Pfaffeedad8322020-07-20 10:24:25419 case languageExtensionPluginCommands.ListVariablesInScope:
420 return plugin.listVariablesInScope(parameters.rawLocation);
421 case languageExtensionPluginCommands.EvaluateVariable:
422 return plugin.evaluateVariable(parameters.name, parameters.location);
Philip Pfaffe480fa882020-10-22 09:38:36423 case languageExtensionPluginCommands.GetTypeInfo:
424 return plugin.getTypeInfo(parameters.expression, parameters.context);
425 case languageExtensionPluginCommands.GetFormatter:
426 return plugin.getFormatter(parameters.expressionOrField, parameters.context);
Eric Leese5aaa2222020-10-01 11:49:35427 case languageExtensionPluginCommands.GetFunctionInfo:
428 return plugin.getFunctionInfo(parameters.rawLocation);
Eric Leese553115e2020-10-19 12:09:04429 case languageExtensionPluginCommands.GetInlinedFunctionRanges:
430 return plugin.getInlinedFunctionRanges(parameters.rawLocation);
431 case languageExtensionPluginCommands.GetInlinedCalleesRanges:
432 return plugin.getInlinedCalleesRanges(parameters.rawLocation);
Philip Pfaffeedad8322020-07-20 10:24:25433 }
434 throw new Error(`Unknown language plugin method ${method}`);
435 }
436
Benedikt Meurer929fc7c2020-11-20 14:21:06437 await new Promise(resolve => {
438 extensionServer.sendRequest(
439 {command: commands.RegisterLanguageExtensionPlugin, pluginName, port: channel.port2, supportedScriptTypes},
440 () => resolve(), [channel.port2]);
441 });
442 },
443
444 /**
445 * @param {*} plugin The language plugin instance to unregister.
446 * @return {!Promise<void>}
447 */
448 unregisterLanguageExtensionPlugin: async function(plugin) {
449 const port = this._plugins.get(plugin);
450 if (!port) {
451 throw new Error('Tried to unregister a plugin that was not previously registered');
452 }
453 this._plugins.delete(plugin);
454 port.postMessage({event: languageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin});
455 port.close();
Philip Pfaffeedad8322020-07-20 10:24:25456 }
457 };
458
Blink Reformat4c46d092018-04-07 15:32:37459 function declareInterfaceClass(implConstructor) {
460 return function() {
461 const impl = {__proto__: implConstructor.prototype};
462 implConstructor.apply(impl, arguments);
463 populateInterfaceClass(this, impl);
464 };
465 }
466
467 function defineDeprecatedProperty(object, className, oldName, newName) {
468 let warningGiven = false;
469 function getter() {
470 if (!warningGiven) {
471 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
472 warningGiven = true;
473 }
474 return object[newName];
475 }
476 object.__defineGetter__(oldName, getter);
477 }
478
479 function extractCallbackArgument(args) {
480 const lastArgument = args[args.length - 1];
481 return typeof lastArgument === 'function' ? lastArgument : undefined;
482 }
483
Philip Pfaffeedad8322020-07-20 10:24:25484 const LanguageServicesAPI = declareInterfaceClass(LanguageServicesAPIImpl);
Blink Reformat4c46d092018-04-07 15:32:37485 const Button = declareInterfaceClass(ButtonImpl);
486 const EventSink = declareInterfaceClass(EventSinkImpl);
487 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
488 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
Tim van der Lippeffa78622019-09-16 12:07:12489 /**
490 * @constructor
491 * @param {string} hostPanelName
492 */
493 const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
Blink Reformat4c46d092018-04-07 15:32:37494 const Request = declareInterfaceClass(RequestImpl);
495 const Resource = declareInterfaceClass(ResourceImpl);
496 const TraceSession = declareInterfaceClass(TraceSessionImpl);
497
Tim van der Lippeffa78622019-09-16 12:07:12498 class ElementsPanel extends PanelWithSidebarClass {
499 constructor() {
500 super('elements');
501 }
Blink Reformat4c46d092018-04-07 15:32:37502 }
503
Tim van der Lippeffa78622019-09-16 12:07:12504 class SourcesPanel extends PanelWithSidebarClass {
505 constructor() {
506 super('sources');
507 }
Blink Reformat4c46d092018-04-07 15:32:37508 }
509
Blink Reformat4c46d092018-04-07 15:32:37510 /**
511 * @constructor
512 * @extends {ExtensionViewImpl}
513 */
514 function ExtensionPanelImpl(id) {
515 ExtensionViewImpl.call(this, id);
516 this.onSearch = new EventSink(events.PanelSearch + id);
517 }
518
519 ExtensionPanelImpl.prototype = {
520 /**
521 * @return {!Object}
522 */
523 createStatusBarButton: function(iconPath, tooltipText, disabled) {
524 const id = 'button-' + extensionServer.nextObjectId();
525 const request = {
526 command: commands.CreateToolbarButton,
527 panel: this._id,
528 id: id,
529 icon: iconPath,
530 tooltip: tooltipText,
531 disabled: !!disabled
532 };
533 extensionServer.sendRequest(request);
534 return new Button(id);
535 },
536
537 show: function() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34538 if (!userAction) {
Blink Reformat4c46d092018-04-07 15:32:37539 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34540 }
Blink Reformat4c46d092018-04-07 15:32:37541
542 const request = {command: commands.ShowPanel, id: this._id};
543 extensionServer.sendRequest(request);
544 },
545
546 __proto__: ExtensionViewImpl.prototype
547 };
548
549 /**
550 * @constructor
551 * @extends {ExtensionViewImpl}
552 */
553 function ExtensionSidebarPaneImpl(id) {
554 ExtensionViewImpl.call(this, id);
555 }
556
557 ExtensionSidebarPaneImpl.prototype = {
558 setHeight: function(height) {
559 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
560 },
561
562 setExpression: function(expression, rootTitle, evaluateOptions) {
563 const request = {
564 command: commands.SetSidebarContent,
565 id: this._id,
566 expression: expression,
567 rootTitle: rootTitle,
568 evaluateOnPage: true,
569 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34570 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37571 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34572 }
Blink Reformat4c46d092018-04-07 15:32:37573 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
574 },
575
576 setObject: function(jsonObject, rootTitle, callback) {
577 extensionServer.sendRequest(
578 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
579 },
580
581 setPage: function(page) {
582 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
583 },
584
585 __proto__: ExtensionViewImpl.prototype
586 };
587
588 /**
589 * @constructor
590 */
591 function ButtonImpl(id) {
592 this._id = id;
593 this.onClicked = new EventSink(events.ButtonClicked + id);
594 }
595
596 ButtonImpl.prototype = {
597 update: function(iconPath, tooltipText, disabled) {
598 const request =
599 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
600 extensionServer.sendRequest(request);
601 }
602 };
603
604 /**
605 * @constructor
606 */
607 function Timeline() {
608 }
609
610 Timeline.prototype = {
611 /**
612 * @param {string} categoryName
613 * @param {string} categoryTooltip
614 * @return {!TraceProvider}
615 */
616 addTraceProvider: function(categoryName, categoryTooltip) {
617 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
618 extensionServer.sendRequest(
619 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
620 return new TraceProvider(id);
621 }
622 };
623
624 /**
625 * @constructor
626 * @param {string} id
627 */
628 function TraceSessionImpl(id) {
629 this._id = id;
630 }
631
632 TraceSessionImpl.prototype = {
633 /**
634 * @param {string=} url
635 * @param {number=} timeOffset
636 */
637 complete: function(url, timeOffset) {
638 const request =
639 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
640 extensionServer.sendRequest(request);
641 }
642 };
643
644 /**
645 * @constructor
646 * @param {string} id
647 */
648 function TraceProvider(id) {
649 /**
650 * @this {EventSinkImpl}
651 */
652 function dispatchRecordingStarted(message) {
653 const sessionId = message.arguments[0];
654 this._fire(new TraceSession(sessionId));
655 }
656
657 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
658 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
659 }
660
661 /**
662 * @constructor
663 */
664 function InspectedWindow() {
665 /**
666 * @this {EventSinkImpl}
667 */
668 function dispatchResourceEvent(message) {
669 this._fire(new Resource(message.arguments[0]));
670 }
671
672 /**
673 * @this {EventSinkImpl}
674 */
675 function dispatchResourceContentEvent(message) {
676 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
677 }
678
679 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
680 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
681 }
682
683 InspectedWindow.prototype = {
684 reload: function(optionsOrUserAgent) {
685 let options = null;
686 if (typeof optionsOrUserAgent === 'object') {
687 options = optionsOrUserAgent;
688 } else if (typeof optionsOrUserAgent === 'string') {
689 options = {userAgent: optionsOrUserAgent};
690 console.warn(
691 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
692 'Use inspectedWindow.reload({ userAgent: value}) instead.');
693 }
694 extensionServer.sendRequest({command: commands.Reload, options: options});
695 },
696
697 /**
698 * @return {?Object}
699 */
700 eval: function(expression, evaluateOptions) {
701 const callback = extractCallbackArgument(arguments);
702 function callbackWrapper(result) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34703 if (result.isError || result.isException) {
Blink Reformat4c46d092018-04-07 15:32:37704 callback(undefined, result);
Tim van der Lippe1d6e57a2019-09-30 11:55:34705 } else {
Blink Reformat4c46d092018-04-07 15:32:37706 callback(result.value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34707 }
Blink Reformat4c46d092018-04-07 15:32:37708 }
709 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
Tim van der Lippe1d6e57a2019-09-30 11:55:34710 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37711 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34712 }
Blink Reformat4c46d092018-04-07 15:32:37713 extensionServer.sendRequest(request, callback && callbackWrapper);
714 return null;
715 },
716
717 getResources: function(callback) {
718 function wrapResource(resourceData) {
719 return new Resource(resourceData);
720 }
721 function callbackWrapper(resources) {
722 callback(resources.map(wrapResource));
723 }
724 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
725 }
726 };
727
728 /**
729 * @constructor
730 */
731 function ResourceImpl(resourceData) {
732 this._url = resourceData.url;
733 this._type = resourceData.type;
734 }
735
736 ResourceImpl.prototype = {
737 get url() {
738 return this._url;
739 },
740
741 get type() {
742 return this._type;
743 },
744
745 getContent: function(callback) {
746 function callbackWrapper(response) {
747 callback(response.content, response.encoding);
748 }
749
750 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
751 },
752
753 setContent: function(content, commit, callback) {
754 extensionServer.sendRequest(
755 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
756 }
757 };
758
759 function getTabId() {
760 return inspectedTabId;
761 }
762
763 let keyboardEventRequestQueue = [];
764 let forwardTimer = null;
Blink Reformat4c46d092018-04-07 15:32:37765 function forwardKeyboardEvent(event) {
Jan Schefflere7d7bb12019-10-24 09:18:52766 // Check if the event should be forwarded.
767 // This is a workaround for crbug.com/923338.
768 const focused = document.activeElement;
769 if (focused) {
770 const isInput = focused.nodeName === 'INPUT' || focused.nodeName === 'TEXTAREA';
771 if (isInput && !(event.ctrlKey || event.altKey || event.metaKey)) {
772 return;
773 }
774 }
775
Joel Einbinder67f28fb2018-08-02 00:33:47776 let modifiers = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34777 if (event.shiftKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47778 modifiers |= 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34779 }
780 if (event.ctrlKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47781 modifiers |= 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:34782 }
783 if (event.altKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47784 modifiers |= 4;
Tim van der Lippe1d6e57a2019-09-30 11:55:34785 }
786 if (event.metaKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47787 modifiers |= 8;
Tim van der Lippe1d6e57a2019-09-30 11:55:34788 }
Joel Einbinder67f28fb2018-08-02 00:33:47789 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37790 // We only care about global hotkeys, not about random text
Tim van der Lippe1d6e57a2019-09-30 11:55:34791 if (!keysToForwardSet.has(num)) {
Blink Reformat4c46d092018-04-07 15:32:37792 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34793 }
Joel Einbinder67f28fb2018-08-02 00:33:47794 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37795 const requestPayload = {
796 eventType: event.type,
797 ctrlKey: event.ctrlKey,
798 altKey: event.altKey,
799 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47800 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37801 keyIdentifier: event.keyIdentifier,
802 key: event.key,
803 code: event.code,
804 location: event.location,
805 keyCode: event.keyCode
806 };
807 keyboardEventRequestQueue.push(requestPayload);
Tim van der Lippe1d6e57a2019-09-30 11:55:34808 if (!forwardTimer) {
Blink Reformat4c46d092018-04-07 15:32:37809 forwardTimer = setTimeout(forwardEventQueue, 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34810 }
Blink Reformat4c46d092018-04-07 15:32:37811 }
812
813 function forwardEventQueue() {
814 forwardTimer = null;
815 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
816 extensionServer.sendRequest(request);
817 keyboardEventRequestQueue = [];
818 }
819
820 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37821
822 /**
823 * @constructor
824 */
825 function ExtensionServerClient() {
826 this._callbacks = {};
827 this._handlers = {};
828 this._lastRequestId = 0;
829 this._lastObjectId = 0;
830
831 this.registerHandler('callback', this._onCallback.bind(this));
832
833 const channel = new MessageChannel();
834 this._port = channel.port1;
835 this._port.addEventListener('message', this._onMessage.bind(this), false);
836 this._port.start();
837
838 window.parent.postMessage('registerExtension', '*', [channel.port2]);
839 }
840
841 ExtensionServerClient.prototype = {
842 /**
843 * @param {!Object} message
844 * @param {function()=} callback
Philip Pfaffeedad8322020-07-20 10:24:25845 * @param {!Array<*>=} transfers
Blink Reformat4c46d092018-04-07 15:32:37846 */
Philip Pfaffeedad8322020-07-20 10:24:25847 sendRequest: function(message, callback, transfers) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34848 if (typeof callback === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37849 message.requestId = this._registerCallback(callback);
Tim van der Lippe1d6e57a2019-09-30 11:55:34850 }
Philip Pfaffeedad8322020-07-20 10:24:25851 this._port.postMessage(message, transfers);
Blink Reformat4c46d092018-04-07 15:32:37852 },
853
854 /**
855 * @return {boolean}
856 */
857 hasHandler: function(command) {
858 return !!this._handlers[command];
859 },
860
861 registerHandler: function(command, handler) {
862 this._handlers[command] = handler;
863 },
864
865 unregisterHandler: function(command) {
866 delete this._handlers[command];
867 },
868
869 /**
870 * @return {string}
871 */
872 nextObjectId: function() {
873 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
874 },
875
876 _registerCallback: function(callback) {
877 const id = ++this._lastRequestId;
878 this._callbacks[id] = callback;
879 return id;
880 },
881
882 _onCallback: function(request) {
883 if (request.requestId in this._callbacks) {
884 const callback = this._callbacks[request.requestId];
885 delete this._callbacks[request.requestId];
886 callback(request.result);
887 }
888 },
889
890 _onMessage: function(event) {
891 const request = event.data;
892 const handler = this._handlers[request.command];
Tim van der Lippe1d6e57a2019-09-30 11:55:34893 if (handler) {
Blink Reformat4c46d092018-04-07 15:32:37894 handler.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34895 }
Blink Reformat4c46d092018-04-07 15:32:37896 }
897 };
898
899 function populateInterfaceClass(interfaze, implementation) {
900 for (const member in implementation) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34901 if (member.charAt(0) === '_') {
Blink Reformat4c46d092018-04-07 15:32:37902 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34903 }
Blink Reformat4c46d092018-04-07 15:32:37904 let descriptor = null;
905 // Traverse prototype chain until we find the owner.
Tim van der Lippe1d6e57a2019-09-30 11:55:34906 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
Blink Reformat4c46d092018-04-07 15:32:37907 descriptor = Object.getOwnPropertyDescriptor(owner, member);
Tim van der Lippe1d6e57a2019-09-30 11:55:34908 }
909 if (!descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37910 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34911 }
912 if (typeof descriptor.value === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37913 interfaze[member] = descriptor.value.bind(implementation);
Tim van der Lippe1d6e57a2019-09-30 11:55:34914 } else if (typeof descriptor.get === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37915 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
Tim van der Lippe1d6e57a2019-09-30 11:55:34916 } else {
Blink Reformat4c46d092018-04-07 15:32:37917 Object.defineProperty(interfaze, member, descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34918 }
Blink Reformat4c46d092018-04-07 15:32:37919 }
920 }
921
922 const extensionServer = new ExtensionServerClient();
923 const coreAPI = new InspectorExtensionAPI();
924
925 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
926
927 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
928 chrome.devtools.inspectedWindow = {};
Tim van der Lippeffa78622019-09-16 12:07:12929 Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
Blink Reformat4c46d092018-04-07 15:32:37930 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
931 chrome.devtools.network = coreAPI.network;
932 chrome.devtools.panels = coreAPI.panels;
933 chrome.devtools.panels.themeName = themeName;
Kim-Anh Tran45ba0b32020-08-13 06:32:31934 chrome.devtools.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37935
936 // default to expose experimental APIs for now.
937 if (extensionInfo.exposeExperimentalAPIs !== false) {
938 chrome.experimental = chrome.experimental || {};
939 chrome.experimental.devtools = chrome.experimental.devtools || {};
940
941 const properties = Object.getOwnPropertyNames(coreAPI);
942 for (let i = 0; i < properties.length; ++i) {
943 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34944 if (descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37945 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34946 }
Blink Reformat4c46d092018-04-07 15:32:37947 }
948 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
949 }
950
Tim van der Lippe1d6e57a2019-09-30 11:55:34951 if (extensionInfo.exposeWebInspectorNamespace) {
Blink Reformat4c46d092018-04-07 15:32:37952 window.webInspector = coreAPI;
Tim van der Lippe1d6e57a2019-09-30 11:55:34953 }
Blink Reformat4c46d092018-04-07 15:32:37954 testHook(extensionServer, coreAPI);
Tim van der Lippe226fc222019-10-10 12:17:12955};
Blink Reformat4c46d092018-04-07 15:32:37956
957/**
Tim van der Lipped71c22d2020-03-19 12:29:19958 * @param {!{startPage: string, name: string, exposeExperimentalAPIs: boolean}} extensionInfo
Blink Reformat4c46d092018-04-07 15:32:37959 * @param {string} inspectedTabId
960 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47961 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37962 * @param {function(!Object, !Object)|undefined} testHook
963 * @return {string}
964 */
Tim van der Lippe29fab472019-08-15 14:46:48965self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Philip Pfaffeedad8322020-07-20 10:24:25966 const argumentsJSON =
967 [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Tim van der Lippe1d6e57a2019-09-30 11:55:34968 if (!testHook) {
Blink Reformat4c46d092018-04-07 15:32:37969 testHook = () => {};
Tim van der Lippe1d6e57a2019-09-30 11:55:34970 }
Blink Reformat4c46d092018-04-07 15:32:37971 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
Tim van der Lippe226fc222019-10-10 12:17:12972 '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
Blink Reformat4c46d092018-04-07 15:32:37973 '})';
Tim van der Lippe29fab472019-08-15 14:46:48974};