blob: 9f02d8acb3f02ea0aab4099ff0dd9a5cb3da38a5 [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
31/* eslint-disable indent */
32
33function defineCommonExtensionSymbols(apiPrivate) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3434 if (!apiPrivate.panels) {
Blink Reformat4c46d092018-04-07 15:32:3735 apiPrivate.panels = {};
Tim van der Lippe1d6e57a2019-09-30 11:55:3436 }
Blink Reformat4c46d092018-04-07 15:32:3737 apiPrivate.panels.SearchAction = {
38 CancelSearch: 'cancelSearch',
39 PerformSearch: 'performSearch',
40 NextSearchResult: 'nextSearchResult',
41 PreviousSearchResult: 'previousSearchResult'
42 };
43
44 /** @enum {string} */
45 apiPrivate.Events = {
46 ButtonClicked: 'button-clicked-',
47 PanelObjectSelected: 'panel-objectSelected-',
48 NetworkRequestFinished: 'network-request-finished',
49 OpenResource: 'open-resource',
50 PanelSearch: 'panel-search-',
51 RecordingStarted: 'trace-recording-started-',
52 RecordingStopped: 'trace-recording-stopped-',
53 ResourceAdded: 'resource-added',
54 ResourceContentCommitted: 'resource-content-committed',
55 ViewShown: 'view-shown-',
56 ViewHidden: 'view-hidden-'
57 };
58
59 /** @enum {string} */
60 apiPrivate.Commands = {
61 AddRequestHeaders: 'addRequestHeaders',
62 AddTraceProvider: 'addTraceProvider',
63 ApplyStyleSheet: 'applyStyleSheet',
64 CompleteTraceSession: 'completeTraceSession',
65 CreatePanel: 'createPanel',
66 CreateSidebarPane: 'createSidebarPane',
67 CreateToolbarButton: 'createToolbarButton',
68 EvaluateOnInspectedPage: 'evaluateOnInspectedPage',
69 ForwardKeyboardEvent: '_forwardKeyboardEvent',
70 GetHAR: 'getHAR',
71 GetPageResources: 'getPageResources',
72 GetRequestContent: 'getRequestContent',
73 GetResourceContent: 'getResourceContent',
74 InspectedURLChanged: 'inspectedURLChanged',
75 OpenResource: 'openResource',
76 Reload: 'Reload',
77 Subscribe: 'subscribe',
78 SetOpenResourceHandler: 'setOpenResourceHandler',
79 SetResourceContent: 'setResourceContent',
80 SetSidebarContent: 'setSidebarContent',
81 SetSidebarHeight: 'setSidebarHeight',
82 SetSidebarPage: 'setSidebarPage',
83 ShowPanel: 'showPanel',
84 Unsubscribe: 'unsubscribe',
85 UpdateButton: 'updateButton'
86 };
87}
88
89/**
90 * @param {!ExtensionDescriptor} extensionInfo
91 * @param {string} inspectedTabId
92 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:4793 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:3794 * @param {number} injectedScriptId
95 * @param {function(!Object, !Object)} testHook
96 * @suppressGlobalPropertiesCheck
97 */
Joel Einbinder67f28fb2018-08-02 00:33:4798function injectedExtensionAPI(extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
99 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:37100 const chrome = window.chrome || {};
101 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
Tim van der Lippe1d6e57a2019-09-30 11:55:34102 if (devtools_descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37103 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34104 }
Blink Reformat4c46d092018-04-07 15:32:37105
106 const apiPrivate = {};
107
108 defineCommonExtensionSymbols(apiPrivate);
109
110 const commands = apiPrivate.Commands;
111 const events = apiPrivate.Events;
112 let userAction = false;
113
114 // Here and below, all constructors are private to API implementation.
115 // For a public type Foo, if internal fields are present, these are on
116 // a private FooImpl type, an instance of FooImpl is used in a closure
117 // by Foo consutrctor to re-bind publicly exported members to an instance
118 // of Foo.
119
120 /**
121 * @constructor
122 */
123 function EventSinkImpl(type, customDispatch) {
124 this._type = type;
125 this._listeners = [];
126 this._customDispatch = customDispatch;
127 }
128
129 EventSinkImpl.prototype = {
130 addListener: function(callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34131 if (typeof callback !== 'function') {
Blink Reformat4c46d092018-04-07 15:32:37132 throw 'addListener: callback is not a function';
Tim van der Lippe1d6e57a2019-09-30 11:55:34133 }
134 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37135 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34136 }
Blink Reformat4c46d092018-04-07 15:32:37137 this._listeners.push(callback);
138 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
139 },
140
141 removeListener: function(callback) {
142 const listeners = this._listeners;
143
144 for (let i = 0; i < listeners.length; ++i) {
145 if (listeners[i] === callback) {
146 listeners.splice(i, 1);
147 break;
148 }
149 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34150 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37151 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34152 }
Blink Reformat4c46d092018-04-07 15:32:37153 },
154
155 /**
156 * @param {...} vararg
157 */
158 _fire: function(vararg) {
159 const listeners = this._listeners.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34160 for (let i = 0; i < listeners.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37161 listeners[i].apply(null, arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34162 }
Blink Reformat4c46d092018-04-07 15:32:37163 },
164
165 _dispatch: function(request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34166 if (this._customDispatch) {
Blink Reformat4c46d092018-04-07 15:32:37167 this._customDispatch.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34168 } else {
Blink Reformat4c46d092018-04-07 15:32:37169 this._fire.apply(this, request.arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34170 }
Blink Reformat4c46d092018-04-07 15:32:37171 }
172 };
173
174 /**
175 * @constructor
176 */
177 function InspectorExtensionAPI() {
178 this.inspectedWindow = new InspectedWindow();
179 this.panels = new Panels();
180 this.network = new Network();
181 this.timeline = new Timeline();
182 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
183 }
184
185 /**
186 * @constructor
187 */
188 function Network() {
189 /**
190 * @this {EventSinkImpl}
191 */
192 function dispatchRequestEvent(message) {
193 const request = message.arguments[1];
194 request.__proto__ = new Request(message.arguments[0]);
195 this._fire(request);
196 }
197 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
198 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
199 this.onNavigated = new EventSink(events.InspectedURLChanged);
200 }
201
202 Network.prototype = {
203 getHAR: function(callback) {
204 function callbackWrapper(result) {
205 const entries = (result && result.entries) || [];
206 for (let i = 0; i < entries.length; ++i) {
207 entries[i].__proto__ = new Request(entries[i]._requestId);
208 delete entries[i]._requestId;
209 }
210 callback(result);
211 }
212 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
213 },
214
215 addRequestHeaders: function(headers) {
216 extensionServer.sendRequest(
217 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
218 }
219 };
220
221 /**
222 * @constructor
223 */
224 function RequestImpl(id) {
225 this._id = id;
226 }
227
228 RequestImpl.prototype = {
229 getContent: function(callback) {
230 function callbackWrapper(response) {
231 callback(response.content, response.encoding);
232 }
233 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
234 }
235 };
236
237 /**
238 * @constructor
239 */
240 function Panels() {
241 const panels = {
242 elements: new ElementsPanel(),
243 sources: new SourcesPanel(),
244 };
245
246 function panelGetter(name) {
247 return panels[name];
248 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34249 for (const panel in panels) {
Tim van der Lippeffa78622019-09-16 12:07:12250 Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
Tim van der Lippe1d6e57a2019-09-30 11:55:34251 }
Blink Reformat4c46d092018-04-07 15:32:37252 this.applyStyleSheet = function(styleSheet) {
253 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
254 };
255 }
256
257 Panels.prototype = {
258 create: function(title, icon, page, callback) {
259 const id = 'extension-panel-' + extensionServer.nextObjectId();
260 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
261 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
262 },
263
264 setOpenResourceHandler: function(callback) {
265 const hadHandler = extensionServer.hasHandler(events.OpenResource);
266
267 function callbackWrapper(message) {
268 // Allow the panel to show itself when handling the event.
269 userAction = true;
270 try {
271 callback.call(null, new Resource(message.resource), message.lineNumber);
272 } finally {
273 userAction = false;
274 }
275 }
276
Tim van der Lippe1d6e57a2019-09-30 11:55:34277 if (!callback) {
Blink Reformat4c46d092018-04-07 15:32:37278 extensionServer.unregisterHandler(events.OpenResource);
Tim van der Lippe1d6e57a2019-09-30 11:55:34279 } else {
Blink Reformat4c46d092018-04-07 15:32:37280 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
Tim van der Lippe1d6e57a2019-09-30 11:55:34281 }
Blink Reformat4c46d092018-04-07 15:32:37282
283 // 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:34284 if (hadHandler === !callback) {
Blink Reformat4c46d092018-04-07 15:32:37285 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
Tim van der Lippe1d6e57a2019-09-30 11:55:34286 }
Blink Reformat4c46d092018-04-07 15:32:37287 },
288
289 openResource: function(url, lineNumber, callback) {
290 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
291 },
292
293 get SearchAction() {
294 return apiPrivate.panels.SearchAction;
295 }
296 };
297
298 /**
299 * @constructor
300 */
301 function ExtensionViewImpl(id) {
302 this._id = id;
303
304 /**
305 * @this {EventSinkImpl}
306 */
307 function dispatchShowEvent(message) {
308 const frameIndex = message.arguments[0];
Tim van der Lippe1d6e57a2019-09-30 11:55:34309 if (typeof frameIndex === 'number') {
Blink Reformat4c46d092018-04-07 15:32:37310 this._fire(window.parent.frames[frameIndex]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34311 } else {
Blink Reformat4c46d092018-04-07 15:32:37312 this._fire();
Tim van der Lippe1d6e57a2019-09-30 11:55:34313 }
Blink Reformat4c46d092018-04-07 15:32:37314 }
315
316 if (id) {
317 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
318 this.onHidden = new EventSink(events.ViewHidden + id);
319 }
320 }
321
322 /**
323 * @constructor
324 * @extends {ExtensionViewImpl}
325 * @param {string} hostPanelName
326 */
327 function PanelWithSidebarImpl(hostPanelName) {
328 ExtensionViewImpl.call(this, null);
329 this._hostPanelName = hostPanelName;
330 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
331 }
332
333 PanelWithSidebarImpl.prototype = {
334 createSidebarPane: function(title, callback) {
335 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
336 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
337 function callbackWrapper() {
338 callback(new ExtensionSidebarPane(id));
339 }
340 extensionServer.sendRequest(request, callback && callbackWrapper);
341 },
342
343 __proto__: ExtensionViewImpl.prototype
344 };
345
346 function declareInterfaceClass(implConstructor) {
347 return function() {
348 const impl = {__proto__: implConstructor.prototype};
349 implConstructor.apply(impl, arguments);
350 populateInterfaceClass(this, impl);
351 };
352 }
353
354 function defineDeprecatedProperty(object, className, oldName, newName) {
355 let warningGiven = false;
356 function getter() {
357 if (!warningGiven) {
358 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
359 warningGiven = true;
360 }
361 return object[newName];
362 }
363 object.__defineGetter__(oldName, getter);
364 }
365
366 function extractCallbackArgument(args) {
367 const lastArgument = args[args.length - 1];
368 return typeof lastArgument === 'function' ? lastArgument : undefined;
369 }
370
371 const Button = declareInterfaceClass(ButtonImpl);
372 const EventSink = declareInterfaceClass(EventSinkImpl);
373 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
374 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
Tim van der Lippeffa78622019-09-16 12:07:12375 /**
376 * @constructor
377 * @param {string} hostPanelName
378 */
379 const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
Blink Reformat4c46d092018-04-07 15:32:37380 const Request = declareInterfaceClass(RequestImpl);
381 const Resource = declareInterfaceClass(ResourceImpl);
382 const TraceSession = declareInterfaceClass(TraceSessionImpl);
383
Tim van der Lippeffa78622019-09-16 12:07:12384 class ElementsPanel extends PanelWithSidebarClass {
385 constructor() {
386 super('elements');
387 }
Blink Reformat4c46d092018-04-07 15:32:37388 }
389
Tim van der Lippeffa78622019-09-16 12:07:12390 class SourcesPanel extends PanelWithSidebarClass {
391 constructor() {
392 super('sources');
393 }
Blink Reformat4c46d092018-04-07 15:32:37394 }
395
Blink Reformat4c46d092018-04-07 15:32:37396 /**
397 * @constructor
398 * @extends {ExtensionViewImpl}
399 */
400 function ExtensionPanelImpl(id) {
401 ExtensionViewImpl.call(this, id);
402 this.onSearch = new EventSink(events.PanelSearch + id);
403 }
404
405 ExtensionPanelImpl.prototype = {
406 /**
407 * @return {!Object}
408 */
409 createStatusBarButton: function(iconPath, tooltipText, disabled) {
410 const id = 'button-' + extensionServer.nextObjectId();
411 const request = {
412 command: commands.CreateToolbarButton,
413 panel: this._id,
414 id: id,
415 icon: iconPath,
416 tooltip: tooltipText,
417 disabled: !!disabled
418 };
419 extensionServer.sendRequest(request);
420 return new Button(id);
421 },
422
423 show: function() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34424 if (!userAction) {
Blink Reformat4c46d092018-04-07 15:32:37425 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34426 }
Blink Reformat4c46d092018-04-07 15:32:37427
428 const request = {command: commands.ShowPanel, id: this._id};
429 extensionServer.sendRequest(request);
430 },
431
432 __proto__: ExtensionViewImpl.prototype
433 };
434
435 /**
436 * @constructor
437 * @extends {ExtensionViewImpl}
438 */
439 function ExtensionSidebarPaneImpl(id) {
440 ExtensionViewImpl.call(this, id);
441 }
442
443 ExtensionSidebarPaneImpl.prototype = {
444 setHeight: function(height) {
445 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
446 },
447
448 setExpression: function(expression, rootTitle, evaluateOptions) {
449 const request = {
450 command: commands.SetSidebarContent,
451 id: this._id,
452 expression: expression,
453 rootTitle: rootTitle,
454 evaluateOnPage: true,
455 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34456 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37457 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34458 }
Blink Reformat4c46d092018-04-07 15:32:37459 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
460 },
461
462 setObject: function(jsonObject, rootTitle, callback) {
463 extensionServer.sendRequest(
464 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
465 },
466
467 setPage: function(page) {
468 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
469 },
470
471 __proto__: ExtensionViewImpl.prototype
472 };
473
474 /**
475 * @constructor
476 */
477 function ButtonImpl(id) {
478 this._id = id;
479 this.onClicked = new EventSink(events.ButtonClicked + id);
480 }
481
482 ButtonImpl.prototype = {
483 update: function(iconPath, tooltipText, disabled) {
484 const request =
485 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
486 extensionServer.sendRequest(request);
487 }
488 };
489
490 /**
491 * @constructor
492 */
493 function Timeline() {
494 }
495
496 Timeline.prototype = {
497 /**
498 * @param {string} categoryName
499 * @param {string} categoryTooltip
500 * @return {!TraceProvider}
501 */
502 addTraceProvider: function(categoryName, categoryTooltip) {
503 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
504 extensionServer.sendRequest(
505 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
506 return new TraceProvider(id);
507 }
508 };
509
510 /**
511 * @constructor
512 * @param {string} id
513 */
514 function TraceSessionImpl(id) {
515 this._id = id;
516 }
517
518 TraceSessionImpl.prototype = {
519 /**
520 * @param {string=} url
521 * @param {number=} timeOffset
522 */
523 complete: function(url, timeOffset) {
524 const request =
525 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
526 extensionServer.sendRequest(request);
527 }
528 };
529
530 /**
531 * @constructor
532 * @param {string} id
533 */
534 function TraceProvider(id) {
535 /**
536 * @this {EventSinkImpl}
537 */
538 function dispatchRecordingStarted(message) {
539 const sessionId = message.arguments[0];
540 this._fire(new TraceSession(sessionId));
541 }
542
543 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
544 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
545 }
546
547 /**
548 * @constructor
549 */
550 function InspectedWindow() {
551 /**
552 * @this {EventSinkImpl}
553 */
554 function dispatchResourceEvent(message) {
555 this._fire(new Resource(message.arguments[0]));
556 }
557
558 /**
559 * @this {EventSinkImpl}
560 */
561 function dispatchResourceContentEvent(message) {
562 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
563 }
564
565 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
566 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
567 }
568
569 InspectedWindow.prototype = {
570 reload: function(optionsOrUserAgent) {
571 let options = null;
572 if (typeof optionsOrUserAgent === 'object') {
573 options = optionsOrUserAgent;
574 } else if (typeof optionsOrUserAgent === 'string') {
575 options = {userAgent: optionsOrUserAgent};
576 console.warn(
577 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
578 'Use inspectedWindow.reload({ userAgent: value}) instead.');
579 }
580 extensionServer.sendRequest({command: commands.Reload, options: options});
581 },
582
583 /**
584 * @return {?Object}
585 */
586 eval: function(expression, evaluateOptions) {
587 const callback = extractCallbackArgument(arguments);
588 function callbackWrapper(result) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34589 if (result.isError || result.isException) {
Blink Reformat4c46d092018-04-07 15:32:37590 callback(undefined, result);
Tim van der Lippe1d6e57a2019-09-30 11:55:34591 } else {
Blink Reformat4c46d092018-04-07 15:32:37592 callback(result.value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34593 }
Blink Reformat4c46d092018-04-07 15:32:37594 }
595 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
Tim van der Lippe1d6e57a2019-09-30 11:55:34596 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37597 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34598 }
Blink Reformat4c46d092018-04-07 15:32:37599 extensionServer.sendRequest(request, callback && callbackWrapper);
600 return null;
601 },
602
603 getResources: function(callback) {
604 function wrapResource(resourceData) {
605 return new Resource(resourceData);
606 }
607 function callbackWrapper(resources) {
608 callback(resources.map(wrapResource));
609 }
610 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
611 }
612 };
613
614 /**
615 * @constructor
616 */
617 function ResourceImpl(resourceData) {
618 this._url = resourceData.url;
619 this._type = resourceData.type;
620 }
621
622 ResourceImpl.prototype = {
623 get url() {
624 return this._url;
625 },
626
627 get type() {
628 return this._type;
629 },
630
631 getContent: function(callback) {
632 function callbackWrapper(response) {
633 callback(response.content, response.encoding);
634 }
635
636 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
637 },
638
639 setContent: function(content, commit, callback) {
640 extensionServer.sendRequest(
641 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
642 }
643 };
644
645 function getTabId() {
646 return inspectedTabId;
647 }
648
649 let keyboardEventRequestQueue = [];
650 let forwardTimer = null;
651
652 function forwardKeyboardEvent(event) {
Joel Einbinder67f28fb2018-08-02 00:33:47653 let modifiers = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34654 if (event.shiftKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47655 modifiers |= 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34656 }
657 if (event.ctrlKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47658 modifiers |= 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:34659 }
660 if (event.altKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47661 modifiers |= 4;
Tim van der Lippe1d6e57a2019-09-30 11:55:34662 }
663 if (event.metaKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47664 modifiers |= 8;
Tim van der Lippe1d6e57a2019-09-30 11:55:34665 }
Joel Einbinder67f28fb2018-08-02 00:33:47666 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37667 // We only care about global hotkeys, not about random text
Tim van der Lippe1d6e57a2019-09-30 11:55:34668 if (!keysToForwardSet.has(num)) {
Blink Reformat4c46d092018-04-07 15:32:37669 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34670 }
Joel Einbinder67f28fb2018-08-02 00:33:47671 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37672 const requestPayload = {
673 eventType: event.type,
674 ctrlKey: event.ctrlKey,
675 altKey: event.altKey,
676 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47677 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37678 keyIdentifier: event.keyIdentifier,
679 key: event.key,
680 code: event.code,
681 location: event.location,
682 keyCode: event.keyCode
683 };
684 keyboardEventRequestQueue.push(requestPayload);
Tim van der Lippe1d6e57a2019-09-30 11:55:34685 if (!forwardTimer) {
Blink Reformat4c46d092018-04-07 15:32:37686 forwardTimer = setTimeout(forwardEventQueue, 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34687 }
Blink Reformat4c46d092018-04-07 15:32:37688 }
689
690 function forwardEventQueue() {
691 forwardTimer = null;
692 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
693 extensionServer.sendRequest(request);
694 keyboardEventRequestQueue = [];
695 }
696
697 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37698
699 /**
700 * @constructor
701 */
702 function ExtensionServerClient() {
703 this._callbacks = {};
704 this._handlers = {};
705 this._lastRequestId = 0;
706 this._lastObjectId = 0;
707
708 this.registerHandler('callback', this._onCallback.bind(this));
709
710 const channel = new MessageChannel();
711 this._port = channel.port1;
712 this._port.addEventListener('message', this._onMessage.bind(this), false);
713 this._port.start();
714
715 window.parent.postMessage('registerExtension', '*', [channel.port2]);
716 }
717
718 ExtensionServerClient.prototype = {
719 /**
720 * @param {!Object} message
721 * @param {function()=} callback
722 */
723 sendRequest: function(message, callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34724 if (typeof callback === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37725 message.requestId = this._registerCallback(callback);
Tim van der Lippe1d6e57a2019-09-30 11:55:34726 }
Blink Reformat4c46d092018-04-07 15:32:37727 this._port.postMessage(message);
728 },
729
730 /**
731 * @return {boolean}
732 */
733 hasHandler: function(command) {
734 return !!this._handlers[command];
735 },
736
737 registerHandler: function(command, handler) {
738 this._handlers[command] = handler;
739 },
740
741 unregisterHandler: function(command) {
742 delete this._handlers[command];
743 },
744
745 /**
746 * @return {string}
747 */
748 nextObjectId: function() {
749 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
750 },
751
752 _registerCallback: function(callback) {
753 const id = ++this._lastRequestId;
754 this._callbacks[id] = callback;
755 return id;
756 },
757
758 _onCallback: function(request) {
759 if (request.requestId in this._callbacks) {
760 const callback = this._callbacks[request.requestId];
761 delete this._callbacks[request.requestId];
762 callback(request.result);
763 }
764 },
765
766 _onMessage: function(event) {
767 const request = event.data;
768 const handler = this._handlers[request.command];
Tim van der Lippe1d6e57a2019-09-30 11:55:34769 if (handler) {
Blink Reformat4c46d092018-04-07 15:32:37770 handler.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34771 }
Blink Reformat4c46d092018-04-07 15:32:37772 }
773 };
774
775 function populateInterfaceClass(interfaze, implementation) {
776 for (const member in implementation) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34777 if (member.charAt(0) === '_') {
Blink Reformat4c46d092018-04-07 15:32:37778 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34779 }
Blink Reformat4c46d092018-04-07 15:32:37780 let descriptor = null;
781 // Traverse prototype chain until we find the owner.
Tim van der Lippe1d6e57a2019-09-30 11:55:34782 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
Blink Reformat4c46d092018-04-07 15:32:37783 descriptor = Object.getOwnPropertyDescriptor(owner, member);
Tim van der Lippe1d6e57a2019-09-30 11:55:34784 }
785 if (!descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37786 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34787 }
788 if (typeof descriptor.value === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37789 interfaze[member] = descriptor.value.bind(implementation);
Tim van der Lippe1d6e57a2019-09-30 11:55:34790 } else if (typeof descriptor.get === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37791 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
Tim van der Lippe1d6e57a2019-09-30 11:55:34792 } else {
Blink Reformat4c46d092018-04-07 15:32:37793 Object.defineProperty(interfaze, member, descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34794 }
Blink Reformat4c46d092018-04-07 15:32:37795 }
796 }
797
798 const extensionServer = new ExtensionServerClient();
799 const coreAPI = new InspectorExtensionAPI();
800
801 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
802
803 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
804 chrome.devtools.inspectedWindow = {};
Tim van der Lippeffa78622019-09-16 12:07:12805 Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
Blink Reformat4c46d092018-04-07 15:32:37806 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
807 chrome.devtools.network = coreAPI.network;
808 chrome.devtools.panels = coreAPI.panels;
809 chrome.devtools.panels.themeName = themeName;
810
811 // default to expose experimental APIs for now.
812 if (extensionInfo.exposeExperimentalAPIs !== false) {
813 chrome.experimental = chrome.experimental || {};
814 chrome.experimental.devtools = chrome.experimental.devtools || {};
815
816 const properties = Object.getOwnPropertyNames(coreAPI);
817 for (let i = 0; i < properties.length; ++i) {
818 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34819 if (descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37820 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34821 }
Blink Reformat4c46d092018-04-07 15:32:37822 }
823 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
824 }
825
Tim van der Lippe1d6e57a2019-09-30 11:55:34826 if (extensionInfo.exposeWebInspectorNamespace) {
Blink Reformat4c46d092018-04-07 15:32:37827 window.webInspector = coreAPI;
Tim van der Lippe1d6e57a2019-09-30 11:55:34828 }
Blink Reformat4c46d092018-04-07 15:32:37829 testHook(extensionServer, coreAPI);
830}
831
832/**
833 * @param {!ExtensionDescriptor} extensionInfo
834 * @param {string} inspectedTabId
835 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47836 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37837 * @param {function(!Object, !Object)|undefined} testHook
838 * @return {string}
839 */
Tim van der Lippe29fab472019-08-15 14:46:48840self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Joel Einbinder67f28fb2018-08-02 00:33:47841 const argumentsJSON = [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Tim van der Lippe1d6e57a2019-09-30 11:55:34842 if (!testHook) {
Blink Reformat4c46d092018-04-07 15:32:37843 testHook = () => {};
Tim van der Lippe1d6e57a2019-09-30 11:55:34844 }
Blink Reformat4c46d092018-04-07 15:32:37845 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
846 '(' + injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
847 '})';
Tim van der Lippe29fab472019-08-15 14:46:48848};