ce3f976cd8a68ba0c6a20f6143a072bf760b2cc8
[WebKit-https.git] / Source / WebCore / inspector / front-end / ExtensionAPI.js
1 /*
2  * Copyright (C) 2011 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 function defineCommonExtensionSymbols(apiPrivate)
32 {
33     if (!apiPrivate.audits)
34         apiPrivate.audits = {};
35
36     apiPrivate.audits.Severity = {
37         Info: "info",
38         Warning: "warning",
39         Severe: "severe"
40     };
41
42     if (!apiPrivate.console)
43         apiPrivate.console = {};
44     apiPrivate.console.Severity = {
45         Tip: "tip",
46         Debug: "debug",
47         Log: "log",
48         Warning: "warning",
49         Error: "error"
50     };
51     apiPrivate.Events = {
52         AuditStarted: "audit-started-",
53         ButtonClicked: "button-clicked-",
54         ConsoleMessageAdded: "console-message-added",
55         ElementsPanelObjectSelected: "panel-objectSelected-elements",
56         NetworkRequestFinished: "network-request-finished",
57         Reset: "reset",
58         OpenResource: "open-resource",
59         PanelSearch: "panel-search-",
60         Reload: "Reload",
61         ResourceAdded: "resource-added",
62         ResourceContentCommitted: "resource-content-committed",
63         TimelineEventRecorded: "timeline-event-recorded",
64         ViewShown: "view-shown-",
65         ViewHidden: "view-hidden-"
66     };
67     apiPrivate.Commands = {
68         AddAuditCategory: "addAuditCategory",
69         AddAuditResult: "addAuditResult",
70         AddConsoleMessage: "addConsoleMessage",
71         AddRequestHeaders: "addRequestHeaders",
72         CreatePanel: "createPanel",
73         CreateSidebarPane: "createSidebarPane",
74         CreateStatusBarButton: "createStatusBarButton",
75         EvaluateOnInspectedPage: "evaluateOnInspectedPage",
76         GetConsoleMessages: "getConsoleMessages",
77         GetHAR: "getHAR",
78         GetPageResources: "getPageResources",
79         GetRequestContent: "getRequestContent",
80         GetResourceContent: "getResourceContent",
81         Subscribe: "subscribe",
82         SetOpenResourceHandler: "setOpenResourceHandler",
83         SetResourceContent: "setResourceContent",
84         SetSidebarContent: "setSidebarContent",
85         SetSidebarHeight: "setSidebarHeight",
86         SetSidebarPage: "setSidebarPage",
87         StopAuditCategoryRun: "stopAuditCategoryRun",
88         Unsubscribe: "unsubscribe",
89         UpdateButton: "updateButton",
90         InspectedURLChanged: "inspectedURLChanged"
91     };
92 }
93
94 function injectedExtensionAPI(injectedScriptId)
95 {
96
97 var apiPrivate = {};
98
99 defineCommonExtensionSymbols(apiPrivate);
100
101 var commands = apiPrivate.Commands;
102 var events = apiPrivate.Events;
103
104 // Here and below, all constructors are private to API implementation.
105 // For a public type Foo, if internal fields are present, these are on
106 // a private FooImpl type, an instance of FooImpl is used in a closure
107 // by Foo consutrctor to re-bind publicly exported members to an instance
108 // of Foo.
109
110 /**
111  * @constructor
112  */
113 function EventSinkImpl(type, customDispatch)
114 {
115     this._type = type;
116     this._listeners = [];
117     this._customDispatch = customDispatch;
118 }
119
120 EventSinkImpl.prototype = {
121     addListener: function(callback)
122     {
123         if (typeof callback !== "function")
124             throw "addListener: callback is not a function";
125         if (this._listeners.length === 0)
126             extensionServer.sendRequest({ command: commands.Subscribe, type: this._type });
127         this._listeners.push(callback);
128         extensionServer.registerHandler("notify-" + this._type, this._dispatch.bind(this));
129     },
130
131     removeListener: function(callback)
132     {
133         var listeners = this._listeners;
134
135         for (var i = 0; i < listeners.length; ++i) {
136             if (listeners[i] === callback) {
137                 listeners.splice(i, 1);
138                 break;
139             }
140         }
141         if (this._listeners.length === 0)
142             extensionServer.sendRequest({ command: commands.Unsubscribe, type: this._type });
143     },
144
145     _fire: function()
146     {
147         var listeners = this._listeners.slice();
148         for (var i = 0; i < listeners.length; ++i)
149             listeners[i].apply(null, arguments);
150     },
151
152     _dispatch: function(request)
153     {
154          if (this._customDispatch)
155              this._customDispatch.call(this, request);
156          else
157              this._fire.apply(this, request.arguments);
158     }
159 }
160
161 /**
162  * @constructor
163  */
164 function InspectorExtensionAPI()
165 {
166     this.audits = new Audits();
167     this.inspectedWindow = new InspectedWindow();
168     this.panels = new Panels();
169     this.network = new Network();
170     defineDeprecatedProperty(this, "webInspector", "resources", "network");
171     this.timeline = new Timeline();
172     this.console = new ConsoleAPI();
173
174     this.onReset = new EventSink(events.Reset);
175 }
176
177 /**
178  * @constructor
179  */
180 InspectorExtensionAPI.prototype = {
181     log: function(message)
182     {
183         extensionServer.sendRequest({ command: commands.Log, message: message });
184     }
185 }
186
187 /**
188  * @constructor
189  */
190 function ConsoleAPI()
191 {
192     this.onMessageAdded = new EventSink(events.ConsoleMessageAdded);
193 }
194
195 ConsoleAPI.prototype = {
196     getMessages: function(callback)
197     {
198         extensionServer.sendRequest({ command: commands.GetConsoleMessages }, callback);
199     },
200
201     addMessage: function(severity, text, url, line)
202     {
203         extensionServer.sendRequest({ command: commands.AddConsoleMessage, severity: severity, text: text, url: url, line: line });
204     },
205
206     get Severity()
207     {
208         return apiPrivate.console.Severity;
209     }
210 }
211
212 /**
213  * @constructor
214  */
215 function Network()
216 {
217     function dispatchRequestEvent(message)
218     {
219         var request = message.arguments[1];
220         request.__proto__ = new Request(message.arguments[0]);
221         this._fire(request);
222     }
223     this.onRequestFinished = new EventSink(events.NetworkRequestFinished, dispatchRequestEvent);
224     defineDeprecatedProperty(this, "network", "onFinished", "onRequestFinished");
225     this.onNavigated = new EventSink(events.InspectedURLChanged);
226 }
227
228 Network.prototype = {
229     getHAR: function(callback)
230     {
231         function callbackWrapper(result)
232         {
233             var entries = (result && result.entries) || [];
234             for (var i = 0; i < entries.length; ++i) {
235                 entries[i].__proto__ = new Request(entries[i]._requestId);
236                 delete entries[i]._requestId;
237             }
238             callback(result);
239         }
240         return extensionServer.sendRequest({ command: commands.GetHAR }, callback && callbackWrapper);
241     },
242
243     addRequestHeaders: function(headers)
244     {
245         return extensionServer.sendRequest({ command: commands.AddRequestHeaders, headers: headers, extensionId: window.location.hostname });
246     }
247 }
248
249 /**
250  * @constructor
251  */
252 function RequestImpl(id)
253 {
254     this._id = id;
255 }
256
257 RequestImpl.prototype = {
258     getContent: function(callback)
259     {
260         function callbackWrapper(response)
261         {
262             callback(response.content, response.encoding);
263         }
264         extensionServer.sendRequest({ command: commands.GetRequestContent, id: this._id }, callback && callbackWrapper);
265     }
266 }
267
268 /**
269  * @constructor
270  */
271 function Panels()
272 {
273     var panels = {
274         elements: new ElementsPanel()
275     };
276
277     function panelGetter(name)
278     {
279         return panels[name];
280     }
281     for (var panel in panels)
282         this.__defineGetter__(panel, panelGetter.bind(null, panel));
283 }
284
285 Panels.prototype = {
286     create: function(title, icon, page, callback)
287     {
288         var id = "extension-panel-" + extensionServer.nextObjectId();
289         var request = {
290             command: commands.CreatePanel,
291             id: id,
292             title: title,
293             icon: icon,
294             page: page
295         };
296         extensionServer.sendRequest(request, callback && callback.bind(this, new ExtensionPanel(id)));
297     },
298
299     setOpenResourceHandler: function(callback)
300     {
301         var hadHandler = extensionServer.hasHandler(events.OpenResource);
302
303         if (!callback)
304             extensionServer.unregisterHandler(events.OpenResource);
305         else {
306             function callbackWrapper(message)
307             {
308                 callback.call(null, message.resource, message.lineNumber);
309             }
310             extensionServer.registerHandler(events.OpenResource, callbackWrapper);
311         }
312         // Only send command if we either removed an existing handler or added handler and had none before.
313         if (hadHandler === !callback)
314             extensionServer.sendRequest({ command: commands.SetOpenResourceHandler, "handlerPresent": !!callback });
315     }
316 }
317
318 /**
319  * @constructor
320  */
321 function ExtensionViewImpl(id)
322 {
323     this._id = id;
324
325     function dispatchShowEvent(message)
326     {
327         var frameIndex = message.arguments[0];
328         this._fire(window.top.frames[frameIndex]);
329     }
330     this.onShown = new EventSink(events.ViewShown + id, dispatchShowEvent);
331     this.onHidden = new EventSink(events.ViewHidden + id);
332 }
333
334 /**
335  * @constructor
336  */
337 function PanelWithSidebarImpl(id)
338 {
339     this._id = id;
340 }
341
342 PanelWithSidebarImpl.prototype = {
343     createSidebarPane: function(title, callback)
344     {
345         var id = "extension-sidebar-" + extensionServer.nextObjectId();
346         var request = {
347             command: commands.CreateSidebarPane,
348             panel: this._id,
349             id: id,
350             title: title
351         };
352         function callbackWrapper()
353         {
354             callback(new ExtensionSidebarPane(id));
355         }
356         extensionServer.sendRequest(request, callback && callbackWrapper);
357     }
358 }
359
360 PanelWithSidebarImpl.prototype.__proto__ = ExtensionViewImpl.prototype;
361
362 /**
363  * @constructor
364  * @extends {PanelWithSidebar}
365  */
366 function ElementsPanel()
367 {
368     var id = "elements";
369     PanelWithSidebar.call(this, id);
370     this.onSelectionChanged = new EventSink(events.ElementsPanelObjectSelected);
371 }
372
373 /**
374  * @constructor
375  * @extends {ExtensionViewImpl}
376  */
377 function ExtensionPanelImpl(id)
378 {
379     ExtensionViewImpl.call(this, id);
380     this.onSearch = new EventSink(events.PanelSearch + id);
381 }
382
383 ExtensionPanelImpl.prototype = {
384     createStatusBarButton: function(iconPath, tooltipText, disabled)
385     {
386         var id = "button-" + extensionServer.nextObjectId();
387         var request = {
388             command: commands.CreateStatusBarButton,
389             panel: this._id,
390             id: id,
391             icon: iconPath,
392             tooltip: tooltipText,
393             disabled: !!disabled
394         };
395         extensionServer.sendRequest(request);
396         return new Button(id);
397     }
398 };
399
400 ExtensionPanelImpl.prototype.__proto__ = ExtensionViewImpl.prototype;
401
402 /**
403  * @constructor
404  * @extends {ExtensionViewImpl}
405  */
406 function ExtensionSidebarPaneImpl(id)
407 {
408     ExtensionViewImpl.call(this, id);
409 }
410
411 ExtensionSidebarPaneImpl.prototype = {
412     setHeight: function(height)
413     {
414         extensionServer.sendRequest({ command: commands.SetSidebarHeight, id: this._id, height: height });
415     },
416
417     setExpression: function(expression, rootTitle, callback)
418     {
419         extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: expression, rootTitle: rootTitle, evaluateOnPage: true }, callback);
420     },
421
422     setObject: function(jsonObject, rootTitle, callback)
423     {
424         extensionServer.sendRequest({ command: commands.SetSidebarContent, id: this._id, expression: jsonObject, rootTitle: rootTitle }, callback);
425     },
426
427     setPage: function(page)
428     {
429         extensionServer.sendRequest({ command: commands.SetSidebarPage, id: this._id, page: page });
430     }
431 }
432
433 /**
434  * @constructor
435  */
436 function ButtonImpl(id)
437 {
438     this._id = id;
439     this.onClicked = new EventSink(events.ButtonClicked + id);
440 }
441
442 ButtonImpl.prototype = {
443     update: function(iconPath, tooltipText, disabled)
444     {
445         var request = {
446             command: commands.UpdateButton,
447             id: this._id,
448             icon: iconPath,
449             tooltip: tooltipText,
450             disabled: !!disabled
451         };
452         extensionServer.sendRequest(request);
453     }
454 };
455
456 /**
457  * @constructor
458  */
459 function Audits()
460 {
461 }
462
463 Audits.prototype = {
464     addCategory: function(displayName, resultCount)
465     {
466         var id = "extension-audit-category-" + extensionServer.nextObjectId();
467         extensionServer.sendRequest({ command: commands.AddAuditCategory, id: id, displayName: displayName, resultCount: resultCount });
468         return new AuditCategory(id);
469     }
470 }
471
472 /**
473  * @constructor
474  */
475 function AuditCategoryImpl(id)
476 {
477     function dispatchAuditEvent(request)
478     {
479         var auditResult = new AuditResult(request.arguments[0]);
480         try {
481             this._fire(auditResult);
482         } catch (e) {
483             console.error("Uncaught exception in extension audit event handler: " + e);
484             auditResult.done();
485         }
486     }
487     this._id = id;
488     this.onAuditStarted = new EventSink(events.AuditStarted + id, dispatchAuditEvent);
489 }
490
491 /**
492  * @constructor
493  */
494 function AuditResultImpl(id)
495 {
496     this._id = id;
497
498     this.createURL = this._nodeFactory.bind(null, "url");
499     this.createSnippet = this._nodeFactory.bind(null, "snippet");
500     this.createText = this._nodeFactory.bind(null, "text");
501 }
502
503 AuditResultImpl.prototype = {
504     addResult: function(displayName, description, severity, details)
505     {
506         // shorthand for specifying details directly in addResult().
507         if (details && !(details instanceof AuditResultNode))
508             details = new AuditResultNode(details instanceof Array ? details : [details]);
509
510         var request = {
511             command: commands.AddAuditResult,
512             resultId: this._id,
513             displayName: displayName,
514             description: description,
515             severity: severity,
516             details: details
517         };
518         extensionServer.sendRequest(request);
519     },
520
521     createResult: function()
522     {
523         return new AuditResultNode(Array.prototype.slice.call(arguments));
524     },
525
526     done: function()
527     {
528         extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id });
529     },
530
531     get Severity()
532     {
533         return apiPrivate.audits.Severity;
534     },
535
536     createResourceLink: function(url, lineNumber)
537     {
538         return {
539             type: "resourceLink",
540             arguments: [url, lineNumber && lineNumber - 1]
541         };
542     },
543
544     _nodeFactory: function(type)
545     {
546         return {
547             type: type,
548             arguments: Array.prototype.slice.call(arguments, 1)
549         };
550     }
551 }
552
553 /**
554  * @constructor
555  */
556 function AuditResultNode(contents)
557 {
558     this.contents = contents;
559     this.children = [];
560     this.expanded = false;
561 }
562
563 AuditResultNode.prototype = {
564     addChild: function()
565     {
566         var node = new AuditResultNode(Array.prototype.slice.call(arguments));
567         this.children.push(node);
568         return node;
569     }
570 };
571
572 /**
573  * @constructor
574  */
575 function InspectedWindow()
576 {
577     function dispatchResourceEvent(message)
578     {
579         this._fire(new Resource(message.arguments[0]));
580     }
581     function dispatchResourceContentEvent(message)
582     {
583         this._fire(new Resource(message.arguments[0]), message.arguments[1]);
584     }
585     this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
586     this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
587 }
588
589 InspectedWindow.prototype = {
590     reload: function(optionsOrUserAgent)
591     {
592         var options = null;
593         if (typeof optionsOrUserAgent === "object")
594             options = optionsOrUserAgent;
595         else if (typeof optionsOrUserAgent === "string") {
596             options = { userAgent: optionsOrUserAgent };
597             console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " +
598                          "Use inspectedWindow.reload({ userAgent: value}) instead.");
599         }
600         return extensionServer.sendRequest({ command: commands.Reload, options: options });
601     },
602
603     eval: function(expression, callback)
604     {
605         function callbackWrapper(result)
606         {
607             callback(result.value, result.isException);
608         }
609         return extensionServer.sendRequest({ command: commands.EvaluateOnInspectedPage, expression: expression }, callback && callbackWrapper);
610     },
611
612     getResources: function(callback)
613     {
614         function wrapResource(resourceData)
615         {
616             return new Resource(resourceData);
617         }
618         function callbackWrapper(resources)
619         {
620             callback(resources.map(wrapResource));
621         }
622         return extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper);
623     }
624 }
625
626 /**
627  * @constructor
628  */
629 function ResourceImpl(resourceData)
630 {
631     this._url = resourceData.url
632     this._type = resourceData.type;
633 }
634
635 ResourceImpl.prototype = {
636     get url()
637     {
638         return this._url;
639     },
640
641     get type()
642     {
643         return this._type;
644     },
645
646     getContent: function(callback)
647     {
648         function callbackWrapper(response)
649         {
650             callback(response.content, response.encoding);
651         }
652
653         return extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper);
654     },
655
656     setContent: function(content, commit, callback)
657     {
658         return extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback);
659     }
660 }
661
662 /**
663  * @constructor
664  */
665 function TimelineImpl()
666 {
667     this.onEventRecorded = new EventSink(events.TimelineEventRecorded);
668 }
669
670 /**
671  * @constructor
672  */
673 function ExtensionServerClient()
674 {
675     this._callbacks = {};
676     this._handlers = {};
677     this._lastRequestId = 0;
678     this._lastObjectId = 0;
679
680     this.registerHandler("callback", this._onCallback.bind(this));
681
682     var channel = new MessageChannel();
683     this._port = channel.port1;
684     this._port.addEventListener("message", this._onMessage.bind(this), false);
685     this._port.start();
686
687     top.postMessage("registerExtension", [ channel.port2 ], "*");
688 }
689
690 ExtensionServerClient.prototype = {
691     sendRequest: function(message, callback)
692     {
693         if (typeof callback === "function")
694             message.requestId = this._registerCallback(callback);
695         return this._port.postMessage(message);
696     },
697
698     hasHandler: function(command)
699     {
700         return !!this._handlers[command];
701     },
702
703     registerHandler: function(command, handler)
704     {
705         this._handlers[command] = handler;
706     },
707
708     unregisterHandler: function(command)
709     {
710         delete this._handlers[command];
711     },
712
713     nextObjectId: function()
714     {
715         return injectedScriptId + "_" + ++this._lastObjectId;
716     },
717
718     _registerCallback: function(callback)
719     {
720         var id = ++this._lastRequestId;
721         this._callbacks[id] = callback;
722         return id;
723     },
724
725     _onCallback: function(request)
726     {
727         if (request.requestId in this._callbacks) {
728             var callback = this._callbacks[request.requestId];
729             delete this._callbacks[request.requestId];
730             callback(request.result);
731         }
732     },
733
734     _onMessage: function(event)
735     {
736         var request = event.data;
737         var handler = this._handlers[request.command];
738         if (handler)
739             handler.call(this, request);
740     }
741 }
742
743 function populateInterfaceClass(interface, implementation)
744 {
745     for (var member in implementation) {
746         if (member.charAt(0) === "_")
747             continue;
748         var descriptor = null;
749         // Traverse prototype chain until we find the owner.
750         for (var owner = implementation; owner && !descriptor; owner = owner.__proto__)
751             descriptor = Object.getOwnPropertyDescriptor(owner, member);
752         if (!descriptor)
753             continue;
754         if (typeof descriptor.value === "function")
755             interface[member] = descriptor.value.bind(implementation);
756         else if (typeof descriptor.get === "function")
757             interface.__defineGetter__(member, descriptor.get.bind(implementation));
758         else
759             Object.defineProperty(interface, member, descriptor);
760     }
761 }
762
763 function declareInterfaceClass(implConstructor)
764 {
765     return function()
766     {
767         var impl = { __proto__: implConstructor.prototype };
768         implConstructor.apply(impl, arguments);
769         populateInterfaceClass(this, impl);
770     }
771 }
772
773 function defineDeprecatedProperty(object, className, oldName, newName)
774 {
775     var warningGiven = false;
776     function getter()
777     {
778         if (!warningGiven) {
779             console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead");
780             warningGiven = true;
781         }
782         return object[newName];
783     }
784     object.__defineGetter__(oldName, getter);
785 }
786
787 var AuditCategory = declareInterfaceClass(AuditCategoryImpl);
788 var AuditResult = declareInterfaceClass(AuditResultImpl);
789 var Button = declareInterfaceClass(ButtonImpl);
790 var EventSink = declareInterfaceClass(EventSinkImpl);
791 var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
792 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
793 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
794 var Request = declareInterfaceClass(RequestImpl);
795 var Resource = declareInterfaceClass(ResourceImpl);
796 var Timeline = declareInterfaceClass(TimelineImpl);
797
798 var extensionServer = new ExtensionServerClient();
799
800 return new InspectorExtensionAPI();
801 }
802
803 // Default implementation; platforms will override.
804 function buildPlatformExtensionAPI(extensionInfo)
805 {
806     function platformExtensionAPI(coreAPI)
807     {
808         window.webInspector = coreAPI;
809     }
810     return platformExtensionAPI.toString();
811 }
812
813
814 function buildExtensionAPIInjectedScript(extensionInfo)
815 {
816     return "(function(injectedScriptHost, inspectedWindow, injectedScriptId){ " +
817         defineCommonExtensionSymbols.toString() + ";" +
818         injectedExtensionAPI.toString() + ";" +
819         buildPlatformExtensionAPI(extensionInfo) + ";" +
820         "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" +
821         "return {};" +
822         "})";
823 }