blob: c216721411cb1287b9d250ba9c3296fdb0f59e66 [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',
99 GetFunctionInfo: 'getFunctionInfo'
Blink Reformat4c46d092018-04-07 15:32:37100 };
101}
102
103/**
104 * @param {!ExtensionDescriptor} extensionInfo
105 * @param {string} inspectedTabId
106 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47107 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37108 * @param {number} injectedScriptId
109 * @param {function(!Object, !Object)} testHook
110 * @suppressGlobalPropertiesCheck
111 */
Tim van der Lippe226fc222019-10-10 12:17:12112self.injectedExtensionAPI = function(
113 extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
Joel Einbinder67f28fb2018-08-02 00:33:47114 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:37115 const chrome = window.chrome || {};
116 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
Tim van der Lippe1d6e57a2019-09-30 11:55:34117 if (devtools_descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37118 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34119 }
Blink Reformat4c46d092018-04-07 15:32:37120
121 const apiPrivate = {};
122
123 defineCommonExtensionSymbols(apiPrivate);
124
125 const commands = apiPrivate.Commands;
Philip Pfaffeedad8322020-07-20 10:24:25126 const languageExtensionPluginCommands = apiPrivate.LanguageExtensionPluginCommands;
Blink Reformat4c46d092018-04-07 15:32:37127 const events = apiPrivate.Events;
128 let userAction = false;
129
130 // Here and below, all constructors are private to API implementation.
131 // For a public type Foo, if internal fields are present, these are on
132 // a private FooImpl type, an instance of FooImpl is used in a closure
133 // by Foo consutrctor to re-bind publicly exported members to an instance
134 // of Foo.
135
136 /**
137 * @constructor
138 */
139 function EventSinkImpl(type, customDispatch) {
140 this._type = type;
141 this._listeners = [];
142 this._customDispatch = customDispatch;
143 }
144
145 EventSinkImpl.prototype = {
146 addListener: function(callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34147 if (typeof callback !== 'function') {
Blink Reformat4c46d092018-04-07 15:32:37148 throw 'addListener: callback is not a function';
Tim van der Lippe1d6e57a2019-09-30 11:55:34149 }
150 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37151 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34152 }
Blink Reformat4c46d092018-04-07 15:32:37153 this._listeners.push(callback);
154 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
155 },
156
157 removeListener: function(callback) {
158 const listeners = this._listeners;
159
160 for (let i = 0; i < listeners.length; ++i) {
161 if (listeners[i] === callback) {
162 listeners.splice(i, 1);
163 break;
164 }
165 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34166 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37167 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34168 }
Blink Reformat4c46d092018-04-07 15:32:37169 },
170
171 /**
172 * @param {...} vararg
173 */
174 _fire: function(vararg) {
175 const listeners = this._listeners.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34176 for (let i = 0; i < listeners.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37177 listeners[i].apply(null, arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34178 }
Blink Reformat4c46d092018-04-07 15:32:37179 },
180
181 _dispatch: function(request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34182 if (this._customDispatch) {
Blink Reformat4c46d092018-04-07 15:32:37183 this._customDispatch.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34184 } else {
Blink Reformat4c46d092018-04-07 15:32:37185 this._fire.apply(this, request.arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34186 }
Blink Reformat4c46d092018-04-07 15:32:37187 }
188 };
189
190 /**
191 * @constructor
192 */
193 function InspectorExtensionAPI() {
194 this.inspectedWindow = new InspectedWindow();
195 this.panels = new Panels();
196 this.network = new Network();
197 this.timeline = new Timeline();
Philip Pfaffeedad8322020-07-20 10:24:25198 this.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37199 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
200 }
201
202 /**
203 * @constructor
204 */
205 function Network() {
206 /**
207 * @this {EventSinkImpl}
208 */
209 function dispatchRequestEvent(message) {
210 const request = message.arguments[1];
211 request.__proto__ = new Request(message.arguments[0]);
212 this._fire(request);
213 }
214 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
215 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
216 this.onNavigated = new EventSink(events.InspectedURLChanged);
217 }
218
219 Network.prototype = {
220 getHAR: function(callback) {
221 function callbackWrapper(result) {
222 const entries = (result && result.entries) || [];
223 for (let i = 0; i < entries.length; ++i) {
224 entries[i].__proto__ = new Request(entries[i]._requestId);
225 delete entries[i]._requestId;
226 }
227 callback(result);
228 }
229 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
230 },
231
232 addRequestHeaders: function(headers) {
233 extensionServer.sendRequest(
234 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
235 }
236 };
237
238 /**
239 * @constructor
240 */
241 function RequestImpl(id) {
242 this._id = id;
243 }
244
245 RequestImpl.prototype = {
246 getContent: function(callback) {
247 function callbackWrapper(response) {
248 callback(response.content, response.encoding);
249 }
250 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
251 }
252 };
253
254 /**
255 * @constructor
256 */
257 function Panels() {
258 const panels = {
259 elements: new ElementsPanel(),
260 sources: new SourcesPanel(),
261 };
262
263 function panelGetter(name) {
264 return panels[name];
265 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34266 for (const panel in panels) {
Tim van der Lippeffa78622019-09-16 12:07:12267 Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
Tim van der Lippe1d6e57a2019-09-30 11:55:34268 }
Blink Reformat4c46d092018-04-07 15:32:37269 this.applyStyleSheet = function(styleSheet) {
270 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
271 };
272 }
273
274 Panels.prototype = {
275 create: function(title, icon, page, callback) {
276 const id = 'extension-panel-' + extensionServer.nextObjectId();
277 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
278 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
279 },
280
281 setOpenResourceHandler: function(callback) {
282 const hadHandler = extensionServer.hasHandler(events.OpenResource);
283
284 function callbackWrapper(message) {
285 // Allow the panel to show itself when handling the event.
286 userAction = true;
287 try {
288 callback.call(null, new Resource(message.resource), message.lineNumber);
289 } finally {
290 userAction = false;
291 }
292 }
293
Tim van der Lippe1d6e57a2019-09-30 11:55:34294 if (!callback) {
Blink Reformat4c46d092018-04-07 15:32:37295 extensionServer.unregisterHandler(events.OpenResource);
Tim van der Lippe1d6e57a2019-09-30 11:55:34296 } else {
Blink Reformat4c46d092018-04-07 15:32:37297 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
Tim van der Lippe1d6e57a2019-09-30 11:55:34298 }
Blink Reformat4c46d092018-04-07 15:32:37299
300 // 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:34301 if (hadHandler === !callback) {
Blink Reformat4c46d092018-04-07 15:32:37302 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
Tim van der Lippe1d6e57a2019-09-30 11:55:34303 }
Blink Reformat4c46d092018-04-07 15:32:37304 },
305
306 openResource: function(url, lineNumber, callback) {
307 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
308 },
309
310 get SearchAction() {
311 return apiPrivate.panels.SearchAction;
312 }
313 };
314
315 /**
316 * @constructor
317 */
318 function ExtensionViewImpl(id) {
319 this._id = id;
320
321 /**
322 * @this {EventSinkImpl}
323 */
324 function dispatchShowEvent(message) {
325 const frameIndex = message.arguments[0];
Tim van der Lippe1d6e57a2019-09-30 11:55:34326 if (typeof frameIndex === 'number') {
Blink Reformat4c46d092018-04-07 15:32:37327 this._fire(window.parent.frames[frameIndex]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34328 } else {
Blink Reformat4c46d092018-04-07 15:32:37329 this._fire();
Tim van der Lippe1d6e57a2019-09-30 11:55:34330 }
Blink Reformat4c46d092018-04-07 15:32:37331 }
332
333 if (id) {
334 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
335 this.onHidden = new EventSink(events.ViewHidden + id);
336 }
337 }
338
339 /**
340 * @constructor
341 * @extends {ExtensionViewImpl}
342 * @param {string} hostPanelName
343 */
344 function PanelWithSidebarImpl(hostPanelName) {
345 ExtensionViewImpl.call(this, null);
346 this._hostPanelName = hostPanelName;
347 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
348 }
349
350 PanelWithSidebarImpl.prototype = {
351 createSidebarPane: function(title, callback) {
352 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
353 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
354 function callbackWrapper() {
355 callback(new ExtensionSidebarPane(id));
356 }
357 extensionServer.sendRequest(request, callback && callbackWrapper);
358 },
359
360 __proto__: ExtensionViewImpl.prototype
361 };
362
Philip Pfaffeedad8322020-07-20 10:24:25363 /**
364 * @constructor
365 */
366 function LanguageServicesAPIImpl() {
367 this._plugins = new Map();
368 }
369
370 LanguageServicesAPIImpl.prototype = {
371 /**
372 * @param {*} plugin The language plugin instance to register.
373 * @param {string} pluginName The plugin name
374 * @param {{language: string, symbol_types: !Array<string>}} supportedScriptTypes Script language and debug symbol types supported by this extension.
375 */
376 registerLanguageExtensionPlugin: function(plugin, pluginName, supportedScriptTypes) {
377 if (this._plugins.has(plugin)) {
378 throw new Error('Tried to register a plugin twice');
379 }
380 const channel = new MessageChannel();
381 const port = channel.port1;
382 this._plugins.set(plugin, port);
383 port.onmessage = ({data: {requestId, method, parameters}}) => {
384 dispatchMethodCall(method, parameters)
385 .then(result => port.postMessage({requestId, result}))
386 .catch(error => port.postMessage({requestId, error: {message: error.message}}));
387 };
388
389 /**
390 * @param {string} method
391 * @param {*} parameters
392 * @return {!Promise<*>}
393 */
394 function dispatchMethodCall(method, parameters) {
395 switch (method) {
396 case languageExtensionPluginCommands.AddRawModule:
397 return plugin.addRawModule(parameters.rawModuleId, parameters.symbolsURL, parameters.rawModule);
398 case languageExtensionPluginCommands.RemoveRawModule:
399 return plugin.removeRawModule(parameters.rawModuleId);
400 case languageExtensionPluginCommands.SourceLocationToRawLocation:
401 return plugin.sourceLocationToRawLocation(parameters.sourceLocation);
402 case languageExtensionPluginCommands.RawLocationToSourceLocation:
403 return plugin.rawLocationToSourceLocation(parameters.rawLocation);
Benedikt Meurer723d5432020-10-09 09:02:42404 case languageExtensionPluginCommands.GetScopeInfo:
405 return plugin.getScopeInfo(parameters.type);
Philip Pfaffeedad8322020-07-20 10:24:25406 case languageExtensionPluginCommands.ListVariablesInScope:
407 return plugin.listVariablesInScope(parameters.rawLocation);
408 case languageExtensionPluginCommands.EvaluateVariable:
409 return plugin.evaluateVariable(parameters.name, parameters.location);
Eric Leese5aaa2222020-10-01 11:49:35410 case languageExtensionPluginCommands.GetFunctionInfo:
411 return plugin.getFunctionInfo(parameters.rawLocation);
Philip Pfaffeedad8322020-07-20 10:24:25412 }
413 throw new Error(`Unknown language plugin method ${method}`);
414 }
415
416 extensionServer.sendRequest(
417 {command: commands.RegisterLanguageExtensionPlugin, pluginName, port: channel.port2, supportedScriptTypes},
418 undefined, [channel.port2]);
419 }
420 };
421
Blink Reformat4c46d092018-04-07 15:32:37422 function declareInterfaceClass(implConstructor) {
423 return function() {
424 const impl = {__proto__: implConstructor.prototype};
425 implConstructor.apply(impl, arguments);
426 populateInterfaceClass(this, impl);
427 };
428 }
429
430 function defineDeprecatedProperty(object, className, oldName, newName) {
431 let warningGiven = false;
432 function getter() {
433 if (!warningGiven) {
434 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
435 warningGiven = true;
436 }
437 return object[newName];
438 }
439 object.__defineGetter__(oldName, getter);
440 }
441
442 function extractCallbackArgument(args) {
443 const lastArgument = args[args.length - 1];
444 return typeof lastArgument === 'function' ? lastArgument : undefined;
445 }
446
Philip Pfaffeedad8322020-07-20 10:24:25447 const LanguageServicesAPI = declareInterfaceClass(LanguageServicesAPIImpl);
Blink Reformat4c46d092018-04-07 15:32:37448 const Button = declareInterfaceClass(ButtonImpl);
449 const EventSink = declareInterfaceClass(EventSinkImpl);
450 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
451 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
Tim van der Lippeffa78622019-09-16 12:07:12452 /**
453 * @constructor
454 * @param {string} hostPanelName
455 */
456 const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
Blink Reformat4c46d092018-04-07 15:32:37457 const Request = declareInterfaceClass(RequestImpl);
458 const Resource = declareInterfaceClass(ResourceImpl);
459 const TraceSession = declareInterfaceClass(TraceSessionImpl);
460
Tim van der Lippeffa78622019-09-16 12:07:12461 class ElementsPanel extends PanelWithSidebarClass {
462 constructor() {
463 super('elements');
464 }
Blink Reformat4c46d092018-04-07 15:32:37465 }
466
Tim van der Lippeffa78622019-09-16 12:07:12467 class SourcesPanel extends PanelWithSidebarClass {
468 constructor() {
469 super('sources');
470 }
Blink Reformat4c46d092018-04-07 15:32:37471 }
472
Blink Reformat4c46d092018-04-07 15:32:37473 /**
474 * @constructor
475 * @extends {ExtensionViewImpl}
476 */
477 function ExtensionPanelImpl(id) {
478 ExtensionViewImpl.call(this, id);
479 this.onSearch = new EventSink(events.PanelSearch + id);
480 }
481
482 ExtensionPanelImpl.prototype = {
483 /**
484 * @return {!Object}
485 */
486 createStatusBarButton: function(iconPath, tooltipText, disabled) {
487 const id = 'button-' + extensionServer.nextObjectId();
488 const request = {
489 command: commands.CreateToolbarButton,
490 panel: this._id,
491 id: id,
492 icon: iconPath,
493 tooltip: tooltipText,
494 disabled: !!disabled
495 };
496 extensionServer.sendRequest(request);
497 return new Button(id);
498 },
499
500 show: function() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34501 if (!userAction) {
Blink Reformat4c46d092018-04-07 15:32:37502 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34503 }
Blink Reformat4c46d092018-04-07 15:32:37504
505 const request = {command: commands.ShowPanel, id: this._id};
506 extensionServer.sendRequest(request);
507 },
508
509 __proto__: ExtensionViewImpl.prototype
510 };
511
512 /**
513 * @constructor
514 * @extends {ExtensionViewImpl}
515 */
516 function ExtensionSidebarPaneImpl(id) {
517 ExtensionViewImpl.call(this, id);
518 }
519
520 ExtensionSidebarPaneImpl.prototype = {
521 setHeight: function(height) {
522 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
523 },
524
525 setExpression: function(expression, rootTitle, evaluateOptions) {
526 const request = {
527 command: commands.SetSidebarContent,
528 id: this._id,
529 expression: expression,
530 rootTitle: rootTitle,
531 evaluateOnPage: true,
532 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34533 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37534 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34535 }
Blink Reformat4c46d092018-04-07 15:32:37536 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
537 },
538
539 setObject: function(jsonObject, rootTitle, callback) {
540 extensionServer.sendRequest(
541 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
542 },
543
544 setPage: function(page) {
545 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
546 },
547
548 __proto__: ExtensionViewImpl.prototype
549 };
550
551 /**
552 * @constructor
553 */
554 function ButtonImpl(id) {
555 this._id = id;
556 this.onClicked = new EventSink(events.ButtonClicked + id);
557 }
558
559 ButtonImpl.prototype = {
560 update: function(iconPath, tooltipText, disabled) {
561 const request =
562 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
563 extensionServer.sendRequest(request);
564 }
565 };
566
567 /**
568 * @constructor
569 */
570 function Timeline() {
571 }
572
573 Timeline.prototype = {
574 /**
575 * @param {string} categoryName
576 * @param {string} categoryTooltip
577 * @return {!TraceProvider}
578 */
579 addTraceProvider: function(categoryName, categoryTooltip) {
580 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
581 extensionServer.sendRequest(
582 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
583 return new TraceProvider(id);
584 }
585 };
586
587 /**
588 * @constructor
589 * @param {string} id
590 */
591 function TraceSessionImpl(id) {
592 this._id = id;
593 }
594
595 TraceSessionImpl.prototype = {
596 /**
597 * @param {string=} url
598 * @param {number=} timeOffset
599 */
600 complete: function(url, timeOffset) {
601 const request =
602 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
603 extensionServer.sendRequest(request);
604 }
605 };
606
607 /**
608 * @constructor
609 * @param {string} id
610 */
611 function TraceProvider(id) {
612 /**
613 * @this {EventSinkImpl}
614 */
615 function dispatchRecordingStarted(message) {
616 const sessionId = message.arguments[0];
617 this._fire(new TraceSession(sessionId));
618 }
619
620 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
621 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
622 }
623
624 /**
625 * @constructor
626 */
627 function InspectedWindow() {
628 /**
629 * @this {EventSinkImpl}
630 */
631 function dispatchResourceEvent(message) {
632 this._fire(new Resource(message.arguments[0]));
633 }
634
635 /**
636 * @this {EventSinkImpl}
637 */
638 function dispatchResourceContentEvent(message) {
639 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
640 }
641
642 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
643 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
644 }
645
646 InspectedWindow.prototype = {
647 reload: function(optionsOrUserAgent) {
648 let options = null;
649 if (typeof optionsOrUserAgent === 'object') {
650 options = optionsOrUserAgent;
651 } else if (typeof optionsOrUserAgent === 'string') {
652 options = {userAgent: optionsOrUserAgent};
653 console.warn(
654 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
655 'Use inspectedWindow.reload({ userAgent: value}) instead.');
656 }
657 extensionServer.sendRequest({command: commands.Reload, options: options});
658 },
659
660 /**
661 * @return {?Object}
662 */
663 eval: function(expression, evaluateOptions) {
664 const callback = extractCallbackArgument(arguments);
665 function callbackWrapper(result) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34666 if (result.isError || result.isException) {
Blink Reformat4c46d092018-04-07 15:32:37667 callback(undefined, result);
Tim van der Lippe1d6e57a2019-09-30 11:55:34668 } else {
Blink Reformat4c46d092018-04-07 15:32:37669 callback(result.value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34670 }
Blink Reformat4c46d092018-04-07 15:32:37671 }
672 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
Tim van der Lippe1d6e57a2019-09-30 11:55:34673 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37674 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34675 }
Blink Reformat4c46d092018-04-07 15:32:37676 extensionServer.sendRequest(request, callback && callbackWrapper);
677 return null;
678 },
679
680 getResources: function(callback) {
681 function wrapResource(resourceData) {
682 return new Resource(resourceData);
683 }
684 function callbackWrapper(resources) {
685 callback(resources.map(wrapResource));
686 }
687 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
688 }
689 };
690
691 /**
692 * @constructor
693 */
694 function ResourceImpl(resourceData) {
695 this._url = resourceData.url;
696 this._type = resourceData.type;
697 }
698
699 ResourceImpl.prototype = {
700 get url() {
701 return this._url;
702 },
703
704 get type() {
705 return this._type;
706 },
707
708 getContent: function(callback) {
709 function callbackWrapper(response) {
710 callback(response.content, response.encoding);
711 }
712
713 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
714 },
715
716 setContent: function(content, commit, callback) {
717 extensionServer.sendRequest(
718 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
719 }
720 };
721
722 function getTabId() {
723 return inspectedTabId;
724 }
725
726 let keyboardEventRequestQueue = [];
727 let forwardTimer = null;
728
Jan Schefflere7d7bb12019-10-24 09:18:52729 /**
730 * @suppressGlobalPropertiesCheck
731 */
Blink Reformat4c46d092018-04-07 15:32:37732 function forwardKeyboardEvent(event) {
Jan Schefflere7d7bb12019-10-24 09:18:52733 // Check if the event should be forwarded.
734 // This is a workaround for crbug.com/923338.
735 const focused = document.activeElement;
736 if (focused) {
737 const isInput = focused.nodeName === 'INPUT' || focused.nodeName === 'TEXTAREA';
738 if (isInput && !(event.ctrlKey || event.altKey || event.metaKey)) {
739 return;
740 }
741 }
742
Joel Einbinder67f28fb2018-08-02 00:33:47743 let modifiers = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34744 if (event.shiftKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47745 modifiers |= 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34746 }
747 if (event.ctrlKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47748 modifiers |= 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:34749 }
750 if (event.altKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47751 modifiers |= 4;
Tim van der Lippe1d6e57a2019-09-30 11:55:34752 }
753 if (event.metaKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47754 modifiers |= 8;
Tim van der Lippe1d6e57a2019-09-30 11:55:34755 }
Joel Einbinder67f28fb2018-08-02 00:33:47756 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37757 // We only care about global hotkeys, not about random text
Tim van der Lippe1d6e57a2019-09-30 11:55:34758 if (!keysToForwardSet.has(num)) {
Blink Reformat4c46d092018-04-07 15:32:37759 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34760 }
Joel Einbinder67f28fb2018-08-02 00:33:47761 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37762 const requestPayload = {
763 eventType: event.type,
764 ctrlKey: event.ctrlKey,
765 altKey: event.altKey,
766 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47767 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37768 keyIdentifier: event.keyIdentifier,
769 key: event.key,
770 code: event.code,
771 location: event.location,
772 keyCode: event.keyCode
773 };
774 keyboardEventRequestQueue.push(requestPayload);
Tim van der Lippe1d6e57a2019-09-30 11:55:34775 if (!forwardTimer) {
Blink Reformat4c46d092018-04-07 15:32:37776 forwardTimer = setTimeout(forwardEventQueue, 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34777 }
Blink Reformat4c46d092018-04-07 15:32:37778 }
779
780 function forwardEventQueue() {
781 forwardTimer = null;
782 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
783 extensionServer.sendRequest(request);
784 keyboardEventRequestQueue = [];
785 }
786
787 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37788
789 /**
790 * @constructor
791 */
792 function ExtensionServerClient() {
793 this._callbacks = {};
794 this._handlers = {};
795 this._lastRequestId = 0;
796 this._lastObjectId = 0;
797
798 this.registerHandler('callback', this._onCallback.bind(this));
799
800 const channel = new MessageChannel();
801 this._port = channel.port1;
802 this._port.addEventListener('message', this._onMessage.bind(this), false);
803 this._port.start();
804
805 window.parent.postMessage('registerExtension', '*', [channel.port2]);
806 }
807
808 ExtensionServerClient.prototype = {
809 /**
810 * @param {!Object} message
811 * @param {function()=} callback
Philip Pfaffeedad8322020-07-20 10:24:25812 * @param {!Array<*>=} transfers
Blink Reformat4c46d092018-04-07 15:32:37813 */
Philip Pfaffeedad8322020-07-20 10:24:25814 sendRequest: function(message, callback, transfers) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34815 if (typeof callback === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37816 message.requestId = this._registerCallback(callback);
Tim van der Lippe1d6e57a2019-09-30 11:55:34817 }
Philip Pfaffeedad8322020-07-20 10:24:25818 this._port.postMessage(message, transfers);
Blink Reformat4c46d092018-04-07 15:32:37819 },
820
821 /**
822 * @return {boolean}
823 */
824 hasHandler: function(command) {
825 return !!this._handlers[command];
826 },
827
828 registerHandler: function(command, handler) {
829 this._handlers[command] = handler;
830 },
831
832 unregisterHandler: function(command) {
833 delete this._handlers[command];
834 },
835
836 /**
837 * @return {string}
838 */
839 nextObjectId: function() {
840 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
841 },
842
843 _registerCallback: function(callback) {
844 const id = ++this._lastRequestId;
845 this._callbacks[id] = callback;
846 return id;
847 },
848
849 _onCallback: function(request) {
850 if (request.requestId in this._callbacks) {
851 const callback = this._callbacks[request.requestId];
852 delete this._callbacks[request.requestId];
853 callback(request.result);
854 }
855 },
856
857 _onMessage: function(event) {
858 const request = event.data;
859 const handler = this._handlers[request.command];
Tim van der Lippe1d6e57a2019-09-30 11:55:34860 if (handler) {
Blink Reformat4c46d092018-04-07 15:32:37861 handler.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34862 }
Blink Reformat4c46d092018-04-07 15:32:37863 }
864 };
865
866 function populateInterfaceClass(interfaze, implementation) {
867 for (const member in implementation) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34868 if (member.charAt(0) === '_') {
Blink Reformat4c46d092018-04-07 15:32:37869 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34870 }
Blink Reformat4c46d092018-04-07 15:32:37871 let descriptor = null;
872 // Traverse prototype chain until we find the owner.
Tim van der Lippe1d6e57a2019-09-30 11:55:34873 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
Blink Reformat4c46d092018-04-07 15:32:37874 descriptor = Object.getOwnPropertyDescriptor(owner, member);
Tim van der Lippe1d6e57a2019-09-30 11:55:34875 }
876 if (!descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37877 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34878 }
879 if (typeof descriptor.value === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37880 interfaze[member] = descriptor.value.bind(implementation);
Tim van der Lippe1d6e57a2019-09-30 11:55:34881 } else if (typeof descriptor.get === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37882 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
Tim van der Lippe1d6e57a2019-09-30 11:55:34883 } else {
Blink Reformat4c46d092018-04-07 15:32:37884 Object.defineProperty(interfaze, member, descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34885 }
Blink Reformat4c46d092018-04-07 15:32:37886 }
887 }
888
889 const extensionServer = new ExtensionServerClient();
890 const coreAPI = new InspectorExtensionAPI();
891
892 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
893
894 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
895 chrome.devtools.inspectedWindow = {};
Tim van der Lippeffa78622019-09-16 12:07:12896 Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
Blink Reformat4c46d092018-04-07 15:32:37897 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
898 chrome.devtools.network = coreAPI.network;
899 chrome.devtools.panels = coreAPI.panels;
900 chrome.devtools.panels.themeName = themeName;
Kim-Anh Tran45ba0b32020-08-13 06:32:31901 chrome.devtools.languageServices = new LanguageServicesAPI();
Blink Reformat4c46d092018-04-07 15:32:37902
903 // default to expose experimental APIs for now.
904 if (extensionInfo.exposeExperimentalAPIs !== false) {
905 chrome.experimental = chrome.experimental || {};
906 chrome.experimental.devtools = chrome.experimental.devtools || {};
907
908 const properties = Object.getOwnPropertyNames(coreAPI);
909 for (let i = 0; i < properties.length; ++i) {
910 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34911 if (descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37912 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34913 }
Blink Reformat4c46d092018-04-07 15:32:37914 }
915 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
916 }
917
Tim van der Lippe1d6e57a2019-09-30 11:55:34918 if (extensionInfo.exposeWebInspectorNamespace) {
Blink Reformat4c46d092018-04-07 15:32:37919 window.webInspector = coreAPI;
Tim van der Lippe1d6e57a2019-09-30 11:55:34920 }
Blink Reformat4c46d092018-04-07 15:32:37921 testHook(extensionServer, coreAPI);
Tim van der Lippe226fc222019-10-10 12:17:12922};
Blink Reformat4c46d092018-04-07 15:32:37923
924/**
Tim van der Lipped71c22d2020-03-19 12:29:19925 * @param {!{startPage: string, name: string, exposeExperimentalAPIs: boolean}} extensionInfo
Blink Reformat4c46d092018-04-07 15:32:37926 * @param {string} inspectedTabId
927 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47928 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37929 * @param {function(!Object, !Object)|undefined} testHook
930 * @return {string}
931 */
Tim van der Lippe29fab472019-08-15 14:46:48932self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Philip Pfaffeedad8322020-07-20 10:24:25933 const argumentsJSON =
934 [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Tim van der Lippe1d6e57a2019-09-30 11:55:34935 if (!testHook) {
Blink Reformat4c46d092018-04-07 15:32:37936 testHook = () => {};
Tim van der Lippe1d6e57a2019-09-30 11:55:34937 }
Blink Reformat4c46d092018-04-07 15:32:37938 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
Tim van der Lippe226fc222019-10-10 12:17:12939 '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
Blink Reformat4c46d092018-04-07 15:32:37940 '})';
Tim van der Lippe29fab472019-08-15 14:46:48941};