[DevTools] turn Common.Renderer into UI.Renderer

Clients will need to render a generic object/DOMNode, and
be able to call `select()` to select the first child in the
resulting TreeOutline. This CL:
- Turns Common.Renderer > UI.Renderer
- Returns a tree, so interested clients can use it

Bug: 865674
Change-Id: Id877537a3c8713b99d34e6a4558037e0c8b3a111
Reviewed-on: https://chromium-review.googlesource.com/c/1298023
Reviewed-by: Dmitry Gozman <[email protected]>
Reviewed-by: Joel Einbinder <[email protected]>
Commit-Queue: Erik Luo <[email protected]>
Cr-Original-Commit-Position: refs/heads/master@{#604637}
Cr-Mirrored-From: https://chromium.googlesource.com/chromium/src
Cr-Mirrored-Commit: 3ea41c834168b61e1e2c1b32d23d1775f9ddbe71
diff --git a/front_end/browser_console/BrowserConsole.js b/front_end/browser_console/BrowserConsole.js
index a98df12..e8b8f6f 100644
--- a/front_end/browser_console/BrowserConsole.js
+++ b/front_end/browser_console/BrowserConsole.js
@@ -3,7 +3,7 @@
 // found in the LICENSE file.
 
 /**
- * @implements {Common.Renderer}
+ * @implements {UI.Renderer}
  * @implements {UI.ContextMenu.Provider}
  */
 BrowserConsole.BrowserConsole = class {
@@ -25,10 +25,9 @@
   /**
    * @override
    * @param {!Object} object
-   * @param {!Common.Renderer.Options} options
-   * @return {!Promise.<?Node>}
+   * @return {!Promise<?{node: !Node, tree: ?UI.TreeOutline}>}
    */
-  render(object, options) {
+  render(object) {
     const consoleMessage = /** @type {!SDK.ConsoleMessage} */ (object);
     const request = SDK.NetworkLog.requestForConsoleMessage(consoleMessage);
     let messageElement = null;
@@ -51,6 +50,7 @@
         messageElement.appendChild(fragment);
       }
     }
-    return Promise.resolve(/** @type {?Node} */ (messageElement));
+    const result = messageElement ? {node: messageElement, tree: null} : null;
+    return Promise.resolve(/** @type {?{node: !Node, tree: ?UI.TreeOutline}} */ (result));
   }
 };
