Web Inspector: further align front-end configurations: get rid of saveAsAvailable...
[WebKit-https.git] / Source / WebCore / inspector / front-end / ExtensionServer.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 /**
32  * @constructor
33  */
34 WebInspector.ExtensionServer = function()
35 {
36     this._clientObjects = {};
37     this._handlers = {};
38     this._subscribers = {};
39     this._subscriptionStartHandlers = {};
40     this._subscriptionStopHandlers = {};
41     this._extraHeaders = {};
42     this._requests = {};
43     this._lastRequestId = 0;
44     this._registeredExtensions = {};
45     this._status = new WebInspector.ExtensionStatus();
46
47     var commands = WebInspector.extensionAPI.Commands;
48
49     this._registerHandler(commands.AddAuditCategory, this._onAddAuditCategory.bind(this));
50     this._registerHandler(commands.AddAuditResult, this._onAddAuditResult.bind(this));
51     this._registerHandler(commands.AddConsoleMessage, this._onAddConsoleMessage.bind(this));
52     this._registerHandler(commands.AddRequestHeaders, this._onAddRequestHeaders.bind(this));
53     this._registerHandler(commands.CreatePanel, this._onCreatePanel.bind(this));
54     this._registerHandler(commands.CreateSidebarPane, this._onCreateSidebarPane.bind(this));
55     this._registerHandler(commands.CreateStatusBarButton, this._onCreateStatusBarButton.bind(this));
56     this._registerHandler(commands.EvaluateOnInspectedPage, this._onEvaluateOnInspectedPage.bind(this));
57     this._registerHandler(commands.GetHAR, this._onGetHAR.bind(this));
58     this._registerHandler(commands.GetConsoleMessages, this._onGetConsoleMessages.bind(this));
59     this._registerHandler(commands.GetPageResources, this._onGetPageResources.bind(this));
60     this._registerHandler(commands.GetRequestContent, this._onGetRequestContent.bind(this));
61     this._registerHandler(commands.GetResourceContent, this._onGetResourceContent.bind(this));
62     this._registerHandler(commands.Log, this._onLog.bind(this));
63     this._registerHandler(commands.Reload, this._onReload.bind(this));
64     this._registerHandler(commands.SetOpenResourceHandler, this._onSetOpenResourceHandler.bind(this));
65     this._registerHandler(commands.SetResourceContent, this._onSetResourceContent.bind(this));
66     this._registerHandler(commands.SetSidebarHeight, this._onSetSidebarHeight.bind(this));
67     this._registerHandler(commands.SetSidebarContent, this._onSetSidebarContent.bind(this));
68     this._registerHandler(commands.SetSidebarPage, this._onSetSidebarPage.bind(this));
69     this._registerHandler(commands.StopAuditCategoryRun, this._onStopAuditCategoryRun.bind(this));
70     this._registerHandler(commands.Subscribe, this._onSubscribe.bind(this));
71     this._registerHandler(commands.Unsubscribe, this._onUnsubscribe.bind(this));
72     this._registerHandler(commands.UpdateButton, this._onUpdateButton.bind(this));
73
74     window.addEventListener("message", this._onWindowMessage.bind(this), false);
75 }
76
77 WebInspector.ExtensionServer.prototype = {
78     hasExtensions: function()
79     {
80         return !!Object.keys(this._registeredExtensions).length;
81     },
82
83     notifySearchAction: function(panelId, action, searchString)
84     {
85         this._postNotification(WebInspector.extensionAPI.Events.PanelSearch + panelId, action, searchString);
86     },
87
88     notifyViewShown: function(identifier, frameIndex)
89     {
90         this._postNotification(WebInspector.extensionAPI.Events.ViewShown + identifier, frameIndex);
91     },
92
93     notifyViewHidden: function(identifier)
94     {
95         this._postNotification(WebInspector.extensionAPI.Events.ViewHidden + identifier);
96     },
97
98     notifyButtonClicked: function(identifier)
99     {
100         this._postNotification(WebInspector.extensionAPI.Events.ButtonClicked + identifier);
101     },
102
103     _inspectedURLChanged: function(event)
104     {
105         this._requests = {};
106         var url = event.data;
107         this._postNotification(WebInspector.extensionAPI.Events.InspectedURLChanged, url);
108     },
109
110     _mainFrameNavigated: function(event)
111     {
112         this._postNotification(WebInspector.extensionAPI.Events.Reset);
113     },
114
115     startAuditRun: function(category, auditRun)
116     {
117         this._clientObjects[auditRun.id] = auditRun;
118         this._postNotification("audit-started-" + category.id, auditRun.id);
119     },
120
121     stopAuditRun: function(auditRun)
122     {
123         delete this._clientObjects[auditRun.id];
124     },
125
126     /**
127      * @param {...*} vararg
128      */
129     _postNotification: function(type, vararg)
130     {
131         var subscribers = this._subscribers[type];
132         if (!subscribers)
133             return;
134         var message = {
135             command: "notify-" + type,
136             arguments: Array.prototype.slice.call(arguments, 1)
137         };
138         for (var i = 0; i < subscribers.length; ++i)
139             subscribers[i].postMessage(message);
140     },
141
142     _onSubscribe: function(message, port)
143     {
144         var subscribers = this._subscribers[message.type];
145         if (subscribers)
146             subscribers.push(port);
147         else {
148             this._subscribers[message.type] = [ port ];
149             if (this._subscriptionStartHandlers[message.type])
150                 this._subscriptionStartHandlers[message.type]();
151         }
152     },
153
154     _onUnsubscribe: function(message, port)
155     {
156         var subscribers = this._subscribers[message.type];
157         if (!subscribers)
158             return;
159         subscribers.remove(port);
160         if (!subscribers.length) {
161             delete this._subscribers[message.type];
162             if (this._subscriptionStopHandlers[message.type])
163                 this._subscriptionStopHandlers[message.type]();
164         }
165     },
166
167     _onAddRequestHeaders: function(message)
168     {
169         var id = message.extensionId;
170         if (typeof id !== "string")
171             return this._status.E_BADARGTYPE("extensionId", typeof id, "string");
172         var extensionHeaders = this._extraHeaders[id];
173         if (!extensionHeaders) {
174             extensionHeaders = {};
175             this._extraHeaders[id] = extensionHeaders;
176         }
177         for (var name in message.headers)
178             extensionHeaders[name] = message.headers[name];
179         var allHeaders = /** @type NetworkAgent.Headers */ {};
180         for (var extension in this._extraHeaders) {
181             var headers = this._extraHeaders[extension];
182             for (name in headers) {
183                 if (typeof headers[name] === "string")
184                     allHeaders[name] = headers[name];
185             }
186         }
187         NetworkAgent.setExtraHTTPHeaders(allHeaders);
188     },
189
190     _onCreatePanel: function(message, port)
191     {
192         var id = message.id;
193         // The ids are generated on the client API side and must be unique, so the check below
194         // shouldn't be hit unless someone is bypassing the API.
195         if (id in this._clientObjects || id in WebInspector.panels)
196             return this._status.E_EXISTS(id);
197
198         var page = this._expandResourcePath(port._extensionOrigin, message.page);
199         var icon = this._expandResourcePath(port._extensionOrigin, message.icon)
200         var panel = new WebInspector.ExtensionPanel(id, message.title, page, icon);
201         this._clientObjects[id] = panel;
202         WebInspector.panels[id] = panel;
203         WebInspector.addPanel(panel);
204         return this._status.OK();
205     },
206
207     _onCreateStatusBarButton: function(message, port)
208     {
209         var panel = this._clientObjects[message.panel];
210         if (!panel || !(panel instanceof WebInspector.ExtensionPanel))
211             return this._status.E_NOTFOUND(message.panel);
212         var button = new WebInspector.ExtensionButton(message.id, this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled);
213         this._clientObjects[message.id] = button;
214         panel.addStatusBarItem(button.element);
215         return this._status.OK();
216     },
217
218     _onUpdateButton: function(message, port)
219     {
220         var button = this._clientObjects[message.id];
221         if (!button || !(button instanceof WebInspector.ExtensionButton))
222             return this._status.E_NOTFOUND(message.id);
223         button.update(this._expandResourcePath(port._extensionOrigin, message.icon), message.tooltip, message.disabled);
224         return this._status.OK();
225     },
226
227     _onCreateSidebarPane: function(message)
228     {
229         var panel = WebInspector.panels[message.panel];
230         if (!panel)
231             return this._status.E_NOTFOUND(message.panel);
232         if (!panel.sidebarElement || !panel.sidebarPanes)
233             return this._status.E_NOTSUPPORTED();
234         var id = message.id;
235         var sidebar = new WebInspector.ExtensionSidebarPane(message.title, message.id);
236         this._clientObjects[id] = sidebar;
237         panel.sidebarPanes[id] = sidebar;
238         panel.sidebarElement.appendChild(sidebar.element);
239
240         return this._status.OK();
241     },
242
243     _onSetSidebarHeight: function(message)
244     {
245         var sidebar = this._clientObjects[message.id];
246         if (!sidebar)
247             return this._status.E_NOTFOUND(message.id);
248         sidebar.setHeight(message.height);
249         return this._status.OK();
250     },
251
252     _onSetSidebarContent: function(message, port)
253     {
254         var sidebar = this._clientObjects[message.id];
255         if (!sidebar)
256             return this._status.E_NOTFOUND(message.id);
257         function callback(error)
258         {
259             var result = error ? this._status.E_FAILED(error) : this._status.OK();
260             this._dispatchCallback(message.requestId, port, result);
261         }
262         if (message.evaluateOnPage)
263             sidebar.setExpression(message.expression, message.rootTitle, callback.bind(this));
264         else
265             sidebar.setObject(message.expression, message.rootTitle, callback.bind(this));
266     },
267
268     _onSetSidebarPage: function(message, port)
269     {
270         var sidebar = this._clientObjects[message.id];
271         if (!sidebar)
272             return this._status.E_NOTFOUND(message.id);
273         sidebar.setPage(this._expandResourcePath(port._extensionOrigin, message.page));
274     },
275
276     _onSetOpenResourceHandler: function(message, port)
277     {
278         var name = this._registeredExtensions[port._extensionOrigin].name || ("Extension " + port._extensionOrigin);
279         if (message.handlerPresent)
280             WebInspector.openAnchorLocationRegistry.registerHandler(name, this._handleOpenURL.bind(this, port));
281         else
282             WebInspector.openAnchorLocationRegistry.unregisterHandler(name);
283     },
284
285     _handleOpenURL: function(port, url)
286     {
287         var resource = WebInspector.resourceForURL(url);
288         if (!resource)
289             return false;
290         port.postMessage({
291             command: "open-resource",
292             resource: this._makeResource(resource)
293         });
294         return true;
295     },
296
297     _onLog: function(message)
298     {
299         WebInspector.log(message.message);
300     },
301
302     _onReload: function(message)
303     {
304         var options = /** @type ExtensionReloadOptions */ (message.options || {});
305         NetworkAgent.setUserAgentOverride(typeof options.userAgent === "string" ? options.userAgent : "");
306         var injectedScript;
307         if (options.injectedScript) {
308             // Wrap client script into anonymous function, return another anonymous function that
309             // returns empty object for compatibility with InjectedScriptManager on the backend.
310             injectedScript = "((function(){" + options.injectedScript + "})(),function(){return {}})";
311         }
312         PageAgent.reload(!!options.ignoreCache, injectedScript);
313         return this._status.OK();
314     },
315
316     _onEvaluateOnInspectedPage: function(message, port)
317     {
318         function callback(error, resultPayload, wasThrown)
319         {
320             var result = {};
321             if (error) {
322                 result.isException = true;
323                 result.value = error.message;
324             }  else
325                 result.value = resultPayload.value;
326
327             if (wasThrown)
328                 result.isException = true;
329       
330             this._dispatchCallback(message.requestId, port, result);
331         }
332         RuntimeAgent.evaluate(message.expression, "", true, undefined, undefined, true, callback.bind(this));
333     },
334
335     _onGetConsoleMessages: function()
336     {
337         return WebInspector.console.messages.map(this._makeConsoleMessage);
338     },
339
340     _onAddConsoleMessage: function(message)
341     {
342         function convertSeverity(level)
343         {
344             switch (level) {
345                 case WebInspector.extensionAPI.console.Severity.Tip:
346                     return WebInspector.ConsoleMessage.MessageLevel.Tip;
347                 case WebInspector.extensionAPI.console.Severity.Log:
348                     return WebInspector.ConsoleMessage.MessageLevel.Log;
349                 case WebInspector.extensionAPI.console.Severity.Warning:
350                     return WebInspector.ConsoleMessage.MessageLevel.Warning;
351                 case WebInspector.extensionAPI.console.Severity.Error:
352                     return WebInspector.ConsoleMessage.MessageLevel.Error;
353                 case WebInspector.extensionAPI.console.Severity.Debug:
354                     return WebInspector.ConsoleMessage.MessageLevel.Debug;
355             }
356         }
357         var level = convertSeverity(message.severity);
358         if (!level)
359             return this._status.E_BADARG("message.severity", message.severity);
360
361         var consoleMessage = WebInspector.ConsoleMessage.create(
362             WebInspector.ConsoleMessage.MessageSource.JS,
363             level,
364             message.text,
365             WebInspector.ConsoleMessage.MessageType.Log,
366             message.url,
367             message.line);
368         WebInspector.console.addMessage(consoleMessage);
369     },
370
371     _makeConsoleMessage: function(message)
372     {
373         function convertLevel(level)
374         {
375             if (!level)
376                 return;
377             switch (level) {
378                 case WebInspector.ConsoleMessage.MessageLevel.Tip:
379                     return WebInspector.extensionAPI.console.Severity.Tip;
380                 case WebInspector.ConsoleMessage.MessageLevel.Log:
381                     return WebInspector.extensionAPI.console.Severity.Log;
382                 case WebInspector.ConsoleMessage.MessageLevel.Warning:
383                     return WebInspector.extensionAPI.console.Severity.Warning;
384                 case WebInspector.ConsoleMessage.MessageLevel.Error:
385                     return WebInspector.extensionAPI.console.Severity.Error;
386                 case WebInspector.ConsoleMessage.MessageLevel.Debug:
387                     return WebInspector.extensionAPI.console.Severity.Debug;
388                 default:
389                     return WebInspector.extensionAPI.console.Severity.Log;
390             }
391         }
392         var result = {
393             severity: convertLevel(message.level),
394             text: message.text,
395         };
396         if (message.url)
397             result.url = message.url;
398         if (message.line)
399             result.line = message.line;
400         return result;
401     },
402
403     _onGetHAR: function()
404     {
405         var requests = WebInspector.networkLog.resources;
406         var harLog = (new WebInspector.HARLog(requests)).build();
407         for (var i = 0; i < harLog.entries.length; ++i)
408             harLog.entries[i]._requestId = this._requestId(requests[i]);
409         return harLog;
410     },
411
412     _makeResource: function(resource)
413     {
414         return {
415             url: resource.url,
416             type: WebInspector.Resource.Type.toString(resource.type)
417         };
418     },
419
420     _onGetPageResources: function()
421     {
422         var resources = [];
423         function pushResourceData(resource)
424         {
425             resources.push(this._makeResource(resource));
426         }
427         WebInspector.resourceTreeModel.forAllResources(pushResourceData.bind(this));
428         return resources;
429     },
430
431     _getResourceContent: function(resource, message, port)
432     {
433         function onContentAvailable(content, encoded)
434         {
435             var response = {
436                 encoding: encoded ? "base64" : "",
437                 content: content
438             };
439             this._dispatchCallback(message.requestId, port, response);
440         }
441         resource.requestContent(onContentAvailable.bind(this));
442     },
443
444     _onGetRequestContent: function(message, port)
445     {
446         var request = this._requestById(message.id);
447         if (!request)
448             return this._status.E_NOTFOUND(message.id);
449         this._getResourceContent(request, message, port);
450     },
451
452     _onGetResourceContent: function(message, port)
453     {
454         var resource = WebInspector.resourceTreeModel.resourceForURL(message.url);
455         if (!resource)
456             return this._status.E_NOTFOUND(message.url);
457         this._getResourceContent(resource, message, port);
458     },
459
460     _onSetResourceContent: function(message, port)
461     {
462         function callbackWrapper(error)
463         {
464             var response = error ? this._status.E_FAILED(error) : this._status.OK();
465             this._dispatchCallback(message.requestId, port, response);
466         }
467         var resource = WebInspector.resourceTreeModel.resourceForURL(message.url);
468         if (!resource)
469             return this._status.E_NOTFOUND(message.url);
470         resource.setContent(message.content, message.commit, callbackWrapper.bind(this));
471     },
472
473     _requestId: function(request)
474     {
475         if (!request._extensionRequestId) {
476             request._extensionRequestId = ++this._lastRequestId;
477             this._requests[request._extensionRequestId] = request;
478         }
479         return request._extensionRequestId;
480     },
481
482     _requestById: function(id)
483     {
484         return this._requests[id];
485     },
486
487     _onAddAuditCategory: function(message)
488     {
489         var category = new WebInspector.ExtensionAuditCategory(message.id, message.displayName, message.resultCount);
490         if (WebInspector.panels.audits.getCategory(category.id))
491             return this._status.E_EXISTS(category.id);
492         this._clientObjects[message.id] = category;
493         WebInspector.panels.audits.addCategory(category);
494     },
495
496     _onAddAuditResult: function(message)
497     {
498         var auditResult = this._clientObjects[message.resultId];
499         if (!auditResult)
500             return this._status.E_NOTFOUND(message.resultId);
501         try {
502             auditResult.addResult(message.displayName, message.description, message.severity, message.details);
503         } catch (e) {
504             return e;
505         }
506         return this._status.OK();
507     },
508
509     _onStopAuditCategoryRun: function(message)
510     {
511         var auditRun = this._clientObjects[message.resultId];
512         if (!auditRun)
513             return this._status.E_NOTFOUND(message.resultId);
514         auditRun.cancel();
515     },
516
517     _dispatchCallback: function(requestId, port, result)
518     {
519         if (requestId)
520             port.postMessage({ command: "callback", requestId: requestId, result: result });
521     },
522
523     initExtensions: function()
524     {
525         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ConsoleMessageAdded,
526             WebInspector.console, WebInspector.ConsoleModel.Events.MessageAdded, this._notifyConsoleMessageAdded);
527         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.NetworkRequestFinished,
528             WebInspector.networkManager, WebInspector.NetworkManager.EventTypes.ResourceFinished, this._notifyRequestFinished);
529         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceAdded,
530             WebInspector.resourceTreeModel,
531             WebInspector.ResourceTreeModel.EventTypes.ResourceAdded,
532             this._notifyResourceAdded);
533         if (WebInspector.panels.elements) {
534             this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ElementsPanelObjectSelected,
535                 WebInspector.panels.elements.treeOutline,
536                 WebInspector.ElementsTreeOutline.Events.SelectedNodeChanged,
537                 this._notifyElementsSelectionChanged);
538         }
539         this._registerAutosubscriptionHandler(WebInspector.extensionAPI.Events.ResourceContentCommitted,
540             WebInspector.resourceTreeModel,
541             WebInspector.ResourceTreeModel.EventTypes.ResourceContentCommitted,
542             this._notifyResourceContentCommitted);
543
544         function onTimelineSubscriptionStarted()
545         {
546             WebInspector.timelineManager.addEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded,
547                 this._notifyTimelineEventRecorded, this);
548             WebInspector.timelineManager.start();
549         }
550         function onTimelineSubscriptionStopped()
551         {
552             WebInspector.timelineManager.stop();
553             WebInspector.timelineManager.removeEventListener(WebInspector.TimelineManager.EventTypes.TimelineEventRecorded,
554                 this._notifyTimelineEventRecorded, this);
555         }
556         this._registerSubscriptionHandler(WebInspector.extensionAPI.Events.TimelineEventRecorded,
557             onTimelineSubscriptionStarted.bind(this), onTimelineSubscriptionStopped.bind(this));
558
559         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.InspectedURLChanged,
560             this._inspectedURLChanged, this);
561         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.MainFrameNavigated, this._mainFrameNavigated, this);
562         InspectorExtensionRegistry.getExtensionsAsync();
563     },
564
565     _notifyConsoleMessageAdded: function(event)
566     {
567         this._postNotification(WebInspector.extensionAPI.Events.ConsoleMessageAdded, this._makeConsoleMessage(event.data));
568     },
569
570     _notifyResourceAdded: function(event)
571     {
572         var resource = event.data;
573         this._postNotification(WebInspector.extensionAPI.Events.ResourceAdded, this._makeResource(resource));
574     },
575
576     _notifyResourceContentCommitted: function(event)
577     {
578         this._postNotification(WebInspector.extensionAPI.Events.ResourceContentCommitted, this._makeResource(event.data.resource), event.data.content);
579     },
580
581     _notifyRequestFinished: function(event)
582     {
583         var request = event.data;
584         this._postNotification(WebInspector.extensionAPI.Events.NetworkRequestFinished, this._requestId(request), (new WebInspector.HAREntry(request)).build());
585     },
586
587     _notifyElementsSelectionChanged: function()
588     {
589         this._postNotification(WebInspector.extensionAPI.Events.ElementsPanelObjectSelected);
590     },
591
592     _notifyTimelineEventRecorded: function(event)
593     {
594         this._postNotification(WebInspector.extensionAPI.Events.TimelineEventRecorded, event.data);
595     },
596
597     /**
598      * @param {Array.<ExtensionDescriptor>} extensions
599      */
600     _addExtensions: function(extensions)
601     {
602         // See ExtensionAPI.js and ExtensionCommon.js for details.
603         InspectorFrontendHost.setExtensionAPI(this._buildExtensionAPIScript());
604         for (var i = 0; i < extensions.length; ++i)
605             this._addExtension(extensions[i].startPage, extensions[i].name);
606     },
607
608     _addExtension: function(startPage, name)
609     {
610         const urlOriginRegExp = new RegExp("([^:]+:\/\/[^/]*)\/"); // Can't use regexp literal here, MinJS chokes on it.
611
612         try {
613             var originMatch = urlOriginRegExp.exec(startPage);
614             if (!originMatch) {
615                 console.error("Skipping extension with invalid URL: " + startPage);
616                 return false;
617             }
618             this._registeredExtensions[originMatch[1]] = { name: name };
619             var iframe = document.createElement("iframe");
620             iframe.src = startPage;
621             iframe.style.display = "none";
622             document.body.appendChild(iframe);
623         } catch (e) {
624             console.error("Failed to initialize extension " + startPage + ":" + e);
625             return false;
626         }
627         return true;
628     },
629
630     _buildExtensionAPIScript: function()
631     {
632         var platformAPI = WebInspector.buildPlatformExtensionAPI ? WebInspector.buildPlatformExtensionAPI() : "";
633         return buildExtensionAPIInjectedScript(platformAPI);
634     },
635
636     _onWindowMessage: function(event)
637     {
638         if (event.data === "registerExtension")
639             this._registerExtension(event.origin, event.ports[0]);
640     },
641
642     _registerExtension: function(origin, port)
643     {
644         if (!this._registeredExtensions.hasOwnProperty(origin)) {
645             if (origin !== window.location.origin) // Just ignore inspector frames.
646                 console.error("Ignoring unauthorized client request from " + origin);
647             return;
648         }
649         port._extensionOrigin = origin;
650         port.addEventListener("message", this._onmessage.bind(this), false);
651         port.start();
652     },
653
654     _onmessage: function(event)
655     {
656         var message = event.data;
657         var result;
658
659         if (message.command in this._handlers)
660             result = this._handlers[message.command](message, event.target);
661         else
662             result = this._status.E_NOTSUPPORTED(message.command);
663
664         if (result && message.requestId)
665             this._dispatchCallback(message.requestId, event.target, result);
666     },
667
668     _registerHandler: function(command, callback)
669     {
670         this._handlers[command] = callback;
671     },
672
673     _registerSubscriptionHandler: function(eventTopic, onSubscribeFirst, onUnsubscribeLast)
674     {
675         this._subscriptionStartHandlers[eventTopic] =  onSubscribeFirst;
676         this._subscriptionStopHandlers[eventTopic] =  onUnsubscribeLast;
677     },
678
679     _registerAutosubscriptionHandler: function(eventTopic, eventTarget, frontendEventType, handler)
680     {
681         this._registerSubscriptionHandler(eventTopic,
682             eventTarget.addEventListener.bind(eventTarget, frontendEventType, handler, this),
683             eventTarget.removeEventListener.bind(eventTarget, frontendEventType, handler, this));
684     },
685
686     _expandResourcePath: function(extensionPath, resourcePath)
687     {
688         if (!resourcePath)
689             return;
690         return extensionPath + this._normalizePath(resourcePath);
691     },
692
693     _normalizePath: function(path)
694     {
695         var source = path.split("/");
696         var result = [];
697
698         for (var i = 0; i < source.length; ++i) {
699             if (source[i] === ".")
700                 continue;
701             // Ignore empty path components resulting from //, as well as a leading and traling slashes.
702             if (source[i] === "")
703                 continue;
704             if (source[i] === "..")
705                 result.pop();
706             else
707                 result.push(source[i]);
708         }
709         return "/" + result.join("/");
710     }
711 }
712
713 /**
714  * @constructor
715  */
716 WebInspector.ExtensionStatus = function()
717 {
718     function makeStatus(code, description)
719     {
720         var details = Array.prototype.slice.call(arguments, 2);
721         var status = { code: code, description: description, details: details };
722         if (code !== "OK") {
723             status.isError = true;
724             console.log("Extension server error: " + String.vsprintf(description, details));
725         }
726         return status;
727     }
728
729     this.OK = makeStatus.bind(null, "OK", "OK");
730     this.E_EXISTS = makeStatus.bind(null, "E_EXISTS", "Object already exists: %s");
731     this.E_BADARG = makeStatus.bind(null, "E_BADARG", "Invalid argument %s: %s");
732     this.E_BADARGTYPE = makeStatus.bind(null, "E_BADARGTYPE", "Invalid type for argument %s: got %s, expected %s");
733     this.E_NOTFOUND = makeStatus.bind(null, "E_NOTFOUND", "Object not found: %s");
734     this.E_NOTSUPPORTED = makeStatus.bind(null, "E_NOTSUPPORTED", "Object does not support requested operation: %s");
735     this.E_FAILED = makeStatus.bind(null, "E_FAILED", "Operation failed: %s");
736 }
737
738 WebInspector.addExtensions = function(extensions)
739 {
740     WebInspector.extensionServer._addExtensions(extensions);
741 }
742
743 WebInspector.extensionAPI = {};
744 defineCommonExtensionSymbols(WebInspector.extensionAPI);
745
746 WebInspector.extensionServer = new WebInspector.ExtensionServer();
747
748 window.addExtension = WebInspector.extensionServer._addExtension.bind(WebInspector.extensionServer);