2 * Copyright (C) 2010 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
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
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.
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.
31 WebInspector.injectedExtensionAPI = function(InjectedScriptHost, inspectedWindow, injectedScriptId)
34 // Here and below, all constructors are private to API implementation.
35 // For a public type Foo, if internal fields are present, these are on
36 // a private FooImpl type, an instance of FooImpl is used in a closure
37 // by Foo consutrctor to re-bind publicly exported members to an instance
40 function EventSinkImpl(type, customDispatch)
44 this._customDispatch = customDispatch;
47 EventSinkImpl.prototype = {
48 addListener: function(callback)
50 if (typeof callback != "function")
51 throw new "addListener: callback is not a function";
52 if (this._listeners.length === 0)
53 extensionServer.sendRequest({ command: "subscribe", type: this._type });
54 this._listeners.push(callback);
55 extensionServer.registerHandler("notify-" + this._type, bind(this._dispatch, this));
58 removeListener: function(callback)
60 var listeners = this._listeners;
62 for (var i = 0; i < listeners.length; ++i) {
63 if (listeners[i] === callback) {
64 listeners.splice(i, 1);
68 if (this._listeners.length === 0)
69 extensionServer.sendRequest({ command: "unsubscribe", type: this._type });
74 var listeners = this._listeners.slice();
75 for (var i = 0; i < listeners.length; ++i)
76 listeners[i].apply(null, arguments);
79 _dispatch: function(request)
81 if (this._customDispatch)
82 this._customDispatch.call(this, request);
84 this._fire.apply(this, request.arguments);
88 function InspectorExtensionAPI()
90 this.audits = new Audits();
91 this.inspectedWindow = new InspectedWindow();
92 this.panels = new Panels();
93 this.resources = new Resources();
95 this.onReset = new EventSink("reset");
98 InspectorExtensionAPI.prototype = {
99 log: function(message)
101 extensionServer.sendRequest({ command: "log", message: message });
107 function resourceDispatch(request)
109 var resource = request.arguments[1];
110 resource.__proto__ = new Resource(request.arguments[0]);
111 this._fire(resource);
113 this.onFinished = new EventSink("resource-finished", resourceDispatch);
116 Resources.prototype = {
117 getHAR: function(callback)
119 function callbackWrapper(result)
121 var entries = (result && result.entries) || [];
122 for (var i = 0; i < entries.length; ++i) {
123 entries[i].__proto__ = new Resource(entries[i]._resourceId);
124 delete entries[i]._resourceId;
128 return extensionServer.sendRequest({ command: "getHAR" }, callback && callbackWrapper);
131 addRequestHeaders: function(headers)
133 return extensionServer.sendRequest({ command: "addRequestHeaders", headers: headers, extensionId: location.hostname });
137 function ResourceImpl(id)
142 ResourceImpl.prototype = {
143 getContent: function(callback)
145 function callbackWrapper(response)
147 callback(response.content, response.encoding);
149 extensionServer.sendRequest({ command: "getResourceContent", id: this._id }, callback && callbackWrapper);
156 elements: new ElementsPanel()
159 function panelGetter(name)
163 for (var panel in panels)
164 this.__defineGetter__(panel, bind(panelGetter, null, panel));
168 create: function(title, iconURL, pageURL, callback)
170 var id = "extension-panel-" + extensionServer.nextObjectId();
172 command: "createPanel",
175 icon: expandURL(iconURL),
176 url: expandURL(pageURL)
178 extensionServer.sendRequest(request, callback && bind(callback, this, new ExtensionPanel(id)));
182 function PanelImpl(id)
187 function PanelWithSidebarImpl(id)
189 PanelImpl.call(this, id);
192 PanelWithSidebarImpl.prototype = {
193 createSidebarPane: function(title, url, callback)
195 var id = "extension-sidebar-" + extensionServer.nextObjectId();
197 command: "createSidebarPane",
203 function callbackWrapper()
205 callback(new ExtensionSidebarPane(id));
207 extensionServer.sendRequest(request, callback && callbackWrapper);
210 createWatchExpressionSidebarPane: function(title, callback)
212 var id = "watch-sidebar-" + extensionServer.nextObjectId();
214 command: "createWatchExpressionSidebarPane",
219 function callbackWrapper()
221 callback(new WatchExpressionSidebarPane(id));
223 extensionServer.sendRequest(request, callback && callbackWrapper);
227 PanelWithSidebarImpl.prototype.__proto__ = PanelImpl.prototype;
229 function ElementsPanel()
232 PanelWithSidebar.call(this, id);
233 this.onSelectionChanged = new EventSink("panel-objectSelected-" + id);
236 function ExtensionPanel(id)
238 Panel.call(this, id);
239 this.onSearch = new EventSink("panel-search-" + id);
242 function ExtensionSidebarPaneImpl(id)
247 ExtensionSidebarPaneImpl.prototype = {
248 setHeight: function(height)
250 extensionServer.sendRequest({ command: "setSidebarHeight", id: this._id, height: height });
254 function WatchExpressionSidebarPaneImpl(id)
256 ExtensionSidebarPaneImpl.call(this, id);
257 this.onUpdated = new EventSink("watch-sidebar-updated-" + id);
260 WatchExpressionSidebarPaneImpl.prototype = {
261 setExpression: function(expression, rootTitle)
263 extensionServer.sendRequest({ command: "setWatchSidebarContent", id: this._id, expression: expression, rootTitle: rootTitle, evaluateOnPage: true });
266 setObject: function(jsonObject, rootTitle)
268 extensionServer.sendRequest({ command: "setWatchSidebarContent", id: this._id, expression: jsonObject, rootTitle: rootTitle });
272 WatchExpressionSidebarPaneImpl.prototype.__proto__ = ExtensionSidebarPaneImpl.prototype;
274 function WatchExpressionSidebarPane(id)
276 var impl = new WatchExpressionSidebarPaneImpl(id);
277 ExtensionSidebarPane.call(this, id, impl);
285 addCategory: function(displayName, resultCount)
287 var id = "extension-audit-category-" + extensionServer.nextObjectId();
288 extensionServer.sendRequest({ command: "addAuditCategory", id: id, displayName: displayName, resultCount: resultCount });
289 return new AuditCategory(id);
293 function AuditCategoryImpl(id)
295 function auditResultDispatch(request)
297 var auditResult = new AuditResult(request.arguments[0]);
299 this._fire(auditResult);
301 console.error("Uncaught exception in extension audit event handler: " + e);
306 this.onAuditStarted = new EventSink("audit-started-" + id, auditResultDispatch);
309 function AuditResultImpl(id)
313 var formatterTypes = [
318 for (var i = 0; i < formatterTypes.length; ++i)
319 this[formatterTypes[i]] = bind(this._nodeFactory, null, formatterTypes[i]);
322 AuditResultImpl.prototype = {
323 addResult: function(displayName, description, severity, details)
325 // shorthand for specifying details directly in addResult().
326 if (details && !(details instanceof AuditResultNode))
327 details = details instanceof Array ? this.createNode.apply(this, details) : this.createNode(details);
330 command: "addAuditResult",
332 displayName: displayName,
333 description: description,
337 extensionServer.sendRequest(request);
340 createResult: function()
342 var node = new AuditResultNode();
343 node.contents = Array.prototype.slice.call(arguments);
349 extensionServer.sendRequest({ command: "stopAuditCategoryRun", resultId: this._id });
354 return apiPrivate.audits.Severity;
357 _nodeFactory: function(type)
361 arguments: Array.prototype.slice.call(arguments, 1)
366 function AuditResultNode(contents)
368 this.contents = contents;
370 this.expanded = false;
373 AuditResultNode.prototype = {
376 var node = AuditResultImpl.prototype.createResult.apply(null, arguments);
377 this.children.push(node);
382 function InspectedWindow()
384 this.onDOMContentLoaded = new EventSink("inspectedPageDOMContentLoaded");
385 this.onLoaded = new EventSink("inspectedPageLoaded");
386 this.onNavigated = new EventSink("inspectedURLChanged");
389 InspectedWindow.prototype = {
390 reload: function(userAgent)
392 return extensionServer.sendRequest({ command: "reload", userAgent: userAgent });
395 eval: function(expression, callback)
397 function callbackWrapper(result)
399 var value = result.value;
400 if (!result.isException)
401 value = value === "undefined" ? undefined : JSON.parse(value);
402 callback(value, result.isException);
404 return extensionServer.sendRequest({ command: "evaluateOnInspectedPage", expression: expression }, callback && callbackWrapper);
408 function ExtensionServerClient()
410 this._callbacks = {};
412 this._lastRequestId = 0;
413 this._lastObjectId = 0;
415 this.registerHandler("callback", bind(this._onCallback, this));
417 var channel = new MessageChannel();
418 this._port = channel.port1;
419 this._port.addEventListener("message", bind(this._onMessage, this), false);
422 top.postMessage("registerExtension", [ channel.port2 ], "*");
425 ExtensionServerClient.prototype = {
426 sendRequest: function(message, callback)
428 if (typeof callback === "function")
429 message.requestId = this._registerCallback(callback);
430 return this._port.postMessage(message);
433 registerHandler: function(command, handler)
435 this._handlers[command] = handler;
438 nextObjectId: function()
440 return injectedScriptId + "_" + ++this._lastObjectId;
443 _registerCallback: function(callback)
445 var id = ++this._lastRequestId;
446 this._callbacks[id] = callback;
450 _onCallback: function(request)
452 if (request.requestId in this._callbacks) {
453 var callback = this._callbacks[request.requestId];
454 delete this._callbacks[request.requestId];
455 callback(request.result);
459 _onMessage: function(event)
461 var request = event.data;
462 var handler = this._handlers[request.command];
464 handler.call(this, request);
468 function expandURL(url)
472 if (/^[^/]+:/.exec(url)) // See if url has schema.
474 var baseURL = location.protocol + "//" + location.hostname + location.port;
476 return baseURL + url;
477 return baseURL + location.pathname.replace(/\/[^/]*$/,"/") + url;
480 function bind(func, thisObject)
482 var args = Array.prototype.slice.call(arguments, 2);
483 return function() { return func.apply(thisObject, args.concat(Array.prototype.slice.call(arguments, 0))); };
486 function populateInterfaceClass(interface, implementation)
488 for (var member in implementation) {
489 if (member.charAt(0) === "_")
491 var value = implementation[member];
492 interface[member] = typeof value === "function" ? bind(value, implementation)
493 : interface[member] = implementation[member];
497 function declareInterfaceClass(implConstructor)
501 var impl = { __proto__: implConstructor.prototype };
502 implConstructor.apply(impl, arguments);
503 populateInterfaceClass(this, impl);
507 var AuditCategory = declareInterfaceClass(AuditCategoryImpl);
508 var AuditResult = declareInterfaceClass(AuditResultImpl);
509 var EventSink = declareInterfaceClass(EventSinkImpl);
510 var ExtensionSidebarPane = declareInterfaceClass(ExtensionSidebarPaneImpl);
511 var Panel = declareInterfaceClass(PanelImpl);
512 var PanelWithSidebar = declareInterfaceClass(PanelWithSidebarImpl);
513 var Resource = declareInterfaceClass(ResourceImpl);
514 var WatchExpressionSidebarPane = declareInterfaceClass(WatchExpressionSidebarPaneImpl);
516 var extensionServer = new ExtensionServerClient();
518 webInspector = new InspectorExtensionAPI();