diff --git a/front_end/browser_console/module.json b/front_end/browser_console/module.json
index 7dbf71b..d259b77 100644
--- a/front_end/browser_console/module.json
+++ b/front_end/browser_console/module.json
@@ -8,7 +8,7 @@
             "className": "BrowserConsole.BrowserConsole"
         },
         {
-            "type": "@Common.Renderer",
+            "type": "@UI.Renderer",
             "contextTypes": [
                 "SDK.ConsoleMessage"
             ],
diff --git a/front_end/common/ModuleExtensionInterfaces.js b/front_end/common/ModuleExtensionInterfaces.js
index cdc0dd3..6d88d49 100644
--- a/front_end/common/ModuleExtensionInterfaces.js
+++ b/front_end/common/ModuleExtensionInterfaces.js
@@ -5,37 +5,6 @@
 /**
  * @interface
  */
-Common.Renderer = function() {};
-
-Common.Renderer.prototype = {
-  /**
-   * @param {!Object} object
-   * @param {!Common.Renderer.Options} options
-   * @return {!Promise.<?Node>}
-   */
-  render(object, options) {}
-};
-
-/**
- * @param {?Object} object
- * @param {!Common.Renderer.Options=} options
- * @return {!Promise.<?Node>}
- */
-Common.Renderer.render = function(object, options) {
-  if (!object)
-    return Promise.reject(new Error('Can\'t render ' + object));
-  return self.runtime.extension(Common.Renderer, object)
-      .instance()
-      .then(renderer => renderer.render(object, options || {}));
-};
-
-/** @typedef {!{title: (string|!Element|undefined), expanded: (boolean|undefined),
- *    editable: (boolean|undefined) }} */
-Common.Renderer.Options;
-
-/**
- * @interface
- */
 Common.Revealer = function() {};
 
 /**
diff --git a/front_end/console/ConsoleViewMessage.js b/front_end/console/ConsoleViewMessage.js
index 7ab961e..b4f863a 100644
--- a/front_end/console/ConsoleViewMessage.js
+++ b/front_end/console/ConsoleViewMessage.js
@@ -252,14 +252,17 @@
     } else {
       let rendered = false;
       this._completeElementForTestPromise = null;
-      for (const extension of self.runtime.extensions(Common.Renderer, this._message)) {
+      for (const extension of self.runtime.extensions(UI.Renderer, this._message)) {
         if (extension.descriptor()['source'] === this._message.source) {
           messageElement = createElement('span');
           let callback;
           this._completeElementForTestPromise = new Promise(fulfill => callback = fulfill);
           extension.instance().then(renderer => {
             renderer.render(this._message)
-                .then(element => messageElement.appendChild(element || this._format([messageText])))
+                .then(result => {
+                  const renderedNode = result ? result.node : null;
+                  messageElement.appendChild(renderedNode || this._format([messageText]));
+                })
                 .then(callback);
           });
           rendered = true;
@@ -673,18 +676,15 @@
     const domModel = remoteObject.runtimeModel().target().model(SDK.DOMModel);
     if (!domModel)
       return result;
-    domModel.pushObjectAsNodeToFrontend(remoteObject).then(node => {
+    domModel.pushObjectAsNodeToFrontend(remoteObject).then(async node => {
       if (!node) {
         result.appendChild(this._formatParameterAsObject(remoteObject, false));
         return;
       }
-      Common.Renderer.render(node).then(rendererNode => {
-        if (rendererNode)
-          result.appendChild(rendererNode);
-        else
-          result.appendChild(this._formatParameterAsObject(remoteObject, false));
-        this._formattedParameterAsNodeForTest();
-      });
+      const renderResult = await UI.Renderer.render(/** @type {!Object} */ (node));
+      const renderedNode = renderResult ? renderResult.node : null;
+      result.appendChild(renderedNode || this._formatParameterAsObject(remoteObject, false));
+      this._formattedParameterAsNodeForTest();
     });
 
     return result;
diff --git a/front_end/elements/ElementsTreeOutline.js b/front_end/elements/ElementsTreeOutline.js
index f93b5c2..2105264 100644
--- a/front_end/elements/ElementsTreeOutline.js
+++ b/front_end/elements/ElementsTreeOutline.js
@@ -1532,19 +1532,19 @@
 };
 
 /**
- * @implements {Common.Renderer}
+ * @implements {UI.Renderer}
  */
 Elements.ElementsTreeOutline.Renderer = class {
   /**
    * @override
    * @param {!Object} object
-   * @return {!Promise.<?Node>}
+   * @return {!Promise<?{node: !Node, tree: ?UI.TreeOutline}>}
    */
   render(object) {
     return new Promise(renderPromise);
 
     /**
-     * @param {function(!Element)} resolve
+     * @param {function(!{node: !Node, tree: ?UI.TreeOutline})} resolve
      * @param {function(!Error)} reject
      */
     function renderPromise(resolve, reject) {
@@ -1570,7 +1570,7 @@
           treeOutline._element.classList.add('single-node');
         treeOutline.setVisible(true);
         treeOutline.element.treeElementForTest = treeOutline.firstChild();
-        resolve(treeOutline.element);
+        resolve({node: treeOutline.element, tree: treeOutline});
       }
     }
   }
