Bug 795691 - b2g fixes for the web console actors; r=past,vingtetun,ttaubert
authorMihai Sucan <mihai.sucan@gmail.com>
Thu, 11 Oct 2012 21:24:57 +0300
changeset 111012 0743f910f5a5851c28155141bd8611a673bbb53a
parent 111011 4067355bbc56340308ba1c29aa60d870799497f2
child 111013 7d2e011a7b9fc4f8351a6c4c1ae9c4f9e0c87ef0
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
reviewerspast, vingtetun, ttaubert
bugs795691
milestone19.0a1
Bug 795691 - b2g fixes for the web console actors; r=past,vingtetun,ttaubert
b2g/chrome/content/dbg-browser-actors.js
browser/base/content/browser-appmenu.inc
browser/base/content/browser-menubar.inc
browser/base/content/browser-sets.inc
browser/base/content/browser.js
browser/devtools/webconsole/HUDService.jsm
browser/devtools/webconsole/webconsole.js
browser/locales/en-US/chrome/browser/browser.dtd
browser/locales/en-US/chrome/browser/devtools/webconsole.properties
toolkit/devtools/webconsole/dbg-webconsole-actors.js
--- a/b2g/chrome/content/dbg-browser-actors.js
+++ b/b2g/chrome/content/dbg-browser-actors.js
@@ -112,17 +112,17 @@ DeviceTabActor.prototype.grip = function
   let response = {
     'actor': this.actorID,
     'title': this.browser.title,
     'url': this.browser.document.documentURI
   };
 
   // Walk over tab actors added by extensions and add them to a new ActorPool.
   let actorPool = new ActorPool(this.conn);
-  this._createExtraActors(DebuggerServer.globalActorFactories, actorPool);
+  this._createExtraActors(DebuggerServer.tabActorFactories, actorPool);
   if (!actorPool.isEmpty()) {
     this._tabActorPool = actorPool;
     this.conn.addActorPool(this._tabActorPool);
   }
 
   this._appendExtraActors(response);
   return response;
 };
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -147,16 +147,17 @@
           </menupopup>
       </splitmenu>
       <menuseparator class="appmenu-menuseparator"/>
       <menu id="appmenu_webDeveloper"
             label="&appMenuWebDeveloper.label;">
         <menupopup id="appmenu_webDeveloper_popup">
           <menuitem id="appmenu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar"/>
           <menuitem id="appmenu_webConsole" observes="devtoolsMenuBroadcaster_WebConsole"/>
+          <menuitem id="appmenu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
           <menuitem id="appmenu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect"/>
           <menuitem id="appmenu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
           <menuitem id="appmenu_debugger" observes="devtoolsMenuBroadcaster_Debugger"/>
           <menuitem id="appmenu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
           <menuitem id="appmenu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
           <menuitem id="appmenu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad"/>
           <menuitem id="appmenu_styleeditor" observes="devtoolsMenuBroadcaster_StyleEditor"/>
           <menuitem id="appmenu_pageSource" observes="devtoolsMenuBroadcaster_PageSource"/>
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -529,16 +529,17 @@
 #endif
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
                 <menupopup id="menuWebDeveloperPopup">
                   <menuitem id="menu_devToolbar" observes="devtoolsMenuBroadcaster_DevToolbar" accesskey="&devToolbarMenu.accesskey;"/>
                   <menuitem id="webConsole" observes="devtoolsMenuBroadcaster_WebConsole" accesskey="&webConsoleCmd.accesskey;"/>
+                  <menuitem id="menu_remoteWebConsole" observes="devtoolsMenuBroadcaster_RemoteWebConsole"/>
                   <menuitem id="menu_pageinspect" observes="devtoolsMenuBroadcaster_Inspect" accesskey="&inspectMenu.accesskey;"/>
                   <menuitem id="menu_responsiveUI" observes="devtoolsMenuBroadcaster_ResponsiveUI" accesskey="&responsiveDesignTool.accesskey;"/>
                   <menuitem id="menu_debugger" observes="devtoolsMenuBroadcaster_Debugger" accesskey="&debuggerMenu.accesskey;"/>
                   <menuitem id="menu_remoteDebugger" observes="devtoolsMenuBroadcaster_RemoteDebugger"/>
                   <menuitem id="menu_chromeDebugger" observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
                   <menuitem id="menu_scratchpad" observes="devtoolsMenuBroadcaster_Scratchpad" accesskey="&scratchpad.accesskey;"/>
                   <menuitem id="menu_styleeditor" observes="devtoolsMenuBroadcaster_StyleEditor" accesskey="&styleeditor.accesskey;"/>
                   <menuitem id="menu_pageSource" observes="devtoolsMenuBroadcaster_PageSource" accesskey="&pageSourceCmd.accesskey;"/>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -86,16 +86,17 @@
     <command id="cmd_fullZoomToggle"  oncommand="ZoomManager.toggleZoom();"/>
     <command id="Browser:OpenLocation" oncommand="openLocation();"/>
 
     <command id="Tools:Search" oncommand="BrowserSearch.webSearch();"/>
     <command id="Tools:Downloads" oncommand="BrowserDownloadsUI();"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:WebConsole" oncommand="HUDConsoleUI.toggleHUD();"/>
