diff options
Diffstat (limited to 'Source/WebInspectorUI/UserInterface/Protocol/Connection.js')
-rw-r--r-- | Source/WebInspectorUI/UserInterface/Protocol/Connection.js | 315 |
1 files changed, 315 insertions, 0 deletions
diff --git a/Source/WebInspectorUI/UserInterface/Protocol/Connection.js b/Source/WebInspectorUI/UserInterface/Protocol/Connection.js new file mode 100644 index 000000000..df059c163 --- /dev/null +++ b/Source/WebInspectorUI/UserInterface/Protocol/Connection.js @@ -0,0 +1,315 @@ +/* + * 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.Connection = class InspectorBackendConnection +{ + constructor() + { + this._lastSequenceId = 1; + this._pendingResponses = new Map; + this._agents = {}; + this._deferredScripts = []; + 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(script) + { + console.assert(typeof script === "function"); + + if (!this._pendingResponses.size) + script.call(this); + else + this._deferredScripts.push(script); + } + + // 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"]) { + if (messageObject["error"].code !== -32000) + console.error("Request with id = " + messageObject["id"] + " failed. " + JSON.stringify(messageObject["error"])); + } + + let sequenceId = messageObject["id"]; + console.assert(this._pendingResponses.has(sequenceId), sequenceId, this._pendingResponses); + + let responseData = this._pendingResponses.take(sequenceId) || {}; + let {request, command, callback, promise} = responseData; + + let processingStartTimestamp = timestamp(); + for (let tracer of InspectorBackend.activeTracers) + tracer.logWillHandleResponse(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 = (timestamp() - processingStartTimestamp).toFixed(3); + let roundTripTime = (processingStartTimestamp - responseData.sendRequestTimestamp).toFixed(3); + + for (let tracer of InspectorBackend.activeTracers) + tracer.logDidHandleResponse(messageObject, {rtt: roundTripTime, dispatch: processingTime}); + + if (this._deferredScripts.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) { + WebInspector.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 + "'"); + 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."); + return; + } + + let event = agent.getEvent(eventName); + if (!event) { + console.error("Protocol Error: Attempted to dispatch an unspecified method '" + qualifiedName + "'"); + return; + } + + let eventArguments = []; + if (messageObject["params"]) + eventArguments = event.parameterNames.map((name) => messageObject["params"][name]); + + let processingStartTimestamp = timestamp(); + for (let tracer of InspectorBackend.activeTracers) + tracer.logWillHandleEvent(messageObject); + + InspectorBackend.currentDispatchState.event = messageObject; + + try { + agent.dispatchEvent(eventName, eventArguments); + } catch (e) { + for (let tracer of InspectorBackend.activeTracers) + tracer.logFrontendException(messageObject, e); + + WebInspector.reportInternalError(e, {"cause": `An uncaught exception was thrown while handling event: ${qualifiedName}`}); + } + + InspectorBackend.currentDispatchState.event = null; + + let processingDuration = (timestamp() - processingStartTimestamp).toFixed(3); + for (let tracer of InspectorBackend.activeTracers) + tracer.logDidHandleEvent(messageObject, {dispatch: processingDuration}); + } + + _sendCommandToBackendWithCallback(command, parameters, callback) + { + let sequenceId = this._lastSequenceId++; + + let messageObject = { + "id": sequenceId, + "method": command.qualifiedName, + }; + + if (!isEmptyObject(parameters)) + messageObject["params"] = parameters; + + let responseData = {command, request: messageObject, callback}; + + if (InspectorBackend.activeTracer) + responseData.sendRequestTimestamp = timestamp(); + + this._pendingResponses.set(sequenceId, responseData); + this._sendMessageToBackend(messageObject); + } + + _sendCommandToBackendExpectingPromise(command, parameters) + { + let sequenceId = this._lastSequenceId++; + + let messageObject = { + "id": sequenceId, + "method": command.qualifiedName, + }; + + if (!isEmptyObject(parameters)) + messageObject["params"] = parameters; + + let responseData = {command, request: messageObject}; + + if (InspectorBackend.activeTracer) + responseData.sendRequestTimestamp = timestamp(); + + 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(messageObject); + + this.sendMessageToBackend(JSON.stringify(messageObject)); + } + + _flushPendingScripts() + { + console.assert(this._pendingResponses.size === 0); + + let scriptsToRun = this._deferredScripts; + this._deferredScripts = []; + for (let script of scriptsToRun) + script.call(this); + } +}; + +InspectorBackend.MainConnection = class InspectorBackendPageConnection 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; + + const workerDomains = InspectorBackend.workerSupportedDomains; + + for (let domain of workerDomains) { + let agent = InspectorBackend._agents[domain]; + let clone = Object.create(InspectorBackend._agents[domain]); + clone.connection = this; + clone.dispatcher = new agent.dispatcher.constructor; + this._agents[domain] = clone; + } + } + + sendMessageToBackend(message) + { + WorkerAgent.sendMessageToWorker(this._workerId, message); + } +}; + +InspectorBackend.mainConnection = new InspectorBackend.MainConnection; |