diff --git a/front_end/elements/module.json b/front_end/elements/module.json
index 6cffd5f..d4996e0 100644
--- a/front_end/elements/module.json
+++ b/front_end/elements/module.json
@@ -18,7 +18,7 @@
             "className": "Elements.ElementsPanel.ContextMenuProvider"
         },
         {
-            "type": "@Common.Renderer",
+            "type": "@UI.Renderer",
             "contextTypes": [
                 "SDK.DOMNode",
                 "SDK.DeferredDOMNode"
diff --git a/front_end/extensions/ExtensionPanel.js b/front_end/extensions/ExtensionPanel.js
index 1f1a885..fa58d12 100644
--- a/front_end/extensions/ExtensionPanel.js
+++ b/front_end/extensions/ExtensionPanel.js
@@ -277,15 +277,15 @@
       return;
     }
     this._objectPropertiesView.element.removeChildren();
-    Common.Renderer
-        .render(object, {
-          title: title,
-          expanded: true,
-          editable: false,
-        })
-        .then(element => {
-          this._objectPropertiesView.element.appendChild(element);
-          callback();
-        });
+    UI.Renderer.render(object, {title, editable: false}).then(result => {
+      if (!result) {
+        callback();
+        return;
+      }
+      if (result.tree && result.tree.firstChild())
+        result.tree.firstChild().expand();
+      this._objectPropertiesView.element.appendChild(result.node);
+      callback();
+    });
   }
 };
diff --git a/front_end/object_ui/ObjectPropertiesSection.js b/front_end/object_ui/ObjectPropertiesSection.js
index 7e80eb4..745339d 100644
--- a/front_end/object_ui/ObjectPropertiesSection.js
+++ b/front_end/object_ui/ObjectPropertiesSection.js
@@ -1381,14 +1381,14 @@
 ObjectUI.ObjectPropertiesSectionExpandController._treeOutlineId = Symbol('treeOutlineId');
 
 /**
- * @implements {Common.Renderer}
+ * @implements {UI.Renderer}
  */
 ObjectUI.ObjectPropertiesSection.Renderer = class {
   /**
    * @override
    * @param {!Object} object
-   * @param {!Common.Renderer.Options=} options
-   * @return {!Promise<?Node>}
+   * @param {!UI.Renderer.Options=} options
+   * @return {!Promise<?{node: !Node, tree: ?UI.TreeOutline}>}
    */
   render(object, options) {
     if (!(object instanceof SDK.RemoteObject))
@@ -1398,9 +1398,8 @@
     const section = new ObjectUI.ObjectPropertiesSection(object, title);
     if (!title)
       section.titleLessMode();
-    if (options.expanded)
-      section.expand();
     section.editable = !!options.editable;
-    return Promise.resolve(section.element);
+    return Promise.resolve(
+        /** @type {?{node: !Node, tree: ?UI.TreeOutline}} */ ({node: section.element, tree: section}));
   }
 };
\ No newline at end of file
diff --git a/front_end/object_ui/module.json b/front_end/object_ui/module.json
index d56753b..e2d1236 100644
--- a/front_end/object_ui/module.json
+++ b/front_end/object_ui/module.json
@@ -1,7 +1,7 @@
 {
     "extensions": [
         {
-            "type": "@Common.Renderer",
+            "type": "@UI.Renderer",
             "contextTypes": [
                 "SDK.RemoteObject"
             ],
diff --git a/front_end/ui/UIUtils.js b/front_end/ui/UIUtils.js
index 9c2761c..7531b84 100644
--- a/front_end/ui/UIUtils.js
+++ b/front_end/ui/UIUtils.js
@@ -2045,3 +2045,32 @@
   });
   return fragment;
 };
+
+/**
+ * @interface
+ */
+UI.Renderer = function() {};
+
+UI.Renderer.prototype = {
+  /**
+   * @param {!Object} object
+   * @param {!UI.Renderer.Options=} options
+   * @return {!Promise<?{node: !Node, tree: ?UI.TreeOutline}>}
+   */
+  render(object, options) {}
+};
+
+/**
+ * @param {?Object} object
+ * @param {!UI.Renderer.Options=} options
+ * @return {!Promise<?{node: !Node, tree: ?UI.TreeOutline}>}
+ */
+UI.Renderer.render = async function(object, options) {
+  if (!object)
+    throw new Error('Can\'t render ' + object);
+  const renderer = await self.runtime.extension(UI.Renderer, object).instance();
+  return renderer ? renderer.render(object, options || {}) : null;
+};
+
+/** @typedef {!{title: (string|!Element|undefined), editable: (boolean|undefined) }} */
+UI.Renderer.Options;