Bug 1248601 - Register the Developer Toolbar dynamically. r=jwalker
authorAlexandre Poirot <poirot.alex@gmail.com>
Sat, 27 Feb 2016 04:51:11 -0800
changeset 322111 9b8fd7fc3d8d9babbc0668230acc4f65e15640bd
parent 322110 c71cf2cec67dd2bc97b0a7091544e6679faa438d
child 322112 fe9a225a5eb94b245ab44ffa52946f4693508495
push id5913
push userjlund@mozilla.com
push dateMon, 25 Apr 2016 16:57:49 +0000
treeherdermozilla-beta@dcaf0a6fa115 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjwalker
bugs1248601
milestone47.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1248601 - Register the Developer Toolbar dynamically. r=jwalker
browser/base/content/browser.js
browser/base/content/browser.xul
devtools/client/shared/developer-toolbar.js
devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -155,17 +155,17 @@ XPCOMUtils.defineLazyGetter(this, "Popup
     Cu.reportError(ex);
     return null;
   }
 });
 
 XPCOMUtils.defineLazyGetter(this, "DeveloperToolbar", function() {
   let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
   let { DeveloperToolbar } = require("devtools/client/shared/developer-toolbar");
-  return new DeveloperToolbar(window, document.getElementById("developer-toolbar"));
+  return new DeveloperToolbar(window);
 });
 
 XPCOMUtils.defineLazyGetter(this, "BrowserToolboxProcess", function() {
   let tmp = {};
   Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", tmp);
   return tmp.BrowserToolboxProcess;
 });
 
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -1104,39 +1104,16 @@
 #else
             &exitDOMFullscreen.button;
 #endif
     </html:button>
   </html:div>
 
   <vbox id="browser-bottombox" layer="true">
     <notificationbox id="global-notificationbox" notificationside="bottom"/>
-    <toolbar id="developer-toolbar"
-             hidden="true">
-#ifdef XP_MACOSX
-          <toolbarbutton id="developer-toolbar-closebutton"
-                         class="devtools-closebutton"
-                         oncommand="DeveloperToolbar.hide();"
-                         tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
-#endif
-          <stack class="gclitoolbar-stack-node" flex="1">
-            <textbox class="gclitoolbar-input-node" rows="1"/>
-            <hbox class="gclitoolbar-complete-node"/>
-          </stack>
-          <toolbarbutton id="developer-toolbar-toolbox-button"
-                         class="developer-toolbar-button"
-                         observes="devtoolsMenuBroadcaster_DevToolbox"
-                         tooltiptext="&devToolbarToolsButton.tooltip;"/>
-#ifndef XP_MACOSX
-          <toolbarbutton id="developer-toolbar-closebutton"
-                         class="devtools-closebutton"
-                         oncommand="DeveloperToolbar.hide();"
-                         tooltiptext="&devToolbarCloseButton.tooltiptext;"/>
-#endif
-   </toolbar>
   </vbox>
 
   <svg:svg height="0">
 #include tab-shape.inc.svg
     <svg:clipPath id="urlbar-back-button-clip-path">
 #ifndef XP_MACOSX
       <svg:path d="M -9,-4 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
 #else
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -208,46 +208,42 @@ exports.CommandUtils = CommandUtils;
  * Linux. See the comments for TooltipPanel and OutputPanel for further details.
  *
  * When bug 780102 is fixed all isLinux checks can be removed and we can revert
  * to using panels.
  */
 loader.lazyGetter(this, "isLinux", function() {
   return OS == "Linux";
 });
+loader.lazyGetter(this, "isMac", function() {
+  return OS == "Darwin";
+});
 
 loader.lazyGetter(this, "OS", function() {
   let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
   return os;
 });
 
 /**
  * A component to manage the global developer toolbar, which contains a GCLI
  * and buttons for various developer tools.
  * @param aChromeWindow The browser window to which this toolbar is attached
- * @param aToolbarElement See browser.xul:<toolbar id="developer-toolbar">
  */
