Merge m-c to s-c.
authorRichard Newman <rnewman@mozilla.com>
Sun, 26 Aug 2012 22:52:01 -0700
changeset 111042 cf04bd598966ec84dad283264d2513fadeb0d888
parent 111041 c05ca15d97b562914ff885779f393c6ca08a6d74 (current diff)
parent 105548 8af6a22827ecaffe6328a02633411da0421ccb90 (diff)
child 111043 11a42c29d92be7f2d8c0ae3c3eed8766c51baedb
push id93
push usernmatsakis@mozilla.com
push dateWed, 31 Oct 2012 21:26:57 +0000
milestone17.0a1
Merge m-c to s-c.
browser/config/tooltool-manifests/macosx32/clang.manifest
browser/config/tooltool-manifests/macosx64/clang.manifest
browser/devtools/commandline/GcliCommands.jsm
browser/devtools/commandline/GcliCookieCommands.jsm
browser/devtools/commandline/GcliTiltCommands.jsm
browser/devtools/commandline/gcli.css
browser/devtools/commandline/gclioutput.xhtml
browser/devtools/commandline/gclitooltip.xhtml
browser/devtools/commandline/test/browser_gcli_addon.js
browser/devtools/commandline/test/browser_gcli_break.html
browser/devtools/commandline/test/browser_gcli_break.js
browser/devtools/commandline/test/browser_gcli_calllog.js
browser/devtools/commandline/test/browser_gcli_commands.js
browser/devtools/commandline/test/browser_gcli_cookie.js
browser/devtools/commandline/test/browser_gcli_dbg.js
browser/devtools/commandline/test/browser_gcli_edit.js
browser/devtools/commandline/test/browser_gcli_inspect.html
browser/devtools/commandline/test/browser_gcli_inspect.js
browser/devtools/commandline/test/browser_gcli_integrate.js
browser/devtools/commandline/test/browser_gcli_jsb.js
browser/devtools/commandline/test/browser_gcli_pagemod_export.js
browser/devtools/commandline/test/browser_gcli_pref.js
browser/devtools/commandline/test/browser_gcli_responsivemode.js
browser/devtools/commandline/test/browser_gcli_restart.js
browser/devtools/commandline/test/browser_gcli_settings.js
browser/devtools/commandline/test/resources.html
browser/devtools/commandline/test/resources_dbg.html
browser/devtools/commandline/test/resources_inpage.js
browser/devtools/commandline/test/resources_inpage1.css
browser/devtools/commandline/test/resources_inpage2.css
browser/devtools/commandline/test/resources_jsb_script.js
browser/themes/gnomestripe/devtools/gcli.css
browser/themes/gnomestripe/devtools/inspector-option-icon.png
browser/themes/pinstripe/devtools/gcli.css
browser/themes/pinstripe/devtools/inspector-option-icon.png
browser/themes/pinstripe/social/panelarrow-down.png
browser/themes/pinstripe/social/panelarrow-horiz.png
browser/themes/pinstripe/social/panelarrow-up.png
browser/themes/winstripe/devtools/gcli.css
browser/themes/winstripe/devtools/inspector-option-icon.png
ipc/chromium/src/base/file_util_linux.cc
--- a/accessible/src/generic/Accessible.cpp
+++ b/accessible/src/generic/Accessible.cpp
@@ -794,18 +794,17 @@ Accessible::ChildAtPoint(int32_t aX, int
   NS_ENSURE_TRUE(frame, nullptr);
 
   nsPresContext *presContext = frame->PresContext();
 
   nsRect screenRect = frame->GetScreenRectInAppUnits();
   nsPoint offset(presContext->DevPixelsToAppUnits(aX) - screenRect.x,
                  presContext->DevPixelsToAppUnits(aY) - screenRect.y);
 
-  nsIPresShell* presShell = presContext->PresShell();
-  nsIFrame *foundFrame = presShell->GetFrameForPoint(frame, offset);
+  nsIFrame *foundFrame = nsLayoutUtils::GetFrameForPoint(frame, offset);
 
   nsIContent* content = nullptr;
   if (!foundFrame || !(content = foundFrame->GetContent()))
     return fallbackAnswer;
 
   // Get accessible for the node with the point or the first accessible in
   // the DOM parent chain.
   DocAccessible* contentDocAcc = GetAccService()->
--- a/accessible/src/mac/mozAccessible.mm
+++ b/accessible/src/mac/mozAccessible.mm
@@ -97,17 +97,17 @@ GetClosestInterestingAccessible(id anObj
 
 - (BOOL)accessibilityIsIgnored
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN;
 
   // unknown (either unimplemented, or irrelevant) elements are marked as ignored
   // as well as expired elements.
   return !mGeckoAccessible || ([[self role] isEqualToString:NSAccessibilityUnknownRole] &&
-                               !(mGeckoAccessible->NativeInteractiveState() & states::FOCUSABLE));
+                               !(mGeckoAccessible->InteractiveState() & states::FOCUSABLE));
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO);
 }
 
 - (NSArray*)accessibilityAttributeNames
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL;
 
--- a/b2g/confvars.sh
+++ b/b2g/confvars.sh
@@ -1,18 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MOZ_APP_BASENAME=B2G
 MOZ_APP_VENDOR=Mozilla
 
 MOZ_APP_VERSION=17.0a1
-
-MOZ_UA_OS_AGNOSTIC=1
 MOZ_APP_UA_NAME=Firefox
 
 MOZ_BRANDING_DIRECTORY=b2g/branding/unofficial
 MOZ_OFFICIAL_BRANDING_DIRECTORY=b2g/branding/official
 # MOZ_APP_DISPLAYNAME is set by branding/configure.sh
 
 MOZ_SAFE_BROWSING=
 MOZ_SERVICES_SYNC=1
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1002,18 +1002,16 @@ pref("devtools.gcli.allowSet", false);
 pref("devtools.commands.dir", "");
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.htmlHeight", 112);
 pref("devtools.inspector.htmlPanelOpen", false);
 pref("devtools.inspector.sidebarOpen", false);
 pref("devtools.inspector.activeSidebar", "ruleview");
-pref("devtools.inspector.highlighterShowVeil", true);
-pref("devtools.inspector.highlighterShowInfobar", true);
 
 // Enable the Layout View
 pref("devtools.layoutview.enabled", true);
 pref("devtools.layoutview.open", false);
 
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.enabled", true);
 
@@ -1025,17 +1023,19 @@ pref("devtools.debugger.remote-autoconne
 pref("devtools.debugger.remote-connection-retries", 3);
 pref("devtools.debugger.remote-timeout", 3000);
 
 // The default Debugger UI settings
 pref("devtools.debugger.ui.height", 250);
 pref("devtools.debugger.ui.remote-win.width", 900);
 pref("devtools.debugger.ui.remote-win.height", 400);
 pref("devtools.debugger.ui.stackframes-width", 200);
+pref("devtools.debugger.ui.stackframes-pane-visible", true);
 pref("devtools.debugger.ui.variables-width", 300);
+pref("devtools.debugger.ui.variables-pane-visible", true);
 
 // Enable the style inspector
 pref("devtools.styleinspector.enabled", true);
 
 // Enable the Tilt inspector
 pref("devtools.tilt.enabled", true);
 pref("devtools.tilt.intro_transition", true);
 pref("devtools.tilt.outro_transition", true);
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -145,66 +145,86 @@ var gPluginHandler = {
         self.addLinkClickCallback(updateLink, "openPluginUpdatePage");
         /* FALLTHRU */
 
       case "PluginVulnerableNoUpdate":
       case "PluginClickToPlay":
         self._handleClickToPlayEvent(plugin);
         break;
 
+      case "PluginPlayPreview":
+        self._handlePlayPreviewEvent(plugin);
+        break;
+
       case "PluginDisabled":
         let manageLink = doc.getAnonymousElementByAttribute(plugin, "class", "managePluginsLink");
         self.addLinkClickCallback(manageLink, "managePlugins");
         break;
     }
 
     // Hide the in-content UI if it's too big. The crashed plugin handler already did this.
     if (event.type != "PluginCrashed") {
       let overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
       /* overlay might be null, so only operate on it if it exists */
       if (overlay != null && self.isTooSmall(plugin, overlay))
           overlay.style.visibility = "hidden";
     }
   },
 
+  canActivatePlugin: function PH_canActivatePlugin(objLoadingContent) {
+    return !objLoadingContent.activated &&
+           objLoadingContent.pluginFallbackType !== Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW;
+  },
+
   activatePlugins: function PH_activatePlugins(aContentWindow) {
     let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
     browser._clickToPlayPluginsActivated = true;
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
     let plugins = cwu.plugins;
     for (let plugin of plugins) {
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-      if (!objLoadingContent.activated)
+      if (gPluginHandler.canActivatePlugin(objLoadingContent))
         objLoadingContent.playPlugin();
     }
     let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
     if (notification)
       notification.remove();
   },
 
   activateSinglePlugin: function PH_activateSinglePlugin(aContentWindow, aPlugin) {
     let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
-    if (!objLoadingContent.activated)
+    if (gPluginHandler.canActivatePlugin(objLoadingContent))
       objLoadingContent.playPlugin();
 
     let cwu = aContentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                             .getInterface(Ci.nsIDOMWindowUtils);
     let haveUnplayedPlugins = cwu.plugins.some(function(plugin) {
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-      return (plugin != aPlugin && !objLoadingContent.activated);
+      return (plugin != aPlugin && gPluginHandler.canActivatePlugin(objLoadingContent));
     });
     let browser = gBrowser.getBrowserForDocument(aContentWindow.document);
     let notification = PopupNotifications.getNotification("click-to-play-plugins", browser);
     if (notification && !haveUnplayedPlugins) {
       browser._clickToPlayDoorhangerShown = false;
       notification.remove();
     }
   },
 
+  stopPlayPreview: function PH_stopPlayPreview(aPlugin, aPlayPlugin) {
+    let objLoadingContent = aPlugin.QueryInterface(Ci.nsIObjectLoadingContent);
+    if (objLoadingContent.activated)
+      return;
+
+    if (aPlayPlugin)
+      objLoadingContent.playPlugin();
+    else
+      objLoadingContent.cancelPlayPreview();
+  },
+
   newPluginInstalled : function(event) {
     // browser elements are anonymous so we can't just use target.
     var browser = event.originalTarget;
     // clear the plugin list, now that at least one plugin has been installed
     browser.missingPlugins = null;
 
     var notificationBox = gBrowser.getNotificationBox(browser);
     var notification = notificationBox.getNotificationWithValue("missing-plugins");
@@ -285,32 +305,72 @@ var gPluginHandler = {
           gPluginHandler.activateSinglePlugin(aEvent.target.ownerDocument.defaultView.top, aPlugin);
       }, true);
     }
 
     if (!browser._clickToPlayDoorhangerShown)
       gPluginHandler._showClickToPlayNotification(browser);
   },
 
