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