-function DeveloperToolbar(aChromeWindow, aToolbarElement)
+function DeveloperToolbar(aChromeWindow)
 {
   this._chromeWindow = aChromeWindow;
 
   this.target = null; // Will be setup when show() is called
 
-  this._element = aToolbarElement;
-  this._element.hidden = true;
-  this._doc = this._element.ownerDocument;
+  this._doc = aChromeWindow.document;
 
   this._telemetry = new Telemetry();
   this._errorsCount = {};
   this._warningsCount = {};
   this._errorListeners = {};
-  this._errorCounterButton = this._doc
-                             .getElementById("developer-toolbar-toolbox-button");
-  this._errorCounterButton._defaultTooltipText =
-      this._errorCounterButton.getAttribute("tooltiptext");
 
   EventEmitter.decorate(this);
 }
 exports.DeveloperToolbar = DeveloperToolbar;
 
 /**
  * Inspector notifications dispatched through the nsIObserverService
  */
@@ -268,17 +264,17 @@ const NOTIFICATIONS = {
  */
 DeveloperToolbar.prototype.NOTIFICATIONS = NOTIFICATIONS;
 
 /**
  * Is the toolbar open?
  */
 Object.defineProperty(DeveloperToolbar.prototype, "visible", {
   get: function() {
-    return !this._element.hidden;
+    return this._element && !this._element.hidden;
   },
   enumerable: true
 });
 
 var _gSequenceId = 0;
 
 /**
  * Getter for a unique ID.
@@ -286,16 +282,72 @@ var _gSequenceId = 0;
 Object.defineProperty(DeveloperToolbar.prototype, "sequenceId", {
   get: function() {
     return _gSequenceId++;
   },
   enumerable: true
 });
 
 /**
+ * Create the <toolbar> element to insert within browser UI
+ */
+DeveloperToolbar.prototype.createToolbar = function() {
+  if (this._element) {
+    return;
+  }
+  let toolbar = this._doc.createElement("toolbar");
+  toolbar.setAttribute("id", "developer-toolbar");
+  toolbar.setAttribute("hidden", "true");
+
+  let close = this._doc.createElement("toolbarbutton");
+  close.setAttribute("id", "developer-toolbar-closebutton");
+  close.setAttribute("class", "devtools-closebutton");
+  close.setAttribute("oncommand", "DeveloperToolbar.hide();");
+  close.setAttribute("tooltiptext", "developerToolbarCloseButton.tooltiptext");
+
+  let stack = this._doc.createElement("stack");
+  stack.setAttribute("flex", "1");
+
+  let input = this._doc.createElement("textbox");
+  input.setAttribute("class", "gclitoolbar-input-node");
+  input.setAttribute("rows", "1");
+  stack.appendChild(input);
+
+  let hbox = this._doc.createElement("hbox");
+  hbox.setAttribute("class", "gclitoolbar-complete-node");
+  stack.appendChild(hbox);
+
+  let toolboxBtn = this._doc.createElement("toolbarbutton");
+  toolboxBtn.setAttribute("id", "developer-toolbar-toolbox-button");
+  toolboxBtn.setAttribute("class", "developer-toolbar-button");
+  toolboxBtn.setAttribute("observes", "devtoolsMenuBroadcaster_DevToolbox");
+  toolboxBtn.setAttribute("tooltiptext", "devToolbarToolsButton.tooltip");
+
+  // On Mac, the close button is on the left,
+  // while it is on the right on every other platforms.
+  if (isMac) {
+    toolbar.appendChild(close);
+    toolbar.appendChild(stack);
+    toolbar.appendChild(toolboxBtn);
+  } else {
+    toolbar.appendChild(stack);
+    toolbar.appendChild(toolboxBtn);
+    toolbar.appendChild(close);
+  }
+
+  let bottomBox = this._doc.getElementById("browser-bottombox");
+  this._element = toolbar;
+  bottomBox.appendChild(this._element);
+
+  this._errorCounterButton = toolboxBtn
+  this._errorCounterButton._defaultTooltipText =
+      this._errorCounterButton.getAttribute("tooltiptext");
+};
+
+/**
  * Called from browser.xul in response to menu-click or keyboard shortcut to
  * toggle the toolbar
  */
 DeveloperToolbar.prototype.toggle = function() {
   if (this.visible) {
     return this.hide().catch(console.error);
   } else {
     return this.show(true).catch(console.error);
@@ -351,16 +403,18 @@ DeveloperToolbar.prototype.show = functi
   if (this._showPromise != null) {
     return this._showPromise;
   }
 
   // hide() is async, so ensure we don't need to wait for hide() to finish
   var waitPromise = this._hidePromise || promise.resolve();
 
   this._showPromise = waitPromise.then(() => {
+    this.createToolbar();
+
     Services.prefs.setBoolPref("devtools.toolbar.visible", true);
 
     this._telemetry.toolOpened("developertoolbar");
 
     this._notify(NOTIFICATIONS.LOAD);
 
     this._input = this._doc.querySelector(".gclitoolbar-input-node");
 
@@ -378,17 +432,20 @@ DeveloperToolbar.prototype.show = functi
       this.target = TargetFactory.forTab(this._chromeWindow.gBrowser.selectedTab);
       const options = {
         environment: CommandUtils.createEnvironment(this, "target"),
         document: this.outputPanel.document,
       };
       return CommandUtils.createRequisition(this.target, options).then(requisition => {
         this.requisition = requisition;
 
-        return this.requisition.update(this._input.value).then(() => {
+        // The <textbox> `value` may still be undefined on the XUL binding if
+        // we fetch it early
+        let value = this._input.value || "";
+        return this.requisition.update(value).then(() => {
           const Inputter = require('gcli/mozui/inputter').Inputter;
           const Completer = require('gcli/mozui/completer').Completer;
           const Tooltip = require('gcli/mozui/tooltip').Tooltip;
           const FocusManager = require('gcli/ui/focus').FocusManager;
 
           this.onOutput = this.requisition.commandOutputManager.onOutput;
 
           this.focusManager = new FocusManager(this._doc, requisition.system.settings);
@@ -594,16 +651,19 @@ DeveloperToolbar.prototype.destroy = fun
   this.focusManager.destroy();
 
   this.outputPanel.destroy();
   this.tooltipPanel.destroy();
   delete this._input;
 
   CommandUtils.destroyRequisition(this.requisition, this.target);
   this.target = undefined;
+
+  this._element.remove();
+  delete this._element;
 };
 
 /**
  * Utility for sending notifications
  * @param topic a NOTIFICATION constant
  */
 DeveloperToolbar.prototype._notify = function(topic) {
   let data = { toolbar: this };
--- a/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
+++ b/devtools/client/shared/test/browser_toolbar_webconsole_errors_count.js
@@ -1,18 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the developer toolbar errors count works properly.
 
 function test() {
   const TEST_URI = TEST_URI_ROOT + "browser_toolbar_webconsole_errors_count.html";
 
-  let webconsole = document.getElementById("developer-toolbar-toolbox-button");
-  let tab1, tab2;
+
+  let tab1, tab2, webconsole;
 
   Services.prefs.setBoolPref("javascript.options.strict", true);
 
   registerCleanupFunction(() => {
     Services.prefs.clearUserPref("javascript.options.strict");
   });
 
   ignoreAllUncaughtExceptions();
@@ -29,16 +29,17 @@ function test() {
     }
     else {
       onOpenToolbar();
     }
   }
 
   function onOpenToolbar() {
     ok(DeveloperToolbar.visible, "DeveloperToolbar is visible");
+    webconsole = document.getElementById("developer-toolbar-toolbox-button");
 
     waitForButtonUpdate({
       name: "web console button shows page errors",
       errors: 3,
       warnings: 0,
       callback: addErrors,
     });
   }