blob: b273787f3b3599571d0c0e9820311ed54936b3f6 [file] [log] [blame]
/*
* Copyright (C) 2011 Google Inc. All rights reserved.
* Copyright (C) 2013-2016 Apple Inc. All rights reserved.
* Copyright (C) 2014 University of Washington.
*
* 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.
*/
InspectorBackend.globalSequenceId = 1;
InspectorBackend.Connection = class InspectorBackendConnection
{
constructor()
{
this._pendingResponses = new Map;
this._agents = {};
this._deferredCallbacks = [];
this._target = null;
}
// Public
get target()
{
return this._target;
}
set target(target)
{
console.assert(!this._target);
this._target = target;
for (let domain in this._agents) {
let dispatcher = this._agents[domain].dispatcher;
if (dispatcher)
dispatcher.target = target;
}
}
dispatch(message)
{
let messageObject = typeof message === "string" ? JSON.parse(message) : message;
if ("id" in messageObject)
this._dispatchResponse(messageObject);
else
this._dispatchEvent(messageObject);
}
runAfterPendingDispatches(callback)
{
console.assert(typeof callback === "function");
if (!this._pendingResponses.size)
callback.call(this);
else
this._deferredCallbacks.push(callback);
}
// Protected
sendMessageToBackend(message)
{
throw new Error("Should be implemented by a InspectorBackend.Connection subclass");
}
// Private
_dispatchResponse(messageObject)
{
console.assert(this._pendingResponses.size >= 0);
if (messageObject["error"]) {
// FIXME: Eliminate Target.exists
if (messageObject["error"].code !== -32000 && messageObject["error"].message !== "'Target' domain was not found")
console.error("Request with id = " + messageObject["id"] + " failed. " + JSON.stringify(messageObject["error"]));
}
let sequenceId = messageObject["id"];
console.assert(this._pendingResponses.has(sequenceId), sequenceId, this._target ? this._target.identifier : "(unknown)", this._pendingResponses);
let responseData = this._pendingResponses.take(sequenceId) || {};
let {request, command, callback, promise} = responseData;
let processingStartTimestamp = performance.now();
for (let tracer of InspectorBackend.activeTracers)
tracer.logWillHandleResponse(this, messageObject);
InspectorBackend.currentDispatchState.request = request;
InspectorBackend.currentDispatchState.response = messageObject;
if (typeof callback === "function")
this._dispatchResponseToCallback(command, request, messageObject, callback);
else if (typeof promise === "object")
this._dispatchResponseToPromise(command, messageObject, promise);
else
console.error("Received a command response without a corresponding callback or promise.", messageObject, command);
InspectorBackend.currentDispatchState.request = null;
InspectorBackend.currentDispatchState.response = null;
let processingTime = (performance.now() - processingStartTimestamp).toFixed(3);
let roundTripTime = (processingStartTimestamp - responseData.sendRequestTimestamp).toFixed(3);
for (let tracer of InspectorBackend.activeTracers)
tracer.logDidHandleResponse(this, messageObject, {rtt: roundTripTime, dispatch: processingTime});
if (this._deferredCallbacks.length && !this._pendingResponses.size)
this._flushPendingScripts();
}
_dispatchResponseToCallback(command, requestObject, responseObject, callback)
{
let callbackArguments = [];
callbackArguments.push(responseObject["error"] ? responseObject["error"].message : null);
if (responseObject["result"]) {
for (let parameterName of command.replySignature)
callbackArguments.push(responseObject["result"][parameterName]);
}
try {
callback.apply(null, callbackArguments);
} catch (e) {
WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while dispatching response callback for command ${command.qualifiedName}.`});
}
}
_dispatchResponseToPromise(command, messageObject, promise)
{
let {resolve, reject} = promise;
if (messageObject["error"])
reject(new Error(messageObject["error"].message));
else
resolve(messageObject["result"]);
}
_dispatchEvent(messageObject)
{
let qualifiedName = messageObject["method"];
let [domainName, eventName] = qualifiedName.split(".");
if (!(domainName in this._agents)) {
console.error("Protocol Error: Attempted to dispatch method '" + eventName + "' for non-existing domain '" + domainName + "'", messageObject);
return;
}
let agent = this._agents[domainName];
if (!agent.active) {
console.error("Protocol Error: Attempted to dispatch method for domain '" + domainName + "' which exists but is not active.", messageObject);
return;
}
let event = agent.getEvent(eventName);
if (!event) {
console.error("Protocol Error: Attempted to dispatch an unspecified method '" + qualifiedName + "'", messageObject);
return;
}
let eventArguments = [];
if (messageObject["params"])
eventArguments = event.parameterNames.map((name) => messageObject["params"][name]);
let processingStartTimestamp = performance.now();
for (let tracer of InspectorBackend.activeTracers)
tracer.logWillHandleEvent(this, messageObject);
InspectorBackend.currentDispatchState.event = messageObject;
try {
agent.dispatchEvent(eventName, eventArguments);
} catch (e) {
for (let tracer of InspectorBackend.activeTracers)
tracer.logFrontendException(this, messageObject, e);
WI.reportInternalError(e, {"cause": `An uncaught exception was thrown while handling event: ${qualifiedName}`});
}
InspectorBackend.currentDispatchState.event = null;
let processingDuration = (performance.now() - processingStartTimestamp).toFixed(3);
for (let tracer of InspectorBackend.activeTracers)
tracer.logDidHandleEvent(this, messageObject, {dispatch: processingDuration});
}
_sendCommandToBackendWithCallback(command, parameters, callback)
{
let sequenceId = InspectorBackend.globalSequenceId++;
let messageObject = {
"id": sequenceId,
"method": command.qualifiedName,
};
if (!isEmptyObject(parameters))
messageObject["params"] = parameters;
let responseData = {command, request: messageObject, callback};
if (InspectorBackend.activeTracer)
responseData.sendRequestTimestamp = performance.now();
this._pendingResponses.set(sequenceId, responseData);
this._sendMessageToBackend(messageObject);
}
_sendCommandToBackendExpectingPromise(command, parameters)
{
let sequenceId = InspectorBackend.globalSequenceId++;
let messageObject = {
"id": sequenceId,
"method": command.qualifiedName,
};
if (!isEmptyObject(parameters))
messageObject["params"] = parameters;
let responseData = {command, request: messageObject};
if (InspectorBackend.activeTracer)
responseData.sendRequestTimestamp = performance.now();
let responsePromise = new Promise(function(resolve, reject) {
responseData.promise = {resolve, reject};
});
this._pendingResponses.set(sequenceId, responseData);
this._sendMessageToBackend(messageObject);
return responsePromise;
}
_sendMessageToBackend(messageObject)
{
for (let tracer of InspectorBackend.activeTracers)
tracer.logFrontendRequest(this, messageObject);
this.sendMessageToBackend(JSON.stringify(messageObject));
}
_flushPendingScripts()
{
console.assert(this._pendingResponses.size === 0);
let scriptsToRun = this._deferredCallbacks;
this._deferredCallbacks = [];
for (let script of scriptsToRun)
script.call(this);
}
};
InspectorBackend.BackendConnection = class InspectorBackendBackendConnection extends InspectorBackend.Connection
{
constructor()
{
super();
this._agents = InspectorBackend._agents;
}
sendMessageToBackend(message)
{
InspectorFrontendHost.sendMessageToBackend(message);
}
};
InspectorBackend.WorkerConnection = class InspectorBackendWorkerConnection extends InspectorBackend.Connection
{
constructor(workerId)
{
super();
this._workerId = workerId;
for (let [domain, agent] of Object.entries(InspectorBackend._agents)) {
let clone = Object.create(agent);
clone.connection = this;
if (agent.dispatcher)
clone.dispatcher = new agent.dispatcher.constructor;
this._agents[domain] = clone;
}
}
sendMessageToBackend(message)
{
// Ignore errors if a worker went away quickly.
WorkerAgent.sendMessageToWorker(this._workerId, message).catch(function(){});
}
};
InspectorBackend.TargetConnection = class InspectorBackendTargetConnection extends InspectorBackend.Connection
{
constructor(targetId)
{
super();
this._targetId = targetId;
for (let [domain, agent] of Object.entries(InspectorBackend._agents)) {
let clone = Object.create(agent);
clone.connection = this;
if (agent.dispatcher)
clone.dispatcher = new agent.dispatcher.constructor;
this._agents[domain] = clone;
}
}
sendMessageToBackend(message)
{
TargetAgent.sendMessageToTarget(this._targetId, message);
}
};
InspectorBackend.backendConnection = new InspectorBackend.BackendConnection;