blob: 7d33b731c2a257f6ea9b6085b6adb89ab5aaeec4 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
WebInspector.ExtensionServer = function()
{
this._clientObjects = {};
this._handlers = {};
this._subscribers = {};
this._extraHeaders = {};
this._status = new WebInspector.ExtensionStatus();
this._registerHandler("addRequestHeaders", this._onAddRequestHeaders.bind(this));
this._registerHandler("addAuditCategory", this._onAddAuditCategory.bind(this));
this._registerHandler("addAuditResult", this._onAddAuditResult.bind(this));
this._registerHandler("createPanel", this._onCreatePanel.bind(this));
this._registerHandler("createSidebarPane", this._onCreateSidebar.bind(this));
this._registerHandler("createWatchExpressionSidebarPane", this._onCreateWatchExpressionSidebarPane.bind(this));
this._registerHandler("evaluateOnInspectedPage", this._onEvaluateOnInspectedPage.bind(this));
this._registerHandler("getHAR", this._onGetHAR.bind(this));
this._registerHandler("getResourceContent", this._onGetResourceContent.bind(this));
this._registerHandler("log", this._onLog.bind(this));
this._registerHandler("reload", this._onReload.bind(this));
this._registerHandler("setSidebarHeight", this._onSetSidebarHeight.bind(this));
this._registerHandler("setWatchSidebarContent", this._onSetWatchSidebarContent.bind(this));
this._registerHandler("stopAuditCategoryRun", this._onStopAuditCategoryRun.bind(this));
this._registerHandler("subscribe", this._onSubscribe.bind(this));
this._registerHandler("unsubscribe", this._onUnsubscribe.bind(this));
window.addEventListener("message", this._onWindowMessage.bind(this), false);
}
WebInspector.ExtensionServer.prototype = {
notifyPanelShown: function(panelName)
{
this._postNotification("panel-shown-" + panelName);
},
notifyObjectSelected: function(panelId, objectId)
{
this._postNotification("panel-objectSelected-" + panelId, objectId);
},
notifySearchAction: function(panelId, action, searchString)
{
this._postNotification("panel-search-" + panelId, action, searchString);
},
notifyPageLoaded: function(milliseconds)
{
this._postNotification("inspectedPageLoaded", milliseconds);
},
notifyPageDOMContentLoaded: function(milliseconds)
{
this._postNotification("inspectedPageDOMContentLoaded", milliseconds);
},
notifyInspectedURLChanged: function()
{
this._postNotification("inspectedURLChanged");
},
notifyInspectorReset: function()
{
this._postNotification("reset");
},
notifyExtensionWatchSidebarUpdated: function(id)
{
this._postNotification("watch-sidebar-updated-" + id);
},
startAuditRun: function(category, auditRun)
{
this._clientObjects[auditRun.id] = auditRun;
this._postNotification("audit-started-" + category.id, auditRun.id);
},
stopAuditRun: function(auditRun)
{
delete this._clientObjects[auditRun.id];
},
_notifyResourceFinished: function(event)
{
var resource = event.data;
this._postNotification("resource-finished", resource.identifier, (new WebInspector.HAREntry(resource)).build());
},
_postNotification: function(type, details)
{
var subscribers = this._subscribers[type];
if (!subscribers)
return;
var message = {
command: "notify-" + type,
arguments: Array.prototype.slice.call(arguments, 1)
};
for (var i = 0; i < subscribers.length; ++i)
subscribers[i].postMessage(message);
},
_onSubscribe: function(message, port)
{
var subscribers = this._subscribers[message.type];
if (subscribers)
subscribers.push(port);
else
this._subscribers[message.type] = [ port ];
},
_onUnsubscribe: function(message, port)
{
var subscribers = this._subscribers[message.type];
if (!subscribers)
return;
subscribers.remove(port);
if (!subscribers.length)
delete this._subscribers[message.type];
},
_onAddRequestHeaders: function(message)
{
var id = message.extensionId;
if (typeof id !== "string")
return this._status.E_BADARGTYPE("extensionId", typeof id, "string");
var extensionHeaders = this._extraHeaders[id];
if (!extensionHeaders) {
extensionHeaders = {};
this._extraHeaders[id] = extensionHeaders;
}
for (name in message.headers)
extensionHeaders[name] = message.headers[name];
var allHeaders = {};
for (extension in this._extraHeaders) {
var headers = this._extraHeaders[extension];
for (name in headers) {
if (typeof headers[name] === "string")
allHeaders[name] = headers[name];
}
}
InspectorBackend.setExtraHeaders(allHeaders);
},
_onCreatePanel: function(message, port)
{
var id = message.id;
// The ids are generated on the client API side and must be unique, so the check below
// shouldn't be hit unless someone is bypassing the API.
if (id in this._clientObjects || id in WebInspector.panels)
return this._status.E_EXISTS(id);
var panel = new WebInspector.ExtensionPanel(id, message.title, message.icon);
this._clientObjects[id] = panel;
var toolbarElement = document.getElementById("toolbar");
var lastToolbarItem = WebInspector.panelOrder[WebInspector.panelOrder.length - 1].toolbarItem;
WebInspector.addPanelToolbarIcon(toolbarElement, panel, lastToolbarItem);
WebInspector.panels[id] = panel;
var iframe = this._createClientIframe(panel.element, message.url);
iframe.style.height = "100%";
return this._status.OK();
},
_onCreateSidebar: function(message)
{
var sidebar = this._createSidebar(message, WebInspector.SidebarPane);
if (sidebar.isError)
return sidebar;
this._createClientIframe(sidebar.bodyElement, message.url);
return this._status.OK();
},
_onCreateWatchExpressionSidebarPane: function(message)
{
var sidebar = this._createSidebar(message, WebInspector.ExtensionWatchSidebarPane);
return sidebar.isError ? sidebar : this._status.OK();
},
_createSidebar: function(message, constructor)
{
var panel = WebInspector.panels[message.panel];
if (!panel)
return this._status.E_NOTFOUND(message.panel);
if (!panel.sidebarElement || !panel.sidebarPanes)
return this._status.E_NOTSUPPORTED();
var id = message.id;
var sidebar = new constructor(message.title, message.id);
this._clientObjects[id] = sidebar;
panel.sidebarPanes[id] = sidebar;
panel.sidebarElement.appendChild(sidebar.element);
return sidebar;
},
_createClientIframe: function(parent, url, requestId, port)
{
var iframe = document.createElement("iframe");
iframe.src = url;
iframe.style.width = "100%";
parent.appendChild(iframe);
return iframe;
},
_onSetSidebarHeight: function(message)
{
var sidebar = this._clientObjects[message.id];
if (!sidebar)
return this._status.E_NOTFOUND(message.id);
sidebar.bodyElement.firstChild.style.height = message.height;
},
_onSetWatchSidebarContent: function(message)
{
var sidebar = this._clientObjects[message.id];
if (!sidebar)
return this._status.E_NOTFOUND(message.id);
if (message.evaluateOnPage)
sidebar.setExpression(message.expression, message.rootTitle);
else
sidebar.setObject(message.expression, message.rootTitle);
},
_onLog: function(message)
{
WebInspector.log(message.message);
},
_onReload: function(message)
{
if (typeof message.userAgent === "string")
InspectorBackend.setUserAgentOverride(message.userAgent);
InspectorBackend.reloadPage(false);
return this._status.OK();
},
_onEvaluateOnInspectedPage: function(message, port)
{
function callback(resultPayload)
{
var resultObject = WebInspector.RemoteObject.fromPayload(resultPayload);
var result = {};
if (resultObject.isError())
result.isException = true;
result.value = resultObject.description;
this._dispatchCallback(message.requestId, port, result);
}
var evalExpression = "JSON.stringify(eval(unescape('" + escape(message.expression) + "')));";
InspectorBackend.evaluate(evalExpression, "none", true, callback.bind(this));
},
_onRevealAndSelect: function(message)
{
if (message.panelId === "resources" && type === "resource")
return this._onRevealAndSelectResource(message);
else
return this._status.E_NOTSUPPORTED(message.panelId, message.type);
},
_onRevealAndSelectResource: function(message)
{
var id = message.id;
var resource = null;
resource = WebInspector.networkResourceById(id) || WebInspector.resourceForURL(id);
if (!resource)
return this._status.E_NOTFOUND(typeof id + ": " + id);
WebInspector.panels.resources.showResource(resource, message.line);
WebInspector.showPanel("resources");
},
_dispatchCallback: function(requestId, port, result)
{
port.postMessage({ command: "callback", requestId: requestId, result: result });
},
_onGetHAR: function(request)
{
var harLog = new WebInspector.HARLog();
harLog.includeResourceIds = true;
return harLog.build();
},
_onGetResourceContent: function(message, port)
{
function onContentAvailable(content, encoded)
{
var response = {
encoding: encoded ? "base64" : "",
content: content
};
this._dispatchCallback(message.requestId, port, response);
}
var resource = WebInspector.networkResourceById(message.id);
if (!resource)
return this._status.E_NOTFOUND(message.id);
resource.requestContent(onContentAvailable.bind(this));
},
_onAddAuditCategory: function(request)
{
var category = new WebInspector.ExtensionAuditCategory(request.id, request.displayName, request.resultCount);
if (WebInspector.panels.audits.getCategory(category.id))
return this._status.E_EXISTS(category.id);
this._clientObjects[request.id] = category;
WebInspector.panels.audits.addCategory(category);
},
_onAddAuditResult: function(request)
{
var auditResult = this._clientObjects[request.resultId];
if (!auditResult)
return this._status.E_NOTFOUND(request.resultId);
try {
auditResult.addResult(request.displayName, request.description, request.severity, request.details);
} catch (e) {
return e;
}
return this._status.OK();
},
_onStopAuditCategoryRun: function(request)
{
var auditRun = this._clientObjects[request.resultId];
if (!auditRun)
return this._status.E_NOTFOUND(request.resultId);
auditRun.cancel();
},
initExtensions: function()
{
// The networkManager is normally created after the ExtensionServer is constructed, but before initExtensions() is called.
WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceFinished, this._notifyResourceFinished, this);
InspectorExtensionRegistry.getExtensionsAsync();
},
_addExtensions: function(extensions)
{
// See ExtensionAPI.js and ExtensionCommon.js for details.
InspectorFrontendHost.setExtensionAPI(this._buildExtensionAPIInjectedScript());
for (var i = 0; i < extensions.length; ++i) {
var extension = extensions[i];
try {
if (!extension.startPage)
return;
var iframe = document.createElement("iframe");
iframe.src = extension.startPage;
iframe.style.display = "none";
document.body.appendChild(iframe);
} catch (e) {
console.error("Failed to initialize extension " + extension.startPage + ":" + e);
}
}
},
_buildExtensionAPIInjectedScript: function()
{
var resourceTypes = {};
var resourceTypeProperties = Object.getOwnPropertyNames(WebInspector.Resource.Type);
for (var i = 0; i < resourceTypeProperties.length; ++i) {
var propName = resourceTypeProperties[i];
var propValue = WebInspector.Resource.Type[propName];
if (typeof propValue === "number")
resourceTypes[propName] = WebInspector.Resource.Type.toString(propValue);
}
var platformAPI = WebInspector.buildPlatformExtensionAPI ? WebInspector.buildPlatformExtensionAPI() : "";
return "(function(){ " +
"var apiPrivate = {};" +
"(" + WebInspector.commonExtensionSymbols.toString() + ")(apiPrivate);" +
"(" + WebInspector.injectedExtensionAPI.toString() + ").apply(this, arguments);" +
platformAPI +
"})";
},
_onWindowMessage: function(event)
{
if (event.data !== "registerExtension")
return;
var port = event.ports[0];
port.addEventListener("message", this._onmessage.bind(this), false);
port.start();
},
_onmessage: function(event)
{
var request = event.data;
var result;
if (request.command in this._handlers)
result = this._handlers[request.command](request, event.target);
else
result = this._status.E_NOTSUPPORTED(request.command);
if (result && request.requestId)
this._dispatchCallback(request.requestId, event.target, result);
},
_registerHandler: function(command, callback)
{
this._handlers[command] = callback;
}
}
WebInspector.ExtensionServer._statuses =
{
OK: "",
E_EXISTS: "Object already exists: %s",
E_BADARG: "Invalid argument %s: %s",
E_BADARGTYPE: "Invalid type for argument %s: got %s, expected %s",
E_NOTFOUND: "Object not found: %s",
E_NOTSUPPORTED: "Object does not support requested operation: %s",
}
WebInspector.ExtensionStatus = function()
{
function makeStatus(code)
{
var description = WebInspector.ExtensionServer._statuses[code] || code;
var details = Array.prototype.slice.call(arguments, 1);
var status = { code: code, description: description, details: details };
if (code !== "OK") {
status.isError = true;
console.log("Extension server error: " + String.vsprintf(description, details));
}
return status;
}
for (status in WebInspector.ExtensionServer._statuses)
this[status] = makeStatus.bind(null, status);
}
WebInspector.addExtensions = function(extensions)
{
WebInspector.extensionServer._addExtensions(extensions);
}
WebInspector.extensionServer = new WebInspector.ExtensionServer();