Merge m-c to fx-team
authorPanos Astithas <past@mozilla.com>
Fri, 21 Dec 2012 10:28:48 +0200
changeset 125760 c965fa0804cf21e3db87866ce16fc2698a5a5d02
parent 125740 beec8736b9ec7dd74c100aa2b0437dacdb4c6a08 (current diff)
parent 125759 4d709ea729b30e2e6a72dc35c3fe168fcabb819c (diff)
child 125761 60d13dd4291259b8c0eb26f2f65249e4985c4a78
child 125964 a0948a41a5d53dd56435c37fd77394b07005f4fa
push id2151
push userlsblakk@mozilla.com
push dateTue, 19 Feb 2013 18:06:57 +0000
treeherdermozilla-beta@4952e88741ec [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone20.0a1
first release with
nightly linux32
c965fa0804cf / 20.0a1 / 20121221030826 / files
nightly linux64
c965fa0804cf / 20.0a1 / 20121221030826 / files
nightly mac
c965fa0804cf / 20.0a1 / 20121221030826 / files
nightly win32
c965fa0804cf / 20.0a1 / 20121221030826 / files
nightly win64
c965fa0804cf / 20.0a1 / 20121221030826 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1030,17 +1030,16 @@ pref("devtools.responsiveUI.enabled", tr
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-enabled", true);
 pref("devtools.debugger.remote-host", "localhost");
 pref("devtools.debugger.remote-autoconnect", false);
 pref("devtools.debugger.remote-connection-retries", 3);
 pref("devtools.debugger.remote-timeout", 20000);
 
 // The default Debugger UI settings
-pref("devtools.debugger.ui.height", 250);
 pref("devtools.debugger.ui.win-x", 0);
 pref("devtools.debugger.ui.win-y", 0);
 pref("devtools.debugger.ui.win-width", 900);
 pref("devtools.debugger.ui.win-height", 400);
 pref("devtools.debugger.ui.stackframes-width", 200);
 pref("devtools.debugger.ui.variables-width", 300);
 pref("devtools.debugger.ui.panes-visible-on-startup", false);
 pref("devtools.debugger.ui.variables-sorting-enabled", true);
--- a/browser/base/content/browser-appmenu.inc
+++ b/browser/base/content/browser-appmenu.inc
@@ -142,52 +142,52 @@
                       label="&printPreviewCmd.label;"
                       command="cmd_printPreview"/>
             <menuitem id="appmenu_printSetup"
                       label="&printSetupCmd.label;"
                       command="cmd_pageSetup"/>
           </menupopup>
       </splitmenu>
       <menuseparator class="appmenu-menuseparator"/>
-      <menu id="appmenu_webDeveloper"
-            label="&appMenuWebDeveloper.label;">
+      <splitmenu id="appmenu_webDeveloper"
+                 command="Tools:DevToolbox"
+                 label="&appMenuWebDeveloper.label;">
         <menupopup id="appmenu_webDeveloper_popup">
           <menuitem id="appmenu_devToolbox"
                     observes="devtoolsMenuBroadcaster_DevToolbox"/>
           <menuseparator id="appmenu_devtools_separator"/>
           <menuitem id="appmenu_devToolbar"
                     observes="devtoolsMenuBroadcaster_DevToolbar"/>
           <menuitem id="appmenu_chromeDebugger"
                     observes="devtoolsMenuBroadcaster_ChromeDebugger"/>
           <menuitem id="appmenu_responsiveUI"
                     observes="devtoolsMenuBroadcaster_ResponsiveUI"/>
           <menuitem id="appmenu_scratchpad"
                     observes="devtoolsMenuBroadcaster_Scratchpad"/>
           <menuitem id="appmenu_pageSource"
                     observes="devtoolsMenuBroadcaster_PageSource"/>
           <menuitem id="appmenu_errorConsole"
                     observes="devtoolsMenuBroadcaster_ErrorConsole"/>
-          <menuseparator id="appmenu_devToolsConnectSeparator"/>
           <menuitem id="appmenu_devtools_connect"
                     observes="devtoolsMenuBroadcaster_connect"/>
           <menuseparator id="appmenu_devToolsEndSeparator"/>
           <menuitem id="appmenu_getMoreDevtools"
                     observes="devtoolsMenuBroadcaster_GetMoreTools"/>
           <menuseparator/>
 #define ID_PREFIX appmenu_developer_
 #define OMIT_ACCESSKEYS
 #include browser-charsetmenu.inc
 #undef ID_PREFIX
 #undef OMIT_ACCESSKEYS
           <menuitem label="&goOfflineCmd.label;"
                     type="checkbox"
                     observes="workOfflineMenuitemState"
                     oncommand="BrowserOffline.toggleOfflineStatus();"/>
         </menupopup>
-      </menu>
+      </splitmenu>
       <menuseparator class="appmenu-menuseparator"/>
 #define ID_PREFIX appmenu_
 #define OMIT_ACCESSKEYS
 #include browser-charsetmenu.inc
 #undef ID_PREFIX
 #undef OMIT_ACCESSKEYS
       <menuitem id="appmenu_fullScreen"
                 class="menuitem-tooltip"
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -579,17 +579,16 @@
                             observes="devtoolsMenuBroadcaster_Scratchpad"
                             accesskey="&scratchpad.accesskey;"/>
                   <menuitem id="menu_pageSource"
                             observes="devtoolsMenuBroadcaster_PageSource"
                             accesskey="&pageSourceCmd.accesskey;"/>
                   <menuitem id="javascriptConsole"
                             observes="devtoolsMenuBroadcaster_ErrorConsole"
                             accesskey="&errorConsoleCmd.accesskey;"/>
-                  <menuseparator id="menu_devToolsConnectSeparator"/>
                   <menuitem id="menu_devtools_connect"
                             observes="devtoolsMenuBroadcaster_connect"/>
                   <menuseparator id="devToolsEndSeparator"/>
                   <menuitem id="getMoreDevtools"
                             observes="devtoolsMenuBroadcaster_GetMoreTools"
                             accesskey="&getMoreDevtoolsCmd.accesskey;"/>
                 </menupopup>
               </menu>
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -92,17 +92,17 @@
     <command id="Tools:DevToolbox" oncommand="gDevToolsBrowser.toggleToolboxCommand(gBrowser);"/>
     <command id="Tools:DevToolbar" oncommand="DeveloperToolbar.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:DevToolbarFocus" oncommand="DeveloperToolbar.focusToggle();" disabled="true"/>
     <command id="Tools:ChromeDebugger" oncommand="DebuggerUI.toggleChromeDebugger();" disabled="true" hidden="true"/>
     <command id="Tools:Scratchpad" oncommand="Scratchpad.openScratchpad();" disabled="true" hidden="true"/>
     <command id="Tools:ResponsiveUI" oncommand="ResponsiveUI.toggle();" disabled="true" hidden="true"/>
     <command id="Tools:Addons" oncommand="BrowserOpenAddonsMgr();"/>
     <command id="Tools:ErrorConsole" oncommand="toJavaScriptConsole()" disabled="true" hidden="true"/>
-    <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)"/>
+    <command id="Tools:DevToolsConnect" oncommand="gDevToolsBrowser.openConnectScreen(gBrowser)" disabled="true" hidden="true"/>
     <command id="Tools:Sanitize"
      oncommand="Cc['@mozilla.org/browser/browserglue;1'].getService(Ci.nsIBrowserGlue).sanitize(window);"/>
     <command id="Tools:PrivateBrowsing"
 #ifdef MOZ_PER_WINDOW_PRIVATE_BROWSING
       oncommand="OpenBrowserWindow({private: true});"/>
 #else
       oncommand="gPrivateBrowsingUI.toggleMode();"/>
 #endif
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1439,16 +1439,24 @@ var gBrowserInit = {
     // Enable Scratchpad in the UI, if the preference allows this.
     let scratchpadEnabled = gPrefService.getBoolPref(Scratchpad.prefEnabledName);
     if (scratchpadEnabled) {
       let cmd = document.getElementById("Tools:Scratchpad");
       cmd.removeAttribute("disabled");
       cmd.removeAttribute("hidden");
     }
 
+    // Enable DevTools connection screen, if the preference allows this.
+    let devtoolsRemoteEnabled = gPrefService.getBoolPref("devtools.debugger.remote-enabled");
+    if (devtoolsRemoteEnabled) {
+      let cmd = document.getElementById("Tools:DevToolsConnect");
+      cmd.removeAttribute("disabled");
+      cmd.removeAttribute("hidden");
+    }
+
 #ifdef MENUBAR_CAN_AUTOHIDE
     // If the user (or the locale) hasn't enabled the top-level "Character
     // Encoding" menu via the "browser.menu.showCharacterEncoding" preference,
     // hide it.
     if ("true" != gPrefService.getComplexValue("browser.menu.showCharacterEncoding",
                                                Ci.nsIPrefLocalizedString).data)
       document.getElementById("appmenu_charsetMenu").hidden = true;
 #endif
--- a/browser/devtools/debugger/DebuggerUI.jsm
+++ b/browser/devtools/debugger/DebuggerUI.jsm
@@ -169,22 +169,16 @@ DebuggerUI.prototype = {
    * @return ChromeDebuggerProcess | null
    *         The chrome debugger instance if it exists, null otherwise.
    */
   getChromeDebugger: function DUI_getChromeDebugger() {
     return '_chromeDebugger' in this ? this._chromeDebugger : null;
   },
 
   /**
-   * Get the preferences associated with the debugger frontend.
-   * @return object
-   */
-  get preferences() Prefs,
-
-  /**
    * Currently, there can only be one debugger per tab.
    * Show an asynchronous notification which asks the user to switch the
    * script debugger to the current tab if it's already open in another one.
    */
   showTabSwitchNotification: function DUI_showTabSwitchNotification() {
     let gBrowser = this.chromeWindow.gBrowser;
     let selectedBrowser = gBrowser.selectedBrowser;
 
@@ -274,17 +268,16 @@ DebuggerPane.prototype = {
 
     let gBrowser = this._win.gBrowser;
     let ownerDocument = gBrowser.parentNode.ownerDocument;
 
     this._splitter = ownerDocument.createElement("splitter");
     this._splitter.setAttribute("class", "devtools-horizontal-splitter");
 
     this._frame = ownerDocument.createElement("iframe");
-    this._frame.height = Prefs.height;
 
     this._nbox = gBrowser.getNotificationBox(this._tab.linkedBrowser);
     this._nbox.appendChild(this._splitter);
     this._nbox.appendChild(this._frame);
 
     let self = this;
 
     this._frame.addEventListener("Debugger:Loaded", function dbgLoaded() {
@@ -321,17 +314,16 @@ DebuggerPane.prototype = {
     if (typeof aCloseCallback == "function") {
       let frame = this._frame;
       frame.addEventListener("unload", function onUnload() {
         frame.removeEventListener("unload", onUnload, true);
         aCloseCallback();
       }, true)
     }
 
-    Prefs.height = this._frame.height;
     this._frame.removeEventListener("Debugger:Unloaded", this.close, true);
     this._nbox.removeChild(this._splitter);
     this._nbox.removeChild(this._frame);
 
     this._splitter = null;
     this._frame = null;
     this._nbox = null;
     this._win = null;
@@ -594,31 +586,17 @@ let L10N = {
 
 XPCOMUtils.defineLazyGetter(L10N, "stringBundle", function() {
   return Services.strings.createBundle(DBG_STRINGS_URI);
 });
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
-let Prefs = {
-  /**
-   * Gets the preferred height of the debugger pane.
-   * @return number
-   */
-  get height()
-    Services.prefs.getIntPref("devtools.debugger.ui.height"),
-
-  /**
-   * Sets the preferred height of the debugger pane.
-   * @param number aValue
-   */
-  set height(aValue)
-    Services.prefs.setIntPref("devtools.debugger.ui.height", aValue)
-};
+let Prefs = {};
 
 /**
  * Gets the preferred default remote debugging host.
  * @return string
  */
 XPCOMUtils.defineLazyGetter(Prefs, "remoteHost", function() {
   return Services.prefs.getCharPref("devtools.debugger.remote-host");
 });
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -6,16 +6,17 @@
 "use strict";
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const DBG_STRINGS_URI = "chrome://browser/locale/devtools/debugger.properties";
 const NEW_SCRIPT_DISPLAY_DELAY = 200; // ms
+const FETCH_SOURCE_RESPONSE_DELAY = 50; // ms
 const FRAME_STEP_CLEAR_DELAY = 100; // ms
 const CALL_STACK_PAGE_SIZE = 25; // frames
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 Cu.import("resource:///modules/source-editor.jsm");
@@ -1161,39 +1162,51 @@ SourceScripts.prototype = {
    *        - forced: force the source to be immediately added
    */
   _addSource: function SS__addSource(aSource, aOptions = {}) {
     let url = aSource.url;
     let label = SourceUtils.getSourceLabel(url);
 
     DebuggerView.Sources.push(label, url, {
       forced: aOptions.forced,
+      tooltip: url,
       attachment: aSource
     });
   },
 
   /**
    * Gets a specified source's text.
    *
    * @param object aSource
    *        The source object coming from the active thread.
    * @param function aCallback
    *        Function called after the source text has been loaded.
+   * @param function aOnTimeout
+   *        Function called when the source text takes too long to fetch.
    */
-  getText: function SS_getText(aSource, aCallback) {
+  getText: function SS_getText(aSource, aCallback, aOnTimeout) {
     // If already loaded, return the source text immediately.
     if (aSource.loaded) {
       aCallback(aSource.url, aSource.text);
       return;
     }
 
+    // If the source text takes too long to fetch, invoke a timeout to
+    // avoid blocking any operations.
+    if (aOnTimeout) {
+      var fetchTimeout = window.setTimeout(aOnTimeout, FETCH_SOURCE_RESPONSE_DELAY);
+    }
+
     // Get the source text from the active thread.
     this.activeThread.source(aSource.source).source(function(aResponse) {
+      window.clearTimeout(fetchTimeout);
+
       if (aResponse.error) {
-        Cu.reportError("Error loading " + aUrl);
+        Cu.reportError("Error loading " + aSource.url + "\n" + aResponse.error);
+        aCallback(aSource.url, "");
         return;
       }
       aSource.loaded = true;
       aSource.text = aResponse.source;
       aCallback(aSource.url, aResponse.source);
     });
   }
 };
@@ -1604,17 +1617,16 @@ let Prefs = {
   map: function P_map(aType, aPropertyName, aPrefName) {
     Object.defineProperty(this, aPropertyName, {
       get: function() this._get(aType, aPrefName),
       set: function(aValue) this._set(aType, aPrefName, aValue)
     });
   }
 };
 
-Prefs.map("Int", "height", "devtools.debugger.ui.height");
 Prefs.map("Int", "windowX", "devtools.debugger.ui.win-x");
 Prefs.map("Int", "windowY", "devtools.debugger.ui.win-y");
 Prefs.map("Int", "windowWidth", "devtools.debugger.ui.win-width");
 Prefs.map("Int", "windowHeight", "devtools.debugger.ui.win-height");
 Prefs.map("Int", "stackframesWidth", "devtools.debugger.ui.stackframes-width");
 Prefs.map("Int", "variablesWidth", "devtools.debugger.ui.variables-width");
 Prefs.map("Bool", "panesVisibleOnStartup", "devtools.debugger.ui.panes-visible-on-startup");
 Prefs.map("Bool", "variablesSortingEnabled", "devtools.debugger.ui.variables-sorting-enabled");
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -1255,16 +1255,17 @@ create({ constructor: WatchExpressionsVi
 /**
  * Functions handling the global search UI.
  */
 function GlobalSearchView() {
   dumpn("GlobalSearchView was instantiated");
   MenuContainer.call(this);
   this._startSearch = this._startSearch.bind(this);
   this._onFetchSourceFinished = this._onFetchSourceFinished.bind(this);
+  this._onFetchSourceTimeout = this._onFetchSourceTimeout.bind(this);
   this._onFetchSourcesFinished = this._onFetchSourcesFinished.bind(this);
   this._createItemView = this._createItemView.bind(this);
   this._onScroll = this._onScroll.bind(this);
   this._onHeaderClick = this._onHeaderClick.bind(this);
   this._onLineClick = this._onLineClick.bind(this);
   this._onMatchClick = this._onMatchClick.bind(this);
 }
 
@@ -1397,45 +1398,50 @@ create({ constructor: GlobalSearchView, 
    * @param string aQuery
    *        The string to search for.
    */
   _startSearch: function DVGS__startSearch(aQuery) {
     let locations = DebuggerView.Sources.values;
     this._sourcesCount = locations.length;
     this._searchedToken = aQuery;
 
-    this._fetchSources(
-      this._onFetchSourceFinished,
-      this._onFetchSourcesFinished, locations);
+    this._fetchSources(locations, {
+      onFetch: this._onFetchSourceFinished,
+      onTimeout: this._onFetchSourceTimeout,
+      onFinished: this._onFetchSourcesFinished
+    });
   },
 
   /**
    * Starts fetching all the sources, silently.
    *
-   * @param function aFetchCallback
-   *        Called after each source is fetched.
-   * @param function aFetchedCallback
-   *        Called if all the sources were already fetched.
    * @param array aLocations
    *        The locations for the sources to fetch.
+   * @param object aCallbacks
+   *        An object containing the callback functions to invoke:
+   *          - onFetch: called after each source is fetched
+   *          - onTimeout: called when a source's text takes too long to fetch
+   *          - onFinished: called if all the sources were already fetched
    */
-  _fetchSources: function DVGS__fetchSources(aFetchCallback, aFetchedCallback, aLocations) {
+  _fetchSources:
+  function DVGS__fetchSources(aLocations, { onFetch, onTimeout, onFinished }) {
     // If all the sources were already fetched, then don't do anything.
     if (this._cache.size == aLocations.length) {
-      aFetchedCallback();
+      onFinished();
       return;
     }
 
     // Fetch each new source.
     for (let location of aLocations) {
       if (this._cache.has(location)) {
         continue;
       }
       let sourceItem = DebuggerView.Sources.getItemByValue(location);
-      DebuggerController.SourceScripts.getText(sourceItem.attachment, aFetchCallback);
+      let sourceObject = sourceItem.attachment;
+      DebuggerController.SourceScripts.getText(sourceObject, onFetch, onTimeout);
     }
   },
 
   /**
    * Called when a source has been fetched.
    *
    * @param string aLocation
    *        The location of the source.
@@ -1448,19 +1454,33 @@ create({ constructor: GlobalSearchView, 
 
     // Check if all sources were fetched and stored in the cache.
     if (this._cache.size == this._sourcesCount) {
       this._onFetchSourcesFinished();
     }
   },
 
   /**
+   * Called when a source's text takes too long to fetch.
+   */
+  _onFetchSourceTimeout: function DVGS__onFetchSourceTimeout() {
+    // Remove the source from the load queue.
+    this._sourcesCount--;
+
+    // Check if the remaining sources were fetched and stored in the cache.
+    if (this._cache.size == this._sourcesCount) {
+      this._onFetchSourcesFinished();
+    }
+  },
+
+  /**
    * Called when all the sources have been fetched.
    */
   _onFetchSourcesFinished: function DVGS__onFetchSourcesFinished() {
+    // At least one source needs to be present to perform a global search.
     if (!this._sourcesCount) {
       return;
     }
     // All sources are fetched and stored in the cache, we can start searching.
     this._performGlobalSearch();
     this._sourcesCount = 0;
   },
 
--- a/browser/devtools/debugger/debugger-toolbar.js
+++ b/browser/devtools/debugger/debugger-toolbar.js
@@ -6,17 +6,16 @@
 "use strict";
 
 /**
  * Functions handling the toolbar view: close button, expand/collapse button,
  * pause/resume and stepping buttons etc.
  */
 function ToolbarView() {
   dumpn("ToolbarView was instantiated");
-  this._onCloseClick = this._onCloseClick.bind(this);
   this._onTogglePanesPressed = this._onTogglePanesPressed.bind(this);
   this._onResumePressed = this._onResumePressed.bind(this);
   this._onStepOverPressed = this._onStepOverPressed.bind(this);
   this._onStepInPressed = this._onStepInPressed.bind(this);
   this._onStepOutPressed = this._onStepOutPressed.bind(this);
 }
 
 ToolbarView.prototype = {
@@ -104,23 +103,16 @@ ToolbarView.prototype = {
    * @param boolean aVisibleFlag
    *        Specifies the intended visibility.
    */
   toggleSourcesContainer: function DVT_toggleSourcesContainer(aVisibleFlag) {
     this._sources.setAttribute("hidden", !aVisibleFlag);
   },
 
   /**
-   * Listener handling the close button click event.
-   */
-  _onCloseClick: function DVT__onCloseClick() {
-    DebuggerController._shutdownDebugger();
-  },
-
-  /**
    * Listener handling the toggle button click event.
    */
   _onTogglePanesPressed: function DVT__onTogglePanesPressed() {
     DebuggerView.togglePanes({
       visible: DebuggerView.panesHidden,
       animated: true
     });
   },
--- a/browser/devtools/debugger/debugger-view.js
+++ b/browser/devtools/debugger/debugger-view.js
@@ -613,28 +613,29 @@ MenuContainer.prototype = {
    * @param string aValue
    *        The actual internal value of the item.
    * @param object aOptions [optional]
    *        Additional options or flags supported by this operation:
    *          - forced: true to force the item to be immediately appended
    *          - unsorted: true if the items should not always remain sorted
    *          - relaxed: true if this container should allow dupes & degenerates
    *          - description: an optional description of the item
+   *          - tooltip: an optional tooltip for the item
    *          - attachment: some attached primitive/object
    * @return MenuItem
    *         The item associated with the displayed element if a forced push,
    *         undefined if the item was staged for a later commit.
    */
   push: function DVMC_push(aLabel, aValue, aOptions = {}) {
     let item = new MenuItem(
       aLabel, aValue, aOptions.description, aOptions.attachment);
 
     // Batch the item to be added later.
     if (!aOptions.forced) {
-      this._stagedItems.push(item);
+      this._stagedItems.push({ item: item, options: aOptions });
     }
     // Immediately insert the item at the specified index.
     else if (aOptions.forced && aOptions.forced.atIndex !== undefined) {
       return this._insertItemAt(aOptions.forced.atIndex, item, aOptions);
     }
     // Find the target position in this container and insert the item there.
     else if (!aOptions.unsorted) {
       return this._insertItemAt(this._findExpectedIndex(aLabel), item, aOptions);
@@ -652,21 +653,22 @@ MenuContainer.prototype = {
    *        Additional options or flags supported by this operation:
    *          - unsorted: true if the items should not be sorted beforehand
    */
   commit: function DVMC_commit(aOptions = {}) {
     let stagedItems = this._stagedItems;
 
     // By default, sort the items before adding them to this container.
     if (!aOptions.unsorted) {
-      stagedItems.sort(function(a, b) a.label.toLowerCase() > b.label.toLowerCase());
+      stagedItems.sort(function(a, b) a.item.label.toLowerCase() >
+                                      b.item.label.toLowerCase());
     }
     // Append the prepared items to this container.
-    for (let item of stagedItems) {
-      this._appendItem(item, aOptions);
+    for (let { item, options } of stagedItems) {
+      this._appendItem(item, options);
     }
     // Recreate the temporary items list for ulterior pushes.
     this._stagedItems = [];
   },
 
   /**
    * Updates this container to reflect the information provided by the
    * currently selected item.
@@ -745,31 +747,31 @@ MenuContainer.prototype = {
    *
    * @param string aLabel
    *        The item's label.
    * @return boolean
    *         True if the label is known, false otherwise.
    */
   containsLabel: function DVMC_containsLabel(aLabel) {
     return this._itemsByLabel.has(aLabel) ||
-           this._stagedItems.some(function(o) o.label == aLabel);
+           this._stagedItems.some(function({item}) item.label == aLabel);
   },
 
   /**
    * Checks whether an item with the specified value is among the elements
    * shown in this container.
    *
    * @param string aValue
    *        The item's value.
    * @return boolean
    *         True if the value is known, false otherwise.
    */
   containsValue: function DVMC_containsValue(aValue) {
     return this._itemsByValue.has(aValue) ||
-           this._stagedItems.some(function(o) o.value == aValue);
+           this._stagedItems.some(function({item}) item.value == aValue);
   },
 
   /**
    * Checks whether an item with the specified trimmed value is among the
    * elements shown in this container.
    *
    * @param string aValue
    *        The item's value.
@@ -783,17 +785,17 @@ MenuContainer.prototype = {
                                      aTrim = SourceUtils.trimUrlQuery) {
     let trimmedValue = aTrim(aValue);
 
     for (let [value] of this._itemsByValue) {
       if (aTrim(value) == trimmedValue) {
         return true;
       }
     }
-    return this._stagedItems.some(function(o) aTrim(o.value) == trimmedValue);
+    return this._stagedItems.some(function({item}) aTrim(item.value) == trimmedValue);
   },
 
   /**
    * Gets the preferred selected value to be displayed in this container.
    * @return string
    */
   get preferredValue() this._preferredValue,
 
@@ -1041,18 +1043,25 @@ MenuContainer.prototype = {
    * @return MenuItem
    *         The item associated with the displayed element, null if rejected.
    */
   _appendItem: function DVMC__appendItem(aItem, aOptions = {}) {
     if (!aOptions.relaxed && !this.isEligible(aItem)) {
       return null;
     }
 
-    return this._entangleItem(aItem, this._container.appendItem(
+    this._entangleItem(aItem, this._container.appendItem(
       aItem.label, aItem.value, "", aOptions.attachment));
+
+    // Handle any additional options after entangling the item.
+    if (aOptions.tooltip) {
+      aItem._target.setAttribute("tooltiptext", aOptions.tooltip);
+    }
+
+    return aItem;
   },
 
   /**
    * Immediately inserts an item in this container at the specified index.
    *
    * @param number aIndex
    *        The position in the container intended for this item.
    * @param MenuItem aItem
@@ -1063,18 +1072,25 @@ MenuContainer.prototype = {
    * @return MenuItem
    *         The item associated with the displayed element, null if rejected.
    */
   _insertItemAt: function DVMC__insertItemAt(aIndex, aItem, aOptions) {
     if (!aOptions.relaxed && !this.isEligible(aItem)) {
       return null;
     }
 
-    return this._entangleItem(aItem, this._container.insertItemAt(
+    this._entangleItem(aItem, this._container.insertItemAt(
       aIndex, aItem.label, aItem.value, "", aOptions.attachment));
+
+    // Handle any additional options after entangling the item.
+    if (aOptions.tooltip) {
+      aItem._target.setAttribute("tooltiptext", aOptions.tooltip);
+    }
+
+    return aItem;
   },
 
   /**
    * Entangles an item (model) with a displayed node element (view).
    *
    * @param MenuItem aItem
    *        The item describing the element.
    * @param nsIDOMNode aElement
--- a/browser/devtools/framework/Sidebar.jsm
+++ b/browser/devtools/framework/Sidebar.jsm
@@ -110,16 +110,26 @@ ToolSidebar.prototype = {
         currentID = id;
         break;
       }
     }
     return currentID;
   },
 
   /**
+   * Returns the requested tab based on the id.
+   *
+   * @param String id
+   *        unique id of the requested tab.
+   */
+  getTab: function ToolSidebar_getTab(id) {
+    return this._tabbox.tabpanels.querySelector("#sidebar-panel-" + id);
+  },
+
+  /**
    * Event handler.
    */
   handleEvent: function ToolSidebar_eventHandler(event) {
     if (event.type == "select") {
       let previousTool = this._currentTool;
       this._currentTool = this.getCurrentTabID();
       if (previousTool) {
         this.emit(previousTool + "-unselected");
@@ -171,17 +181,17 @@ ToolSidebar.prototype = {
    * Clean-up.
    */
   destroy: function ToolSidebar_destroy() {
     if (this._destroyed) {
       return Promise.resolve(null);
     }
     this._destroyed = true;
 
-    this._tabbox.removeEventListener("select", this, true);
+    this._tabbox.tabpanels.removeEventListener("select", this, true);
 
     while (this._tabbox.tabpanels.hasChildNodes()) {
       this._tabbox.tabpanels.removeChild(this._tabbox.tabpanels.firstChild);
     }
 
     while (this._tabbox.tabs.hasChildNodes()) {
       this._tabbox.tabs.removeChild(this._tabbox.tabs.firstChild);
     }
--- a/browser/devtools/framework/Target.jsm
+++ b/browser/devtools/framework/Target.jsm
@@ -290,19 +290,20 @@ TabWebProgressListener.prototype = {
       this.target.emit("will-navigate", request);
     }
   },
 
   onProgressChange: function() {},
   onSecurityChange: function() {},
   onStatusChange: function() {},
 
-  onLocationChange: function TwPL_onLocationChange(webProgress) {
-    let window = webProgress.DOMWindow;
-    if (this.target) {
+  onLocationChange: function TwPL_onLocationChange(webProgress, request, URI, flags) {
+    if (this.target &&
+        !(flags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT)) {
+      let window = webProgress.DOMWindow;
       this.target.emit("navigate", window);
     }
   },
 };
 
 
 /**
  * A WindowTarget represents a page living in a xul window or panel. Generally
--- a/browser/devtools/framework/ToolDefinitions.jsm
+++ b/browser/devtools/framework/ToolDefinitions.jsm
@@ -64,16 +64,18 @@ let webConsoleDefinition = {
   id: "webconsole",
   key: l10n("cmd.commandkey", webConsoleStrings),
   accesskey: l10n("webConsoleCmd.accesskey", webConsoleStrings),
   modifiers: Services.appinfo.OS == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 0,
   icon: "chrome://browser/skin/devtools/webconsole-tool-icon.png",
   url: "chrome://browser/content/devtools/webconsole.xul",
   label: l10n("ToolboxWebconsole.label", webConsoleStrings),
+  tooltip: l10n("ToolboxWebconsole.tooltip", webConsoleStrings),
+
   isTargetSupported: function(target) {
     return true;
   },
   build: function(iframeWindow, toolbox) {
     let panel = new WebConsolePanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
@@ -83,16 +85,17 @@ let debuggerDefinition = {
   key: l10n("open.commandkey", debuggerStrings),
   accesskey: l10n("debuggerMenu.accesskey", debuggerStrings),
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   ordinal: 1,
   killswitch: "devtools.debugger.enabled",
   icon: "chrome://browser/skin/devtools/tools-icons-small.png",
   url: "chrome://browser/content/debugger.xul",
   label: l10n("ToolboxDebugger.label", debuggerStrings),
+  tooltip: l10n("ToolboxDebugger.tooltip", debuggerStrings),
 
   isTargetSupported: function(target) {
     return true;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new DebuggerPanel(iframeWindow, toolbox);
     return panel.open();
@@ -103,16 +106,17 @@ let inspectorDefinition = {
   id: "inspector",
   accesskey: l10n("inspector.accesskey", inspectorStrings),
   key: l10n("inspector.commandkey", inspectorStrings),
   ordinal: 2,
   modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
   icon: "chrome://browser/skin/devtools/tools-icons-small.png",
   url: "chrome://browser/content/devtools/inspector/inspector.xul",
   label: l10n("inspector.label", inspectorStrings),
+  tooltip: l10n("inspector.tooltip", inspectorStrings),
 
   isTargetSupported: function(target) {
     return !target.isRemote;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new InspectorPanel(iframeWindow, toolbox);
     return panel.open();
@@ -122,16 +126,17 @@ let inspectorDefinition = {
 let styleEditorDefinition = {
   id: "styleeditor",
   key: l10n("open.commandkey", styleEditorStrings),
   ordinal: 3,
   accesskey: l10n("open.accesskey", styleEditorStrings),
   modifiers: "shift",
   label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
   url: "chrome://browser/content/styleeditor.xul",
+  tooltip: l10n("ToolboxStyleEditor.tooltip", styleEditorStrings),
 
   isTargetSupported: function(target) {
     return !target.isRemote && !target.isChrome;
   },
 
   build: function(iframeWindow, toolbox) {
     let panel = new StyleEditorPanel(iframeWindow, toolbox);
     return panel.open();
@@ -139,16 +144,17 @@ let styleEditorDefinition = {
 };
 
 let profilerDefinition = {
   id: "jsprofiler",
   killswitch: "devtools.profiler.enabled",
   icon: "chrome://browser/skin/devtools/tools-icons-small.png",
   url: "chrome://browser/content/profiler.xul",
   label: l10n("profiler.label", profilerStrings),
+  tooltip: l10n("profiler.tooltip", profilerStrings),
 
   isTargetSupported: function (target) {
     if (target.isRemote || target.isChrome) {
       return false;
     }
 
     return true;
   },
--- a/browser/devtools/framework/Toolbox.jsm
+++ b/browser/devtools/framework/Toolbox.jsm
@@ -11,16 +11,27 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/commonjs/promise/core.js");
 Cu.import("resource:///modules/devtools/EventEmitter.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Hosts",
                                   "resource:///modules/devtools/ToolboxHosts.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CommandUtils",
                                   "resource:///modules/devtools/DeveloperToolbar.jsm");
+XPCOMUtils.defineLazyGetter(this, "toolboxStrings", function() {
+  let bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
+  let l10n = function(name) {
+    try {
+      return bundle.GetStringFromName(name);
+    } catch (ex) {
+      Services.console.logStringMessage("Error reading '" + name + "'");
+    }
+  };
+  return l10n;
+});
 
 // DO NOT put Require.jsm or gcli.jsm into lazy getters as this breaks the
 // requisition import a few lines down.
 Cu.import("resource:///modules/devtools/gcli.jsm");
 Cu.import("resource://gre/modules/devtools/Require.jsm");
 
 let Requisition = require('gcli/cli').Requisition;
 let CommandOutputManager = require('gcli/canon').CommandOutputManager;
@@ -280,16 +291,18 @@ Toolbox.prototype = {
       if (position == this.hostType ||
          (!sideEnabled && position == this.HostType.SIDE)) {
         continue;
       }
 
       let button = this.doc.createElement("toolbarbutton");
       button.id = "toolbox-dock-" + position;
       button.className = "toolbox-dock-button";
+      button.setAttribute("tooltiptext", toolboxStrings("toolboxDockButtons." +
+                                                        position + ".tooltip"));
       button.addEventListener("command", function(position) {
         this.switchHost(position);
       }.bind(this, position));
 
       dockBox.appendChild(button);
     }
   },
 
@@ -343,16 +356,17 @@ Toolbox.prototype = {
 
     let id = toolDefinition.id;
 
     let radio = this.doc.createElement("radio");
     radio.setAttribute("label", toolDefinition.label);
     radio.className = "toolbox-tab devtools-tab";
     radio.id = "toolbox-tab-" + id;
     radio.setAttribute("toolid", id);
+    radio.setAttribute("tooltiptext", toolDefinition.tooltip);
 
     let ordinal = (typeof toolDefinition.ordinal == "number") ?
                   toolDefinition.ordinal : MAX_ORDINAL;
     radio.setAttribute("ordinal", ordinal);
 
     radio.addEventListener("command", function(id) {
       this.selectTool(id);
     }.bind(this, id));
--- a/browser/devtools/framework/connect/connect.css
+++ b/browser/devtools/framework/connect/connect.css
@@ -1,19 +1,30 @@
 html {
-  background: url("chrome://browser/skin/newtab/noise.png");
+  background-color: #111;
+  background-image: url("chrome://browser/skin/newtab/noise.png");
 }
 
 body {
-  font-family: Arial;
+  font-family: Arial, sans-serif;
+  color: white;
+  max-width: 600px;
+  margin: 30px auto 0;
+  box-shadow: 0 2px 3px black;
+  background-color: #3C3E40;
+}
+
+h1 {
+  margin: 0;
   padding: 20px;
-  border-radius: 3px;
-  max-width: 600px;
-  min-height: 400px;
-  margin: 10px auto 0;
+  background-color: rgba(0,0,0,0.12);
+  background-image: radial-gradient(ellipse farthest-corner at center top , rgb(159, 223, 255), rgba(101, 203, 255, 0.3)), radial-gradient(ellipse farthest-side at center top , rgba(101, 203, 255, 0.4), rgba(101, 203, 255, 0));
+  background-size: 100% 2px, 100% 5px;
+  background-repeat: no-repeat;
+  border-bottom: 1px solid rgba(0,0,0,0.1);
 }
 
 label {
   display: block;
   margin: 10px;
   font-size: 0;
 }
 
@@ -24,28 +35,31 @@ label > span {
   text-align: right;
   margin-right: 10px;
 }
 
 #submit {
   margin-left: 160px;
 }
 
-
-#actors, #connection-form {
-  margin: 20px;
+input:invalid {
+  box-shadow: 0 0 2px 2px #F06;
 }
 
-input {
-  border: 1px solid grey;
+section {
+  min-height: 160px;
+  margin: 60px 20px;
+  display: none; /* By default, hidden */
 }
 
-#connection-form,
-#connecting,
-#actors-list {
+.error-message {
+  color: red;
+}
+
+.error-message:not(.active) {
   display: none;
 }
 
 body:not(.actors-mode):not(.connecting) > #connection-form {
   display: block;
 }
 
 body.actors-mode > #actors-list {
@@ -55,33 +69,40 @@ body.actors-mode > #actors-list {
 body.connecting > #connecting {
   display: block;
 }
 
 #connecting {
   text-align: center;
 }
 
-#throbber {
-  height: 7px; width: 7px;
-  border-radius: 50%;
-  background: black;
-  display: inline-block;
-  animation-duration: 0.6s;
-  animation-name: anim;
-  animation-direction: alternate;
-  animation-iteration-count: infinite;
-  animation-timing-function: linear;
+#connecting > p > img {
+  vertical-align: top;
 }
-@keyframes anim {to {
-    transform: scale(0.5) rotate(0.1deg);
-}}
 
 #actors {
   padding-left: 0;
   font-size: 0.9rem;
 }
 
 #actors > a {
   display: block;
   margin: 5px;
   padding: 5px;
+  color: white;
 }
+
+.remote-process {
+  font-style: italic;
+  opacity: 0.8;
+}
+
+footer {
+  padding: 10px;
+  background-color: rgba(0,0,0,0.12);
+  border-top: 1px solid rgba(0,0,0,0.1);
+  font-size: small;
+}
+
+footer > a,
+footer > a:visited {
+  color: white;
+}
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -5,96 +5,163 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
 Cu.import("resource:///modules/devtools/Target.jsm");
 Cu.import("resource:///modules/devtools/Toolbox.jsm");
 Cu.import("resource:///modules/devtools/gDevTools.jsm");
+Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/devtools/dbg-client.jsm");
 
 let gClient;
+let gConnectionTimeout;
 
-function submit() {
-  document.body.classList.add("connecting");
+XPCOMUtils.defineLazyGetter(window, 'l10n', function () {
+  return Services.strings.createBundle('chrome://browser/locale/devtools/connection-screen.properties');
+});
 
-  let host = document.getElementById("host").value;
-  let port = document.getElementById("port").value;
-  if (!host) {
-    host = Services.prefs.getCharPref("devtools.debugger.remote-host");
-  } else {
-    Services.prefs.setCharPref("devtools.debugger.remote-host", host);
+/**
+ * Once DOM is ready, we prefil the host/port inputs with
+ * pref-stored values.
+ */
+window.addEventListener("DOMContentLoaded", function onDOMReady() {
+  window.removeEventListener("DOMContentLoaded", onDOMReady, true);
+  let host = Services.prefs.getCharPref("devtools.debugger.remote-host");
+  let port = Services.prefs.getIntPref("devtools.debugger.remote-port");
+
+  if (host) {
+    document.getElementById("host").value = host;
   }
-  if (!port) {
-    port = Services.prefs.getIntPref("devtools.debugger.remote-port");
-  } else {
-    Services.prefs.setIntPref("devtools.debugger.remote-port", port);
+
+  if (port) {
+    document.getElementById("port").value = port;
   }
 
-  let transport = debuggerSocketConnect(host, port);
-  let client = gClient = new DebuggerClient(transport);
+}, true);
 
-  client.connect(function(aType, aTraits) {
-    client.listTabs(function(aResponse) {
-      document.body.classList.remove("connecting");
-      document.body.classList.add("actors-mode");
+/**
+ * Called when the "connect" button is clicked.
+ */
+function submit() {
+  // Show the "connecting" screen
+  document.body.classList.add("connecting");
 
-      let parent = document.getElementById("actors");
-      let focusSet = false;
+  // Save the host/port values
+  let host = document.getElementById("host").value;
+  Services.prefs.setCharPref("devtools.debugger.remote-host", host);
+
+  let port = document.getElementById("port").value;
+  Services.prefs.setIntPref("devtools.debugger.remote-port", port);
 
-      // Add Global Process debugging...
-      let globals = JSON.parse(JSON.stringify(aResponse));
-      delete globals.tabs;
-      delete globals.selected;
-      // ...only if there are appropriate actors (a 'from' property will always
-      // be there).
-      if (Object.keys(globals).length > 1) {
-        let a = document.createElement("a");
-        a.onclick = function() {
-          connect(globals, true);
-        }
+  // Initiate the connection
+  let transport = debuggerSocketConnect(host, port);
+  gClient = new DebuggerClient(transport);
+  let delay = Services.prefs.getIntPref("devtools.debugger.remote-timeout");
+  gConnectionTimeout = setTimeout(handleConnectionTimeout, delay);
+  gClient.connect(onConnectionReady);
+}
 
-        a.title = a.textContent = "Remote process";
-        a.href = "#";
+/**
+ * Connection is ready. List actors and build buttons.
+ */
+function onConnectionReady(aType, aTraits) {
+  clearTimeout(gConnectionTimeout);
+  gClient.listTabs(function(aResponse) {
+    document.body.classList.remove("connecting");
+    document.body.classList.add("actors-mode");
 
-        parent.appendChild(a);
-      }
+    let parent = document.getElementById("actors");
 
-      // Add one entry for each open tab.
-      if (aResponse.tabs.length > 0) {
-        let header = document.createElement("div");
-        header.innerHTML = "Tabs:";
-        parent.appendChild(header);
-      }
-      for (let i = 0; i < aResponse.tabs.length; i++) {
-        let tab = aResponse.tabs[i];
+    // Add Global Process debugging...
+    let globals = JSON.parse(JSON.stringify(aResponse));
+    delete globals.tabs;
+    delete globals.selected;
+    // ...only if there are appropriate actors (a 'from' property will always
+    // be there).
 
-        let a = document.createElement("a");
-        a.onclick = function() {
-          connect(tab);
-        }
+    // Add one entry for each open tab.
+    for (let i = 0; i < aResponse.tabs.length; i++) {
+      buildLink(aResponse.tabs[i], parent, i == aResponse.selected);
+    }
+
+    // Build the Remote Process button
+    if (Object.keys(globals).length > 1) {
+      let a = document.createElement("a");
+      a.onclick = function() {
+        openToolbox(globals, true);
 
-        a.title = a.textContent = tab.title;
-        a.href = "#";
-
-        if (i == aResponse.selected) {
-          a.title += " [*]";
-          a.textContent = a.title;
-        }
+      }
+      a.title = a.textContent = window.l10n.GetStringFromName("remoteProcess");
+      a.className = "remote-process";
+      a.href = "#";
+      parent.appendChild(a);
+    }
+    // Move the selected tab on top
+    let selectedLink = parent.querySelector("a.selected");
+    if (selectedLink) {
+      parent.insertBefore(selectedLink, parent.firstChild);
+    }
 
-        parent.appendChild(a);
+    // Ensure the first link is focused
+    let firstLink = parent.querySelector("a:first-of-type");
+    if (firstLink) {
+      firstLink.focus();
+    }
 
-        if (!focusSet) {
-          a.focus();
-          focusSet = true;
-        }
-      }
-    });
   });
 }
 
-function connect(form, chrome=false) {
+/**
+ * Build one button for an actor.
+ */
+function buildLink(tab, parent, selected) {
+  let a = document.createElement("a");
+  a.onclick = function() {
+    openToolbox(tab);
+  }
+
+  a.textContent = tab.title;
+  a.title = tab.url;
+  if (!a.textContent) {
+    a.textContent = tab.url;
+  }
+  a.href = "#";
+
+  if (selected) {
+    a.classList.add("selected");
+  }
+
+  parent.appendChild(a);
+}
+
+/**
+ * An error occured. Let's show it and return to the first screen.
+ */
+function showError(type) {
+  document.body.className = "error";
+  let activeError = document.querySelector(".error-message.active");
+  if (activeError) {
+    activeError.classList.remove("active");
+  }
+  activeError = document.querySelector(".error-" + type);
+  if (activeError) {
+    activeError.classList.add("active");
+  }
+}
+
+/**
+ * Connection timeout.
+ */
+function handleConnectionTimeout() {
+  showError("timeout");
+}
+
+/**
+ * The user clicked on one of the buttons.
+ * Opens the toolbox.
+ */
+function openToolbox(form, chrome=false) {
   let target = TargetFactory.forRemote(form, gClient, chrome);
   gDevTools.showToolbox(target, "webconsole", Toolbox.HostType.WINDOW);
-  window.close();
 }
--- a/browser/devtools/framework/connect/connect.xhtml
+++ b/browser/devtools/framework/connect/connect.xhtml
@@ -7,39 +7,42 @@
 <!ENTITY % connectionDTD SYSTEM "chrome://browser/locale/devtools/connection-screen.dtd" >
  %connectionDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <head>
     <title>&title;</title>
+    <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
     <link rel="stylesheet" href="chrome://browser/content/devtools/connect.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="connect.js"></script>
   </head>
   <body>
-    <p>
-    </p>
+    <h1>&header;</h1>
     <section id="connection-form">
-      <form onsubmit="window.submit()" action="#">
+      <form validate="validate" onsubmit="window.submit()" action="#">
         <label>
           <span>&host;</span>
-          <input id="host" type="text" placeholder="localhost"></input>
+          <input required="required" class="devtools-textinput" id="host" type="text"></input>
         </label>
         <label>
           <span>&port;</span>
-          <input id="port" type="number" placeholder="6000"></input>
+          <input required="required" class="devtools-textinput" id="port" type="number" pattern="\d+"></input>
         </label>
         <label>
-          <input id="submit" type="submit" value="&connect;"></input>
+          <input class="devtools-toolbarbutton" id="submit" type="submit" value="&connect;"></input>
         </label>
       </form>
+      <p class="error-message error-timeout">&errorTimeout;</p>
+      <p class="error-message error-refused">&errorRefused;</p>
+      <p class="error-message error-unexpected">&errorUnexpected;</p>
     </section>
     <section id="actors-list">
       <p>&availability;</p>
       <ul id="actors"></ul>
     </section>
     <section id="connecting">
-      <p>&connecting;</p>
-      <div id="throbber"></div>
+      <p><img src="chrome://browser/skin/tabbrowser/loading.png"></img> &connecting;</p>
     </section>
+    <footer>&help;</footer>
   </body>
 </html>
--- a/browser/devtools/framework/toolbox.xul
+++ b/browser/devtools/framework/toolbox.xul
@@ -1,35 +1,43 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
+<!DOCTYPE window [
+<!ENTITY % toolboxDTD SYSTEM "chrome://browser/locale/devtools/toolbox.dtd" >
+ %toolboxDTD;
+]>
 <?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/shared/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/framework/toolbox.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/toolbox.css" type="text/css"?>
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 <?xul-overlay href="chrome://browser/content/source-editor-overlay.xul"?>
 
 <window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
   <notificationbox id="toolbox-notificationbox" flex="1">
     <toolbar class="devtools-tabbar">
 #ifdef XP_MACOSX
       <hbox id="toolbox-controls">
-        <toolbarbutton id="toolbox-close" class="devtools-closebutton"></toolbarbutton>
+        <toolbarbutton id="toolbox-close"
+                       class="devtools-closebutton"
+                       tooltiptext="&toolboxCloseButton.tooltip;"/>
         <hbox id="toolbox-dock-buttons"/>
       </hbox>
 #endif
       <radiogroup id="toolbox-tabs" orient="horizontal">
       </radiogroup>
       <hbox id="toolbox-buttons" flex="1" pack="end"/>
 #ifndef XP_MACOSX
       <hbox id="toolbox-controls">
         <hbox id="toolbox-dock-buttons"/>
-        <toolbarbutton id="toolbox-close" class="devtools-closebutton"></toolbarbutton>
+        <toolbarbutton id="toolbox-close"
+                       class="devtools-closebutton"
+                       tooltiptext="&toolboxCloseButton.tooltip;"/>
       </hbox>
 #endif
     </toolbar>
     <deck id="toolbox-deck" flex="1">
     </deck>
   </notificationbox>
 </window>
--- a/browser/devtools/inspector/Breadcrumbs.jsm
+++ b/browser/devtools/inspector/Breadcrumbs.jsm
@@ -78,17 +78,16 @@ HTMLBreadcrumbs.prototype = {
     }.bind(this);
 
     this.container.addEventListener("underflow", this.onscrollboxreflow, false);
     this.container.addEventListener("overflow", this.onscrollboxreflow, false);
 
     this.update = this.update.bind(this);
     this.updateSelectors = this.updateSelectors.bind(this);
     this.selection.on("new-node", this.update);
-    this.selection.on("detached", this.update);
     this.selection.on("pseudoclass", this.updateSelectors);
     this.selection.on("attribute-changed", this.updateSelectors);
     this.update();
   },
 
   /**
    * Build a string that represents the node: tagName#id.class1.class2.
    *
@@ -291,17 +290,16 @@ HTMLBreadcrumbs.prototype = {
   {
     this.nodeHierarchy.forEach(function(crumb) {
       if (LayoutHelpers.isNodeConnected(crumb.node)) {
         DOMUtils.clearPseudoClassLocks(crumb.node);
       }
     });
 
     this.selection.off("new-node", this.update);
-    this.selection.off("detached", this.update);
     this.selection.off("pseudoclass", this.updateSelectors);
     this.selection.off("attribute-changed", this.updateSelectors);
 
     this.container.removeEventListener("underflow", this.onscrollboxreflow, false);
     this.container.removeEventListener("overflow", this.onscrollboxreflow, false);
     this.onscrollboxreflow = null;
 
     this.empty();
--- a/browser/devtools/inspector/Highlighter.jsm
+++ b/browser/devtools/inspector/Highlighter.jsm
@@ -128,17 +128,16 @@ Highlighter.prototype = {
 
     this.transitionDisabler = null;
     this.pageEventsMuter = null;
 
     this.unlock();
 
     this.selection.on("new-node", this.highlight);
     this.selection.on("new-node", this.updateInfobar);
-    this.selection.on("detached", this.highlight);
     this.selection.on("pseudoclass", this.updateInfobar);
     this.selection.on("attribute-changed", this.updateInfobar);
 
     this.onToolSelected = function(event, id) {
       if (id != "inspector") {
         this.chromeWin.clearTimeout(this.pageEventsMuter);
         this.detachMouseListeners();
         this.hide();
@@ -163,17 +162,16 @@ Highlighter.prototype = {
     this.inspectButton.removeEventListener("command", this.unlock);
     this.inspectButton = null;
 
     this.toolbox.off("select", this.onToolSelected);
     this.toolbox = null;
 
     this.selection.off("new-node", this.highlight);
     this.selection.off("new-node", this.updateInfobar);
-    this.selection.off("detached", this.highlight);
     this.selection.off("pseudoclass", this.updateInfobar);
     this.selection.off("attribute-changed", this.updateInfobar);
 
     this.detachMouseListeners();
     this.detachPageListeners();
 
     this.chromeWin.clearTimeout(this.transitionDisabler);
     this.chromeWin.clearTimeout(this.pageEventsMuter);
--- a/browser/devtools/inspector/InspectorPanel.jsm
+++ b/browser/devtools/inspector/InspectorPanel.jsm
@@ -63,16 +63,18 @@ InspectorPanel.prototype = {
     this._resetNodeMenu = this._resetNodeMenu.bind(this);
     this.nodemenu.addEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.addEventListener("popuphiding", this._resetNodeMenu, true);
 
     // Create an empty selection
     this._selection = new Selection();
     this.onNewSelection = this.onNewSelection.bind(this);
     this.selection.on("new-node", this.onNewSelection);
+    this.onDetached = this.onDetached.bind(this);
+    this.selection.on("detached", this.onDetached);
 
     this.breadcrumbs = new HTMLBreadcrumbs(this);
 
     if (this.tabTarget) {
       this.browser = this.target.tab.linkedBrowser;
       this.scheduleLayoutChange = this.scheduleLayoutChange.bind(this);
       this.browser.addEventListener("resize", this.scheduleLayoutChange, true);
 
@@ -165,29 +167,34 @@ InspectorPanel.prototype = {
 
     let defaultTab = Services.prefs.getCharPref("devtools.inspector.activeSidebar");
 
     this._setDefaultSidebar = function(event, toolId) {
       Services.prefs.setCharPref("devtools.inspector.activeSidebar", toolId);
     }.bind(this);
 
     this.sidebar.on("select", this._setDefaultSidebar);
+    this.toggleHighlighter = this.toggleHighlighter.bind(this);
 
     this.sidebar.addTab("ruleview",
                         "chrome://browser/content/devtools/cssruleview.xul",
                         "ruleview" == defaultTab);
 
     this.sidebar.addTab("computedview",
                         "chrome://browser/content/devtools/csshtmltree.xul",
                         "computedview" == defaultTab);
 
     this.sidebar.addTab("layoutview",
                         "chrome://browser/content/devtools/layoutview/view.xhtml",
                         "layoutview" == defaultTab);
 
+    let ruleViewTab = this.sidebar.getTab("ruleview");
+    ruleViewTab.addEventListener("mouseover", this.toggleHighlighter, false);
+    ruleViewTab.addEventListener("mouseout", this.toggleHighlighter, false);
+
     this.sidebar.show();
   },
 
   /**
    * Reset the inspector on navigate away.
    */
   onNavigatedAway: function InspectorPanel_onNavigatedAway(event, newWindow) {
     this.selection.setNode(null);
@@ -271,16 +278,25 @@ InspectorPanel.prototype = {
   /**
    * When a new node is selected.
    */
   onNewSelection: function InspectorPanel_onNewSelection() {
     this.cancelLayoutChange();
   },
 
   /**
+   * When a node is deleted, select its parent node.
+   */
+  onDetached: function InspectorPanel_onDetached(event, parentNode) {
+    this.cancelLayoutChange();
+    this.breadcrumbs.cutAfter(this.breadcrumbs.indexOf(parentNode));
+    this.selection.setNode(parentNode, "detached");
+  },
+
+  /**
    * Destroy the inspector.
    */
   destroy: function InspectorPanel__destroy() {
     if (this._destroyed) {
       return Promise.resolve(null);
     }
     this._destroyed = true;
 
@@ -305,16 +321,17 @@ InspectorPanel.prototype = {
     this.sidebar.off("select", this._setDefaultSidebar);
     this.sidebar.destroy();
     this.sidebar = null;
 
     this.nodemenu.removeEventListener("popupshowing", this._setupNodeMenu, true);
     this.nodemenu.removeEventListener("popuphiding", this._resetNodeMenu, true);
     this.breadcrumbs.destroy();
     this.selection.off("new-node", this.onNewSelection);
+    this.selection.off("detached", this.onDetached);
     this._destroyMarkup();
     this._selection.destroy();
     this._selection = null;
     this.panelWin.inspector = null;
     this.target = null;
     this.panelDoc = null;
     this.panelWin = null;
     this.breadcrumbs = null;
@@ -443,16 +460,29 @@ InspectorPanel.prototype = {
           node = node.parentNode;
         } while (hierarchical && node.parentNode)
       }
     }
     this.selection.emit("pseudoclass");
   },
 
   /**
+   * Toggle the highlighter when ruleview is hovered.
+   */
+  toggleHighlighter: function InspectorPanel_toggleHighlighter(event)
+  {
+    if (event.type == "mouseover") {
+      this.highlighter.hide();
+    }
+    else if (event.type == "mouseout") {
+      this.highlighter.show();
+    }
+  },
+
+  /**
    * Copy the innerHTML of the selected Node to the clipboard.
    */
   copyInnerHTML: function InspectorPanel_copyInnerHTML()
   {
     if (!this.selection.isNode()) {
       return;
     }
     let toCopy = this.selection.node.innerHTML;
--- a/browser/devtools/inspector/Selection.jsm
+++ b/browser/devtools/inspector/Selection.jsm
@@ -65,31 +65,33 @@ this.Selection = function Selection(node
 }
 
 Selection.prototype = {
   _node: null,
 
   _onMutations: function(mutations) {
     let attributeChange = false;
     let detached = false;
+    let parentNode = null;
     for (let m of mutations) {
       if (!attributeChange && m.type == "attributes") {
         attributeChange = true;
       }
       if (m.type == "childList") {
         if (!detached && !this.isConnected()) {
+          parentNode = m.target;
           detached = true;
         }
       }
     }
 
     if (attributeChange)
       this.emit("attribute-changed");
     if (detached)
-      this.emit("detached");
+      this.emit("detached", parentNode);
   },
 
   _attachEvents: function SN__attachEvents() {
     if (!this.window || !this.isNode() || !this.track) {
       return;
     }
 
     if (this.track.attributes) {
--- a/browser/devtools/inspector/test/Makefile.in
+++ b/browser/devtools/inspector/test/Makefile.in
@@ -30,14 +30,15 @@ include $(topsrcdir)/config/rules.mk
 		browser_inspector_bug_566084_location_changed.js \
 		$(filter disabled-temporarily--bug-816990, browser_inspector_sidebarstate.js) \
 		browser_inspector_pseudoclass_lock.js \
 		browser_inspector_cmd_inspect.js \
 		browser_inspector_cmd_inspect.html \
 		browser_inspector_highlighter_autohide.js \
 		browser_inspector_changes.js \
 		browser_inspector_bug_674871.js \
+		browser_inspector_bug_817558_delete_node.js \
 		head.js \
 		helpers.js \
 		$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
--- a/browser/devtools/inspector/test/browser_inspector_bug_566084_location_changed.js
+++ b/browser/devtools/inspector/test/browser_inspector_bug_566084_location_changed.js
@@ -99,17 +99,17 @@ function test() {
     isnot(content.location.href.indexOf("test2"), -1,
           "page navigated to the correct location");
 
     let para = content.document.querySelector("p");
     ok(para, "found the paragraph element, third time");
     is(para.textContent, "test2", "paragraph content is correct");
 
     let root = content.document.documentElement;
-    ok(inspector.selection.node, root, "Selection is the root of the new page.");
+    is(inspector.selection.node, root, "Selection is the root of the new page.");
 
     ok(alertActive1_called, "first notification box has been showed");
     ok(alertActive2_called, "second notification box has been showed");
     testEnd();
   }
 
 
   function testEnd() {
new file mode 100644
--- /dev/null
+++ b/browser/devtools/inspector/test/browser_inspector_bug_817558_delete_node.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test()
+{
+  waitForExplicitFinish();
+  //ignoreAllUncaughtExceptions();
+
+  let node, iframe, inspector;
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    waitForFocus(setupTest, content);
+  }, true);
+
+  content.location = "http://mochi.test:8888/browser/browser/devtools/inspector/test/browser_inspector_destroyselection.html";
+
+  function setupTest()
+  {
+    iframe = content.document.querySelector("iframe");
+    node = iframe.contentDocument.querySelector("span");
+    openInspector(runTests);
+  }
+
+  function runTests(aInspector)
+  {
+    inspector = aInspector;
+    inspector.selection.setNode(node);
+
+    let parentNode = node.parentNode;
+    parentNode.removeChild(node);
+
+    let tmp = {};
+    Cu.import("resource:///modules/devtools/LayoutHelpers.jsm", tmp);
+    ok(!tmp.LayoutHelpers.isNodeConnected(node), "Node considered as disconnected.");
+    executeSoon(function() {
+      is(inspector.selection.node, parentNode, "parent of selection got selected");
+
+      finishUp();
+    });
+  }
+
+  function finishUp() {
+    node = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
+
--- a/browser/devtools/layoutview/view.js
+++ b/browser/devtools/layoutview/view.js
@@ -30,17 +30,16 @@ function LayoutView(aInspector, aWindow)
 
 LayoutView.prototype = {
   init: function LV_init() {
     this.cssLogic = new CssLogic();
 
     this.update = this.update.bind(this);
     this.onNewNode = this.onNewNode.bind(this);
     this.onHighlighterLocked = this.onHighlighterLocked.bind(this);
-    this.inspector.selection.on("detached", this.onNewNode);
     this.inspector.selection.on("new-node", this.onNewNode);
     this.inspector.sidebar.on("layoutview-selected", this.onNewNode);
     if (this.inspector.highlighter) {
       this.inspector.highlighter.on("locked", this.onHighlighterLocked);
     }
 
     // Store for the different dimensions of the node.
     // 'selector' refers to the element that holds the value in view.xhtml;
@@ -96,17 +95,16 @@ LayoutView.prototype = {
   },
 
   /**
    * Destroy the nodes. Remove listeners.
    */
   destroy: function LV_destroy() {
     this.inspector.sidebar.off("layoutview-selected", this.onNewNode);
     this.inspector.selection.off("new-node", this.onNewNode);
-    this.inspector.selection.off("detached", this.onNewNode);
     if (this.browser) {
       this.browser.removeEventListener("MozAfterPaint", this.update, true);
     }
     if (this.inspector.highlighter) {
       this.inspector.highlighter.on("locked", this.onHighlighterLocked);
     }
     this.sizeHeadingLabel = null;
     this.sizeLabel = null;
--- a/browser/devtools/markupview/MarkupView.jsm
+++ b/browser/devtools/markupview/MarkupView.jsm
@@ -7,24 +7,26 @@
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Ci = Components.interfaces;
 
 // Page size for pageup/pagedown
 const PAGE_SIZE = 10;
 
 const PREVIEW_AREA = 700;
+const DEFAULT_MAX_CHILDREN = 100;
 
 this.EXPORTED_SYMBOLS = ["MarkupView"];
 
 Cu.import("resource:///modules/devtools/LayoutHelpers.jsm");
 Cu.import("resource:///modules/devtools/CssRuleView.jsm");
 Cu.import("resource:///modules/devtools/Templater.jsm");
 Cu.import("resource:///modules/devtools/Undo.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 /**
  * Vocabulary for the purposes of this file:
  *
  * MarkupContainer - the structure that holds an editor and its
  *  immediate children in the markup panel.
  * Node - A content node.
  * object.elt - A UI element in the markup panel.
@@ -41,16 +43,22 @@ Cu.import("resource://gre/modules/Servic
  */
 this.MarkupView = function MarkupView(aInspector, aFrame, aControllerWindow)
 {
   this._inspector = aInspector;
   this._frame = aFrame;
   this.doc = this._frame.contentDocument;
   this._elt = this.doc.querySelector("#root");
 
+  try {
+    this.maxChildren = Services.prefs.getIntPref("devtools.markup.pagesize");
+  } catch(ex) {
+    this.maxChildren = DEFAULT_MAX_CHILDREN;
+  }
+
   this.undo = new UndoStack();
   this.undo.installController(aControllerWindow);
 
   this._containers = new WeakMap();
 
   this._observer = new this.doc.defaultView.MutationObserver(this._mutationObserver.bind(this));
 
   this._boundOnNewSelection = this._onNewSelection.bind(this);
@@ -64,17 +72,17 @@ this.MarkupView = function MarkupView(aI
   this._frame.addEventListener("focus", this._boundFocus, false);
 
   this._initPreview();
 }
 
 MarkupView.prototype = {
   _selectedContainer: null,
 
-  template: function MT_template(aName, aDest, aOptions)
+  template: function MT_template(aName, aDest, aOptions={stack: "markup-view.xhtml"})
   {
     let node = this.doc.getElementById("template-" + aName).cloneNode(true);
     node.removeAttribute("id");
     template(node, aDest, aOptions);
     return node;
   },
 
   /**
@@ -283,32 +291,34 @@ MarkupView.prototype = {
       attributes: true,
       childList: true,
       characterData: true,
     });
 
     let walker = documentWalker(aNode);
     let parent = walker.parentNode();
     if (parent) {
-      // Make sure parents of this node are imported too.
       var container = new MarkupContainer(this, aNode);
     } else {
       var container = new RootContainer(this, aNode);
       this._elt.appendChild(container.elt);
       this._rootNode = aNode;
       aNode.addEventListener("load", function MP_watch_contentLoaded(aEvent) {
         // Fake a childList mutation here.
         this._mutationObserver([{target: aEvent.target, type: "childList"}]);
       }.bind(this), true);
-
     }
 
     this._containers.set(aNode, container);
+    // FIXME: set an expando to prevent the the wrapper from disappearing
+    // See bug 819131 for details.
+    aNode.__preserveHack = true;
     container.expanded = aExpand;
 
+    container.childrenDirty = true;
     this._updateChildren(container);
 
     if (parent) {
       this.importNode(parent, true);
     }
     return container;
   },
 
@@ -322,32 +332,35 @@ MarkupView.prototype = {
       if (!container) {
         // Container might not exist if this came from a load event for an iframe
         // we're not viewing.
         continue;
       }
       if (mutation.type === "attributes" || mutation.type === "characterData") {
         container.update();
       } else if (mutation.type === "childList") {
+        container.childrenDirty = true;
         this._updateChildren(container);
       }
     }
     this._inspector.emit("markupmutation");
   },
 
   /**
    * Make sure the given node's parents are expanded and the
    * node is scrolled on to screen.
    */
   showNode: function MT_showNode(aNode, centered)
   {
-    this.importNode(aNode);
+    let container = this.importNode(aNode);
+    this._updateChildren(container);
     let walker = documentWalker(aNode);
     let parent;
     while (parent = walker.parentNode()) {
+      this._updateChildren(this.getContainer(parent));
       this.expandNode(parent);
     }
     LayoutHelpers.scrollIntoViewIfNeeded(this._containers.get(aNode).editor.elt, centered);
   },
 
   /**
    * Expand the container's children.
    */
@@ -416,20 +429,43 @@ MarkupView.prototype = {
     if (this._selectedContainer) {
       this._selectedContainer.selected = false;
     }
     this._selectedContainer = container;
     if (aNode) {
       this._selectedContainer.selected = true;
     }
 
+    this._ensureSelectionVisible();
+
     return true;
   },
 
   /**
+   * Make sure that every ancestor of the selection are updated
+   * and included in the list of visible children.
+   */
+  _ensureSelectionVisible: function MT_ensureSelectionVisible()
+  {
+    let node = this._selectedContainer.node;
+    let walker = documentWalker(node);
+    while (node) {
+      let container = this._containers.get(node);
+      let parent = walker.parentNode();
+      if (!container.elt.parentNode) {
+        let parentContainer = this._containers.get(parent);
+        parentContainer.childrenDirty = true;
+        this._updateChildren(parentContainer, node);
+      }
+
+      node = parent;
+    }
+  },
+
+  /**
    * Unmark selected node (no node selected).
    */
   unmarkSelectedNode: function MT_unmarkSelectedNode()
   {
     if (this._selectedContainer) {
       this._selectedContainer.selected = false;
       this._selectedContainer = null;
     }
@@ -443,39 +479,149 @@ MarkupView.prototype = {
     if (aNode === this._inspector.selection) {
       this._inspector.change("markupview");
     }
   },
 
   /**
    * Make sure all children of the given container's node are
    * imported and attached to the container in the right order.
+   * @param aCentered If provided, this child will be included
+   *        in the visible subset, and will be roughly centered
+   *        in that list.
    */
-  _updateChildren: function MT__updateChildren(aContainer)
+  _updateChildren: function MT__updateChildren(aContainer, aCentered)
   {
+    if (!aContainer.childrenDirty) {
+      return false;
+    }
+
     // Get a tree walker pointing at the first child of the node.
     let treeWalker = documentWalker(aContainer.node);
     let child = treeWalker.firstChild();
     aContainer.hasChildren = !!child;
-    if (aContainer.expanded) {
-      let lastContainer = null;
-      while (child) {
-        let container = this.importNode(child, false);
+
+    if (!aContainer.expanded) {
+      return;
+    }
+
+    aContainer.childrenDirty = false;
+
+    let children = this._getVisibleChildren(aContainer, aCentered);
+    let fragment = this.doc.createDocumentFragment();
+
+    for (child of children.children) {
+      let container = this.importNode(child, false);
+      fragment.appendChild(container.elt);
+    }
+
+    while (aContainer.children.firstChild) {
+      aContainer.children.removeChild(aContainer.children.firstChild);
+    }
 
-        // Make sure children are in the right order.
-        let before = lastContainer ? lastContainer.nextSibling : aContainer.children.firstChild;
-        aContainer.children.insertBefore(container.elt, before);
-        lastContainer = container.elt;
-        child = treeWalker.nextSibling();
+    if (!(children.hasFirst && children.hasLast)) {
+      let data = {
+        showing: this.strings.GetStringFromName("markupView.more.showing"),
+        showAll: this.strings.formatStringFromName(
+                  "markupView.more.showAll",
+                  [aContainer.node.children.length.toString()], 1),
+        allButtonClick: function() {
+          aContainer.maxChildren = -1;
+          aContainer.childrenDirty = true;
+          this._updateChildren(aContainer);
+        }.bind(this)
+      };
+
+      if (!children.hasFirst) {
+        let span = this.template("more-nodes", data);
+        fragment.insertBefore(span, fragment.firstChild);
       }
-
-      while (aContainer.children.lastChild != lastContainer) {
-        aContainer.children.removeChild(aContainer.children.lastChild);
+      if (!children.hasLast) {
+        let span = this.template("more-nodes", data);
+        fragment.appendChild(span);
       }
     }
+
+    aContainer.children.appendChild(fragment);
+
+    return true;
+  },
+
+  /**
+   * Return a list of the children to display for this container.
+   */
+  _getVisibleChildren: function MV__getVisibleChildren(aContainer, aCentered)
+  {
+    let maxChildren = aContainer.maxChildren || this.maxChildren;
+    if (maxChildren == -1) {
+      maxChildren = Number.MAX_VALUE;
+    }
+    let firstChild = documentWalker(aContainer.node).firstChild();
+    let lastChild = documentWalker(aContainer.node).lastChild();
+
+    if (!firstChild) {
+      // No children, we're done.
+      return { hasFirst: true, hasLast: true, children: [] };
+    }
+
+    // By default try to put the selected child in the middle of the list.
+    let start = aCentered || firstChild;
+
+    // Start by reading backward from the starting point....
+    let nodes = [];
+    let backwardWalker = documentWalker(start);
+    if (backwardWalker.previousSibling()) {
+      let backwardCount = Math.floor(maxChildren / 2);
+      let backwardNodes = this._readBackward(backwardWalker, backwardCount);
+      nodes = backwardNodes;
+    }
+
+    // Then read forward by any slack left in the max children...
+    let forwardWalker = documentWalker(start);
+    let forwardCount = maxChildren - nodes.length;
+    nodes = nodes.concat(this._readForward(forwardWalker, forwardCount));
+
+    // If there's any room left, it means we've run all the way to the end.
+    // In that case, there might still be more items at the front.
+    let remaining = maxChildren - nodes.length;
+    if (remaining > 0 && nodes[0] != firstChild) {
+      let firstNodes = this._readBackward(backwardWalker, remaining);
+
+      // Then put it all back together.
+      nodes = firstNodes.concat(nodes);
+    }
+
+    return {
+      hasFirst: nodes[0] == firstChild,
+      hasLast: nodes[nodes.length - 1] == lastChild,
+      children: nodes
+    };
+  },
+
+  _readForward: function MV__readForward(aWalker, aCount)
+  {
+    let ret = [];
+    let node = aWalker.currentNode;
+    do {
+      ret.push(node);
+      node = aWalker.nextSibling();
+    } while (node && --aCount);
+    return ret;
+  },
+
+  _readBackward: function MV__readBackward(aWalker, aCount)
+  {
+    let ret = [];
+    let node = aWalker.currentNode;
+    do {
+      ret.push(node);
+      node = aWalker.previousSibling();
+    } while(node && --aCount);
+    ret.reverse();
+    return ret;
   },
 
   /**
    * Tear down the markup panel.
    */
   destroy: function MT_destroy()
   {
     this.undo.destroy();
@@ -613,19 +759,17 @@ function MarkupContainer(aMarkupView, aN
     this.editor = new GenericEditor(this.markup, aNode);
   }
 
   // The template will fill the following properties
   this.elt = null;
   this.expander = null;
   this.codeBox = null;
   this.children = null;
-  let options = { stack: "markup-view.xhtml" };
-  this.markup.template("container", this, options);
-
+  this.markup.template("container", this);
   this.elt.container = this;
 
   this.expander.addEventListener("click", function() {
     this.markup.navigate(this);
 
     if (this.expanded) {
       this.markup.collapseNode(this.node);
     } else {
@@ -729,17 +873,17 @@ MarkupContainer.prototype = {
    * Try to put keyboard focus on the current editor.
    */
   focus: function MC_focus()
   {
     let focusable = this.editor.elt.querySelector("[tabindex]");
     if (focusable) {
       focusable.focus();
     }
-  }
+  },
 }
 
 /**
  * Dummy container node used for the root document element.
  */
 function RootContainer(aMarkupView, aNode)
 {
   this.doc = aMarkupView.doc;
@@ -836,23 +980,22 @@ function ElementEditor(aContainer, aNode
   this.attrs = [];
 
   // The templates will fill the following properties
   this.elt = null;
   this.tag = null;
   this.attrList = null;
   this.newAttr = null;
   this.closeElt = null;
-  let options = { stack: "markup-view.xhtml" };
 
   // Create the main editor
-  this.template("element", this, options);
+  this.template("element", this);
 
   // Create the closing tag
-  this.template("elementClose", this, options);
+  this.template("elementClose", this);
 
   // Make the tag name editable (unless this is a document element)
   if (aNode != aNode.ownerDocument.documentElement) {
     this.tag.setAttribute("tabindex", "0");
     _editableField({
       element: this.tag,
       trigger: "dblclick",
       stopOnReturn: true,
@@ -922,18 +1065,17 @@ ElementEditor.prototype = {
       var attr = this.attrs[aAttr.name];
       var name = attr.querySelector(".attrname");
       var val = attr.querySelector(".attrvalue");
     } else {
       // Create the template editor, which will save some variables here.
       let data = {
         attrName: aAttr.name,
       };
-      let options = { stack: "markup-view.xhtml" };
-      this.template("attribute", data, options);
+      this.template("attribute", data);
       var {attr, inner, name, val} = data;
 
       // Figure out where we should place the attribute.
       let before = aBefore || null;
       if (aAttr.name == "id") {
         before = this.attrList.firstChild;
       } else if (aAttr.name == "class") {
         let idNode = this.attrs["id"];
@@ -1255,8 +1397,13 @@ function whitespaceTextFilter(aNode)
 {
     if (aNode.nodeType == Ci.nsIDOMNode.TEXT_NODE &&
         !/[^\s]/.exec(aNode.nodeValue)) {
       return Ci.nsIDOMNodeFilter.FILTER_SKIP;
     } else {
       return Ci.nsIDOMNodeFilter.FILTER_ACCEPT;
     }
 }
+
+XPCOMUtils.defineLazyGetter(MarkupView.prototype, "strings", function () {
+  return Services.strings.createBundle(
+          "chrome://browser/locale/devtools/inspector.properties");
+});
--- a/browser/devtools/markupview/markup-view.xhtml
+++ b/browser/devtools/markupview/markup-view.xhtml
@@ -11,16 +11,18 @@
   <link rel="stylesheet" href="chrome://browser/skin/devtools/markup-view.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://browser/skin/devtools/common.css" type="text/css"/>
 </head>
 <body class="devtools-theme-background devtools-monospace" role="application">
   <div id="root"></div>
   <div id="templates" style="display:none">
     <ul>
       <li id="template-container" save="${elt}" class="container"><span save="${expander}" class="expander"></span><span save="${codeBox}" class="codebox"><ul save="${children}" class="children"></ul></span></li>
+
+      <li id="template-more-nodes" class="more-nodes devtools-class-comment" save="${elt}"><span>${showing}</span> <button href="#" onclick="${allButtonClick}">${showAll}</button></li>
     </ul>
 
     <span id="template-element" save="${elt}" class="editor"><span>&lt;</span><span save="${tag}" class="tagname devtools-theme-tagname"></span><span save="${attrList}"></span><span save="${newAttr}" class="newattr" tabindex="0"></span>&gt;</span>
 
     <span id="template-attribute" save="${attr}" data-attr="${attrName}" class="attreditor" style="display:none"> <span class="editable" save="${inner}" tabindex="0"><span save="${name}" class="attrname devtools-theme-attrname"></span>=&quot;<span save="${val}" class="attrvalue devtools-theme-attrvalue"></span>&quot;</span></span>
 
     <span id="template-text" save="${elt}" class="editor text">
       <pre save="${value}" style="display:inline-block;" tabindex="0"></pre>
--- a/browser/devtools/markupview/test/Makefile.in
+++ b/browser/devtools/markupview/test/Makefile.in
@@ -12,14 +12,16 @@ include $(DEPTH)/config/autoconf.mk
 include $(topsrcdir)/config/rules.mk
 
 _BROWSER_FILES = \
 		browser_inspector_markup_navigation.html \
 		browser_inspector_markup_navigation.js \
 		browser_inspector_markup_mutation.html \
 		browser_inspector_markup_mutation.js \
 		browser_inspector_markup_edit.html \
-		browser_inspector_markup_edit.js \
+    browser_inspector_markup_edit.js \
+    browser_inspector_markup_subset.html \
+    browser_inspector_markup_subset.js \
 		head.js \
 		$(NULL)
 
 libs::	$(_BROWSER_FILES)
 	$(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_subset.html
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+
+<html class="html">
+  <body class="body">
+    <div id="a"></div>
+    <div id="b"></div>
+    <div id="c"></div>
+    <div id="d"></div>
+    <div id="e"></div>
+    <div id="f"></div>
+    <div id="g"></div>
+    <div id="h"></div>
+    <div id="i"></div>
+    <div id="j"></div>
+    <div id="k"></div>
+    <div id="l"></div>
+    <div id="m"></div>
+    <div id="n"></div>
+    <div id="o"></div>
+    <div id="p"></div>
+    <div id="q"></div>
+    <div id="r"></div>
+    <div id="s"></div>
+    <div id="t"></div>
+    <div id="u"></div>
+    <div id="v"></div>
+    <div id="w"></div>
+    <div id="x"></div>
+    <div id="y"></div>
+    <div id="z"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_inspector_markup_subset.js
@@ -0,0 +1,146 @@
+/* Any copyright", " is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that the markup view loads only as many nodes as specified
+ * by the devtools.markup.pagesize preference.
+ */
+
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.markup.pagesize");
+});
+Services.prefs.setIntPref("devtools.markup.pagesize", 5);
+
+
+function test() {
+  waitForExplicitFinish();
+
+  // Will hold the doc we're viewing
+  let doc;
+
+  let inspector;
+
+  // Holds the MarkupTool object we're testing.
+  let markup;
+
+  function assertChildren(expected)
+  {
+    let container = markup.getContainer(doc.querySelector("body"));
+    let found = [];
+    for (let child of container.children.children) {
+      if (child.classList.contains("more-nodes")) {
+        found += "*more*";
+      } else {
+        found += child.container.node.getAttribute("id");
+      }
+    }
+    is(expected, found, "Got the expected children.");
+  }
+
+  function forceReload()
+  {
+    let container = markup.getContainer(doc.querySelector("body"));
+    container.childrenDirty = true;
+  }
+
+  let selections = [
+    {
+      desc: "Select the first item",
+      selector: "#a",
+      before: function() {
+      },
+      after: function() {
+        assertChildren("abcde*more*");
+      }
+    },
+    {
+      desc: "Select the last item",
+      selector: "#z",
+      before: function() {},
+      after: function() {
+        assertChildren("*more*vwxyz");
+      }
+    },
+    {
+      desc: "Select an already-visible item",
+      selector: "#v",
+      before: function() {},
+      after: function() {
+        // Because "v" was already visible, we shouldn't have loaded
+        // a different page.
+        assertChildren("*more*vwxyz");
+      },
+    },
+    {
+      desc: "Verify childrenDirty reloads the page",
+      selector: "#w",
+      before: function() {
+        forceReload();
+      },
+      after: function() {
+        // But now that we don't already have a loaded page, selecting
+        // w should center around w.
+        assertChildren("*more*uvwxy*more*");
+      },
+    },
+  ];
+
+  // Create the helper tab for parsing...
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.selectedBrowser.addEventListener("load", function onload() {
+    gBrowser.selectedBrowser.removeEventListener("load", onload, true);
+    doc = content.document;
+    waitForFocus(setupTest, content);
+  }, true);
+  content.location = "http://mochi.test:8888/browser/browser/devtools/markupview/test/browser_inspector_markup_subset.html";
+
+  function setupTest() {
+    var target = TargetFactory.forTab(gBrowser.selectedTab);
+    let toolbox = gDevTools.showToolbox(target, "inspector").then(function(toolbox) {
+      toolbox.once("inspector-selected", function SE_selected(id, aInspector) {
+        inspector = aInspector;
+        markup = inspector.markup;
+        runNextSelection();
+      });
+    });
+  }
+
+  function runTests() {
+    inspector.selection.once("new-node", startTests);
+    executeSoon(function() {
+      inspector.selection.setNode(doc.body);
+    });
+  }
+
+  function runNextSelection() {
+    let selection = selections.shift();
+    if (!selection) {
+      clickMore();
+      return;
+    }
+
+    info(selection.desc);
+    selection.before();
+    inspector.selection.once("new-node", function() {
+      selection.after();
+      runNextSelection();
+    });
+    inspector.selection.setNode(doc.querySelector(selection.selector));
+  }
+
+  function clickMore() {
+    info("Check that clicking more loads the whole thing.");
+    // Make sure that clicking the "more" button loads all the nodes.
+    let container = markup.getContainer(doc.querySelector("body"));
+    let button = container.elt.querySelector("button");
+    button.click();
+    assertChildren("abcdefghijklmnopqrstuvwxyz");
+    finishUp();
+  }
+
+  function finishUp() {
+    doc = inspector = null;
+    gBrowser.removeCurrentTab();
+    finish();
+  }
+}
--- a/browser/devtools/responsivedesign/responsivedesign.jsm
+++ b/browser/devtools/responsivedesign/responsivedesign.jsm
@@ -153,19 +153,16 @@ function ResponsiveUI(aWindow, aTab)
   // Events
   this.tab.addEventListener("TabClose", this);
   this.tabContainer.addEventListener("TabSelect", this);
   this.mainWindow.document.addEventListener("keypress", this.bound_onKeypress, false);
 
   this.buildUI();
   this.checkMenus();
 
-  let target = TargetFactory.forTab(this.tab);
-  this.toolboxWasOpen = !!gDevTools.getToolbox(target);
-
   try {
     if (Services.prefs.getBoolPref("devtools.responsiveUI.rotate")) {
       this.rotate();
     }
   } catch(e) {}
 
   if (this._floatingScrollbars)
     switchToFloatingScrollbars(this.tab);
@@ -234,27 +231,19 @@ ResponsiveUI.prototype = {
    * Handle keypressed.
    *
    * @param aEvent
    */
   onKeypress: function RUI_onKeypress(aEvent) {
     if (aEvent.keyCode == this.mainWindow.KeyEvent.DOM_VK_ESCAPE &&
         this.mainWindow.gBrowser.selectedBrowser == this.browser) {
 
-      // If the toolbox wasn't open at first but is open now,
-      // we don't want to close the Responsive Mode on Escape.
-      // We let the toolbox close first.
-
-      let target = TargetFactory.forTab(this.tab);
-      let isToolboxOpen =  !!gDevTools.getToolbox(target);
-      if (this.toolboxWasOpen || !isToolboxOpen) {
-        aEvent.preventDefault();
-        aEvent.stopPropagation();
-        this.close();
-      }
+      aEvent.preventDefault();
+      aEvent.stopPropagation();
+      this.close();
     }
   },
 
   /**
    * Handle events
    */
   handleEvent: function (aEvent) {
     switch (aEvent.type) {
--- a/browser/devtools/scratchpad/scratchpad-manager.jsm
+++ b/browser/devtools/scratchpad/scratchpad-manager.jsm
@@ -64,21 +64,39 @@ this.ScratchpadManager = {
   },
 
   /**
    * Iterate through open scratchpad windows and save their states.
    */
   saveOpenWindows: function SPM_saveOpenWindows() {
     this._scratchpads = [];
 
+    function clone(src) {
+      let dest = {};
+
+      for (let key in src) {
+        if (src.hasOwnProperty(key)) {
+          dest[key] = src[key];
+        }
+      }
+
+      return dest;
+    }
+
+    // We need to clone objects we get from Scratchpad instances
+    // because such (cross-window) objects have a property 'parent'
+    // that holds on to a ChromeWindow instance. This means that
+    // such objects are not primitive-values-only anymore so they
+    // can leak.
+
     let enumerator = Services.wm.getEnumerator("devtools:scratchpad");
     while (enumerator.hasMoreElements()) {
       let win = enumerator.getNext();
       if (!win.closed && win.Scratchpad.initialized) {
-        this._scratchpads.push(win.Scratchpad.getState());
+        this._scratchpads.push(clone(win.Scratchpad.getState()));
       }
     }
   },
 
   /**
    * Open a new scratchpad window with an optional initial state.
    *
    * @param object aState
@@ -101,16 +119,17 @@ this.ScratchpadManager = {
         return;
       }
 
       params.SetString(1, JSON.stringify(aState));
     }
 
     let win = Services.ww.openWindow(null, SCRATCHPAD_WINDOW_URL, "_blank",
                                      SCRATCHPAD_WINDOW_FEATURES, params);
+
     // Only add the shutdown observer if we've opened a scratchpad window.
     ShutdownObserver.init();
 
     return win;
   }
 };
 
 
@@ -123,24 +142,25 @@ var ShutdownObserver = {
 
   init: function SDO_init()
   {
     if (this._initialized) {
       return;
     }
 
     Services.obs.addObserver(this, "quit-application-granted", false);
+
     this._initialized = true;
   },
 
   observe: function SDO_observe(aMessage, aTopic, aData)
   {
     if (aTopic == "quit-application-granted") {
       ScratchpadManager.saveOpenWindows();
       this.uninit();
     }
   },
 
   uninit: function SDO_uninit()
   {
     Services.obs.removeObserver(this, "quit-application-granted");
   }
-};
+};
\ No newline at end of file
--- a/browser/devtools/styleinspector/CssHtmlTree.jsm
+++ b/browser/devtools/styleinspector/CssHtmlTree.jsm
@@ -128,23 +128,25 @@ this.CssHtmlTree = function CssHtmlTree(
   this.cssLogic = aStyleInspector.cssLogic;
   this.propertyViews = [];
 
   let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"].
     getService(Ci.nsIXULChromeRegistry);
   this.getRTLAttr = chromeReg.isLocaleRTL("global") ? "rtl" : "ltr";
 
   // Create bound methods.
+  this.siFocusWindow = this.focusWindow.bind(this);
   this.siBoundMenuUpdate = this.computedViewMenuUpdate.bind(this);
   this.siBoundCopy = this.computedViewCopy.bind(this);
   this.siBoundCopyDeclaration = this.computedViewCopyDeclaration.bind(this);
   this.siBoundCopyProperty = this.computedViewCopyProperty.bind(this);
   this.siBoundCopyPropertyValue = this.computedViewCopyPropertyValue.bind(this);
 
   this.styleDocument.addEventListener("copy", this.siBoundCopy);
+  this.styleDocument.addEventListener("mousedown", this.siFocusWindow);
 
   // Nodes used in templating
   this.root = this.styleDocument.getElementById("root");
   this.templateRoot = this.styleDocument.getElementById("templateRoot");
   this.propertyContainer = this.styleDocument.getElementById("propertyContainer");
   this.panel = aStyleInspector.panel;
 
   // No results text.
@@ -553,16 +555,27 @@ CssHtmlTree.prototype = {
     menuitem.disabled = disablePropertyItems;
     menuitem = outerDoc.querySelector("#computed-view-copy-property");
     menuitem.disabled = disablePropertyItems;
     menuitem = outerDoc.querySelector("#computed-view-copy-property-value");
     menuitem.disabled = disablePropertyItems;
   },
 
   /**
+   * Focus the window on mousedown.
+   *
+   * @param aEvent The event object
+   */
+  focusWindow: function si_focusWindow(aEvent)
+  {
+    let win = this.styleDocument.defaultView;
+    win.focus();
+  },
+
+  /**
    * Copy selected text.
    *
    * @param aEvent The event object
    */
   computedViewCopy: function si_computedViewCopy(aEvent)
   {
     let win = this.styleDocument.defaultView;
     let text = win.getSelection().toString();
@@ -699,16 +712,17 @@ CssHtmlTree.prototype = {
       menuitem.removeEventListener("command", this.siBoundCopyPropertyValue);
 
       menu.removeEventListener("popupshowing", this.siBoundMenuUpdate);
       menu.parentNode.removeChild(menu);
     }
 
     // Remove bound listeners
     this.styleDocument.removeEventListener("copy", this.siBoundCopy);
+    this.styleDocument.removeEventListener("mousedown", this.siFocusWindow);
 
     // Nodes used in templating
     delete this.root;
     delete this.propertyContainer;
     delete this.panel;
 
     // The document in which we display the results (csshtmltree.xul).
     delete this.styleDocument;
--- a/browser/devtools/styleinspector/CssRuleView.jsm
+++ b/browser/devtools/styleinspector/CssRuleView.jsm
@@ -1397,16 +1397,18 @@ RuleEditor.prototype = {
     code.addEventListener("click", function() {
       let selection = this.doc.defaultView.getSelection();
       if (selection.isCollapsed) {
         this.newProperty();
       }
     }.bind(this), false);
 
     this.element.addEventListener("mousedown", function() {
+      this.doc.defaultView.focus();
+
       let editorNodes =
         this.doc.querySelectorAll(".styleinspector-propertyeditor");
 
       if (editorNodes) {
         for (let node of editorNodes) {
           if (node.inplaceEditor) {
             node.inplaceEditor._clear();
           }
--- a/browser/devtools/tilt/TiltVisualizer.jsm
+++ b/browser/devtools/tilt/TiltVisualizer.jsm
@@ -176,32 +176,30 @@ TiltVisualizer.prototype = {
 
     let target = TargetFactory.forTab(aTab);
     let toolbox = gDevTools.getToolbox(target);
     if (toolbox) {
       let panel = toolbox.getPanel("inspector");
       if (panel) {
         this.inspector = panel;
         this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
-        this.inspector.selection.on("detached", this.onNewNodeFromInspector);
         this.onNewNodeFromInspector();
       }
     }
   },
 
   /**
    * Unregister inspector event listeners.
    */
   unbindInspector: function TV_unbindInspector()
   {
     this._browserTab = null;
 
     if (this.inspector) {
       this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
-      this.inspector.selection.off("detached", this.onNewNodeFromInspector);
       this.inspector = null;
     }
 
     gDevTools.off("inspector-ready", this.onInspectorReady);
     gDevTools.off("toolbox-destroyed", this.onToolboxDestroyed);
 
     Services.obs.removeObserver(this.onNewNodeFromTilt,
                                 this.presenter.NOTIFICATIONS.HIGHLIGHTING);
@@ -212,31 +210,29 @@ TiltVisualizer.prototype = {
   /**
    * When a new inspector is started.
    */
   onInspectorReady: function TV_onInspectorReady(event, toolbox, panel)
   {
     if (toolbox.target.tab === this._browserTab) {
       this.inspector = panel;
       this.inspector.selection.on("new-node", this.onNewNodeFromInspector);
-      this.inspector.selection.on("detached", this.onNewNodeFromInspector);
       this.onNewNodeFromTilt();
     }
   },
 
   /**
    * When the toolbox, therefor the inspector, is closed.
    */
   onToolboxDestroyed: function TV_onToolboxDestroyed(event, tab)
   {
     if (tab === this._browserTab &&
         this.inspector) {
       if (this.inspector.selection) {
         this.inspector.selection.off("new-node", this.onNewNodeFromInspector);
-        this.inspector.selection.off("detached", this.onNewNodeFromInspector);
       }
       this.inspector = null;
     }
   },
 
   /**
    * When a new node is selected in the inspector.
    */
--- a/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/connection-screen.dtd
@@ -1,15 +1,22 @@
 <!-- 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/. -->
 
-<!-- LOCALIZATION NOTE : FILE This file contains the Remove Connection strings.
-  - The Remote Connection window can be start by running the command:
-  - `devtools connect`
+<!-- LOCALIZATION NOTE : FILE This file contains the Remote Connection strings.
+  - The Remote Connection window can reached from the "connect…" menuitem
+  - in the Web Developer menu.
   - -->
 
 <!ENTITY title      "Connect">
+<!ENTITY header     "Connect to remote device">
 <!ENTITY host       "Host:">
 <!ENTITY port       "Port:">
 <!ENTITY connect    "Connect">
 <!ENTITY connecting "Connecting…">
 <!ENTITY availability "Available remote objects:">
+<!ENTITY remoteProcess "remote process">
+<!ENTITY connectionError "Error:">
+<!ENTITY errorTimeout "Error: connection timeout.">
+<!ENTITY errorRefused "Error: connection refused.">
+<!ENTITY errorUnexpected "Unexpected error.">
+<!ENTITY help "Firefox Developer Tools can debug remote devices (Firefox for Android and Firefox OS for example). Make sure that you have turned on the 'Debugger Server' option on the remote device. See <a target='_' href='https://developer.mozilla.org/en-US/docs/Tools/Debugger#Remote_Debugging'>documentation</a>.">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/connection-screen.properties
@@ -0,0 +1,9 @@
+# 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/.
+
+# LOCALIZATION NOTE : FILE This file contains the Remote Connection strings.
+# The Remote Connection window can reached from the "connect…" menuitem
+# in the Web Developer menu.
+
+remoteProcess=Remote Process
--- a/browser/locales/en-US/chrome/browser/devtools/debugger.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/debugger.properties
@@ -181,16 +181,21 @@ watchExpressionsScopeLabel=Watch express
 # the global scope.
 globalScopeLabel=Global
 
 # LOCALIZATION NOTE (ToolboxDebugger.label):
 # This string is displayed in the title of the tab when the debugger is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxDebugger.label=Debugger
 
+# LOCALIZATION NOTE (ToolboxDebugger.tooltip):
+# This string is displayed in the tooltip of the tab when the debugger is
+# displayed inside the developer tools window..
+ToolboxDebugger.tooltip=JavaScript Debugger
+
 # LOCALIZATION NOTE (variablesEditableNameTooltip): The text that is displayed
 # in the variables list on an item with an editable name.
 variablesEditableNameTooltip=Double click to edit
 
 # LOCALIZATION NOTE (variablesEditableValueTooltip): The text that is displayed
 # in the variables list on an item with an editable name.
 variablesEditableValueTooltip=Click to change value
 
--- a/browser/locales/en-US/chrome/browser/devtools/inspector.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/inspector.properties
@@ -28,8 +28,15 @@ breadcrumbs.siblings=Siblings
 nodeMenu.tooltiptext=Node operations
 
 
 # LOCALIZATION NOTE (inspector.*)
 # Used for the menuitem in the tool menu
 inspector.label=Inspector
 inspector.commandkey=I
 inspector.accesskey=I
+
+# LOCALIZATION NOTE (markupView.more.*)
+# When there are too many nodes to load at once, we will offer to
+# show all the nodes.
+markupView.more.showing=Some nodes were hidden.
+markupView.more.showAll=Show All %S Nodes
+inspector.tooltip=DOM and Style Inspector
--- a/browser/locales/en-US/chrome/browser/devtools/profiler.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/profiler.properties
@@ -8,9 +8,14 @@
 # English, or another language commonly spoken among web developers.
 # You want to make that choice consistent across the developer tools.
 # A good criteria is the language in which you'd find the best
 # documentation on web development on the web.
 
 # LOCALIZATION NOTE (profiler.label):
 # This string is displayed in the title of the tab when the profiler is
 # displayed inside the developer tools window and in the Developer Tools Menu.
-profiler.label=Profiler
\ No newline at end of file
+profiler.label=Profiler
+
+# LOCALIZATION NOTE (profiler.tooltip):
+# This string is displayed in the tooltip of the tab when the profiler is
+# displayed inside the developer tools window.
+profiler.tooltip=Profiler
--- a/browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/styleeditor.properties
@@ -73,8 +73,13 @@ undo.commandkey=Z
 # conjunction with accel+shift (accel is Command on Mac or Ctrl on other
 # platforms) to Redo a change in the editor.
 redo.commandkey=Z
 
 # LOCALIZATION NOTE (ToolboxStyleEditor.label):
 # This string is displayed in the title of the tab when the debugger is
 # displayed inside the developer tools window and in the Developer Tools Menu.
 ToolboxStyleEditor.label=Style Editor
+
+# LOCALIZATION NOTE (ToolboxStyleEditor.tooltip):
+# This string is displayed in the tooltip of the tab when the debugger is
+# displayed inside the developer tools window.
+ToolboxStyleEditor.tooltip=CSS Stylesheets Editor
--- a/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.dtd
@@ -1,7 +1,9 @@
 <!-- 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/. -->
 
 <!ENTITY window.title  "Developer Tools">
 
 <!ENTITY closeCmd.key  "W">
+
+<!ENTITY toolboxCloseButton.tooltip  "Close Developer Tools">
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/devtools/toolbox.properties
@@ -0,0 +1,3 @@
+toolboxDockButtons.bottom.tooltip=Dock to bottom of browser window
+toolboxDockButtons.side.tooltip=Dock to side of browser window
+toolboxDockButtons.window.tooltip=Show in separate window
--- a/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/webconsole.properties
@@ -169,16 +169,21 @@ remoteWebConsoleSelectTabMessage=Select 
 listTabs.globalConsoleActor=*Global Console*
 
 # LOCALIZATION NOTE (ToolboxWebconsole.label):
 # This string is displayed in the title of the tab when the web console is
 # displayed inside the developer tools window it is probably the same string
 # as webConsoleWindowTitleAndURL before the '-'
 ToolboxWebconsole.label=Web Console
 
+# LOCALIZATION NOTE (ToolboxWebconsole.tooltip):
+# This string is displayed in the tooltip of the tab when the web console is
+# displayed inside the developer tools window.
+ToolboxWebconsole.tooltip=Web Console
+
 # LOCALIZATION NOTE (longStringEllipsis): The string displayed after a long
 # string. This string is clickable such that the rest of the string is retrieved
 # from the server.
 longStringEllipsis=[…]
 
 # LOCALIZATION NOTE (executeEmptyInput): This is displayed when the user tries
 # to execute code, but the input is empty.
 executeEmptyInput=No value to execute.
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -36,16 +36,17 @@
     locale/browser/devtools/sourceeditor.properties   (%chrome/browser/devtools/sourceeditor.properties)
     locale/browser/devtools/sourceeditor.dtd          (%chrome/browser/devtools/sourceeditor.dtd)
     locale/browser/devtools/profiler.properties       (%chrome/browser/devtools/profiler.properties)
     locale/browser/devtools/layoutview.dtd            (%chrome/browser/devtools/layoutview.dtd)
     locale/browser/devtools/responsiveUI.properties   (%chrome/browser/devtools/responsiveUI.properties)
     locale/browser/devtools/toolbox.dtd            (%chrome/browser/devtools/toolbox.dtd)
     locale/browser/devtools/inspector.dtd          (%chrome/browser/devtools/inspector.dtd)
     locale/browser/devtools/connection-screen.dtd  (%chrome/browser/devtools/connection-screen.dtd)
+    locale/browser/devtools/connection-screen.properties (%chrome/browser/devtools/connection-screen.properties)
     locale/browser/newTab.dtd                      (%chrome/browser/newTab.dtd)
     locale/browser/newTab.properties               (%chrome/browser/newTab.properties)
     locale/browser/openLocation.dtd                (%chrome/browser/openLocation.dtd)
     locale/browser/openLocation.properties         (%chrome/browser/openLocation.properties)
     locale/browser/pageInfo.dtd                    (%chrome/browser/pageInfo.dtd)
     locale/browser/pageInfo.properties             (%chrome/browser/pageInfo.properties)
     locale/browser/quitDialog.properties           (%chrome/browser/quitDialog.properties)
     locale/browser/safeMode.dtd                    (%chrome/browser/safeMode.dtd)
--- a/browser/themes/gnomestripe/devtools/common.css
+++ b/browser/themes/gnomestripe/devtools/common.css
@@ -123,35 +123,41 @@
 .devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker,
 .devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-dropmarker {
   -moz-appearance: none !important;
   list-style-image: url("chrome://browser/skin/devtools/dropmarker.png");
   -moz-box-align: center;
   margin: 0 3px;
 }
 
-/* Search input */
+/* Text input */
 
+.devtools-textinput,
 .devtools-searchinput {
   -moz-appearance: none;
   margin: 0 3px;
   border: 1px solid hsla(210,8%,5%,.6);
   border-radius: 2px;
   background-color: transparent;
-  background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
-  background-repeat: no-repeat;
-  background-position: 4px center, top left, top left;
+  background-image: -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
+  padding: 3px;
+  box-shadow: 0 1px 1px hsla(210,8%,5%,.3) inset,
+              0 0 0 1px hsla(210,16%,76%,.1) inset,
+              0 1px 0 hsla(210,16%,76%,.15);
+  color: inherit;
+}
+
+.devtools-searchinput {
   padding-top: 0;
   padding-bottom: 0;
   -moz-padding-start: 18px;
   -moz-padding-end: 12px;
-  box-shadow: 0 1px 1px hsla(210,8%,5%,.3) inset,
-              0 0 0 1px hsla(210,16%,76%,.1) inset,
-              0 1px 0 hsla(210,16%,76%,.15);
-  color: inherit;
+  background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
+  background-position: 4px center, top left, top left;
+  background-repeat: no-repeat;
 }
 
 .devtools-searchinput:-moz-locale-dir(rtl) {
   background-position: calc(100% - 4px) center, top left, top left;
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-search-icons {
   display: none;
--- a/browser/themes/gnomestripe/devtools/markup-view.css
+++ b/browser/themes/gnomestripe/devtools/markup-view.css
@@ -44,11 +44,15 @@ li.container {
   -moz-appearance: treetwisty;
   padding: 11px 0;
 }
 
 .expander[expanded] {
   -moz-appearance: treetwistyopen;
 }
 
+.more-nodes {
+  padding-left: 16px;
+}
+
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
 }
--- a/browser/themes/pinstripe/devtools/common.css
+++ b/browser/themes/pinstripe/devtools/common.css
@@ -129,35 +129,41 @@
 .devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker,
 .devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-dropmarker {
   -moz-appearance: none !important;
   list-style-image: url("chrome://browser/skin/devtools/dropmarker.png");
   margin: 0 3px;
   border: 0;
 }
 
-/* Search input */
+/* Text input */
 
+.devtools-textinput,
 .devtools-searchinput {
   -moz-appearance: none;
   margin: 0 3px;
   background-color: transparent;
   border: 1px solid hsla(210,8%,5%,.6);
   border-radius: 20px;
+  background-image: -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
+  padding: 3px;
+  box-shadow: 0 1px 1px hsla(210,8%,5%,.3) inset,
+              0 0 0 1px hsla(210,16%,76%,.1) inset,
+              0 1px 0 hsla(210,16%,76%,.15);
+  color: inherit;
+}
+
+.devtools-searchinput {
   background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
   background-repeat: no-repeat;
   background-position: 4px center, top left, top left;
   padding-top: 0;
   padding-bottom: 0;
   -moz-padding-start: 18px;
   -moz-padding-end: 12px;
-  box-shadow: 0 1px 1px hsla(210,8%,5%,.3) inset,
-              0 0 0 1px hsla(210,16%,76%,.1) inset,
-              0 1px 0 hsla(210,16%,76%,.15);
-  color: inherit;
 }
 
 .devtools-searchinput:-moz-locale-dir(rtl) {
   background-position: calc(100% - 4px) center, top left, top left;
 }
 
 .devtools-searchinput > .textbox-input-box > .textbox-search-icons {
   display: none;
--- a/browser/themes/pinstripe/devtools/markup-view.css
+++ b/browser/themes/pinstripe/devtools/markup-view.css
@@ -47,11 +47,15 @@ li.container {
   width: 14px;
   height: 14px;
 }
 
 .expander[expanded] {
   -moz-appearance: treetwistyopen;
 }
 
+.more-nodes {
+  padding-left: 16px;
+}
+
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
 }
--- a/browser/themes/winstripe/devtools/common.css
+++ b/browser/themes/winstripe/devtools/common.css
@@ -127,36 +127,42 @@
 
 .devtools-toolbarbutton[type=menu] > .toolbarbutton-menu-dropmarker,
 .devtools-toolbarbutton[type=menu-button] > .toolbarbutton-menubutton-dropmarker {
   -moz-appearance: none !important;
   list-style-image: url("chrome://browser/skin/devtools/dropmarker.png");
   -moz-box-align: center;
 }
 
-/* Search input */
+/* Text input */
 
+.devtools-textinput,
 .devtools-searchinput {
   -moz-appearance: none;
   margin: 0 3px;
   border: 1px solid hsla(211,68%,6%,.6);
   box-shadow: inset 0 1px 0 hsla(211,68%,6%,.05), 0 0 0 1px hsla(210,40%,83%,.1);
   border-radius: 2px;
   background-color: transparent;
+  background-image: -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
+  padding: 3px;
+  transition-property: background-color, border-color, box-shadow;
+  transition-duration: 150ms;
+  transition-timing-function: ease;
+  color: inherit;
+}
+
+.devtools-searchinput {
   background-image: url(magnifying-glass.png), -moz-linear-gradient(hsla(210,16%,76%,.15), hsla(210,16%,76%,.35));
   background-repeat: no-repeat;
   background-position: 4px center, top left, top left;
   padding-top: 0;
   padding-bottom: 0;
   -moz-padding-start: 18px;
   -moz-padding-end: 12px;
-  transition-property: background-color, border-color, box-shadow;
-  transition-duration: 150ms;
-  transition-timing-function: ease;
-  color: inherit;
 }
 
 .devtools-searchinput[focused] {
   border-color: hsl(200,70%,40%) hsl(200,75%,37%) hsl(200,80%,35%);
   background-origin: padding-box;
   background-clip: padding-box;
   box-shadow: inset 0 0 0 1px hsla(211,68%,6%,.1);
 }
--- a/browser/themes/winstripe/devtools/markup-view.css
+++ b/browser/themes/winstripe/devtools/markup-view.css
@@ -49,11 +49,15 @@ li.container {
   background-position: center;
   background-image: url("chrome://global/skin/tree/twisty-clsd.png");
 }
 
 .expander[expanded] {
   background-image: url("chrome://global/skin/tree/twisty-open.png");
 }
 
+.more-nodes {
+  padding-left: 16px;
+}
+
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
 }