<!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  &lt;burg@cs.washington.edu&gt;
</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  &lt;burg@cs.washington.edu&gt;
+
</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[&quot;method&quot;] = command.methodName;
+        if (parameters)
+            messageObject[&quot;params&quot;] = parameters;
+        messageObject[&quot;id&quot;] = this._registerPendingResponse(callback, command.methodName);
+
+        var stringifiedMessage = JSON.stringify(messageObject);
+        if (this.dumpInspectorProtocolMessages)
+            console.log(&quot;frontend: &quot; + stringifiedMessage);
+
+        InspectorFrontendHost.sendMessageToBackend(stringifiedMessage);
+    },
+
</ins><span class="cx">     _getAgent: function(domain)
</span><span class="cx">     {
</span><span class="cx">         var agentName = domain + &quot;Agent&quot;;
</span><span class="lines">@@ -73,11 +90,8 @@
</span><span class="cx">     {
</span><span class="cx">         var domainAndMethod = method.split(&quot;.&quot;);
</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]][&quot;invoke&quot;] = this._invoke.bind(this, method, signature);
-        agent[domainAndMethod[1]][&quot;promise&quot;] = this._promise.bind(this, method, signature);
-        agent[domainAndMethod[1]][&quot;supports&quot;] = 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 &lt; signature.length; ++i) {
-            if (signature[i][&quot;name&quot;] === paramName)
-                return true;
-        }
-
-        return false;
-    },
-
-    _sendMessageToBackend: function(method, signature, vararg)
-    {
-        var args = Array.prototype.slice.call(arguments, 2);
-        var callback = typeof args.lastValue === &quot;function&quot; ? args.pop() : null;
-
-        var params = {};
-        var hasParams = false;
-        for (var i = 0; i &lt; signature.length; ++i) {
-            var param = signature[i];
-            var paramName = param[&quot;name&quot;];
-            var typeName = param[&quot;type&quot;];
-            var optionalFlag = param[&quot;optional&quot;];
-
-            if (!args.length &amp;&amp; !optionalFlag) {
-                console.error(&quot;Protocol Error: Invalid number of arguments for method '&quot; + method + &quot;' call. It must have the following arguments '&quot; + JSON.stringify(signature) + &quot;'.&quot;);
-                return;
-            }
-
-            var value = args.shift();
-            if (optionalFlag &amp;&amp; typeof value === &quot;undefined&quot;) {
-                continue;
-            }
-
-            if (typeof value !== typeName) {
-                console.error(&quot;Protocol Error: Invalid type of argument '&quot; + paramName + &quot;' for method '&quot; + method + &quot;' call. It must be '&quot; + typeName + &quot;' but it is '&quot; + typeof value + &quot;'.&quot;);
-                return;
-            }
-
-            params[paramName] = value;
-            hasParams = true;
-        }
-
-        if (args.length === 1 &amp;&amp; !callback) {
-            if (typeof args[0] !== &quot;undefined&quot;) {
-                console.error(&quot;Protocol Error: Optional callback argument for method '&quot; + method + &quot;' call must be a function but its type is '&quot; + typeof args[0] + &quot;'.&quot;);
-                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(&quot;frontend: &quot; + 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[&quot;name&quot;] === parameterName
+        });
+    },
+
+    _invokeWithArguments: function()
+    {
+        var instance = this._instance;
+        var args = Array.prototype.slice.call(arguments);
+        var callback = typeof args.lastValue === &quot;function&quot; ? args.pop() : null;
+
+        var parameters = {};
+        for (var i = 0; i &lt; instance._callSignature.length; ++i) {
+            var parameter = instance._callSignature[i];
+            var parameterName = parameter[&quot;name&quot;];
+            var typeName = parameter[&quot;type&quot;];
+            var optionalFlag = parameter[&quot;optional&quot;];
+
+            if (!args.length &amp;&amp; !optionalFlag) {
+                console.error(&quot;Protocol Error: Invalid number of arguments for method '&quot; + instance.methodName + &quot;' call. It must have the following arguments '&quot; + JSON.stringify(signature) + &quot;'.&quot;);
+                return;
+            }
+
+            var value = args.shift();
+            if (optionalFlag &amp;&amp; typeof value === &quot;undefined&quot;)
+                continue;
+
+            if (typeof value !== typeName) {
+                console.error(&quot;Protocol Error: Invalid type of argument '&quot; + parameterName + &quot;' for method '&quot; + instance.methodName + &quot;' call. It must be '&quot; + typeName + &quot;' but it is '&quot; + typeof value + &quot;'.&quot;);
+                return;
+            }
+
+            parameters[parameterName] = value;
+        }
+
+        if (args.length === 1 &amp;&amp; !callback) {
+            if (typeof args[0] !== &quot;undefined&quot;) {
+                console.error(&quot;Protocol Error: Optional callback argument for method '&quot; + instance.methodName + &quot;' call must be a function but its type is '&quot; + typeof args[0] + &quot;'.&quot;);
+                return;
+            }
+        }
+
+        instance._backend._invokeMethod(instance, Object.keys(parameters).length ? parameters : null, callback);
+    },
+}
</ins></span></pre>
</div>
</div>

</body>
</html>