blob: f89fd797f9b439ac0e42d8e80014f4f8ca06eadf [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
Blink Reformat4c46d092018-04-07 15:32:3731function defineCommonExtensionSymbols(apiPrivate) {
Tim van der Lippe1d6e57a2019-09-30 11:55:3432 if (!apiPrivate.panels) {
Blink Reformat4c46d092018-04-07 15:32:3733 apiPrivate.panels = {};
Tim van der Lippe1d6e57a2019-09-30 11:55:3434 }
Blink Reformat4c46d092018-04-07 15:32:3735 apiPrivate.panels.SearchAction = {
36 CancelSearch: 'cancelSearch',
37 PerformSearch: 'performSearch',
38 NextSearchResult: 'nextSearchResult',
39 PreviousSearchResult: 'previousSearchResult'
40 };
41
42 /** @enum {string} */
43 apiPrivate.Events = {
44 ButtonClicked: 'button-clicked-',
45 PanelObjectSelected: 'panel-objectSelected-',
46 NetworkRequestFinished: 'network-request-finished',
47 OpenResource: 'open-resource',
48 PanelSearch: 'panel-search-',
49 RecordingStarted: 'trace-recording-started-',
50 RecordingStopped: 'trace-recording-stopped-',
51 ResourceAdded: 'resource-added',
52 ResourceContentCommitted: 'resource-content-committed',
53 ViewShown: 'view-shown-',
54 ViewHidden: 'view-hidden-'
55 };
56
57 /** @enum {string} */
58 apiPrivate.Commands = {
59 AddRequestHeaders: 'addRequestHeaders',
60 AddTraceProvider: 'addTraceProvider',
61 ApplyStyleSheet: 'applyStyleSheet',
62 CompleteTraceSession: 'completeTraceSession',
63 CreatePanel: 'createPanel',
64 CreateSidebarPane: 'createSidebarPane',
65 CreateToolbarButton: 'createToolbarButton',
66 EvaluateOnInspectedPage: 'evaluateOnInspectedPage',
67 ForwardKeyboardEvent: '_forwardKeyboardEvent',
68 GetHAR: 'getHAR',
69 GetPageResources: 'getPageResources',
70 GetRequestContent: 'getRequestContent',
71 GetResourceContent: 'getResourceContent',
72 InspectedURLChanged: 'inspectedURLChanged',
73 OpenResource: 'openResource',
74 Reload: 'Reload',
75 Subscribe: 'subscribe',
76 SetOpenResourceHandler: 'setOpenResourceHandler',
77 SetResourceContent: 'setResourceContent',
78 SetSidebarContent: 'setSidebarContent',
79 SetSidebarHeight: 'setSidebarHeight',
80 SetSidebarPage: 'setSidebarPage',
81 ShowPanel: 'showPanel',
82 Unsubscribe: 'unsubscribe',
83 UpdateButton: 'updateButton'
84 };
85}
86
87/**
88 * @param {!ExtensionDescriptor} extensionInfo
89 * @param {string} inspectedTabId
90 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:4791 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:3792 * @param {number} injectedScriptId
93 * @param {function(!Object, !Object)} testHook
94 * @suppressGlobalPropertiesCheck
95 */
Tim van der Lippe226fc222019-10-10 12:17:1296self.injectedExtensionAPI = function(
97 extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
Joel Einbinder67f28fb2018-08-02 00:33:4798 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:3799 const chrome = window.chrome || {};
100 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
Tim van der Lippe1d6e57a2019-09-30 11:55:34101 if (devtools_descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37102 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34103 }
Blink Reformat4c46d092018-04-07 15:32:37104
105 const apiPrivate = {};
106
107 defineCommonExtensionSymbols(apiPrivate);
108
109 const commands = apiPrivate.Commands;
110 const events = apiPrivate.Events;
111 let userAction = false;
112
113 // Here and below, all constructors are private to API implementation.
114 // For a public type Foo, if internal fields are present, these are on
115 // a private FooImpl type, an instance of FooImpl is used in a closure
116 // by Foo consutrctor to re-bind publicly exported members to an instance
117 // of Foo.
118
119 /**
120 * @constructor
121 */
122 function EventSinkImpl(type, customDispatch) {
123 this._type = type;
124 this._listeners = [];
125 this._customDispatch = customDispatch;
126 }
127
128 EventSinkImpl.prototype = {
129 addListener: function(callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34130 if (typeof callback !== 'function') {
Blink Reformat4c46d092018-04-07 15:32:37131 throw 'addListener: callback is not a function';
Tim van der Lippe1d6e57a2019-09-30 11:55:34132 }
133 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37134 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34135 }
Blink Reformat4c46d092018-04-07 15:32:37136 this._listeners.push(callback);
137 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
138 },
139
140 removeListener: function(callback) {
141 const listeners = this._listeners;
142
143 for (let i = 0; i < listeners.length; ++i) {
144 if (listeners[i] === callback) {
145 listeners.splice(i, 1);
146 break;
147 }
148 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34149 if (this._listeners.length === 0) {
Blink Reformat4c46d092018-04-07 15:32:37150 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
Tim van der Lippe1d6e57a2019-09-30 11:55:34151 }
Blink Reformat4c46d092018-04-07 15:32:37152 },
153
154 /**
155 * @param {...} vararg
156 */
157 _fire: function(vararg) {
158 const listeners = this._listeners.slice();
Tim van der Lippe1d6e57a2019-09-30 11:55:34159 for (let i = 0; i < listeners.length; ++i) {
Blink Reformat4c46d092018-04-07 15:32:37160 listeners[i].apply(null, arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34161 }
Blink Reformat4c46d092018-04-07 15:32:37162 },
163
164 _dispatch: function(request) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34165 if (this._customDispatch) {
Blink Reformat4c46d092018-04-07 15:32:37166 this._customDispatch.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34167 } else {
Blink Reformat4c46d092018-04-07 15:32:37168 this._fire.apply(this, request.arguments);
Tim van der Lippe1d6e57a2019-09-30 11:55:34169 }
Blink Reformat4c46d092018-04-07 15:32:37170 }
171 };
172
173 /**
174 * @constructor
175 */
176 function InspectorExtensionAPI() {
177 this.inspectedWindow = new InspectedWindow();
178 this.panels = new Panels();
179 this.network = new Network();
180 this.timeline = new Timeline();
181 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
182 }
183
184 /**
185 * @constructor
186 */
187 function Network() {
188 /**
189 * @this {EventSinkImpl}
190 */
191 function dispatchRequestEvent(message) {
192 const request = message.arguments[1];
193 request.__proto__ = new Request(message.arguments[0]);
194 this._fire(request);
195 }
196 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
197 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
198 this.onNavigated = new EventSink(events.InspectedURLChanged);
199 }
200
201 Network.prototype = {
202 getHAR: function(callback) {
203 function callbackWrapper(result) {
204 const entries = (result && result.entries) || [];
205 for (let i = 0; i < entries.length; ++i) {
206 entries[i].__proto__ = new Request(entries[i]._requestId);
207 delete entries[i]._requestId;
208 }
209 callback(result);
210 }
211 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
212 },
213
214 addRequestHeaders: function(headers) {
215 extensionServer.sendRequest(
216 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
217 }
218 };
219
220 /**
221 * @constructor
222 */
223 function RequestImpl(id) {
224 this._id = id;
225 }
226
227 RequestImpl.prototype = {
228 getContent: function(callback) {
229 function callbackWrapper(response) {
230 callback(response.content, response.encoding);
231 }
232 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
233 }
234 };
235
236 /**
237 * @constructor
238 */
239 function Panels() {
240 const panels = {
241 elements: new ElementsPanel(),
242 sources: new SourcesPanel(),
243 };
244
245 function panelGetter(name) {
246 return panels[name];
247 }
Tim van der Lippe1d6e57a2019-09-30 11:55:34248 for (const panel in panels) {
Tim van der Lippeffa78622019-09-16 12:07:12249 Object.defineProperty(this, panel, {get: panelGetter.bind(null, panel), enumerable: true});
Tim van der Lippe1d6e57a2019-09-30 11:55:34250 }
Blink Reformat4c46d092018-04-07 15:32:37251 this.applyStyleSheet = function(styleSheet) {
252 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
253 };
254 }
255
256 Panels.prototype = {
257 create: function(title, icon, page, callback) {
258 const id = 'extension-panel-' + extensionServer.nextObjectId();
259 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
260 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
261 },
262
263 setOpenResourceHandler: function(callback) {
264 const hadHandler = extensionServer.hasHandler(events.OpenResource);
265
266 function callbackWrapper(message) {
267 // Allow the panel to show itself when handling the event.
268 userAction = true;
269 try {
270 callback.call(null, new Resource(message.resource), message.lineNumber);
271 } finally {
272 userAction = false;
273 }
274 }
275
Tim van der Lippe1d6e57a2019-09-30 11:55:34276 if (!callback) {
Blink Reformat4c46d092018-04-07 15:32:37277 extensionServer.unregisterHandler(events.OpenResource);
Tim van der Lippe1d6e57a2019-09-30 11:55:34278 } else {
Blink Reformat4c46d092018-04-07 15:32:37279 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
Tim van der Lippe1d6e57a2019-09-30 11:55:34280 }
Blink Reformat4c46d092018-04-07 15:32:37281
282 // 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:34283 if (hadHandler === !callback) {
Blink Reformat4c46d092018-04-07 15:32:37284 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
Tim van der Lippe1d6e57a2019-09-30 11:55:34285 }
Blink Reformat4c46d092018-04-07 15:32:37286 },
287
288 openResource: function(url, lineNumber, callback) {
289 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
290 },
291
292 get SearchAction() {
293 return apiPrivate.panels.SearchAction;
294 }
295 };
296
297 /**
298 * @constructor
299 */
300 function ExtensionViewImpl(id) {
301 this._id = id;
302
303 /**
304 * @this {EventSinkImpl}
305 */
306 function dispatchShowEvent(message) {
307 const frameIndex = message.arguments[0];
Tim van der Lippe1d6e57a2019-09-30 11:55:34308 if (typeof frameIndex === 'number') {
Blink Reformat4c46d092018-04-07 15:32:37309 this._fire(window.parent.frames[frameIndex]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34310 } else {
Blink Reformat4c46d092018-04-07 15:32:37311 this._fire();
Tim van der Lippe1d6e57a2019-09-30 11:55:34312 }
Blink Reformat4c46d092018-04-07 15:32:37313 }
314
315 if (id) {
316 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
317 this.onHidden = new EventSink(events.ViewHidden + id);
318 }
319 }
320
321 /**
322 * @constructor
323 * @extends {ExtensionViewImpl}
324 * @param {string} hostPanelName
325 */
326 function PanelWithSidebarImpl(hostPanelName) {
327 ExtensionViewImpl.call(this, null);
328 this._hostPanelName = hostPanelName;
329 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
330 }
331
332 PanelWithSidebarImpl.prototype = {
333 createSidebarPane: function(title, callback) {
334 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
335 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
336 function callbackWrapper() {
337 callback(new ExtensionSidebarPane(id));
338 }
339 extensionServer.sendRequest(request, callback && callbackWrapper);
340 },
341
342 __proto__: ExtensionViewImpl.prototype
343 };
344
345 function declareInterfaceClass(implConstructor) {
346 return function() {
347 const impl = {__proto__: implConstructor.prototype};
348 implConstructor.apply(impl, arguments);
349 populateInterfaceClass(this, impl);
350 };
351 }
352
353 function defineDeprecatedProperty(object, className, oldName, newName) {
354 let warningGiven = false;
355 function getter() {
356 if (!warningGiven) {
357 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
358 warningGiven = true;
359 }
360 return object[newName];
361 }
362 object.__defineGetter__(oldName, getter);
363 }
364
365 function extractCallbackArgument(args) {
366 const lastArgument = args[args.length - 1];
367 return typeof lastArgument === 'function' ? lastArgument : undefined;
368 }
369
370 const Button = declareInterfaceClass(ButtonImpl);
371 const EventSink = declareInterfaceClass(EventSinkImpl);
372 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
373 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
Tim van der Lippeffa78622019-09-16 12:07:12374 /**
375 * @constructor
376 * @param {string} hostPanelName
377 */
378 const PanelWithSidebarClass = declareInterfaceClass(PanelWithSidebarImpl);
Blink Reformat4c46d092018-04-07 15:32:37379 const Request = declareInterfaceClass(RequestImpl);
380 const Resource = declareInterfaceClass(ResourceImpl);
381 const TraceSession = declareInterfaceClass(TraceSessionImpl);
382
Tim van der Lippeffa78622019-09-16 12:07:12383 class ElementsPanel extends PanelWithSidebarClass {
384 constructor() {
385 super('elements');
386 }
Blink Reformat4c46d092018-04-07 15:32:37387 }
388
Tim van der Lippeffa78622019-09-16 12:07:12389 class SourcesPanel extends PanelWithSidebarClass {
390 constructor() {
391 super('sources');
392 }
Blink Reformat4c46d092018-04-07 15:32:37393 }
394
Blink Reformat4c46d092018-04-07 15:32:37395 /**
396 * @constructor
397 * @extends {ExtensionViewImpl}
398 */
399 function ExtensionPanelImpl(id) {
400 ExtensionViewImpl.call(this, id);
401 this.onSearch = new EventSink(events.PanelSearch + id);
402 }
403
404 ExtensionPanelImpl.prototype = {
405 /**
406 * @return {!Object}
407 */
408 createStatusBarButton: function(iconPath, tooltipText, disabled) {
409 const id = 'button-' + extensionServer.nextObjectId();
410 const request = {
411 command: commands.CreateToolbarButton,
412 panel: this._id,
413 id: id,
414 icon: iconPath,
415 tooltip: tooltipText,
416 disabled: !!disabled
417 };
418 extensionServer.sendRequest(request);
419 return new Button(id);
420 },
421
422 show: function() {
Tim van der Lippe1d6e57a2019-09-30 11:55:34423 if (!userAction) {
Blink Reformat4c46d092018-04-07 15:32:37424 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34425 }
Blink Reformat4c46d092018-04-07 15:32:37426
427 const request = {command: commands.ShowPanel, id: this._id};
428 extensionServer.sendRequest(request);
429 },
430
431 __proto__: ExtensionViewImpl.prototype
432 };
433
434 /**
435 * @constructor
436 * @extends {ExtensionViewImpl}
437 */
438 function ExtensionSidebarPaneImpl(id) {
439 ExtensionViewImpl.call(this, id);
440 }
441
442 ExtensionSidebarPaneImpl.prototype = {
443 setHeight: function(height) {
444 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
445 },
446
447 setExpression: function(expression, rootTitle, evaluateOptions) {
448 const request = {
449 command: commands.SetSidebarContent,
450 id: this._id,
451 expression: expression,
452 rootTitle: rootTitle,
453 evaluateOnPage: true,
454 };
Tim van der Lippe1d6e57a2019-09-30 11:55:34455 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37456 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34457 }
Blink Reformat4c46d092018-04-07 15:32:37458 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
459 },
460
461 setObject: function(jsonObject, rootTitle, callback) {
462 extensionServer.sendRequest(
463 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
464 },
465
466 setPage: function(page) {
467 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
468 },
469
470 __proto__: ExtensionViewImpl.prototype
471 };
472
473 /**
474 * @constructor
475 */
476 function ButtonImpl(id) {
477 this._id = id;
478 this.onClicked = new EventSink(events.ButtonClicked + id);
479 }
480
481 ButtonImpl.prototype = {
482 update: function(iconPath, tooltipText, disabled) {
483 const request =
484 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
485 extensionServer.sendRequest(request);
486 }
487 };
488
489 /**
490 * @constructor
491 */
492 function Timeline() {
493 }
494
495 Timeline.prototype = {
496 /**
497 * @param {string} categoryName
498 * @param {string} categoryTooltip
499 * @return {!TraceProvider}
500 */
501 addTraceProvider: function(categoryName, categoryTooltip) {
502 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
503 extensionServer.sendRequest(
504 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
505 return new TraceProvider(id);
506 }
507 };
508
509 /**
510 * @constructor
511 * @param {string} id
512 */
513 function TraceSessionImpl(id) {
514 this._id = id;
515 }
516
517 TraceSessionImpl.prototype = {
518 /**
519 * @param {string=} url
520 * @param {number=} timeOffset
521 */
522 complete: function(url, timeOffset) {
523 const request =
524 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
525 extensionServer.sendRequest(request);
526 }
527 };
528
529 /**
530 * @constructor
531 * @param {string} id
532 */
533 function TraceProvider(id) {
534 /**
535 * @this {EventSinkImpl}
536 */
537 function dispatchRecordingStarted(message) {
538 const sessionId = message.arguments[0];
539 this._fire(new TraceSession(sessionId));
540 }
541
542 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
543 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
544 }
545
546 /**
547 * @constructor
548 */
549 function InspectedWindow() {
550 /**
551 * @this {EventSinkImpl}
552 */
553 function dispatchResourceEvent(message) {
554 this._fire(new Resource(message.arguments[0]));
555 }
556
557 /**
558 * @this {EventSinkImpl}
559 */
560 function dispatchResourceContentEvent(message) {
561 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
562 }
563
564 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
565 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
566 }
567
568 InspectedWindow.prototype = {
569 reload: function(optionsOrUserAgent) {
570 let options = null;
571 if (typeof optionsOrUserAgent === 'object') {
572 options = optionsOrUserAgent;
573 } else if (typeof optionsOrUserAgent === 'string') {
574 options = {userAgent: optionsOrUserAgent};
575 console.warn(
576 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
577 'Use inspectedWindow.reload({ userAgent: value}) instead.');
578 }
579 extensionServer.sendRequest({command: commands.Reload, options: options});
580 },
581
582 /**
583 * @return {?Object}
584 */
585 eval: function(expression, evaluateOptions) {
586 const callback = extractCallbackArgument(arguments);
587 function callbackWrapper(result) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34588 if (result.isError || result.isException) {
Blink Reformat4c46d092018-04-07 15:32:37589 callback(undefined, result);
Tim van der Lippe1d6e57a2019-09-30 11:55:34590 } else {
Blink Reformat4c46d092018-04-07 15:32:37591 callback(result.value);
Tim van der Lippe1d6e57a2019-09-30 11:55:34592 }
Blink Reformat4c46d092018-04-07 15:32:37593 }
594 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
Tim van der Lippe1d6e57a2019-09-30 11:55:34595 if (typeof evaluateOptions === 'object') {
Blink Reformat4c46d092018-04-07 15:32:37596 request.evaluateOptions = evaluateOptions;
Tim van der Lippe1d6e57a2019-09-30 11:55:34597 }
Blink Reformat4c46d092018-04-07 15:32:37598 extensionServer.sendRequest(request, callback && callbackWrapper);
599 return null;
600 },
601
602 getResources: function(callback) {
603 function wrapResource(resourceData) {
604 return new Resource(resourceData);
605 }
606 function callbackWrapper(resources) {
607 callback(resources.map(wrapResource));
608 }
609 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
610 }
611 };
612
613 /**
614 * @constructor
615 */
616 function ResourceImpl(resourceData) {
617 this._url = resourceData.url;
618 this._type = resourceData.type;
619 }
620
621 ResourceImpl.prototype = {
622 get url() {
623 return this._url;
624 },
625
626 get type() {
627 return this._type;
628 },
629
630 getContent: function(callback) {
631 function callbackWrapper(response) {
632 callback(response.content, response.encoding);
633 }
634
635 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
636 },
637
638 setContent: function(content, commit, callback) {
639 extensionServer.sendRequest(
640 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
641 }
642 };
643
644 function getTabId() {
645 return inspectedTabId;
646 }
647
648 let keyboardEventRequestQueue = [];
649 let forwardTimer = null;
650
651 function forwardKeyboardEvent(event) {
Joel Einbinder67f28fb2018-08-02 00:33:47652 let modifiers = 0;
Tim van der Lippe1d6e57a2019-09-30 11:55:34653 if (event.shiftKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47654 modifiers |= 1;
Tim van der Lippe1d6e57a2019-09-30 11:55:34655 }
656 if (event.ctrlKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47657 modifiers |= 2;
Tim van der Lippe1d6e57a2019-09-30 11:55:34658 }
659 if (event.altKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47660 modifiers |= 4;
Tim van der Lippe1d6e57a2019-09-30 11:55:34661 }
662 if (event.metaKey) {
Joel Einbinder67f28fb2018-08-02 00:33:47663 modifiers |= 8;
Tim van der Lippe1d6e57a2019-09-30 11:55:34664 }
Joel Einbinder67f28fb2018-08-02 00:33:47665 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37666 // We only care about global hotkeys, not about random text
Tim van der Lippe1d6e57a2019-09-30 11:55:34667 if (!keysToForwardSet.has(num)) {
Blink Reformat4c46d092018-04-07 15:32:37668 return;
Tim van der Lippe1d6e57a2019-09-30 11:55:34669 }
Joel Einbinder67f28fb2018-08-02 00:33:47670 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37671 const requestPayload = {
672 eventType: event.type,
673 ctrlKey: event.ctrlKey,
674 altKey: event.altKey,
675 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47676 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37677 keyIdentifier: event.keyIdentifier,
678 key: event.key,
679 code: event.code,
680 location: event.location,
681 keyCode: event.keyCode
682 };
683 keyboardEventRequestQueue.push(requestPayload);
Tim van der Lippe1d6e57a2019-09-30 11:55:34684 if (!forwardTimer) {
Blink Reformat4c46d092018-04-07 15:32:37685 forwardTimer = setTimeout(forwardEventQueue, 0);
Tim van der Lippe1d6e57a2019-09-30 11:55:34686 }
Blink Reformat4c46d092018-04-07 15:32:37687 }
688
689 function forwardEventQueue() {
690 forwardTimer = null;
691 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
692 extensionServer.sendRequest(request);
693 keyboardEventRequestQueue = [];
694 }
695
696 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37697
698 /**
699 * @constructor
700 */
701 function ExtensionServerClient() {
702 this._callbacks = {};
703 this._handlers = {};
704 this._lastRequestId = 0;
705 this._lastObjectId = 0;
706
707 this.registerHandler('callback', this._onCallback.bind(this));
708
709 const channel = new MessageChannel();
710 this._port = channel.port1;
711 this._port.addEventListener('message', this._onMessage.bind(this), false);
712 this._port.start();
713
714 window.parent.postMessage('registerExtension', '*', [channel.port2]);
715 }
716
717 ExtensionServerClient.prototype = {
718 /**
719 * @param {!Object} message
720 * @param {function()=} callback
721 */
722 sendRequest: function(message, callback) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34723 if (typeof callback === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37724 message.requestId = this._registerCallback(callback);
Tim van der Lippe1d6e57a2019-09-30 11:55:34725 }
Blink Reformat4c46d092018-04-07 15:32:37726 this._port.postMessage(message);
727 },
728
729 /**
730 * @return {boolean}
731 */
732 hasHandler: function(command) {
733 return !!this._handlers[command];
734 },
735
736 registerHandler: function(command, handler) {
737 this._handlers[command] = handler;
738 },
739
740 unregisterHandler: function(command) {
741 delete this._handlers[command];
742 },
743
744 /**
745 * @return {string}
746 */
747 nextObjectId: function() {
748 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
749 },
750
751 _registerCallback: function(callback) {
752 const id = ++this._lastRequestId;
753 this._callbacks[id] = callback;
754 return id;
755 },
756
757 _onCallback: function(request) {
758 if (request.requestId in this._callbacks) {
759 const callback = this._callbacks[request.requestId];
760 delete this._callbacks[request.requestId];
761 callback(request.result);
762 }
763 },
764
765 _onMessage: function(event) {
766 const request = event.data;
767 const handler = this._handlers[request.command];
Tim van der Lippe1d6e57a2019-09-30 11:55:34768 if (handler) {
Blink Reformat4c46d092018-04-07 15:32:37769 handler.call(this, request);
Tim van der Lippe1d6e57a2019-09-30 11:55:34770 }
Blink Reformat4c46d092018-04-07 15:32:37771 }
772 };
773
774 function populateInterfaceClass(interfaze, implementation) {
775 for (const member in implementation) {
Tim van der Lippe1d6e57a2019-09-30 11:55:34776 if (member.charAt(0) === '_') {
Blink Reformat4c46d092018-04-07 15:32:37777 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34778 }
Blink Reformat4c46d092018-04-07 15:32:37779 let descriptor = null;
780 // Traverse prototype chain until we find the owner.
Tim van der Lippe1d6e57a2019-09-30 11:55:34781 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__) {
Blink Reformat4c46d092018-04-07 15:32:37782 descriptor = Object.getOwnPropertyDescriptor(owner, member);
Tim van der Lippe1d6e57a2019-09-30 11:55:34783 }
784 if (!descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37785 continue;
Tim van der Lippe1d6e57a2019-09-30 11:55:34786 }
787 if (typeof descriptor.value === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37788 interfaze[member] = descriptor.value.bind(implementation);
Tim van der Lippe1d6e57a2019-09-30 11:55:34789 } else if (typeof descriptor.get === 'function') {
Blink Reformat4c46d092018-04-07 15:32:37790 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
Tim van der Lippe1d6e57a2019-09-30 11:55:34791 } else {
Blink Reformat4c46d092018-04-07 15:32:37792 Object.defineProperty(interfaze, member, descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34793 }
Blink Reformat4c46d092018-04-07 15:32:37794 }
795 }
796
797 const extensionServer = new ExtensionServerClient();
798 const coreAPI = new InspectorExtensionAPI();
799
800 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
801
802 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
803 chrome.devtools.inspectedWindow = {};
Tim van der Lippeffa78622019-09-16 12:07:12804 Object.defineProperty(chrome.devtools.inspectedWindow, 'tabId', {get: getTabId});
Blink Reformat4c46d092018-04-07 15:32:37805 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
806 chrome.devtools.network = coreAPI.network;
807 chrome.devtools.panels = coreAPI.panels;
808 chrome.devtools.panels.themeName = themeName;
809
810 // default to expose experimental APIs for now.
811 if (extensionInfo.exposeExperimentalAPIs !== false) {
812 chrome.experimental = chrome.experimental || {};
813 chrome.experimental.devtools = chrome.experimental.devtools || {};
814
815 const properties = Object.getOwnPropertyNames(coreAPI);
816 for (let i = 0; i < properties.length; ++i) {
817 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
Tim van der Lippe1d6e57a2019-09-30 11:55:34818 if (descriptor) {
Blink Reformat4c46d092018-04-07 15:32:37819 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
Tim van der Lippe1d6e57a2019-09-30 11:55:34820 }
Blink Reformat4c46d092018-04-07 15:32:37821 }
822 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
823 }
824
Tim van der Lippe1d6e57a2019-09-30 11:55:34825 if (extensionInfo.exposeWebInspectorNamespace) {
Blink Reformat4c46d092018-04-07 15:32:37826 window.webInspector = coreAPI;
Tim van der Lippe1d6e57a2019-09-30 11:55:34827 }
Blink Reformat4c46d092018-04-07 15:32:37828 testHook(extensionServer, coreAPI);
Tim van der Lippe226fc222019-10-10 12:17:12829};
Blink Reformat4c46d092018-04-07 15:32:37830
831/**
832 * @param {!ExtensionDescriptor} extensionInfo
833 * @param {string} inspectedTabId
834 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47835 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37836 * @param {function(!Object, !Object)|undefined} testHook
837 * @return {string}
838 */
Tim van der Lippe29fab472019-08-15 14:46:48839self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Joel Einbinder67f28fb2018-08-02 00:33:47840 const argumentsJSON = [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Tim van der Lippe1d6e57a2019-09-30 11:55:34841 if (!testHook) {
Blink Reformat4c46d092018-04-07 15:32:37842 testHook = () => {};
Tim van der Lippe1d6e57a2019-09-30 11:55:34843 }
Blink Reformat4c46d092018-04-07 15:32:37844 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
Tim van der Lippe226fc222019-10-10 12:17:12845 '(' + self.injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
Blink Reformat4c46d092018-04-07 15:32:37846 '})';
Tim van der Lippe29fab472019-08-15 14:46:48847};
Tim van der Lippe226fc222019-10-10 12:17:12848
849/* Legacy exported object */
850self.Extensions = self.Extensions || {};
851
852/* Legacy exported object */
853Extensions = Extensions || {};
854
855Extensions.extensionAPI = {};
856defineCommonExtensionSymbols(Extensions.extensionAPI);