+  _handlePlayPreviewEvent: function PH_handlePlayPreviewEvent(aPlugin) {
+    let doc = aPlugin.ownerDocument;
+    let previewContent = doc.getAnonymousElementByAttribute(aPlugin, "class", "previewPluginContent");
+    if (!previewContent) {
+      // the XBL binding is not attached (element is display:none), fallback to click-to-play logic
+      gPluginHandler.stopPlayPreview(aPlugin, false);
+      return;
+    }
+    let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+    if (!iframe) {
+      // lazy initialization of the iframe
+      iframe = doc.createElementNS("http://www.w3.org/1999/xhtml", "iframe");
+      iframe.className = "previewPluginContentFrame";
+      previewContent.appendChild(iframe);
+
+      // Force a style flush, so that we ensure our binding is attached.
+      aPlugin.clientTop;
+    }
+    let pluginInfo = getPluginInfo(aPlugin);
+    let playPreviewUri = "data:application/x-moz-playpreview;," + pluginInfo.mimetype;
+    iframe.src = playPreviewUri;
+
+    // MozPlayPlugin event can be dispatched from the extension chrome
+    // code to replace the preview content with the native plugin
+    previewContent.addEventListener("MozPlayPlugin", function playPluginHandler(aEvent) {
+      if (!aEvent.isTrusted)
+        return;
+
+      previewContent.removeEventListener("MozPlayPlugin", playPluginHandler, true);
+
+      let playPlugin = !aEvent.detail;
+      gPluginHandler.stopPlayPreview(aPlugin, playPlugin);
+
+      // cleaning up: removes overlay iframe from the DOM
+      let iframe = previewContent.getElementsByClassName("previewPluginContentFrame")[0];
+      if (iframe)
+        previewContent.removeChild(iframe);
+    }, true);
+  },
+
   reshowClickToPlayNotification: function PH_reshowClickToPlayNotification() {
     if (!Services.prefs.getBoolPref("plugins.click_to_play"))
       return;
 
     let browser = gBrowser.selectedBrowser;
 
     let pluginsPermission = Services.perms.testPermission(browser.currentURI, "plugins");
     if (pluginsPermission == Ci.nsIPermissionManager.DENY_ACTION)
       return;
 
     let contentWindow = browser.contentWindow;
     let cwu = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                            .getInterface(Ci.nsIDOMWindowUtils);
     let pluginNeedsActivation = cwu.plugins.some(function(plugin) {
       let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
-      return !objLoadingContent.activated;
+      return gPluginHandler.canActivatePlugin(objLoadingContent);
     });
     if (pluginNeedsActivation)
       gPluginHandler._showClickToPlayNotification(browser);
   },
 
   _showClickToPlayNotification: function PH_showClickToPlayNotification(aBrowser) {
     aBrowser._clickToPlayDoorhangerShown = true;
     let contentWindow = aBrowser.contentWindow;
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -36,16 +36,17 @@ let SocialUI = {
         // Exceptions here sometimes don't get reported properly, report them
         // manually :(
         try {
           this.updateToggleCommand();
           SocialShareButton.updateButtonHiddenState();
           SocialToolbar.updateButtonHiddenState();
           SocialSidebar.updateSidebar();
           SocialChatBar.update();
+          SocialFlyout.unload();
         } catch (e) {
           Components.utils.reportError(e);
           throw e;
         }
         break;
       case "social:ambient-notification-changed":
         SocialToolbar.updateButton();
         break;
@@ -66,19 +67,21 @@ let SocialUI = {
   _providerReady: function SocialUI_providerReady() {
     // If we couldn't find a provider, nothing to do here.
     if (!Social.provider)
       return;
 
     this.updateToggleCommand();
 
     let toggleCommand = this.toggleCommand;
-    let label = gNavigatorBundle.getFormattedString("social.enable.label",
-                                                    [Social.provider.name]);
-    let accesskey = gNavigatorBundle.getString("social.enable.accesskey");
+    let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
+    let label = gNavigatorBundle.getFormattedString("social.toggle.label",
+                                                    [Social.provider.name,
+                                                     brandShortName]);
+    let accesskey = gNavigatorBundle.getString("social.toggle.accesskey");
     toggleCommand.setAttribute("label", label);
     toggleCommand.setAttribute("accesskey", accesskey);
 
     SocialToolbar.init();
     SocialShareButton.init();
     SocialSidebar.init();
   },
 
@@ -132,17 +135,17 @@ let SocialUI = {
     Social.lastEventReceived = now;
 
     // Enable the social functionality, and indicate that it was activated
     Social.active = true;
 
     // Show a warning, allow undoing the activation
     let description = document.getElementById("social-activation-message");
     let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
-    let message = gNavigatorBundle.getFormattedString("social.activated.message",
+    let message = gNavigatorBundle.getFormattedString("social.activated.description",
                                                       [Social.provider.name, brandShortName]);
     description.value = message;
 
     SocialUI.notificationPanel.hidden = false;
 
     setTimeout(function () {
       SocialUI.notificationPanel.openPopup(SocialToolbar.button, "bottomcenter topright");
     }.bind(this), 0);
@@ -174,16 +177,126 @@ let SocialChatBar = {
       this.chatbar.newChat(aProvider, aURL, aCallback);
   },
   update: function() {
     if (!this.canShow)
       this.chatbar.removeAll();
   }
 }
 
+function sizeSocialPanelToContent(iframe) {
+  // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
+  // Need to handle dynamic sizing
+  let doc = iframe.contentDocument;
+  if (!doc) {
+    return;
+  }
+  // "notif" is an implementation detail that we should get rid of
+  // eventually
+  let body = doc.getElementById("notif") || doc.body;
+  if (!body || !body.firstChild) {
+    return;
+  }
+
+  let [height, width] = [body.firstChild.offsetHeight || 300, 330];
+  iframe.style.width = width + "px";
+  iframe.style.height = height + "px";
+}
+
+let SocialFlyout = {
+  get panel() {
+    return document.getElementById("social-flyout-panel");
+  },
+
+  dispatchPanelEvent: function(name) {
+    let doc = this.panel.firstChild.contentDocument;
+    let evt = doc.createEvent("CustomEvent");
+    evt.initCustomEvent(name, true, true, {});
+    doc.documentElement.dispatchEvent(evt);
+  },
+
+  _createFrame: function() {
+    let panel = this.panel;
+    if (!Social.provider || panel.firstChild)
+      return;
+    // create and initialize the panel for this window
+    let iframe = document.createElement("iframe");
+    iframe.setAttribute("type", "content");
+    iframe.setAttribute("flex", "1");
+    iframe.setAttribute("origin", Social.provider.origin);
+    panel.appendChild(iframe);
+  },
+
+  unload: function() {
+    let panel = this.panel;
+    if (!panel.firstChild)
+      return
+    panel.removeChild(panel.firstChild);
+  },
+
+  onShown: function(aEvent) {
+    let iframe = this.panel.firstChild;
+    iframe.docShell.isActive = true;
+    iframe.docShell.isAppTab = true;
+    if (iframe.contentDocument.readyState == "complete") {
+      this.dispatchPanelEvent("socialFrameShow");
+    } else {
+      // first time load, wait for load and dispatch after load
+      iframe.addEventListener("load", function panelBrowserOnload(e) {
+        iframe.removeEventListener("load", panelBrowserOnload, true);
+        setTimeout(function() {
+          SocialFlyout.dispatchPanelEvent("socialFrameShow");
+        }, 0);
+      }, true);
+    }
+  },
+
+  onHidden: function(aEvent) {
+    this.panel.firstChild.docShell.isActive = false;
+    this.dispatchPanelEvent("socialFrameHide");
+  },
+
+  open: function(aURL, yOffset, aCallback) {
+    if (!Social.provider)
+      return;
+    let panel = this.panel;
+    if (!panel.firstChild)
+      this._createFrame();
+    panel.hidden = false;
+    let iframe = panel.firstChild;
+
+    let src = iframe.getAttribute("src");
+    if (src != aURL) {
+      iframe.addEventListener("load", function documentLoaded() {
+        iframe.removeEventListener("load", documentLoaded, true);
+        sizeSocialPanelToContent(iframe);
+        if (aCallback) {
+          try {
+            aCallback(iframe.contentWindow);
+          } catch(e) {
+            Cu.reportError(e);
+          }
+        }
+      }, true);
+      iframe.setAttribute("src", aURL);
+    }
+    else if (aCallback) {
+      try {
+        aCallback(iframe.contentWindow);
+      } catch(e) {
+        Cu.reportError(e);
+      }
+    }
+
+    sizeSocialPanelToContent(iframe);
+    let anchor = document.getElementById("social-sidebar-browser");
+    panel.openPopup(anchor, "start_before", 0, yOffset, false, false);
+  }
+}
+
 let SocialShareButton = {
   // Called once, after window load, when the Social.provider object is initialized
   init: function SSB_init() {
     this.updateButtonHiddenState();
     this.updateProfileInfo();
   },
 
   updateProfileInfo: function SSB_updateProfileInfo() {
@@ -331,16 +444,18 @@ var SocialToolbar = {
   },
 
   updateButton: function SocialToolbar_updateButton() {
     this.updateButtonHiddenState();
     let provider = Social.provider;
     let iconNames = Object.keys(provider.ambientNotificationIcons);
     let iconBox = document.getElementById("social-status-iconbox");
     let notifBox = document.getElementById("social-notification-box");
+    let panel = document.getElementById("social-notification-panel");
+    panel.hidden = false;
     let notificationFrames = document.createDocumentFragment();
     let iconContainers = document.createDocumentFragment();
 
     for each(let name in iconNames) {
       let icon = provider.ambientNotificationIcons[name];
 
       let notificationFrameId = "social-status-" + icon.name;
       let notificationFrame = document.getElementById(notificationFrameId);
@@ -390,69 +505,50 @@ var SocialToolbar = {
   },
 
   showAmbientPopup: function SocialToolbar_showAmbientPopup(iconContainer) {
     let iconImage = iconContainer.firstChild;
     let panel = document.getElementById("social-notification-panel");
     let notifBox = document.getElementById("social-notification-box");
     let notificationFrame = document.getElementById(iconImage.getAttribute("notificationFrameId"));
 
-    panel.hidden = false;
-
-    function sizePanelToContent() {
-      // FIXME: bug 764787: Maybe we can use nsIDOMWindowUtils.getRootBounds() here?
-      // Need to handle dynamic sizing
-      let doc = notificationFrame.contentDocument;
-      if (!doc) {
-        return;
-      }
-      // "notif" is an implementation detail that we should get rid of
-      // eventually
-      let body = doc.getElementById("notif") || doc.body;
-      if (!body || !body.firstChild) {
-        return;
-      }
-
-      // Clear dimensions on all browsers so the panel size will
-      // only use the selected browser.
-      let frameIter = notifBox.firstElementChild;
-      while (frameIter) {
-        frameIter.hidden = (frameIter != notificationFrame);
-        frameIter = frameIter.nextElementSibling;
-      }
-
-      let [height, width] = [body.firstChild.offsetHeight || 300, 330];
-      notificationFrame.style.width = width + "px";
-      notificationFrame.style.height = height + "px";
+    // Clear dimensions on all browsers so the panel size will
+    // only use the selected browser.
+    let frameIter = notifBox.firstElementChild;
+    while (frameIter) {
+      frameIter.collapsed = (frameIter != notificationFrame);
+      frameIter = frameIter.nextElementSibling;
     }
 
-    sizePanelToContent();
-
     function dispatchPanelEvent(name) {
       let evt = notificationFrame.contentDocument.createEvent("CustomEvent");
       evt.initCustomEvent(name, true, true, {});
       notificationFrame.contentDocument.documentElement.dispatchEvent(evt);
     }
 
-    panel.addEventListener("popuphiding", function onpopuphiding() {
-      panel.removeEventListener("popuphiding", onpopuphiding);
+    panel.addEventListener("popuphidden", function onpopuphiding() {
+      panel.removeEventListener("popuphidden", onpopuphiding);
       SocialToolbar.button.removeAttribute("open");
+      notificationFrame.docShell.isActive = false;
       dispatchPanelEvent("socialFrameHide");
     });
 
     panel.addEventListener("popupshown", function onpopupshown() {
       panel.removeEventListener("popupshown", onpopupshown);
       SocialToolbar.button.setAttribute("open", "true");
+      notificationFrame.docShell.isActive = true;
       notificationFrame.docShell.isAppTab = true;
       if (notificationFrame.contentDocument.readyState == "complete") {
+        sizeSocialPanelToContent(notificationFrame);
         dispatchPanelEvent("socialFrameShow");
       } else {
         // first time load, wait for load and dispatch after load
         notificationFrame.addEventListener("load", function panelBrowserOnload(e) {
           notificationFrame.removeEventListener("load", panelBrowserOnload, true);
+          sizeSocialPanelToContent(notificationFrame);
           setTimeout(function() {
             dispatchPanelEvent("socialFrameShow");
           }, 0);
         }, true);
       }
     });
 
     panel.openPopup(iconImage, "bottomcenter topleft", 0, 0, false, false);
--- a/browser/base/content/browser-tabPreviews.js
+++ b/browser/base/content/browser-tabPreviews.js
@@ -184,19 +184,24 @@ var ctrlTab = {
 
     // Rotate the list until the selected tab is first
     while (!list[0].selected)
       list.push(list.shift());
 
     list = list.filter(function (tab) !tab.closing);
 
     if (this.recentlyUsedLimit != 0) {
-      let recentlyUsedTabs = this._recentlyUsedTabs;
-      if (this.recentlyUsedLimit > 0)
-        recentlyUsedTabs = this._recentlyUsedTabs.slice(0, this.recentlyUsedLimit);
+      let recentlyUsedTabs = [];
+      for (let tab of this._recentlyUsedTabs) {
+        if (!tab.hidden && !tab.closing) {
+          recentlyUsedTabs.push(tab);
+          if (this.recentlyUsedLimit > 0 && recentlyUsedTabs.length >= this.recentlyUsedLimit)
+            break;
+        }
+      }
       for (let i = recentlyUsedTabs.length - 1; i >= 0; i--) {
         list.splice(list.indexOf(recentlyUsedTabs[i]), 1);
         list.unshift(recentlyUsedTabs[i]);
       }
     }
 
     return this._tabList = list;
   },
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1015,16 +1015,17 @@ var gBrowserInit = {
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
     gBrowser.addEventListener("PluginNotFound",     gPluginHandler, true);
     gBrowser.addEventListener("PluginCrashed",      gPluginHandler, true);
     gBrowser.addEventListener("PluginBlocklisted",  gPluginHandler, true);
     gBrowser.addEventListener("PluginOutdated",     gPluginHandler, true);
     gBrowser.addEventListener("PluginDisabled",     gPluginHandler, true);
     gBrowser.addEventListener("PluginClickToPlay",  gPluginHandler, true);
+    gBrowser.addEventListener("PluginPlayPreview",  gPluginHandler, true);
     gBrowser.addEventListener("PluginVulnerableUpdatable", gPluginHandler, true);
     gBrowser.addEventListener("PluginVulnerableNoUpdate", gPluginHandler, true);
     gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
 #ifdef XP_MACOSX
     gBrowser.addEventListener("npapi-carbon-event-model-failure", gPluginHandler, true);
 #endif
 
     Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
@@ -1234,17 +1235,16 @@ var gBrowserInit = {
 
     // Misc. inits.
     CombinedStopReload.init();
     allTabs.readPref();
     TabsOnTop.init();
     BookmarksMenuButton.init();
     TabsInTitlebar.init();
     gPrivateBrowsingUI.init();
-    DownloadsButton.initializePlaceholder();
     retrieveToolbarIconsizesFromTheme();
 
     gDelayedStartupTimeoutId = setTimeout(this._delayedStartup.bind(this), 0, isLoadingBlank, mustLoadSidebar);
     gStartupRan = true;
   },
 
   _delayedStartup: function(isLoadingBlank, mustLoadSidebar) {
     let tmp = {};
@@ -3122,39 +3122,16 @@ var newWindowButtonObserver = {
     url = getShortcutOrURI(url, postData);
     if (url) {
       // allow third-party services to fixup this URL
       openNewWindowWith(url, null, postData.value, true);
     }
   }
 }
 
-var DownloadsButtonDNDObserver = {
-  onDragOver: function (aEvent)
-  {
-    var types = aEvent.dataTransfer.types;
-    if (types.contains("text/x-moz-url") ||
-        types.contains("text/uri-list") ||
-        types.contains("text/plain"))
-      aEvent.preventDefault();
-  },
-
-  onDragExit: function (aEvent)
-  {
-  },
-
-  onDrop: function (aEvent)
-  {
-    let name = { };
-    let url = browserDragAndDrop.drop(aEvent, name);
-    if (url)
-      saveURL(url, name, null, true, true);
-  }
-}
-
 const DOMLinkHandler = {
   handleEvent: function (event) {
     switch (event.type) {
       case "DOMLinkAdded":
         this.onLinkAdded(event);
         break;
     }
   },
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -266,16 +266,23 @@
                 command="Social:UnsharePage"/>
 #endif
       </hbox>
     </panel>
 
     <panel id="social-notification-panel" type="arrow" hidden="true" noautofocus="true">
       <box id="social-notification-box" flex="1"></box>
     </panel>
+    <panel id="social-flyout-panel"
+           onpopupshown="SocialFlyout.onShown()"
+           onpopuphidden="SocialFlyout.onHidden()"
+           type="arrow"
+           hidden="true"
+           noautofocus="true"
+           position="topcenter topright"/>
 
     <menupopup id="inspector-node-popup">
       <menuitem id="inspectorHTMLCopyInner"
                 label="&inspectorHTMLCopyInner.label;"
                 accesskey="&inspectorHTMLCopyInner.accesskey;"
                 command="Inspector:CopyInner"/>
       <menuitem id="inspectorHTMLCopyOuter"
                 label="&inspectorHTMLCopyOuter.label;"
@@ -510,17 +517,17 @@
       <hbox class="titlebar-placeholder" type="caption-buttons" ordinal="1000"/>
 #endif
     </toolbar>
 
     <toolbar id="nav-bar" class="toolbar-primary chromeclass-toolbar"
              toolbarname="&navbarCmd.label;" accesskey="&navbarCmd.accesskey;"
              fullscreentoolbar="true" mode="icons" customizable="true"
              iconsize="large"
-             defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,home-button,bookmarks-menu-button-container,window-controls"
+             defaultset="unified-back-forward-button,urlbar-container,reload-button,stop-button,search-container,downloads-button,home-button,bookmarks-menu-button-container,window-controls"
              context="toolbar-context-menu">
 
       <toolbaritem id="unified-back-forward-button" class="chromeclass-toolbar-additional"
                    context="backForwardMenu" removable="true"
                    forwarddisabled="true"
                    title="&backForwardItem.title;">
         <toolbarbutton id="back-button" class="toolbarbutton-1"
                        label="&backCmd.label;"
@@ -925,24 +932,24 @@
 # Update primaryToolbarButtons in browser/themes/browserShared.inc when adding
 # or removing default items with the toolbarbutton-1 class.
 
       <toolbarbutton id="print-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      label="&printButton.label;" command="cmd_print"
                      tooltiptext="&printButton.tooltip;"/>
 
       <!-- This is a placeholder for the Downloads Indicator.  It is visible
-           only during the customization of the toolbar or in the palette, and
-           is replaced when customization is done. -->
+           during the customization of the toolbar, in the palette, and before
+           the Downloads Indicator overlay is loaded. -->
       <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
-                     observes="Tools:Downloads"
-                     ondrop="DownloadsButtonDNDObserver.onDrop(event)"
-                     ondragover="DownloadsButtonDNDObserver.onDragOver(event)"
-                     ondragenter="DownloadsButtonDNDObserver.onDragOver(event)"
-                     ondragexit="DownloadsButtonDNDObserver.onDragExit(event)"
+                     oncommand="DownloadsIndicatorView.onCommand(event);"
+                     ondrop="DownloadsIndicatorView.onDrop(event);"
+                     ondragover="DownloadsIndicatorView.onDragOver(event);"
+                     ondragenter="DownloadsIndicatorView.onDragOver(event);"
+                     ondragleave="DownloadsIndicatorView.onDragLeave(event);"
                      label="&downloads.label;"
                      tooltiptext="&downloads.tooltip;"/>
 
       <toolbarbutton id="history-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
                      observes="viewHistorySidebar" label="&historyButton.label;"
                      tooltiptext="&historyButton.tooltip;"/>
 
       <toolbarbutton id="bookmarks-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
@@ -1063,16 +1070,17 @@
     <splitter id="social-sidebar-splitter"
               class="chromeclass-extrachrome sidebar-splitter"
               observes="socialSidebarBroadcaster"/>
     <vbox id="social-sidebar-box"
           class="chromeclass-extrachrome"
           observes="socialSidebarBroadcaster">
       <browser id="social-sidebar-browser"
                type="content"
+               disableglobalhistory="true"
                flex="1"
                style="min-width: 14em; width: 18em; max-width: 36em;"/>
     </vbox>
     <vbox id="browser-border-end" hidden="true" layer="true"/>
   </hbox>
 
   <hbox id="full-screen-warning-container" hidden="true" fadeout="true">
     <hbox style="width: 100%;" pack="center"> <!-- Inner hbox needed due to bug 579776. -->
@@ -1101,34 +1109,16 @@
              nowindowdrag="true"
              hidden="true">
 #ifdef XP_MACOSX
       <toolbarbutton id="highlighter-closebutton"
                      class="devtools-closebutton"
                      oncommand="InspectorUI.closeInspectorUI(false);"
                      tooltiptext="&inspectCloseButton.tooltiptext;"/>
 #endif
-      <toolbarbutton id="inspector-option-toolbarbutton"
-                     type="menu"
-                     tabindex="0"
-                     tooltiptext="&inspectOptionButton.tooltiptext;">
-        <menupopup id="inspector-option-popup"
-                   position="before_start">
-          <menuitem id="inspectorToggleVeil"
-                    type="checkbox"
-                    label="&inspectorToggleVeil.label;"
-                    accesskey="&inspectorToggleVeil.accesskey;"
-                    command="Inspector:ToggleVeil"/>
-          <menuitem id="inspectorToggleInfobar"
-                    type="checkbox"
-                    label="&inspectorToggleInfobar.label;"
-                    accesskey="&inspectorToggleInfobar.accesskey;"
-                    command="Inspector:ToggleInfobar"/>
-        </menupopup>
-      </toolbarbutton>
       <toolbarbutton id="inspector-inspect-toolbutton"
                      class="devtools-toolbarbutton"
                      command="Inspector:Inspect"/>
       <toolbarbutton id="inspector-treepanel-toolbutton"
                      class="devtools-toolbarbutton"
                      tabindex="0"
                      aria-label="&markupButton.arialabel;"
                      accesskey="&markupButton.accesskey;"
--- a/browser/base/content/highlighter.css
+++ b/browser/base/content/highlighter.css
@@ -7,58 +7,59 @@
 }
 
 #highlighter-controls {
   position: absolute;
   top: 0;
   left: 0;
 }
 
-#highlighter-veil-container {
+#highlighter-outline-container {
   overflow: hidden;
+  position: relative;
 }
 
-#highlighter-veil-container:not([dim]) > .highlighter-veil,
-#highlighter-veil-container:not([dim]) > hbox > .highlighter-veil {
-  visibility: hidden;
+#highlighter-outline {
+  position: absolute;
 }
 
-#highlighter-veil-container:not([disable-transitions]) > .highlighter-veil,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > .highlighter-veil,
-#highlighter-veil-container:not([disable-transitions]) > #highlighter-veil-middlebox > #highlighter-veil-transparentbox {
-  transition-property: width, height;
+#highlighter-outline[hidden] {
+  opacity: 0;
+  pointer-events: none;
+  display: -moz-box;
+}
+
+#highlighter-outline:not([disable-transitions]) {
+  transition-property: opacity, top, left, width, height;
   transition-duration: 0.1s;
   transition-timing-function: linear;
 }
 
-#highlighter-veil-bottombox,
-#highlighter-veil-rightbox {
-  -moz-box-flex: 1;
-}
-
-#highlighter-veil-middlebox:-moz-locale-dir(rtl) {
-  -moz-box-direction: reverse;
-}
-
 .inspector-breadcrumbs-button {
   direction: ltr;
 }
 
 /*
  * Node Infobar
  */
 
 #highlighter-nodeinfobar-container {
   position: absolute;
   max-width: 95%;
 }
 
-#highlighter-nodeinfobar-container:not([disable-transitions]) {
-  transition-property: top, left;
+#highlighter-nodeinfobar-container[hidden] {
+  opacity: 0;
+  pointer-events: none;
+  display: -moz-box;
+}
+
+#highlighter-nodeinfobar-container:not([disable-transitions]),
+#highlighter-nodeinfobar-container[disable-transitions][force-transitions] {
+  transition-property: transform, opacity, top, left;
   transition-duration: 0.1s;
   transition-timing-function: linear;
 }
 
 #highlighter-nodeinfobar-text {
   overflow: hidden;
   white-space: nowrap;
   text-overflow: ellipsis;
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -20,16 +20,43 @@
                   xbl:inherits="src,origin,collapsed=minimized" type="content"/>
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <field name="iframe" readonly="true">
         document.getAnonymousElementByAttribute(this, "anonid", "iframe");
       </field>
 
+      <property name="minimized">
+        <getter>
+          return this.getAttribute("minimized") == "true";
+        </getter>
+        <setter>
+          this.isActive = !val;
+          if (val)
+            this.setAttribute("minimized", "true");
+          else
+            this.removeAttribute("minimized");
+        </setter>
+      </property>
+
+      <property name="isActive">
+        <getter>
+          return this.iframe.docShell.isActive;
+        </getter>
+        <setter>
+          this.iframe.docShell.isActive = !!val;
+
+          // let the chat frame know if it is being shown or hidden
+          let evt = this.iframe.contentDocument.createEvent("CustomEvent");
+          evt.initCustomEvent(val ? "socialFrameHide" : "socialFrameShow", true, true, {});
+          this.iframe.contentDocument.documentElement.dispatchEvent(evt);
+        </setter>
+      </property>
+
       <method name="init">
         <parameter name="aProvider"/>
         <parameter name="aURL"/>
         <parameter name="aCallback"/>
         <body><![CDATA[
           this._callback = aCallback;
           this.setAttribute("origin", aProvider.origin);
           this.setAttribute("src", aURL);
@@ -39,37 +66,29 @@
       <method name="close">
         <body><![CDATA[
           this.parentNode.remove(this);
         ]]></body>
       </method>
 
       <method name="toggle">
         <body><![CDATA[
-          let type;
-          if (this.getAttribute("minimized") == "true") {
-            this.removeAttribute("minimized");
-            type = "socialFrameShow";
-          } else {
-            this.setAttribute("minimized", "true");
-            type = "socialFrameHide";
-          }
-          // let the chat frame know if it is being shown or hidden
-          let evt = this.iframe.contentDocument.createEvent("CustomEvent");
-          evt.initCustomEvent(type, true, true, {});
-          this.iframe.contentDocument.documentElement.dispatchEvent(evt);
+          this.minimized = !this.minimized;
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="focus" phase="capturing">
         this.parentNode.selectedChat = this;
       </handler>
-      <handler event="DOMContentLoaded" action="if (this._callback) this._callback(this.iframe.contentWindow);"/>
+      <handler event="load"><![CDATA[
+        this.isActive = !this.minimized;
+        if (this._callback) this._callback(this.iframe.contentWindow);
+      ]]></handler>
       <handler event="DOMTitleChanged" action="this.setAttribute('label', this.iframe.contentDocument.title);"/>
       <handler event="DOMLinkAdded"><![CDATA[
         // much of this logic is from DOMLinkHandler in browser.js
         // this sets the presence icon for a chat user, we simply use favicon style updating
         let link = event.originalTarget;
         let rel = link.rel && link.rel.toLowerCase();
         if (!link || !link.ownerDocument || !rel || !link.href)
           return;
@@ -187,32 +206,34 @@
             this.showChat(newChat);
         ]]></body>
       </method>
 
       <method name="collapseChat">
         <parameter name="aChatbox"/>
         <body><![CDATA[
           aChatbox.collapsed = true;
+          aChatbox.isActive = false;
           let menu = document.createElementNS("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul", "menuitem");
           menu.setAttribute("label", aChatbox.iframe.contentDocument.title);
           menu.chat = aChatbox;
           this.menuitemMap.set(aChatbox, menu);
           this.menupopup.appendChild(menu);
           this.menupopup.parentNode.collapsed = false;
         ]]></body>
       </method>
 
       <method name="showChat">
         <parameter name="aChatbox"/>
         <body><![CDATA[
           let menuitem = this.menuitemMap.get(aChatbox);
           this.menuitemMap.delete(aChatbox);
           this.menupopup.removeChild(menuitem);
           aChatbox.collapsed = false;
+          aChatbox.isActive = !aChatbox.minimized;
         ]]></body>
       </method>
 
       <method name="remove">
         <parameter name="aChatbox"/>
         <body><![CDATA[
           if (this.selectedChat == aChatbox) {
             this.selectedChat = aChatbox.previousSibling ? aChatbox.previousSibling : aChatbox.nextSibling
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -59,19 +59,16 @@ endif
 # 480169)
 
 # browser_drag.js is disabled, as it needs to be updated for the new behavior from bug 320638.
 
 # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 
 # browser_pageInfo.js + feed_tab.html is disabled for leaking (bug 767896)
 
-# browser_social_shareButton.js is disabled for not properly
-#   tearing down the social providers (bug 780010).
-
 _BROWSER_FILES = \
                  head.js \
                  browser_typeAheadFind.js \
                  browser_keywordSearch.js \
                  browser_allTabsPanel.js \
                  browser_alltabslistener.js \
                  browser_bug304198.js \
                  title_test.svg \
@@ -177,16 +174,17 @@ endif
                  browser_hide_removing.js \
                  browser_overflowScroll.js \
                  browser_locationBarCommand.js \
                  browser_locationBarExternalLoad.js \
                  browser_page_style_menu.js \
                  browser_pinnedTabs.js \
                  browser_plainTextLinks.js \
                  browser_pluginnotification.js \
+                 browser_pluginplaypreview.js \
                  browser_relatedTabs.js \
                  browser_sanitize-passwordDisabledHosts.js \
                  browser_sanitize-sitepermissions.js \
                  browser_sanitize-timespans.js \
                  browser_clearplugindata.js \
                  browser_clearplugindata.html \
                  browser_clearplugindata_noage.html \
                  browser_popupUI.js \
@@ -258,23 +256,26 @@ endif
                  browser_minimize.js \
                  browser_aboutSyncProgress.js \
                  browser_middleMouse_inherit.js \
                  redirect_bug623155.sjs \
                  browser_tabDrop.js \
                  browser_lastAccessedTab.js \
                  browser_bug734076.js \
                  browser_social_toolbar.js \
+                 browser_social_shareButton.js \
                  browser_social_sidebar.js \
+                 browser_social_flyout.js \
                  browser_social_mozSocial_API.js \
                  browser_social_isVisible.js \
                  browser_social_chatwindow.js \
                  social_panel.html \
                  social_sidebar.html \
                  social_chat.html \
+                 social_flyout.html \
                  social_window.html \
                  social_worker.js \
                  $(NULL)
 
 ifneq (cocoa,$(MOZ_WIDGET_TOOLKIT))
 _BROWSER_FILES += \
 		browser_bug462289.js \
 		$(NULL)
--- a/browser/base/content/test/browser_pluginnotification.js
+++ b/browser/base/content/test/browser_pluginnotification.js
@@ -2,28 +2,16 @@ var rootDir = getRootDirectory(gTestPath
 const gTestRoot = rootDir;
 const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
 
 var gTestBrowser = null;
 var gNextTest = null;
 var gClickToPlayPluginActualEvents = 0;
 var gClickToPlayPluginExpectedEvents = 5;
 
-function get_test_plugin() {
-  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  var tags = ph.getPluginTags();
-
-  // Find the test plugin
-  for (var i = 0; i < tags.length; i++) {
-    if (tags[i].name == "Test Plug-in")
-      return tags[i];
-  }
-  ok(false, "Unable to find plugin");
-}
-
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 // This listens for the next opened tab and checks it is of the right url.
 // opencallback is called when the new tab is fully loaded
 // closecallback is called when the tab is closed
 function TabOpenListener(url, opencallback, closecallback) {
   this.url = url;
   this.opencallback = opencallback;
@@ -112,31 +100,31 @@ function test1() {
   ok("application/x-unknown" in gTestBrowser.missingPlugins, "Test 1, Should know about application/x-unknown");
   ok(!("application/x-test" in gTestBrowser.missingPlugins), "Test 1, Should not know about application/x-test");
 
   var pluginNode = gTestBrowser.contentDocument.getElementById("unknown");
   ok(pluginNode, "Test 1, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "Test 1, plugin fallback type should be PLUGIN_UNSUPPORTED");
 
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   ok(plugin, "Should have a test plugin");
   plugin.disabled = false;
   plugin.blocklisted = false;
   prepareTest(test2, gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a working plugin in it.
 function test2() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
   ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 2, Should not have displayed the missing plugin notification");
   ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 2, Should not have displayed the blocked plugin notification");
   ok(!gTestBrowser.missingPlugins, "Test 2, Should not be a missing plugin list");
 
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   ok(plugin, "Should have a test plugin");
   plugin.disabled = true;
   prepareTest(test3, gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a disabled plugin in it.
 function test3() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
@@ -157,17 +145,17 @@ function test3() {
 }
 
 function test4(tab, win) {
   is(win.wrappedJSObject.gViewController.currentViewId, "addons://list/plugin", "Test 4, Should have displayed the plugins pane");
   gBrowser.removeTab(tab);
 }
 
 function prepareTest5() {
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   plugin.disabled = false;
   plugin.blocklisted = true;
   prepareTest(test5, gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a blocked plugin in it.
 function test5() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
@@ -200,17 +188,17 @@ function test6() {
 function test7() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
   ok(notificationBox.getNotificationWithValue("missing-plugins"), "Test 7, Should have displayed the missing plugin notification");
   ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 7, Should not have displayed the blocked plugin notification");
   ok(gTestBrowser.missingPlugins, "Test 7, Should be a missing plugin list");
   ok("application/x-unknown" in gTestBrowser.missingPlugins, "Test 7, Should know about application/x-unknown");
   ok("application/x-test" in gTestBrowser.missingPlugins, "Test 7, Should know about application/x-test");
 
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   plugin.disabled = false;
   plugin.blocklisted = false;
   Services.prefs.setBoolPref("plugins.click_to_play", true);
 
   prepareTest(test8, gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a working plugin that is click-to-play
@@ -456,17 +444,17 @@ function test13c() {
 }
 
 // Tests that the plugin's "activated" property is true for working plugins with click-to-play disabled.
 function test14() {
   var plugin = gTestBrowser.contentDocument.getElementById("test1");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
   ok(objLoadingContent.activated, "Test 14, Plugin should be activated");
 
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   plugin.disabled = false;
   plugin.blocklisted = false;
   Services.perms.removeAll();
   Services.prefs.setBoolPref("plugins.click_to_play", true);
   prepareTest(test15, gTestRoot + "plugin_alternate_content.html");
 }
 
 // Tests that the overlay is shown instead of alternate content when
@@ -635,17 +623,17 @@ function test18c() {
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_VULNERABLE_NO_UPDATE, "Test 18c, plugin fallback type should be PLUGIN_VULNERABLE_NO_UPDATE");
   ok(!objLoadingContent.activated, "Test 18c, Plugin should not be activated");
   var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "mainBox");
   ok(overlay.style.visibility != "hidden", "Test 18c, Plugin overlay should exist, not be hidden");
   var updateLink = doc.getAnonymousElementByAttribute(plugin, "class", "checkForUpdatesLink");
   ok(updateLink.style.display != "block", "Test 18c, Plugin should not have an update link");
 
   unregisterFakeBlocklistService();
-  var plugin = get_test_plugin();
+  var plugin = getTestPlugin();
   plugin.clicktoplay = false;
 
   prepareTest(test19a, gTestRoot + "plugin_test.html");
 }
 
 // Tests that clicking the icon of the overlay activates the plugin
 function test19a() {
   var doc = gTestBrowser.contentDocument;
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_pluginplaypreview.js
@@ -0,0 +1,311 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gNextTestSkip = 0;
+var gPlayPreviewPluginActualEvents = 0;
+var gPlayPreviewPluginExpectedEvents = 1;
+
+var gPlayPreviewRegistration = null;
+
+function registerPlayPreview(mimeType, targetUrl) {
+
+  function StreamConverterFactory() {}
+  StreamConverterFactory.prototype = {
+    QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory]),
+    _targetConstructor: null,
+
+    register: function register(targetConstructor) {
+      this._targetConstructor = targetConstructor;
+      var proto = targetConstructor.prototype;
+      var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+      registrar.registerFactory(proto.classID, proto.classDescription,
+                                proto.contractID, this);
+    },
+
+    unregister: function unregister() {
+      var proto = this._targetConstructor.prototype;
+      var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+      registrar.unregisterFactory(proto.classID, this);
+      this._targetConstructor = null;
+    },
+
+    // nsIFactory
+    createInstance: function createInstance(aOuter, iid) {
+      if (aOuter !== null)
+        throw Cr.NS_ERROR_NO_AGGREGATION;
+      return (new (this._targetConstructor)).QueryInterface(iid);
+    },
+
+    // nsIFactory
+    lockFactory: function lockFactory(lock) {
+      // No longer used as of gecko 1.7.
+      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    }
+  };
+
+  function OverlayStreamConverter() {}
+  OverlayStreamConverter.prototype = {
+    QueryInterface: XPCOMUtils.generateQI([
+        Ci.nsISupports,
+        Ci.nsIStreamConverter,
+        Ci.nsIStreamListener,
+        Ci.nsIRequestObserver
+    ]),
+
+    classID: Components.ID('{4c6030f7-e20a-264f-0f9b-ada3a9e97384}'),
+    classDescription: 'overlay-test-data Component',
+    contractID: '@mozilla.org/streamconv;1?from=application/x-moz-playpreview&to=*/*',
+
+    // nsIStreamConverter::convert
+    convert: function(aFromStream, aFromType, aToType, aCtxt) {
+      throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+    },
+
+    // nsIStreamConverter::asyncConvertData
+    asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
+      var isValidRequest = false;
+      try {
+        var request = aCtxt;
+        request.QueryInterface(Ci.nsIChannel);
+        var spec = request.URI.spec;
+        var expectedSpec = 'data:application/x-moz-playpreview;,' + mimeType;
+        isValidRequest = (spec == expectedSpec);
+      } catch (e) { }
+      if (!isValidRequest)
+        throw Cr.NS_ERROR_NOT_IMPLEMENTED;
+
+      // Store the listener passed to us
+      this.listener = aListener;
+    },
+
+    // nsIStreamListener::onDataAvailable
+    onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+      // Do nothing since all the data loading is handled by the viewer.
+      ok(false, "onDataAvailable should not be called");
+    },
+
+    // nsIRequestObserver::onStartRequest
+    onStartRequest: function(aRequest, aContext) {
+
+      // Setup the request so we can use it below.
+      aRequest.QueryInterface(Ci.nsIChannel);
+      // Cancel the request so the viewer can handle it.
+      aRequest.cancel(Cr.NS_BINDING_ABORTED);
+
+      // Create a new channel that is viewer loaded as a resource.
+      var ioService = Services.io;
+      var channel = ioService.newChannel(targetUrl, null, null);
+      channel.asyncOpen(this.listener, aContext);
+    },
+
+    // nsIRequestObserver::onStopRequest
+    onStopRequest: function(aRequest, aContext, aStatusCode) {
+      // Do nothing.
+    }
+  };
+
+  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  ph.registerPlayPreviewMimeType(mimeType);
+
+  var factory = new StreamConverterFactory();
+  factory.register(OverlayStreamConverter);
+
+  return (gPlayPreviewRegistration = {
+    unregister: function() {
+      ph.unregisterPlayPreviewMimeType(mimeType);
+      factory.unregister();
+      gPlayPreviewRegistration = null;
+    }
+  });
+}
+
+function unregisterPlayPreview() {
+  gPlayPreviewRegistration.unregister();
+}
+
+Components.utils.import('resource://gre/modules/XPCOMUtils.jsm');
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    if (gPlayPreviewRegistration)
+      gPlayPreviewRegistration.unregister();
+    Services.prefs.clearUserPref("plugins.click_to_play");
+  });
+
+  var newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+  gTestBrowser.addEventListener("PluginPlayPreview", handlePluginPlayPreview, true);
+
+  registerPlayPreview('application/x-test', 'about:');
+  prepareTest(test1a, gTestRoot + "plugin_test.html", 1);
+}
+
+function finishTest() {
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gTestBrowser.removeEventListener("PluginPlayPreview", handlePluginPlayPreview, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function handlePluginPlayPreview() {
+  gPlayPreviewPluginActualEvents++;
+}
+
+function pageLoad() {
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states
+
+  // iframe might triggers load event as well, making sure we skip some to let
+  // all iframes on the page be loaded as well
+  if (gNextTestSkip) {
+    gNextTestSkip--;
+    return;
+  }
+  executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url, skip) {
+  gNextTest = nextTest;
+  gNextTestSkip = skip;
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Tests a page with normal play preview registration (1/2)
+function test1a() {
+  var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
+  ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
+  ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
+
+  var pluginInfo = getTestPlugin();
+  ok(pluginInfo, "Should have a test plugin");
+
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 1a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 1a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 1a, the overlay div is expected");
+
+  var iframe = overlay.getElementsByClassName("previewPluginContentFrame")[0];
+  ok(iframe && iframe.localName == "iframe", "Test 1a, the overlay iframe is expected");
+  var iframeHref = iframe.contentWindow.location.href;
+  ok(iframeHref == "about:", "Test 1a, the overlay about: content is expected");
+
+  var rect = iframe.getBoundingClientRect();
+  ok(rect.width == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px width before being replaced by actual plugin");
+  ok(rect.height == 200, "Test 1a, Plugin with id=" + plugin.id + " overlay rect should have 200px height before being replaced by actual plugin");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, null);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.activated;
+  waitForCondition(condition, test1b, "Test 1a, Waited too long for plugin to stop play preview");
+}
+
+// Tests that activating via MozPlayPlugin through the notification works (part 2/2)
+function test1b() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 1b, Plugin should be activated");
+
+  is(gPlayPreviewPluginActualEvents, gPlayPreviewPluginExpectedEvents,
+     "There should be exactly one PluginPlayPreview event");
+
+  unregisterPlayPreview();
+
+  prepareTest(test2, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- the mime type was just unregistered.
+function test2() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 2, Plugin should be activated");
+
+  registerPlayPreview('application/x-unknown', 'about:');
+
+  prepareTest(test3, gTestRoot + "plugin_test.html");
+}
+
+// Tests a page with a working plugin in it -- diffent play preview type is reserved.
+function test3() {
+  var plugin = gTestBrowser.contentDocument.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 3, Plugin should be activated");
+
+  unregisterPlayPreview();
+
+  registerPlayPreview('application/x-test', 'about:');
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  prepareTest(test4a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a fallback to the click-to-play
+function test4a() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 4a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 4a, the overlay div is expected");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, true);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.pluginFallbackType == Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY;
+  waitForCondition(condition, test4b, "Test 4a, Waited too long for plugin to stop play preview");
+}
+
+function test4b() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.pluginFallbackType != Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 4b, plugin fallback type should not be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 4b, Plugin should not be activated");
+
+  prepareTest(test5a, gTestRoot + "plugin_test.html", 1);
+}
+
+// Test a bypass of the click-to-play
+function test5a() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_PLAY_PREVIEW, "Test 5a, plugin fallback type should be PLUGIN_PLAY_PREVIEW");
+  ok(!objLoadingContent.activated, "Test 5a, Plugin should not be activated");
+
+  var overlay = doc.getAnonymousElementByAttribute(plugin, "class", "previewPluginContent");
+  ok(overlay, "Test 5a, the overlay div is expected");
+
+  var e = overlay.ownerDocument.createEvent("CustomEvent");
+  e.initCustomEvent("MozPlayPlugin", true, true, false);
+  overlay.dispatchEvent(e);
+  var condition = function() objLoadingContent.activated;
+  waitForCondition(condition, test5b, "Test 5a, Waited too long for plugin to stop play preview");
+}
+
+function test5b() {
+  var doc = gTestBrowser.contentDocument;
+  var plugin = doc.getElementById("test");
+  var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(objLoadingContent.activated, "Test 5b, Plugin should be activated");
+
+  finishTest();
+}
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_social_flyout.js
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+  waitForExplicitFinish();
+
+  let manifest = { // normal provider
+    name: "provider 1",
+    origin: "https://example.com",
+    sidebarURL: "https://example.com/browser/browser/base/content/test/social_sidebar.html",
+    workerURL: "https://example.com/browser/browser/base/content/test/social_worker.js",
+    iconURL: "chrome://branding/content/icon48.png"
+  };
+  runSocialTestWithProvider(manifest, function (finishcb) {
+    runSocialTests(tests, undefined, undefined, finishcb);
+  });
+}
+
+var tests = {
+  testOpenCloseFlyout: function(next) {
+    let panel = document.getElementById("social-flyout-panel");
+    let port = Social.provider.port;
+    ok(port, "provider has a port");
+    port.onmessage = function (e) {
+      let topic = e.data.topic;
+      switch (topic) {
+        case "got-sidebar-message":
+          port.postMessage({topic: "test-flyout-open"});
+          break;
+        case "got-flyout-visibility":
+          if (e.data.result == "hidden") {
+            ok(true, "flyout visibility is 'hidden'");
+            next();
+          } else if (e.data.result == "shown") {
+            ok(true, "flyout visibility is 'shown");
+            panel.hidePopup();
+          }
+          break;
+        case "got-flyout-message":
+          ok(e.data.result == "ok", "got flyout message");
+          break;
+      }
+    }
+    port.postMessage({topic: "test-init"});
+  }
+}
+
--- a/browser/base/content/test/browser_social_mozSocial_API.js
+++ b/browser/base/content/test/browser_social_mozSocial_API.js
@@ -37,16 +37,18 @@ var tests = {
 
     let port = Social.provider.port;
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-panel-message":
           ok(true, "got panel message");
+          // Check the panel isn't in our history.
+          ensureSocialUrlNotRemembered(e.data.location);
           break;
         case "got-social-panel-visibility":
           if (e.data.result == "shown") {
             ok(true, "panel shown");
             let panel = document.getElementById("social-notification-panel");
             panel.hidePopup();
           } else if (e.data.result == "hidden") {
             ok(true, "panel hidden");
@@ -89,16 +91,18 @@ var tests = {
     ok(port, "provider has a port");
     port.postMessage({topic: "test-service-window"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-service-window-message":
           // The sidebar message will always come first, since it loads by default
           ok(true, "got service window message");
+          // the service window URL should not be stored in history.
+          ensureSocialUrlNotRemembered(e.data.location);
           port.postMessage({topic: "test-close-service-window"});
           break;
         case "got-service-window-closed-message":
           ok(true, "got service window closed message");
           next();
           break;
       }
     }
--- a/browser/base/content/test/browser_social_shareButton.js
+++ b/browser/base/content/test/browser_social_shareButton.js
@@ -1,17 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let prefName = "social.enabled",
-    shareButton,
-    sharePopup,
-    okButton,
-    undoButton,
     gFinishCB;
 
 function test() {
   waitForExplicitFinish();
 
   // Need to load a non-empty page for the social share button to appear
   let tab = gBrowser.selectedTab = gBrowser.addTab("about:", {skipAnimation: true});
   tab.linkedBrowser.addEventListener("load", function tabLoad(event) {
@@ -41,54 +37,69 @@ function tabLoaded() {
   });
 }
 
 function testInitial(finishcb) {
   ok(Social.provider, "Social provider is active");
   ok(Social.provider.enabled, "Social provider is enabled");
   ok(Social.provider.port, "Social provider has a port to its FrameWorker");
 
-  shareButton = SocialShareButton.shareButton;
-  sharePopup = SocialShareButton.sharePopup;
+  let {shareButton, sharePopup} = SocialShareButton;
   ok(shareButton, "share button exists");
   ok(sharePopup, "share popup exists");
 
-  okButton = document.getElementById("editSharePopupOkButton");
-  undoButton = document.getElementById("editSharePopupUndoButton");
-
-  is(shareButton.hidden, false, "share button should be visible");
+  let okButton = document.getElementById("editSharePopupOkButton");
+  let undoButton = document.getElementById("editSharePopupUndoButton");
 
-  // Test clicking the share button
-  shareButton.addEventListener("click", function listener() {
-    shareButton.removeEventListener("click", listener);
-    is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked");
-    executeSoon(testSecondClick.bind(window, testPopupOKButton));
-  });
-  EventUtils.synthesizeMouseAtCenter(shareButton, {});
+  // ensure the worker initialization and handshakes are all done and we
+  // have a profile.
+  waitForCondition(function() Social.provider.profile, function() {
+    is(shareButton.hidden, false, "share button should be visible");
+    // check dom values
+    let profile = Social.provider.profile;
+    let portrait = document.getElementById("socialUserPortrait").getAttribute("src");
+    is(profile.portrait, portrait, "portrait is set");
+    let displayName = document.getElementById("socialUserDisplayName");
+    is(displayName.label, profile.displayName, "display name is set");
+    ok(!document.getElementById("editSharePopupHeader").hidden, "user profile is visible");
+  
+    // Test clicking the share button
+    shareButton.addEventListener("click", function listener() {
+      shareButton.removeEventListener("click", listener);
+      is(shareButton.hasAttribute("shared"), true, "Share button should have 'shared' attribute after share button is clicked");
+      executeSoon(testSecondClick.bind(window, testPopupOKButton));
+    });
+    EventUtils.synthesizeMouseAtCenter(shareButton, {});
+  }, "provider didn't provide a profile");
 }
 
 function testSecondClick(nextTest) {
+  let {shareButton, sharePopup} = SocialShareButton;
   sharePopup.addEventListener("popupshown", function listener() {
     sharePopup.removeEventListener("popupshown", listener);
     ok(true, "popup was shown after second click");
     executeSoon(nextTest);
   });
   EventUtils.synthesizeMouseAtCenter(shareButton, {});
 }
 
 function testPopupOKButton() {
+  let {shareButton, sharePopup} = SocialShareButton;
+  let okButton = document.getElementById("editSharePopupOkButton");
   sharePopup.addEventListener("popuphidden", function listener() {
     sharePopup.removeEventListener("popuphidden", listener);
     is(shareButton.hasAttribute("shared"), true, "Share button should still have 'shared' attribute after OK button is clicked");
     executeSoon(testSecondClick.bind(window, testPopupUndoButton));
   });
   EventUtils.synthesizeMouseAtCenter(okButton, {});
 }
 
 function testPopupUndoButton() {
+  let {shareButton, sharePopup} = SocialShareButton;
+  let undoButton = document.getElementById("editSharePopupUndoButton");
   sharePopup.addEventListener("popuphidden", function listener() {
     sharePopup.removeEventListener("popuphidden", listener);
     is(shareButton.hasAttribute("shared"), false, "Share button should not have 'shared' attribute after Undo button is clicked");
     executeSoon(testShortcut);
   });
   EventUtils.synthesizeMouseAtCenter(undoButton, {});
 }
 
@@ -97,28 +108,31 @@ function testShortcut() {
   keyTarget.addEventListener("keyup", function listener() {
     keyTarget.removeEventListener("keyup", listener);
     executeSoon(checkShortcutWorked.bind(window, keyTarget));
   });
   EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
 }
 
 function checkShortcutWorked(keyTarget) {
+  let {sharePopup, shareButton} = SocialShareButton;
   is(shareButton.hasAttribute("shared"), true, "Share button should be in the 'shared' state after keyboard shortcut is used");
 
   // Test a second invocation of the shortcut
   sharePopup.addEventListener("popupshown", function listener() {
     sharePopup.removeEventListener("popupshown", listener);
     ok(true, "popup was shown after second use of keyboard shortcut");
     executeSoon(checkOKButton);
   });
   EventUtils.synthesizeKey("l", {accelKey: true, shiftKey: true}, keyTarget);
 }
 
 function checkOKButton() {
+  let okButton = document.getElementById("editSharePopupOkButton");
+  let undoButton = document.getElementById("editSharePopupUndoButton");
   is(document.activeElement, okButton, "ok button should be focused by default");
 
   // This rest of particular test doesn't really apply on Mac, since buttons
   // aren't focusable by default.
   if (navigator.platform.indexOf("Mac") != -1) {
     executeSoon(testCloseBySpace);
     return;
   }
@@ -151,22 +165,24 @@ function checkNextInTabOrder(element, ne
   // Register a cleanup function to remove the listener in case this test fails
   registerCleanupFunction(function () {
     element.removeEventListener("focus", listener);
   });
   EventUtils.synthesizeKey("VK_TAB", {});
 }
 
 function testCloseBySpace() {
-  is(document.activeElement.id, okButton.id, "testCloseBySpace, the ok button should be focused");
+  let sharePopup = SocialShareButton.sharePopup;
+  is(document.activeElement.id, "editSharePopupOkButton", "testCloseBySpace, the ok button should be focused");
   sharePopup.addEventListener("popuphidden", function listener() {
     sharePopup.removeEventListener("popuphidden", listener);
     ok(true, "space closed the share popup");
     executeSoon(testDisable);
   });
   EventUtils.synthesizeKey("VK_SPACE", {});
 }
 
 function testDisable() {
+  let shareButton = SocialShareButton.shareButton;
   Services.prefs.setBoolPref(prefName, false);
   is(shareButton.hidden, true, "Share button should be hidden when pref is disabled");
   gFinishCB();
 }
--- a/browser/base/content/test/head.js
+++ b/browser/base/content/test/head.js
@@ -83,19 +83,50 @@ function waitForCondition(condition, nex
     if (condition()) {
       moveOn();
     }
     tries++;
   }, 100);
   var moveOn = function() { clearInterval(interval); nextTest(); };
 }
 
+// Check that a specified (string) URL hasn't been "remembered" (ie, is not
+// in history, will not appear in about:newtab or auto-complete, etc.)
+function ensureSocialUrlNotRemembered(url) {
+  let gh = Cc["@mozilla.org/browser/global-history;2"]
+           .getService(Ci.nsIGlobalHistory2);
+  let uri = Services.io.newURI(url, null, null);
+  ok(!gh.isVisited(uri), "social URL " + url + " should not be in global history");
+}
+
+function getTestPlugin() {
+  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  var tags = ph.getPluginTags();
+
+  // Find the test plugin
+  for (var i = 0; i < tags.length; i++) {
+    if (tags[i].name == "Test Plug-in")
+      return tags[i];
+  }
+  ok(false, "Unable to find plugin");
+  return null;
+}
+
 function runSocialTestWithProvider(manifest, callback) {
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
+  // Check that none of the provider's content ends up in history.
+  registerCleanupFunction(function () {
+    for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
+      if (manifest[what]) {
+        ensureSocialUrlNotRemembered(manifest[what]);
+      }
+    }
+  });
+
   info("runSocialTestWithProvider: " + manifest.toSource());
 
   let oldProvider;
   SocialService.addProvider(manifest, function(provider) {
     info("runSocialTestWithProvider: provider added");
     oldProvider = Social.provider;
     Social.provider = provider;
 
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/social_flyout.html
@@ -0,0 +1,22 @@
+<html>
+  <head>
+    <meta charset="utf-8">
+    <script>
+      function pingWorker() {
+        var port = navigator.mozSocial.getWorker().port;
+        port.postMessage({topic: "flyout-message", result: "ok"});
+      }
+      window.addEventListener("socialFrameShow", function(e) {
+        var port = navigator.mozSocial.getWorker().port;
+        port.postMessage({topic: "flyout-visibility", result: "shown"});
+      }, false);
+      window.addEventListener("socialFrameHide", function(e) {
+        var port = navigator.mozSocial.getWorker().port;
+        port.postMessage({topic: "flyout-visibility", result: "hidden"});
+      }, false);
+    </script>
+  </head>
+  <body onload="pingWorker();">
+    <p>This is a test social flyout panel.</p>
+  </body>
+</html>
--- a/browser/base/content/test/social_panel.html
+++ b/browser/base/content/test/social_panel.html
@@ -1,15 +1,17 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
       function pingWorker() {
         var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "panel-message", result: "ok"});
+        port.postMessage({topic: "panel-message",
+                          result: "ok",
+                          location: window.location.href});
       }
       window.addEventListener("socialFrameShow", function(e) {
         var port = navigator.mozSocial.getWorker().port;
         port.postMessage({topic: "status-panel-visibility", result: "shown"});
       }, false);
       window.addEventListener("socialFrameHide", function(e) {
         var port = navigator.mozSocial.getWorker().port;
         port.postMessage({topic: "status-panel-visibility", result: "hidden"});
--- a/browser/base/content/test/social_sidebar.html
+++ b/browser/base/content/test/social_sidebar.html
@@ -3,16 +3,19 @@
     <meta charset="utf-8">
     <script>
       var win;
       function pingWorker() {
         var port = navigator.mozSocial.getWorker().port;
         port.onmessage = function(e) {
           var topic = e.data.topic;
           switch (topic) {
+            case "test-flyout-open":
+              navigator.mozSocial.openPanel("social_flyout.html");
+              break;
             case "test-chatbox-open":
               navigator.mozSocial.openChatWindow("social_chat.html", function(chatwin) {
                 port.postMessage({topic: "chatbox-opened", result: chatwin ? "ok" : "failed"});
               });
               break;
             case "test-service-window":
               win = navigator.mozSocial.openServiceWindow("social_window.html", "test-service-window", "width=300,height=300");
               break;
--- a/browser/base/content/test/social_window.html
+++ b/browser/base/content/test/social_window.html
@@ -1,14 +1,17 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
       function pingWorker() {
         var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "service-window-message", result: "ok"});
+        port.postMessage({topic: "service-window-message",
+                          location: window.location.href,
+                          result: "ok"
+                         });
       }
     </script>
   </head>
   <body onload="pingWorker();">
     <p>This is a test social service window.</p>
   </body>
 </html>
--- a/browser/base/content/test/social_worker.js
+++ b/browser/base/content/test/social_worker.js
@@ -13,17 +13,18 @@ onconnect = function(e) {
         testPort = port;
         break;
       case "sidebar-message":
         sidebarPort = port;
         if (testPort && event.data.result == "ok")
           testPort.postMessage({topic:"got-sidebar-message"});
         break;
       case "service-window-message":
-        testPort.postMessage({topic:"got-service-window-message"});
+        testPort.postMessage({topic:"got-service-window-message",
+                              location: event.data.location});
         break;
       case "service-window-closed-message":
         testPort.postMessage({topic:"got-service-window-closed-message"});
         break;
       case "test-service-window":
         sidebarPort.postMessage({topic:"test-service-window"});
         break;
       case "test-service-window-twice":
@@ -32,35 +33,49 @@ onconnect = function(e) {
       case "test-service-window-twice-result":
         testPort.postMessage({topic: "test-service-window-twice-result", result: event.data.result })
         break;
       case "test-close-service-window":
         sidebarPort.postMessage({topic:"test-close-service-window"});
         break;
       case "panel-message":
         if (testPort && event.data.result == "ok")
-          testPort.postMessage({topic:"got-panel-message"});
+          testPort.postMessage({topic:"got-panel-message",
+                                location: event.data.location
+                               });
         break;
       case "status-panel-visibility":
         testPort.postMessage({topic:"got-social-panel-visibility", result: event.data.result });
         break;
       case "test-chatbox-open":
         sidebarPort.postMessage({topic:"test-chatbox-open"});
         break;
       case "chatbox-message":
         testPort.postMessage({topic:"got-chatbox-message", result: event.data.result});
         break;
       case "chatbox-visibility":
         testPort.postMessage({topic:"got-chatbox-visibility", result: event.data.result});
         break;
+      case "test-flyout-open":
+        sidebarPort.postMessage({topic:"test-flyout-open"});
+        break;
+      case "flyout-message":
+        testPort.postMessage({topic:"got-flyout-message", result: event.data.result});
+        break;
+      case "flyout-visibility":
+        testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result});
+        break;
       case "social.initialize":
         // This is the workerAPI port, respond and set up a notification icon.
         port.postMessage({topic: "social.initialize-response"});
         let profile = {
-          userName: "foo"
+          portrait: "https://example.com/portrait.jpg",
+          userName: "trickster",
+          displayName: "Kuma Lisa",
+          profileURL: "http://en.wikipedia.org/wiki/Kuma_Lisa"
         };
         port.postMessage({topic: "social.user-profile", data: profile});
         let icon = {
           name: "testIcon",
           iconURL: "chrome://branding/content/icon48.png",
           contentPanel: "https://example.com/browser/browser/base/content/test/social_panel.html",
           counter: 1
         };
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -10,52 +10,41 @@
 
 <bindings id="downloadBindings"
           xmlns="http://www.mozilla.org/xbl"
           xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
           xmlns:xbl="http://www.mozilla.org/xbl">
 
   <binding id="download"
            extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
-    <resources>
-      <stylesheet src="chrome://browser/skin/downloads/downloads.css"/>
-    </resources>
-    <content orient="horizontal">
-      <xul:hbox class="downloadInfo"
-                align="center"
-                flex="1"
-                onclick="DownloadsView.onDownloadClick(event);">
-        <xul:vbox pack="center">
-          <xul:image class="downloadTypeIcon"
-                     validate="always"
-                     xbl:inherits="src=image"/>
-          <xul:image class="downloadTypeIcon blockedIcon"/>
-        </xul:vbox>
-        <xul:vbox pack="center"
-                  flex="1">
-          <xul:description class="downloadTarget"
-                           crop="center"
-                           xbl:inherits="value=target,tooltiptext=target"/>
-          <xul:progressmeter anonid="progressmeter"
-                             class="downloadProgress"
-                             min="0"
-                             max="100"
-                             xbl:inherits="mode=progressmode,value=progress"/>
-          <xul:description class="downloadDetails"
-                           crop="end"
-                           xbl:inherits="value=status,tooltiptext=statusTip"/>
-        </xul:vbox>
-      </xul:hbox>
-      <xul:hbox class="downloadButtonContainer"
-                align="center">
-        <xul:button class="downloadButton downloadCancel"
-                    tooltiptext="&cmd.cancel.label;"
-                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
-        <xul:button class="downloadButton downloadRetry"
-                    tooltiptext="&cmd.retry.label;"
-                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
-        <xul:button class="downloadButton downloadShow"
-                    tooltiptext="&cmd.show.label;"
-                    oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
-      </xul:hbox>
+    <content orient="horizontal"
+             align="center"
+             onclick="DownloadsView.onDownloadClick(event);">
+      <xul:image class="downloadTypeIcon"
+                 validate="always"
+                 xbl:inherits="src=image"/>
+      <xul:image class="downloadTypeIcon blockedIcon"/>
+      <xul:vbox pack="center"
+                flex="1">
+        <xul:description class="downloadTarget"
+                         crop="center"
+                         xbl:inherits="value=target,tooltiptext=target"/>
+        <xul:progressmeter anonid="progressmeter"
+                           class="downloadProgress"
+                           min="0"
+                           max="100"
+                           xbl:inherits="mode=progressmode,value=progress"/>
+        <xul:description class="downloadDetails"
+                         crop="end"
+                         xbl:inherits="value=status,tooltiptext=statusTip"/>
+      </xul:vbox>
+      <xul:button class="downloadButton downloadCancel"
+                  tooltiptext="&cmd.cancel.label;"
+                  oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_cancel');"/>
+      <xul:button class="downloadButton downloadRetry"
+                  tooltiptext="&cmd.retry.label;"
+                  oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_retry');"/>
+      <xul:button class="downloadButton downloadShow"
+                  tooltiptext="&cmd.show.label;"
+                  oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_show');"/>
     </content>
   </binding>
 </bindings>
--- a/browser/components/downloads/content/downloads.js
+++ b/browser/components/downloads/content/downloads.js
@@ -255,22 +255,21 @@ const DownloadsPanel = {
   //////////////////////////////////////////////////////////////////////////////
   //// Related operations
 
   /**
    * Shows or focuses the user interface dedicated to downloads history.
    */
   showDownloadsHistory: function DP_showDownloadsHistory()
   {
-    // Hide the panel before invoking the Library window, otherwise focus will
-    // return to the browser window when the panel closes automatically.
+    // Hide the panel before showing another window, otherwise focus will return
+    // to the browser window when the panel closes automatically.
     this.hidePanel();
 
-    // Open the Library window and select the Downloads query.
-    PlacesCommandHook.showPlacesOrganizer("Downloads");
+    BrowserDownloadsUI();
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Internal functions
 
   /**
    * Move focus to the main element in the downloads panel, unless another
    * element in the panel is already focused.
@@ -351,17 +350,17 @@ const DownloadsOverlayLoader = {
   _loadedOverlays: {},
 
   /**
    * Loads the specified overlay and invokes the given callback when finished.
    *
    * @param aOverlay
    *        String containing the URI of the overlay to load in the current
    *        window.  If this overlay has already been loaded using this
-   *        function, then the overlay is not loaded again. 
+   *        function, then the overlay is not loaded again.
    * @param aCallback
    *        Invoked when loading is completed.  If the overlay is already
    *        loaded, the function is called immediately.
    */
   ensureOverlayLoaded: function DOL_ensureOverlayLoaded(aOverlay, aCallback)
   {
     // The overlay is already loaded, invoke the callback immediately.
     if (aOverlay in this._loadedOverlays) {
@@ -420,47 +419,79 @@ const DownloadsOverlayLoader = {
  * download state and real-time data.  In addition, handles part of the user
  * interaction events raised by the downloads list widget.
  */
 const DownloadsView = {
   //////////////////////////////////////////////////////////////////////////////
   //// Functions handling download items in the list
 
   /**
+   * Maximum number of items shown by the list at any given time.
+   */
+  kItemCountLimit: 3,
+
+  /**
    * Indicates whether we are still loading downloads data asynchronously.
    */
   loading: false,
 
   /**
-   * Object containing all the available DownloadsViewItem objects, indexed by
-   * their numeric download identifier.
+   * Ordered array of all DownloadsDataItem objects.  We need to keep this array
+   * because only a limited number of items are shown at once, and if an item
+   * that is currently visible is removed from the list, we might need to take
+   * another item from the array and make it appear at the bottom.
+   */
+  _dataItems: [],
+
+  /**
+   * Object containing the available DownloadsViewItem objects, indexed by their
+   * numeric download identifier.  There is a limited number of view items in
+   * the panel at any given time.
    */
   _viewItems: {},
 
   /**
    * Called when the number of items in the list changes.
    */
   _itemCountChanged: function DV_itemCountChanged()
   {
-    if (Object.keys(this._viewItems).length > 0) {
+    let count = this._dataItems.length;
+    let hiddenCount = count - this.kItemCountLimit;
+
+    if (count > 0) {
       DownloadsPanel.panel.setAttribute("hasdownloads", "true");
     } else {
       DownloadsPanel.panel.removeAttribute("hasdownloads");
     }
+
+    let s = DownloadsCommon.strings;
+    this.downloadsHistory.label = (hiddenCount > 0)
+                                  ? s.showMoreDownloads(hiddenCount)
+                                  : s.showAllDownloads;
+    this.downloadsHistory.accessKey = s.showDownloadsAccessKey;
   },
 
   /**
    * Element corresponding to the list of downloads.
    */
   get richListBox()
   {
     delete this.richListBox;
     return this.richListBox = document.getElementById("downloadsListBox");
   },
 
+  /**
+   * Element corresponding to the button for showing more downloads.
+   */
+  get downloadsHistory()
+  {
+    delete this.downloadsHistory;
+    return this.downloadsHistory = document.getElementById("downloadsHistory");
+  },
+
   //////////////////////////////////////////////////////////////////////////////
   //// Callback functions from DownloadsData
 
   /**
    * Called before multiple downloads are about to be loaded.
    */
   onDataLoadStarting: function DV_onDataLoadStarting()
   {
@@ -469,16 +500,20 @@ const DownloadsView = {
 
   /**
    * Called after data loading finished.
    */
   onDataLoadCompleted: function DV_onDataLoadCompleted()
   {
     this.loading = false;
 
+    // We suppressed item count change notifications during the batch load, at
+    // this point we should just call the function once.
+    this._itemCountChanged();
+
     // Notify the panel that all the initially available downloads have been
     // loaded.  This ensures that the interface is visible, if still required.
     DownloadsPanel.onViewLoadCompleted();
   },
 
   /**
    * Called when the downloads database becomes unavailable (for example,
    * entering Private Browsing Mode).  References to existing data should be
@@ -488,16 +523,17 @@ const DownloadsView = {
   {
     DownloadsPanel.terminate();
 
     // Clear the list by replacing with a shallow copy.
     let emptyView = this.richListBox.cloneNode(false);
     this.richListBox.parentNode.replaceChild(emptyView, this.richListBox);
     this.richListBox = emptyView;
     this._viewItems = {};
+    this._dataItems = [];
   },
 
   /**
    * Called when a new download data item is available, either during the
    * asynchronous data load or when a new download is started.
    *
    * @param aDataItem
    *        DownloadsDataItem object that was just added.
@@ -505,59 +541,119 @@ const DownloadsView = {
    *        When true, indicates that this item is the most recent and should be
    *        added in the topmost position.  This happens when a new download is
    *        started.  When false, indicates that the item is the least recent
    *        and should be appended.  The latter generally happens during the
    *        asynchronous data load.
    */
   onDataItemAdded: function DV_onDataItemAdded(aDataItem, aNewest)
   {
-    // Make the item and add it in the appropriate place in the list.
-    let element = document.createElement("richlistitem");
-    let viewItem = new DownloadsViewItem(aDataItem, element);
-    this._viewItems[aDataItem.downloadId] = viewItem;
     if (aNewest) {
-      this.richListBox.insertBefore(element, this.richListBox.firstChild);
+      this._dataItems.unshift(aDataItem);
     } else {
-      this.richListBox.appendChild(element);
+      this._dataItems.push(aDataItem);
     }
 
-    this._itemCountChanged();
+    let itemsNowOverflow = this._dataItems.length > this.kItemCountLimit;
+    if (aNewest || !itemsNowOverflow) {
+      // The newly added item is visible in the panel and we must add the
+      // corresponding element.  This is either because it is the first item, or
+      // because it was added at the bottom but the list still doesn't overflow.
+      this._addViewItem(aDataItem, aNewest);
+    }
+    if (aNewest && itemsNowOverflow) {
+      // If the list overflows, remove the last item from the panel to make room
+      // for the new one that we just added at the top.
+      this._removeViewItem(this._dataItems[this.kItemCountLimit]);
+    }
+
+    // For better performance during batch loads, don't update the count for
+    // every item, because the interface won't be visible until load finishes.
+    if (!this.loading) {
+      this._itemCountChanged();
+    }
   },
 
   /**
    * Called when a data item is removed.  Ensures that the widget associated
    * with the view item is removed from the user interface.
    *
    * @param aDataItem
    *        DownloadsDataItem object that is being removed.
    */
   onDataItemRemoved: function DV_onDataItemRemoved(aDataItem)
   {
-    let element = this.getViewItem(aDataItem)._element;
-    let previousSelectedIndex = this.richListBox.selectedIndex;
-    this.richListBox.removeChild(element);
-    this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
-                                              this.richListBox.itemCount - 1);
-    delete this._viewItems[aDataItem.downloadId];
+    let itemIndex = this._dataItems.indexOf(aDataItem);
+    this._dataItems.splice(itemIndex, 1);
+
+    if (itemIndex < this.kItemCountLimit) {
+      // The item to remove is visible in the panel.
+      this._removeViewItem(aDataItem);
+      if (this._dataItems.length >= this.kItemCountLimit) {
+        // Reinsert the next item into the panel.
+        this._addViewItem(this._dataItems[this.kItemCountLimit - 1], false);
+      }
+    }
 
     this._itemCountChanged();
   },
 
   /**
    * Returns the view item associated with the provided data item for this view.
    *
    * @param aDataItem
    *        DownloadsDataItem object for which the view item is requested.
    *
    * @return Object that can be used to notify item status events.
    */
   getViewItem: function DV_getViewItem(aDataItem)
   {
-    return this._viewItems[aDataItem.downloadId];
+    // If the item is visible, just return it, otherwise return a mock object
+    // that doesn't react to notifications.
+    if (aDataItem.downloadId in this._viewItems) {
+      return this._viewItems[aDataItem.downloadId];
+    }
+    return this._invisibleViewItem;
+  },
+
+  /**
+   * Mock DownloadsDataItem object that doesn't react to notifications.
+   */
+  _invisibleViewItem: Object.freeze({
+    onStateChange: function () { },
+    onProgressChange: function () { }
+  }),
+
+  /**
+   * Creates a new view item associated with the specified data item, and adds
+   * it to the top or the bottom of the list.
+   */
+  _addViewItem: function DV_addViewItem(aDataItem, aNewest)
+  {
+    let element = document.createElement("richlistitem");
+    let viewItem = new DownloadsViewItem(aDataItem, element);
+    this._viewItems[aDataItem.downloadId] = viewItem;
+    if (aNewest) {
+      this.richListBox.insertBefore(element, this.richListBox.firstChild);
+    } else {
+      this.richListBox.appendChild(element);
+    }
+  },
+
+  /**
+   * Removes the view item associated with the specified data item.
+   */
+  _removeViewItem: function DV_removeViewItem(aDataItem)
+  {
+    let element = this.getViewItem(aDataItem)._element;
+    let previousSelectedIndex = this.richListBox.selectedIndex;
+    this.richListBox.removeChild(element);
+    this.richListBox.selectedIndex = Math.min(previousSelectedIndex,
+                                              this.richListBox.itemCount - 1);
+    delete this._viewItems[aDataItem.downloadId];
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   /**
    * Helper function to do commands on a specific download item.
    *
@@ -574,18 +670,19 @@ const DownloadsView = {
     while (target.nodeName != "richlistitem") {
       target = target.parentNode;
     }
     new DownloadsViewItemController(target).doCommand(aCommand);
   },
 
   onDownloadClick: function DV_onDownloadClick(aEvent)
   {
-    // Handle primary clicks only.
-    if (aEvent.button == 0) {
+    // Handle primary clicks only, and exclude the action button.
+    if (aEvent.button == 0 &&
+        !aEvent.originalTarget.hasAttribute("oncommand")) {
       goDoCommand("downloadsCmd_open");
     }
   },
 
   onDownloadKeyPress: function DV_onDownloadKeyPress(aEvent)
   {
     // Handle unmodified keys only.
     if (aEvent.altKey || aEvent.ctrlKey || aEvent.shiftKey || aEvent.metaKey) {
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -103,14 +103,12 @@
                    flex="1"
                    context="downloadsContextMenu"
                    onkeypress="DownloadsView.onDownloadKeyPress(event);"
                    oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                    ondragstart="DownloadsView.onDownloadDragStart(event);"/>
 
       <button id="downloadsHistory"
               class="plain"
-              label="&downloadshistory.label;"
-              accesskey="&downloadshistory.accesskey;"
               oncommand="DownloadsPanel.showDownloadsHistory();"/>
     </panel>
   </popupset>
 </overlay>
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -48,40 +48,16 @@ const DownloadsButton = {
    * if not available because it has been removed from the toolbars.
    */
   get _placeholder()
   {
     return document.getElementById("downloads-button");
   },
 
   /**
-   * This function is called synchronously at window initialization.  It only
-   * sets the visibility of user interface elements to avoid flickering.
-   *
-   * NOTE: To keep startup time to a minimum, this function should not perform
-   *       any expensive operations or input/output, and should not cause the
-   *       Download Manager service to start.
-   */
-  initializePlaceholder: function DB_initializePlaceholder()
-  {
-    // Exit now if the feature is disabled.  To improve startup time, we don't
-    // load the DownloadsCommon module yet, but check the preference directly.
-    if (gPrefService.getBoolPref("browser.download.useToolkitUI")) {
-      return;
-    }
-
-    // We must hide the placeholder used for toolbar customization, unless it
-    // has been removed from the toolbars and is now located in the palette.
-    let placeholder = this._placeholder;
-    if (placeholder) {
-      placeholder.collapsed = true;
-    }
-  },
-
-  /**
    * This function is called asynchronously just after window initialization.
    *
    * NOTE: This function should limit the input/output it performs to improve
    *       startup time, and in particular should not cause the Download Manager
    *       service to start.
    */
   initializeIndicator: function DB_initializeIndicator()
   {
@@ -136,22 +112,18 @@ const DownloadsButton = {
    *
    * NOTE: This function is also called on startup, thus it should limit the
    *       input/output it performs, and in particular should not cause the
    *       Download Manager service to start.
    */
   _update: function DB_update() {
     this._updatePositionInternal();
 
-    let placeholder = this._placeholder;
     if (!DownloadsCommon.useToolkitUI) {
       DownloadsIndicatorView.ensureInitialized();
-      if (placeholder) {
-        placeholder.collapsed = true;
-      }
     } else {
       DownloadsIndicatorView.ensureTerminated();
     }
   },
 
   /**
    * Determines the position where the indicator should appear, and moves its
    * associated element to the new position.  This does not happen if the
@@ -176,57 +148,49 @@ const DownloadsButton = {
   {
     let indicator = DownloadsIndicatorView.indicator;
     if (!indicator) {
       // Exit now if the indicator overlay isn't loaded yet.
       return null;
     }
 
     let placeholder = this._placeholder;
-
-    // Firstly, determine if we should always hide the indicator.
-    if (!placeholder && !this._anchorRequested &&
-        !DownloadsIndicatorView.hasDownloads) {
+    if (!placeholder) {
+      // The placeholder has been removed from the browser window.
       indicator.collapsed = true;
       return null;
     }
+
+    // Position the indicator where the placeholder is located.  We should
+    // update the position even if the placeholder is located on an invisible
+    // toolbar, because the toolbar may be displayed later.
+    placeholder.parentNode.insertBefore(indicator, placeholder);
+    placeholder.collapsed = true;
     indicator.collapsed = false;
 
     indicator.open = this._anchorRequested;
 
-    // Determine if we should display the indicator in a known position.
-    if (placeholder) {
-      placeholder.parentNode.insertBefore(indicator, placeholder);
-      // Determine if the placeholder is located on a visible toolbar.
-      if (isElementVisible(placeholder.parentNode)) {
-        return DownloadsIndicatorView.indicatorAnchor;
-      }
-    }
-
-    // If not customized, the indicator is normally in the navigation bar.
-    // Always place it in the default position, unless we need an anchor.
-    if (!this._anchorRequested) {
-      this._navBar.appendChild(indicator);
+    // Determine if the placeholder is located on an invisible toolbar.
+    if (!isElementVisible(placeholder.parentNode)) {
       return null;
     }
 
-    // Show the indicator temporarily in the navigation bar, if visible.
-    if (isElementVisible(this._navBar)) {
-      this._navBar.appendChild(indicator);
-      return DownloadsIndicatorView.indicatorAnchor;
-    }
+    return DownloadsIndicatorView.indicatorAnchor;
+  },
 
-    // Show the indicator temporarily in the tab bar, if visible.
-    if (!this._tabsToolbar.collapsed) {
-      this._tabsToolbar.appendChild(indicator);
-      return DownloadsIndicatorView.indicatorAnchor;
+  /**
+   * Indicates whether the indicator is visible in the browser window.
+   */
+  get isVisible()
+  {
+    if (!this._placeholder) {
+      return false;
     }
-
-    // The temporary anchor cannot be shown.
-    return null;
+    let element = DownloadsIndicatorView.indicator || this._placeholder;
+    return isElementVisible(element.parentNode);
   },
 
   /**
    * Indicates whether we should try and show the indicator temporarily as an
    * anchor for the panel, even if the indicator would be hidden by default.
    */
   _anchorRequested: false,
 
@@ -379,16 +343,20 @@ const DownloadsIndicatorView = {
       return;
     }
 
     function DIV_SEN_callback() {
       if (this._notificationTimeout) {
         clearTimeout(this._notificationTimeout);
       }
 
+      // Now that the overlay is loaded, place the indicator in its final
+      // position.
+      DownloadsButton.updatePosition();
+
       let indicator = this.indicator;
       indicator.setAttribute("notification", "true");
       this._notificationTimeout = setTimeout(
         function () indicator.removeAttribute("notification"), 1000);
     }
 
     this._ensureOperational(DIV_SEN_callback.bind(this));
   },
@@ -525,17 +493,17 @@ const DownloadsIndicatorView = {
 
   onCommand: function DIV_onCommand(aEvent)
   {
     if (DownloadsCommon.useToolkitUI) {
       // The panel won't suppress attention for us, we need to clear now.
       DownloadsCommon.indicatorData.attention = false;
     }
 
-    BrowserDownloadsUI();
+    DownloadsPanel.showPanel();
 
     aEvent.stopPropagation();
   },
 
   onDragOver: function DIV_onDragOver(aEvent)
   {
     browserDragAndDrop.dragOver(aEvent);
   },
--- a/browser/components/downloads/src/DownloadsCommon.jsm
+++ b/browser/components/downloads/src/DownloadsCommon.jsm
@@ -47,16 +47,18 @@ const Cr = Components.results;
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gBrowserGlue",
                                    "@mozilla.org/browser/browserglue;1",
                                    "nsIBrowserGlue");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
+                                  "resource://gre/modules/PluralForm.jsm");
 
 const nsIDM = Ci.nsIDownloadManager;
 
 const kDownloadsStringBundleUrl =
   "chrome://browser/locale/downloads/downloads.properties";
 
 const kDownloadsStringsRequiringFormatting = {
   sizeWithUnits: true,
@@ -64,16 +66,20 @@ const kDownloadsStringsRequiringFormatti
   shortTimeLeftMinutes: true,
   shortTimeLeftHours: true,
   shortTimeLeftDays: true,
   statusSeparator: true,
   statusSeparatorBeforeNumber: true,
   fileExecutableSecurityWarning: true
 };
 
+const kDownloadsStringsRequiringPluralForm = {
+  showMoreDownloads: true
+};
+
 XPCOMUtils.defineLazyGetter(this, "DownloadsLocalFileCtor", function () {
   return Components.Constructor("@mozilla.org/file/local;1",
                                 "nsILocalFile", "initWithPath");
 });
 
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsCommon
 
@@ -97,16 +103,24 @@ const DownloadsCommon = {
       let stringName = string.key;
       if (stringName in kDownloadsStringsRequiringFormatting) {
         strings[stringName] = function () {
           // Convert "arguments" to a real array before calling into XPCOM.
           return sb.formatStringFromName(stringName,
                                          Array.slice(arguments, 0),
                                          arguments.length);
         };
+      } else if (stringName in kDownloadsStringsRequiringPluralForm) {
+        strings[stringName] = function (aCount) {
+          // Convert "arguments" to a real array before calling into XPCOM.
+          let formattedString = sb.formatStringFromName(stringName,
+                                         Array.slice(arguments, 0),
+                                         arguments.length);
+          return PluralForm.get(aCount, formattedString);
+        };
       } else {
         strings[stringName] = string.value;
       }
     }
     delete this.strings;
     return this.strings = strings;
   },
 
@@ -430,33 +444,36 @@ const DownloadsData = {
 
     if (aActiveOnly) {
       if (this._loadState == this.kLoadNone) {
         // Indicate to the views that a batch loading operation is in progress.
         this._views.forEach(
           function (view) view.onDataLoadStarting()
         );
 
-        // Reload the list using the Download Manager service.
+        // Reload the list using the Download Manager service.  The list is
+        // returned in no particular order.
         let downloads = Services.downloads.activeDownloads;
         while (downloads.hasMoreElements()) {
           let download = downloads.getNext().QueryInterface(Ci.nsIDownload);
           this._getOrAddDataItem(download, true);
         }
         this._loadState = this.kLoadActive;
 
         // Indicate to the views that the batch loading operation is complete.
         this._views.forEach(
           function (view) view.onDataLoadCompleted()
         );
       }
     } else {
       if (this._loadState != this.kLoadAll) {
         // Load only the relevant columns from the downloads database.  The
-        // columns are read in the init_FromDataRow method of DownloadsDataItem.
+        // columns are read in the _initFromDataRow method of DownloadsDataItem.
+        // Order by descending download identifier so that the most recent
+        // downloads are notified first to the listening views.
         let statement = Services.downloads.DBConnection.createAsyncStatement(
           "SELECT id, target, name, source, referrer, state, "
         +        "startTime, endTime, currBytes, maxBytes "
         + "FROM moz_downloads "
         + "ORDER BY id DESC"
         );
         try {
           this._pendingStatement = statement.executeAsync(this);
--- a/browser/components/downloads/src/DownloadsUI.js
+++ b/browser/components/downloads/src/DownloadsUI.js
@@ -63,62 +63,32 @@ DownloadsUI.prototype = {
       return;
     }
 
     if (!aReason) {
       aReason = Ci.nsIDownloadManagerUI.REASON_USER_INTERACTED;
     }
 
     if (aReason == Ci.nsIDownloadManagerUI.REASON_NEW_DOWNLOAD) {
-      // New download notifications are already handled by the panel service.
-      // We don't handle them here because we don't want them to depend on the
-      // "browser.download.manager.showWhenStarting" and
-      // "browser.download.manager.focusWhenStarting" preferences.
-      return;
+      // If the indicator is visible, then new download notifications are
+      // already handled by the panel service.
+      let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
+      if (browserWin &&
+          browserWin.windowState != Ci.nsIDOMChromeWindow.STATE_MINIMIZED &&
+          browserWin.DownloadsButton.isVisible) {
+        return;
+      }
     }
 
-    // Show the panel in the most recent browser window, if present.
-    let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
-    if (browserWin) {
-      // The most recent browser window could have been minimized, in that case
-      // it must be restored to allow the panel to open properly.
-      if (browserWin.windowState == Ci.nsIDOMChromeWindow.STATE_MINIMIZED) {
-        browserWin.restore();
-      }
-      browserWin.focus();
-      browserWin.DownloadsPanel.showPanel();
-      return;
-    }
-
-    // If no browser window is visible and the user requested to show the
-    // current downloads, try and open a new window.  We'll open the panel when
-    // delayed loading is finished.
-    Services.obs.addObserver(function DUIO_observe(aSubject, aTopic, aData) {
-      Services.obs.removeObserver(DUIO_observe, aTopic);
-      aSubject.DownloadsPanel.showPanel();
-    }, "browser-delayed-startup-finished", false);
-
-    // We must really build an empty arguments list for the new window.
-    let windowFirstArg = Cc["@mozilla.org/supports-string;1"]
-                         .createInstance(Ci.nsISupportsString);
-    let windowArgs = Cc["@mozilla.org/supports-array;1"]
-                     .createInstance(Ci.nsISupportsArray);
-    windowArgs.AppendElement(windowFirstArg);
-    Services.ww.openWindow(null, "chrome://browser/content/browser.xul",
-                           null, "chrome,dialog=no,all", windowArgs);
+    this._toolkitUI.show(aWindowContext, aID, aReason);
   },
 
   get visible()
   {
-    if (DownloadsCommon.useToolkitUI) {
-      return this._toolkitUI.visible;
-    }
-
-    let browserWin = gBrowserGlue.getMostRecentBrowserWindow();
-    return browserWin ? browserWin.DownloadsPanel.isPanelShowing : false;
+    return this._toolkitUI.visible;
   },
 
   getAttention: function DUI_getAttention()
   {
     if (DownloadsCommon.useToolkitUI) {
       this._toolkitUI.getAttention();
     }
   }
--- a/browser/components/downloads/test/browser/browser_basic_functionality.js
+++ b/browser/components/downloads/test/browser/browser_basic_functionality.js
@@ -19,16 +19,23 @@ function gen_test()
     { endTime: 1180493839859234, state: nsIDM.DOWNLOAD_FINISHED },
     { endTime: 1180493839859233, state: nsIDM.DOWNLOAD_FAILED },
     { endTime: 1180493839859232, state: nsIDM.DOWNLOAD_CANCELED },
     { endTime: 1180493839859231, state: nsIDM.DOWNLOAD_BLOCKED_PARENTAL },
     { endTime: 1180493839859230, state: nsIDM.DOWNLOAD_DIRTY },
     { endTime: 1180493839859229, state: nsIDM.DOWNLOAD_BLOCKED_POLICY },
   ];
 
+  // For testing purposes, show all the download items at once.
+  var originalCountLimit = DownloadsView.kItemCountLimit;
+  DownloadsView.kItemCountLimit = DownloadData.length;
+  registerCleanupFunction(function () {
+    DownloadsView.kItemCountLimit = originalCountLimit;
+  });
+
   try {
     // Ensure that state is reset in case previous tests didn't finish.
     for (let yy in gen_resetState()) yield;
 
     // Populate the downloads database with the data required by this test.
     for (let yy in gen_addDownloadRows(DownloadData)) yield;
 
     // Open the user interface and wait for data to be fully loaded.
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1180,17 +1180,17 @@ BrowserGlue.prototype = {
     var notifyBox = browser.getNotificationBox();
     var notification = notifyBox.appendNotification(text, title, null,
                                                     notifyBox.PRIORITY_CRITICAL_MEDIUM,
                                                     buttons);
     notification.persistence = -1; // Until user closes it
   },
 
   _migrateUI: function BG__migrateUI() {
-    const UI_VERSION = 6;
+    const UI_VERSION = 7;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul#";
     let currentUIVersion = 0;
     try {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } catch(ex) {}
     if (currentUIVersion >= UI_VERSION)
       return;
 
@@ -1278,16 +1278,53 @@ BrowserGlue.prototype = {
       // convert tabsontop attribute to pref
       let toolboxResource = this._rdf.GetResource(BROWSER_DOCURL + "navigator-toolbox");
       let tabsOnTopResource = this._rdf.GetResource("tabsontop");
       let tabsOnTopAttribute = this._getPersist(toolboxResource, tabsOnTopResource);
       if (tabsOnTopAttribute)
         Services.prefs.setBoolPref("browser.tabs.onTop", tabsOnTopAttribute == "true");
     }
 
+    // This migration step is executed only if the Downloads Panel feature is
+    // enabled.  By default, the feature is enabled only in the Nightly channel.
+    // This means that, unless the preference that enables the feature is
+    // changed manually, the Downloads button is added to the toolbar only if
+    // migration happens while running a build from the Nightly channel.  This
+    // migration code will be updated when the feature will be enabled on all
+    // channels, see bug 748381 for details.
+    if (currentUIVersion < 7 &&
+        !Services.prefs.getBoolPref("browser.download.useToolkitUI")) {
+      // This code adds the customizable downloads buttons.
+      let currentsetResource = this._rdf.GetResource("currentset");
+      let toolbarResource = this._rdf.GetResource(BROWSER_DOCURL + "nav-bar");
+      let currentset = this._getPersist(toolbarResource, currentsetResource);
+
+      // Since the Downloads button is located in the navigation bar by default,
+      // migration needs to happen only if the toolbar was customized using a
+      // previous UI version, and the button was not already placed on the
+      // toolbar manually.
+      if (currentset &&
+          currentset.indexOf("downloads-button") == -1) {
+        // The element is added either after the search bar or before the home
+        // button. As a last resort, the element is added just before the
+        // non-customizable window controls.
+        if (currentset.indexOf("search-container") != -1) {
+          currentset = currentset.replace(/(^|,)search-container($|,)/,
+                                          "$1search-container,downloads-button$2")
+        } else if (currentset.indexOf("home-button") != -1) {
+          currentset = currentset.replace(/(^|,)home-button($|,)/,
+                                          "$1downloads-button,home-button$2")
+        } else {
+          currentset = currentset.replace(/(^|,)window-controls($|,)/,
+                                          "$1downloads-button,window-controls$2")
+        }
+        this._setPersist(toolbarResource, currentsetResource, currentset);
+      }
+    }
+
     if (this._dirty)
       this._dataSource.QueryInterface(Ci.nsIRDFRemoteDataSource).Flush();
 
     delete this._rdf;
     delete this._dataSource;
 
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
--- a/browser/components/preferences/main.js
+++ b/browser/components/preferences/main.js
@@ -18,35 +18,22 @@ var gMainPane = {
     this._pane = document.getElementById("paneMain");
 
     // set up the "use current page" label-changing listener
     this._updateUseCurrentButton();
     window.addEventListener("focus", this._updateUseCurrentButton.bind(this), false);
 
     this.updateBrowserStartupLastSession();
 
-    this.setupDownloadsWindowOptions();
-
     // Notify observers that the UI is now ready
     Components.classes["@mozilla.org/observer-service;1"]
               .getService(Components.interfaces.nsIObserverService)
               .notifyObservers(window, "main-pane-loaded", null);
   },
 
-  setupDownloadsWindowOptions: function ()
-  {
-    var showWhenDownloading = document.getElementById("showWhenDownloading");
-    var closeWhenDone = document.getElementById("closeWhenDone");
-
-    // These radio-buttons should not be visible if we have enabled the Downloads Panel.
-    let shouldHide = !DownloadsCommon.useToolkitUI;
-    showWhenDownloading.hidden = shouldHide;
-    closeWhenDone.hidden = shouldHide;
-  },
-
   // HOME PAGE
 
   /*
    * Preferences:
    *
    * browser.startup.homepage
    * - the user's home page, as a string; if the home page is a set of tabs,
    *   this will be those URLs separated by the pipe character "|"
--- a/browser/components/thumbnails/PageThumbs.jsm
+++ b/browser/components/thumbnails/PageThumbs.jsm
@@ -480,17 +480,17 @@ let PageThumbsWorker = {
   _callbacks: [],
 
   /**
    * Get the worker, spawning it if necessary.
    * Code of the worker is in companion file PageThumbsWorker.js
    */
   get _worker() {
     delete this._worker;
-    this._worker = new ChromeWorker("resource://gre/modules/PageThumbsWorker.js");
+    this._worker = new ChromeWorker("resource:///modules/PageThumbsWorker.js");
     this._worker.addEventListener("message", this);
     return this._worker;
   },
 
   /**
    * Post a message to the dedicated thread, registering a callback
    * to be executed once the reply has been received.
    *
--- a/browser/components/thumbnails/PageThumbsWorker.js
+++ b/browser/components/thumbnails/PageThumbsWorker.js
@@ -14,16 +14,19 @@
 importScripts("resource://gre/modules/osfile.jsm");
 
 let PageThumbsWorker = {
   handleMessage: function Worker_handleMessage(aEvent) {
     let msg = aEvent.data;
     let data = {result: null, data: null};
 
     switch (msg.type) {
+      case "removeFile":
+        data.result = this.removeFile(msg);
+        break;
       case "removeFiles":
         data.result = this.removeFiles(msg);
         break;
       case "getFilesInDirectory":
         data.result = this.getFilesInDirectory(msg);
         break;
       default:
         data.result = false;
@@ -43,16 +46,25 @@ let PageThumbsWorker = {
         entries.push(entry.name);
       }
     }
 
     iter.close();
     return entries;
   },
 
+  removeFile: function Worker_removeFile(msg) {
+    try {
+      OS.File.remove(msg.path);
+      return true;
+    } catch (e) {
+      return false;
+    }
+  },
+
   removeFiles: function Worker_removeFiles(msg) {
     for (let file of msg.paths) {
       try {
         OS.File.remove(file);
       } catch (e) {
         // We couldn't remove the file for some reason.
         // Let's just continue with the next one.
       }
--- a/browser/components/thumbnails/test/browser_thumbnails_storage.js
+++ b/browser/components/thumbnails/test/browser_thumbnails_storage.js
@@ -14,42 +14,52 @@ XPCOMUtils.defineLazyGetter(this, "Sanit
 
 /**
  * These tests ensure that the thumbnail storage is working as intended.
  * Newly captured thumbnails should be saved as files and they should as well
  * be removed when the user sanitizes their history.
  */
 function runTests() {
   yield clearHistory();
-
-  // create a thumbnail
-  yield addTab(URL);
-  yield whenFileExists();
-  gBrowser.removeTab(gBrowser.selectedTab);
+  yield createThumbnail();
 
-  // clear all browser history
-  yield clearHistory();
-
-  // create a thumbnail
-  yield addTab(URL);
-  yield whenFileExists();
-  gBrowser.removeTab(gBrowser.selectedTab);
-
-  // make sure copy() updates an existing file
+  // Make sure Storage.copy() updates an existing file.
   PageThumbsStorage.copy(URL, URL_COPY);
   let copy = PageThumbsStorage.getFileForURL(URL_COPY);
   let mtime = copy.lastModifiedTime -= 60;
 
   PageThumbsStorage.copy(URL, URL_COPY);
   isnot(PageThumbsStorage.getFileForURL(URL_COPY).lastModifiedTime, mtime,
         "thumbnail file was updated");
 
-  // clear last 10 mins of history
+  let file = PageThumbsStorage.getFileForURL(URL);
+  let fileCopy = PageThumbsStorage.getFileForURL(URL_COPY);
+
+  // Clear the browser history. Retry until the files are gone because Windows
+  // locks them sometimes.
+  while (file.exists() || fileCopy.exists()) {
+    yield clearHistory();
+  }
+
+  yield createThumbnail();
+
+  // Clear the last 10 minutes of browsing history.
   yield clearHistory(true);
-  ok(!copy.exists(), "copy of thumbnail has been removed");
+
+  // Retry until the file is gone because Windows locks it sometimes.
+  while (file.exists()) {
+    // Re-add our URL to the history so that history observer's onDeleteURI()
+    // is called again.
+    let time = Date.now() * 1000;
+    let trans = Ci.nsINavHistoryService.TRANSITION_LINK;
+    PlacesUtils.history.addVisit(makeURI(URL), time, null, trans, false, 0);
+
+    // Try again...
+    yield clearHistory(true);
+  }
 }
 
 function clearHistory(aUseRange) {
   let s = new Sanitizer();
   s.prefDomain = "privacy.cpd.";
 
   let prefs = gPrefService.getBranch(s.prefDomain);
   prefs.setBoolPref("history", true);
@@ -60,30 +70,38 @@ function clearHistory(aUseRange) {
   prefs.setBoolPref("offlineApps", false);
   prefs.setBoolPref("passwords", false);
   prefs.setBoolPref("sessions", false);
   prefs.setBoolPref("siteSettings", false);
 
   if (aUseRange) {
     let usec = Date.now() * 1000;
     s.range = [usec - 10 * 60 * 1000 * 1000, usec];
+    s.ignoreTimespan = false;
   }
 
   s.sanitize();
   s.range = null;
+  s.ignoreTimespan = true;
 
-  executeSoon(function () {
-    if (PageThumbsStorage.getFileForURL(URL).exists())
-      clearHistory(aUseRange);
-    else
+  executeSoon(next);
+}
+
+function createThumbnail() {
+  addTab(URL, function () {
+    whenFileExists(function () {
+      gBrowser.removeTab(gBrowser.selectedTab);
       next();
+    });
   });
 }
 
-function whenFileExists() {
-  let callback = whenFileExists;
-
+function whenFileExists(aCallback) {
+  let callback;
   let file = PageThumbsStorage.getFileForURL(URL);
-  if (file.exists() && file.fileSize)
-    callback = next;
+  if (file.exists() && file.fileSize) {
+    callback = aCallback;
+  } else {
+    callback = function () whenFileExists(aCallback);
+  }
 
   executeSoon(callback);
 }
--- a/browser/components/thumbnails/test/head.js
+++ b/browser/components/thumbnails/test/head.js
@@ -52,20 +52,21 @@ let TestRunner = {
  */
 function next() {
   TestRunner.next();
 }
 
 /**
  * Creates a new tab with the given URI.
  * @param aURI The URI that's loaded in the tab.
+ * @param aCallback The function to call when the tab has loaded.
  */
-function addTab(aURI) {
+function addTab(aURI, aCallback) {
   let tab = gBrowser.selectedTab = gBrowser.addTab(aURI);
-  whenLoaded(tab.linkedBrowser);
+  whenLoaded(tab.linkedBrowser, aCallback);
 }
 
 /**
  * Loads a new URI into the currently selected tab.
  * @param aURI The URI to load.
  */
 function navigateTo(aURI) {
   let browser = gBrowser.selectedTab.linkedBrowser;
--- a/browser/config/mozconfigs/win32/debug
+++ b/browser/config/mozconfigs/win32/debug
@@ -1,17 +1,21 @@
 ac_add_options --enable-debug
 ac_add_options --enable-trace-malloc
 ac_add_options --enable-signmar
 ENABLE_MARIONETTE=1
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
-mk_add_options MOZ_MAKE_FLAGS=-j1
+if test -n "${_PYMAKE}"; then
+  mk_add_options MOZ_MAKE_FLAGS=-j4
+else
+  mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
 
 if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
   . $topsrcdir/build/win32/mozconfig.vs2010-win64
 else
   . $topsrcdir/build/win32/mozconfig.vs2010
 fi
 
 # Package js shell.
--- a/browser/config/mozconfigs/win32/nightly
+++ b/browser/config/mozconfigs/win32/nightly
@@ -10,17 +10,21 @@ ac_add_options --enable-profiling
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
-mk_add_options MOZ_MAKE_FLAGS=-j1
+if test -n "${_PYMAKE}"; then
+  mk_add_options MOZ_MAKE_FLAGS=-j4
+else
+  mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
 
 if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
   . $topsrcdir/build/win32/mozconfig.vs2010-win64
 else
   . $topsrcdir/build/win32/mozconfig.vs2010
 fi
 
 # Package js shell.
--- a/browser/config/mozconfigs/win32/release
+++ b/browser/config/mozconfigs/win32/release
@@ -7,16 +7,22 @@ ac_add_options --enable-update-packaging
 ac_add_options --enable-jemalloc
 ac_add_options --enable-official-branding
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
+if test -n "${_PYMAKE}"; then
+  mk_add_options MOZ_MAKE_FLAGS=-j4
+else
+  mk_add_options MOZ_MAKE_FLAGS=-j1
+fi
+
 if test "$PROCESSOR_ARCHITECTURE" = "AMD64" -o "$PROCESSOR_ARCHITEW6432" = "AMD64"; then
   . $topsrcdir/build/win32/mozconfig.vs2010-win64
 else
   . $topsrcdir/build/win32/mozconfig.vs2010
 fi
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
deleted file mode 100644
--- a/browser/config/tooltool-manifests/macosx32/clang.manifest
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-{
-"clang_version": "r161022"
-},
-{
-"size": 47,
-"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
-"algorithm": "sha512",
-"filename": "setup.sh"
-},
-{
-"size": 54405078,
-"digest": "940f02ee8e4a760f52d6fe9cd1dc8dec01abc61b8086d46b4aa7d7292cf7c353a2cec1c9687491ade756ba2654b9e93986123155cb931bd18431fbbfdef671a9",
-"algorithm": "sha512",
-"filename": "clang.tar.bz2"
-}
-]
deleted file mode 100644
--- a/browser/config/tooltool-manifests/macosx64/clang.manifest
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-{
-"clang_version": "r161022"
-},
-{
-"size": 47,
-"digest": "2005a41fe97a5e00997063705f39d42b6a43b1cf7ba306cbc7b1513de34cdcd050fc6326efa2107f19ba0cc67914745dbf13154fa748010a93cf072481ef4aaa",
-"algorithm": "sha512",
-"filename": "setup.sh"
-},
-{
-"size": 54405078,
-"digest": "940f02ee8e4a760f52d6fe9cd1dc8dec01abc61b8086d46b4aa7d7292cf7c353a2cec1c9687491ade756ba2654b9e93986123155cb931bd18431fbbfdef671a9",
-"algorithm": "sha512",
-"filename": "clang.tar.bz2"
-}
-]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdAddon.jsm
@@ -0,0 +1,290 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+
+/**
+ * 'addon' command.
+ */
+gcli.addCommand({
+  name: "addon",
+  description: gcli.lookup("addonDesc")
+});
+
+/**
+ * 'addon list' command.
+ */
+gcli.addCommand({
+  name: "addon list",
+  description: gcli.lookup("addonListDesc"),
+  params: [{
+    name: 'type',
+    type: {
+      name: 'selection',
+      data: ["dictionary", "extension", "locale", "plugin", "theme", "all"]
+    },
+    defaultValue: 'all',
+    description: gcli.lookup("addonListTypeDesc"),
+  }],
+  exec: function(aArgs, context) {
+    function representEnabledAddon(aAddon) {
+      return "<li><![CDATA[" + aAddon.name + "\u2002" + aAddon.version +
+      getAddonStatus(aAddon) + "]]></li>";
+    }
+
+    function representDisabledAddon(aAddon) {
+      return "<li class=\"gcli-addon-disabled\">" +
+        "<![CDATA[" + aAddon.name + "\u2002" + aAddon.version + aAddon.version +
+        "]]></li>";
+    }
+
+    function getAddonStatus(aAddon) {
+      let operations = [];
+
+      if (aAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
+        operations.push("PENDING_ENABLE");
+      }
+
+      if (aAddon.pendingOperations & AddonManager.PENDING_DISABLE) {
+        operations.push("PENDING_DISABLE");
+      }
+
+      if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
+        operations.push("PENDING_UNINSTALL");
+      }
+
+      if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) {
+        operations.push("PENDING_INSTALL");
+      }
+
+      if (aAddon.pendingOperations & AddonManager.PENDING_UPGRADE) {
+        operations.push("PENDING_UPGRADE");
+      }
+
+      if (operations.length) {
+        return " (" + operations.join(", ") + ")";
+      }
+      return "";
+    }
+
+    /**
+     * Compares two addons by their name. Used in sorting.
+     */
+    function compareAddonNames(aNameA, aNameB) {
+      return String.localeCompare(aNameA.name, aNameB.name);
+    }
+
+    /**
+     * Resolves the promise which is the scope (this) of this function, filling
+     * it with an HTML representation of the passed add-ons.
+     */
+    function list(aType, aAddons) {
+      if (!aAddons.length) {
+        this.resolve(gcli.lookup("addonNoneOfType"));
+      }
+
+      // Separate the enabled add-ons from the disabled ones.
+      let enabledAddons = [];
+      let disabledAddons = [];
+
+      aAddons.forEach(function(aAddon) {
+        if (aAddon.isActive) {
+          enabledAddons.push(aAddon);
+        } else {
+          disabledAddons.push(aAddon);
+        }
+      });
+
+      let header;
+      switch(aType) {
+        case "dictionary":
+          header = gcli.lookup("addonListDictionaryHeading");
+          break;
+        case "extension":
+          header = gcli.lookup("addonListExtensionHeading");
+          break;
+        case "locale":
+          header = gcli.lookup("addonListLocaleHeading");
+          break;
+        case "plugin":
+          header = gcli.lookup("addonListPluginHeading");
+          break;
+        case "theme":
+          header = gcli.lookup("addonListThemeHeading");
+        case "all":
+          header = gcli.lookup("addonListAllHeading");
+          break;
+        default:
+          header = gcli.lookup("addonListUnknownHeading");
+      }
+
+      // Map and sort the add-ons, and create an HTML list.
+      this.resolve(header +
+        "<ol>" +
+        enabledAddons.sort(compareAddonNames).map(representEnabledAddon).join("") +
+        disabledAddons.sort(compareAddonNames).map(representDisabledAddon).join("") +
+        "</ol>");
+    }
+
+    // Create the promise that will be resolved when the add-on listing has
+    // been finished.
+    let promise = context.createPromise();
+    let types = aArgs.type == "all" ? null : [aArgs.type];
+    AddonManager.getAddonsByTypes(types, list.bind(promise, aArgs.type));
+    return promise;
+  }
+});
+
+// We need a list of addon names for the enable and disable commands. Because
+// getting the name list is async we do not add the commands until we have the
+// list.
+AddonManager.getAllAddons(function addonAsync(aAddons) {
+  // We listen for installs to keep our addon list up to date. There is no need
+  // to listen for uninstalls because uninstalled addons are simply disabled
+  // until restart (to enable undo functionality).
+  AddonManager.addAddonListener({
+    onInstalled: function(aAddon) {
+      addonNameCache.push({
+        name: representAddon(aAddon).replace(/\s/g, "_"),
+        value: aAddon.name
+      });
+    },
+    onUninstalled: function(aAddon) {
+      let name = representAddon(aAddon).replace(/\s/g, "_");
+
+      for (let i = 0; i < addonNameCache.length; i++) {
+        if(addonNameCache[i].name == name) {
+          addonNameCache.splice(i, 1);
+          break;
+        }
+      }
+    },
+  });
+
+  /**
+   * Returns a string that represents the passed add-on.
+   */
+  function representAddon(aAddon) {
+    let name = aAddon.name + " " + aAddon.version;
+    return name.trim();
+  }
+
+  let addonNameCache = [];
+
+  // The name parameter, used in "addon enable" and "addon disable."
+  let nameParameter = {
+    name: "name",
+    type: {
+      name: "selection",
+      lookup: addonNameCache
+    },
+    description: gcli.lookup("addonNameDesc")
+  };
+
+  for (let addon of aAddons) {
+    addonNameCache.push({
+      name: representAddon(addon).replace(/\s/g, "_"),
+      value: addon.name
+    });
+  }
+
+  /**
+   * 'addon enable' command.
+   */
+  gcli.addCommand({
+    name: "addon enable",
+    description: gcli.lookup("addonEnableDesc"),
+    params: [nameParameter],
+    exec: function(aArgs, context) {
+      /**
+       * Enables the addon in the passed list which has a name that matches
+       * according to the passed name comparer, and resolves the promise which
+       * is the scope (this) of this function to display the result of this
+       * enable attempt.
+       */
+      function enable(aName, addons) {
+        // Find the add-on.
+        let addon = null;
+        addons.some(function(candidate) {
+          if (candidate.name == aName) {
+            addon = candidate;
+            return true;
+          } else {
+            return false;
+          }
+        });
+
+        let name = representAddon(addon);
+
+        if (!addon.userDisabled) {
+          this.resolve("<![CDATA[" +
+            gcli.lookupFormat("addonAlreadyEnabled", [name]) + "]]>");
+        } else {
+          addon.userDisabled = false;
+          // nl-nl: {$1} is ingeschakeld.
+          this.resolve("<![CDATA[" +
+            gcli.lookupFormat("addonEnabled", [name]) + "]]>");
+        }
+      }
+
+      let promise = context.createPromise();
+      // List the installed add-ons, enable one when done listing.
+      AddonManager.getAllAddons(enable.bind(promise, aArgs.name));
+      return promise;
+    }
+  });
+
+  /**
+   * 'addon disable' command.
+   */
+  gcli.addCommand({
+    name: "addon disable",
+    description: gcli.lookup("addonDisableDesc"),
+    params: [nameParameter],
+    exec: function(aArgs, context) {
+      /**
+       * Like enable, but ... you know ... the exact opposite.
+       */
+      function disable(aName, addons) {
+        // Find the add-on.
+        let addon = null;
+        addons.some(function(candidate) {
+          if (candidate.name == aName) {
+            addon = candidate;
+            return true;
+          } else {
+            return false;
+          }
+        });
+
+        let name = representAddon(addon);
+
+        if (addon.userDisabled) {
+          this.resolve("<![CDATA[" +
+            gcli.lookupFormat("addonAlreadyDisabled", [name]) + "]]>");
+        } else {
+          addon.userDisabled = true;
+          // nl-nl: {$1} is uitgeschakeld.
+          this.resolve("<![CDATA[" +
+            gcli.lookupFormat("addonDisabled", [name]) + "]]>");
+        }
+      }
+
+      let promise = context.createPromise();
+      // List the installed add-ons, disable one when done listing.
+      AddonManager.getAllAddons(disable.bind(promise, aArgs.name));
+      return promise;
+    }
+  });
+  Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdBreak.jsm
@@ -0,0 +1,170 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
+                                  "resource:///modules/HUDService.jsm");
+
+/**
+ * 'break' command
+ */
+gcli.addCommand({
+  name: "break",
+  description: gcli.lookup("breakDesc"),
+  manual: gcli.lookup("breakManual")
+});
+
+
+/**
+ * 'break list' command
+ */
+gcli.addCommand({
+  name: "break list",
+  description: gcli.lookup("breaklistDesc"),
+  returnType: "html",
+  exec: function(args, context) {
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger();
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+    let breakpoints = dbg.breakpoints;
+
+    if (Object.keys(breakpoints).length === 0) {
+      return gcli.lookup("breaklistNone");
+    }
+
+    let reply = gcli.lookup("breaklistIntro");
+    reply += "<ol>";
+    for each (let breakpoint in breakpoints) {
+      let text = gcli.lookupFormat("breaklistLineEntry",
+                                   [breakpoint.location.url,
+                                    breakpoint.location.line]);
+      reply += "<li>" + text + "</li>";
+    };
+    reply += "</ol>";
+    return reply;
+  }
+});
+
+
+/**
+ * 'break add' command
+ */
+gcli.addCommand({
+  name: "break add",
+  description: gcli.lookup("breakaddDesc"),
+  manual: gcli.lookup("breakaddManual")
+});
+
+/**
+ * 'break add line' command
+ */
+gcli.addCommand({
+  name: "break add line",
+  description: gcli.lookup("breakaddlineDesc"),
+  params: [
+    {
+      name: "file",
+      type: {
+        name: "selection",
+        data: function() {
+          let win = HUDService.currentContext();
+          let dbg = win.DebuggerUI.getDebugger();
+          let files = [];
+          if (dbg) {
+            let scriptsView = dbg.contentWindow.DebuggerView.Scripts;
+            for each (let script in scriptsView.scriptLocations) {
+              files.push(script);
+            }
+          }
+          return files;
+        }
+      },
+      description: gcli.lookup("breakaddlineFileDesc")
+    },
+    {
+      name: "line",
+      type: { name: "number", min: 1, step: 10 },
+      description: gcli.lookup("breakaddlineLineDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    args.type = "line";
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger();
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+    var promise = context.createPromise();
+    let position = { url: args.file, line: args.line };
+    dbg.addBreakpoint(position, function(aBreakpoint, aError) {
+      if (aError) {
+        promise.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
+        return;
+      }
+      promise.resolve(gcli.lookup("breakaddAdded"));
+    });
+    return promise;
+  }
+});
+
+
+/**
+ * 'break del' command
+ */
+gcli.addCommand({
+  name: "break del",
+  description: gcli.lookup("breakdelDesc"),
+  params: [
+    {
+      name: "breakid",
+      type: {
+        name: "number",
+        min: 0,
+        max: function() {
+          let win = HUDService.currentContext();
+          let dbg = win.DebuggerUI.getDebugger();
+          if (!dbg) {
+            return gcli.lookup("breakaddDebuggerStopped");
+          }
+          return Object.keys(dbg.breakpoints).length - 1;
+        },
+      },
+      description: gcli.lookup("breakdelBreakidDesc")
+    }
+  ],
+  returnType: "html",
+  exec: function(args, context) {
+    let win = HUDService.currentContext();
+    let dbg = win.DebuggerUI.getDebugger();
+    if (!dbg) {
+      return gcli.lookup("breakaddDebuggerStopped");
+    }
+
+    let breakpoints = dbg.breakpoints;
+    let id = Object.keys(dbg.breakpoints)[args.breakid];
+    if (!id || !(id in breakpoints)) {
+      return gcli.lookup("breakNotFound");
+    }
+
+    let promise = context.createPromise();
+    try {
+      dbg.removeBreakpoint(breakpoints[id], function() {
+        promise.resolve(gcli.lookup("breakdelRemoved"));
+      });
+    } catch (ex) {
+      // If the debugger has been closed already, don't scare the user.
+      promise.resolve(gcli.lookup("breakdelRemoved"));
+    }
+    return promise;
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdCalllog.jsm
@@ -0,0 +1,103 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
+                                  "resource:///modules/HUDService.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
+  let JsDebugger = {};
+  Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
+
+  let global = Components.utils.getGlobalForObject({});
+  JsDebugger.addDebuggerToGlobal(global);
+
+  return global.Debugger;
+});
+
+let debuggers = [];
+
+/**
+ * 'calllog' command
+ */
+gcli.addCommand({
+  name: "calllog",
+  description: gcli.lookup("calllogDesc")
+})
+
+/**
+ * 'calllog start' command
+ */
+gcli.addCommand({
+  name: "calllog start",
+  description: gcli.lookup("calllogStartDesc"),
+
+  exec: function(args, context) {
+    let contentWindow = context.environment.contentDocument.defaultView;
+
+    let dbg = new Debugger(contentWindow);
+    dbg.onEnterFrame = function(frame) {
+      // BUG 773652 -  Make the output from the GCLI calllog command nicer
+      contentWindow.console.log("Method call: " + this.callDescription(frame));
+    }.bind(this);
+
+    debuggers.push(dbg);
+
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab;
+    HUDService.activateHUDForContext(tab);
+
+    return gcli.lookup("calllogStartReply");
+  },
+
+  callDescription: function(frame) {
+    let name = "<anonymous>";
+    if (frame.callee.name) {
+      name = frame.callee.name;
+    }
+    else {
+      let desc = frame.callee.getOwnPropertyDescriptor("displayName");
+      if (desc && desc.value && typeof desc.value == "string") {
+        name = desc.value;
+      }
+    }
+
+    let args = frame.arguments.map(this.valueToString).join(", ");
+    return name + "(" + args + ")";
+  },
+
+  valueToString: function(value) {
+    if (typeof value !== "object" || value === null) {
+      return uneval(value);
+    }
+    return "[object " + value.class + "]";
+  }
+});
+
+/**
+ * 'calllog stop' command
+ */
+gcli.addCommand({
+  name: "calllog stop",
+  description: gcli.lookup("calllogStopDesc"),
+
+  exec: function(args, context) {
+    let numDebuggers = debuggers.length;
+    if (numDebuggers == 0) {
+      return gcli.lookup("calllogStopNoLogging");
+    }
+
+    for (let dbg of debuggers) {
+      dbg.onEnterFrame = undefined;
+    }
+    debuggers = [];
+
+    return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdCalllogChrome.jsm
@@ -0,0 +1,155 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+
+let EXPORTED_SYMBOLS = [ ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
+                                  "resource:///modules/HUDService.jsm");
+
+XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
+  let JsDebugger = {};
+  Cu.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
+
+  let global = Components.utils.getGlobalForObject({});
+  JsDebugger.addDebuggerToGlobal(global);
+
+  return global.Debugger;
+});
+
+let debuggers = [];
+let sandboxes = [];
+
+/**
+ * 'calllog chromestart' command
+ */
+gcli.addCommand({
+  name: "calllog chromestart",
+  description: gcli.lookup("calllogChromeStartDesc"),
+  get hidden() gcli.hiddenByChromePref(),
+  params: [
+    {
+      name: "sourceType",
+      type: {
+        name: "selection",
+        data: ["content-variable", "chrome-variable", "jsm", "javascript"]
+      }
+    },
+    {
+      name: "source",
+      type: "string",
+      description: gcli.lookup("calllogChromeSourceTypeDesc"),
+      manual: gcli.lookup("calllogChromeSourceTypeManual"),
+    }
+  ],
+  exec: function(args, context) {
+    let globalObj;
+    let contentWindow = context.environment.contentDocument.defaultView;
+
+    if (args.sourceType == "jsm") {
+      try {
+        globalObj = Cu.import(args.source);
+      }
+      catch (e) {
+        return gcli.lookup("callLogChromeInvalidJSM");
+      }
+    } else if (args.sourceType == "content-variable") {
+      if (args.source in contentWindow) {
+        globalObj = Cu.getGlobalForObject(contentWindow[args.source]);
+      } else {
+        throw new Error(gcli.lookup("callLogChromeVarNotFoundContent"));
+      }
+    } else if (args.sourceType == "chrome-variable") {
+      let chromeWin = context.environment.chromeDocument.defaultView;
+      if (args.source in chromeWin) {
+        globalObj = Cu.getGlobalForObject(chromeWin[args.source]);
+      } else {
+        return gcli.lookup("callLogChromeVarNotFoundChrome");
+      }
+    } else {
+      let chromeWin = context.environment.chromeDocument.defaultView;
+      let sandbox = new Cu.Sandbox(chromeWin,
+                                   {
+                                     sandboxPrototype: chromeWin,
+                                     wantXrays: false,
+                                     sandboxName: "gcli-cmd-calllog-chrome"
+                                   });
+      let returnVal;
+      try {
+        returnVal = Cu.evalInSandbox(args.source, sandbox, "ECMAv5");
+        sandboxes.push(sandbox);
+      } catch(e) {
+        // We need to save the message before cleaning up else e contains a dead
+        // object.
+        let msg = gcli.lookup("callLogChromeEvalException") + ": " + e;
+        Cu.nukeSandbox(sandbox);
+        return msg;
+      }
+
+      if (typeof returnVal == "undefined") {
+        return gcli.lookup("callLogChromeEvalNeedsObject");
+      }
+
+      globalObj = Cu.getGlobalForObject(returnVal);
+    }
+
+    let dbg = new Debugger(globalObj);
+    debuggers.push(dbg);
+
+    dbg.onEnterFrame = function(frame) {
+      // BUG 773652 -  Make the output from the GCLI calllog command nicer
+      contentWindow.console.log(gcli.lookup("callLogChromeMethodCall") +
+                                ": " + this.callDescription(frame));
+    }.bind(this);
+
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab;
+    HUDService.activateHUDForContext(tab);
+
+    return gcli.lookup("calllogChromeStartReply");
+  },
+
+  valueToString: function(value) {
+    if (typeof value !== "object" || value === null)
+      return uneval(value);
+    return "[object " + value.class + "]";
+  },
+
+  callDescription: function(frame) {
+    let name = frame.callee.name || gcli.lookup("callLogChromeAnonFunction");
+    let args = frame.arguments.map(this.valueToString).join(", ");
+    return name + "(" + args + ")";
+  }
+});
+
+/**
+ * 'calllog chromestop' command
+ */
+gcli.addCommand({
+  name: "calllog chromestop",
+  description: gcli.lookup("calllogChromeStopDesc"),
+  get hidden() gcli.hiddenByChromePref(),
+  exec: function(args, context) {
+    let numDebuggers = debuggers.length;
+    if (numDebuggers == 0) {
+      return gcli.lookup("calllogChromeStopNoLogging");
+    }
+
+    for (let dbg of debuggers) {
+      dbg.onEnterFrame = undefined;
+      dbg.enabled = false;
+    }
+    for (let sandbox of sandboxes) {
+      Cu.nukeSandbox(sandbox);
+    }
+    debuggers = [];
+    sandboxes = [];
+
+    return gcli.lookupFormat("calllogChromeStopReply", [ numDebuggers ]);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdCmd.jsm
@@ -0,0 +1,129 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+let EXPORTED_SYMBOLS = [ "CmdCommands" ];
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+let prefSvc = "@mozilla.org/preferences-service;1";
+XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
+  let prefService = Cc[prefSvc].getService(Ci.nsIPrefService);
+  return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
+                                  "resource://gre/modules/NetUtil.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+                                  "resource:///modules/devtools/Console.jsm");
+
+const PREF_DIR = "devtools.commands.dir";
+
+/**
+ * A place to store the names of the commands that we have added as a result of
+ * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the
+ * added commands.
+ */
+let commands = [];
+
+/**
+ * Exported API
+ */
+let CmdCommands = {
+  /**
+   * Called to look in a directory pointed at by the devtools.commands.dir pref
+   * for *.mozcmd files which are then loaded.
+   * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
+   * we eval the script from the .mozcmd file. This should be a chrome window.
+   */
+  refreshAutoCommands: function GC_refreshAutoCommands(aSandboxPrincipal) {
+    // First get rid of the last set of commands
+    commands.forEach(function(name) {
+      gcli.removeCommand(name);
+    });
+
+    let dirName = prefBranch.getComplexValue(PREF_DIR,
+                                             Ci.nsISupportsString).data.trim();
+    if (dirName == "") {
+      return;
+    }
+
+    let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+    dir.initWithPath(dirName);
+    if (!dir.exists() || !dir.isDirectory()) {
+      throw new Error('\'' + dirName + '\' is not a directory.');
+    }
+
+    let en = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
+
+    while (true) {
+      let file = en.nextFile;
+      if (!file) {
+        break;
+      }
+      if (file.leafName.match(/.*\.mozcmd$/) && file.isFile() && file.isReadable()) {
+        loadCommandFile(file, aSandboxPrincipal);
+      }
+    }
+  },
+};
+
+/**
+ * Load the commands from a single file
+ * @param nsIFile aFile The file containing the commands that we should read
+ * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
+ * we eval the script from the .mozcmd file. This should be a chrome window.
+ */
+function loadCommandFile(aFile, aSandboxPrincipal) {
+  NetUtil.asyncFetch(aFile, function refresh_fetch(aStream, aStatus) {
+    if (!Components.isSuccessCode(aStatus)) {
+      console.error("NetUtil.asyncFetch(" + aFile.path + ",..) failed. Status=" + aStatus);
+      return;
+    }
+
+    let source = NetUtil.readInputStreamToString(aStream, aStream.available());
+    aStream.close();
+
+    let sandbox = new Cu.Sandbox(aSandboxPrincipal, {
+      sandboxPrototype: aSandboxPrincipal,
+      wantXrays: false,
+      sandboxName: aFile.path
+    });
+    let data = Cu.evalInSandbox(source, sandbox, "1.8", aFile.leafName, 1);
+
+    if (!Array.isArray(data)) {
+      console.error("Command file '" + aFile.leafName + "' does not have top level array.");
+      return;
+    }
+
+    data.forEach(function(commandSpec) {
+      gcli.addCommand(commandSpec);
+      commands.push(commandSpec.name);
+    });
+  }.bind(this));
+}
+
+/**
+ * 'cmd' command
+ */
+gcli.addCommand({
+  name: "cmd",
+  get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); },
+  description: gcli.lookup("cmdDesc")
+});
+
+/**
+ * 'cmd refresh' command
+ */
+gcli.addCommand({
+  name: "cmd refresh",
+  description: gcli.lookup("cmdRefreshDesc"),
+  get hidden() { return !prefBranch.prefHasUserValue(PREF_DIR); },
+  exec: function Command_cmdRefresh(args, context) {
+    let chromeWindow = context.environment.chromeDocument.defaultView;
+    GcliCommands.refreshAutoCommands(chromeWindow);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdConsole.jsm
@@ -0,0 +1,62 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
+                                  "resource:///modules/HUDService.jsm");
+
+/**
+ * 'console' command
+ */
+gcli.addCommand({
+  name: "console",
+  description: gcli.lookup("consoleDesc"),
+  manual: gcli.lookup("consoleManual")
+});
+
+/**
+ * 'console clear' command
+ */
+gcli.addCommand({
+  name: "console clear",
+  description: gcli.lookup("consoleclearDesc"),
+  exec: function Command_consoleClear(args, context) {
+    let window = context.environment.contentDocument.defaultView;
+    let hud = HUDService.getHudByWindow(window);
+    // hud will be null if the web console has not been opened for this window
+    if (hud) {
+      hud.jsterm.clearOutput();
+    }
+  }
+});
+
+/**
+ * 'console close' command
+ */
+gcli.addCommand({
+  name: "console close",
+  description: gcli.lookup("consolecloseDesc"),
+  exec: function Command_consoleClose(args, context) {
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
+    HUDService.deactivateHUDForContext(tab);
+  }
+});
+
+/**
+ * 'console open' command
+ */
+gcli.addCommand({
+  name: "console open",
+  description: gcli.lookup("consoleopenDesc"),
+  exec: function Command_consoleOpen(args, context) {
+    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
+    HUDService.activateHUDForContext(tab);
+  }
+});
rename from browser/devtools/commandline/GcliCookieCommands.jsm
rename to browser/devtools/commandline/CmdCookie.jsm
--- a/browser/devtools/commandline/GcliCookieCommands.jsm
+++ b/browser/devtools/commandline/CmdCookie.jsm
@@ -1,17 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 let EXPORTED_SYMBOLS = [ ];
 
-Components.utils.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "console",
+                                  "resource:///modules/devtools/Console.jsm");
 
 // We should really be using nsICookieManager so we can read more than just the
 // key/value of cookies. The difficulty is filtering the cookies that are
 // relevant to the current page. See
 // https://github.com/firebug/firebug/blob/master/extension/content/firebug/cookies/cookieObserver.js#L123
 // For details on how this is done with Firebug
 
 /**
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdDbg.jsm
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+/**
+ * 'dbg' command
+ */
+gcli.addCommand({
+  name: "dbg",
+  description: gcli.lookup("dbgDesc"),
+  manual: gcli.lookup("dbgManual")
+});
+
+
+/**
+ * 'dbg interrupt' command
+ */
+gcli.addCommand({
+  name: "dbg interrupt",
+  description: gcli.lookup("dbgInterrupt"),
+  params: [],
+  exec: function(args, context) {
+    let win = context.environment.chromeDocument.defaultView;
+    let dbg = win.DebuggerUI.getDebugger();
+
+    if (dbg) {
+      let controller = dbg.contentWindow.DebuggerController;
+      let thread = controller.activeThread;
+      if (!thread.paused) {
+        thread.interrupt();
+      }
+    }
+  }
+});
+
+/**
+ * 'dbg continue' command
+ */
+gcli.addCommand({
+  name: "dbg continue",
+  description: gcli.lookup("dbgContinue"),
+  params: [],
+  exec: function(args, context) {
+    let win = context.environment.chromeDocument.defaultView;
+    let dbg = win.DebuggerUI.getDebugger();
+
+    if (dbg) {
+      let controller = dbg.contentWindow.DebuggerController;
+      let thread = controller.activeThread;
+      if (thread.paused) {
+        thread.resume();
+      }
+    }
+  }
+});
+
+
+/**
+ * 'dbg step' command
+ */
+gcli.addCommand({
+  name: "dbg step",
+  description: gcli.lookup("dbgStepDesc"),
+  manual: gcli.lookup("dbgStepManual")
+});
+
+
+/**
+ * 'dbg step over' command
+ */
+gcli.addCommand({
+  name: "dbg step over",
+  description: gcli.lookup("dbgStepOverDesc"),
+  params: [],
+  exec: function(args, context) {
+    let win = context.environment.chromeDocument.defaultView;
+    let dbg = win.DebuggerUI.getDebugger();
+
+    if (dbg) {
+      let controller = dbg.contentWindow.DebuggerController;
+      let thread = controller.activeThread;
+      if (thread.paused) {
+        thread.stepOver();
+      }
+    }
+  }
+});
+
+/**
+ * 'dbg step in' command
+ */
+gcli.addCommand({
+  name: 'dbg step in',
+  description: gcli.lookup("dbgStepInDesc"),
+  params: [],
+  exec: function(args, context) {
+    let win = context.environment.chromeDocument.defaultView;
+    let dbg = win.DebuggerUI.getDebugger();
+
+    if (dbg) {
+      let controller = dbg.contentWindow.DebuggerController;
+      let thread = controller.activeThread;
+      if (thread.paused) {
+        thread.stepIn();
+      }
+    }
+  }
+});
+
+/**
+ * 'dbg step over' command
+ */
+gcli.addCommand({
+  name: 'dbg step out',
+  description: gcli.lookup("dbgStepOutDesc"),
+  params: [],
+  exec: function(args, context) {
+    let win = context.environment.chromeDocument.defaultView;
+    let dbg = win.DebuggerUI.getDebugger();
+
+    if (dbg) {
+      let controller = dbg.contentWindow.DebuggerController;
+      let thread = controller.activeThread;
+      if (thread.paused) {
+        thread.stepOut();
+      }
+    }
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdEcho.jsm
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+
+/**
+ * 'echo' command
+ */
+gcli.addCommand({
+  name: "echo",
+  description: gcli.lookup("echoDesc"),
+  params: [
+    {
+      name: "message",
+      type: "string",
+      description: gcli.lookup("echoMessageDesc")
+    }
+  ],
+  returnType: "string",
+  hidden: true,
+  exec: function Command_echo(args, context) {
+    return args.message;
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdExport.jsm
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+
+/**
+ * 'export' command
+ */
+gcli.addCommand({
+  name: "export",
+  description: gcli.lookup("exportDesc"),
+});
+
+/**
+ * The 'export html' command. This command allows the user to export the page to
+ * HTML after they do DOM changes.
+ */
+gcli.addCommand({
+  name: "export html",
+  description: gcli.lookup("exportHtmlDesc"),
+  exec: function(args, context) {
+    let document = context.environment.contentDocument;
+    let window = document.defaultView;
+    let page = document.documentElement.outerHTML;
+    window.open('data:text/plain;charset=utf8,' + encodeURIComponent(page));
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdJsb.jsm
@@ -0,0 +1,136 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+const XMLHttpRequest =
+  Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1");
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource:///modules/devtools/gcli.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "js_beautify",
+                                  "resource:///modules/devtools/Jsbeautify.jsm");
+
+/**
+ * jsb command.
+ */
+gcli.addCommand({
+  name: 'jsb',
+  description: gcli.lookup('jsbDesc'),
+  returnValue:'string',
+  params: [
+    {
+      name: 'url',
+      type: 'string',
+      description: gcli.lookup('jsbUrlDesc'),
+      manual: 'The URL of the JS to prettify'
+    },
+    {
+      name: 'indentSize',
+      type: 'number',
+      description: gcli.lookup('jsbIndentSizeDesc'),
+      manual: gcli.lookup('jsbIndentSizeManual'),
+      defaultValue: 2
+    },
+    {
+      name: 'indentChar',
+      type: {
+        name: 'selection',
+        lookup: [
+          { name: "space", value: " " },
+          { name: "tab", value: "\t" }
+        ]
+      },
+      description: gcli.lookup('jsbIndentCharDesc'),
+      manual: gcli.lookup('jsbIndentCharManual'),
+      defaultValue: ' ',
+    },
+    {
+      name: 'preserveNewlines',
+      type: 'boolean',
+      description: gcli.lookup('jsbPreserveNewlinesDesc'),
+      manual: gcli.lookup('jsbPreserveNewlinesManual')
+    },
+    {
+      name: 'preserveMaxNewlines',
+      type: 'number',
+      description: gcli.lookup('jsbPreserveMaxNewlinesDesc'),
+      manual: gcli.lookup('jsbPreserveMaxNewlinesManual'),
+      defaultValue: -1
+    },
+    {
+      name: 'jslintHappy',
+      type: 'boolean',
+      description: gcli.lookup('jsbJslintHappyDesc'),
+      manual: gcli.lookup('jsbJslintHappyManual')
+    },
+    {
+      name: 'braceStyle',
+      type: {
+        name: 'selection',
+        data: ['collapse', 'expand', 'end-expand', 'expand-strict']
+      },
+      description: gcli.lookup('jsbBraceStyleDesc'),
+      manual: gcli.lookup('jsbBraceStyleManual'),
+      defaultValue: "collapse"
+    },
+    {
+      name: 'spaceBeforeConditional',
+      type: 'boolean',
+      description: gcli.lookup('jsbSpaceBeforeConditionalDesc'),
+      manual: gcli.lookup('jsbSpaceBeforeConditionalManual')
+    },
+    {
+      name: 'unescapeStrings',
+      type: 'boolean',
+      description: gcli.lookup('jsbUnescapeStringsDesc'),
+      manual: gcli.lookup('jsbUnescapeStringsManual')
+    }
+  ],
+  exec: function(args, context) {
+    let opts = {
+      indent_size: args.indentSize,
+      indent_char: args.indentChar,
+      preserve_newlines: args.preserveNewlines,
+      max_preserve_newlines: args.preserveMaxNewlines == -1 ?
+                             undefined : args.preserveMaxNewlines,
+      jslint_happy: args.jslintHappy,
+      brace_style: args.braceStyle,
+      space_before_conditional: args.spaceBeforeConditional,
+      unescape_strings: args.unescapeStrings
+    }
+
+    let xhr = new XMLHttpRequest();
+
+    try {
+      xhr.open("GET", args.url, true);
+    } catch(e) {
+      return gcli.lookup('jsbInvalidURL');
+    }
+
+    let promise = context.createPromise();
+
+    xhr.onreadystatechange = function(aEvt) {
+      if (xhr.readyState == 4) {
+        if (xhr.status == 200 || xhr.status == 0) {
+          let browserDoc = context.environment.chromeDocument;
+          let browserWindow = browserDoc.defaultView;
+          let browser = browserWindow.gBrowser;
+  
+          browser.selectedTab = browser.addTab("data:text/plain;base64," +
+            browserWindow.btoa(js_beautify(xhr.responseText, opts)));
+          promise.resolve();
+        }
+        else {
+          promise.resolve("Unable to load page to beautify: " + args.url + " " +
+                          xhr.status + " " + xhr.statusText);
+        }
+      };
+    }
+    xhr.send(null);
+    return promise;
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdPagemod.jsm
@@ -0,0 +1,264 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+
+/**
+ * 'pagemod' command
+ */
+gcli.addCommand({
+  name: "pagemod",
+  description: gcli.lookup("pagemodDesc"),
+});
+
+/**
+ * The 'pagemod replace' command. This command allows the user to search and
+ * replace within text nodes and attributes.
+ */
+gcli.addCommand({
+  name: "pagemod replace",
+  description: gcli.lookup("pagemodReplaceDesc"),
+  params: [
+    {
+      name: "search",
+      type: "string",
+      description: gcli.lookup("pagemodReplaceSearchDesc"),
+    },
+    {
+      name: "replace",
+      type: "string",
+      description: gcli.lookup("pagemodReplaceReplaceDesc"),
+    },
+    {
+      name: "ignoreCase",
+      type: "boolean",
+      description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"),
+    },
+    {
+      name: "selector",
+      type: "string",
+      description: gcli.lookup("pagemodReplaceSelectorDesc"),
+      defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)",
+    },
+    {
+      name: "root",
+      type: "node",
+      description: gcli.lookup("pagemodReplaceRootDesc"),
+      defaultValue: null,
+    },
+    {
+      name: "attrOnly",
+      type: "boolean",
+      description: gcli.lookup("pagemodReplaceAttrOnlyDesc"),
+    },
+    {
+      name: "contentOnly",
+      type: "boolean",
+      description: gcli.lookup("pagemodReplaceContentOnlyDesc"),
+    },
+    {
+      name: "attributes",
+      type: "string",
+      description: gcli.lookup("pagemodReplaceAttributesDesc"),
+      defaultValue: null,
+    },
+  ],
+  exec: function(args, context) {
+    let document = context.environment.contentDocument;
+    let searchTextNodes = !args.attrOnly;
+    let searchAttributes = !args.contentOnly;
+    let regexOptions = args.ignoreCase ? 'ig' : 'g';
+    let search = new RegExp(escapeRegex(args.search), regexOptions);
+    let attributeRegex = null;
+    if (args.attributes) {
+      attributeRegex = new RegExp(args.attributes, regexOptions);
+    }
+
+    let root = args.root || document;
+    let elements = root.querySelectorAll(args.selector);
+    elements = Array.prototype.slice.call(elements);
+
+    let replacedTextNodes = 0;
+    let replacedAttributes = 0;
+
+    function replaceAttribute() {
+      replacedAttributes++;
+      return args.replace;
+    }
+    function replaceTextNode() {
+      replacedTextNodes++;
+      return args.replace;
+    }
+
+    for (let i = 0; i < elements.length; i++) {
+      let element = elements[i];
+      if (searchTextNodes) {
+        for (let y = 0; y < element.childNodes.length; y++) {
+          let node = element.childNodes[y];
+          if (node.nodeType == node.TEXT_NODE) {
+            node.textContent = node.textContent.replace(search, replaceTextNode);
+          }
+        }
+      }
+
+      if (searchAttributes) {
+        if (!element.attributes) {
+          continue;
+        }
+        for (let y = 0; y < element.attributes.length; y++) {
+          let attr = element.attributes[y];
+          if (!attributeRegex || attributeRegex.test(attr.name)) {
+            attr.value = attr.value.replace(search, replaceAttribute);
+          }
+        }
+      }
+    }
+
+    return gcli.lookupFormat("pagemodReplaceResult",
+                             [elements.length, replacedTextNodes,
+                              replacedAttributes]);
+  }
+});
+
+/**
+ * 'pagemod remove' command
+ */
+gcli.addCommand({
+  name: "pagemod remove",
+  description: gcli.lookup("pagemodRemoveDesc"),
+});
+
+
+/**
+ * The 'pagemod remove element' command.
+ */
+gcli.addCommand({
+  name: "pagemod remove element",
+  description: gcli.lookup("pagemodRemoveElementDesc"),
+  params: [
+    {
+      name: "search",
+      type: "string",
+      description: gcli.lookup("pagemodRemoveElementSearchDesc"),
+    },
+    {
+      name: "root",
+      type: "node",
+      description: gcli.lookup("pagemodRemoveElementRootDesc"),
+      defaultValue: null,
+    },
+    {
+      name: 'stripOnly',
+      type: 'boolean',
+      description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"),
+    },
+    {
+      name: 'ifEmptyOnly',
+      type: 'boolean',
+      description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"),
+    },
+  ],
+  exec: function(args, context) {
+    let document = context.environment.contentDocument;
+    let root = args.root || document;
+    let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
+
+    let removed = 0;
+    for (let i = 0; i < elements.length; i++) {
+      let element = elements[i];
+      let parentNode = element.parentNode;
+      if (!parentNode || !element.removeChild) {
+        continue;
+      }
+      if (args.stripOnly) {
+        while (element.hasChildNodes()) {
+          parentNode.insertBefore(element.childNodes[0], element);
+        }
+      }
+      if (!args.ifEmptyOnly || !element.hasChildNodes()) {
+        element.parentNode.removeChild(element);
+        removed++;
+      }
+    }
+
+    return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements",
+                             [elements.length, removed]);
+  }
+});
+
+/**
+ * The 'pagemod remove attribute' command.
+ */
+gcli.addCommand({
+  name: "pagemod remove attribute",
+  description: gcli.lookup("pagemodRemoveAttributeDesc"),
+  params: [
+    {
+      name: "searchAttributes",
+      type: "string",
+      description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"),
+    },
+    {
+      name: "searchElements",
+      type: "string",
+      description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"),
+    },
+    {
+      name: "root",
+      type: "node",
+      description: gcli.lookup("pagemodRemoveAttributeRootDesc"),
+      defaultValue: null,
+    },
+    {
+      name: "ignoreCase",
+      type: "boolean",
+      description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"),
+    },
+  ],
+  exec: function(args, context) {
+    let document = context.environment.contentDocument;
+
+    let root = args.root || document;
+    let regexOptions = args.ignoreCase ? 'ig' : 'g';
+    let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
+    let elements = root.querySelectorAll(args.searchElements);
+    elements = Array.prototype.slice.call(elements);
+
+    let removed = 0;
+    for (let i = 0; i < elements.length; i++) {
+      let element = elements[i];
+      if (!element.attributes) {
+        continue;
+      }
+
+      var attrs = Array.prototype.slice.call(element.attributes);
+      for (let y = 0; y < attrs.length; y++) {
+        let attr = attrs[y];
+        if (attributeRegex.test(attr.name)) {
+          element.removeAttribute(attr.name);
+          removed++;
+        }
+      }
+    }
+
+    return gcli.lookupFormat("pagemodRemoveAttributeResult",
+                             [elements.length, removed]);
+  }
+});
+
+/**
+ * Make a given string safe to use  in a regular expression.
+ *
+ * @param string aString
+ *        The string you want to use in a regex.
+ * @return string
+ *         The equivalent of |aString| but safe to use in a regex.
+ */
+function escapeRegex(aString) {
+  return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdRestart.jsm
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+/**
+ * Restart command
+ *
+ * @param boolean nocache
+ *        Disables loading content from cache upon restart.
+ *
+ * Examples :
+ * >> restart
+ * - restarts browser immediately
+ * >> restart --nocache
+ * - restarts immediately and starts Firefox without using cache
+ */
+gcli.addCommand({
+  name: "restart",
+  description: gcli.lookup("restartFirefoxDesc"),
+  params: [
+    {
+      name: "nocache",
+      type: "boolean",
+      description: gcli.lookup("restartFirefoxNocacheDesc")
+    }
+  ],
+  returnType: "string",
+  exec: function Restart(args, context) {
+    let canceled = Cc["@mozilla.org/supports-PRBool;1"]
+                     .createInstance(Ci.nsISupportsPRBool);
+    Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");
+    if (canceled.data) {
+      return gcli.lookup("restartFirefoxRequestCancelled");
+    }
+
+    // disable loading content from cache.
+    if (args.nocache) {
+      Services.appinfo.invalidateCachesOnRestart();
+    }
+
+    // restart
+    Cc['@mozilla.org/toolkit/app-startup;1']
+      .getService(Ci.nsIAppStartup)
+      .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
+    return gcli.lookup("restartFirefoxRestarting");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/CmdScreenshot.jsm
@@ -0,0 +1,134 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+let EXPORTED_SYMBOLS = [ ];
+
+Cu.import("resource:///modules/devtools/gcli.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers",
+                                  "resource:///modules/devtools/LayoutHelpers.jsm");
+
+/**
+ * 'screenshot' command
+ */
+gcli.addCommand({
+  name: "screenshot",
+  description: gcli.lookup("screenshotDesc"),
+  manual: gcli.lookup("screenshotManual"),
+  returnType: "string",
+  params: [
+    {
+      name: "filename",
+      type: "string",
+      description: gcli.lookup("screenshotFilenameDesc"),
+      manual: gcli.lookup("screenshotFilenameManual")
+    },
+    {
+      name: "delay",
+      type: { name: "number", min: 0 },
+      defaultValue: 0,
+      description: gcli.lookup("screenshotDelayDesc"),
+      manual: gcli.lookup("screenshotDelayManual")
+    },
+    {
+      name: "fullpage",
+      type: "boolean",
+      description: gcli.lookup("screenshotFullPageDesc"),
+      manual: gcli.lookup("screenshotFullPageManual")
+    },
+    {
+      name: "node",
+      type: "node",
+      defaultValue: null,
+      description: gcli.lookup("inspectNodeDesc"),
+      manual: gcli.lookup("inspectNodeManual")
+    }
+  ],
+  exec: function Command_screenshot(args, context) {
+    var document = context.environment.contentDocument;
+    if (args.delay > 0) {
+      var promise = context.createPromise();
+      document.defaultView.setTimeout(function Command_screenshotDelay() {
+        let reply = this.grabScreen(document, args.filename);
+        promise.resolve(reply);
+      }.bind(this), args.delay * 1000);
+      return promise;
+    }
+    else {
+      return this.grabScreen(document, args.filename, args.fullpage, args.node);
+    }
+  },
+  grabScreen:
+  function Command_screenshotGrabScreen(document, filename, fullpage, node) {
+    let window = document.defaultView;
+    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    let left = 0;
+    let top = 0;
+    let width;
+    let height;
+
+    if (!fullpage) {
+      if (!node) {
+        left = window.scrollX;
+        top = window.scrollY;
+        width = window.innerWidth;
+        height = window.innerHeight;
+      } else {
+        let rect = LayoutHelpers.getRect(node, window);
+        top = rect.top;
+        left = rect.left;
+        width = rect.width;
+        height = rect.height;
+      }
+    } else {
+      width = window.innerWidth + window.scrollMaxX;
+      height = window.innerHeight + window.scrollMaxY;
+    }
+    canvas.width = width;
+    canvas.height = height;
+
+    let ctx = canvas.getContext("2d");
+    ctx.drawWindow(window, left, top, width, height, "#fff");
+
+    let data = canvas.toDataURL("image/png", "");
+    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
+
+    // Check there is a .png extension to filename
+    if (!filename.match(/.png$/i)) {
+      filename += ".png";
+    }
+
+    // If the filename is relative, tack it onto the download directory
+    if (!filename.match(/[\\\/]/)) {
+      let downloadMgr = Cc["@mozilla.org/download-manager;1"]
+        .getService(Ci.nsIDownloadManager);
+      let tempfile = downloadMgr.userDownloadsDirectory;
+      tempfile.append(filename);
+      filename = tempfile.path;
+    }
+
+    try {
+      file.initWithPath(filename);
+    } catch (ex) {
+      return "Error saving to " + filename;
+    }
+
+    let ioService = Cc["@mozilla.org/network/io-service;1"]
+      .getService(Ci.nsIIOService);
+
+    let Persist = Ci.nsIWebBrowserPersist;
+    let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
+      .createInstance(Persist);
+    persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
+                           Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
+
+    let source = ioService.newURI(data, "UTF8", null);
+    persist.saveURI(source, null, null, null, null, file);
+
+    return "Saved to " + filename;
+  }
+});
rename from browser/devtools/commandline/GcliCommands.jsm
rename to browser/devtools/commandline/Commands.jsm
--- a/browser/devtools/commandline/GcliCommands.jsm
+++ b/browser/devtools/commandline/Commands.jsm
@@ -1,1549 +1,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 
-let EXPORTED_SYMBOLS = [ "GcliCommands" ];
-
-const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
-
-const XMLHttpRequest =
-  Components.Constructor("@mozilla.org/xmlextras/xmlhttprequest;1");
-
-Cu.import("resource:///modules/devtools/gcli.jsm");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "HUDService",
-                                  "resource:///modules/HUDService.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
-                                  "resource://gre/modules/NetUtil.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "LayoutHelpers",
-                                  "resource:///modules/devtools/LayoutHelpers.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "console",
-                                  "resource://gre/modules/devtools/Console.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
-                                  "resource://gre/modules/AddonManager.jsm");
-
-XPCOMUtils.defineLazyModuleGetter(this, "js_beautify",
-                                  "resource:///modules/devtools/Jsbeautify.jsm");
-
-XPCOMUtils.defineLazyGetter(this, "Debugger", function() {
-  let JsDebugger = {};
-  Components.utils.import("resource://gre/modules/jsdebugger.jsm", JsDebugger);
-
-  let global = Components.utils.getGlobalForObject({});
-  JsDebugger.addDebuggerToGlobal(global);
-
-  return global.Debugger;
-});
-
-let prefSvc = "@mozilla.org/preferences-service;1";
-XPCOMUtils.defineLazyGetter(this, "prefBranch", function() {
-  let prefService = Cc[prefSvc].getService(Ci.nsIPrefService);
-  return prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
-});
-
-Cu.import("resource:///modules/devtools/GcliTiltCommands.jsm", {});
-Cu.import("resource:///modules/devtools/GcliCookieCommands.jsm", {});
-
-/**
- * A place to store the names of the commands that we have added as a result of
- * calling refreshAutoCommands(). Used by refreshAutoCommands to remove the
- * added commands.
- */
-let commands = [];
-
-/**
- * Exported API
- */
-let GcliCommands = {
-  /**
-   * Called to look in a directory pointed at by the devtools.commands.dir pref
-   * for *.mozcmd files which are then loaded.
-   * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
-   * we eval the script from the .mozcmd file. This should be a chrome window.
-   */
-  refreshAutoCommands: function GC_refreshAutoCommands(aSandboxPrincipal) {
-    // First get rid of the last set of commands
-    commands.forEach(function(name) {
-      gcli.removeCommand(name);
-    });
-
-    let dirName = prefBranch.getComplexValue("devtools.commands.dir",
-                                             Ci.nsISupportsString).data;
-    if (dirName == "") {
-      return;
-    }
-
-    let dir = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-    dir.initWithPath(dirName);
-    if (!dir.exists() || !dir.isDirectory()) {
-      throw new Error('\'' + dirName + '\' is not a directory.');
-    }
-
-    let en = dir.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
-
-    while (true) {
-      let file = en.nextFile;
-      if (!file) {
-        break;
-      }
-      if (file.leafName.match(/.*\.mozcmd$/) && file.isFile() && file.isReadable()) {
-        loadCommandFile(file, aSandboxPrincipal);
-      }
-    }
-  },
-};
-
-/**
- * Load the commands from a single file
- * @param nsIFile aFile The file containing the commands that we should read
- * @param nsIPrincipal aSandboxPrincipal Scope object for the Sandbox in which
- * we eval the script from the .mozcmd file. This should be a chrome window.
- */
-function loadCommandFile(aFile, aSandboxPrincipal) {
-  NetUtil.asyncFetch(aFile, function refresh_fetch(aStream, aStatus) {
-    if (!Components.isSuccessCode(aStatus)) {
-      console.error("NetUtil.asyncFetch(" + aFile.path + ",..) failed. Status=" + aStatus);
-      return;
-    }
-
-    let source = NetUtil.readInputStreamToString(aStream, aStream.available());
-    aStream.close();
-
-    let sandbox = new Cu.Sandbox(aSandboxPrincipal, {
-      sandboxPrototype: aSandboxPrincipal,
-      wantXrays: false,
-      sandboxName: aFile.path
-    });
-    let data = Cu.evalInSandbox(source, sandbox, "1.8", aFile.leafName, 1);
-
-    if (!Array.isArray(data)) {
-      console.error("Command file '" + aFile.leafName + "' does not have top level array.");
-      return;
-    }
-
-    data.forEach(function(commandSpec) {
-      gcli.addCommand(commandSpec);
-      commands.push(commandSpec.name);
-    });
-  }.bind(this));
-}
-
-/**
- * 'cmd' command
- */
-gcli.addCommand({
-  name: "cmd",
-  description: gcli.lookup("cmdDesc"),
-  hidden: true
-});
-
-/**
- * 'cmd refresh' command
- */
-gcli.addCommand({
-  name: "cmd refresh",
-  description: gcli.lookup("cmdRefreshDesc"),
-  hidden: true,
-  exec: function Command_cmdRefresh(args, context) {
-    GcliCommands.refreshAutoCommands(context.environment.chromeDocument.defaultView);
-  }
-});
-
-/**
- * 'echo' command
- */
-gcli.addCommand({
-  name: "echo",
-  description: gcli.lookup("echoDesc"),
-  params: [
-    {
-      name: "message",
-      type: "string",
-      description: gcli.lookup("echoMessageDesc")
-    }
-  ],
-  returnType: "string",
-  hidden: true,
-  exec: function Command_echo(args, context) {
-    return args.message;
-  }
-});
-
-
-/**
- * 'screenshot' command
- */
-gcli.addCommand({
-  name: "screenshot",
-  description: gcli.lookup("screenshotDesc"),
-  manual: gcli.lookup("screenshotManual"),
-  returnType: "string",
-  params: [
-    {
-      name: "filename",
-      type: "string",
-      description: gcli.lookup("screenshotFilenameDesc"),
-      manual: gcli.lookup("screenshotFilenameManual")
-    },
-    {
-      name: "delay",
-      type: { name: "number", min: 0 },
-      defaultValue: 0,
-      description: gcli.lookup("screenshotDelayDesc"),
-      manual: gcli.lookup("screenshotDelayManual")
-    },
-    {
-      name: "fullpage",
-      type: "boolean",
-      defaultValue: false,
-      description: gcli.lookup("screenshotFullPageDesc"),
-      manual: gcli.lookup("screenshotFullPageManual")
-    },
-    {
-      name: "node",
-      type: "node",
-      defaultValue: null,
-      description: gcli.lookup("inspectNodeDesc"),
-      manual: gcli.lookup("inspectNodeManual")
-    }
-  ],
-  exec: function Command_screenshot(args, context) {
-    var document = context.environment.contentDocument;
-    if (args.delay > 0) {
-      var promise = context.createPromise();
-      document.defaultView.setTimeout(function Command_screenshotDelay() {
-        let reply = this.grabScreen(document, args.filename);
-        promise.resolve(reply);
-      }.bind(this), args.delay * 1000);
-      return promise;
-    }
-    else {
-      return this.grabScreen(document, args.filename, args.fullpage, args.node);
-    }
-  },
-  grabScreen:
-  function Command_screenshotGrabScreen(document, filename, fullpage, node) {
-    let window = document.defaultView;
-    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-    let left = 0;
-    let top = 0;
-    let width;
-    let height;
-
-    if (!fullpage) {
-      if (!node) {
-        left = window.scrollX;
-        top = window.scrollY;
-        width = window.innerWidth;
-        height = window.innerHeight;
-      } else {
-        let rect = LayoutHelpers.getRect(node, window);
-        top = rect.top;
-        left = rect.left;
-        width = rect.width;
-        height = rect.height;
-      }
-    } else {
-      width = window.innerWidth + window.scrollMaxX;
-      height = window.innerHeight + window.scrollMaxY;
-    }
-    canvas.width = width;
-    canvas.height = height;
-
-    let ctx = canvas.getContext("2d");
-    ctx.drawWindow(window, left, top, width, height, "#fff");
-
-    let data = canvas.toDataURL("image/png", "");
-    let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile);
-
-    // Check there is a .png extension to filename
-    if (!filename.match(/.png$/i)) {
-      filename += ".png";
-    }
-
-    // If the filename is relative, tack it onto the download directory
-    if (!filename.match(/[\\\/]/)) {
-      let downloadMgr = Cc["@mozilla.org/download-manager;1"]
-        .getService(Ci.nsIDownloadManager);
-      let tempfile = downloadMgr.userDownloadsDirectory;
-      tempfile.append(filename);
-      filename = tempfile.path;
-    }
-
-    try {
-      file.initWithPath(filename);
-    } catch (ex) {
-      return "Error saving to " + filename;
-    }
-
-    let ioService = Cc["@mozilla.org/network/io-service;1"]
-      .getService(Ci.nsIIOService);
-
-    let Persist = Ci.nsIWebBrowserPersist;
-    let persist = Cc["@mozilla.org/embedding/browser/nsWebBrowserPersist;1"]
-      .createInstance(Persist);
-    persist.persistFlags = Persist.PERSIST_FLAGS_REPLACE_EXISTING_FILES |
-                           Persist.PERSIST_FLAGS_AUTODETECT_APPLY_CONVERSION;
-
-    let source = ioService.newURI(data, "UTF8", null);
-    persist.saveURI(source, null, null, null, null, file);
-
-    return "Saved to " + filename;
-  }
-});
-
-
-let callLogDebuggers = [];
-
-/**
- * 'calllog' command
- */
-gcli.addCommand({
-  name: "calllog",
-  description: gcli.lookup("calllogDesc")
-})
-
-/**
- * 'calllog start' command
- */
-gcli.addCommand({
-  name: "calllog start",
-  description: gcli.lookup("calllogStartDesc"),
-
-  exec: function(args, context) {
-    let contentWindow = context.environment.contentDocument.defaultView;
-
-    let dbg = new Debugger(contentWindow);
-    dbg.onEnterFrame = function(frame) {
-      // BUG 773652 -  Make the output from the GCLI calllog command nicer
-      contentWindow.console.log("Method call: " + this.callDescription(frame));
-    }.bind(this);
-
-    callLogDebuggers.push(dbg);
-
-    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab;
-    HUDService.activateHUDForContext(tab);
-
-    return gcli.lookup("calllogStartReply");
-  },
-
-  callDescription: function(frame) {
-    let name = "<anonymous>";
-    if (frame.callee.name) {
-      name = frame.callee.name;
-    }
-    else {
-      let desc = frame.callee.getOwnPropertyDescriptor("displayName");
-      if (desc && desc.value && typeof desc.value == "string") {
-        name = desc.value;
-      }
-    }
-
-    let args = frame.arguments.map(this.valueToString).join(", ");
-    return name + "(" + args + ")";
-  },
-
-  valueToString: function(value) {
-    if (typeof value !== "object" || value === null) {
-      return uneval(value);
-    }
-    return "[object " + value.class + "]";
-  }
-});
+let EXPORTED_SYMBOLS = [ ];
 
-/**
- * 'calllog stop' command
- */
-gcli.addCommand({
-  name: "calllog stop",
-  description: gcli.lookup("calllogStopDesc"),
-
-  exec: function(args, context) {
-    let numDebuggers = callLogDebuggers.length;
-    if (numDebuggers == 0) {
-      return gcli.lookup("calllogStopNoLogging");
-    }
-
-    for (let dbg of callLogDebuggers) {
-      dbg.onEnterFrame = undefined;
-    }
-    callLogDebuggers = [];
-
-    return gcli.lookupFormat("calllogStopReply", [ numDebuggers ]);
-  }
-});
-
-
-/**
- * 'console' command
- */
-gcli.addCommand({
-  name: "console",
-  description: gcli.lookup("consoleDesc"),
-  manual: gcli.lookup("consoleManual")
-});
-
-/**
- * 'console clear' command
- */
-gcli.addCommand({
-  name: "console clear",
-  description: gcli.lookup("consoleclearDesc"),
-  exec: function Command_consoleClear(args, context) {
-    let window = context.environment.contentDocument.defaultView;
-    let hud = HUDService.getHudByWindow(window);
-    // hud will be null if the web console has not been opened for this window
-    if (hud) {
-      hud.jsterm.clearOutput();
-    }
-  }
-});
-
-/**
- * 'console close' command
- */
-gcli.addCommand({
-  name: "console close",
-  description: gcli.lookup("consolecloseDesc"),
-  exec: function Command_consoleClose(args, context) {
-    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
-    HUDService.deactivateHUDForContext(tab);
-  }
-});
-
-/**
- * 'console open' command
- */
-gcli.addCommand({
-  name: "console open",
-  description: gcli.lookup("consoleopenDesc"),
-  exec: function Command_consoleOpen(args, context) {
-    let tab = context.environment.chromeDocument.defaultView.gBrowser.selectedTab
-    HUDService.activateHUDForContext(tab);
-  }
-});
-
-/**
- * Restart command
- *
- * @param boolean nocache
- *        Disables loading content from cache upon restart.
- *
- * Examples :
- * >> restart
- * - restarts browser immediately
- * >> restart --nocache
- * - restarts immediately and starts Firefox without using cache
- */
-gcli.addCommand({
-  name: "restart",
-  description: gcli.lookup("restartFirefoxDesc"),
-  params: [
-    {
-      name: "nocache",
-      type: "boolean",
-      defaultValue: false,
-      description: gcli.lookup("restartFirefoxNocacheDesc")
-    }
-  ],
-  returnType: "string",
-  exec: function Restart(args, context) {
-    let canceled = Cc["@mozilla.org/supports-PRBool;1"]
-                     .createInstance(Ci.nsISupportsPRBool);
-    Services.obs.notifyObservers(canceled, "quit-application-requested", "restart");
-    if (canceled.data) {
-      return gcli.lookup("restartFirefoxRequestCancelled");
-    }
-
-    // disable loading content from cache.
-    if (args.nocache) {
-      Services.appinfo.invalidateCachesOnRestart();
-    }
-
-    // restart
-    Cc['@mozilla.org/toolkit/app-startup;1']
-      .getService(Ci.nsIAppStartup)
-      .quit(Ci.nsIAppStartup.eAttemptQuit | Ci.nsIAppStartup.eRestart);
-    return gcli.lookup("restartFirefoxRestarting");
-  }
-});
-
-/**
- * 'inspect' command
- */
-gcli.addCommand({
-  name: "inspect",
-  description: gcli.lookup("inspectDesc"),
-  manual: gcli.lookup("inspectManual"),
-  params: [
-    {
-      name: "node",
-      type: "node",
-      description: gcli.lookup("inspectNodeDesc"),
-      manual: gcli.lookup("inspectNodeManual")
-    }
-  ],
-  exec: function Command_inspect(args, context) {
-    let document = context.environment.chromeDocument;
-    document.defaultView.InspectorUI.openInspectorUI(args.node);
-  }
-});
-
-/**
- * 'edit' command
- */
-gcli.addCommand({
-  name: "edit",
-  description: gcli.lookup("editDesc"),
-  manual: gcli.lookup("editManual2"),
-  params: [
-     {
-       name: 'resource',
-       type: {
-         name: 'resource',
-         include: 'text/css'
-       },
-       description: gcli.lookup("editResourceDesc")
-     },
-     {
-       name: "line",
-       defaultValue: 1,
-       type: {
-         name: "number",
-         min: 1,
-         step: 10
-       },
-       description: gcli.lookup("editLineToJumpToDesc")
-     }
-   ],
-   exec: function(args, context) {
-     let win = HUDService.currentContext();
-     win.StyleEditor.openChrome(args.resource.element, args.line);
-   }
-});
-
-/**
- * 'break' command
- */
-gcli.addCommand({
-  name: "break",
-  description: gcli.lookup("breakDesc"),
-  manual: gcli.lookup("breakManual")
-});
-
-
-/**
- * 'break list' command
- */
-gcli.addCommand({
-  name: "break list",
-  description: gcli.lookup("breaklistDesc"),
-  returnType: "html",
-  exec: function(args, context) {
-    let win = HUDService.currentContext();
-    let dbg = win.DebuggerUI.getDebugger();
-    if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
-    }
-    let breakpoints = dbg.breakpoints;
-
-    if (Object.keys(breakpoints).length === 0) {
-      return gcli.lookup("breaklistNone");
-    }
-
-    let reply = gcli.lookup("breaklistIntro");
-    reply += "<ol>";
-    for each (let breakpoint in breakpoints) {
-      let text = gcli.lookupFormat("breaklistLineEntry",
-                                   [breakpoint.location.url,
-                                    breakpoint.location.line]);
-      reply += "<li>" + text + "</li>";
-    };
-    reply += "</ol>";
-    return reply;
-  }
-});
-
-
-/**
- * 'break add' command
- */
-gcli.addCommand({
-  name: "break add",
-  description: gcli.lookup("breakaddDesc"),
-  manual: gcli.lookup("breakaddManual")
-});
-
-/**
- * 'break add line' command
- */
-gcli.addCommand({
-  name: "break add line",
-  description: gcli.lookup("breakaddlineDesc"),
-  params: [
-    {
-      name: "file",
-      type: {
-        name: "selection",
-        data: function() {
-          let win = HUDService.currentContext();
-          let dbg = win.DebuggerUI.getDebugger();
-          let files = [];
-          if (dbg) {
-            let scriptsView = dbg.contentWindow.DebuggerView.Scripts;
-            for each (let script in scriptsView.scriptLocations) {
-              files.push(script);
-            }
-          }
-          return files;
-        }
-      },
-      description: gcli.lookup("breakaddlineFileDesc")
-    },
-    {
-      name: "line",
-      type: { name: "number", min: 1, step: 10 },
-      description: gcli.lookup("breakaddlineLineDesc")
-    }
-  ],
-  returnType: "html",
-  exec: function(args, context) {
-    args.type = "line";
-    let win = HUDService.currentContext();
-    let dbg = win.DebuggerUI.getDebugger();
-    if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
-    }
-    var promise = context.createPromise();
-    let position = { url: args.file, line: args.line };
-    dbg.addBreakpoint(position, function(aBreakpoint, aError) {
-      if (aError) {
-        promise.resolve(gcli.lookupFormat("breakaddFailed", [aError]));
-        return;
-      }
-      promise.resolve(gcli.lookup("breakaddAdded"));
-    });
-    return promise;
-  }
-});
-
-
-/**
- * 'break del' command
- */
-gcli.addCommand({
-  name: "break del",
-  description: gcli.lookup("breakdelDesc"),
-  params: [
-    {
-      name: "breakid",
-      type: {
-        name: "number",
-        min: 0,
-        max: function() {
-          let win = HUDService.currentContext();
-          let dbg = win.DebuggerUI.getDebugger();
-          if (!dbg) {
-            return gcli.lookup("breakaddDebuggerStopped");
-          }
-          return Object.keys(dbg.breakpoints).length - 1;
-        },
-      },
-      description: gcli.lookup("breakdelBreakidDesc")
-    }
-  ],
-  returnType: "html",
-  exec: function(args, context) {
-    let win = HUDService.currentContext();
-    let dbg = win.DebuggerUI.getDebugger();
-    if (!dbg) {
-      return gcli.lookup("breakaddDebuggerStopped");
-    }
-
-    let breakpoints = dbg.breakpoints;
-    let id = Object.keys(dbg.breakpoints)[args.breakid];
-    if (!id || !(id in breakpoints)) {
-      return gcli.lookup("breakNotFound");
-    }
-
-    let promise = context.createPromise();
-    try {
-      dbg.removeBreakpoint(breakpoints[id], function() {
-        promise.resolve(gcli.lookup("breakdelRemoved"));
-      });
-    } catch (ex) {
-      // If the debugger has been closed already, don't scare the user.
-      promise.resolve(gcli.lookup("breakdelRemoved"));
-    }
-    return promise;
-  }
-});
-
-/**
- * 'export' command
- */
-gcli.addCommand({
-  name: "export",
-  description: gcli.lookup("exportDesc"),
-});
-
-/**
- * The 'export html' command. This command allows the user to export the page to
- * HTML after they do DOM changes.
- */
-gcli.addCommand({
-  name: "export html",
-  description: gcli.lookup("exportHtmlDesc"),
-  exec: function(args, context) {
-    let document = context.environment.contentDocument;
-    let window = document.defaultView;
-    let page = document.documentElement.outerHTML;
-    window.open('data:text/plain;charset=utf8,' + encodeURIComponent(page));
-  }
-});
-
-/**
- * 'pagemod' command
- */
-gcli.addCommand({
-  name: "pagemod",
-  description: gcli.lookup("pagemodDesc"),
-});
+const Cu = Components.utils;
 
-/**
- * The 'pagemod replace' command. This command allows the user to search and
- * replace within text nodes and attributes.
- */
-gcli.addCommand({
-  name: "pagemod replace",
-  description: gcli.lookup("pagemodReplaceDesc"),
-  params: [
-    {
-      name: "search",
-      type: "string",
-      description: gcli.lookup("pagemodReplaceSearchDesc"),
-    },
-    {
-      name: "replace",
-      type: "string",
-      description: gcli.lookup("pagemodReplaceReplaceDesc"),
-    },
-    {
-      name: "ignoreCase",
-      type: "boolean",
-      description: gcli.lookup("pagemodReplaceIgnoreCaseDesc"),
-    },
-    {
-      name: "selector",
-      type: "string",
-      description: gcli.lookup("pagemodReplaceSelectorDesc"),
-      defaultValue: "*:not(script):not(style):not(embed):not(object):not(frame):not(iframe):not(frameset)",
-    },
-    {
-      name: "root",
-      type: "node",
-      description: gcli.lookup("pagemodReplaceRootDesc"),
-      defaultValue: null,
-    },
-    {
-      name: "attrOnly",
-      type: "boolean",
-      description: gcli.lookup("pagemodReplaceAttrOnlyDesc"),
-    },
-    {
-      name: "contentOnly",
-      type: "boolean",
-      description: gcli.lookup("pagemodReplaceContentOnlyDesc"),
-    },
-    {
-      name: "attributes",
-      type: "string",
-      description: gcli.lookup("pagemodReplaceAttributesDesc"),
-      defaultValue: null,
-    },
-  ],
-  exec: function(args, context) {
-    let document = context.environment.contentDocument;
-    let searchTextNodes = !args.attrOnly;
-    let searchAttributes = !args.contentOnly;
-    let regexOptions = args.ignoreCase ? 'ig' : 'g';
-    let search = new RegExp(escapeRegex(args.search), regexOptions);
-    let attributeRegex = null;
-    if (args.attributes) {
-      attributeRegex = new RegExp(args.attributes, regexOptions);
-    }
-
-    let root = args.root || document;
-    let elements = root.querySelectorAll(args.selector);
-    elements = Array.prototype.slice.call(elements);
-
-    let replacedTextNodes = 0;
-    let replacedAttributes = 0;
-
-    function replaceAttribute() {
-      replacedAttributes++;
-      return args.replace;
-    }
-    function replaceTextNode() {
-      replacedTextNodes++;
-      return args.replace;
-    }
-
-    for (let i = 0; i < elements.length; i++) {
-      let element = elements[i];
-      if (searchTextNodes) {
-        for (let y = 0; y < element.childNodes.length; y++) {
-          let node = element.childNodes[y];
-          if (node.nodeType == node.TEXT_NODE) {
-            node.textContent = node.textContent.replace(search, replaceTextNode);
-          }
-        }
-      }
-
-      if (searchAttributes) {
-        if (!element.attributes) {
-          continue;
-        }
-        for (let y = 0; y < element.attributes.length; y++) {
-          let attr = element.attributes[y];
-          if (!attributeRegex || attributeRegex.test(attr.name)) {
-            attr.value = attr.value.replace(search, replaceAttribute);
-          }
-        }
-      }
-    }
-
-    return gcli.lookupFormat("pagemodReplaceResult",
-                             [elements.length, replacedTextNodes,
-                              replacedAttributes]);
-  }
-});
-
-/**
- * 'pagemod remove' command
- */
-gcli.addCommand({
-  name: "pagemod remove",
-  description: gcli.lookup("pagemodRemoveDesc"),
-});
-
-
-/**
- * The 'pagemod remove element' command.
- */
-gcli.addCommand({
-  name: "pagemod remove element",
-  description: gcli.lookup("pagemodRemoveElementDesc"),
-  params: [
-    {
-      name: "search",
-      type: "string",
-      description: gcli.lookup("pagemodRemoveElementSearchDesc"),
-    },
-    {
-      name: "root",
-      type: "node",
-      description: gcli.lookup("pagemodRemoveElementRootDesc"),
-      defaultValue: null,
-    },
-    {
-      name: 'stripOnly',
-      type: 'boolean',
-      description: gcli.lookup("pagemodRemoveElementStripOnlyDesc"),
-    },
-    {
-      name: 'ifEmptyOnly',
-      type: 'boolean',
-      description: gcli.lookup("pagemodRemoveElementIfEmptyOnlyDesc"),
-    },
-  ],
-  exec: function(args, context) {
-    let document = context.environment.contentDocument;
-    let root = args.root || document;
-    let elements = Array.prototype.slice.call(root.querySelectorAll(args.search));
-
-    let removed = 0;
-    for (let i = 0; i < elements.length; i++) {
-      let element = elements[i];
-      let parentNode = element.parentNode;
-      if (!parentNode || !element.removeChild) {
-        continue;
-      }
-      if (args.stripOnly) {
-        while (element.hasChildNodes()) {
-          parentNode.insertBefore(element.childNodes[0], element);
-        }
-      }
-      if (!args.ifEmptyOnly || !element.hasChildNodes()) {
-        element.parentNode.removeChild(element);
-        removed++;
-      }
-    }
-
-    return gcli.lookupFormat("pagemodRemoveElementResultMatchedAndRemovedElements",
-                             [elements.length, removed]);
-  }
-});
-
-/**
- * The 'pagemod remove attribute' command.
- */
-gcli.addCommand({
-  name: "pagemod remove attribute",
-  description: gcli.lookup("pagemodRemoveAttributeDesc"),
-  params: [
-    {
-      name: "searchAttributes",
-      type: "string",
-      description: gcli.lookup("pagemodRemoveAttributeSearchAttributesDesc"),
-    },
-    {
-      name: "searchElements",
-      type: "string",
-      description: gcli.lookup("pagemodRemoveAttributeSearchElementsDesc"),
-    },
-    {
-      name: "root",
-      type: "node",
-      description: gcli.lookup("pagemodRemoveAttributeRootDesc"),
-      defaultValue: null,
-    },
-    {
-      name: "ignoreCase",
-      type: "boolean",
-      description: gcli.lookup("pagemodRemoveAttributeIgnoreCaseDesc"),
-    },
-  ],
-  exec: function(args, context) {
-    let document = context.environment.contentDocument;
-
-    let root = args.root || document;
-    let regexOptions = args.ignoreCase ? 'ig' : 'g';
-    let attributeRegex = new RegExp(args.searchAttributes, regexOptions);
-    let elements = root.querySelectorAll(args.searchElements);
-    elements = Array.prototype.slice.call(elements);
-
-    let removed = 0;
-    for (let i = 0; i < elements.length; i++) {
-      let element = elements[i];
-      if (!element.attributes) {
-        continue;
-      }
-
-      var attrs = Array.prototype.slice.call(element.attributes);
-      for (let y = 0; y < attrs.length; y++) {
-        let attr = attrs[y];
-        if (attributeRegex.test(attr.name)) {
-          element.removeAttribute(attr.name);
-          removed++;
-        }
-      }
-    }
-
-    return gcli.lookupFormat("pagemodRemoveAttributeResult",
-                             [elements.length, removed]);
-  }
-});
-
-
-/**
- * Make a given string safe to use  in a regular expression.
- *
- * @param string aString
- *        The string you want to use in a regex.
- * @return string
- *         The equivalent of |aString| but safe to use in a regex.
- */
-function escapeRegex(aString) {
-  return aString.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, "\\$&");
-}
-
-/**
- * 'addon' command.
- */
-gcli.addCommand({
-  name: "addon",
-  description: gcli.lookup("addonDesc")
-});
-
-/**
- * 'addon list' command.
- */
-gcli.addCommand({
-  name: "addon list",
-  description: gcli.lookup("addonListDesc"),
-  params: [{
-    name: 'type',
-    type: {
-      name: 'selection',
-      data: ["dictionary", "extension", "locale", "plugin", "theme", "all"]
-    },
-    defaultValue: 'all',
-    description: gcli.lookup("addonListTypeDesc"),
-  }],
-  exec: function(aArgs, context) {
-    function representEnabledAddon(aAddon) {
-      return "<li><![CDATA[" + aAddon.name + "\u2002" + aAddon.version +
-      getAddonStatus(aAddon) + "]]></li>";
-    }
-
-    function representDisabledAddon(aAddon) {
-      return "<li class=\"gcli-addon-disabled\">" +
-        "<![CDATA[" + aAddon.name + "\u2002" + aAddon.version + aAddon.version +
-        "]]></li>";
-    }
-
-    function getAddonStatus(aAddon) {
-      let operations = [];
-
-      if (aAddon.pendingOperations & AddonManager.PENDING_ENABLE) {
-        operations.push("PENDING_ENABLE");
-      }
-
-      if (aAddon.pendingOperations & AddonManager.PENDING_DISABLE) {
-        operations.push("PENDING_DISABLE");
-      }
-
-      if (aAddon.pendingOperations & AddonManager.PENDING_UNINSTALL) {
-        operations.push("PENDING_UNINSTALL");
-      }
-
-      if (aAddon.pendingOperations & AddonManager.PENDING_INSTALL) {
-        operations.push("PENDING_INSTALL");
-      }
-
-      if (aAddon.pendingOperations & AddonManager.PENDING_UPGRADE) {
-        operations.push("PENDING_UPGRADE");
-      }
-
-      if (operations.length) {
-        return " (" + operations.join(", ") + ")";
-      }
-      return "";
-    }
-
-    /**
-     * Compares two addons by their name. Used in sorting.
-     */
-    function compareAddonNames(aNameA, aNameB) {
-      return String.localeCompare(aNameA.name, aNameB.name);
-    }
-
-    /**
-     * Resolves the promise which is the scope (this) of this function, filling
-     * it with an HTML representation of the passed add-ons.
-     */
-    function list(aType, aAddons) {
-      if (!aAddons.length) {
-        this.resolve(gcli.lookup("addonNoneOfType"));
-      }
-
-      // Separate the enabled add-ons from the disabled ones.
-      let enabledAddons = [];
-      let disabledAddons = [];
-
-      aAddons.forEach(function(aAddon) {
-        if (aAddon.isActive) {
-          enabledAddons.push(aAddon);
-        } else {
-          disabledAddons.push(aAddon);
-        }
-      });
-
-      let header;
-      switch(aType) {
-        case "dictionary":
-          header = gcli.lookup("addonListDictionaryHeading");
-          break;
-        case "extension":
-          header = gcli.lookup("addonListExtensionHeading");
-          break;
-        case "locale":
-          header = gcli.lookup("addonListLocaleHeading");
-          break;
-        case "plugin":
-          header = gcli.lookup("addonListPluginHeading");
-          break;
-        case "theme":
-          header = gcli.lookup("addonListThemeHeading");
-        case "all":
-          header = gcli.lookup("addonListAllHeading");
-          break;
-        default:
-          header = gcli.lookup("addonListUnknownHeading");
-      }
-
-      // Map and sort the add-ons, and create an HTML list.
-      this.resolve(header +
-        "<ol>" +
-        enabledAddons.sort(compareAddonNames).map(representEnabledAddon).join("") +
-        disabledAddons.sort(compareAddonNames).map(representDisabledAddon).join("") +
-        "</ol>");
-    }
-
-    // Create the promise that will be resolved when the add-on listing has
-    // been finished.
-    let promise = context.createPromise();
-    let types = aArgs.type == "all" ? null : [aArgs.type];
-    AddonManager.getAddonsByTypes(types, list.bind(promise, aArgs.type));
-    return promise;
-  }
-});
-
-
-/**
- * 'dbg' command
- */
-gcli.addCommand({
-  name: "dbg",
-  description: gcli.lookup("dbgDesc"),
-  manual: gcli.lookup("dbgManual")
-});
-
-
-/**
- * 'dbg interrupt' command
- */
-gcli.addCommand({
-  name: "dbg interrupt",
-  description: gcli.lookup("dbgInterrupt"),
-  params: [],
-  exec: function(args, context) {
-    let win = context.environment.chromeDocument.defaultView;
-    let dbg = win.DebuggerUI.getDebugger();
-
-    if (dbg) {
-      let controller = dbg.contentWindow.DebuggerController;
-      let thread = controller.activeThread;
-      if (!thread.paused) {
-        thread.interrupt();
-      }
-    }
-  }
-});
-
-/**
- * 'dbg continue' command
- */
-gcli.addCommand({
-  name: "dbg continue",
-  description: gcli.lookup("dbgContinue"),
-  params: [],
-  exec: function(args, context) {
-    let win = context.environment.chromeDocument.defaultView;
-    let dbg = win.DebuggerUI.getDebugger();
-
-    if (dbg) {
-      let controller = dbg.contentWindow.DebuggerController;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.resume();
-      }
-    }
-  }
-});
-
-
-/**
- * 'dbg step' command
- */
-gcli.addCommand({
-  name: "dbg step",
-  description: gcli.lookup("dbgStepDesc"),
-  manual: gcli.lookup("dbgStepManual")
-});
-
-
-/**
- * 'dbg step over' command
- */
-gcli.addCommand({
-  name: "dbg step over",
-  description: gcli.lookup("dbgStepOverDesc"),
-  params: [],
-  exec: function(args, context) {
-    let win = context.environment.chromeDocument.defaultView;
-    let dbg = win.DebuggerUI.getDebugger();
-
-    if (dbg) {
-      let controller = dbg.contentWindow.DebuggerController;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepOver();
-      }
-    }
-  }
-});
-
-/**
- * 'dbg step in' command
- */
-gcli.addCommand({
-  name: 'dbg step in',
-  description: gcli.lookup("dbgStepInDesc"),
-  params: [],
-  exec: function(args, context) {
-    let win = context.environment.chromeDocument.defaultView;
-    let dbg = win.DebuggerUI.getDebugger();
-
-    if (dbg) {
-      let controller = dbg.contentWindow.DebuggerController;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepIn();
-      }
-    }
-  }
-});
-
-/**
- * 'dbg step over' command
- */
-gcli.addCommand({
-  name: 'dbg step out',
-  description: gcli.lookup("dbgStepOutDesc"),
-  params: [],
-  exec: function(args, context) {
-    let win = context.environment.chromeDocument.defaultView;
-    let dbg = win.DebuggerUI.getDebugger();
-
-    if (dbg) {
-      let controller = dbg.contentWindow.DebuggerController;
-      let thread = controller.activeThread;
-      if (thread.paused) {
-        thread.stepOut();
-      }
-    }
-  }
-});
-
-// We need a list of addon names for the enable and disable commands. Because
-// getting the name list is async we do not add the commands until we have the
-// list.
-AddonManager.getAllAddons(function addonAsync(aAddons) {
-  // We listen for installs to keep our addon list up to date. There is no need
-  // to listen for uninstalls because uninstalled addons are simply disabled
-  // until restart (to enable undo functionality).
-  AddonManager.addAddonListener({
-    onInstalled: function(aAddon) {
-      addonNameCache.push({
-        name: representAddon(aAddon).replace(/\s/g, "_"),
-        value: aAddon.name
-      });
-    },
-    onUninstalled: function(aAddon) {
-      let name = representAddon(aAddon).replace(/\s/g, "_");
-
-      for (let i = 0; i < addonNameCache.length; i++) {
-        if(addonNameCache[i].name == name) {
-          addonNameCache.splice(i, 1);
-          break;
-        }
-      }
-    },
-  });
-
-  /**
-   * Returns a string that represents the passed add-on.
-   */
-  function representAddon(aAddon) {
-    let name = aAddon.name + " " + aAddon.version;
-    return name.trim();
-  }
-
-  let addonNameCache = [];
-
-  // The name parameter, used in "addon enable" and "addon disable."
-  let nameParameter = {
-    name: "name",
-    type: {
-      name: "selection",
-      lookup: addonNameCache
-    },
-    description: gcli.lookup("addonNameDesc")
-  };
-
-  for (let addon of aAddons) {
-    addonNameCache.push({
-      name: representAddon(addon).replace(/\s/g, "_"),
-      value: addon.name
-    });
-  }
-
-  /**
-   * 'addon enable' command.
-   */
-  gcli.addCommand({
-    name: "addon enable",
-    description: gcli.lookup("addonEnableDesc"),
-    params: [nameParameter],
-    exec: function(aArgs, context) {
-      /**
-       * Enables the addon in the passed list which has a name that matches
-       * according to the passed name comparer, and resolves the promise which
-       * is the scope (this) of this function to display the result of this
-       * enable attempt.
-       */
-      function enable(aName, addons) {
-        // Find the add-on.
-        let addon = null;
-        addons.some(function(candidate) {
-          if (candidate.name == aName) {
-            addon = candidate;
-            return true;
-          } else {
-            return false;
-          }
-        });
-
-        let name = representAddon(addon);
-
-        if (!addon.userDisabled) {
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonAlreadyEnabled", [name]) + "]]>");
-        } else {
-          addon.userDisabled = false;
-          // nl-nl: {$1} is ingeschakeld.
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonEnabled", [name]) + "]]>");
-        }
-      }
-
-      let promise = context.createPromise();
-      // List the installed add-ons, enable one when done listing.
-      AddonManager.getAllAddons(enable.bind(promise, aArgs.name));
-      return promise;
-    }
-  });
-
-  /**
-   * 'addon disable' command.
-   */
-  gcli.addCommand({
-    name: "addon disable",
-    description: gcli.lookup("addonDisableDesc"),
-    params: [nameParameter],
-    exec: function(aArgs, context) {
-      /**
-       * Like enable, but ... you know ... the exact opposite.
-       */
-      function disable(aName, addons) {
-        // Find the add-on.
-        let addon = null;
-        addons.some(function(candidate) {
-          if (candidate.name == aName) {
-            addon = candidate;
-            return true;
-          } else {
-            return false;
-          }
-        });
-
-        let name = representAddon(addon);
-
-        if (addon.userDisabled) {
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonAlreadyDisabled", [name]) + "]]>");
-        } else {
-          addon.userDisabled = true;
-          // nl-nl: {$1} is uitgeschakeld.
-          this.resolve("<![CDATA[" +
-            gcli.lookupFormat("addonDisabled", [name]) + "]]>");
-        }
-      }
-
-      let promise = context.createPromise();
-      // List the installed add-ons, disable one when done listing.
-      AddonManager.getAllAddons(disable.bind(promise, aArgs.name));
-      return promise;
-    }
-  });
-  Services.obs.notifyObservers(null, "gcli_addon_commands_ready", null);
-});
-
-/* Responsive Mode commands */
-(function gcli_cmd_resize_container() {
-  function gcli_cmd_resize(args, context) {
-    let browserDoc = context.environment.chromeDocument;
-    let browserWindow = browserDoc.defaultView;
-    let mgr = browserWindow.ResponsiveUI.ResponsiveUIManager;
-    mgr.handleGcliCommand(browserWindow,
-                          browserWindow.gBrowser.selectedTab,
-                          this.name,
-                          args);
-  }
-
-  gcli.addCommand({
-    name: 'resize',
-    description: gcli.lookup('resizeModeDesc')
-  });
-
-  gcli.addCommand({
-    name: 'resize on',
-    description: gcli.lookup('resizeModeOnDesc'),
-    manual: gcli.lookup('resizeModeManual'),
-    exec: gcli_cmd_resize
-  });
-
-  gcli.addCommand({
-    name: 'resize off',
-    description: gcli.lookup('resizeModeOffDesc'),
-    manual: gcli.lookup('resizeModeManual'),
-    exec: gcli_cmd_resize
-  });
-
-  gcli.addCommand({
-    name: 'resize toggle',
-    description: gcli.lookup('resizeModeToggleDesc'),
-    manual: gcli.lookup('resizeModeManual'),
-    exec: gcli_cmd_resize
-  });
-
-  gcli.addCommand({
-    name: 'resize to',
-    description: gcli.lookup('resizeModeToDesc'),
-    params: [
-      {
-        name: 'width',
-        type: 'number',
-        description: gcli.lookup("resizePageArgWidthDesc"),
-      },
-      {
-        name: 'height',
-        type: 'number',
-        description: gcli.lookup("resizePageArgHeightDesc"),
-      },
-    ],
-    exec: gcli_cmd_resize
-  });
-})();
-
-/**
- * jsb command.
- */
-gcli.addCommand({
-  name: 'jsb',
-  description: gcli.lookup('jsbDesc'),
-  returnValue:'string',
-  hidden: true,
-  params: [
-    {
-      name: 'url',
-      type: 'string',
-      description: gcli.lookup('jsbUrlDesc'),
-      manual: 'The URL of the JS to prettify'
-    },
-    {
-      name: 'indentSize',
-      type: 'number',
-      description: gcli.lookup('jsbIndentSizeDesc'),
-      manual: gcli.lookup('jsbIndentSizeManual'),
-      defaultValue: 2
-    },
-    {
-      name: 'indentChar',
-      type: {
-        name: 'selection',
-        lookup: [{name: "space", value: " "}, {name: "tab", value: "\t"}]
-      },
-      description: gcli.lookup('jsbIndentCharDesc'),
-      manual: gcli.lookup('jsbIndentCharManual'),
-      defaultValue: ' ',
-    },
-    {
-      name: 'preserveNewlines',
-      type: 'boolean',
-      description: gcli.lookup('jsbPreserveNewlinesDesc'),
-      manual: gcli.lookup('jsbPreserveNewlinesManual'),
-      defaultValue: true
-    },
-    {
-      name: 'preserveMaxNewlines',
-      type: 'number',
-      description: gcli.lookup('jsbPreserveMaxNewlinesDesc'),
-      manual: gcli.lookup('jsbPreserveMaxNewlinesManual'),
-      defaultValue: -1
-    },
-    {
-      name: 'jslintHappy',
-      type: 'boolean',
-      description: gcli.lookup('jsbJslintHappyDesc'),
-      manual: gcli.lookup('jsbJslintHappyManual'),
-      defaultValue: false
-    },
-    {
-      name: 'braceStyle',
-      type: {
-        name: 'selection',
-        data: ['collapse', 'expand', 'end-expand', 'expand-strict']
-      },
-      description: gcli.lookup('jsbBraceStyleDesc'),
-      manual: gcli.lookup('jsbBraceStyleManual'),
-      defaultValue: "collapse"
-    },
-    {
-      name: 'spaceBeforeConditional',
-      type: 'boolean',
-      description: gcli.lookup('jsbSpaceBeforeConditionalDesc'),
-      manual: gcli.lookup('jsbSpaceBeforeConditionalManual'),
-      defaultValue: true
-    },
-    {
-      name: 'unescapeStrings',
-      type: 'boolean',
-      description: gcli.lookup('jsbUnescapeStringsDesc'),
-      manual: gcli.lookup('jsbUnescapeStringsManual'),
-      defaultValue: false
-    }
-  ],
-  exec: function(args, context) {
-  let opts = {
-    indent_size: args.indentSize,
-    indent_char: args.indentChar,
-    preserve_newlines: args.preserveNewlines,
-    max_preserve_newlines: args.preserveMaxNewlines == -1 ?
-                           undefined : args.preserveMaxNewlines,
-    jslint_happy: args.jslintHappy,
-    brace_style: args.braceStyle,
-    space_before_conditional: args.spaceBeforeConditional,
-    unescape_strings: args.unescapeStrings
-  }
-
-  let xhr = new XMLHttpRequest();
-
-  try {
-    xhr.open("GET", args.url, true);
-  } catch(e) {
-    return gcli.lookup('jsbInvalidURL');
-  }
-
-  let promise = context.createPromise();
-
-  xhr.onreadystatechange = function(aEvt) {
-    if (xhr.readyState == 4) {
-      if (xhr.status == 200 || xhr.status == 0) {
-        let browserDoc = context.environment.chromeDocument;
-        let browserWindow = browserDoc.defaultView;
-        let browser = browserWindow.gBrowser;
-
-        browser.selectedTab = browser.addTab("data:text/plain;base64," +
-          browserWindow.btoa(js_beautify(xhr.responseText, opts)));
-        promise.resolve();
-      }
-      else {
-        promise.resolve("Unable to load page to beautify: " + args.url + " " +
-                        xhr.status + " " + xhr.statusText);
-      }
-    };
-  }
-  xhr.send(null);
-  return promise;
-  }
-});
+Cu.import("resource:///modules/devtools/CmdAddon.jsm");
+Cu.import("resource:///modules/devtools/CmdBreak.jsm");
+Cu.import("resource:///modules/devtools/CmdCalllog.jsm");
+Cu.import("resource:///modules/devtools/CmdCalllogChrome.jsm");
+Cu.import("resource:///modules/devtools/CmdConsole.jsm");
+Cu.import("resource:///modules/devtools/CmdCookie.jsm");
+Cu.import("resource:///modules/devtools/CmdDbg.jsm");
+Cu.import("resource:///modules/devtools/CmdEcho.jsm");
+Cu.import("resource:///modules/devtools/CmdEdit.jsm");
+Cu.import("resource:///modules/devtools/CmdExport.jsm");
+Cu.import("resource:///modules/devtools/CmdInspect.jsm");
+Cu.import("resource:///modules/devtools/CmdJsb.jsm");
+Cu.import("resource:///modules/devtools/CmdPagemod.jsm");
+Cu.import("resource:///modules/devtools/CmdResize.jsm");
+Cu.import("resource:///modules/devtools/CmdRestart.jsm");
+Cu.import("resource:///modules/devtools/CmdScreenshot.jsm");
+Cu.import("resource:///modules/devtools/CmdTilt.jsm");
rename from browser/devtools/commandline/gcli.css
rename to browser/devtools/commandline/commandline.css
rename from browser/devtools/commandline/gclioutput.xhtml
rename to browser/devtools/commandline/commandlineoutput.xhtml
--- a/browser/devtools/commandline/gclioutput.xhtml
+++ b/browser/devtools/commandline/commandlineoutput.xhtml
@@ -4,15 +4,15 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://browser/content/devtools/gcli.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://browser/skin/devtools/gcli.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/content/devtools/commandline.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/skin/devtools/commandline.css" type="text/css"/>
 </head>
 <body class="gcli-body">
 <div id="gcli-output-root"></div>
 </body>
 </html>
rename from browser/devtools/commandline/gclitooltip.xhtml
rename to browser/devtools/commandline/commandlinetooltip.xhtml
--- a/browser/devtools/commandline/gclitooltip.xhtml
+++ b/browser/devtools/commandline/commandlinetooltip.xhtml
@@ -4,16 +4,16 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml">
 <head>
   <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
   <link rel="stylesheet" href="chrome://global/skin/global.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://browser/content/devtools/gcli.css" type="text/css"/>
-  <link rel="stylesheet" href="chrome://browser/skin/devtools/gcli.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/content/devtools/commandline.css" type="text/css"/>
+  <link rel="stylesheet" href="chrome://browser/skin/devtools/commandline.css" type="text/css"/>
 </head>
 <body class="gcli-body">
 <div id="gcli-tooltip-root"></div>
 <div id="gcli-tooltip-connector"></div>
 </body>
 </html>
--- a/browser/devtools/commandline/gcli.jsm
+++ b/browser/devtools/commandline/gcli.jsm
@@ -101,16 +101,22 @@ define('gcli/index', ['require', 'export
   require('gcli/ui/focus').startup();
   require('gcli/ui/fields/basic').startup();
   require('gcli/ui/fields/javascript').startup();
   require('gcli/ui/fields/selection').startup();
 
   require('gcli/commands/help').startup();
   require('gcli/commands/pref').startup();
 
+  var Cc = Components.classes;
+  var Ci = Components.interfaces;
+  var prefSvc = "@mozilla.org/preferences-service;1";
+  var prefService = Cc[prefSvc].getService(Ci.nsIPrefService);
+  var prefBranch = prefService.getBranch(null).QueryInterface(Ci.nsIPrefBranch2);
+
   // The API for use by command authors
   exports.addCommand = require('gcli/canon').addCommand;
   exports.removeCommand = require('gcli/canon').removeCommand;
   exports.lookup = mozl10n.lookup;
   exports.lookupFormat = mozl10n.lookupFormat;
 
   /**
    * This code is internal and subject to change without notice.
@@ -126,16 +132,20 @@ define('gcli/index', ['require', 'export
    * - hintElement: GCLITerm.hintNode
    * - inputBackgroundElement: GCLITerm.inputStack
    */
   exports.createDisplay = function(opts) {
     var FFDisplay = require('gcli/ui/ffdisplay').FFDisplay;
     return new FFDisplay(opts);
   };
 
+  exports.hiddenByChromePref = function() {
+    return !prefBranch.prefHasUserValue("devtools.chrome.enabled");
+  };
+
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -800,47 +810,18 @@ Conversion.prototype.toString = function
 Conversion.prototype.getPredictions = function() {
   if (typeof this.predictions === 'function') {
     return this.predictions();
   }
   return this.predictions || [];
 };
 
 /**
- * Accessor for a prediction by index.
- * This is useful above <tt>getPredictions()[index]</tt> because it normalizes
- * index to be within the bounds of the predictions, which means that the UI
- * can maintain an index of which prediction to choose without caring how many
- * predictions there are.
- * @param index The index of the prediction to choose
- */
-Conversion.prototype.getPredictionAt = function(index) {
-  if (index == null) {
-    return undefined;
-  }
-
-  var predictions = this.getPredictions();
-  if (predictions.length === 0) {
-    return undefined;
-  }
-
-  index = index % predictions.length;
-  if (index < 0) {
-    index = predictions.length + index;
-  }
-  return predictions[index];
-};
-
-/**
- * Accessor for a prediction by index.
- * This is useful above <tt>getPredictions()[index]</tt> because it normalizes
- * index to be within the bounds of the predictions, which means that the UI
- * can maintain an index of which prediction to choose without caring how many
- * predictions there are.
- * @param index The index of the prediction to choose
+ * Return an index constrained by the available predictions. Basically
+ * (index % predicitons.length)
  */
 Conversion.prototype.constrainPredictionIndex = function(index) {
   if (index == null) {
     return undefined;
   }
 
   var predictions = this.getPredictions();
   if (predictions.length === 0) {
@@ -1122,17 +1103,16 @@ exports.getType = function(typeSpec) {
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 define('gcli/argument', ['require', 'exports', 'module' ], function(require, exports, module) {
-var argument = exports;
 
 
 /**
  * Thinking out loud here:
  * Arguments are an area where we could probably refactor things a bit better.
  * The split process in Requisition creates a set of Arguments, which are then
  * assigned. The assign process sometimes converts them into subtypes of
  * Argument. We might consider that what gets assigned is _always_ one of the
@@ -1176,34 +1156,62 @@ Argument.prototype.merge = function(foll
   // Is it possible that this gets called when we're merging arguments
   // for the single string?
   return new Argument(
     this.text + this.suffix + following.prefix + following.text,
     this.prefix, following.suffix);
 };
 
 /**
- * Returns a new Argument like this one but with the text set to
- * <tt>replText</tt> and the end adjusted to fit.
- * @param replText Text to replace the old text value
- */
-Argument.prototype.beget = function(replText, options) {
+ * Returns a new Argument like this one but with various items changed.
+ * @param options Values to use in creating a new Argument.
+ * Warning: some implementations of beget make additions to the options
+ * argument. You should be aware of this in the unlikely event that you want to
+ * reuse 'options' arguments.
+ * Properties:
+ * - text: The new text value
+ * - prefixSpace: Should the prefix be altered to begin with a space?
+ * - prefixPostSpace: Should the prefix be altered to end with a space?
+ * - suffixSpace: Should the suffix be altered to end with a space?
+ * - type: Constructor to use in creating new instances. Default: Argument
+ */
+Argument.prototype.beget = function(options) {
+  var text = this.text;
   var prefix = this.prefix;
   var suffix = this.suffix;
 
-  // We need to add quotes when the replacement string has spaces or is empty
-  var quote = (replText.indexOf(' ') >= 0 || replText.length == 0) ?
-      '\'' : '';
-
-  if (options) {
-    prefix = (options.prefixSpace ? ' ' : '') + quote;
-    suffix = quote;
-  }
-
-  return new Argument(replText, prefix, suffix);
+  if (options.text != null) {
+    text = options.text;
+
+    // We need to add quotes when the replacement string has spaces or is empty
+    var needsQuote = text.indexOf(' ') >= 0 || text.length == 0;
+    if (needsQuote && /['"]/.test(prefix)) {
+      prefix = prefix + '\'';
+      suffix = '\'' + suffix;
+    }
+  }
+
+  if (options.prefixSpace && prefix.charAt(0) !== ' ') {
+    prefix = ' ' + prefix;
+  }
+
+  if (options.prefixPostSpace && prefix.charAt(prefix.length - 1) !== ' ') {
+    prefix = prefix + ' ';
+  }
+
+  if (options.suffixSpace && suffix.charAt(suffix.length - 1) !== ' ') {
+    suffix = suffix + ' ';
+  }
+
+  if (text === this.text && suffix === this.suffix && prefix === this.prefix) {
+    return this;
+  }
+
+  var type = options.type || Argument;
+  return new type(text, prefix, suffix);
 };
 
 /**
  * We need to keep track of which assignment we've been assigned to
  */
 Argument.prototype.assign = function(assignment) {
   this.assignment = assignment;
 };
@@ -1277,81 +1285,85 @@ Object.defineProperty(Argument.prototype
             'null' :
             this.assignment.param.name;
     return '<' + this.prefix + ':' + this.text + ':' + this.suffix + '>' +
         ' (a=' + assignStatus + ',' + ' t=' + this.type + ')';
   },
   enumerable: true
 });
 
-argument.Argument = Argument;
+exports.Argument = Argument;
 
 
 /**
  * BlankArgument is a marker that the argument wasn't typed but is there to
  * fill a slot. Assignments begin with their arg set to a BlankArgument.
  */
 function BlankArgument() {
   this.text = '';
   this.prefix = '';
   this.suffix = '';
 }
 
 BlankArgument.prototype = Object.create(Argument.prototype);
 
 BlankArgument.prototype.type = 'BlankArgument';
 
-argument.BlankArgument = BlankArgument;
+exports.BlankArgument = BlankArgument;
 
 
 /**
  * ScriptArgument is a marker that the argument is designed to be Javascript.
  * It also implements the special rules that spaces after the { or before the
  * } are part of the pre/suffix rather than the content, and that they are
  * never 'blank' so they can be used by Requisition._split() and not raise an
  * ERROR status due to being blank.
  */
 function ScriptArgument(text, prefix, suffix) {
   this.text = text !== undefined ? text : '';
   this.prefix = prefix !== undefined ? prefix : '';
   this.suffix = suffix !== undefined ? suffix : '';
 
-  while (this.text.charAt(0) === ' ') {
-    this.prefix = this.prefix + ' ';
-    this.text = this.text.substring(1);
-  }
-
-  while (this.text.charAt(this.text.length - 1) === ' ') {
-    this.suffix = ' ' + this.suffix;
-    this.text = this.text.slice(0, -1);
-  }
+  ScriptArgument._moveSpaces(this);
 }
 
 ScriptArgument.prototype = Object.create(Argument.prototype);
 
 ScriptArgument.prototype.type = 'ScriptArgument';
 
 /**
- * Returns a new Argument like this one but with the text set to
- * <tt>replText</tt> and the end adjusted to fit.
- * @param replText Text to replace the old text value
- */
-ScriptArgument.prototype.beget = function(replText, options) {
-  var prefix = this.prefix;
-  var suffix = this.suffix;
-
-  if (options && options.normalize) {
-    prefix = '{ ';
-    suffix = ' }';
-  }
-
-  return new ScriptArgument(replText, prefix, suffix);
-};
-
-argument.ScriptArgument = ScriptArgument;
+ * Private/Dangerous: Alters a ScriptArgument to move the spaces at the start
+ * or end of the 'text' into the prefix/suffix. With a string, " a " is 3 chars
+ * long, but with a ScriptArgument, { a } is only one char long.
+ * Arguments are generally supposed to be immutable, so this method should only
+ * be called on a ScriptArgument that isn't exposed to the outside world yet.
+ */
+ScriptArgument._moveSpaces = function(arg) {
+  while (arg.text.charAt(0) === ' ') {
+    arg.prefix = arg.prefix + ' ';
+    arg.text = arg.text.substring(1);
+  }
+
+  while (arg.text.charAt(arg.text.length - 1) === ' ') {
+    arg.suffix = ' ' + arg.suffix;
+    arg.text = arg.text.slice(0, -1);
+  }
+};
+
+/**
+ * As Argument.beget that implements the space rule documented in the ctor.
+ */
+ScriptArgument.prototype.beget = function(options) {
+  options.type = ScriptArgument;
+  var begotten = Argument.prototype.beget.call(this, options);
+  ScriptArgument._moveSpaces(begotten);
+  return begotten;
+};
+
+exports.ScriptArgument = ScriptArgument;
 
 
 /**
  * Commands like 'echo' with a single string argument, and used with the
  * special format like: 'echo a b c' effectively have a number of arguments
  * merged together.
  */
 function MergedArgument(args, start, end) {
@@ -1401,63 +1413,72 @@ MergedArgument.prototype.equals = functi
   }
 
   // We might need to add a check that args is the same here
 
   return this.text === that.text &&
        this.prefix === that.prefix && this.suffix === that.suffix;
 };
 
-argument.MergedArgument = MergedArgument;
+exports.MergedArgument = MergedArgument;
 
 
 /**
  * TrueNamedArguments are for when we have an argument like --verbose which
  * has a boolean value, and thus the opposite of '--verbose' is ''.
  */
-function TrueNamedArgument(name, arg) {
+function TrueNamedArgument(arg) {
   this.arg = arg;
-  this.text = arg ? arg.text : '--' + name;
-  this.prefix = arg ? arg.prefix : ' ';
-  this.suffix = arg ? arg.suffix : '';
+  this.text = arg.text;
+  this.prefix = arg.prefix;
+  this.suffix = arg.suffix;
 }
 
 TrueNamedArgument.prototype = Object.create(Argument.prototype);
 
 TrueNamedArgument.prototype.type = 'TrueNamedArgument';
 
 TrueNamedArgument.prototype.assign = function(assignment) {
   if (this.arg) {
     this.arg.assign(assignment);
   }
   this.assignment = assignment;
 };
 
 TrueNamedArgument.prototype.getArgs = function() {
-  // NASTY! getArgs has a fairly specific use: in removing used arguments
-  // from a command line. Unlike other arguments which are EITHER used
-  // in assignments directly OR grouped in things like MergedArguments,
-  // TrueNamedArgument is used raw from the UI, or composed of another arg
-  // from the CLI, so we return both here so they can both be removed.
-  return this.arg ? [ this, this.arg ] : [ this ];
+  return [ this.arg ];
 };
 
 TrueNamedArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
   if (that == null || !(that instanceof TrueNamedArgument)) {
     return false;
   }
 
   return this.text === that.text &&
        this.prefix === that.prefix && this.suffix === that.suffix;
 };
 
-argument.TrueNamedArgument = TrueNamedArgument;
+/**
+ * As Argument.beget that rebuilds nameArg and valueArg
+ */
+TrueNamedArgument.prototype.beget = function(options) {
+  if (options.text) {
+    console.error('Can\'t change text of a TrueNamedArgument', this, options);
+  }
+
+  options.type = TrueNamedArgument;
+  var begotten = Argument.prototype.beget.call(this, options);
+  begotten.arg = new Argument(begotten.text, begotten.prefix, begotten.suffix);
+  return begotten;
+};
+
+exports.TrueNamedArgument = TrueNamedArgument;
 
 
 /**
  * FalseNamedArguments are for when we don't have an argument like --verbose
  * which has a boolean value, and thus the opposite of '' is '--verbose'.
  */
 function FalseNamedArgument() {
   this.text = '';
@@ -1480,58 +1501,73 @@ FalseNamedArgument.prototype.equals = fu
   if (that == null || !(that instanceof FalseNamedArgument)) {
     return false;
   }
 
   return this.text === that.text &&
        this.prefix === that.prefix && this.suffix === that.suffix;
 };
 
-argument.FalseNamedArgument = FalseNamedArgument;
+exports.FalseNamedArgument = FalseNamedArgument;
 
 
 /**
  * A named argument is for cases where we have input in one of the following
  * formats:
  * <ul>
  * <li>--param value
  * <li>-p value
  * </ul>
  * We model this as a normal argument but with a long prefix.
- */
-function NamedArgument(nameArg, valueArg) {
-  this.nameArg = nameArg;
-  this.valueArg = valueArg;
-
-  if (valueArg == null) {
+ *
+ * There are 2 ways to construct a NamedArgument. One using 2 Arguments which
+ * are taken to be the argument for the name (e.g. '--param') and one for the
+ * value to assign to that parameter.
+ * Alternatively, you can pass in the text/prefix/suffix values in the same
+ * way as an Argument is constructed. If you do this then you are expected to
+ * assign to nameArg and valueArg before exposing the new NamedArgument.
+ */
+function NamedArgument() {
+  if (typeof arguments[0] === 'string') {
+    this.nameArg = null;
+    this.valueArg = null;
+    this.text = arguments[0];
+    this.prefix = arguments[1];
+    this.suffix = arguments[2];
+  }
+  else if (arguments[1] == null) {
+    this.nameArg = arguments[0];
+    this.valueArg = null;
     this.text = '';
-    this.prefix = nameArg.toString();
+    this.prefix = this.nameArg.toString();
     this.suffix = '';
   }
   else {
-    this.text = valueArg.text;
-    this.prefix = nameArg.toString() + valueArg.prefix;
-    this.suffix = valueArg.suffix;
+    this.nameArg = arguments[0];
+    this.valueArg = arguments[1];
+    this.text = this.valueArg.text;
+    this.prefix = this.nameArg.toString() + this.valueArg.prefix;
+    this.suffix = this.valueArg.suffix;
   }
 }
 
 NamedArgument.prototype = Object.create(Argument.prototype);
 
 NamedArgument.prototype.type = 'NamedArgument';
 
 NamedArgument.prototype.assign = function(assignment) {
   this.nameArg.assign(assignment);
   if (this.valueArg != null) {
     this.valueArg.assign(assignment);
   }
   this.assignment = assignment;
 };
 
 NamedArgument.prototype.getArgs = function() {
-  return [ this.nameArg, this.valueArg ];
+  return this.valueArg ? [ this.nameArg, this.valueArg ] : [ this.nameArg ];
 };
 
 NamedArgument.prototype.equals = function(that) {
   if (this === that) {
     return true;
   }
   if (that == null) {
     return false;
@@ -1542,17 +1578,40 @@ NamedArgument.prototype.equals = functio
   }
 
   // We might need to add a check that nameArg and valueArg are the same
 
   return this.text === that.text &&
        this.prefix === that.prefix && this.suffix === that.suffix;
 };
 
-argument.NamedArgument = NamedArgument;
+/**
+ * As Argument.beget that rebuilds nameArg and valueArg
+ */
+NamedArgument.prototype.beget = function(options) {
+  options.type = NamedArgument;
+  var begotten = Argument.prototype.beget.call(this, options);
+
+  // Cut the prefix into |whitespace|non-whitespace|whitespace| so we can
+  // rebuild nameArg and valueArg from the parts
+  var matches = /^([\s]*)([^\s]*)([\s]*)$/.exec(begotten.prefix);
+
+  if (this.valueArg == null && begotten.text === '') {
+    begotten.nameArg = new Argument(matches[2], matches[1], matches[3]);
+    begotten.valueArg = null;
+  }
+  else {
+    begotten.nameArg = new Argument(matches[2], matches[1], '');
+    begotten.valueArg = new Argument(begotten.text, matches[3], begotten.suffix);
+  }
+
+  return begotten;
+};
+
+exports.NamedArgument = NamedArgument;
 
 
 /**
  * An argument the groups together a number of plain arguments together so they
  * can be jointly assigned to a single array parameter
  */
 function ArrayArgument() {
   this.args = [];
@@ -1615,17 +1674,17 @@ ArrayArgument.prototype.equals = functio
  * Helper when we're putting arguments back together
  */
 ArrayArgument.prototype.toString = function() {
   return '{' + this.args.map(function(arg) {
     return arg.toString();
   }, this).join(',') + '}';
 };
 
-argument.ArrayArgument = ArrayArgument;
+exports.ArrayArgument = ArrayArgument;
 
 
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -1792,29 +1851,40 @@ SelectionType.prototype._findPredictions
   // Cache lower case versions of all the option names
   for (i = 0; i < lookup.length; i++) {
     option = lookup[i];
     if (option._gcliLowerName == null) {
       option._gcliLowerName = option.name.toLowerCase();
     }
   }
 
+  // Exact hidden matches. If 'hidden: true' then we only allow exact matches
+  // All the tests after here check that !option.value.hidden
+  for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
+    option = lookup[i];
+    if (option.name === arg.text) {
+      this._addToPredictions(predictions, option, arg);
+    }
+  }
+
   // Start with prefix matching
   for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
     option = lookup[i];
-    if (option._gcliLowerName.indexOf(match) === 0) {
-      this._addToPredictions(predictions, option, arg);
+    if (option._gcliLowerName.indexOf(match) === 0 && !option.value.hidden) {
+      if (predictions.indexOf(option) === -1) {
+        this._addToPredictions(predictions, option, arg);
+      }
     }
   }
 
   // Try infix matching if we get less half max matched
   if (predictions.length < (maxPredictions / 2)) {
     for (i = 0; i < lookup.length && predictions.length < maxPredictions; i++) {
       option = lookup[i];
-      if (option._gcliLowerName.indexOf(match) !== -1) {
+      if (option._gcliLowerName.indexOf(match) !== -1 && !option.value.hidden) {
         if (predictions.indexOf(option) === -1) {
           this._addToPredictions(predictions, option, arg);
         }
       }
     }
   }
 
   // Try fuzzy matching if we don't get a prefix match
@@ -2186,19 +2256,16 @@ CommandType.prototype.lookup = function(
     return { name: command.name, value: command };
   }, this);
 };
 
 /**
  * Add an option to our list of predicted options
  */
 CommandType.prototype._addToPredictions = function(predictions, option, arg) {
-  if (option.value.hidden) {
-    return;
-  }
   // The command type needs to exclude sub-commands when the CLI
   // is blank, but include them when we're filtering. This hack
   // excludes matches when the filter text is '' and when the
   // name includes a space.
   if (arg.text.length !== 0 || option.name.indexOf(' ') === -1) {
     predictions.push(option);
   }
 };
@@ -2326,16 +2393,17 @@ function Command(commandSpec) {
 
   if (this.params == null) {
     this.params = [];
   }
   if (!Array.isArray(this.params)) {
     throw new Error('command.params must be an array in ' + this.name);
   }
 
+  this.hasNamedParameters = false;
   this.description = 'description' in this ? this.description : undefined;
   this.description = lookup(this.description, 'canonDescNone');
   this.manual = 'manual' in this ? this.manual : undefined;
   this.manual = lookup(this.manual);
 
   // At this point this.params has nested param groups. We want to flatten it
   // out and replace the param object literals with Parameter objects
   var paramSpecs = this.params;
@@ -2354,28 +2422,36 @@ function Command(commandSpec) {
 
   // In theory this could easily be made recursive, so param groups could
   // contain nested param groups. Current thinking is that the added
   // complexity for the UI probably isn't worth it, so this implementation
   // prevents nesting.
   paramSpecs.forEach(function(spec) {
     if (!spec.group) {
       if (usingGroups) {
-        console.error('Parameters can\'t come after param groups.' +
-            ' Ignoring ' + this.name + '/' + spec.name);
+        throw new Error('Parameters can\'t come after param groups.' +
+                        ' Ignoring ' + this.name + '/' + spec.name);
       }
       else {
         var param = new Parameter(spec, this, null);
         this.params.push(param);
+
+        if (!param.isPositionalAllowed) {
+          this.hasNamedParameters = true;
+        }
       }
     }
     else {
       spec.params.forEach(function(ispec) {
         var param = new Parameter(ispec, this, spec.group);
         this.params.push(param);
+
+        if (!param.isPositionalAllowed) {
+          this.hasNamedParameters = true;
+        }
       }, this);
 
       usingGroups = true;
     }
   }, this);
 }
 
 canon.Command = Command;
@@ -2390,69 +2466,69 @@ function Parameter(paramSpec, command, g
   this.paramSpec = paramSpec;
   this.name = this.paramSpec.name;
   this.type = this.paramSpec.type;
   this.groupName = groupName;
   this.defaultValue = this.paramSpec.defaultValue;
 
   if (!this.name) {
     throw new Error('In ' + this.command.name +
-      ': all params must have a name');
+                    ': all params must have a name');
   }
 
   var typeSpec = this.type;
   this.type = types.getType(typeSpec);
   if (this.type == null) {
     console.error('Known types: ' + types.getTypeNames().join(', '));
     throw new Error('In ' + this.command.name + '/' + this.name +
-      ': can\'t find type for: ' + JSON.stringify(typeSpec));
+                    ': can\'t find type for: ' + JSON.stringify(typeSpec));
   }
 
   // boolean parameters have an implicit defaultValue:false, which should
   // not be changed. See the docs.
   if (this.type instanceof BooleanType) {
     if (this.defaultValue !== undefined) {
-      console.error('In ' + this.command.name + '/' + this.name +
-          ': boolean parameters can not have a defaultValue.' +
-          ' Ignoring');
+      throw new Error('In ' + this.command.name + '/' + this.name +
+                      ': boolean parameters can not have a defaultValue.' +
+                      ' Ignoring');
     }
     this.defaultValue = false;
   }
 
   // Check the defaultValue for validity.
   // Both undefined and null get a pass on this test. undefined is used when
   // there is no defaultValue, and null is used when the parameter is
   // optional, neither are required to parse and stringify.
   if (this.defaultValue != null) {
     try {
       var defaultText = this.type.stringify(this.defaultValue);
       var defaultConversion = this.type.parseString(defaultText);
       if (defaultConversion.getStatus() !== Status.VALID) {
-        console.error('In ' + this.command.name + '/' + this.name +
-            ': Error round tripping defaultValue. status = ' +
-            defaultConversion.getStatus());
+        throw new Error('In ' + this.command.name + '/' + this.name +
+                        ': Error round tripping defaultValue. status = ' +
+                        defaultConversion.getStatus());
       }
     }
     catch (ex) {
-      console.error('In ' + this.command.name + '/' + this.name +
-        ': ' + ex);
+      throw new Error('In ' + this.command.name + '/' + this.name +
+                      ': ' + ex);
     }
   }
 
   // Some types (boolean, array) have a non 'undefined' blank value. Give the
   // type a chance to override the default defaultValue of undefined
   if (this.defaultValue === undefined) {
     this.defaultValue = this.type.getBlank().value;
   }
 
   // All parameters that can only be set via a named parameter must have a
   // non-undefined default value
   if (!this.isPositionalAllowed && this.defaultValue === undefined) {
-    console.error('In ' + this.command.name + '/' + this.name +
-            ': Missing defaultValue for optional parameter.');
+    throw new Error('In ' + this.command.name + '/' + this.name +
+                    ': Missing defaultValue for optional parameter.');
   }
 }
 
 /**
  * Does the given name uniquely identify this param (among the other params
  * in this command)
  * @param name The name to check
  */
@@ -2505,16 +2581,26 @@ Object.defineProperty(Parameter.prototyp
 Object.defineProperty(Parameter.prototype, 'isDataRequired', {
   get: function() {
     return this.defaultValue === undefined;
   },
   enumerable: true
 });
 
 /**
+ * Reflect the paramSpec 'hidden' property (dynamically so it can change)
+ */
+Object.defineProperty(Parameter.prototype, 'hidden', {
+  get: function() {
+    return this.paramSpec.hidden;
+  },
+  enumerable: true
+});
+
+/**
  * Are we allowed to assign data to this parameter using positional
  * parameters?
  */
 Object.defineProperty(Parameter.prototype, 'isPositionalAllowed', {
   get: function() {
     return this.groupName == null;
   },
   enumerable: true
@@ -3930,52 +4016,64 @@ exports.JavascriptType = JavascriptType;
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
-define('gcli/types/node', ['require', 'exports', 'module' , 'gcli/host', 'gcli/l10n', 'gcli/types'], function(require, exports, module) {
+define('gcli/types/node', ['require', 'exports', 'module' , 'gcli/host', 'gcli/l10n', 'gcli/types', 'gcli/argument'], function(require, exports, module) {
 
 
 var host = require('gcli/host');
 var l10n = require('gcli/l10n');
 var types = require('gcli/types');
 var Type = require('gcli/types').Type;
 var Status = require('gcli/types').Status;
 var Conversion = require('gcli/types').Conversion;
+var BlankArgument = require('gcli/argument').BlankArgument;
 
 
 /**
  * Registration and de-registration.
  */
 exports.startup = function() {
   types.registerType(NodeType);
+  types.registerType(NodeListType);
 };
 
 exports.shutdown = function() {
   types.unregisterType(NodeType);
+  types.unregisterType(NodeListType);
 };
 
 /**
  * The object against which we complete, which is usually 'window' if it exists
  * but could be something else in non-web-content environments.
  */
 var doc;
 if (typeof document !== 'undefined') {
   doc = document;
 }
 
 /**
+ * For testing only.
+ * The fake empty NodeList used when there are no matches, we replace this with
+ * something that looks better as soon as we have a document, so not only
+ * should you not use this, but you shouldn't cache it either.
+ */
+exports._empty = [];
+
+/**
  * Setter for the document that contains the nodes we're matching
  */
 exports.setDocument = function(document) {
   doc = document;
+  exports._empty = doc.querySelectorAll('x>:root');
 };
 
 /**
  * Undo the effects of setDocument()
  */
 exports.unsetDocument = function() {
   doc = undefined;
 };
@@ -4036,16 +4134,76 @@ NodeType.prototype.parse = function(arg)
 
   return new Conversion(undefined, arg, Status.ERROR,
           l10n.lookupFormat('nodeParseMultiple', [ nodes.length ]));
 };
 
 NodeType.prototype.name = 'node';
 
 
+
+/**
+ * A CSS expression that refers to a node list.
+ *
+ * The 'allowEmpty' option ensures that we do not complain if the entered CSS
+ * selector is valid, but does not match any nodes. There is some overlap
+ * between this option and 'defaultValue'. What the user wants, in most cases,
+ * would be to use 'defaultText' (i.e. what is typed rather than the value that
+ * it represents). However this isn't a concept that exists yet and should
+ * probably be a part of GCLI if/when it does.
+ * All NodeListTypes have an automatic defaultValue of an empty NodeList so
+ * they can easily be used in named parameters.
+ */
+function NodeListType(typeSpec) {
+  if ('allowEmpty' in typeSpec && typeof typeSpec.allowEmpty !== 'boolean') {
+    throw new Error('Legal values for allowEmpty are [true|false]');
+  }
+
+  this.allowEmpty = typeSpec.allowEmpty;
+}
+
+NodeListType.prototype = Object.create(Type.prototype);
+
+NodeListType.prototype.getBlank = function() {
+  return new Conversion(exports._empty, new BlankArgument(), Status.VALID);
+};
+
+NodeListType.prototype.stringify = function(value) {
+  if (value == null) {
+    return '';
+  }
+  return value.__gcliQuery || 'Error';
+};
+
+NodeListType.prototype.parse = function(arg) {
+  if (arg.text === '') {
+    return new Conversion(undefined, arg, Status.INCOMPLETE);
+  }
+
+  var nodes;
+  try {
+    nodes = doc.querySelectorAll(arg.text);
+  }
+  catch (ex) {
+    return new Conversion(undefined, arg, Status.ERROR,
+            l10n.lookup('nodeParseSyntax'));
+  }
+
+  if (nodes.length === 0 && !this.allowEmpty) {
+    return new Conversion(undefined, arg, Status.INCOMPLETE,
+        l10n.lookup('nodeParseNone'));
+  }
+
+  host.flashNodes(nodes, false);
+  return new Conversion(nodes, arg, Status.VALID, '');
+};
+
+NodeListType.prototype.name = 'nodelist';
+
+
 });
 /*
  * Copyright 2012, Mozilla Foundation and contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -5072,16 +5230,57 @@ Assignment.prototype.getMessage = functi
  * @return An array of objects with name and value elements. For example:
  * [ { name:'bestmatch', value:foo1 }, { name:'next', value:foo2 }, ... ]
  */
 Assignment.prototype.getPredictions = function() {
   return this.conversion.getPredictions();
 };
 
 /**
+ * Accessor for a prediction by index.
+ * This is useful above <tt>getPredictions()[index]</tt> because it normalizes
+ * index to be within the bounds of the predictions, which means that the UI
+ * can maintain an index of which prediction to choose without caring how many
+ * predictions there are.
+ * @param index The index of the prediction to choose
+ */
+Assignment.prototype.getPredictionAt = function(index) {
+  if (index == null) {
+    index = 0;
+  }
+
+  if (this.isInName()) {
+    return undefined;
+  }
+
+  var predictions = this.getPredictions();
+  if (predictions.length === 0) {
+    return undefined;
+  }
+
+  index = index % predictions.length;
+  if (index < 0) {
+    index = predictions.length + index;
+  }
+  return predictions[index];
+};
+
+/**
+ * Some places want to take special action if we are in the name part of a
+ * named argument (i.e. the '--foo' bit).
+ * Currently this does not take actual cursor position into account, it just
+ * assumes that the cursor is at the end. In the future we will probably want
+ * to take this into account.
+ */
+Assignment.prototype.isInName = function() {
+  return this.conversion.arg.type === 'NamedArgument' &&
+         this.conversion.arg.prefix.slice(-1) !== ' ';
+};
+
+/**
  * Report on the status of the last parse() conversion.
  * We force mutations to happen through this method rather than have
  * setValue and setArgument functions to help maintain integrity when we
  * have ArrayArguments and don't want to get confused. This way assignments
  * are just containers for a conversion rather than things that store
  * a connection between an arg/value.
  * @see types.Conversion
  */
@@ -5118,17 +5317,18 @@ Assignment.prototype.ensureVisibleArgume
   // It should only be called when structural changes are happening in which
   // case we're going to ignore the event anyway. But on the other hand
   // perhaps this function shouldn't need to know how it is used, and should
   // do the inefficient thing.
   if (this.conversion.arg.type !== 'BlankArgument') {
     return false;
   }
 
-  var arg = this.conversion.arg.beget('', {
+  var arg = this.conversion.arg.beget({
+    text: '',
     prefixSpace: this.param instanceof CommandAssignment
   });
   this.conversion = this.param.type.parse(arg);
   this.conversion.assign(this);
 
   return true;
 };
 
@@ -5150,42 +5350,16 @@ Assignment.prototype.getStatus = functio
   if (!this.param.isDataRequired && this.arg.type === 'BlankArgument') {
     return Status.VALID;
   }
 
   return this.conversion.getStatus(arg);
 };
 
 /**
- * Replace the current value with the lower value if such a concept exists.
- */
-Assignment.prototype.decrement = function() {
-  var replacement = this.param.type.decrement(this.conversion.value);
-  if (replacement != null) {
-    var str = this.param.type.stringify(replacement);
-    var arg = this.conversion.arg.beget(str);
-    var conversion = new Conversion(replacement, arg);
-    this.setConversion(conversion);
-  }
-};
-
-/**
- * Replace the current value with the higher value if such a concept exists.
- */
-Assignment.prototype.increment = function() {
-  var replacement = this.param.type.increment(this.conversion.value);
-  if (replacement != null) {
-    var str = this.param.type.stringify(replacement);
-    var arg = this.conversion.arg.beget(str);
-    var conversion = new Conversion(replacement, arg);
-    this.setConversion(conversion);
-  }
-};
-
-/**
  * Helper when we're rebuilding command lines.
  */
 Assignment.prototype.toString = function() {
   return this.conversion.toString();
 };
 
 /**
  * For test/debug use only. The output from this function is subject to wanton
@@ -5393,22 +5567,16 @@ function Requisition(environment, doc) {
 
   this.commandOutputManager = canon.commandOutputManager;
 
   this.onAssignmentChange = util.createEvent('Requisition.onAssignmentChange');
   this.onTextChange = util.createEvent('Requisition.onTextChange');
 }
 
 /**
- * Some number that is higher than the most args we'll ever have. Would use
- * MAX_INTEGER if that made sense
- */
-var MORE_THAN_THE_MOST_ARGS_POSSIBLE = 1000000;
-
-/**
  * Avoid memory leaks
  */
 Requisition.prototype.destroy = function() {
   this.commandAssignment.onAssignmentChange.remove(this._commandAssignmentChanged, this);
   this.commandAssignment.onAssignmentChange.remove(this._assignmentChanged, this);
 
   delete this.document;
   delete this.environment;
@@ -5432,57 +5600,16 @@ Requisition.prototype._assignmentChanged
   this.onAssignmentChange(ev);
 
   // Both for argument position and the onTextChange event, we only care
   // about changes to the argument.
   if (ev.conversion.argEquals(ev.oldConversion)) {
     return;
   }
 
-  this._structuralChangeInProgress = true;
-
-  // Refactor? See bug 660765
-  // Do preceding arguments need to have dummy values applied so we don't
-  // get a hole in the command line?
-  var i;
-  if (ev.assignment.param.isPositionalAllowed) {
-    for (i = 0; i < ev.assignment.paramIndex; i++) {
-      var assignment = this.getAssignment(i);
-      if (assignment.param.isPositionalAllowed) {
-        if (assignment.ensureVisibleArgument()) {
-          this._args.push(assignment.arg);
-        }
-      }
-    }
-  }
-
-  // Remember where we found the first match
-  var index = MORE_THAN_THE_MOST_ARGS_POSSIBLE;
-  for (i = 0; i < this._args.length; i++) {
-    if (this._args[i].assignment === ev.assignment) {
-      if (i < index) {
-        index = i;
-      }
-      this._args.splice(i, 1);
-      i--;
-    }
-  }
-
-  if (index === MORE_THAN_THE_MOST_ARGS_POSSIBLE) {
-    this._args.push(ev.assignment.arg);
-  }
-  else {
-    // Is there a way to do this that doesn't involve a loop?
-    var newArgs = ev.conversion.arg.getArgs();
-    for (i = 0; i < newArgs.length; i++) {
-      this._args.splice(index + i, 0, newArgs[i]);
-    }
-  }
-  this._structuralChangeInProgress = false;
-
   this.onTextChange();
 };
 
 /**
  * When the command changes, we need to keep a bunch of stuff in sync
  */
 Requisition.prototype._commandAssignmentChanged = function(ev) {
   // Assignments fire AssignmentChange events on any change, including minor
@@ -5514,16 +5641,38 @@ Requisition.prototype._commandAssignment
 Requisition.prototype.getAssignment = function(nameOrNumber) {
   var name = (typeof nameOrNumber === 'string') ?
     nameOrNumber :
     Object.keys(this._assignments)[nameOrNumber];
   return this._assignments[name] || undefined;
 };
 
 /**
+ * There are a few places where we need to know what the 'next thing' is. What
+ * is the user going to be filling out next (assuming they don't enter a named
+ * argument). The next argument is the first in line that is both blank, and
+ * that can be filled in positionally.
+ * @return The next assignment to be used, or null if all the positional
+ * parameters have values.
+ */
+Requisition.prototype._getFirstBlankPositionalAssignment = function() {
+  var reply = null;
+  Object.keys(this._assignments).some(function(name) {
+    var assignment = this.getAssignment(name);
+    if (assignment.arg.type === 'BlankArgument' &&
+            assignment.param.isPositionalAllowed) {
+      reply = assignment;
+      return true; // i.e. break
+    }
+    return false;
+  }, this);
+  return reply;
+};
+
+/**
  * Where parameter name == assignment names - they are the same
  */
 Requisition.prototype.getParameterNames = function() {
   return Object.keys(this._assignments);
 };
 
 /**
  * A *shallow* clone of the assignments.
@@ -5599,29 +5748,44 @@ Requisition.prototype.getAssignments = f
 };
 
 /**
  * Alter the given assignment using the given arg. This function is better than
  * calling assignment.setConversion(assignment.param.type.parse(arg)) because
  * it adjusts the args in this requisition to keep things up to date
  */
 Requisition.prototype.setAssignment = function(assignment, arg) {
-  var originalArg = assignment.arg;
+  var originalArgs = assignment.arg.getArgs();
   var conversion = assignment.param.type.parse(arg);
   assignment.setConversion(conversion);
 
-  // If this argument isn't assigned to anything (i.e. it was created by
-  // assignment.setBlank) we need to add it into the _args array so
-  // requisition.toString can make sense
-  if (originalArg.type === 'BlankArgument') {
-    this._args.push(arg);
-  }
-  else {
-    var index = this._args.indexOf(originalArg);
-    this._args[index] = conversion.arg;
+  var replacementArgs = arg.getArgs();
+  var maxLen = Math.max(originalArgs.length, replacementArgs.length);
+  for (var i = 0; i < maxLen; i++) {
+    // If there are no more original args, or if the original arg was blank
+    // (i.e. not typed by the user), we'll just need to add at the end
+    if (i >= originalArgs.length || originalArgs[i].type === 'BlankArgument') {
+      this._args.push(replacementArgs[i]);
+      continue;
+    }
+
+    var index = this._args.indexOf(originalArgs[i]);
+    if (index === -1) {
+      console.error('Couldn\'t find ', originalArgs[i], ' in ', this._args);
+      throw new Error('Couldn\'t find ' + originalArgs[i]);
+    }
+
+    // If there are no more replacement args, we just remove the original args
+    // Otherwise swap original args and replacements
+    if (i >= replacementArgs.length) {
+      this._args.splice(index, 1);
+    }
+    else {
+      this._args[index] = replacementArgs[i];
+    }
   }
 };
 
 /**
  * Reset all the assignments to their default values
  */
 Requisition.prototype.setBlankArguments = function() {
   this.getAssignments().forEach(function(assignment) {
@@ -5640,74 +5804,92 @@ Requisition.prototype.setBlankArguments 
  * which should be set to start and end of the selection.
  * @param predictionChoice The index of the prediction that we should choose.
  * This number is not bounded by the size of the prediction array, we take the
  * modulus to get it within bounds
  */
 Requisition.prototype.complete = function(cursor, predictionChoice) {
   var assignment = this.getAssignmentAt(cursor.start);
 
-  var predictions = assignment.conversion.getPredictions();
-  if (predictions.length > 0) {
-    this.onTextChange.holdFire();
-
-    var prediction = assignment.conversion.getPredictionAt(predictionChoice);
-
+  this.onTextChange.holdFire();
+
+  var prediction = assignment.getPredictionAt(predictionChoice);
+  if (prediction == null) {
+    // No predictions generally means we shouldn't change anything on TAB, but
+    // TAB has the connotation of 'next thing' and when we're at the end of
+    // a thing that implies that we should add a space. i.e.
+    // 'help<TAB>' -> 'help '
+    // But we should only do this if the thing that we're 'completing' is valid
+    // and doesn't already end in a space.
+    if (assignment.arg.suffix.slice(-1) !== ' ' &&
+            assignment.getStatus() === Status.VALID) {
+      this._addSpace(assignment);
+    }
+
+    // Also add a space if we are in the name part of an assignment, however
+    // this time we don't want the 'push the space to the next assignment'
+    // logic, so we don't use addSpace
+    if (assignment.isInName()) {
+      var newArg = assignment.conversion.arg.beget({ prefixPostSpace: true });
+      this.setAssignment(assignment, newArg);
+    }
+  }
+  else {
     // Mutate this argument to hold the completion
-    var arg = assignment.arg.beget(prediction.name);
+    var arg = assignment.arg.beget({ text: prediction.name });
     this.setAssignment(assignment, arg);
 
-    if (prediction.incomplete) {
-      // This is the easy case - the prediction is incomplete - no need to add
-      // any spaces
-      return;
-    }
-
-    // The prediction reported !incomplete, which means it's complete so we
-    // should add a space to delimit this argument and let the user move-on.
-    // The question is, where does the space go? The obvious thing to do is to
-    // add it to the suffix of the completed argument, but that's wrong because
-    // spaces are attached to the start of the next argument rather than the
-    // end of the previous one (and this matters to getCurrentAssignment).
-    // However there might not be a next argument (if we've at the end of the
-    // input), in which case we really do use this one.
-    // Also if there is already a space in those positions, don't add another
-
-    var nextIndex = assignment.paramIndex + 1;
-    var nextAssignment = this.getAssignment(nextIndex);
-    if (nextAssignment) {
-      // Add a space onto the next argument (if there isn't one there already)
-      var nextArg = nextAssignment.conversion.arg;
-      if (nextArg.prefix.charAt(0) !== ' ') {
-        nextArg = new Argument(nextArg.text, ' ' + nextArg.prefix, nextArg.suffix);
-        this.setAssignment(nextAssignment, nextArg);
+    if (!prediction.incomplete) {
+      // The prediction is complete, add a space to let the user move-on
+      this._addSpace(assignment);
+
+      // Bug 779443 - Remove or explain the reparse
+      if (assignment instanceof UnassignedAssignment) {
+        this.update(this.toString());
       }
     }
-    else {
-      // There is no next argument, this must be the last assignment, so just
-      // add the space to the prefix of this argument
-      arg = assignment.conversion.arg;
-      if (arg.suffix.charAt(arg.suffix.length - 1) !== ' ') {
-        // It's tempting to think - "we're calling setAssignment twice in one
-        // call to complete, the first time to complete the text, the second
-        // to add a space, why not save the event cascade and do it once"
-        // However if we're setting up the command, the number of parameters
-        // changes as a result, so our call to getAssignment(nextIndex) will
-        // produce the wrong answer
-        arg = new Argument(arg.text, arg.prefix, arg.suffix + ' ');
-        this.setAssignment(assignment, arg);
-      }
-    }
-
-    if (assignment instanceof UnassignedAssignment) {
-      this.update(this.toString());
-    }
-
-    this.onTextChange();
-    this.onTextChange.resumeFire();
+  }
+
+  this.onTextChange();
+  this.onTextChange.resumeFire();
+};
+
+/**
+ * Pressing TAB sometimes requires that we add a space to denote that we're on
+ * to the 'next thing'.
+ * @param assignment The assignment to which to append the space
+ */
+Requisition.prototype._addSpace = function(assignment) {
+  var arg = assignment.conversion.arg.beget({ suffixSpace: true });
+  if (arg !== assignment.conversion.arg) {
+    this.setAssignment(assignment, arg);
+  }
+};
+
+/**
+ * Replace the current value with the lower value if such a concept exists.
+ */
+Requisition.prototype.decrement = function(assignment) {
+  var replacement = assignment.param.type.decrement(assignment.conversion.value);
+  if (replacement != null) {
+    var str = assignment.param.type.stringify(replacement);
+    var arg = assignment.conversion.arg.beget({ text: str });
+    this.setAssignment(assignment, arg);
+  }
+};
+
+/**
+ * Replace the current value with the higher value if such a concept exists.
+ */
+Requisition.prototype.increment = function(assignment) {
+  var replacement = assignment.param.type.increment(assignment.conversion.value);
+  if (replacement != null) {
+    var str = assignment.param.type.stringify(replacement);
+    var arg = assignment.conversion.arg.beget({ text: str });
+    this.setAssignment(assignment, arg);
   }
 };
 
 /**
  * Extract a canonical version of the input
  */
 Requisition.prototype.toCanonicalString = function() {
   var line = [];
@@ -5917,19 +6099,22 @@ Requisition.prototype.getAssignmentAt = 
     // otherwise it looks forwards
     if (arg.assignment.arg.type === 'NamedArgument') {
       // leave the argument as it is
     }
     else if (this._args.length > i + 1) {
       // first to the next argument
       assignment = this._args[i + 1].assignment;
     }
-    else if (assignment && assignment.paramIndex + 1 < this.assignmentCount) {
-      // then to the next assignment
-      assignment = this.getAssignment(assignment.paramIndex + 1);
+    else {
+      // then to the first blank positional parameter, leaving 'as is' if none
+      var nextAssignment = this._getFirstBlankPositionalAssignment();
+      if (nextAssignment != null) {
+        assignment = nextAssignment;
+      }
     }
 
     for (j = 0; j < arg.suffix.length; j++) {
       assignForPos.push(assignment);
     }
   }
 
   // Possible shortcut, we don't really need to go through all the args
@@ -6014,17 +6199,17 @@ Requisition.prototype.exec = function(in
   });
 
   this.commandOutputManager.onOutput({ output: output });
 
   try {
     var context = exports.createExecutionContext(this);
     var reply = command.exec(args, context);
 
-    if (reply != null && reply.isPromise) {
+    if (reply != null && typeof reply.then === 'function') {
       reply.then(
           function(data) { output.complete(data); },
           function(error) { output.error = true; output.complete(error); });
 
       output.promise = reply;
       // Add progress to our promise and add a handler for it here
       // See bug 659300
     }
@@ -6430,17 +6615,17 @@ Requisition.prototype._assign = function
       if (assignment.param.isKnownAs(args[i].text)) {
         var arg = args.splice(i, 1)[0];
         unassignedParams = unassignedParams.filter(function(test) {
           return test !== assignment.param.name;
         });
 
         // boolean parameters don't have values, default to false
         if (assignment.param.type instanceof BooleanType) {
-          arg = new TrueNamedArgument(null, arg);
+          arg = new TrueNamedArgument(arg);
         }
         else {
           var valueArg = null;
           if (i + 1 <= args.length) {
             valueArg = args.splice(i, 1)[0];
           }
           arg = new NamedArgument(arg, valueArg);
         }
@@ -6841,16 +7026,21 @@ FocusManager.prototype.addMonitoredEleme
     element: element,
     where: where,
     onFocus: function() { this._reportFocus(where); }.bind(this),
     onBlur: function() { this._reportBlur(where); }.bind(this)
   };
 
   element.addEventListener('focus', monitor.onFocus, true);
   element.addEventListener('blur', monitor.onBlur, true);
+
+  if (this._document.activeElement === element) {
+    this._reportFocus(where);
+  }
+
   this._monitoredElements.push(monitor);
 };
 
 /**
  * Undo the effects of addMonitoredElement()
  * @param element The element to stop tracking
  * @param where Optional source string for debugging only
  */
@@ -6958,19 +7148,19 @@ FocusManager.prototype._reportBlur = fun
  * The setting has changed
  */
 FocusManager.prototype._eagerHelperChanged = function() {
   this._checkShow();
 };
 
 /**
  * The inputter tells us about keyboard events so we can decide to delay
- * showing the tooltip element, (or if the keypress is F1, show it now)
- */
-FocusManager.prototype.onInputChange = function(ev) {
+ * showing the tooltip element
+ */
+FocusManager.prototype.onInputChange = function() {
   this._recentOutput = false;
   this._checkShow();
 };
 
 /**
  * Generally called for something like a F1 key press, when the user explicitly
  * wants help
  */
@@ -7054,25 +7244,25 @@ FocusManager.prototype._checkShow = func
 };
 
 /**
  * Calculate if we should be showing or hidden taking into account all the
  * available inputs
  */
 FocusManager.prototype._shouldShowTooltip = function() {
   if (!this._hasFocus) {
-    return { visible: false, reason: '!hasFocus' };
+    return { visible: false, reason: 'notHasFocus' };
   }
 
   if (eagerHelper.value === Eagerness.NEVER) {
-    return { visible: false, reason: 'eagerHelper !== NEVER' };
+    return { visible: false, reason: 'eagerHelperNever' };
   }
 
   if (eagerHelper.value === Eagerness.ALWAYS) {
-    return { visible: true, reason: 'eagerHelper !== ALWAYS' };
+    return { visible: true, reason: 'eagerHelperAlways' };
   }
 
   if (this._isError) {
     return { visible: true, reason: 'isError' };
   }
 
   if (this._helpRequested) {
     return { visible: true, reason: 'helpRequested' };
@@ -7086,17 +7276,17 @@ FocusManager.prototype._shouldShowToolti
 };
 
 /**
  * Calculate if we should be showing or hidden taking into account all the
  * available inputs
  */
 FocusManager.prototype._shouldShowOutput = function() {
   if (!this._hasFocus) {
-    return { visible: false, reason: '!hasFocus' };
+    return { visible: false, reason: 'notHasFocus' };
   }
 
   if (this._recentOutput) {
     return { visible: true, reason: 'recentOutput' };
   }
 
   return { visible: false, reason: 'default' };
 };
@@ -7195,17 +7385,17 @@ StringField.prototype.destroy = function
 StringField.prototype.setConversion = function(conversion) {
   this.arg = conversion.arg;
   this.element.value = conversion.arg.text;
   this.setMessage(conversion.message);
 };
 
 StringField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
-  this.arg = this.arg.beget(this.element.value, { prefixSpace: true });
+  this.arg = this.arg.beget({ text: this.element.value, prefixSpace: true });
   return this.type.parse(this.arg);
 };
 
 StringField.claim = function(type) {
   return type instanceof StringType ? Field.MATCH : Field.BASIC;
 };
 
 
@@ -7250,17 +7440,17 @@ NumberField.prototype.destroy = function
 
 NumberField.prototype.setConversion = function(conversion) {
   this.arg = conversion.arg;
   this.element.value = conversion.arg.text;
   this.setMessage(conversion.message);
 };
 
 NumberField.prototype.getConversion = function() {
-  this.arg = this.arg.beget(this.element.value, { prefixSpace: true });
+  this.arg = this.arg.beget({ text: this.element.value, prefixSpace: true });
   return this.type.parse(this.arg);
 };
 
 
 /**
  * A field that uses a checkbox to toggle a boolean field
  */
 function BooleanField(type, options) {
@@ -7297,17 +7487,17 @@ BooleanField.prototype.setConversion = f
   this.element.checked = conversion.value;
   this.setMessage(conversion.message);
 };
 
 BooleanField.prototype.getConversion = function() {
   var arg;
   if (this.named) {
     arg = this.element.checked ?
-            new TrueNamedArgument(this.name) :
+            new TrueNamedArgument(new Argument(' --' + this.name)) :
             new FalseNamedArgument();
   }
   else {
     arg = new Argument(' ' + this.element.checked);
   }
   return this.type.parse(arg);
 };
 
@@ -7749,17 +7939,17 @@ exports.addField(BlankField);
  * limitations under the License.
  */
 
 define('gcli/ui/fields/javascript', ['require', 'exports', 'module' , 'gcli/util', 'gcli/argument', 'gcli/types/javascript', 'gcli/ui/fields/menu', 'gcli/ui/fields'], function(require, exports, module) {
 
 
 var util = require('gcli/util');
 
-var Argument = require('gcli/argument').Argument;
+var ScriptArgument = require('gcli/argument').ScriptArgument;
 var JavascriptType = require('gcli/types/javascript').JavascriptType;
 
 var Menu = require('gcli/ui/fields/menu').Menu;
 var Field = require('gcli/ui/fields').Field;
 var fields = require('gcli/ui/fields');
 
 
 /**
@@ -7776,17 +7966,17 @@ exports.shutdown = function() {
 
 /**
  * A field that allows editing of javascript
  */
 function JavascriptField(type, options) {
   Field.call(this, type, options);
 
   this.onInputChange = this.onInputChange.bind(this);
-  this.arg = new Argument('', '{ ', ' }');
+  this.arg = new ScriptArgument('', '{ ', ' }');
 
   this.element = util.createElement(this.document, 'div');
 
   this.input = util.createElement(this.document, 'input');
   this.input.type = 'text';
   this.input.addEventListener('keyup', this.onInputChange, false);
   this.input.classList.add('gcli-field');
   this.input.classList.add('gcli-field-javascript');
@@ -7794,17 +7984,17 @@ function JavascriptField(type, options) 
 
   this.menu = new Menu({
     document: this.document,
     field: true,
     type: type
   });
   this.element.appendChild(this.menu.element);
 
-  this.setConversion(this.type.parse(new Argument('')));
+  this.setConversion(this.type.parse(new ScriptArgument('')));
 
   this.onFieldChange = util.createEvent('JavascriptField.onFieldChange');
 
   // i.e. Register this.onItemClick as the default action for a menu click
   this.menu.onItemClick.add(this.itemClicked, this);
 }
 
 JavascriptField.prototype = Object.create(Field.prototype);
@@ -7864,17 +8054,17 @@ JavascriptField.prototype.onInputChange 
   this.item = ev.currentTarget.item;
   var conversion = this.getConversion();
   this.onFieldChange({ conversion: conversion });
   this.setMessage(conversion.message);
 };
 
 JavascriptField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
-  this.arg = this.arg.beget(this.input.value, { normalize: true });
+  this.arg = new ScriptArgument(this.input.value, '{ ', ' }');
   return this.type.parse(this.arg);
 };
 
 JavascriptField.DEFAULT_VALUE = '__JavascriptField.DEFAULT_VALUE';
 
 
 });
 /*
@@ -8066,27 +8256,31 @@ Menu.prototype.setChoiceIndex = function
   }
 
   nodes.item(choice).classList.add('gcli-menu-highlight');
 };
 
 /**
  * Allow the inputter to use RETURN to chose the current menu item when
  * it can't execute the command line
+ * @return true if an item was 'clicked', false otherwise
  */
 Menu.prototype.selectChoice = function() {
   var selected = this.element.querySelector('.gcli-menu-highlight .gcli-menu-name');
-  if (selected) {
-    var name = selected.innerHTML;
-    var arg = new Argument(name);
-    arg.suffix = ' ';
-
-    var conversion = this.type.parse(arg);
-    this.onItemClick({ conversion: conversion });
-  }
+  if (!selected) {
+    return false;
+  }
+
+  var name = selected.innerHTML;
+  var arg = new Argument(name);
+  arg.suffix = ' ';
+
+  var conversion = this.type.parse(arg);
+  this.onItemClick({ conversion: conversion });
+  return true;
 };
 
 /**
  * Hide the menu
  */
 Menu.prototype.hide = function() {
   this.element.style.display = 'none';
 };
@@ -8234,17 +8428,17 @@ SelectionField.prototype._addOption = fu
   var option = util.createElement(this.document, 'option');
   option.innerHTML = item.name;
   option.value = item.index;
   this.element.appendChild(option);
 };
 
 
 /**
- * A field that allows editing of javascript
+ * A field that allows selection of one of a number of options
  */
 function SelectionTooltipField(type, options) {
   Field.call(this, type, options);
 
   this.onInputChange = this.onInputChange.bind(this);
   this.arg = new Argument();
 
   this.menu = new Menu({ document: this.document, type: type });
@@ -8292,33 +8486,34 @@ SelectionTooltipField.prototype.onInputC
   this.item = ev.currentTarget.item;
   var conversion = this.getConversion();
   this.onFieldChange({ conversion: conversion });
   this.setMessage(conversion.message);
 };
 
 SelectionTooltipField.prototype.getConversion = function() {
   // This tweaks the prefix/suffix of the argument to fit
-  this.arg = this.arg.beget('typed', { normalize: true });
+  this.arg = this.arg.beget({ text: this.input.value });
   return this.type.parse(this.arg);
 };
 
 /**
  * Allow the menu to highlight the correct prediction choice
  */
 SelectionTooltipField.prototype.setChoiceIndex = function(choice) {
   this.menu.setChoiceIndex(choice);
 };
 
 /**
  * Allow the inputter to use RETURN to chose the current menu item when
  * it can't execute the command line
+ * @return true if an item was 'clicked', false otherwise
  */
 SelectionTooltipField.prototype.selectChoice = function() {
-  this.menu.selectChoice();
+  return this.menu.selectChoice();
 };
 
 Object.defineProperty(SelectionTooltipField.prototype, 'isImportant', {
   get: function() {
     return this.type.name !== 'command';
   },
   enumerable: true
 });
@@ -9341,17 +9536,17 @@ Inputter.prototype.onKeyDown = function(
   if (ev.keyCode === KeyEvent.DOM_VK_F1 ||
       ev.keyCode === KeyEvent.DOM_VK_ESCAPE ||
       ev.keyCode === KeyEvent.DOM_VK_UP ||
       ev.keyCode === KeyEvent.DOM_VK_DOWN) {
     return;
   }
 
   if (this.focusManager) {
-    this.focusManager.onInputChange(ev);
+    this.focusManager.onInputChange();
   }
 
   if (ev.keyCode === KeyEvent.DOM_VK_TAB) {
     this.lastTabDownAt = 0;
     if (!ev.shiftKey) {
       ev.preventDefault();
       // Record the timestamp of this TAB down so onKeyUp can distinguish
       // focus from TAB in the CLI.
@@ -9389,20 +9584,20 @@ Inputter.prototype.onKeyUp = function(ev
     else if (this.element.value === '' || this._scrollingThroughHistory) {
       this._scrollingThroughHistory = true;
       this.requisition.update(this.history.backward());
     }
     else {
       // If the user is on a valid value, then we increment the value, but if
       // they've typed something that's not right we page through predictions
       if (this.assignment.getStatus() === Status.VALID) {
-        this.assignment.increment();
+        this.requisition.increment(assignment);
         // See notes on focusManager.onInputChange in onKeyDown
         if (this.focusManager) {
-          this.focusManager.onInputChange(ev);
+          this.focusManager.onInputChange();
         }
       }
       else {
         this.changeChoice(-1);
       }
     }
     return;
   }
@@ -9413,20 +9608,20 @@ Inputter.prototype.onKeyUp = function(ev
     }
     else if (this.element.value === '' || this._scrollingThroughHistory) {
       this._scrollingThroughHistory = true;
       this.requisition.update(this.history.forward());
     }
     else {
       // See notes above for the UP key
       if (this.assignment.getStatus() === Status.VALID) {
-        this.assignment.decrement();
+        this.requisition.decrement(assignment);
         // See notes on focusManager.onInputChange in onKeyDown
         if (this.focusManager) {
-          this.focusManager.onInputChange(ev);
+          this.focusManager.onInputChange();
         }
       }
       else {
         this.changeChoice(+1);
       }
     }
     return;
   }
@@ -9438,20 +9633,19 @@ Inputter.prototype.onKeyUp = function(ev
     if (worst === Status.VALID) {
       this._scrollingThroughHistory = false;
       this.history.add(this.element.value);
       this.requisition.exec();
     }
     else {
       // If we can't execute the command, but there is a menu choice to use
       // then use it.
-      this.tooltip.selectChoice();
-
-      // See bug 664135 - On pressing return with an invalid input, GCLI
-      // should select the incorrect part of the input for an easy fix
+      if (!this.tooltip.selectChoice()) {
+        this.focusManager.setError(true);
+      }
     }
 
     this._choice = null;
     return;
   }
 
   if (ev.keyCode === KeyEvent.DOM_VK_TAB && !ev.shiftKey) {
     // Being able to complete 'nothing' is OK if there is some context, but
@@ -9750,101 +9944,132 @@ Completer.prototype.update = function(ev
 Completer.prototype._getCompleterTemplateData = function() {
   var input = this.inputter.getInputState();
 
   // directTabText is for when the current input is a prefix of the completion
   // arrowTabText is for when we need to use an -> to show what will be used
   var directTabText = '';
   var arrowTabText = '';
   var current = this.requisition.getAssignmentAt(input.cursor.start);
+  var emptyParameters = [];
 
   if (input.typed.trim().length !== 0) {
-    var prediction = current.conversion.getPredictionAt(this.choice);
+    var cArg = current.arg;
+    var prediction = current.getPredictionAt(this.choice);
+
     if (prediction) {
       var tabText = prediction.name;
-      var existing = current.arg.text;
+      var existing = cArg.text;
+
+      // Normally the cursor being just before whitespace means that you are
+      // 'in' the previous argument, which means that the prediction is based
+      // on that argument, however NamedArguments break this by having 2 parts
+      // so we need to prepend the tabText with a space for NamedArguments,
+      // but only when there isn't already a space at the end of the prefix
+      // (i.e. ' --name' not ' --name ')
+      if (current.isInName()) {
+        tabText = ' ' + tabText;
+      }
 
       if (existing !== tabText) {
         // Decide to use directTabText or arrowTabText
         // Strip any leading whitespace from the user inputted value because the
         // tabText will never have leading whitespace.
         var inputValue = existing.replace(/^\s*/, '');
         var isStrictCompletion = tabText.indexOf(inputValue) === 0;
         if (isStrictCompletion && input.cursor.start === input.typed.length) {
           // Display the suffix of the prediction as the completion
           var numLeadingSpaces = existing.match(/^(\s*)/)[0].length;
 
           directTabText = tabText.slice(existing.length - numLeadingSpaces);
         }
         else {
           // Display the '-> prediction' at the end of the completer element
-          // These JS escapes are aka &nbsp;&rarr; the right arrow
-          arrowTabText = ' \u00a0\u21E5 ' + tabText;
+          // \u21E5 is the JS escape right arrow
+          arrowTabText = '\u21E5 ' + tabText;
         }
       }
     }
+    else {
+      // There's no prediction, but if this is a named argument that needs a
+      // value (that is without any) then we need to show that one is needed
+      // For example 'git commit --message ', clearly needs some more text
+      if (cArg.type === 'NamedArgument' && cArg.text === '') {
+        emptyParameters.push('<' + current.param.type.name + '>\u00a0');
+      }
+    }
+  }
+
+  // Add a space between the typed text (+ directTabText) and the hints,
+  // making sure we don't add 2 sets of padding
+  if (directTabText !== '') {
+    directTabText += '\u00a0';
+  }
+  else if (!this.requisition.typedEndsWithSeparator()) {
+    emptyParameters.unshift('\u00a0');
   }
 
   // statusMarkup is wrapper around requisition.getInputStatusMarkup converting
   // space to &nbsp; in the string member (for HTML display) and status to an
   // appropriate class name (i.e. lower cased, prefixed with gcli-in-)
   var statusMarkup = this.requisition.getInputStatusMarkup(input.cursor.start);
   statusMarkup.forEach(function(member) {
     member.string = member.string.replace(/ /g, '\u00a0'); // i.e. &nbsp;
     member.className = 'gcli-in-' + member.status.toString().toLowerCase();
   }, this);
 
   // Calculate the list of parameters to be filled in
-  var trailingSeparator = this.requisition.typedEndsWithSeparator();
   // We generate an array of emptyParameter markers for each positional
   // parameter to the current command.
   // Generally each emptyParameter marker begins with a space to separate it
   // from whatever came before, unless what comes before ends in a space.
-  // Also if we've got a directTabText prediction or we're in a NamedParameter
-  // then we don't want any text for that parameter at all.
-  // The algorithm to add spaces needs to take this into account.
-
-  var firstBlankParam = true;
-  var emptyParameters = [];
+
+  var command = this.requisition.commandAssignment.value;
+  var jsCommand = command && command.name === '{';
+
   this.requisition.getAssignments().forEach(function(assignment) {
+    // Named arguments are handled with a group [options] marker
     if (!assignment.param.isPositionalAllowed) {
       return;
     }
-    if (current.arg.type === 'NamedArgument') {
-      return;
-    }
-
+
+    // No hints if we've got content for this parameter
     if (assignment.arg.toString().trim() !== '') {
-      if (directTabText !== '') {
-        firstBlankParam = false;
-      }
       return;
     }
 
-    if (directTabText !== '' && firstBlankParam) {
-      firstBlankParam = false;
+    if (directTabText !== '' && current === assignment) {
       return;
     }
 
     var text = (assignment.param.isDataRequired) ?
-        '<' + assignment.param.name + '>' :
-        '[' + assignment.param.name + ']';
-
-    // Add a space if we don't have one at the end of the input or if
-    // this isn't the first param we've mentioned
-    if (!trailingSeparator || !firstBlankParam) {
-      text = '\u00a0' + text; // i.e. &nbsp;
-    }
-
-    firstBlankParam = false;
+        '<' + assignment.param.name + '>\u00a0' :
+        '[' + assignment.param.name + ']\u00a0';
+
     emptyParameters.push(text);
   }.bind(this));
 
-  var command = this.requisition.commandAssignment.value;
-  var jsCommand = command && command.name === '{';
+  var addOptionsMarker = false;
+  // We add an '[options]' marker when there are named parameters that are
+  // not filled in and not hidden, and we don't have any directTabText
+  if (command && command.hasNamedParameters) {
+    command.params.forEach(function(param) {
+      var arg = this.requisition.getAssignment(param.name).arg;
+      if (!param.isPositionalAllowed && !param.hidden
+              && arg.type === "BlankArgument") {
+        addOptionsMarker = true;
+      }
+    }, this);
+  }
+
+  if (addOptionsMarker) {
+    // Add an nbsp if we don't have one at the end of the input or if
+    // this isn't the first param we've mentioned
+    emptyParameters.push('[options]\u00a0');
+  }
 
   // Is the entered command a JS command with no closing '}'?
   // TWEAK: This code should be considered for promotion to Requisition
   var unclosedJs = jsCommand &&
       this.requisition.getAssignment(0).arg.suffix.indexOf('}') === -1;
 
   // The text for the 'jump to scratchpad' feature, or '' if it is disabled
   var link = this.scratchpad && jsCommand ? this.scratchpad.linkText : '';
@@ -10055,21 +10280,23 @@ Tooltip.prototype.choiceChanged = functi
     var choice = this.assignment.conversion.constrainPredictionIndex(ev.choice);
     this.field.setChoiceIndex(choice);
   }
 };
 
 /**
  * Allow the inputter to use RETURN to chose the current menu item when
  * it can't execute the command line
+ * @return true if there was a selection to use, false otherwise
  */
 Tooltip.prototype.selectChoice = function(ev) {
   if (this.field && this.field.selectChoice) {
-    this.field.selectChoice();
-  }
+    return this.field.selectChoice();
+  }
+  return false;
 };
 
 /**
  * Called by the onFieldChange event on the current Field
  */
 Tooltip.prototype.fieldChanged = function(ev) {
   this.assignment.setConversion(ev.conversion);
 
--- a/browser/devtools/commandline/test/Makefile.in
+++ b/browser/devtools/commandline/test/Makefile.in
@@ -7,40 +7,34 @@ DEPTH     = @DEPTH@
 topsrcdir = @top_srcdir@
 srcdir    = @srcdir@
 VPATH     = @srcdir@
 relativesrcdir  = @relativesrcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MOCHITEST_BROWSER_FILES = \
-  browser_gcli_addon.js \
-  browser_gcli_break.js \
-  browser_gcli_calllog.js \
-  browser_gcli_commands.js \
-  browser_gcli_cookie.js \
-  browser_gcli_dbg.js \
-  browser_gcli_edit.js \
-  browser_gcli_inspect.js \
-  browser_gcli_integrate.js \
-  browser_gcli_jsb.js \
-  browser_gcli_pagemod_export.js \
-  browser_gcli_pref.js \
-  browser_gcli_responsivemode.js \
-  browser_gcli_restart.js \
-  browser_gcli_settings.js \
+  browser_dbg_cmd_break.js \
+  browser_dbg_cmd.js \
+  browser_cmd_addon.js \
+  browser_cmd_calllog.js \
+  browser_cmd_calllog_chrome.js \
+  browser_cmd_commands.js \
+  browser_cmd_cookie.js \
+  browser_cmd_integrate.js \
+  browser_cmd_jsb.js \
+  browser_cmd_pagemod_export.js \
+  browser_cmd_pref.js \
+  browser_cmd_restart.js \
+  browser_cmd_settings.js \
   browser_gcli_web.js \
   head.js \
+  helpers.js \
   $(NULL)
 
 MOCHITEST_BROWSER_FILES += \
-  browser_gcli_break.html \
-  browser_gcli_inspect.html \
-  resources_dbg.html \
-  resources_inpage.js \
-  resources_inpage1.css \
-  resources_inpage2.css \
-  resources_jsb_script.js \
-  resources.html \
+  browser_dbg_cmd_break.html \
+  browser_dbg_cmd.html \
+  browser_cmd_pagemod_export.html \
+  browser_cmd_jsb_script.jsi \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
-
rename from browser/devtools/commandline/test/browser_gcli_addon.js
rename to browser/devtools/commandline/test/browser_cmd_addon.js
--- a/browser/devtools/commandline/test/browser_gcli_addon.js
+++ b/browser/devtools/commandline/test/browser_cmd_addon.js
@@ -1,43 +1,94 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the addon commands works as they should
+
 function test() {
-  DeveloperToolbarTest.test("about:blank", function GAT_test() {
-    function GAT_ready() {
-      Services.obs.removeObserver(GAT_ready, "gcli_addon_commands_ready", false);
+  DeveloperToolbarTest.test("about:blank", [ GAT_test ]);
+}
+
+function GAT_test() {
+  var GAT_ready = DeveloperToolbarTest.checkCalled(function() {
+    Services.obs.removeObserver(GAT_ready, "gcli_addon_commands_ready", false);
+
+    helpers.setInput('addon list dictionary');
+    helpers.check({
+      input:  'addon list dictionary',
+      hints:                       '',
+      markup: 'VVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon list extension');
+    helpers.check({
+      input:  'addon list extension',
+      hints:                      '',
+      markup: 'VVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon list locale');
+    helpers.check({
+      input:  'addon list locale',
+      hints:                   '',
+      markup: 'VVVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon list plugin');
+    helpers.check({
+      input:  'addon list plugin',
+      hints:                   '',
+      markup: 'VVVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
 
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list dictionary",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list extension",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list locale",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list plugin",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list theme",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon list all",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon disable Test_Plug-in_1.0.0.0",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.checkInputStatus({
-        typed: "addon enable Test_Plug-in_1.0.0.0",
-        status: "VALID"
-      });
-      DeveloperToolbarTest.exec({ completed: false });
-      finish();
-    }
-    Services.obs.addObserver(GAT_ready, "gcli_addon_commands_ready", false);
+    helpers.setInput('addon list theme');
+    helpers.check({
+      input:  'addon list theme',
+      hints:                  '',
+      markup: 'VVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon list all');
+    helpers.check({
+      input:  'addon list all',
+      hints:                '',
+      markup: 'VVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon disable Test_Plug-in_1.0.0.0');
+    helpers.check({
+      input:  'addon disable Test_Plug-in_1.0.0.0',
+      hints:                                    '',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
+    });
+
+    helpers.setInput('addon disable WRONG');
+    helpers.check({
+      input:  'addon disable WRONG',
+      hints:                     '',
+      markup: 'VVVVVVVVVVVVVVEEEEE',
+      status: 'ERROR'
+    });
+
+    helpers.setInput('addon enable Test_Plug-in_1.0.0.0');
+    helpers.check({
+      input:  'addon enable Test_Plug-in_1.0.0.0',
+      hints:                                   '',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID',
+      args: {
+        command: { name: 'addon enable' },
+        name: { value: 'Test Plug-in', status: 'VALID' },
+      }
+    });
+
+    DeveloperToolbarTest.exec({ completed: false });
   });
+
+  Services.obs.addObserver(GAT_ready, "gcli_addon_commands_ready", false);
 }
rename from browser/devtools/commandline/test/browser_gcli_calllog.js
rename to browser/devtools/commandline/test/browser_cmd_calllog.js
--- a/browser/devtools/commandline/test/browser_gcli_calllog.js
+++ b/browser/devtools/commandline/test/browser_cmd_calllog.js
@@ -4,51 +4,54 @@
 // Tests that the calllog commands works as they should
 
 let imported = {};
 Components.utils.import("resource:///modules/HUDService.jsm", imported);
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-calllog";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    testCallLogStatus();
-    testCallLogExec();
-    finish();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [ testCallLogStatus, testCallLogExec ]);
 }
 
 function testCallLogStatus() {
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "calllog",
-    status: "ERROR"
+  helpers.setInput('calllog');
+  helpers.check({
+    input:  'calllog',
+    hints:         '',
+    markup: 'IIIIIII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "calllog start",
-    status: "VALID",
-    emptyParameters: [ ]
+  helpers.setInput('calllog start');
+  helpers.check({
+    input:  'calllog start',
+    hints:               '',
+    markup: 'VVVVVVVVVVVVV',
+    status: 'VALID'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "calllog start",
-    status: "VALID",
-    emptyParameters: [ ]
+  helpers.setInput('calllog stop');
+  helpers.check({
+    input:  'calllog stop',
+    hints:              '',
+    markup: 'VVVVVVVVVVVV',
+    status: 'VALID'
   });
 }
 
 function testCallLogExec() {
   DeveloperToolbarTest.exec({
     typed: "calllog stop",
     args: { },
     outputMatch: /No call logging/,
   });
 
   let hud = null;
-  function onWebConsoleOpen(aSubject) {
+  var onWebConsoleOpen = DeveloperToolbarTest.checkCalled(function(aSubject) {
     Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
 
     aSubject.QueryInterface(Ci.nsISupportsString);
     hud = imported.HUDService.getHudReferenceById(aSubject.data);
     ok(hud.hudId in imported.HUDService.hudReferences, "console open");
 
     DeveloperToolbarTest.exec({
       typed: "calllog stop",
@@ -65,17 +68,17 @@ function testCallLogExec() {
     let labels = hud.outputNode.querySelectorAll(".webconsole-msg-output");
     is(labels.length, 0, "no output in console");
 
     DeveloperToolbarTest.exec({
       typed: "console close",
       args: {},
       blankOutput: true,
     });
-  }
+  });
 
   Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
 
   DeveloperToolbarTest.exec({
     typed: "calllog start",
     args: { },
     outputMatch: /Call logging started/,
   });
new file mode 100644
--- /dev/null
+++ b/browser/devtools/commandline/test/browser_cmd_calllog_chrome.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+* http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the calllog commands works as they should
+
+let imported = {};
+Components.utils.import("resource:///modules/HUDService.jsm", imported);
+
+const TEST_URI = "data:text/html;charset=utf-8,cmd-calllog-chrome";
+
+function test() {
+  DeveloperToolbarTest.test(TEST_URI, function CLCTest(browser, tab) {
+    testCallLogStatus();
+    testCallLogExec();
+  });
+}
+
+function testCallLogStatus() {
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog",
+    status: "ERROR",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestart content-variable window",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestop",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestart chrome-variable window",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestop",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestart javascript \"({a1: function() {this.a2()}," +
+           "a2: function() {}});\"",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+
+  DeveloperToolbarTest.checkInputStatus({
+    typed: "calllog chromestop",
+    status: "VALID",
+    emptyParameters: [ " " ]
+  });
+}
+
+function testCallLogExec() {
+  DeveloperToolbarTest.exec({
+    typed: "calllog chromestop",
+    args: { },
+    outputMatch: /No call logging/,
+  });
+
+  function onWebConsoleOpen(aSubject) {
+    Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
+
+    aSubject.QueryInterface(Ci.nsISupportsString);
+    let hud = imported.HUDService.getHudReferenceById(aSubject.data);
+    ok(hud.hudId in imported.HUDService.hudReferences, "console open");
+
+    DeveloperToolbarTest.exec({
+      typed: "calllog chromestop",
+      args: { },
+      outputMatch: /Stopped call logging/,
+    });
+
+    DeveloperToolbarTest.exec({
+      typed: "calllog chromestart javascript XXX",
+      outputMatch: /following exception/,
+    });
+
+    DeveloperToolbarTest.exec({
+      typed: "console clear",
+      args: {},
+      blankOutput: true,
+    });
+
+    let labels = hud.jsterm.outputNode.querySelectorAll(".webconsole-msg-output");
+    is(labels.length, 0, "no output in console");
+
+    DeveloperToolbarTest.exec({
+      typed: "console close",
+      args: {},
+      blankOutput: true,
+    });
+
+    executeSoon(finish);
+  }
+  Services.obs.addObserver(onWebConsoleOpen, "web-console-created", false);
+
+  DeveloperToolbarTest.exec({
+    typed: "calllog chromestart javascript \"({a1: function() {this.a2()},a2: function() {}});\"",
+    outputMatch: /Call logging started/,
+  });
+}
rename from browser/devtools/commandline/test/browser_gcli_commands.js
rename to browser/devtools/commandline/test/browser_cmd_commands.js
--- a/browser/devtools/commandline/test/browser_gcli_commands.js
+++ b/browser/devtools/commandline/test/browser_cmd_commands.js
@@ -4,36 +4,28 @@
 // Test various GCLI commands
 
 let imported = {};
 Components.utils.import("resource:///modules/HUDService.jsm", imported);
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-commands";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    testEcho();
-    testConsole(tab);
-
-    imported = undefined;
-    finish();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [ testEcho, testConsole ]);
 }
 
 function testEcho() {
-  /*
   DeveloperToolbarTest.exec({
     typed: "echo message",
     args: { message: "message" },
     outputMatch: /^message$/,
   });
-  */
 }
 
-function testConsole(tab) {
+function testConsole(browser, tab) {
   let hud = null;
   function onWebConsoleOpen(aSubject) {
     Services.obs.removeObserver(onWebConsoleOpen, "web-console-created");
 
     aSubject.QueryInterface(Ci.nsISupportsString);
     hud = imported.HUDService.getHudReferenceById(aSubject.data);
     ok(hud.hudId in imported.HUDService.hudReferences, "console open");
 
@@ -65,11 +57,10 @@ function testConsole(tab) {
       typed: "console close",
       args: {},
       blankOutput: true,
     });
 
     ok(!(hud.hudId in imported.HUDService.hudReferences), "console closed");
 
     imported = undefined;
-    finish();
   }
 }
rename from browser/devtools/commandline/test/browser_gcli_cookie.js
rename to browser/devtools/commandline/test/browser_cmd_cookie.js
--- a/browser/devtools/commandline/test/browser_gcli_cookie.js
+++ b/browser/devtools/commandline/test/browser_cmd_cookie.js
@@ -1,53 +1,83 @@
 /* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the cookie commands works as they should
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-cookie";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    testCookieCommands();
-    finish();
+  DeveloperToolbarTest.test(TEST_URI, [ testCookieCheck, testCookieExec ]);
+}
+
+function testCookieCheck() {
+  helpers.setInput('cookie');
+  helpers.check({
+    input:  'cookie',
+    hints:        '',
+    markup: 'IIIIII',
+    status: 'ERROR'
+  });
+
+  helpers.setInput('cookie lis');
+  helpers.check({
+    input:  'cookie lis',
+    hints:            't',
+    markup: 'IIIIIIVIII',
+    status: 'ERROR'
+  });
+
+  helpers.setInput('cookie list');
+  helpers.check({
+    input:  'cookie list',
+    hints:             '',
+    markup: 'VVVVVVVVVVV',
+    status: 'VALID'
+  });
+
+  helpers.setInput('cookie remove');
+  helpers.check({
+    input:  'cookie remove',
+    hints:               ' <key>',
+    markup: 'VVVVVVVVVVVVV',
+    status: 'ERROR'
+  });
+
+  helpers.setInput('cookie set');
+  helpers.check({
+    input:  'cookie set',
+    hints:            ' <key> <value> [options]',
+    markup: 'VVVVVVVVVV',
+    status: 'ERROR'
+  });
+
+  helpers.setInput('cookie set fruit');
+  helpers.check({
+    input:  'cookie set fruit',
+    hints:                  ' <value> [options]',
+    markup: 'VVVVVVVVVVVVVVVV',
+    status: 'ERROR'
+  });
+
+  helpers.setInput('cookie set fruit ban');
+  helpers.check({
+    input:  'cookie set fruit ban',
+    hints:                      ' [options]',
+    markup: 'VVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      key: { value: 'fruit' },
+      value: { value: 'ban' },
+      secure: { value: false },
+    }
   });
 }
 
-function testCookieCommands() {
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "cook",
-    directTabText: "ie",
-    status: "ERROR"
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "cookie l",
-    directTabText: "ist",
-    status: "ERROR"
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "cookie list",
-    status: "VALID",
-    emptyParameters: [ ]
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "cookie remove",
-    status: "ERROR",
-    emptyParameters: [ " <key>" ]
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "cookie set",
-    status: "ERROR",
-    emptyParameters: [ " <key>", " <value>" ],
-  });
-
+function testCookieExec() {
   DeveloperToolbarTest.exec({
     typed: "cookie set fruit banana",
     args: {
       key: "fruit",
       value: "banana",
       path: "/",
       domain: null,
       secure: false
rename from browser/devtools/commandline/test/browser_gcli_integrate.js
rename to browser/devtools/commandline/test/browser_cmd_integrate.js
rename from browser/devtools/commandline/test/browser_gcli_jsb.js
rename to browser/devtools/commandline/test/browser_cmd_jsb.js
--- a/browser/devtools/commandline/test/browser_gcli_jsb.js
+++ b/browser/devtools/commandline/test/browser_cmd_jsb.js
@@ -1,48 +1,57 @@
-function test() {
-  const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
-                   "test/resources_jsb_script.js";
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests that the jsb command works as it should
 
-  DeveloperToolbarTest.test("about:blank", function GJT_test() {
-    /* Commented out by bug 774057, re-enable with un-hidden jsb command
-    DeveloperToolbarTest.exec({
-      typed: "jsb AAA",
-      outputMatch: /valid/
-    });
+const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
+                 "test/browser_cmd_jsb_script.jsi";
 
-    gBrowser.addTabsProgressListener({
-      onProgressChange: function GJT_onProgressChange(aBrowser) {
-        gBrowser.removeTabsProgressListener(this);
+function test() {
+  DeveloperToolbarTest.test("about:blank", [ /*GJT_test*/ ]);
+}
 
-        let win = aBrowser._contentWindow;
-        let uri = win.document.location.href;
-        let result = win.atob(uri.replace(/.*,/, ""));
-
-        result = result.replace(/[\r\n]]/g, "\n");
+function GJT_test() {
+  helpers.setInput('jsb');
+  helpers.check({
+    input:  'jsb',
+    hints:     ' <url> [indentSize] [indentChar] [preserveNewlines] [preserveMaxNewlines] [jslintHappy] [braceStyle] [spaceBeforeConditional] [unescapeStrings]',
+    markup: 'VVV',
+    status: 'ERROR'
+  });
 
-        checkResult(result);
-        finish();
-      }
-    });
+  gBrowser.addTabsProgressListener({
+    onProgressChange: DeveloperToolbarTest.checkCalled(function GJT_onProgressChange(aBrowser) {
+      gBrowser.removeTabsProgressListener(this);
 
-    info("Checking beautification");
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "jsb " + TEST_URI + " 4 space true -1 false collapse true false",
-      status: "VALID"
-    });
-    DeveloperToolbarTest.exec({ completed: false });
+      let win = aBrowser._contentWindow;
+      let uri = win.document.location.href;
+      let result = win.atob(uri.replace(/.*,/, ""));
 
-    function checkResult(aResult) {
+      result = result.replace(/[\r\n]]/g, "\n");
+
       let correct = "function somefunc() {\n" +
                     "    for (let n = 0; n < 500; n++) {\n" +
                     "        if (n % 2 == 1) {\n" +
                     "            console.log(n);\n" +
                     "            console.log(n + 1);\n" +
                     "        }\n" +
                     "    }\n" +
                     "}";
-      is(aResult, correct, "JS has been correctly prettified");
-    }
-    */
-    finish();
+      is(result, correct, "JS has been correctly prettified");
+    })
   });
+
+  info("Checking beautification");
+
+  helpers.setInput('jsb ' + TEST_URI);
+  /*
+  helpers.check({
+    input:  'jsb',
+    hints:     ' [options]',
+    markup: 'VVV',
+    status: 'VALID'
+  });
+  */
+
+  DeveloperToolbarTest.exec({ completed: false });
 }
rename from browser/devtools/commandline/test/resources_jsb_script.js
rename to browser/devtools/commandline/test/browser_cmd_jsb_script.jsi
rename from browser/devtools/commandline/test/browser_gcli_inspect.html
rename to browser/devtools/commandline/test/browser_cmd_pagemod_export.html
rename from browser/devtools/commandline/test/browser_gcli_pagemod_export.js
rename to browser/devtools/commandline/test/browser_cmd_pagemod_export.js
--- a/browser/devtools/commandline/test/browser_gcli_pagemod_export.js
+++ b/browser/devtools/commandline/test/browser_cmd_pagemod_export.js
@@ -1,32 +1,38 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the inspect command works as it should
 
-const TEST_URI = "http://example.com/browser/browser/devtools/commandline/test/browser_gcli_inspect.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/commandline/"+
+                 "test/browser_cmd_pagemod_export.html";
 
 function test() {
   let initialHtml = "";
 
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    initialHtml = content.document.documentElement.innerHTML;
+  DeveloperToolbarTest.test(TEST_URI, [
+    init,
+    testExportHtml,
+    testPageModReplace,
+    testPageModRemoveElement,
+    testPageModRemoveAttribute
+  ]);
 
-    testExportHtml();
-    testPageModReplace();
-    testPageModRemoveElement();
-    testPageModRemoveAttribute();
-    finish();
-  });
+  function init() {
+    initialHtml = content.document.documentElement.innerHTML;
+  }
 
   function testExportHtml() {
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "export html",
-      status: "VALID"
+    helpers.setInput('export html');
+    helpers.check({
+      input:  'export html',
+      hints:             '',
+      markup: 'VVVVVVVVVVV',
+      status: 'VALID'
     });
 
     let oldOpen = content.open;
     let openURL = "";
     content.open = function(aUrl) {
       openURL = aUrl;
     };
 
@@ -45,43 +51,46 @@ function test() {
     return content.document.documentElement.innerHTML;
   }
 
   function resetContent() {
     content.document.documentElement.innerHTML = initialHtml;
   }
 
   function testPageModReplace() {
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod replace",
-      emptyParameters: [" <search>", " <replace>", " [ignoreCase]",
-                        " [selector]", " [root]", " [attrOnly]",
-                        " [contentOnly]", " [attributes]"],
-      status: "ERROR"
+    helpers.setInput('pagemod replace');
+    helpers.check({
+      input:  'pagemod replace',
+      hints:                 ' <search> <replace> [ignoreCase] [selector] [root] [attrOnly] [contentOnly] [attributes]',
+      markup: 'VVVVVVVVVVVVVVV',
+      status: 'ERROR'
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod replace some foo",
-      emptyParameters: [" [ignoreCase]", " [selector]", " [root]",
-                        " [attrOnly]", " [contentOnly]", " [attributes]"],
-      status: "VALID"
+    helpers.setInput('pagemod replace some foo');
+    helpers.check({
+      input:  'pagemod replace some foo',
+      hints:                          ' [ignoreCase] [selector] [root] [attrOnly] [contentOnly] [attributes]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod replace some foo true",
-      emptyParameters: [" [selector]", " [root]", " [attrOnly]",
-                        " [contentOnly]", " [attributes]"],
-      status: "VALID"
+    helpers.setInput('pagemod replace some foo true');
+    helpers.check({
+      input:  'pagemod replace some foo true',
+      hints:                               ' [selector] [root] [attrOnly] [contentOnly] [attributes]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod replace some foo true --attrOnly",
-      emptyParameters: [" [selector]", " [root]", " [contentOnly]",
-                        " [attributes]"],
-      status: "VALID"
+    helpers.setInput('pagemod replace some foo true --attrOnly');
+    helpers.check({
+      input:  'pagemod replace some foo true --attrOnly',
+      hints:                                          ' [selector] [root] [contentOnly] [attributes]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
     });
 
     DeveloperToolbarTest.exec({
       typed: "pagemod replace sOme foOBar",
       outputMatch: /^[^:]+: 13\. [^:]+: 0\. [^:]+: 0\.\s*$/
     });
 
     is(getContent(), initialHtml, "no change in the page");
@@ -138,31 +147,38 @@ function test() {
           ".someclass changed to .foobarclass");
     isnot(getContent().indexOf('<p id="someid">#someid'), -1,
           "#someid did not change");
 
     resetContent();
   }
 
   function testPageModRemoveElement() {
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod remove",
-      status: "ERROR"
+    helpers.setInput('pagemod remove');
+    helpers.check({
+      input:  'pagemod remove',
+      hints:                '',
+      markup: 'IIIIIIIVIIIIII',
+      status: 'ERROR'
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod remove element",
-      emptyParameters: [" <search>", " [root]", " [stripOnly]", " [ifEmptyOnly]"],
-      status: "ERROR"
+    helpers.setInput('pagemod remove element');
+    helpers.check({
+      input:  'pagemod remove element',
+      hints:                        ' <search> [root] [stripOnly] [ifEmptyOnly]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVV',
+      status: 'ERROR'
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod remove element foo",
-      emptyParameters: [" [root]", " [stripOnly]", " [ifEmptyOnly]"],
-      status: "VALID"
+    helpers.setInput('pagemod remove element foo');
+    helpers.check({
+      input:  'pagemod remove element foo',
+      hints:                            ' [root] [stripOnly] [ifEmptyOnly]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID'
     });
 
     DeveloperToolbarTest.exec({
       typed: "pagemod remove element p",
       outputMatch: /^[^:]+: 3\. [^:]+: 3\.\s*$/
     });
 
     is(getContent().indexOf('<p class="someclass">'), -1, "p.someclass removed");
@@ -207,26 +223,42 @@ function test() {
     isnot(getContent().indexOf(".someclass"), -1, ".someclass still exists");
     isnot(getContent().indexOf("#someid"), -1, "#someid still exists");
     isnot(getContent().indexOf("<strong>p"), -1, "<strong> still exists");
 
     resetContent();
   }
 
   function testPageModRemoveAttribute() {
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod remove attribute",
-      emptyParameters: [" <searchAttributes>", " <searchElements>", " [root]", " [ignoreCase]"],
-      status: "ERROR"
+    helpers.setInput('pagemod remove attribute ');
+    helpers.check({
+      input:  'pagemod remove attribute ',
+      hints:                           '<searchAttributes> <searchElements> [root] [ignoreCase]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'ERROR',
+      args: {
+        searchAttributes: { value: undefined, status: 'INCOMPLETE' },
+        searchElements: { value: undefined, status: 'INCOMPLETE' },
+        root: { value: undefined },
+        ignoreCase: { value: false },
+      }
     });
 
-    DeveloperToolbarTest.checkInputStatus({
-      typed: "pagemod remove attribute foo bar",
-      emptyParameters: [" [root]", " [ignoreCase]"],
-      status: "VALID"
+    helpers.setInput('pagemod remove attribute foo bar');
+    helpers.check({
+      input:  'pagemod remove attribute foo bar',
+      hints:                                  ' [root] [ignoreCase]',
+      markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+      status: 'VALID',
+      args: {
+        searchAttributes: { value: 'foo' },
+        searchElements: { value: 'bar' },
+        root: { value: undefined },
+        ignoreCase: { value: false },
+      }
     });
 
     DeveloperToolbarTest.exec({
       typed: "pagemod remove attribute foo bar",
       outputMatch: /^[^:]+: 0\. [^:]+: 0\.\s*$/
     });
 
     is(getContent(), initialHtml, "nothing changed in the page");
rename from browser/devtools/commandline/test/browser_gcli_pref.js
rename to browser/devtools/commandline/test/browser_cmd_pref.js
--- a/browser/devtools/commandline/test/browser_gcli_pref.js
+++ b/browser/devtools/commandline/test/browser_cmd_pref.js
@@ -17,29 +17,26 @@ imports.XPCOMUtils.defineLazyGetter(impo
 imports.XPCOMUtils.defineLazyGetter(imports, "supportsString", function() {
   return Components.classes["@mozilla.org/supports-string;1"]
           .createInstance(Components.interfaces.nsISupportsString);
 });
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-pref";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    setup();
-
-    testPrefSetEnable();
-    testPrefStatus();
-    testPrefBoolExec();
-    testPrefNumberExec();
-    testPrefStringExec();
-    testPrefSetDisable();
-
-    shutdown();
-    finish();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [
+    setup,
+    testPrefSetEnable,
+    testPrefStatus,
+    testPrefBoolExec,
+    testPrefNumberExec,
+    testPrefStringExec,
+    testPrefSetDisable,
+    shutdown
+  ]);
 }
 
 let tiltEnabledOrig = undefined;
 let tabSizeOrig = undefined;
 let remoteHostOrig = undefined;
 
 function setup() {
   Components.utils.import("resource://gre/modules/devtools/Require.jsm", imports);
@@ -67,102 +64,113 @@ function shutdown() {
   tiltEnabledOrig = undefined;
   tabSizeOrig = undefined;
   remoteHostOrig = undefined;
 
   imports = undefined;
 }
 
 function testPrefStatus() {
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref s",
-    markup: "IIIIVI",
-    status: "ERROR",
-    directTabText: "et"
+  helpers.setInput('pref');
+  helpers.check({
+    input:  'pref',
+    hints:      '',
+    markup: 'IIII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show",
-    markup: "VVVVVVVVV",
-    status: "ERROR",
-    emptyParameters: [ " <setting>" ]
+  helpers.setInput('pref s');
+  helpers.check({
+    input:  'pref s',
+    hints:        'et',
+    markup: 'IIIIVI',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show tempTBo",
-    markup: "VVVVVVVVVVEEEEEEE",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref sh');
+  helpers.check({
+    input:  'pref sh',
+    hints:         'ow',
+    markup: 'IIIIVII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show devtools.toolbar.ena",
-    markup: "VVVVVVVVVVIIIIIIIIIIIIIIIIIIII",
-    directTabText: "bled",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref show ');
+  helpers.check({
+    input:  'pref show ',
+    markup: 'VVVVVVVVVV',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show hideIntro",
-    markup: "VVVVVVVVVVIIIIIIIII",
-    directTabText: "",
-    arrowTabText: "devtools.gcli.hideIntro",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref show usetexttospeech');
+  helpers.check({
+    input:  'pref show usetexttospeech',
+    hints:                           ' -> accessibility.usetexttospeech',
+    markup: 'VVVVVVVVVVIIIIIIIIIIIIIII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show devtools.toolbar.enabled",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
-    status: "VALID",
-    emptyParameters: [ ]
+  helpers.setInput('pref show devtools.til');
+  helpers.check({
+    input:  'pref show devtools.til',
+    hints:                        't.enabled',
+    markup: 'VVVVVVVVVVIIIIIIIIIIII',
+    status: 'ERROR',
+    tooltipState: 'true:importantFieldFlag',
+    args: {
+      setting: { value: undefined, status: 'INCOMPLETE' },
+    }
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show devtools.tilt.enabled 4",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE",
-    directTabText: "",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref reset devtools.tilt.enabled');
+  helpers.check({
+    input:  'pref reset devtools.tilt.enabled',
+    hints:                                  '',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref show devtools.tilt.enabled",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
-    status: "VALID",
-    emptyParameters: [ ]
+  helpers.setInput('pref show devtools.tilt.enabled 4');
+  helpers.check({
+    input:  'pref show devtools.tilt.enabled 4',
+    hints:                                   '',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref reset devtools.tilt.enabled",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
-    status: "VALID",
-    emptyParameters: [ ]
+  helpers.setInput('pref set devtools.tilt.enabled 4');
+  helpers.check({
+    input:  'pref set devtools.tilt.enabled 4',
+    hints:                                  '',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE',
+    status: 'ERROR',
+    args: {
+      setting: { arg: ' devtools.tilt.enabled' },
+      value: { status: 'ERROR', message: 'Can\'t use \'4\'.' },
+    }
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref set devtools.tilt.enabled 4",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVE",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref set devtools.editor.tabsize 4');
+  helpers.check({
+    input:  'pref set devtools.editor.tabsize 4',
+    hints:                                    '',
+    markup: 'VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      setting: { arg: ' devtools.editor.tabsize' },
+      value: { value: 4 },
+    }
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref set devtools.editor.tabsize 4",
-    markup: "VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV",
-    status: "VALID",
-    emptyParameters: [ ]
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "pref list",
-    markup: "EEEEVEEEE",
-    status: "ERROR",
-    emptyParameters: [ ]
+  helpers.setInput('pref list');
+  helpers.check({
+    input:  'pref list',
+    hints:           '',
+    markup: 'EEEEVEEEE',
+    status: 'ERROR'
   });
 }
 
 function testPrefSetEnable() {
   DeveloperToolbarTest.exec({
     typed: "pref set devtools.editor.tabsize 9",
     args: {
       setting: imports.settings.getSetting("devtools.editor.tabsize"),
rename from browser/devtools/commandline/test/browser_gcli_restart.js
rename to browser/devtools/commandline/test/browser_cmd_restart.js
--- a/browser/devtools/commandline/test/browser_gcli_restart.js
+++ b/browser/devtools/commandline/test/browser_cmd_restart.js
@@ -1,48 +1,32 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test that restart command works properly (input wise)
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-command-restart";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    testRestart();
-    finish();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [ testRestart ]);
 }
 
 function testRestart() {
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "restart",
-    markup: "VVVVVVV",
-    status: "VALID",
-    emptyParameters: [ " [nocache]" ],
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "restart ",
-    markup: "VVVVVVVV",
-    status: "VALID",
-    directTabText: "false"
+  helpers.setInput('restart');
+  helpers.check({
+    input:  'restart',
+    markup: 'VVVVVVV',
+    status: 'VALID',
+    args: {
+      nocache: { value: false },
+    }
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "restart t",
-    markup: "VVVVVVVVI",
-    status: "ERROR",
-    directTabText: "rue"
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "restart --nocache",
-    markup: "VVVVVVVVVVVVVVVVV",
-    status: "VALID"
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed:  "restart --noca",
-    markup: "VVVVVVVVEEEEEE",
-    status: "ERROR",
+  helpers.setInput('restart --nocache');
+  helpers.check({
+    input:  'restart --nocache',
+    markup: 'VVVVVVVVVVVVVVVVV',
+    status: 'VALID',
+    args: {
+      nocache: { value: true },
+    }
   });
 }
rename from browser/devtools/commandline/test/browser_gcli_settings.js
rename to browser/devtools/commandline/test/browser_cmd_settings.js
--- a/browser/devtools/commandline/test/browser_gcli_settings.js
+++ b/browser/devtools/commandline/test/browser_cmd_settings.js
@@ -17,24 +17,17 @@ imports.XPCOMUtils.defineLazyGetter(impo
 imports.XPCOMUtils.defineLazyGetter(imports, "supportsString", function() {
   return Components.classes["@mozilla.org/supports-string;1"]
           .createInstance(Components.interfaces.nsISupportsString);
 });
 
 const TEST_URI = "data:text/html;charset=utf-8,gcli-settings";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    setup();
-
-    testSettings();
-
-    shutdown();
-    finish();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [ setup, testSettings, shutdown ]);
 }
 
 let tiltEnabled = undefined;
 let tabSize = undefined;
 let remoteHost = undefined;
 
 let tiltEnabledOrig = undefined;
 let tabSizeOrig = undefined;
rename from browser/devtools/commandline/test/resources_dbg.html
rename to browser/devtools/commandline/test/browser_dbg_cmd.html
rename from browser/devtools/commandline/test/browser_gcli_dbg.js
rename to browser/devtools/commandline/test/browser_dbg_cmd.js
--- a/browser/devtools/commandline/test/browser_gcli_dbg.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd.js
@@ -1,67 +1,72 @@
 function test() {
-  const TEST_URI = TEST_BASE_HTTP + "resources_dbg.html";
+  const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
+                   "test/browser_dbg_cmd.html";
 
-  DeveloperToolbarTest.test(TEST_URI, function GAT_test() {
-    let pane = DebuggerUI.toggleDebugger();
-    ok(pane, "toggleDebugger() should return a pane.");
-    let frame = pane._frame;
+  DeveloperToolbarTest.test(TEST_URI, function() {
+    testDbgCmd();
+  });
+}
 
-    frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
-      frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
+function testDbgCmd() {
+  let pane = DebuggerUI.toggleDebugger();
+  ok(pane, "toggleDebugger() should return a pane.");
+  let frame = pane._frame;
+
+  frame.addEventListener("Debugger:Connecting", function dbgConnected(aEvent) {
+    frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
 
-      // Wait for the initial resume...
-      aEvent.target.ownerDocument.defaultView.gClient
-          .addOneTimeListener("resumed", function() {
+    // Wait for the initial resume...
+    aEvent.target.ownerDocument.defaultView.gClient
+        .addOneTimeListener("resumed", function() {
 
-        info("Starting tests.");
+      info("Starting tests.");
 
-        let contentDoc = content.window.document;
-        let output = contentDoc.querySelector("input[type=text]");
-        let btnDoit = contentDoc.querySelector("input[type=button]");
+      let contentDoc = content.window.document;
+      let output = contentDoc.querySelector("input[type=text]");
+      let btnDoit = contentDoc.querySelector("input[type=button]");
 
-        cmd("dbg interrupt", function() {
-          ok(true, "debugger is paused");
-          pane.contentWindow.gClient.addOneTimeListener("resumed", function() {
-            ok(true, "debugger continued");
-            pane.contentWindow.gClient.addOneTimeListener("paused", function() {
+      cmd("dbg interrupt", function() {
+        ok(true, "debugger is paused");
+        pane.contentWindow.gClient.addOneTimeListener("resumed", function() {
+          ok(true, "debugger continued");
+          pane.contentWindow.gClient.addOneTimeListener("paused", function() {
+            cmd("dbg step in", function() {
               cmd("dbg step in", function() {
                 cmd("dbg step in", function() {
-                  cmd("dbg step in", function() {
-                    is(output.value, "step in", "debugger stepped in");
-                    cmd("dbg step over", function() {
-                      is(output.value, "step over", "debugger stepped over");
-                      cmd("dbg step out", function() {
-                        is(output.value, "step out", "debugger stepped out");
+                  is(output.value, "step in", "debugger stepped in");
+                  cmd("dbg step over", function() {
+                    is(output.value, "step over", "debugger stepped over");
+                    cmd("dbg step out", function() {
+                      is(output.value, "step out", "debugger stepped out");
+                      cmd("dbg continue", function() {
                         cmd("dbg continue", function() {
-                          cmd("dbg continue", function() {
-                            is(output.value, "dbg continue", "debugger continued");
-                            pane.contentWindow.gClient.close(function() {
-                              finish();
-                            });
+                          is(output.value, "dbg continue", "debugger continued");
+                          pane.contentWindow.gClient.close(function() {
+                            finish();
                           });
                         });
                       });
                     });
                   });
                 });
               });
             });
-            EventUtils.sendMouseEvent({type:"click"}, btnDoit);
           });
-          DeveloperToolbarTest.exec({
-            typed: "dbg continue",
-            blankOutput: true
-          });
+          EventUtils.sendMouseEvent({type:"click"}, btnDoit);
+        });
+        DeveloperToolbarTest.exec({
+          typed: "dbg continue",
+          blankOutput: true
         });
       });
+    });
 
-      function cmd(aTyped, aCallback) {
-        pane.contentWindow.gClient.addOneTimeListener("paused", aCallback);
-        DeveloperToolbarTest.exec({
-          typed: aTyped,
-          blankOutput: true
-        });
-      }
-    });
+    function cmd(aTyped, aCallback) {
+      pane.contentWindow.gClient.addOneTimeListener("paused", aCallback);
+      DeveloperToolbarTest.exec({
+        typed: aTyped,
+        blankOutput: true
+      });
+    }
   });
 }
rename from browser/devtools/commandline/test/browser_gcli_break.html
rename to browser/devtools/commandline/test/browser_dbg_cmd_break.html
rename from browser/devtools/commandline/test/browser_gcli_break.js
rename to browser/devtools/commandline/test/browser_dbg_cmd_break.js
--- a/browser/devtools/commandline/test/browser_gcli_break.js
+++ b/browser/devtools/commandline/test/browser_dbg_cmd_break.js
@@ -1,82 +1,120 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Tests that the break command works as it should
 
-const TEST_URI = "http://example.com/browser/browser/devtools/commandline/test/browser_gcli_break.html";
+const TEST_URI = "http://example.com/browser/browser/devtools/commandline/" +
+                 "test/browser_dbg_cmd_break.html";
 
 function test() {
-  DeveloperToolbarTest.test(TEST_URI, function(browser, tab) {
-    testBreakCommands();
-  });
+  DeveloperToolbarTest.test(TEST_URI, [ testBreakCommands ]);
 }
 
 function testBreakCommands() {
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "brea",
-    directTabText: "k",
-    status: "ERROR"
+  helpers.setInput('break');
+  helpers.check({
+    input:  'break',
+    hints:       '',
+    markup: 'IIIII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "break",
-    status: "ERROR"
+  helpers.setInput('break add');
+  helpers.check({
+    input:  'break add',
+    hints:           '',
+    markup: 'IIIIIVIII',
+    status: 'ERROR'
   });
 
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "break add",
-    status: "ERROR"
-  });
-
-  DeveloperToolbarTest.checkInputStatus({
-    typed: "break add line",
-    emptyParameters: [ " <file>", " <line>" ],
-    status: "ERROR"
+  helpers.setInput('break add line');
+  helpers.check({
+    input:  'break add line',
+    hints:                ' <file> <line>',
+    markup: 'VVVVVVVVVVVVVV',
+    status: 'ERROR'
   });
 
   let pane = DebuggerUI.toggleDebugger();
-  pane._frame.addEventListener("Debugger:Connecting", function dbgConnected() {
+
+  var dbgConnected = DeveloperToolbarTest.checkCalled(function() {
     pane._frame.removeEventListener("Debugger:Connecting", dbgConnected, true);
 
     // Wait for the initial resume.
     let client = pane.contentWindow.gClient;
-    client.addOneTimeListener("resumed", function() {
-      client.activeThread.addOneTimeListener("framesadded", function() {
-        DeveloperToolbarTest.checkInputStatus({
-          typed: "break add line " + TEST_URI + " " + content.wrappedJSObject.line0,
-          status: "VALID"
+
+    var resumed = DeveloperToolbarTest.checkCalled(function() {
+
+      var framesAdded = DeveloperToolbarTest.checkCalled(function() {
+        helpers.setInput('break add line ' + TEST_URI + ' ' + content.wrappedJSObject.line0);
+        helpers.check({
+          hints: '',
+          status: 'VALID',
+          args: {
+            file: { value: TEST_URI },
+            line: { value: content.wrappedJSObject.line0 },
+          }
         });
+
         DeveloperToolbarTest.exec({
           args: {
             type: 'line',
             file: TEST_URI,
             line: content.wrappedJSObject.line0
           },
           completed: false
         });
 
-        DeveloperToolbarTest.checkInputStatus({
-          typed: "break list",
-          status: "VALID"
+        helpers.setInput('break list');
+        helpers.check({
+          input:  'break list',
+          hints:            '',
+          markup: 'VVVVVVVVVV',
+          status: 'VALID'
         });
+
         DeveloperToolbarTest.exec();
 
-        client.activeThread.resume(function() {
-          DeveloperToolbarTest.checkInputStatus({
-            typed: "break del 0",
-            status: "VALID"
+        var cleanup = DeveloperToolbarTest.checkCalled(function() {
+          helpers.setInput('break del 9');
+          helpers.check({
+            input:  'break del 9',
+            hints:             '',
+            markup: 'VVVVVVVVVVE',
+            status: 'ERROR',
+            args: {
+              breakid: { status: 'ERROR', message: '9 is greater than maximum allowed: 0.' },
+            }
           });
+
+          helpers.setInput('break del 0');
+          helpers.check({
+            input:  'break del 0',
+            hints:             '',
+            markup: 'VVVVVVVVVVV',
+            status: 'VALID',
+            args: {
+              breakid: { value: 0 },
+            }
+          });
+
           DeveloperToolbarTest.exec({
             args: { breakid: 0 },
             completed: false
           });
+        });
 
-          finish();
-        });
+        client.activeThread.resume(cleanup);
       });
 
+      client.activeThread.addOneTimeListener("framesadded", framesAdded);
+
       // Trigger newScript notifications using eval.
       content.wrappedJSObject.firstCall();
     });
-  }, true);
+
+    client.addOneTimeListener("resumed", resumed);
+  });
+
+  pane._frame.addEventListener("Debugger:Connecting", dbgConnected, true);
 }
--- a/browser/devtools/commandline/test/browser_gcli_web.js
+++ b/browser/devtools/commandline/test/browser_gcli_web.js
@@ -148,28 +148,63 @@ define('gclitest/index', ['require', 'ex
     options = options || {};
     if (options.settings != null) {
       settings.setDefaults(options.settings);
     }
 
     window.display = new Display(options);
     var requisition = window.display.requisition;
 
-    exports.run({
-      window: window,
-      display: window.display,
-      hideExec: true
-    });
-