Web Inspector: [Extensions API] add audit formatters for remote objects and DOM elements
[WebKit-https.git] / Source / WebCore / inspector / front-end / ExtensionAPI.js
1 /*
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 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     this.createObject = this._nodeFactory.bind(null, "object");
502     this.createNode = this._nodeFactory.bind(null, "node");
503 }
504
505 AuditResultImpl.prototype = {
506     addResult: function(displayName, description, severity, details)
507     {
508         // shorthand for specifying details directly in addResult().
509         if (details && !(details instanceof AuditResultNode))
510             details = new AuditResultNode(details instanceof Array ? details : [details]);
511
512         var request = {
513             command: commands.AddAuditResult,
514             resultId: this._id,
515             displayName: displayName,
516             description: description,
517             severity: severity,
518             details: details
519         };
520         extensionServer.sendRequest(request);
521     },
522
523     createResult: function()
524     {
525         return new AuditResultNode(Array.prototype.slice.call(arguments));
526     },
527
528     done: function()
529     {
530         extensionServer.sendRequest({ command: commands.StopAuditCategoryRun, resultId: this._id });
531     },
532
533     get Severity()
534     {
535         return apiPrivate.audits.Severity;
536     },
537
538     createResourceLink: function(url, lineNumber)
539     {
540         return {
541             type: "resourceLink",
542             arguments: [url, lineNumber && lineNumber - 1]
543         };
544     },
545
546     _nodeFactory: function(type)
547     {
548         return {
549             type: type,
550             arguments: Array.prototype.slice.call(arguments, 1)
551         };
552     }
553 }
554
555 /**
556  * @constructor
557  */
558 function AuditResultNode(contents)
559 {
560     this.contents = contents;
561     this.children = [];
562     this.expanded = false;
563 }
564
565 AuditResultNode.prototype = {
566     addChild: function()
567     {
568         var node = new AuditResultNode(Array.prototype.slice.call(arguments));
569         this.children.push(node);
570         return node;
571     }
572 };
573
574 /**
575  * @constructor
576  */
577 function InspectedWindow()
578 {
579     function dispatchResourceEvent(message)
580     {
581         this._fire(new Resource(message.arguments[0]));
582     }
583     function dispatchResourceContentEvent(message)
584     {
585         this._fire(new Resource(message.arguments[0]), message.arguments[1]);
586     }
587     this.onResourceAdded = new EventSink(events.ResourceAdded, dispatchResourceEvent);
588     this.onResourceContentCommitted = new EventSink(events.ResourceContentCommitted, dispatchResourceContentEvent);
589 }
590
591 InspectedWindow.prototype = {
592     reload: function(optionsOrUserAgent)
593     {
594         var options = null;
595         if (typeof optionsOrUserAgent === "object")
596             options = optionsOrUserAgent;
597         else if (typeof optionsOrUserAgent === "string") {
598             options = { userAgent: optionsOrUserAgent };
599             console.warn("Passing userAgent as string parameter to inspectedWindow.reload() is deprecated. " +
600                          "Use inspectedWindow.reload({ userAgent: value}) instead.");
601         }
602         return extensionServer.sendRequest({ command: commands.Reload, options: options });
603     },
604
605     eval: function(expression, callback)
606     {
607         function callbackWrapper(result)
608         {
609             callback(result.value, result.isException);
610         }
611         return extensionServer.sendRequest({ command: commands.EvaluateOnInspectedPage, expression: expression }, callback && callbackWrapper);
612     },
613
614     getResources: function(callback)
615     {
616         function wrapResource(resourceData)
617         {
618             return new Resource(resourceData);
619         }
620         function callbackWrapper(resources)
621         {
622             callback(resources.map(wrapResource));
623         }
624         return extensionServer.sendRequest({ command: commands.GetPageResources }, callback && callbackWrapper);
625     }
626 }
627
628 /**
629  * @constructor
630  */
631 function ResourceImpl(resourceData)
632 {
633     this._url = resourceData.url
634     this._type = resourceData.type;
635 }
636
637 ResourceImpl.prototype = {
638     get url()
639     {
640         return this._url;
641     },
642
643     get type()
644     {
645         return this._type;
646     },
647
648     getContent: function(callback)
649     {
650         function callbackWrapper(response)
651         {
652             callback(response.content, response.encoding);
653         }
654
655         return extensionServer.sendRequest({ command: commands.GetResourceContent, url: this._url }, callback && callbackWrapper);
656     },
657
658     setContent: function(content, commit, callback)
659     {
660         return extensionServer.sendRequest({ command: commands.SetResourceContent, url: this._url, content: content, commit: commit }, callback);
661     }
662 }
663
664 /**
665  * @constructor
666  */
667 function TimelineImpl()
668 {
669     this.onEventRecorded = new EventSink(events.TimelineEventRecorded);
670 }
671
672 /**
673  * @constructor
674  */
675 function ExtensionServerClient()
676 {
677     this._callbacks = {};
678     this._handlers = {};
679     this._lastRequestId = 0;
680     this._lastObjectId = 0;
681
682     this.registerHandler("callback", this._onCallback.bind(this));
683
684     var channel = new MessageChannel();
685     this._port = channel.port1;
686     this._port.addEventListener("message", this._onMessage.bind(this), false);
687     this._port.start();
688
689     top.postMessage("registerExtension", [ channel.port2 ], "*");
690 }
691
692 ExtensionServerClient.prototype = {
693     sendRequest: function(message, callback)
694     {
695         if (typeof callback === "function")
696             message.requestId = this._registerCallback(callback);
697         return this._port.postMessage(message);
698     },
699
700     hasHandler: function(command)
701     {
702         return !!this._handlers[command];
703     },
704
705     registerHandler: function(command, handler)
706     {
707         this._handlers[command] = handler;
708     },
709
710     unregisterHandler: function(command)
711     {
712         delete this._handlers[command];
713     },
714
715     nextObjectId: function()
716     {
717         return injectedScriptId + "_" + ++this._lastObjectId;
718     },
719
720     _registerCallback: function(callback)
721     {
722         var id = ++this._lastRequestId;
723         this._callbacks[id] = callback;
724         return id;
725     },
726
727     _onCallback: function(request)
728     {
729         if (request.requestId in this._callbacks) {
730             var callback = this._callbacks[request.requestId];
731             delete this._callbacks[request.requestId];
732             callback(request.result);
733         }
734     },
735
736     _onMessage: function(event)
737     {
738         var request = event.data;
739         var handler = this._handlers[request.command];
740         if (handler)
741             handler.call(this, request);
742     }
743 }
744
745 function populateInterfaceClass(interface, implementation)
746 {
747     for (var member in implementation) {
748         if (member.charAt(0) === "_")
749             continue;
750         var descriptor = null;
751         // Traverse prototype chain until we find the owner.
752         for (var owner = implementation; owner && !descriptor; owner = owner.__proto__)
753             descriptor = Object.getOwnPropertyDescriptor(owner, member);
754         if (!descriptor)
755             continue;
756         if (typeof descriptor.value === "function")
757             interface[member] = descriptor.value.bind(implementation);
758         else if (typeof descriptor.get === "function")
759             interface.__defineGetter__(member, descriptor.get.bind(implementation));
760         else
761             Object.defineProperty(interface, member, descriptor);
762     }
763 }
764
765 function declareInterfaceClass(implConstructor)
766 {
767     return function()
768     {
769         var impl = { __proto__: implConstructor.prototype };
770         implConstructor.apply(impl, arguments);
771         populateInterfaceClass(this, impl);
772     }
773 }
774
775 function defineDeprecatedProperty(object, className, oldName, newName)
776 {
777     var warningGiven = false;
778     function getter()
779     {
780         if (!warningGiven) {
781             console.warn(className + "." + oldName + " is deprecated. Use " + className + "." + newName + " instead");
782             warningGiven = true;
783         }
784         return object[newName];
785     }
786     object.__defineGetter__(oldName, getter);
787 }
788
789 var AuditCategory = declareInterfaceClass(AuditCategoryImpl);
790 var AuditResult = declareInterfaceClass(AuditResultImpl);
791 var Button = declareInterfaceClass(ButtonImpl);
792 var EventSink = declareInterfaceClass(EventSinkImpl);
793 var ExtensionPanel = declareInterfaceClass(ExtensionPanelImpl);
794 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
795 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
796 var Request = declareInterfaceClass(RequestImpl);
797 var Resource = declareInterfaceClass(ResourceImpl);
798 var Timeline = declareInterfaceClass(TimelineImpl);
799
800 var extensionServer = new ExtensionServerClient();
801
802 return new InspectorExtensionAPI();
803 }
804
805 // Default implementation; platforms will override.
806 function buildPlatformExtensionAPI(extensionInfo)
807 {
808     function platformExtensionAPI(coreAPI)
809     {
810         window.webInspector = coreAPI;
811     }
812     return platformExtensionAPI.toString();
813 }
814
815
816 function buildExtensionAPIInjectedScript(extensionInfo)
817 {
818     return "(function(injectedScriptHost, inspectedWindow, injectedScriptId){ " +
819         defineCommonExtensionSymbols.toString() + ";" +
820         injectedExtensionAPI.toString() + ";" +
821         buildPlatformExtensionAPI(extensionInfo) + ";" +
822         "platformExtensionAPI(injectedExtensionAPI(injectedScriptId));" +
823         "return {};" +
824         "})";
825 }