<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[167984] trunk/Source/WebInspectorUI</title>
</head>
<body>
<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; }
#msg dl a { font-weight: bold}
#msg dl a:link { color:#fc3; }
#msg dl a:active { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/167984">167984</a></dd>
<dt>Author</dt> <dd>burg@cs.washington.edu</dd>
<dt>Date</dt> <dd>2014-04-29 22:52:04 -0700 (Tue, 29 Apr 2014)</dd>
</dl>
<h3>Log Message</h3>
<pre>Web Inspector: reduce per-protocol method function creation in InspectorBackend
https://bugs.webkit.org/show_bug.cgi?id=130701
Reviewed by Timothy Hatcher.
Instead of creating 4 bound functions for every backend method on startup, we
can share common functionality on the InspectorBackendCommand prototype. This
also splits the various client-facing protocol introspection mechanisms from
the message encoding/decoding code.
We use a workaround to make the command instances themselves callable as well
as having .invoke, .promise, and .supports. InspectorAgent.methodName returns a
trampoline function that performs the default method invocation. The trampoline's
__proto__ is patched to point to InspectorBackendClass.prototype, and the command
instance is saved in the closure and on the trampoline function itself.
* UserInterface/Protocol/InspectorBackend.js:
(InspectorBackendClass.prototype._registerPendingResponse):
(InspectorBackendClass.prototype._invokeMethod):
(InspectorBackendClass.prototype.registerCommand):
(InspectorBackendClass.prototype.registerEvent):
(InspectorBackendCommand):
(.callable):
(InspectorBackendCommand.create):
(InspectorBackendCommand.prototype.invoke):
(InspectorBackendCommand.prototype.promise):
(InspectorBackendCommand.prototype.supports):
(InspectorBackendCommand.prototype._invokeWithArguments):
(InspectorBackendClass.prototype._wrap): Deleted.
(InspectorBackendClass.prototype._invoke): Deleted.
(InspectorBackendClass.prototype._promise): Deleted.
(InspectorBackendClass.prototype._supports): Deleted.
(InspectorBackendClass.prototype._sendMessageToBackend): Deleted.
(InspectorBackendClass.prototype._wrapCallbackAndSendMessageObject): Deleted.
(InspectorBackendClass.prototype.sendMessageObjectToBackend): Deleted.</pre>
<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceProtocolInspectorBackendjs">trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorBackend.js</a></li>
</ul>
</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (167983 => 167984)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/ChangeLog        2014-04-30 05:42:57 UTC (rev 167983)
+++ trunk/Source/WebInspectorUI/ChangeLog        2014-04-30 05:52:04 UTC (rev 167984)
</span><span class="lines">@@ -1,5 +1,43 @@
</span><span class="cx"> 2014-04-29 Brian J. Burg <burg@cs.washington.edu>
</span><span class="cx">
</span><ins>+ Web Inspector: reduce per-protocol method function creation in InspectorBackend
+ https://bugs.webkit.org/show_bug.cgi?id=130701
+
+ Reviewed by Timothy Hatcher.
+
+ Instead of creating 4 bound functions for every backend method on startup, we
+ can share common functionality on the InspectorBackendCommand prototype. This
+ also splits the various client-facing protocol introspection mechanisms from
+ the message encoding/decoding code.
+
+ We use a workaround to make the command instances themselves callable as well
+ as having .invoke, .promise, and .supports. InspectorAgent.methodName returns a
+ trampoline function that performs the default method invocation. The trampoline's
+ __proto__ is patched to point to InspectorBackendClass.prototype, and the command
+ instance is saved in the closure and on the trampoline function itself.
+
+ * UserInterface/Protocol/InspectorBackend.js:
+ (InspectorBackendClass.prototype._registerPendingResponse):
+ (InspectorBackendClass.prototype._invokeMethod):
+ (InspectorBackendClass.prototype.registerCommand):
+ (InspectorBackendClass.prototype.registerEvent):
+ (InspectorBackendCommand):
+ (.callable):
+ (InspectorBackendCommand.create):
+ (InspectorBackendCommand.prototype.invoke):
+ (InspectorBackendCommand.prototype.promise):
+ (InspectorBackendCommand.prototype.supports):
+ (InspectorBackendCommand.prototype._invokeWithArguments):
+ (InspectorBackendClass.prototype._wrap): Deleted.
+ (InspectorBackendClass.prototype._invoke): Deleted.
+ (InspectorBackendClass.prototype._promise): Deleted.
+ (InspectorBackendClass.prototype._supports): Deleted.
+ (InspectorBackendClass.prototype._sendMessageToBackend): Deleted.
+ (InspectorBackendClass.prototype._wrapCallbackAndSendMessageObject): Deleted.
+ (InspectorBackendClass.prototype.sendMessageObjectToBackend): Deleted.
+
+2014-04-29 Brian J. Burg <burg@cs.washington.edu>
+
</ins><span class="cx"> Web Inspector: DataGrid columns should be objects not Maps
</span><span class="cx"> https://bugs.webkit.org/show_bug.cgi?id=129383
</span><span class="cx">
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceProtocolInspectorBackendjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorBackend.js (167983 => 167984)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorBackend.js        2014-04-30 05:42:57 UTC (rev 167983)
+++ trunk/Source/WebInspectorUI/UserInterface/Protocol/InspectorBackend.js        2014-04-30 05:52:04 UTC (rev 167984)
</span><span class="lines">@@ -47,20 +47,37 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> InspectorBackendClass.prototype = {
</span><del>- _wrap: function(callback, method)
</del><ins>+ _registerPendingResponse: function(callback, methodName)
</ins><span class="cx"> {
</span><span class="cx"> var callbackId = this._lastCallbackId++;
</span><span class="cx"> if (!callback)
</span><span class="cx"> callback = function() {};
</span><span class="cx">
</span><span class="cx"> this._callbacks[callbackId] = callback;
</span><del>- callback.methodName = method;
</del><ins>+ callback.methodName = methodName;
</ins><span class="cx"> if (this.dumpInspectorTimeStats)
</span><span class="cx"> callback.sendRequestTime = Date.now();
</span><span class="cx">
</span><ins>+ ++this._pendingResponsesCount;
+
</ins><span class="cx"> return callbackId;
</span><span class="cx"> },
</span><span class="cx">
</span><ins>+ _invokeMethod: function(command, parameters, callback)
+ {
+ var messageObject = {};
+ messageObject["method"] = command.methodName;
+ if (parameters)
+ messageObject["params"] = parameters;
+ messageObject["id"] = this._registerPendingResponse(callback, command.methodName);
+
+ var stringifiedMessage = JSON.stringify(messageObject);
+ if (this.dumpInspectorProtocolMessages)
+ console.log("frontend: " + stringifiedMessage);
+
+ InspectorFrontendHost.sendMessageToBackend(stringifiedMessage);
+ },
+
</ins><span class="cx"> _getAgent: function(domain)
</span><span class="cx"> {
</span><span class="cx"> var agentName = domain + "Agent";
</span><span class="lines">@@ -73,11 +90,8 @@
</span><span class="cx"> {
</span><span class="cx"> var domainAndMethod = method.split(".");
</span><span class="cx"> var agent = this._getAgent(domainAndMethod[0]);
</span><ins>+ agent[domainAndMethod[1]] = InspectorBackendCommand.create(this, method, signature);
</ins><span class="cx">
</span><del>- agent[domainAndMethod[1]] = this._sendMessageToBackend.bind(this, method, signature);
- agent[domainAndMethod[1]]["invoke"] = this._invoke.bind(this, method, signature);
- agent[domainAndMethod[1]]["promise"] = this._promise.bind(this, method, signature);
- agent[domainAndMethod[1]]["supports"] = this._supports.bind(this, method, signature);
</del><span class="cx"> this._replyArgs[method] = replyArgs;
</span><span class="cx"> },
</span><span class="cx">
</span><span class="lines">@@ -89,105 +103,11 @@
</span><span class="cx"> agent[domainAndMethod[1]] = values;
</span><span class="cx"> },
</span><span class="cx">
</span><del>- registerEvent: function(eventName, params)
</del><ins>+ registerEvent: function(eventName, parameters)
</ins><span class="cx"> {
</span><del>- this._eventArgs[eventName] = params;
</del><ins>+ this._eventArgs[eventName] = parameters;
</ins><span class="cx"> },
</span><span class="cx">
</span><del>- _invoke: function(method, signature, args, callback)
- {
- this._wrapCallbackAndSendMessageObject(method, args, callback);
- },
-
- _promise: function(method, signature)
- {
- var backend = this;
- var promiseArguments = Array.prototype.slice.call(arguments);
- return new Promise(function(resolve, reject) {
- function convertToPromiseCallback(error, payload) {
- if (error)
- reject(error);
- else
- resolve(payload);
- }
- promiseArguments.push(convertToPromiseCallback);
- backend._sendMessageToBackend.apply(backend, promiseArguments);
- });
- },
-
- _supports: function(method, signature, paramName)
- {
- for (var i = 0; i < signature.length; ++i) {
- if (signature[i]["name"] === paramName)
- return true;
- }
-
- return false;
- },
-
- _sendMessageToBackend: function(method, signature, vararg)
- {
- var args = Array.prototype.slice.call(arguments, 2);
- var callback = typeof args.lastValue === "function" ? args.pop() : null;
-
- var params = {};
- var hasParams = false;
- for (var i = 0; i < signature.length; ++i) {
- var param = signature[i];
- var paramName = param["name"];
- var typeName = param["type"];
- var optionalFlag = param["optional"];
-
- if (!args.length && !optionalFlag) {
- console.error("Protocol Error: Invalid number of arguments for method '" + method + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
- return;
- }
-
- var value = args.shift();
- if (optionalFlag && typeof value === "undefined") {
- continue;
- }
-
- if (typeof value !== typeName) {
- console.error("Protocol Error: Invalid type of argument '" + paramName + "' for method '" + method + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
- return;
- }
-
- params[paramName] = value;
- hasParams = true;
- }
-
- if (args.length === 1 && !callback) {
- if (typeof args[0] !== "undefined") {
- console.error("Protocol Error: Optional callback argument for method '" + method + "' call must be a function but its type is '" + typeof args[0] + "'.");
- return;
- }
- }
-
- this._wrapCallbackAndSendMessageObject(method, hasParams ? params : null, callback);
- },
-
- _wrapCallbackAndSendMessageObject: function(method, params, callback)
- {
- var messageObject = {};
- messageObject.method = method;
- if (params)
- messageObject.params = params;
- messageObject.id = this._wrap(callback, method);
-
- if (this.dumpInspectorProtocolMessages)
- console.log("frontend: " + JSON.stringify(messageObject));
-
- ++this._pendingResponsesCount;
- this.sendMessageObjectToBackend(messageObject);
- },
-
- sendMessageObjectToBackend: function(messageObject)
- {
- var message = JSON.stringify(messageObject);
- InspectorFrontendHost.sendMessageToBackend(message);
- },
-
</del><span class="cx"> registerDomainDispatcher: function(domain, dispatcher)
</span><span class="cx"> {
</span><span class="cx"> this._domainDispatchers[domain] = dispatcher;
</span><span class="lines">@@ -311,3 +231,96 @@
</span><span class="cx"> }
</span><span class="cx">
</span><span class="cx"> InspectorBackend = new InspectorBackendClass();
</span><ins>+
+InspectorBackendCommand = function(backend, methodName, callSignature)
+{
+ this._backend = backend;
+ this.methodName = methodName;
+ this._callSignature = callSignature;
+ this._instance = this;
+}
+
+InspectorBackendCommand.create = function(backend, methodName, callSignature)
+{
+ var instance = new InspectorBackendCommand(backend, methodName, callSignature);
+
+ function callable() {
+ instance._invokeWithArguments.apply(instance, arguments);
+ }
+ callable._instance = instance;
+ callable.__proto__ = InspectorBackendCommand.prototype;
+ return callable;
+}
+
+// As part of the workaround to make commands callable, these functions use |this._instance|.
+// |this| could refer to the callable trampoline, or the InspectorBackendCommand instance.
+InspectorBackendCommand.prototype = {
+ __proto__: Function.prototype,
+
+ invoke: function(args, callback)
+ {
+ var instance = this._instance;
+ instance._backend._invokeMethod(instance, args, callback);
+ },
+
+ promise: function()
+ {
+ var instance = this._instance;
+ var promiseArguments = Array.prototype.slice.call(arguments);
+ return new Promise(function(resolve, reject) {
+ function convertToPromiseCallback(error, payload) {
+ return error ? reject(error) : resolve(payload);
+ }
+ promiseArguments.push(convertToPromiseCallback);
+ instance._invokeWithArguments.apply(instance, promiseArguments);
+ });
+ },
+
+ supports: function(parameterName)
+ {
+ var instance = this._instance;
+ return instance._callSignature.any(function(parameter) {
+ return parameter["name"] === parameterName
+ });
+ },
+
+ _invokeWithArguments: function()
+ {
+ var instance = this._instance;
+ var args = Array.prototype.slice.call(arguments);
+ var callback = typeof args.lastValue === "function" ? args.pop() : null;
+
+ var parameters = {};
+ for (var i = 0; i < instance._callSignature.length; ++i) {
+ var parameter = instance._callSignature[i];
+ var parameterName = parameter["name"];
+ var typeName = parameter["type"];
+ var optionalFlag = parameter["optional"];
+
+ if (!args.length && !optionalFlag) {
+ console.error("Protocol Error: Invalid number of arguments for method '" + instance.methodName + "' call. It must have the following arguments '" + JSON.stringify(signature) + "'.");
+ return;
+ }
+
+ var value = args.shift();
+ if (optionalFlag && typeof value === "undefined")
+ continue;
+
+ if (typeof value !== typeName) {
+ console.error("Protocol Error: Invalid type of argument '" + parameterName + "' for method '" + instance.methodName + "' call. It must be '" + typeName + "' but it is '" + typeof value + "'.");
+ return;
+ }
+
+ parameters[parameterName] = value;
+ }
+
+ if (args.length === 1 && !callback) {
+ if (typeof args[0] !== "undefined") {
+ console.error("Protocol Error: Optional callback argument for method '" + instance.methodName + "' call must be a function but its type is '" + typeof args[0] + "'.");
+ return;
+ }
+ }
+
+ instance._backend._invokeMethod(instance, Object.keys(parameters).length ? parameters : null, callback);
+ },
+}
</ins></span></pre>
</div>
</div>
</body>
</html>