+    <command id="Tools:RemoteWebConsole" oncommand="HUDConsoleUI.toggleRemoteHUD();" disabled="true" hidden="true"/>
     <command id="Tools:Inspect" oncommand="InspectorUI.toggleInspectorUI();"/>
     <command id="Tools:Debugger" oncommand="DebuggerUI.toggleDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:RemoteDebugger" oncommand="DebuggerUI.toggleRemoteDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:StyleEditor" oncommand="StyleEditor.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
@@ -198,16 +199,20 @@
                  type="checkbox" autocheck="false"
                  command="Tools:DevToolbar"
                  key="key_devToolbar"/>
     <broadcaster id="devtoolsMenuBroadcaster_WebConsole"
                  label="&webConsoleCmd.label;"
                  type="checkbox" autocheck="false"
                  key="key_webConsole"
                  command="Tools:WebConsole"/>
+    <broadcaster id="devtoolsMenuBroadcaster_RemoteWebConsole"
+                 label="&remoteWebConsoleCmd.label;"
+                 type="checkbox" autocheck="false"
+                 command="Tools:RemoteWebConsole"/>
     <broadcaster id="devtoolsMenuBroadcaster_Inspect"
                  label="&inspectMenu.label;"
                  type="checkbox" autocheck="false"
                  command="Tools:Inspect"
                  key="key_inspect"/>
     <broadcaster id="devtoolsMenuBroadcaster_Debugger"
                  label="&debuggerMenu.label2;"
                  type="checkbox" autocheck="false"
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1441,16 +1441,20 @@ var gBrowserInit = {
     }
 
     // Enable Remote Debugger?
     let enabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
     if (enabled) {
       let cmd = document.getElementById("Tools:RemoteDebugger");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
+
+      cmd = document.getElementById("Tools:RemoteWebConsole");
+      cmd.removeAttribute("disabled");
+      cmd.removeAttribute("hidden");
     }
 
     // Enable Chrome Debugger?
     let enabled = gPrefService.getBoolPref("devtools.chrome.enabled") &&
                   gPrefService.getBoolPref("devtools.debugger.chrome-enabled") &&
                   gPrefService.getBoolPref("devtools.debugger.remote-enabled");
     if (enabled) {
       let cmd = document.getElementById("Tools:ChromeDebugger");
--- a/browser/devtools/webconsole/HUDService.jsm
+++ b/browser/devtools/webconsole/HUDService.jsm
@@ -108,36 +108,43 @@ HUD_SERVICE.prototype =
 
   /**
    * Activate a HeadsUpDisplay for the given tab context.
    *
    * @param nsIDOMElement aTab
    *        The xul:tab element.
    * @param boolean aAnimated
    *        True if you want to animate the opening of the Web console.
+   * @param object aOptions
+   *        Options for the Web Console:
+   *        - host
+   *          Server to connect to.
+   *        - port
+   *          Port to connect to.
    * @return object
    *         The new HeadsUpDisplay instance.
    */
-  activateHUDForContext: function HS_activateHUDForContext(aTab, aAnimated)
+  activateHUDForContext:
+  function HS_activateHUDForContext(aTab, aAnimated, aOptions)
   {
     let hudId = "hud_" + aTab.linkedPanel;
     if (hudId in this.hudReferences) {
       return this.hudReferences[hudId];
     }
 
     this.wakeup();
 
     let window = aTab.ownerDocument.defaultView;
     let gBrowser = window.gBrowser;
 
     gBrowser.tabContainer.addEventListener("TabClose", this.onTabClose, false);
     gBrowser.tabContainer.addEventListener("TabSelect", this.onTabSelect, false);
     window.addEventListener("unload", this.onWindowUnload, false);
 
-    let hud = new WebConsole(aTab);
+    let hud = new WebConsole(aTab, aOptions);
     this.hudReferences[hudId] = hud;
 
     if (!aAnimated || hud.consolePanel) {
       this.disableAnimation(hudId);
     }
 
     HeadsUpDisplayUICommands.refreshCommand();
 
@@ -488,23 +495,29 @@ HUD_SERVICE.prototype =
  * A WebConsole instance is an interactive console initialized *per tab*
  * that displays console log data as well as provides an interactive terminal to
  * manipulate the current tab's document content.
  *
  * This object only wraps the iframe that holds the Web Console UI.
  *
  * @param nsIDOMElement aTab
  *        The xul:tab for which you want the WebConsole object.
+ * @param object aOptions
+ *        Web Console options: host and port, for the remote Web console.
  */
-function WebConsole(aTab)
+function WebConsole(aTab, aOptions = {})
 {
   this.tab = aTab;
   this.chromeDocument = this.tab.ownerDocument;
   this.chromeWindow = this.chromeDocument.defaultView;
   this.hudId = "hud_" + this.tab.linkedPanel;
+
+  this.remoteHost = aOptions.host;
+  this.remotePort = aOptions.port;
+
   this._onIframeLoad = this._onIframeLoad.bind(this);
   this._initUI();
 }
 
 WebConsole.prototype = {
   /**
    * The xul:tab for which the current Web Console instance was created.
    * @type nsIDOMElement
@@ -1012,17 +1025,18 @@ var HeadsUpDisplayUICommands = {
     let command = window.document.getElementById("Tools:WebConsole");
     if (this.getOpenHUD() != null) {
       command.setAttribute("checked", true);
     } else {
       command.setAttribute("checked", false);
     }
   },
 
-  toggleHUD: function UIC_toggleHUD() {
+  toggleHUD: function UIC_toggleHUD(aOptions)
+  {
     var window = HUDService.currentContext();
     var gBrowser = window.gBrowser;
     var linkedBrowser = gBrowser.selectedTab.linkedBrowser;
     var tabId = gBrowser.getNotificationBox(linkedBrowser).getAttribute("id");
     var hudId = "hud_" + tabId;
     var ownerDocument = gBrowser.selectedTab.ownerDocument;
     var hud = ownerDocument.getElementById(hudId);
     var hudRef = HUDService.hudReferences[hudId];
@@ -1041,21 +1055,62 @@ var HeadsUpDisplayUICommands = {
           // case gracefully.
           if (ownerDocument.getElementById(hudId)) {
             HUDService.deactivateHUDForContext(gBrowser.selectedTab, true);
           }
         });
       }
     }
     else {
-      HUDService.activateHUDForContext(gBrowser.selectedTab, true);
+      HUDService.activateHUDForContext(gBrowser.selectedTab, true, aOptions);
       HUDService.animate(hudId, ANIMATE_IN);
     }
   },
 
+  toggleRemoteHUD: function UIC_toggleRemoteHUD()
+  {
+    if (this.getOpenHUD()) {
+      this.toggleHUD();
+      return;
+    }
+
+    let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
+    let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
+
+    let check = { value: false };
+    let input = { value: host + ":" + port };
+
+    let result = Services.prompt.prompt(null,
+      l10n.getStr("remoteWebConsolePromptTitle"),
+      l10n.getStr("remoteWebConsolePromptMessage"),
+      input, null, check);
+
+    if (!result) {
+      return;
+    }
+
+    let parts = input.value.split(":");
+    if (parts.length != 2) {
+      return;
+    }
+
+    [host, port] = parts;
+    if (!host.length || !port.length) {
+      return;
+    }
+
+    Services.prefs.setCharPref("devtools.debugger.remote-host", host);
+    Services.prefs.setIntPref("devtools.debugger.remote-port", port);
+
+    this.toggleHUD({
+      host: host,
+      port: port,
+    });
+  },
+
   /**
    * Find the hudId for the active chrome window.
    * @return string|null
    *         The hudId or null if the active chrome window has no open Web
    *         Console.
    */
   getOpenHUD: function UIC_getOpenHUD() {
     let chromeWindow = HUDService.currentContext();
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -16,16 +16,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
                                   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerClient",
                                   "resource://gre/modules/devtools/dbg-client.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "debuggerSocketConnect",
+                                  "resource://gre/modules/devtools/dbg-client.jsm");
+
 XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
                                    "@mozilla.org/widget/clipboardhelper;1",
                                    "nsIClipboardHelper");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyPanel",
                                   "resource:///modules/PropertyPanel.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PropertyTreeView",
@@ -355,18 +358,21 @@ WebConsoleFrame.prototype = {
   },
 
   /**
    * Connect to the server using the remote debugging protocol.
    * @private
    */
   _initConnection: function WCF__initConnection()
   {
-    this.proxy = new WebConsoleConnectionProxy(this);
-    this.proxy.initServer();
+    this.proxy = new WebConsoleConnectionProxy(this, {
+      host: this.owner.remoteHost,
+      port: this.owner.remotePort,
+    });
+
     this.proxy.connect(function() {
       this.saveRequestAndResponseBodies = this._saveRequestAndResponseBodies;
       this._onInitComplete();
     }.bind(this));
   },
 
   /**
    * Find the Web Console UI elements and setup event listeners as needed.
@@ -3854,20 +3860,24 @@ CommandController.prototype = {
 
 /**
  * The WebConsoleConnectionProxy handles the connection between the Web Console
  * and the application we connect to through the remote debug protocol.
  *
  * @constructor
  * @param object aWebConsole
  *        The Web Console instance that owns this connection proxy.
+ * @param object aOptions
+ *        Connection options: host and port.
  */
-function WebConsoleConnectionProxy(aWebConsole)
+function WebConsoleConnectionProxy(aWebConsole, aOptions = {})
 {
   this.owner = aWebConsole;
+  this.remoteHost = aOptions.host;
+  this.remotePort = aOptions.port;
 
   this._onPageError = this._onPageError.bind(this);
   this._onConsoleAPICall = this._onConsoleAPICall.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onFileActivity = this._onFileActivity.bind(this);
   this._onLocationChange = this._onLocationChange.bind(this);
 }
@@ -3933,38 +3943,86 @@ WebConsoleConnectionProxy.prototype = {
   /**
    * Initialize a debugger client and connect it to the debugger server.
    *
    * @param function [aCallback]
    *        Optional function to invoke when connection is established.
    */
   connect: function WCCP_connect(aCallback)
   {
-    let transport = DebuggerServer.connectPipe();
+    let transport;
+    if (this.remoteHost) {
+      transport = debuggerSocketConnect(this.remoteHost, this.remotePort);
+    }
+    else {
+      this.initServer();
+      transport = DebuggerServer.connectPipe();
+    }
+
     let client = this.client = new DebuggerClient(transport);
 
     client.addListener("pageError", this._onPageError);
     client.addListener("consoleAPICall", this._onConsoleAPICall);
     client.addListener("networkEvent", this._onNetworkEvent);
     client.addListener("networkEventUpdate", this._onNetworkEventUpdate);
     client.addListener("fileActivity", this._onFileActivity);
     client.addListener("locationChange", this._onLocationChange);
 
+    client.connect(function(aType, aTraits) {
+      client.listTabs(this._onListTabs.bind(this, aCallback));
+    }.bind(this));
+  },
+
+  /**
+   * The "listTabs" response handler.
+   *
+   * @private
+   * @param function [aCallback]
+   *        Optional function to invoke once the connection is established.
+   * @param object aResponse
+   *        The JSON response object received from the server.
+   */
+  _onListTabs: function WCCP__onListTabs(aCallback, aResponse)
+  {
+    let selectedTab;
+
+    if (this.remoteHost) {
+      let tabs = [];
+      for (let tab of aResponse.tabs) {
+        tabs.push(tab.title);
+      }
+
+      tabs.push(l10n.getStr("listTabs.globalConsoleActor"));
+
+      let selected = {};
+      let result = Services.prompt.select(null,
+        l10n.getStr("remoteWebConsoleSelectTabTitle"),
+        l10n.getStr("remoteWebConsoleSelectTabMessage"),
+        tabs.length, tabs, selected);
+
+      if (result && selected.value < aResponse.tabs.length) {
+        selectedTab = aResponse.tabs[selected.value];
+      }
+    }
+    else {
+      selectedTab = aResponse.tabs[aResponse.selected];
+    }
+
+    if (selectedTab) {
+      this._consoleActor = selectedTab.consoleActor;
+      this.owner.onLocationChange(selectedTab.url, selectedTab.title);
+    }
+    else {
+      this._consoleActor = aResponse.consoleActor;
+    }
+
     let listeners = ["PageError", "ConsoleAPI", "NetworkActivity",
                      "FileActivity", "LocationChange"];
-
-    client.connect(function(aType, aTraits) {
-      client.listTabs(function(aResponse) {
-        let tab = aResponse.tabs[aResponse.selected];
-        this._consoleActor = tab.consoleActor;
-        this.owner.onLocationChange(tab.url, tab.title);
-        client.attachConsole(tab.consoleActor, listeners,
-                             this._onAttachConsole.bind(this, aCallback));
-      }.bind(this));
-    }.bind(this));
+    this.client.attachConsole(this._consoleActor, listeners,
+                              this._onAttachConsole.bind(this, aCallback));
   },
 
   /**
    * The "attachConsole" response handler.
    *
    * @private
    * @param function [aCallback]
    *        Optional function to invoke once the connection is established.
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -202,16 +202,17 @@ These should match what Safari and other
 
 <!ENTITY errorConsoleCmd.label        "Error Console">
 <!ENTITY errorConsoleCmd.accesskey    "C">
 <!ENTITY errorConsoleCmd.commandkey   "j">
 
 <!ENTITY webConsoleCmd.label          "Web Console">
 <!ENTITY webConsoleCmd.accesskey      "W">
 <!ENTITY webConsoleCmd.commandkey     "k">
+<!ENTITY remoteWebConsoleCmd.label    "Remote Web Console">
 
 <!ENTITY inspectMenu.label            "Inspect">
 <!ENTITY inspectMenu.accesskey        "I">
 <!ENTITY inspectMenu.commandkey       "I">
 
 <!ENTITY inspectContextMenu.label     "Inspect Element">
 <!ENTITY inspectContextMenu.accesskey "Q">
 
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -140,8 +140,28 @@ Autocomplete.blank=  <- no result
 
 maxTimersExceeded=The maximum allowed number of timers in this page was exceeded.
 
 # LOCALIZATION NOTE (JSTerm.updateNotInspectable):
 # This string is used when the user inspects an evaluation result in the Web
 # Console and tries the Update button, but the new result no longer returns an
 # object that can be inspected.
 JSTerm.updateNotInspectable=After your input has been re-evaluated the result is no longer inspectable.
+
+# LOCALIZATION NOTE (remoteWebConsolePromptTitle): The title displayed on the
+# Web Console prompt asking for the remote host and port to connect to.
+remoteWebConsolePromptTitle=Remote Connection
+
+# LOCALIZATION NOTE (remoteWebConsolePromptMessage): The message displayed on the
+# Web Console prompt asking for the remote host and port to connect to.
+remoteWebConsolePromptMessage=Enter hostname and port number (host:port)
+
+# LOCALIZATION NOTE (remoteWebConsoleSelectTabTitle): The title displayed on the
+# Web Console prompt asking the user to pick a tab to attach to.
+remoteWebConsoleSelectTabTitle=Tab list - Remote Connection
+
+# LOCALIZATION NOTE (remoteWebConsoleSelectTabMessage): The message displayed on the
+# Web Console prompt asking the user to pick a tab to attach to.
+remoteWebConsoleSelectTabMessage=Select one of the tabs you want to attach to, or select the global console.
+
+# LOCALIZATION NOTE (listTabs.globalConsoleActor): The string displayed for the
+# global console in the tabs selection.
+listTabs.globalConsoleActor=*Global Console*
--- a/toolkit/devtools/webconsole/dbg-webconsole-actors.js
+++ b/toolkit/devtools/webconsole/dbg-webconsole-actors.js
@@ -350,16 +350,18 @@ WebConsoleActor.prototype =
             break;
           }
           if (!this.consoleProgressListener) {
             this.consoleProgressListener =
               new ConsoleProgressListener(this._browser, this);
           }
           this.consoleProgressListener.startMonitor(this.consoleProgressListener.
                                                     MONITOR_LOCATION_CHANGE);
+          startedListeners.push(listener);
+          break;
       }
     }
     return {
       startedListeners: startedListeners,
       nativeConsoleAPI: this.hasNativeConsoleAPI(),
     };
   },