blob: 0d3b2ad20d418697901bf4bdfcd742e5287ee286 [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) {
34 if (!apiPrivate.panels)
35 apiPrivate.panels = {};
36 apiPrivate.panels.SearchAction = {
37 CancelSearch: 'cancelSearch',
38 PerformSearch: 'performSearch',
39 NextSearchResult: 'nextSearchResult',
40 PreviousSearchResult: 'previousSearchResult'
41 };
42
43 /** @enum {string} */
44 apiPrivate.Events = {
45 ButtonClicked: 'button-clicked-',
46 PanelObjectSelected: 'panel-objectSelected-',
47 NetworkRequestFinished: 'network-request-finished',
48 OpenResource: 'open-resource',
49 PanelSearch: 'panel-search-',
50 RecordingStarted: 'trace-recording-started-',
51 RecordingStopped: 'trace-recording-stopped-',
52 ResourceAdded: 'resource-added',
53 ResourceContentCommitted: 'resource-content-committed',
54 ViewShown: 'view-shown-',
55 ViewHidden: 'view-hidden-'
56 };
57
58 /** @enum {string} */
59 apiPrivate.Commands = {
60 AddRequestHeaders: 'addRequestHeaders',
61 AddTraceProvider: 'addTraceProvider',
62 ApplyStyleSheet: 'applyStyleSheet',
63 CompleteTraceSession: 'completeTraceSession',
64 CreatePanel: 'createPanel',
65 CreateSidebarPane: 'createSidebarPane',
66 CreateToolbarButton: 'createToolbarButton',
67 EvaluateOnInspectedPage: 'evaluateOnInspectedPage',
68 ForwardKeyboardEvent: '_forwardKeyboardEvent',
69 GetHAR: 'getHAR',
70 GetPageResources: 'getPageResources',
71 GetRequestContent: 'getRequestContent',
72 GetResourceContent: 'getResourceContent',
73 InspectedURLChanged: 'inspectedURLChanged',
74 OpenResource: 'openResource',
75 Reload: 'Reload',
76 Subscribe: 'subscribe',
77 SetOpenResourceHandler: 'setOpenResourceHandler',
78 SetResourceContent: 'setResourceContent',
79 SetSidebarContent: 'setSidebarContent',
80 SetSidebarHeight: 'setSidebarHeight',
81 SetSidebarPage: 'setSidebarPage',
82 ShowPanel: 'showPanel',
83 Unsubscribe: 'unsubscribe',
84 UpdateButton: 'updateButton'
85 };
86}
87
88/**
89 * @param {!ExtensionDescriptor} extensionInfo
90 * @param {string} inspectedTabId
91 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:4792 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:3793 * @param {number} injectedScriptId
94 * @param {function(!Object, !Object)} testHook
95 * @suppressGlobalPropertiesCheck
96 */
Joel Einbinder67f28fb2018-08-02 00:33:4797function injectedExtensionAPI(extensionInfo, inspectedTabId, themeName, keysToForward, testHook, injectedScriptId) {
98 const keysToForwardSet = new Set(keysToForward);
Blink Reformat4c46d092018-04-07 15:32:3799 const chrome = window.chrome || {};
100 const devtools_descriptor = Object.getOwnPropertyDescriptor(chrome, 'devtools');
101 if (devtools_descriptor)
102 return;
103
104 const apiPrivate = {};
105
106 defineCommonExtensionSymbols(apiPrivate);
107
108 const commands = apiPrivate.Commands;
109 const events = apiPrivate.Events;
110 let userAction = false;
111
112 // Here and below, all constructors are private to API implementation.
113 // For a public type Foo, if internal fields are present, these are on
114 // a private FooImpl type, an instance of FooImpl is used in a closure
115 // by Foo consutrctor to re-bind publicly exported members to an instance
116 // of Foo.
117
118 /**
119 * @constructor
120 */
121 function EventSinkImpl(type, customDispatch) {
122 this._type = type;
123 this._listeners = [];
124 this._customDispatch = customDispatch;
125 }
126
127 EventSinkImpl.prototype = {
128 addListener: function(callback) {
129 if (typeof callback !== 'function')
130 throw 'addListener: callback is not a function';
131 if (this._listeners.length === 0)
132 extensionServer.sendRequest({command: commands.Subscribe, type: this._type});
133 this._listeners.push(callback);
134 extensionServer.registerHandler('notify-' + this._type, this._dispatch.bind(this));
135 },
136
137 removeListener: function(callback) {
138 const listeners = this._listeners;
139
140 for (let i = 0; i < listeners.length; ++i) {
141 if (listeners[i] === callback) {
142 listeners.splice(i, 1);
143 break;
144 }
145 }
146 if (this._listeners.length === 0)
147 extensionServer.sendRequest({command: commands.Unsubscribe, type: this._type});
148 },
149
150 /**
151 * @param {...} vararg
152 */
153 _fire: function(vararg) {
154 const listeners = this._listeners.slice();
155 for (let i = 0; i < listeners.length; ++i)
156 listeners[i].apply(null, arguments);
157 },
158
159 _dispatch: function(request) {
160 if (this._customDispatch)
161 this._customDispatch.call(this, request);
162 else
163 this._fire.apply(this, request.arguments);
164 }
165 };
166
167 /**
168 * @constructor
169 */
170 function InspectorExtensionAPI() {
171 this.inspectedWindow = new InspectedWindow();
172 this.panels = new Panels();
173 this.network = new Network();
174 this.timeline = new Timeline();
175 defineDeprecatedProperty(this, 'webInspector', 'resources', 'network');
176 }
177
178 /**
179 * @constructor
180 */
181 function Network() {
182 /**
183 * @this {EventSinkImpl}
184 */
185 function dispatchRequestEvent(message) {
186 const request = message.arguments[1];
187 request.__proto__ = new Request(message.arguments[0]);
188 this._fire(request);
189 }
190 this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
191 defineDeprecatedProperty(this, 'network', 'onFinished', 'onRequestFinished');
192 this.onNavigated = new EventSink(events.InspectedURLChanged);
193 }
194
195 Network.prototype = {
196 getHAR: function(callback) {
197 function callbackWrapper(result) {
198 const entries = (result && result.entries) || [];
199 for (let i = 0; i < entries.length; ++i) {
200 entries[i].__proto__ = new Request(entries[i]._requestId);
201 delete entries[i]._requestId;
202 }
203 callback(result);
204 }
205 extensionServer.sendRequest({command: commands.GetHAR}, callback && callbackWrapper);
206 },
207
208 addRequestHeaders: function(headers) {
209 extensionServer.sendRequest(
210 {command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname});
211 }
212 };
213
214 /**
215 * @constructor
216 */
217 function RequestImpl(id) {
218 this._id = id;
219 }
220
221 RequestImpl.prototype = {
222 getContent: function(callback) {
223 function callbackWrapper(response) {
224 callback(response.content, response.encoding);
225 }
226 extensionServer.sendRequest({command: commands.GetRequestContent, id: this._id}, callback && callbackWrapper);
227 }
228 };
229
230 /**
231 * @constructor
232 */
233 function Panels() {
234 const panels = {
235 elements: new ElementsPanel(),
236 sources: new SourcesPanel(),
237 };
238
239 function panelGetter(name) {
240 return panels[name];
241 }
242 for (const panel in panels)
243 this.__defineGetter__(panel, panelGetter.bind(null, panel));
244 this.applyStyleSheet = function(styleSheet) {
245 extensionServer.sendRequest({command: commands.ApplyStyleSheet, styleSheet: styleSheet});
246 };
247 }
248
249 Panels.prototype = {
250 create: function(title, icon, page, callback) {
251 const id = 'extension-panel-' + extensionServer.nextObjectId();
252 const request = {command: commands.CreatePanel, id: id, title: title, icon: icon, page: page};
253 extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
254 },
255
256 setOpenResourceHandler: function(callback) {
257 const hadHandler = extensionServer.hasHandler(events.OpenResource);
258
259 function callbackWrapper(message) {
260 // Allow the panel to show itself when handling the event.
261 userAction = true;
262 try {
263 callback.call(null, new Resource(message.resource), message.lineNumber);
264 } finally {
265 userAction = false;
266 }
267 }
268
269 if (!callback)
270 extensionServer.unregisterHandler(events.OpenResource);
271 else
272 extensionServer.registerHandler(events.OpenResource, callbackWrapper);
273
274 // Only send command if we either removed an existing handler or added handler and had none before.
275 if (hadHandler === !callback)
276 extensionServer.sendRequest({command: commands.SetOpenResourceHandler, 'handlerPresent': !!callback});
277 },
278
279 openResource: function(url, lineNumber, callback) {
280 extensionServer.sendRequest({command: commands.OpenResource, 'url': url, 'lineNumber': lineNumber}, callback);
281 },
282
283 get SearchAction() {
284 return apiPrivate.panels.SearchAction;
285 }
286 };
287
288 /**
289 * @constructor
290 */
291 function ExtensionViewImpl(id) {
292 this._id = id;
293
294 /**
295 * @this {EventSinkImpl}
296 */
297 function dispatchShowEvent(message) {
298 const frameIndex = message.arguments[0];
299 if (typeof frameIndex === 'number')
300 this._fire(window.parent.frames[frameIndex]);
301 else
302 this._fire();
303 }
304
305 if (id) {
306 this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
307 this.onHidden = new EventSink(events.ViewHidden + id);
308 }
309 }
310
311 /**
312 * @constructor
313 * @extends {ExtensionViewImpl}
314 * @param {string} hostPanelName
315 */
316 function PanelWithSidebarImpl(hostPanelName) {
317 ExtensionViewImpl.call(this, null);
318 this._hostPanelName = hostPanelName;
319 this.onSelectionChanged = new EventSink(events.PanelObjectSelected + hostPanelName);
320 }
321
322 PanelWithSidebarImpl.prototype = {
323 createSidebarPane: function(title, callback) {
324 const id = 'extension-sidebar-' + extensionServer.nextObjectId();
325 const request = {command: commands.CreateSidebarPane, panel: this._hostPanelName, id: id, title: title};
326 function callbackWrapper() {
327 callback(new ExtensionSidebarPane(id));
328 }
329 extensionServer.sendRequest(request, callback && callbackWrapper);
330 },
331
332 __proto__: ExtensionViewImpl.prototype
333 };
334
335 function declareInterfaceClass(implConstructor) {
336 return function() {
337 const impl = {__proto__: implConstructor.prototype};
338 implConstructor.apply(impl, arguments);
339 populateInterfaceClass(this, impl);
340 };
341 }
342
343 function defineDeprecatedProperty(object, className, oldName, newName) {
344 let warningGiven = false;
345 function getter() {
346 if (!warningGiven) {
347 console.warn(className + '.' + oldName + ' is deprecated. Use ' + className + '.' + newName + ' instead');
348 warningGiven = true;
349 }
350 return object[newName];
351 }
352 object.__defineGetter__(oldName, getter);
353 }
354
355 function extractCallbackArgument(args) {
356 const lastArgument = args[args.length - 1];
357 return typeof lastArgument === 'function' ? lastArgument : undefined;
358 }
359
360 const Button = declareInterfaceClass(ButtonImpl);
361 const EventSink = declareInterfaceClass(EventSinkImpl);
362 const ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
363 const ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
364 const PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
365 const Request = declareInterfaceClass(RequestImpl);
366 const Resource = declareInterfaceClass(ResourceImpl);
367 const TraceSession = declareInterfaceClass(TraceSessionImpl);
368
369 /**
370 * @constructor
371 * @extends {PanelWithSidebar}
372 */
373 function ElementsPanel() {
374 PanelWithSidebar.call(this, 'elements');
375 }
376
377 ElementsPanel.prototype = {__proto__: PanelWithSidebar.prototype};
378
379 /**
380 * @constructor
381 * @extends {PanelWithSidebar}
382 */
383 function SourcesPanel() {
384 PanelWithSidebar.call(this, 'sources');
385 }
386
387 SourcesPanel.prototype = {__proto__: PanelWithSidebar.prototype};
388
389 /**
390 * @constructor
391 * @extends {ExtensionViewImpl}
392 */
393 function ExtensionPanelImpl(id) {
394 ExtensionViewImpl.call(this, id);
395 this.onSearch = new EventSink(events.PanelSearch + id);
396 }
397
398 ExtensionPanelImpl.prototype = {
399 /**
400 * @return {!Object}
401 */
402 createStatusBarButton: function(iconPath, tooltipText, disabled) {
403 const id = 'button-' + extensionServer.nextObjectId();
404 const request = {
405 command: commands.CreateToolbarButton,
406 panel: this._id,
407 id: id,
408 icon: iconPath,
409 tooltip: tooltipText,
410 disabled: !!disabled
411 };
412 extensionServer.sendRequest(request);
413 return new Button(id);
414 },
415
416 show: function() {
417 if (!userAction)
418 return;
419
420 const request = {command: commands.ShowPanel, id: this._id};
421 extensionServer.sendRequest(request);
422 },
423
424 __proto__: ExtensionViewImpl.prototype
425 };
426
427 /**
428 * @constructor
429 * @extends {ExtensionViewImpl}
430 */
431 function ExtensionSidebarPaneImpl(id) {
432 ExtensionViewImpl.call(this, id);
433 }
434
435 ExtensionSidebarPaneImpl.prototype = {
436 setHeight: function(height) {
437 extensionServer.sendRequest({command: commands.SetSidebarHeight, id: this._id, height: height});
438 },
439
440 setExpression: function(expression, rootTitle, evaluateOptions) {
441 const request = {
442 command: commands.SetSidebarContent,
443 id: this._id,
444 expression: expression,
445 rootTitle: rootTitle,
446 evaluateOnPage: true,
447 };
448 if (typeof evaluateOptions === 'object')
449 request.evaluateOptions = evaluateOptions;
450 extensionServer.sendRequest(request, extractCallbackArgument(arguments));
451 },
452
453 setObject: function(jsonObject, rootTitle, callback) {
454 extensionServer.sendRequest(
455 {command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle}, callback);
456 },
457
458 setPage: function(page) {
459 extensionServer.sendRequest({command: commands.SetSidebarPage, id: this._id, page: page});
460 },
461
462 __proto__: ExtensionViewImpl.prototype
463 };
464
465 /**
466 * @constructor
467 */
468 function ButtonImpl(id) {
469 this._id = id;
470 this.onClicked = new EventSink(events.ButtonClicked + id);
471 }
472
473 ButtonImpl.prototype = {
474 update: function(iconPath, tooltipText, disabled) {
475 const request =
476 {command: commands.UpdateButton, id: this._id, icon: iconPath, tooltip: tooltipText, disabled: !!disabled};
477 extensionServer.sendRequest(request);
478 }
479 };
480
481 /**
482 * @constructor
483 */
484 function Timeline() {
485 }
486
487 Timeline.prototype = {
488 /**
489 * @param {string} categoryName
490 * @param {string} categoryTooltip
491 * @return {!TraceProvider}
492 */
493 addTraceProvider: function(categoryName, categoryTooltip) {
494 const id = 'extension-trace-provider-' + extensionServer.nextObjectId();
495 extensionServer.sendRequest(
496 {command: commands.AddTraceProvider, id: id, categoryName: categoryName, categoryTooltip: categoryTooltip});
497 return new TraceProvider(id);
498 }
499 };
500
501 /**
502 * @constructor
503 * @param {string} id
504 */
505 function TraceSessionImpl(id) {
506 this._id = id;
507 }
508
509 TraceSessionImpl.prototype = {
510 /**
511 * @param {string=} url
512 * @param {number=} timeOffset
513 */
514 complete: function(url, timeOffset) {
515 const request =
516 {command: commands.CompleteTraceSession, id: this._id, url: url || '', timeOffset: timeOffset || 0};
517 extensionServer.sendRequest(request);
518 }
519 };
520
521 /**
522 * @constructor
523 * @param {string} id
524 */
525 function TraceProvider(id) {
526 /**
527 * @this {EventSinkImpl}
528 */
529 function dispatchRecordingStarted(message) {
530 const sessionId = message.arguments[0];
531 this._fire(new TraceSession(sessionId));
532 }
533
534 this.onRecordingStarted = new EventSink(events.RecordingStarted + id, dispatchRecordingStarted);
535 this.onRecordingStopped = new EventSink(events.RecordingStopped + id);
536 }
537
538 /**
539 * @constructor
540 */
541 function InspectedWindow() {
542 /**
543 * @this {EventSinkImpl}
544 */
545 function dispatchResourceEvent(message) {
546 this._fire(new Resource(message.arguments[0]));
547 }
548
549 /**
550 * @this {EventSinkImpl}
551 */
552 function dispatchResourceContentEvent(message) {
553 this._fire(new Resource(message.arguments[0]), message.arguments[1]);
554 }
555
556 this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
557 this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
558 }
559
560 InspectedWindow.prototype = {
561 reload: function(optionsOrUserAgent) {
562 let options = null;
563 if (typeof optionsOrUserAgent === 'object') {
564 options = optionsOrUserAgent;
565 } else if (typeof optionsOrUserAgent === 'string') {
566 options = {userAgent: optionsOrUserAgent};
567 console.warn(
568 'Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. ' +
569 'Use inspectedWindow.reload({ userAgent: value}) instead.');
570 }
571 extensionServer.sendRequest({command: commands.Reload, options: options});
572 },
573
574 /**
575 * @return {?Object}
576 */
577 eval: function(expression, evaluateOptions) {
578 const callback = extractCallbackArgument(arguments);
579 function callbackWrapper(result) {
580 if (result.isError || result.isException)
581 callback(undefined, result);
582 else
583 callback(result.value);
584 }
585 const request = {command: commands.EvaluateOnInspectedPage, expression: expression};
586 if (typeof evaluateOptions === 'object')
587 request.evaluateOptions = evaluateOptions;
588 extensionServer.sendRequest(request, callback && callbackWrapper);
589 return null;
590 },
591
592 getResources: function(callback) {
593 function wrapResource(resourceData) {
594 return new Resource(resourceData);
595 }
596 function callbackWrapper(resources) {
597 callback(resources.map(wrapResource));
598 }
599 extensionServer.sendRequest({command: commands.GetPageResources}, callback && callbackWrapper);
600 }
601 };
602
603 /**
604 * @constructor
605 */
606 function ResourceImpl(resourceData) {
607 this._url = resourceData.url;
608 this._type = resourceData.type;
609 }
610
611 ResourceImpl.prototype = {
612 get url() {
613 return this._url;
614 },
615
616 get type() {
617 return this._type;
618 },
619
620 getContent: function(callback) {
621 function callbackWrapper(response) {
622 callback(response.content, response.encoding);
623 }
624
625 extensionServer.sendRequest({command: commands.GetResourceContent, url: this._url}, callback && callbackWrapper);
626 },
627
628 setContent: function(content, commit, callback) {
629 extensionServer.sendRequest(
630 {command: commands.SetResourceContent, url: this._url, content: content, commit: commit}, callback);
631 }
632 };
633
634 function getTabId() {
635 return inspectedTabId;
636 }
637
638 let keyboardEventRequestQueue = [];
639 let forwardTimer = null;
640
641 function forwardKeyboardEvent(event) {
Joel Einbinder67f28fb2018-08-02 00:33:47642 let modifiers = 0;
643 if (event.shiftKey)
644 modifiers |= 1;
645 if (event.ctrlKey)
646 modifiers |= 2;
647 if (event.altKey)
648 modifiers |= 4;
649 if (event.metaKey)
650 modifiers |= 8;
651 const num = (event.keyCode & 255) | (modifiers << 8);
Blink Reformat4c46d092018-04-07 15:32:37652 // We only care about global hotkeys, not about random text
Joel Einbinder67f28fb2018-08-02 00:33:47653 if (!keysToForwardSet.has(num))
Blink Reformat4c46d092018-04-07 15:32:37654 return;
Joel Einbinder67f28fb2018-08-02 00:33:47655 event.preventDefault();
Blink Reformat4c46d092018-04-07 15:32:37656 const requestPayload = {
657 eventType: event.type,
658 ctrlKey: event.ctrlKey,
659 altKey: event.altKey,
660 metaKey: event.metaKey,
Joel Einbinder67f28fb2018-08-02 00:33:47661 shiftKey: event.shiftKey,
Blink Reformat4c46d092018-04-07 15:32:37662 keyIdentifier: event.keyIdentifier,
663 key: event.key,
664 code: event.code,
665 location: event.location,
666 keyCode: event.keyCode
667 };
668 keyboardEventRequestQueue.push(requestPayload);
669 if (!forwardTimer)
670 forwardTimer = setTimeout(forwardEventQueue, 0);
671 }
672
673 function forwardEventQueue() {
674 forwardTimer = null;
675 const request = {command: commands.ForwardKeyboardEvent, entries: keyboardEventRequestQueue};
676 extensionServer.sendRequest(request);
677 keyboardEventRequestQueue = [];
678 }
679
680 document.addEventListener('keydown', forwardKeyboardEvent, false);
Blink Reformat4c46d092018-04-07 15:32:37681
682 /**
683 * @constructor
684 */
685 function ExtensionServerClient() {
686 this._callbacks = {};
687 this._handlers = {};
688 this._lastRequestId = 0;
689 this._lastObjectId = 0;
690
691 this.registerHandler('callback', this._onCallback.bind(this));
692
693 const channel = new MessageChannel();
694 this._port = channel.port1;
695 this._port.addEventListener('message', this._onMessage.bind(this), false);
696 this._port.start();
697
698 window.parent.postMessage('registerExtension', '*', [channel.port2]);
699 }
700
701 ExtensionServerClient.prototype = {
702 /**
703 * @param {!Object} message
704 * @param {function()=} callback
705 */
706 sendRequest: function(message, callback) {
707 if (typeof callback === 'function')
708 message.requestId = this._registerCallback(callback);
709 this._port.postMessage(message);
710 },
711
712 /**
713 * @return {boolean}
714 */
715 hasHandler: function(command) {
716 return !!this._handlers[command];
717 },
718
719 registerHandler: function(command, handler) {
720 this._handlers[command] = handler;
721 },
722
723 unregisterHandler: function(command) {
724 delete this._handlers[command];
725 },
726
727 /**
728 * @return {string}
729 */
730 nextObjectId: function() {
731 return injectedScriptId.toString() + '_' + ++this._lastObjectId;
732 },
733
734 _registerCallback: function(callback) {
735 const id = ++this._lastRequestId;
736 this._callbacks[id] = callback;
737 return id;
738 },
739
740 _onCallback: function(request) {
741 if (request.requestId in this._callbacks) {
742 const callback = this._callbacks[request.requestId];
743 delete this._callbacks[request.requestId];
744 callback(request.result);
745 }
746 },
747
748 _onMessage: function(event) {
749 const request = event.data;
750 const handler = this._handlers[request.command];
751 if (handler)
752 handler.call(this, request);
753 }
754 };
755
756 function populateInterfaceClass(interfaze, implementation) {
757 for (const member in implementation) {
758 if (member.charAt(0) === '_')
759 continue;
760 let descriptor = null;
761 // Traverse prototype chain until we find the owner.
762 for (let owner = implementation; owner && !descriptor; owner = owner.__proto__)
763 descriptor = Object.getOwnPropertyDescriptor(owner, member);
764 if (!descriptor)
765 continue;
766 if (typeof descriptor.value === 'function')
767 interfaze[member] = descriptor.value.bind(implementation);
768 else if (typeof descriptor.get === 'function')
769 interfaze.__defineGetter__(member, descriptor.get.bind(implementation));
770 else
771 Object.defineProperty(interfaze, member, descriptor);
772 }
773 }
774
775 const extensionServer = new ExtensionServerClient();
776 const coreAPI = new InspectorExtensionAPI();
777
778 Object.defineProperty(chrome, 'devtools', {value: {}, enumerable: true});
779
780 // Only expose tabId on chrome.devtools.inspectedWindow, not webInspector.inspectedWindow.
781 chrome.devtools.inspectedWindow = {};
782 chrome.devtools.inspectedWindow.__defineGetter__('tabId', getTabId);
783 chrome.devtools.inspectedWindow.__proto__ = coreAPI.inspectedWindow;
784 chrome.devtools.network = coreAPI.network;
785 chrome.devtools.panels = coreAPI.panels;
786 chrome.devtools.panels.themeName = themeName;
787
788 // default to expose experimental APIs for now.
789 if (extensionInfo.exposeExperimentalAPIs !== false) {
790 chrome.experimental = chrome.experimental || {};
791 chrome.experimental.devtools = chrome.experimental.devtools || {};
792
793 const properties = Object.getOwnPropertyNames(coreAPI);
794 for (let i = 0; i < properties.length; ++i) {
795 const descriptor = Object.getOwnPropertyDescriptor(coreAPI, properties[i]);
796 if (descriptor)
797 Object.defineProperty(chrome.experimental.devtools, properties[i], descriptor);
798 }
799 chrome.experimental.devtools.inspectedWindow = chrome.devtools.inspectedWindow;
800 }
801
802 if (extensionInfo.exposeWebInspectorNamespace)
803 window.webInspector = coreAPI;
804 testHook(extensionServer, coreAPI);
805}
806
807/**
808 * @param {!ExtensionDescriptor} extensionInfo
809 * @param {string} inspectedTabId
810 * @param {string} themeName
Joel Einbinder67f28fb2018-08-02 00:33:47811 * @param {!Array<number>} keysToForward
Blink Reformat4c46d092018-04-07 15:32:37812 * @param {function(!Object, !Object)|undefined} testHook
813 * @return {string}
814 */
Tim van der Lippe29fab472019-08-15 14:46:48815self.buildExtensionAPIInjectedScript = function(extensionInfo, inspectedTabId, themeName, keysToForward, testHook) {
Joel Einbinder67f28fb2018-08-02 00:33:47816 const argumentsJSON = [extensionInfo, inspectedTabId || null, themeName, keysToForward].map(_ => JSON.stringify(_)).join(',');
Blink Reformat4c46d092018-04-07 15:32:37817 if (!testHook)
818 testHook = () => {};
819 return '(function(injectedScriptId){ ' + defineCommonExtensionSymbols.toString() + ';' +
820 '(' + injectedExtensionAPI.toString() + ')(' + argumentsJSON + ',' + testHook + ', injectedScriptId);' +
821 '})';
Tim van der Lippe29fab472019-08-15 14:46:48822};