blob: 22c0e9bca9043378214fc2f273246f83b9cbe269 [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',
Philip Pfaffe7e36e272020-12-09 15:23:04103 GetInlinedCalleesRanges: 'getInlinedCalleesRanges',
104 GetMappedLines: 'getMappedLines',
Blink Reformat4c46d092018-04-07 15:32:37105 };
Benedikt Meurer929fc7c2020-11-20 14:21:06106
107 /** @enum {string} */
108 apiPrivate.LanguageExtensionPluginEvents = {
109 UnregisteredLanguageExtensionPlugin: 'unregisteredLanguageExtensionPlugin'
110 };
Blink Reformat4c46d092018-04-07 15:32:37111}
112
113/**
114 * @param {!ExtensionDescriptor} extensionInfo
115 * @param {string} inspectedTabId
116 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47117 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37118 * @param {number} injectedScriptId
119 * @param {function(!Object, !Object)} testHook
Blink Reformat4c46d092018-04-07 15:32:37120 */
Tim van der Lippe226fc222019-10-10 12:17:12121self.injectedExtensionAPI = function(
122 extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
Joel Einbinder67f28fb2018-08-02 00:33:47123 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:37124 const chrome = window.chrome || {};
125 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
Tim van der Lippe1d6e57a2019-09-30 11:55:34126 if (devtools_descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37127 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34128 }
Blink Reformat4c46d092018-04-07 15:32:37129
130 const apiPrivate = {};
131
132 defineCommonExtensionSymbols(apiPrivate);
133
134 const commands = apiPrivate.Commands;
Philip Pfaffeedad8322020-07-20 10:24:25135 const languageExtensionPluginCommands = apiPrivate.LanguageExtensionPluginCommands;
Benedikt Meurer929fc7c2020-11-20 14:21:06136 const languageExtensionPluginEvents = apiPrivate.LanguageExtensionPluginEvents;
Blink Reformat4c46d092018-04-07 15:32:37137 const events = apiPrivate.Events;
138 let userAction = false;
139
140 // Here and below, all constructors are private to API implementation.
141 // For a public type Foo, if internal fields are present, these are on
142 // a private FooImpl type, an instance of FooImpl is used in a closure
143 // by Foo consutrctor to re-bind publicly exported members to an instance
144 // of Foo.
145
146 /**
147 * @constructor
148 */
149 function EventSinkImpl(type, customDispatch) {
150 this._type = type;
151 this._listeners = [];
152 this._customDispatch = customDispatch;
153 }
154
155 EventSinkImpl.prototype = {
156 addListener: function(callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34157 if (typeof callback !== 'function') {
Blink Reformat4c46d092018-04-07 15:32:37158 throw 'addListener: callback is not a function';
Tim van der Lippe1d6e57a2019-09-30 11:55:34159 }
160 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37161 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34162 }
Blink Reformat4c46d092018-04-07 15:32:37163 this._listeners.push(callback);
164 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
165 },
166
167 removeListener: function(callback) {
168 const listeners = this._listeners;
169
170 for (let i = 0; i < listeners.length; ++i) {
171 if (listeners[i] === callback) {
172 listeners.splice(i, 1);
173 break;
174 }
175 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34176 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37177 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34178 }
Blink Reformat4c46d092018-04-07 15:32:37179 },
180
181 /**
182 * @param {...} vararg
183 */
184 _fire: function(vararg) {
185 const listeners = this._listeners.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34186 for (let i = 0; i < listeners.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37187 listeners[i].apply(null, arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34188 }
Blink Reformat4c46d092018-04-07 15:32:37189 },
190
191 _dispatch: function(request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34192 if (this._customDispatch) {
Blink Reformat4c46d092018-04-07 15:32:37193 this._customDispatch.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34194 } else {
Blink Reformat4c46d092018-04-07 15:32:37195 this._fire.apply(this, request.arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34196 }
Blink Reformat4c46d092018-04-07 15:32:37197 }
198 };
199
200 /**
201 * @constructor
202 */
203 function InspectorExtensionAPI() {
204 this.inspectedWindow = new InspectedWindow();
205 this.panels = new Panels();
206 this.network = new Network();
207 this.timeline = new Timeline();
Philip Pfaffeedad8322020-07-20 10:24:25208 this.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37209 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
210 }
211
212 /**
213 * @constructor
214 */
215 function Network() {
216 /**
217 * @this {EventSinkImpl}
218 */
219 function dispatchRequestEvent(message) {
220 const request = message.arguments[1];
221 request.__proto__ = new Request(message.arguments[0]);
222 this._fire(request);
223 }
224 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
225 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
226 this.onNavigated = new EventSink(events.InspectedURLChanged);
227 }
228
229 Network.prototype = {
230 getHAR: function(callback) {
231 function callbackWrapper(result) {
232 const entries = (result && result.entries) || [];
233 for (let i = 0; i < entries.length; ++i) {
234 entries[i].__proto__ = new Request(entries[i]._requestId);
235 delete entries[i]._requestId;
236 }
237 callback(result);
238 }
239 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
240 },
241
242 addRequestHeaders: function(headers) {
243 extensionServer.sendRequest(
244 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
245 }
246 };
247
248 /**
249 * @constructor
250 */
251 function RequestImpl(id) {
252 this._id = id;
253 }
254
255 RequestImpl.prototype = {
256 getContent: function(callback) {
257 function callbackWrapper(response) {
258 callback(response.content, response.encoding);
259 }
260 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
261 }
262 };
263
264 /**
265 * @constructor
266 */
267 function Panels() {
268 const panels = {
269 elements: new ElementsPanel(),
270 sources: new SourcesPanel(),
271 };
272
273 function panelGetter(name) {
274 return panels[name];
275 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34276 for (const panel in panels) {
Tim van der Lippeffa78622019-09-16 12:07:12277 Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
Tim van der Lippe1d6e57a2019-09-30 11:55:34278 }
Blink Reformat4c46d092018-04-07 15:32:37279 this.applyStyleSheet = function(styleSheet) {
280 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
281 };
282 }
283
284 Panels.prototype = {
285 create: function(title, icon, page, callback) {
286 const id = 'extension-panel-' + extensionServer.nextObjectId();
287 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
288 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
289 },
290
291 setOpenResourceHandler: function(callback) {
292 const hadHandler = extensionServer.hasHandler(events.OpenResource);
293
294 function callbackWrapper(message) {
295 // Allow the panel to show itself when handling the event.
296 userAction = true;
297 try {
298 callback.call(null, new Resource(message.resource), message.lineNumber);
299 } finally {
300 userAction = false;
301 }
302 }
303
Tim van der Lippe1d6e57a2019-09-30 11:55:34304 if (!callback) {
Blink Reformat4c46d092018-04-07 15:32:37305 extensionServer.unregisterHandler(events.OpenResource);
Tim van der Lippe1d6e57a2019-09-30 11:55:34306 } else {
Blink Reformat4c46d092018-04-07 15:32:37307 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
Tim van der Lippe1d6e57a2019-09-30 11:55:34308 }
Blink Reformat4c46d092018-04-07 15:32:37309
310 // 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:34311 if (hadHandler === !callback) {
Blink Reformat4c46d092018-04-07 15:32:37312 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
Tim van der Lippe1d6e57a2019-09-30 11:55:34313 }
Blink Reformat4c46d092018-04-07 15:32:37314 },
315
316 openResource: function(url, lineNumber, callback) {
317 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
318 },
319
320 get SearchAction() {
321 return apiPrivate.panels.SearchAction;
322 }
323 };
324
325 /**
326 * @constructor
327 */
328 function ExtensionViewImpl(id) {
329 this._id = id;
330
331 /**
332 * @this {EventSinkImpl}
333 */
334 function dispatchShowEvent(message) {
335 const frameIndex = message.arguments[0];
Tim van der Lippe1d6e57a2019-09-30 11:55:34336 if (typeof frameIndex === 'number') {
Blink Reformat4c46d092018-04-07 15:32:37337 this._fire(window.parent.frames[frameIndex]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34338 } else {
Blink Reformat4c46d092018-04-07 15:32:37339 this._fire();
Tim van der Lippe1d6e57a2019-09-30 11:55:34340 }
Blink Reformat4c46d092018-04-07 15:32:37341 }
342
343 if (id) {
344 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
345 this.onHidden = new EventSink(events.ViewHidden + id);
346 }
347 }
348
349 /**
350 * @constructor
351 * @extends {ExtensionViewImpl}
352 * @param {string} hostPanelName
353 */
354 function PanelWithSidebarImpl(hostPanelName) {
355 ExtensionViewImpl.call(this, null);
356 this._hostPanelName = hostPanelName;
357 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
358 }
359
360 PanelWithSidebarImpl.prototype = {
361 createSidebarPane: function(title, callback) {
362 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
363 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
364 function callbackWrapper() {
365 callback(new ExtensionSidebarPane(id));
366 }
367 extensionServer.sendRequest(request, callback && callbackWrapper);
368 },
369
370 __proto__: ExtensionViewImpl.prototype
371 };
372
Philip Pfaffeedad8322020-07-20 10:24:25373 /**
374 * @constructor
375 */
376 function LanguageServicesAPIImpl() {
Benedikt Meurer929fc7c2020-11-20 14:21:06377 /** @type {!Map<*, !MessagePort>} */
Philip Pfaffeedad8322020-07-20 10:24:25378 this._plugins = new Map();
379 }
380
381 LanguageServicesAPIImpl.prototype = {
382 /**
383 * @param {*} plugin The language plugin instance to register.
384 * @param {string} pluginName The plugin name
385 * @param {{language: string, symbol_types: !Array<string>}} supportedScriptTypes Script language and debug symbol types supported by this extension.
Benedikt Meurer929fc7c2020-11-20 14:21:06386 * @return {!Promise<void>}
Philip Pfaffeedad8322020-07-20 10:24:25387 */
Benedikt Meurer929fc7c2020-11-20 14:21:06388 registerLanguageExtensionPlugin: async function(plugin, pluginName, supportedScriptTypes) {
Philip Pfaffeedad8322020-07-20 10:24:25389 if (this._plugins.has(plugin)) {
Benedikt Meurer929fc7c2020-11-20 14:21:06390 throw new Error(`Tried to register plugin '${pluginName}' twice`);
Philip Pfaffeedad8322020-07-20 10:24:25391 }
392 const channel = new MessageChannel();
393 const port = channel.port1;
394 this._plugins.set(plugin, port);
395 port.onmessage = ({data: {requestId, method, parameters}}) => {
Benedikt Meurer99b14a92020-12-09 07:44:07396 console.time(`${requestId}: ${method}`);
Philip Pfaffeedad8322020-07-20 10:24:25397 dispatchMethodCall(method, parameters)
398 .then(result => port.postMessage({requestId, result}))
Benedikt Meurer99b14a92020-12-09 07:44:07399 .catch(error => port.postMessage({requestId, error: {message: error.message}}))
400 .finally(() => console.timeEnd(`${requestId}: ${method}`));
Philip Pfaffeedad8322020-07-20 10:24:25401 };
402
403 /**
404 * @param {string} method
405 * @param {*} parameters
406 * @return {!Promise<*>}
407 */
408 function dispatchMethodCall(method, parameters) {
409 switch (method) {
410 case languageExtensionPluginCommands.AddRawModule:
411 return plugin.addRawModule(parameters.rawModuleId, parameters.symbolsURL, parameters.rawModule);
412 case languageExtensionPluginCommands.RemoveRawModule:
413 return plugin.removeRawModule(parameters.rawModuleId);
414 case languageExtensionPluginCommands.SourceLocationToRawLocation:
415 return plugin.sourceLocationToRawLocation(parameters.sourceLocation);
416 case languageExtensionPluginCommands.RawLocationToSourceLocation:
417 return plugin.rawLocationToSourceLocation(parameters.rawLocation);
Benedikt Meurer723d5432020-10-09 09:02:42418 case languageExtensionPluginCommands.GetScopeInfo:
419 return plugin.getScopeInfo(parameters.type);
Philip Pfaffeedad8322020-07-20 10:24:25420 case languageExtensionPluginCommands.ListVariablesInScope:
421 return plugin.listVariablesInScope(parameters.rawLocation);
422 case languageExtensionPluginCommands.EvaluateVariable:
423 return plugin.evaluateVariable(parameters.name, parameters.location);
Philip Pfaffe480fa882020-10-22 09:38:36424 case languageExtensionPluginCommands.GetTypeInfo:
425 return plugin.getTypeInfo(parameters.expression, parameters.context);
426 case languageExtensionPluginCommands.GetFormatter:
427 return plugin.getFormatter(parameters.expressionOrField, parameters.context);
Eric Leese5aaa2222020-10-01 11:49:35428 case languageExtensionPluginCommands.GetFunctionInfo:
429 return plugin.getFunctionInfo(parameters.rawLocation);
Eric Leese553115e2020-10-19 12:09:04430 case languageExtensionPluginCommands.GetInlinedFunctionRanges:
431 return plugin.getInlinedFunctionRanges(parameters.rawLocation);
432 case languageExtensionPluginCommands.GetInlinedCalleesRanges:
433 return plugin.getInlinedCalleesRanges(parameters.rawLocation);
Philip Pfaffe7e36e272020-12-09 15:23:04434 case languageExtensionPluginCommands.GetMappedLines:
435 if ('getMappedLines' in plugin) {
436 return plugin.getMappedLines(parameters.rawModuleId, parameters.sourceFileURL);
437 }
438 return Promise.resolve(undefined);
Philip Pfaffeedad8322020-07-20 10:24:25439 }
440 throw new Error(`Unknown language plugin method ${method}`);
441 }
442
Benedikt Meurer929fc7c2020-11-20 14:21:06443 await new Promise(resolve => {
444 extensionServer.sendRequest(
445 {command: commands.RegisterLanguageExtensionPlugin, pluginName, port: channel.port2, supportedScriptTypes},
446 () => resolve(), [channel.port2]);
447 });
448 },
449
450 /**
451 * @param {*} plugin The language plugin instance to unregister.
452 * @return {!Promise<void>}
453 */
454 unregisterLanguageExtensionPlugin: async function(plugin) {
455 const port = this._plugins.get(plugin);
456 if (!port) {
457 throw new Error('Tried to unregister a plugin that was not previously registered');
458 }
459 this._plugins.delete(plugin);
460 port.postMessage({event: languageExtensionPluginEvents.UnregisteredLanguageExtensionPlugin});
461 port.close();
Philip Pfaffeedad8322020-07-20 10:24:25462 }
463 };
464
Blink Reformat4c46d092018-04-07 15:32:37465 function declareInterfaceClass(implConstructor) {
466 return function() {
467 const impl = {__proto__: implConstructor.prototype};
468 implConstructor.apply(impl, arguments);
469 populateInterfaceClass(this, impl);
470 };
471 }
472
473 function defineDeprecatedProperty(object, className, oldName, newName) {
474 let warningGiven = false;
475 function getter() {
476 if (!warningGiven) {
477 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
478 warningGiven = true;
479 }
480 return object[newName];
481 }
482 object.__defineGetter__(oldName, getter);
483 }
484
485 function extractCallbackArgument(args) {
486 const lastArgument = args[args.length - 1];
487 return typeof lastArgument === 'function' ? lastArgument : undefined;
488 }
489
Philip Pfaffeedad8322020-07-20 10:24:25490 const LanguageServicesAPI = declareInterfaceClass(LanguageServicesAPIImpl);
Blink Reformat4c46d092018-04-07 15:32:37491 const Button = declareInterfaceClass(ButtonImpl);
492 const EventSink = declareInterfaceClass(EventSinkImpl);
493 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
494 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
Tim van der Lippeffa78622019-09-16 12:07:12495 /**
496 * @constructor
497 * @param {string} hostPanelName
498 */
499 const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
Blink Reformat4c46d092018-04-07 15:32:37500 const Request = declareInterfaceClass(RequestImpl);
501 const Resource = declareInterfaceClass(ResourceImpl);
502 const TraceSession = declareInterfaceClass(TraceSessionImpl);
503
Tim van der Lippeffa78622019-09-16 12:07:12504 class ElementsPanel extends PanelWithSidebarClass {
505 constructor() {
506 super('elements');
507 }
Blink Reformat4c46d092018-04-07 15:32:37508 }
509
Tim van der Lippeffa78622019-09-16 12:07:12510 class SourcesPanel extends PanelWithSidebarClass {
511 constructor() {
512 super('sources');
513 }
Blink Reformat4c46d092018-04-07 15:32:37514 }
515
Blink Reformat4c46d092018-04-07 15:32:37516 /**
517 * @constructor
518 * @extends {ExtensionViewImpl}
519 */
520 function ExtensionPanelImpl(id) {
521 ExtensionViewImpl.call(this, id);
522 this.onSearch = new EventSink(events.PanelSearch + id);
523 }
524
525 ExtensionPanelImpl.prototype = {
526 /**
527 * @return {!Object}
528 */
529 createStatusBarButton: function(iconPath, tooltipText, disabled) {
530 const id = 'button-' + extensionServer.nextObjectId();
531 const request = {
532 command: commands.CreateToolbarButton,
533 panel: this._id,
534 id: id,
535 icon: iconPath,
536 tooltip: tooltipText,
537 disabled: !!disabled
538 };
539 extensionServer.sendRequest(request);
540 return new Button(id);
541 },
542
543 show: function() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34544 if (!userAction) {
Blink Reformat4c46d092018-04-07 15:32:37545 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34546 }
Blink Reformat4c46d092018-04-07 15:32:37547
548 const request = {command: commands.ShowPanel, id: this._id};
549 extensionServer.sendRequest(request);
550 },
551
552 __proto__: ExtensionViewImpl.prototype
553 };
554
555 /**
556 * @constructor
557 * @extends {ExtensionViewImpl}
558 */
559 function ExtensionSidebarPaneImpl(id) {
560 ExtensionViewImpl.call(this, id);
561 }
562
563 ExtensionSidebarPaneImpl.prototype = {
564 setHeight: function(height) {
565 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
566 },
567
568 setExpression: function(expression, rootTitle, evaluateOptions) {
569 const request = {
570 command: commands.SetSidebarContent,
571 id: this._id,
572 expression: expression,
573 rootTitle: rootTitle,
574 evaluateOnPage: true,
575 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34576 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37577 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34578 }
Blink Reformat4c46d092018-04-07 15:32:37579 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
580 },
581
582 setObject: function(jsonObject, rootTitle, callback) {
583 extensionServer.sendRequest(
584 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
585 },
586
587 setPage: function(page) {
588 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
589 },
590
591 __proto__: ExtensionViewImpl.prototype
592 };
593
594 /**
595 * @constructor
596 */
597 function ButtonImpl(id) {
598 this._id = id;
599 this.onClicked = new EventSink(events.ButtonClicked + id);
600 }
601
602 ButtonImpl.prototype = {
603 update: function(iconPath, tooltipText, disabled) {
604 const request =
605 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
606 extensionServer.sendRequest(request);
607 }
608 };
609
610 /**
611 * @constructor
612 */
613 function Timeline() {
614 }
615
616 Timeline.prototype = {
617 /**
618 * @param {string} categoryName
619 * @param {string} categoryTooltip
620 * @return {!TraceProvider}
621 */
622 addTraceProvider: function(categoryName, categoryTooltip) {
623 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
624 extensionServer.sendRequest(
625 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
626 return new TraceProvider(id);
627 }
628 };
629
630 /**
631 * @constructor
632 * @param {string} id
633 */
634 function TraceSessionImpl(id) {
635 this._id = id;
636 }
637
638 TraceSessionImpl.prototype = {
639 /**
640 * @param {string=} url
641 * @param {number=} timeOffset
642 */
643 complete: function(url, timeOffset) {
644 const request =
645 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
646 extensionServer.sendRequest(request);
647 }
648 };
649
650 /**
651 * @constructor
652 * @param {string} id
653 */
654 function TraceProvider(id) {
655 /**
656 * @this {EventSinkImpl}
657 */
658 function dispatchRecordingStarted(message) {
659 const sessionId = message.arguments[0];
660 this._fire(new TraceSession(sessionId));
661 }
662
663 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
664 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
665 }
666
667 /**
668 * @constructor
669 */
670 function InspectedWindow() {
671 /**
672 * @this {EventSinkImpl}
673 */
674 function dispatchResourceEvent(message) {
675 this._fire(new Resource(message.arguments[0]));
676 }
677
678 /**
679 * @this {EventSinkImpl}
680 */
681 function dispatchResourceContentEvent(message) {
682 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
683 }
684
685 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
686 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
687 }
688
689 InspectedWindow.prototype = {
690 reload: function(optionsOrUserAgent) {
691 let options = null;
692 if (typeof optionsOrUserAgent === 'object') {
693 options = optionsOrUserAgent;
694 } else if (typeof optionsOrUserAgent === 'string') {
695 options = {userAgent: optionsOrUserAgent};
696 console.warn(
697 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
698 'Use inspectedWindow.reload({ userAgent: value}) instead.');
699 }
700 extensionServer.sendRequest({command: commands.Reload, options: options});
701 },
702
703 /**
704 * @return {?Object}
705 */
706 eval: function(expression, evaluateOptions) {
707 const callback = extractCallbackArgument(arguments);
708 function callbackWrapper(result) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34709 if (result.isError || result.isException) {
Blink Reformat4c46d092018-04-07 15:32:37710 callback(undefined, result);
Tim van der Lippe1d6e57a2019-09-30 11:55:34711 } else {
Blink Reformat4c46d092018-04-07 15:32:37712 callback(result.value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34713 }
Blink Reformat4c46d092018-04-07 15:32:37714 }
715 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
Tim van der Lippe1d6e57a2019-09-30 11:55:34716 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37717 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34718 }
Blink Reformat4c46d092018-04-07 15:32:37719 extensionServer.sendRequest(request, callback && callbackWrapper);
720 return null;
721 },
722
723 getResources: function(callback) {
724 function wrapResource(resourceData) {
725 return new Resource(resourceData);
726 }
727 function callbackWrapper(resources) {
728 callback(resources.map(wrapResource));
729 }
730 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
731 }
732 };
733
734 /**
735 * @constructor
736 */
737 function ResourceImpl(resourceData) {
738 this._url = resourceData.url;
739 this._type = resourceData.type;
740 }
741
742 ResourceImpl.prototype = {
743 get url() {
744 return this._url;
745 },
746
747 get type() {
748 return this._type;
749 },
750
751 getContent: function(callback) {
752 function callbackWrapper(response) {
753 callback(response.content, response.encoding);
754 }
755
756 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
757 },
758
759 setContent: function(content, commit, callback) {
760 extensionServer.sendRequest(
761 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
762 }
763 };
764
765 function getTabId() {
766 return inspectedTabId;
767 }
768
769 let keyboardEventRequestQueue = [];
770 let forwardTimer = null;
Blink Reformat4c46d092018-04-07 15:32:37771 function forwardKeyboardEvent(event) {
Jan Schefflere7d7bb12019-10-24 09:18:52772 // Check if the event should be forwarded.
773 // This is a workaround for crbug.com/923338.
774 const focused = document.activeElement;
775 if (focused) {
776 const isInput = focused.nodeName === 'INPUT' || focused.nodeName === 'TEXTAREA';
777 if (isInput && !(event.ctrlKey || event.altKey || event.metaKey)) {
778 return;
779 }
780 }
781
Joel Einbinder67f28fb2018-08-02 00:33:47782 let modifiers = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34783 if (event.shiftKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47784 modifiers |= 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34785 }
786 if (event.ctrlKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47787 modifiers |= 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:34788 }
789 if (event.altKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47790 modifiers |= 4;
Tim van der Lippe1d6e57a2019-09-30 11:55:34791 }
792 if (event.metaKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47793 modifiers |= 8;
Tim van der Lippe1d6e57a2019-09-30 11:55:34794 }
Joel Einbinder67f28fb2018-08-02 00:33:47795 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37796 // We only care about global hotkeys, not about random text
Tim van der Lippe1d6e57a2019-09-30 11:55:34797 if (!keysToForwardSet.has(num)) {
Blink Reformat4c46d092018-04-07 15:32:37798 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34799 }
Joel Einbinder67f28fb2018-08-02 00:33:47800 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37801 const requestPayload = {
802 eventType: event.type,
803 ctrlKey: event.ctrlKey,
804 altKey: event.altKey,
805 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47806 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37807 keyIdentifier: event.keyIdentifier,
808 key: event.key,
809 code: event.code,
810 location: event.location,
811 keyCode: event.keyCode
812 };
813 keyboardEventRequestQueue.push(requestPayload);
Tim van der Lippe1d6e57a2019-09-30 11:55:34814 if (!forwardTimer) {
Blink Reformat4c46d092018-04-07 15:32:37815 forwardTimer = setTimeout(forwardEventQueue, 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34816 }
Blink Reformat4c46d092018-04-07 15:32:37817 }
818
819 function forwardEventQueue() {
820 forwardTimer = null;
821 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
822 extensionServer.sendRequest(request);
823 keyboardEventRequestQueue = [];
824 }
825
826 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37827
828 /**
829 * @constructor
830 */
831 function ExtensionServerClient() {
832 this._callbacks = {};
833 this._handlers = {};
834 this._lastRequestId = 0;
835 this._lastObjectId = 0;
836
837 this.registerHandler('callback', this._onCallback.bind(this));
838
839 const channel = new MessageChannel();
840 this._port = channel.port1;
841 this._port.addEventListener('message', this._onMessage.bind(this), false);
842 this._port.start();
843
844 window.parent.postMessage('registerExtension', '*', [channel.port2]);
845 }
846
847 ExtensionServerClient.prototype = {
848 /**
849 * @param {!Object} message
850 * @param {function()=} callback
Philip Pfaffeedad8322020-07-20 10:24:25851 * @param {!Array<*>=} transfers
Blink Reformat4c46d092018-04-07 15:32:37852 */
Philip Pfaffeedad8322020-07-20 10:24:25853 sendRequest: function(message, callback, transfers) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34854 if (typeof callback === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37855 message.requestId = this._registerCallback(callback);
Tim van der Lippe1d6e57a2019-09-30 11:55:34856 }
Philip Pfaffeedad8322020-07-20 10:24:25857 this._port.postMessage(message, transfers);
Blink Reformat4c46d092018-04-07 15:32:37858 },
859
860 /**
861 * @return {boolean}
862 */
863 hasHandler: function(command) {
864 return !!this._handlers[command];
865 },
866
867 registerHandler: function(command, handler) {
868 this._handlers[command] = handler;
869 },
870
871 unregisterHandler: function(command) {
872 delete this._handlers[command];
873 },
874
875 /**
876 * @return {string}
877 */
878 nextObjectId: function() {
879 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
880 },
881
882 _registerCallback: function(callback) {
883 const id = ++this._lastRequestId;
884 this._callbacks[id] = callback;
885 return id;
886 },
887
888 _onCallback: function(request) {
889 if (request.requestId in this._callbacks) {
890 const callback = this._callbacks[request.requestId];
891 delete this._callbacks[request.requestId];
892 callback(request.result);
893 }
894 },
895
896 _onMessage: function(event) {
897 const request = event.data;
898 const handler = this._handlers[request.command];
Tim van der Lippe1d6e57a2019-09-30 11:55:34899 if (handler) {
Blink Reformat4c46d092018-04-07 15:32:37900 handler.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34901 }
Blink Reformat4c46d092018-04-07 15:32:37902 }
903 };
904
905 function populateInterfaceClass(interfaze, implementation) {
906 for (const member in implementation) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34907 if (member.charAt(0) === '_') {
Blink Reformat4c46d092018-04-07 15:32:37908 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34909 }
Blink Reformat4c46d092018-04-07 15:32:37910 let descriptor = null;
911 // Traverse prototype chain until we find the owner.
Tim van der Lippe1d6e57a2019-09-30 11:55:34912 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
Blink Reformat4c46d092018-04-07 15:32:37913 descriptor = Object.getOwnPropertyDescriptor(owner, member);
Tim van der Lippe1d6e57a2019-09-30 11:55:34914 }
915 if (!descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37916 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34917 }
918 if (typeof descriptor.value === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37919 interfaze[member] = descriptor.value.bind(implementation);
Tim van der Lippe1d6e57a2019-09-30 11:55:34920 } else if (typeof descriptor.get === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37921 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
Tim van der Lippe1d6e57a2019-09-30 11:55:34922 } else {
Blink Reformat4c46d092018-04-07 15:32:37923 Object.defineProperty(interfaze, member, descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34924 }
Blink Reformat4c46d092018-04-07 15:32:37925 }
926 }
927
928 const extensionServer = new ExtensionServerClient();
929 const coreAPI = new InspectorExtensionAPI();
930
931 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
932
933 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
934 chrome.devtools.inspectedWindow = {};
Tim van der Lippeffa78622019-09-16 12:07:12935 Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
Blink Reformat4c46d092018-04-07 15:32:37936 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
937 chrome.devtools.network = coreAPI.network;
938 chrome.devtools.panels = coreAPI.panels;
939 chrome.devtools.panels.themeName = themeName;
Kim-Anh Tran45ba0b32020-08-13 06:32:31940 chrome.devtools.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37941
942 // default to expose experimental APIs for now.
943 if (extensionInfo.exposeExperimentalAPIs !== false) {
944 chrome.experimental = chrome.experimental || {};
945 chrome.experimental.devtools = chrome.experimental.devtools || {};
946
947 const properties = Object.getOwnPropertyNames(coreAPI);
948 for (let i = 0; i < properties.length; ++i) {
949 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34950 if (descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37951 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34952 }
Blink Reformat4c46d092018-04-07 15:32:37953 }
954 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
955 }
956
Tim van der Lippe1d6e57a2019-09-30 11:55:34957 if (extensionInfo.exposeWebInspectorNamespace) {
Blink Reformat4c46d092018-04-07 15:32:37958 window.webInspector = coreAPI;
Tim van der Lippe1d6e57a2019-09-30 11:55:34959 }
Blink Reformat4c46d092018-04-07 15:32:37960 testHook(extensionServer, coreAPI);
Tim van der Lippe226fc222019-10-10 12:17:12961};
Blink Reformat4c46d092018-04-07 15:32:37962
963/**
Tim van der Lipped71c22d2020-03-19 12:29:19964 * @param {!{startPage: string, name: string, exposeExperimentalAPIs: boolean}} extensionInfo
Blink Reformat4c46d092018-04-07 15:32:37965 * @param {string} inspectedTabId
966 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47967 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37968 * @param {function(!Object, !Object)|undefined} testHook
969 * @return {string}
970 */
Tim van der Lippe29fab472019-08-15 14:46:48971self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Philip Pfaffeedad8322020-07-20 10:24:25972 const argumentsJSON =
973 [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Tim van der Lippe1d6e57a2019-09-30 11:55:34974 if (!testHook) {
Blink Reformat4c46d092018-04-07 15:32:37975 testHook = () => {};
Tim van der Lippe1d6e57a2019-09-30 11:55:34976 }
Blink Reformat4c46d092018-04-07 15:32:37977 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
Tim van der Lippe226fc222019-10-10 12:17:12978 '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
Blink Reformat4c46d092018-04-07 15:32:37979 '})';
Tim van der Lippe29fab472019-08-15 14:46:48980};