Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Sat, 23 Apr 2016 10:51:51 +0200
changeset 332498 3a1cffbeb01b4dcf3a6694d3f985a46203abfbf8
parent 332497 d1f0c65db1ec07712bc531eb5585c4f504d8580a (current diff)
parent 332365 37f04460ddb76d6ef4e7c32a8a6b2fbc44cb8776 (diff)
child 332499 482df01e83b54fdcc4b4dbdc9761e0c884b77802
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/base/content/aboutNetError.xhtml
browser/base/content/test/general/authenticate.sjs
browser/base/content/test/general/browser_URLBarSetURI.js
browser/base/content/test/general/browser_action_keyword.js
browser/base/content/test/general/browser_action_keyword_override.js
browser/base/content/test/general/browser_action_searchengine.js
browser/base/content/test/general/browser_action_searchengine_alias.js
browser/base/content/test/general/browser_autocomplete_a11y_label.js
browser/base/content/test/general/browser_autocomplete_autoselect.js
browser/base/content/test/general/browser_autocomplete_cursor.js
browser/base/content/test/general/browser_autocomplete_edit_completed.js
browser/base/content/test/general/browser_autocomplete_enter_race.js
browser/base/content/test/general/browser_autocomplete_no_title.js
browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js
browser/base/content/test/general/browser_autocomplete_tag_star_visibility.js
browser/base/content/test/general/browser_bug1003461-switchtab-override.js
browser/base/content/test/general/browser_bug1024133-switchtab-override-keynav.js
browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
browser/base/content/test/general/browser_bug1070778.js
browser/base/content/test/general/browser_bug1104165-switchtab-decodeuri.js
browser/base/content/test/general/browser_bug1225194-remotetab.js
browser/base/content/test/general/browser_bug304198.js
browser/base/content/test/general/browser_bug556061.js
browser/base/content/test/general/browser_bug562649.js
browser/base/content/test/general/browser_bug623155.js
browser/base/content/test/general/browser_bug783614.js
browser/base/content/test/general/browser_canonizeURL.js
browser/base/content/test/general/browser_locationBarCommand.js
browser/base/content/test/general/browser_locationBarExternalLoad.js
browser/base/content/test/general/browser_removeUnsafeProtocolsFromURLBarPaste.js
browser/base/content/test/general/browser_search_favicon.js
browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
browser/base/content/test/general/browser_urlHighlight.js
browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
browser/base/content/test/general/browser_urlbarCopying.js
browser/base/content/test/general/browser_urlbarDecode.js
browser/base/content/test/general/browser_urlbarDelete.js
browser/base/content/test/general/browser_urlbarEnter.js
browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js
browser/base/content/test/general/browser_urlbarRevert.js
browser/base/content/test/general/browser_urlbarSearchSingleWordNotification.js
browser/base/content/test/general/browser_urlbarSearchSuggestions.js
browser/base/content/test/general/browser_urlbarSearchSuggestionsNotification.js
browser/base/content/test/general/browser_urlbarSearchTelemetry.js
browser/base/content/test/general/browser_urlbarStop.js
browser/base/content/test/general/browser_urlbarTrimURLs.js
browser/base/content/test/general/browser_urlbar_autoFill_backspaced.js
browser/base/content/test/general/browser_urlbar_searchsettings.js
browser/base/content/test/general/browser_wyciwyg_urlbarCopying.js
browser/base/content/test/general/redirect_bug623155.sjs
browser/base/content/test/general/test_wyciwyg_copying.html
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -789,18 +789,18 @@ pref("gecko.handlerService.allowRegister
 pref("browser.safebrowsing.enabled", true);
 pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
 pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
 pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
 pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
-pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
-pref("browser.safebrowsing.downloads.remote.block_uncommon",             false);
+pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", true);
+pref("browser.safebrowsing.downloads.remote.block_uncommon",             true);
 pref("browser.safebrowsing.debug", false);
 
 pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
 pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
 pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
 pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
 
 pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -110,21 +110,16 @@
       }
 
       function showCertificateErrorReporting() {
         // Display error reporting UI
         document.getElementById('certificateErrorReporting').style.display = 'block';
       }
 
       function showAdvancedButton(allowOverride) {
-        // Display weak crypto advanced UI
-        document.getElementById("buttonContainer").style.display = "flex";
-        document.getElementById("advancedButton").style.display = "block";
-        document.getElementById("errorTryAgain").style.display = "none";
-
         // Get the hostname and add it to the panel
         var panelId = gIsCertError ? "badCertAdvancedPanel" : "weakCryptoAdvancedPanel";
         var panel = document.getElementById(panelId);
         for (var span of panel.querySelectorAll("span.hostname")) {
           span.textContent = document.location.hostname;
         }
         if (!gIsCertError) {
           panel.replaceChild(document.getElementById("errorLongDesc"),
@@ -217,31 +212,25 @@
 
       function initPage()
       {
         var err = getErrorCode();
         gIsCertError = (err == "nssBadCert");
 
         // if it's an unknown error or there's no title or description
         // defined, get the generic message
-        var errTitle = document.getElementById("et_" + err);
-        var errDesc  = document.getElementById("ed_" + err);
+        var errTitle = document.getElementById("et_" + err).innerHTML;
+        var errDesc  = document.getElementById("ed_" + err).innerHTML;
         if (!errTitle || !errDesc)
         {
-          errTitle = document.getElementById("et_generic");
-          errDesc  = document.getElementById("ed_generic");
+          errTitle = document.getElementById("et_generic").innerHTML;
+          errDesc  = document.getElementById("ed_generic").innerHTML;
         }
 
-        var title = document.getElementById("errorTitleText");
-        if (title)
-        {
-          title.parentNode.replaceChild(errTitle, title);
-          // change id to the replaced child's id so styling works
-          errTitle.id = "errorTitleText";
-        }
+        document.querySelector(".title-text").innerHTML = errTitle;
 
         var sd = document.getElementById("errorShortDescText");
         if (sd) {
           if (gIsCertError) {
             sd.innerHTML = document.getElementById("ed_nssBadCert").innerHTML;
           }
           else {
             sd.textContent = getDescription();
@@ -250,32 +239,28 @@
         if (gIsCertError) {
           initPageCertError();
           return;
         }
 
         var ld = document.getElementById("errorLongDesc");
         if (ld)
         {
-          ld.parentNode.replaceChild(errDesc, ld);
-          // change id to the replaced child's id so styling works
-          errDesc.id = "errorLongDesc";
+          ld.innerHTML = errDesc;
         }
 
         if (err == "sslv3Used") {
-          document.getElementById("errorTitle").setAttribute("sslv3", "true");
-          document.getElementById("errorTryAgain").style.display = "none";
           document.getElementById("learnMoreContainer").style.display = "block";
           var learnMoreLink = document.getElementById("learnMoreLink");
           learnMoreLink.href = "https://support.mozilla.org/kb/how-resolve-sslv3-error-messages-firefox";
-          document.getElementById("buttonContainer").style.display = "flex";
+          document.body.className = "certerror";
         }
 
         if (err == "weakCryptoUsed") {
-          document.getElementById("errorTitle").setAttribute("weakCrypto", "true");
+          document.body.className = "certerror";
         }
 
         // remove undisplayed errors to avoid bug 39098
         var errContainer = document.getElementById("errorContainer");
         errContainer.parentNode.removeChild(errContainer);
 
         var className = getCSSClass();
         if (className && className != "expertBadCert") {
@@ -292,23 +277,23 @@
           faviconParent.removeChild(favicon);
           favicon.setAttribute("href", "chrome://global/skin/icons/" + className + "_favicon.png");
           faviconParent.appendChild(favicon);
         }
 
         if (err == "remoteXUL") {
           // Remove the "Try again" button for remote XUL errors given that
           // it is useless.
-          document.getElementById("errorTryAgain").style.display = "none";
+          document.getElementById("netErrorButtonContainer").style.display = "none";
         }
 
         if (err == "cspBlocked") {
           // Remove the "Try again" button for CSP violations, since it's
           // almost certainly useless. (Bug 553180)
-          document.getElementById("errorTryAgain").style.display = "none";
+          document.getElementById("netErrorButtonContainer").style.display = "none";
         }
 
         window.addEventListener("AboutNetErrorOptions", function(evt) {
         // Pinning errors are of type nssFailure2
           if (getErrorCode() == "nssFailure2" || getErrorCode() == "weakCryptoUsed") {
             document.getElementById("learnMoreContainer").style.display = "block";
             var learnMoreLink = document.getElementById("learnMoreLink");
             // nssFailure2 also gets us other non-overrideable errors. Choose
@@ -548,21 +533,21 @@
         <div id="ed_corruptedContentError">&corruptedContentError.longDesc;</div>
         <div id="ed_sslv3Used">&sslv3Used.longDesc2;</div>
         <div id="ed_weakCryptoUsed">&weakCryptoUsed.longDesc2;</div>
         <div id="ed_inadequateSecurityError">&inadequateSecurityError.longDesc;</div>
       </div>
     </div>
 
     <!-- PAGE CONTAINER (for styling purposes only) -->
-    <div id="errorPageContainer">
+    <div id="errorPageContainer" class="container">
 
       <!-- Error Title -->
-      <div id="errorTitle">
-        <h1 id="errorTitleText" />
+      <div class="title">
+        <h1 class="title-text"/>
       </div>
 
       <!-- LONG CONTENT (the section most likely to require scrolling) -->
       <div id="errorLongContent">
 
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText" />
@@ -571,60 +556,62 @@
 
         <!-- Long Description (Note: See netError.dtd for used XHTML tags) -->
         <div id="errorLongDesc" />
 
         <div id="learnMoreContainer">
           <p><a href="https://support.mozilla.org/kb/what-does-your-connection-is-not-secure-mean" id="learnMoreLink" target="new">&errorReporting.learnMore;</a></p>
         </div>
 
-        <div id="buttonContainer">
-          <button id="returnButton" autocomplete="off" autofocus="true">&returnToPreviousPage.label;</button>
-          <div id="buttonSpacer"></div>
+        <div id="certErrorButtonContainer" class="button-container">
+          <button id="returnButton" class="primary" autocomplete="off" autofocus="true">&returnToPreviousPage.label;</button>
+          <div class="button-spacer"></div>
           <button id="advancedButton" autocomplete="off" autofocus="true">&advanced.label;</button>
         </div>
       </div>
 
-      <button id="errorTryAgain" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
+      <div id="netErrorButtonContainer" class="button-container">
+        <button id="errorTryAgain" class="primary" autocomplete="off" onclick="retryThis(this);">&retry.label;</button>
+      </div>
+
       <script>
         // Only do autofocus if we're the toplevel frame; otherwise we
         // don't want to call attention to ourselves!  The key part is
         // that autofocus happens on insertion into the tree, so we
         // can remove the button, add @autofocus, and reinsert the
         // button.
         if (window.top == window) {
             var button = document.getElementById("errorTryAgain");
-            var nextSibling = button.nextSibling;
             var parent = button.parentNode;
-            parent.removeChild(button);
+            button.remove();
             button.setAttribute("autofocus", "true");
-            parent.insertBefore(button, nextSibling);
+            parent.appendChild(button);
         }
       </script>
 
       <!-- UI for option to report certificate errors to Mozilla. Removed on
            init for other error types .-->
       <div id="certificateErrorReporting">
         <p>
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic2;</label>
         </p>
       </div>
 
-      <div id="weakCryptoAdvancedPanel">
+      <div id="weakCryptoAdvancedPanel" class="advanced-panel">
         <div id="weakCryptoAdvancedDescription">
           <p>&weakCryptoAdvanced.longDesc;</p>
         </div>
         <div id="advancedLongDesc" />
         <div id="overrideWeakCryptoPanel">
           <a id="overrideWeakCrypto" href="#">&weakCryptoAdvanced.override;</a>
         </div>
       </div>
 
-      <div id="badCertAdvancedPanel">
+      <div id="badCertAdvancedPanel" class="advanced-panel">
         <p id="badCertTechnicalInfo"/>
         <button id="exceptionDialogButton">&securityOverride.exceptionButtonLabel;</button>
       </div>
 
     </div>
 
     <div id="certificateErrorDebugInformation">
       <a name="technicalInformation"></a>
--- a/browser/base/content/blockedSite.xhtml
+++ b/browser/base/content/blockedSite.xhtml
@@ -12,17 +12,17 @@
 ]>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <html xmlns="http://www.w3.org/1999/xhtml" class="blacklist">
   <head>
-    <link rel="stylesheet" href="chrome://global/skin/netError.css" type="text/css" media="all" />
+    <link rel="stylesheet" href="chrome://browser/skin/blockedSite.css" type="text/css" media="all" />
     <link rel="icon" type="image/png" id="favicon" href="chrome://global/skin/icons/blacklist_favicon.png"/>
 
     <script type="application/javascript"><![CDATA[
       // Error url MUST be formatted like this:
       //   about:blocked?e=error_code&u=url(&o=1)?
       //     (o=1 when user overrides are allowed)
 
       // Note that this file uses document.documentURI to get
@@ -160,78 +160,51 @@
           }
         }
 
         // Inform the test harness that we're done loading the page
         var event = new CustomEvent("AboutBlockedLoaded");
         document.dispatchEvent(event);
       }
     ]]></script>
-    <style type="text/css">
-      /* Style warning button to look like a small text link in the
-         bottom right. This is preferable to just using a text link
-         since there is already a mechanism in browser.js for trapping
-         oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
-      #ignoreWarningButton {
-        -moz-appearance: none;
-        background: transparent;
-        border: none;
-        color: white;  /* Hard coded because netError.css forces this page's background to dark red */
-        text-decoration: underline;
-        margin: 0;
-        padding: 0;
-        position: relative;
-        top: 23px;
-        left: 20px;
-        font-size: smaller;
-      }
-      
-      #ignoreWarningButton:-moz-dir(rtl) {
-        left: auto;
-        right: 20px;
-      }
-      
-      #ignoreWarning {
-        text-align: end;
-      }
-    </style>
   </head>
 
   <body dir="&locale.dir;">
-    <div id="errorPageContainer">
-    
+    <div id="errorPageContainer" class="container">
+
       <!-- Error Title -->
-      <div id="errorTitle">
-        <h1 id="errorTitleText_phishing">&safeb.blocked.phishingPage.title2;</h1>
-        <h1 id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
-        <h1 id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
-        <h1 id="errorTitleText_forbidden">&safeb.blocked.forbiddenPage.title2;</h1>
+      <div id="errorTitle" class="title">
+        <h1 class="title-text" id="errorTitleText_phishing">&safeb.blocked.phishingPage.title2;</h1>
+        <h1 class="title-text" id="errorTitleText_malware">&safeb.blocked.malwarePage.title;</h1>
+        <h1 class="title-text" id="errorTitleText_unwanted">&safeb.blocked.unwantedPage.title;</h1>
+        <h1 class="title-text" id="errorTitleText_forbidden">&safeb.blocked.forbiddenPage.title2;</h1>
       </div>
-      
+
       <div id="errorLongContent">
-      
+
         <!-- Short Description -->
         <div id="errorShortDesc">
           <p id="errorShortDescText_phishing">&safeb.blocked.phishingPage.shortDesc2;</p>
           <p id="errorShortDescText_malware">&safeb.blocked.malwarePage.shortDesc;</p>
           <p id="errorShortDescText_unwanted">&safeb.blocked.unwantedPage.shortDesc;</p>
           <p id="errorShortDescText_forbidden">&safeb.blocked.forbiddenPage.shortDesc2;</p>
         </div>
 
         <!-- Long Description -->
         <div id="errorLongDesc">
           <p id="errorLongDescText_phishing">&safeb.blocked.phishingPage.longDesc2;</p>
           <p id="errorLongDescText_malware">&safeb.blocked.malwarePage.longDesc;</p>
           <p id="errorLongDescText_unwanted">&safeb.blocked.unwantedPage.longDesc;</p>
         </div>
-        
+
         <!-- Action buttons -->
-        <div id="buttons">
+        <div id="buttons" class="button-container">
           <!-- Commands handled in browser.js -->
-          <button id="getMeOutButton">&safeb.palm.accept.label;</button>
+          <button id="getMeOutButton" class="primary">&safeb.palm.accept.label;</button>
+          <div class="button-spacer"></div>
           <button id="reportButton">&safeb.palm.reportPage.label;</button>
           <button id="whyForbiddenButton">&safeb.palm.whyForbidden.label;</button>
         </div>
       </div>
       <div id="ignoreWarning">
         <button id="ignoreWarningButton">&safeb.palm.decline.label;</button>
       </div>
     </div>
--- a/browser/base/content/browser.css
+++ b/browser/base/content/browser.css
@@ -989,26 +989,26 @@ chatbar[customSize] > .chatbar-innerbox 
   -moz-binding: url("chrome://browser/content/downloads/download.xml#download-toolbarbutton");
 }
 
 /*** Visibility of downloads indicator controls ***/
 
 /* Bug 924050: If we've loaded the indicator, for now we hide it in the menu panel,
    and just show the icon. This is a hack to side-step very weird layout bugs that
    seem to be caused by the indicator stack interacting with the menu panel. */
-#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > image.toolbarbutton-icon,
+#downloads-button[indicator]:not([cui-areatype="menu-panel"]) > .toolbarbutton-badge-stack > image.toolbarbutton-icon,
 #downloads-button[indicator][cui-areatype="menu-panel"] > #downloads-indicator-anchor {
   display: none;
 }
 
-toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > image.toolbarbutton-icon {
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > .toolbarbutton-badge-stack > image.toolbarbutton-icon {
   display: -moz-box;
 }
 
-toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > stack.toolbarbutton-icon {
+toolbarpaletteitem[place="palette"] > #downloads-button[indicator] > #downloads-indicator-anchor {
   display: none;
 }
 
 #downloads-button:-moz-any([progress], [counter], [paused]) #downloads-indicator-icon,
 #downloads-button:not(:-moz-any([progress], [counter], [paused]))
                                                    #downloads-indicator-progress-area
 {
   visibility: hidden;
@@ -1021,17 +1021,17 @@ toolbar[mode="text"] > #downloads-button
   -moz-box-orient: vertical;
   -moz-box-pack: center;
 }
 
 toolbar[mode="text"] > #downloads-button > .toolbarbutton-text {
   -moz-box-ordinal-group: 1;
 }
 
-toolbar[mode="text"] > #downloads-button > .toolbarbutton-icon {
+toolbar[mode="text"] > #downloads-button > .toolbarbutton-badge-stack > .toolbarbutton-icon {
   display: -moz-box;
   -moz-box-ordinal-group: 2;
   visibility: collapse;
 }
 
 /* hide chat chrome when chat is fullscreen */
 #chat-window[sizemode="fullscreen"] chatbox > .chat-titlebar {
   display: none;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -2492,19 +2492,21 @@ function SetPageProxyState(aState)
 function PageProxyClickHandler(aEvent)
 {
   if (aEvent.button == 1 && gPrefService.getBoolPref("middlemouse.paste"))
     middleMousePaste(aEvent);
 }
 
 var gMenuButtonBadgeManager = {
   BADGEID_APPUPDATE: "update",
+  BADGEID_DOWNLOAD: "download",
   BADGEID_FXA: "fxa",
 
   fxaBadge: null,
+  downloadBadge: null,
   appUpdateBadge: null,
 
   init: function () {
     PanelUI.panel.addEventListener("popupshowing", this, true);
   },
 
   uninit: function () {
     PanelUI.panel.removeEventListener("popupshowing", this, true);
@@ -2512,32 +2514,34 @@ var gMenuButtonBadgeManager = {
 
   handleEvent: function (e) {
     if (e.type === "popupshowing") {
       this.clearBadges();
     }
   },
 
   _showBadge: function () {
-    let badgeToShow = this.appUpdateBadge || this.fxaBadge;
+    let badgeToShow = this.downloadBadge || this.appUpdateBadge || this.fxaBadge;
 
     if (badgeToShow) {
       PanelUI.menuButton.setAttribute("badge-status", badgeToShow);
     } else {
       PanelUI.menuButton.removeAttribute("badge-status");
     }
   },
 
   _changeBadge: function (badgeId, badgeStatus = null) {
     if (badgeId == this.BADGEID_APPUPDATE) {
       this.appUpdateBadge = badgeStatus;
+    } else if (badgeId == this.BADGEID_DOWNLOAD) {
+      this.downloadBadge = badgeStatus;
     } else if (badgeId == this.BADGEID_FXA) {
       this.fxaBadge = badgeStatus;
     } else {
-      Cu.reportError("This badge ID is unknown!");
+      Cu.reportError("The badge ID '" + badgeId + "' is unknown!");
     }
     this._showBadge();
   },
 
   addBadge: function (badgeId, badgeStatus) {
     if (!badgeStatus) {
       Cu.reportError("badgeStatus must be defined");
       return;
@@ -2546,16 +2550,17 @@ var gMenuButtonBadgeManager = {
   },
 
   removeBadge: function (badgeId) {
     this._changeBadge(badgeId);
   },
 
   clearBadges: function () {
     this.appUpdateBadge = null;
+    this.downloadBadge = null;
     this.fxaBadge = null;
     this._showBadge();
   }
 };
 
 // Setup the hamburger button badges for updates, if enabled.
 var gMenuButtonUpdateBadge = {
   enabled: false,
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -895,17 +895,18 @@
                       command="Browser:ShowAllBookmarks"
                       key="manBookmarkKb"/>
           </menupopup>
         </toolbarbutton>
 
         <!-- This is a placeholder for the Downloads Indicator.  It is visible
              during the customization of the toolbar, in the palette, and before
              the Downloads Indicator overlay is loaded. -->
-        <toolbarbutton id="downloads-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
+        <toolbarbutton id="downloads-button"
+                       class="toolbarbutton-1 chromeclass-toolbar-additional badged-button"
                        key="key_openDownloads"
                        oncommand="DownloadsIndicatorView.onCommand(event);"
                        ondrop="DownloadsIndicatorView.onDrop(event);"
                        ondragover="DownloadsIndicatorView.onDragOver(event);"
                        ondragenter="DownloadsIndicatorView.onDragOver(event);"
                        label="&downloads.label;"
                        removable="true"
                        cui-areatype="toolbar"
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -3259,18 +3259,18 @@
 
             // Auxilliary state variables:
 
             visibleTab: this.selectedTab,   // Tab that's on screen.
             spinnerTab: null,               // Tab showing a spinner.
             originalTab: this.selectedTab,  // Tab that we started on.
 
             tabbrowser: this,  // Reference to gBrowser.
-            loadTimer: null,   // TAB_SWITCH_TIMEOUT timer.
-            unloadTimer: null, // UNLOAD_DELAY timer.
+            loadTimer: null,   // TAB_SWITCH_TIMEOUT nsITimer instance.
+            unloadTimer: null, // UNLOAD_DELAY nsITimer instance.
 
             // Map from tabs to STATE_* (below).
             tabState: new Map(),
 
             // True if we're in the midst of switching tabs.
             switchInProgress: false,
 
             // Keep an exact list of content processes (tabParent) in which
@@ -3288,16 +3288,34 @@
             STATE_UNLOADED: 0,
             STATE_LOADING: 1,
             STATE_LOADED: 2,
             STATE_UNLOADING: 3,
 
             // re-entrancy guard:
             _processing: false,
 
+            // Wraps nsITimer. Must not use the vanilla setTimeout and
+            // clearTimeout, because they will be blocked by nsIPromptService
+            // dialogs.
+            setTimer: function(callback, timeout) {
+              let event = {
+                notify: callback
+              };
+
+              var timer = Cc["@mozilla.org/timer;1"]
+                .createInstance(Components.interfaces.nsITimer);
+              timer.initWithCallback(event, timeout, Ci.nsITimer.TYPE_ONE_SHOT);
+              return timer;
+            },
+
+            clearTimer: function(timer) {
+              timer.cancel();
+            },
+
             getTabState: function(tab) {
               let state = this.tabState.get(tab);
               if (state === undefined) {
                 return this.STATE_UNLOADED;
               }
               return state;
             },
 
@@ -3327,18 +3345,24 @@
               window.addEventListener("MozAfterPaint", this);
               window.addEventListener("MozLayerTreeReady", this);
               window.addEventListener("MozLayerTreeCleared", this);
               window.addEventListener("TabRemotenessChange", this);
               this.setTabState(this.requestedTab, this.STATE_LOADED);
             },
 
             destroy: function() {
-              clearTimeout(this.unloadTimer);
-              clearTimeout(this.loadTimer);
+              if (this.unloadTimer) {
+                this.clearTimer(this.unloadTimer);
+                this.unloadTimer = null;
+              }
+              if (this.loadTimer) {
+                this.clearTimer(this.loadTimer);
+                this.loadTimer = null;
+              }
 
               window.removeEventListener("MozAfterPaint", this);
               window.removeEventListener("MozLayerTreeReady", this);
               window.removeEventListener("MozLayerTreeCleared", this);
               window.removeEventListener("TabRemotenessChange", this);
 
               this.tabbrowser._switcher = null;
 
@@ -3454,17 +3478,17 @@
               this.assert(!this.loadTimer);
 
               // loadingTab can be non-null here if we timed out loading the current tab.
               // In that case we just overwrite it with a different tab; it's had its chance.
               this.loadingTab = this.requestedTab;
               this.log("Loading tab " + this.tinfo(this.loadingTab));
 
               this.setTabState(this.requestedTab, this.STATE_LOADING);
-              this.loadTimer = setTimeout(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
+              this.loadTimer = this.setTimer(() => this.onLoadTimeout(), this.TAB_SWITCH_TIMEOUT);
             },
 
             // This function runs before every event. It fixes up the state
             // to account for closed tabs.
             preActions: function() {
               this.assert(this.tabbrowser._switcher);
               this.assert(this.tabbrowser._switcher === this);
 
@@ -3478,17 +3502,17 @@
                 this.lastVisibleTab = null;
               }
               if (this.spinnerTab && !this.spinnerTab.linkedBrowser) {
                 this.spinnerHidden();
                 this.spinnerTab = null;
               }
               if (this.loadingTab && !this.loadingTab.linkedBrowser) {
                 this.loadingTab = null;
-                clearTimeout(this.loadTimer);
+                this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
               }
             },
 
             // This code runs after we've responded to an event or requested a new
             // tab. It's expected that we've already updated all the principal
             // state variables. This function takes care of updating any auxilliary
             // state.
@@ -3533,16 +3557,17 @@
               }
 
               this.logState("done");
             },
 
             // Fires when we're ready to unload unused tabs.
             onUnloadTimeout: function() {
               this.logState("onUnloadTimeout");
+              this.unloadTimer = null;
               this.preActions();
 
               let numPending = 0;
 
               // Unload any tabs that can be unloaded.
               for (let [tab, state] of this.tabState) {
                 if (state == this.STATE_LOADED &&
                     !this.maybeVisibleTabs.has(tab) &&
@@ -3555,17 +3580,17 @@
 
                 if (state != this.STATE_UNLOADED && tab !== this.requestedTab) {
                   numPending++;
                 }
               }
 
               if (numPending) {
                 // Keep the timer going since there may be more tabs to unload.
-                this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+                this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
               }
 
               this.postActions();
             },
 
             // Fires when an ongoing load has taken too long.
             onLoadTimeout: function() {
               this.logState("onLoadTimeout");
@@ -3580,17 +3605,17 @@
               this.logState("onLayersReady");
 
               let tab = this.tabbrowser.getTabForBrowser(browser);
               this.setTabState(tab, this.STATE_LOADED);
 
               this.maybeFinishTabSwitch();
 
               if (this.loadingTab === tab) {
-                clearTimeout(this.loadTimer);
+                this.clearTimer(this.loadTimer);
                 this.loadTimer = null;
                 this.loadingTab = null;
               }
             },
 
             // Fires when we paint the screen. Any tab switches we initiated
             // previously are done, so there's no need to keep the old layers
             // around.
@@ -3648,25 +3673,27 @@
               let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
               if (fl && fl.tabParent && !this.activeSuppressDisplayport.has(fl.tabParent)) {
                 fl.tabParent.suppressDisplayport(true);
                 this.activeSuppressDisplayport.add(fl.tabParent);
               }
 
               this.preActions();
 
-              clearTimeout(this.unloadTimer);
-              this.unloadTimer = setTimeout(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
+              if (this.unloadTimer) {
+                this.clearTimer(this.unloadTimer);
+              }
+              this.unloadTimer = this.setTimer(() => this.onUnloadTimeout(), this.UNLOAD_DELAY);
 
               this.postActions();
             },
 
             handleEvent: function(event, delayed = false) {
               if (this._processing) {
-                setTimeout(() => this.handleEvent(event, true), 0);
+                this.setTimer(() => this.handleEvent(event, true), 0);
                 return;
               }
               if (delayed && this.tabbrowser._switcher != this) {
                 // if we delayed processing this event, we might be out of date, in which
                 // case we drop the delayed events
                 return;
               }
               this._processing = true;
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -1,17 +1,16 @@
 [DEFAULT]
 skip-if = (os == "win" && debug && e10s) # Bug 1253956
 support-files =
   POSTSearchEngine.xml
   accounts_testRemoteCommands.html
   alltabslistener.html
   app_bug575561.html
   app_subframe_bug575561.html
-  authenticate.sjs
   aboutHome_content_script.js
   audio.ogg
   browser_bug479408_sample.html
   browser_bug678392-1.html
   browser_bug678392-2.html
   browser_bug970746.xhtml
   browser_fxa_oauth.html
   browser_fxa_oauth_with_keys.html
@@ -81,30 +80,28 @@ support-files =
   offlineQuotaNotification.cacheManifest
   offlineQuotaNotification.html
   page_style_sample.html
   parsingTestHelpers.jsm
   pinning_headers.sjs
   ssl_error_reports.sjs
   popup_blocker.html
   print_postdata.sjs
-  redirect_bug623155.sjs
   searchSuggestionEngine.sjs
   searchSuggestionEngine.xml
   searchSuggestionEngine2.xml
   subtst_contextmenu.html
   subtst_contextmenu_input.html
   test-mixedcontent-securityerrors.html
   test_bug435035.html
   test_bug462673.html
   test_bug628179.html
   test_bug839103.html
   test_bug959531.html
   test_process_flags_chrome.html
-  test_wyciwyg_copying.html
   title_test.svg
   unknownContentType_file.pif
   unknownContentType_file.pif^headers^
   video.ogg
   web_video.html
   web_video1.ogv
   web_video1.ogv^headers^
   zoom_test.html
@@ -132,53 +129,37 @@ support-files =
   !/toolkit/mozapps/extensions/test/xpinstall/corrupt.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/incompatible.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/installtrigger.html
   !/toolkit/mozapps/extensions/test/xpinstall/redirect.sjs
   !/toolkit/mozapps/extensions/test/xpinstall/restartless-unsigned.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/restartless.xpi
   !/toolkit/mozapps/extensions/test/xpinstall/theme.xpi
 
-[browser_URLBarSetURI.js]
-skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053
 [browser_aboutAccounts.js]
 skip-if = os == "linux" # Bug 958026
 support-files =
   content_aboutAccounts.js
 [browser_aboutCertError.js]
 [browser_aboutSupport_newtab_security_state.js]
 [browser_aboutHealthReport.js]
 skip-if = os == "linux" # Bug 924307
 [browser_aboutHome.js]
 [browser_aboutHome_wrapsCorrectly.js]
-[browser_action_keyword.js]
-[browser_action_keyword_override.js]
-[browser_action_searchengine.js]
-[browser_action_searchengine_alias.js]
 [browser_addKeywordSearch.js]
-[browser_search_favicon.js]
 [browser_alltabslistener.js]
 [browser_audioTabIcon.js]
-[browser_autocomplete_a11y_label.js]
-[browser_autocomplete_cursor.js]
-[browser_autocomplete_edit_completed.js]
-[browser_autocomplete_enter_race.js]
-[browser_autocomplete_no_title.js]
-[browser_autocomplete_autoselect.js]
-[browser_autocomplete_oldschool_wrap.js]
-[browser_autocomplete_tag_star_visibility.js]
 [browser_backButtonFitts.js]
 skip-if = os == "mac" # The Fitt's Law back button is not supported on OS X
 [browser_beforeunload_duplicate_dialogs.js]
 [browser_blob-channelname.js]
 [browser_bookmark_popup.js]
 skip-if = (os == "linux" && debug) # mouseover not reliable on linux debug builds
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" # Disabled on Windows due to frequent failures (bugs 825739, 841341)
-[browser_bug304198.js]
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug356571.js]
 [browser_bug380960.js]
 [browser_bug386835.js]
 [browser_bug406216.js]
 [browser_bug408415.js]
 [browser_bug409481.js]
@@ -221,20 +202,18 @@ skip-if = (os == 'linux' && e10s) # bug 
 [browser_bug537013.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1134458 - Find bar doesn't work correctly in a detached tab
 [browser_bug537474.js]
 [browser_bug550565.js]
 [browser_bug553455.js]
 skip-if = buildapp == 'mulet' # Bug 1066070 - I don't think either popup notifications nor addon install stuff works on mulet?
 [browser_bug555224.js]
 [browser_bug555767.js]
-[browser_bug556061.js]
 [browser_bug559991.js]
 [browser_bug561636.js]
-[browser_bug562649.js]
 [browser_bug563588.js]
 [browser_bug565575.js]
 [browser_bug565667.js]
 skip-if = toolkit != "cocoa"
 [browser_bug567306.js]
 [browser_bug575561.js]
 [browser_bug575830.js]
 [browser_bug577121.js]
@@ -249,17 +228,16 @@ skip-if = toolkit != "cocoa"
 [browser_bug585830.js]
 [browser_bug590206.js]
 [browser_bug592338.js]
 [browser_bug594131.js]
 [browser_bug595507.js]
 [browser_bug596687.js]
 [browser_bug597218.js]
 [browser_bug609700.js]
-[browser_bug623155.js]
 [browser_bug623893.js]
 [browser_bug624734.js]
 [browser_bug633691.js]
 [browser_bug647886.js]
 skip-if = buildapp == 'mulet'
 [browser_bug655584.js]
 [browser_bug664672.js]
 [browser_bug676619.js]
@@ -269,17 +247,16 @@ skip-if = os == "mac" # Bug 1102331 - do
 [browser_bug710878.js]
 [browser_bug719271.js]
 [browser_bug724239.js]
 [browser_bug734076.js]
 [browser_bug735471.js]
 [browser_bug749738.js]
 [browser_bug763468_perwindowpb.js]
 [browser_bug767836_perwindowpb.js]
-[browser_bug783614.js]
 [browser_bug817947.js]
 [browser_bug822367.js]
 tags = mcb
 [browser_bug832435.js]
 [browser_bug839103.js]
 [browser_bug880101.js]
 [browser_bug882977.js]
 [browser_bug902156.js]
@@ -290,19 +267,17 @@ skip-if = buildapp == "mulet" || e10s # 
 [browser_mixedContentFromOnunload.js]
 tags = mcb
 [browser_mixedContentFramesOnHttp.js]
 tags = mcb
 [browser_bug970746.js]
 [browser_bug1015721.js]
 skip-if = os == 'win'
 [browser_bug1064280_changeUrlInPinnedTab.js]
-[browser_bug1070778.js]
 [browser_accesskeys.js]
-[browser_canonizeURL.js]
 [browser_clipboard.js]
 [browser_contentAreaClick.js]
 [browser_contextmenu.js]
 tags = fullscreen
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_contextmenu_input.js]
 skip-if = toolkit == "gtk2" || toolkit == "gtk3" # disabled on Linux due to bug 513558
 [browser_ctrlTab.js]
@@ -341,19 +316,16 @@ skip-if = buildapp == 'mulet'
 [browser_identity_UI.js]
 [browser_insecureLoginForms.js]
 [browser_invalid_uri_back_forward_manipulation.js]
 [browser_keywordBookmarklets.js]
 [browser_keywordSearch.js]
 [browser_keywordSearch_postData.js]
 [browser_lastAccessedTab.js]
 skip-if = toolkit == "windows" # Disabled on Windows due to frequent failures (bug 969405)
-[browser_locationBarCommand.js]
-skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
-[browser_locationBarExternalLoad.js]
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 [browser_minimize.js]
 [browser_misused_characters_in_strings.js]
 [browser_mixedcontent_securityflags.js]
 tags = mcb
 [browser_offlineQuotaNotification.js]
@@ -395,17 +367,16 @@ support-files =
   refresh_header.sjs
   refresh_meta.sjs
 [browser_relatedTabs.js]
 [browser_remoteTroubleshoot.js]
 support-files =
   test_remoteTroubleshoot.html
 [browser_remoteWebNavigation_postdata.js]
 [browser_removeTabsToTheEnd.js]
-[browser_removeUnsafeProtocolsFromURLBarPaste.js]
 [browser_restore_isAppTab.js]
 [browser_sanitize-passwordDisabledHosts.js]
 [browser_sanitize-sitepermissions.js]
 [browser_sanitize-timespans.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
 [browser_save_link-perwindowpb.js]
@@ -429,19 +400,16 @@ run-if = e10s
 [browser_star_hsts.js]
 [browser_subframe_favicons_not_used.js]
 [browser_syncui.js]
 [browser_tab_close_dependent_window.js]
 [browser_tabDrop.js]
 skip-if = buildapp == 'mulet'
 [browser_tabReorder.js]
 skip-if = buildapp == 'mulet'
-[browser_tabMatchesInAwesomebar.js]
-[browser_tabMatchesInAwesomebar_perwindowpb.js]
-skip-if = os == 'linux' # Bug 1104755
 [browser_tab_detach_restore.js]
 [browser_tab_drag_drop_perwindow.js]
 skip-if = buildapp == 'mulet'
 [browser_tab_dragdrop.js]
 skip-if = buildapp == 'mulet' || (e10s && debug) # Bug 1150036: In e10s, content process crashes, main process leaks!
 [browser_tab_dragdrop2.js]
 skip-if = buildapp == 'mulet'
 [browser_tabbar_big_widgets.js]
@@ -489,60 +457,37 @@ support-files =
 [browser_trackingUI_telemetry.js]
 tags = trackingprotection
 support-files =
   trackingPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet'
 [browser_unknownContentType_title.js]
 [browser_unloaddialogs.js]
-[browser_urlHighlight.js]
-[browser_urlbarAutoFillTrimURLs.js]
-[browser_urlbarCopying.js]
-[browser_urlbarDecode.js]
-[browser_urlbarDelete.js]
-[browser_urlbarEnter.js]
-[browser_urlbarEnterAfterMouseOver.js]
-skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
-[browser_urlbarRevert.js]
-[browser_urlbarSearchSingleWordNotification.js]
-[browser_urlbarSearchSuggestions.js]
-[browser_urlbarSearchSuggestionsNotification.js]
-[browser_urlbarSearchTelemetry.js]
-[browser_urlbarStop.js]
-[browser_urlbarTrimURLs.js]
-[browser_urlbar_autoFill_backspaced.js]
-[browser_urlbar_searchsettings.js]
 [browser_utilityOverlay.js]
 [browser_viewSourceInTabOnViewSource.js]
 [browser_visibleFindSelection.js]
 [browser_visibleTabs.js]
 [browser_visibleTabs_bookmarkAllPages.js]
 skip-if = true # Bug 1005420 - fails intermittently. also with e10s enabled: bizarre problem with hidden tab having _mouseenter called, via _setPositionalAttributes, and tab not being found resulting in 'candidate is undefined'
 [browser_visibleTabs_bookmarkAllTabs.js]
 [browser_visibleTabs_contextMenu.js]
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug)
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 skip-if = buildapp == 'mulet'
-[browser_wyciwyg_urlbarCopying.js]
 [browser_zbug569342.js]
 skip-if = e10s # Bug 1094240 - has findbar-related failures
 [browser_registerProtocolHandler_notification.js]
 [browser_no_mcb_on_http_site.js]
 tags = mcb
-[browser_bug1104165-switchtab-decodeuri.js]
-[browser_bug1003461-switchtab-override.js]
-[browser_bug1024133-switchtab-override-keynav.js]
-[browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
 [browser_addCertException.js]
 [browser_bug1045809.js]
 tags = mcb
-[browser_bug1225194-remotetab.js]
 [browser_e10s_switchbrowser.js]
 [browser_e10s_about_process.js]
 [browser_e10s_chrome_process.js]
 [browser_e10s_javascript.js]
 [browser_blockHPKP.js]
 tags = psm
 [browser_mcb_redirect.js]
 tags = mcb
--- a/browser/base/content/test/general/browser_menuButtonBadgeManager.js
+++ b/browser/base/content/test/general/browser_menuButtonBadgeManager.js
@@ -12,26 +12,35 @@ add_task(function* testButtonActivities(
   is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
   is(menuButton.getAttribute("badge-status"), "update-succeeded", "Should have update-succeeded badge status (update > fxa)");
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-failed");
   is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-severe");
+  is(menuButton.getAttribute("badge-status"), "download-severe", "Should have download-severe badge status");
+
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, "download-warning");
+  is(menuButton.getAttribute("badge-status"), "download-warning", "Should have download-warning badge status");
+
   gMenuButtonBadgeManager.addBadge("unknownbadge", "attr");
-  is(menuButton.getAttribute("badge-status"), "update-failed", "Should not have changed badge status");
+  is(menuButton.getAttribute("badge-status"), "download-warning", "Should not have changed badge status");
+
+  gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+  is(menuButton.getAttribute("badge-status"), "update-failed", "Should have update-failed badge status");
 
   gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE);
   is(menuButton.getAttribute("badge-status"), "fxa-needs-authentication", "Should have fxa-needs-authentication badge status");
 
   gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_FXA);
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status");
 
   yield PanelUI.show();
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (Hamburger menu opened)");
   PanelUI.hide();
 
   gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_FXA, "fxa-needs-authentication");
-  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_UPDATE, "update-succeeded");
+  gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_APPUPDATE, "update-succeeded");
   gMenuButtonBadgeManager.clearBadges();
   is(menuButton.hasAttribute("badge-status"), false, "Should not have a badge status (clearBadges called)");
 });
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -931,25 +931,16 @@ function assertMixedContentBlockingState
 
   // Wait for the panel to be closed before continuing. The promisePopupHidden
   // function cannot be used because it's unreliable unless promisePopupShown is
   // also called before closing the panel. This cannot be done until all callers
   // are made asynchronous (bug 1221114).
   return new Promise(resolve => executeSoon(resolve));
 }
 
-function makeActionURI(action, params) {
-  let encodedParams = {};
-  for (let key in params) {
-    encodedParams[key] = encodeURIComponent(params[key]);
-  }
-  let url = "moz-action:" + action + "," + JSON.stringify(encodedParams);
-  return NetUtil.newURI(url);
-}
-
 function is_hidden(element) {
   var style = element.ownerDocument.defaultView.getComputedStyle(element, "");
   if (style.display == "none")
     return true;
   if (style.visibility != "visible")
     return true;
   if (style.display == "-moz-popup")
     return ["hiding","closed"].indexOf(element.state) != -1;
@@ -1016,38 +1007,16 @@ function promiseNotificationShown(notifi
   if (win.PopupNotifications.panel.state == "open") {
     return Promise.resolved();
   }
   let panelPromise = promisePopupShown(win.PopupNotifications.panel);
   notification.reshow();
   return panelPromise;
 }
 
-function promiseSearchComplete(win = window) {
-  return promisePopupShown(win.gURLBar.popup).then(() => {
-    function searchIsComplete() {
-      return win.gURLBar.controller.searchStatus >=
-        Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
-    }
-
-    // Wait until there are at least two matches.
-    return new Promise(resolve => waitForCondition(searchIsComplete, resolve));
-  });
-}
-
-function promiseAutocompleteResultPopup(inputText, win = window) {
-  waitForFocus(() => {
-    win.gURLBar.focus();
-    win.gURLBar.value = inputText;
-    win.gURLBar.controller.startSearch(inputText);
-  }, win);
-
-  return promiseSearchComplete(win);
-}
-
 /**
  * Allows waiting for an observer notification once.
  *
  * @param aTopic
  *        Notification topic to observe.
  *
  * @return {Promise}
  * @resolves An object with subject and data properties from the observed
rename from browser/base/content/test/general/authenticate.sjs
rename to browser/base/content/test/urlbar/authenticate.sjs
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -1,13 +1,86 @@
+[DEFAULT]
+support-files =
+  dummy_page.html
+  head.js
+
+[browser_URLBarSetURI.js]
+skip-if = (os == "linux" || os == "mac") && debug # bug 970052, bug 970053
+[browser_action_keyword.js]
+[browser_action_keyword_override.js]
+[browser_action_searchengine.js]
+[browser_action_searchengine_alias.js]
+[browser_autocomplete_a11y_label.js]
+[browser_autocomplete_autoselect.js]
+[browser_autocomplete_cursor.js]
+[browser_autocomplete_edit_completed.js]
+[browser_autocomplete_enter_race.js]
+[browser_autocomplete_no_title.js]
+[browser_autocomplete_oldschool_wrap.js]
+[browser_autocomplete_tag_star_visibility.js]
+[browser_bug1104165-switchtab-decodeuri.js]
+[browser_bug1003461-switchtab-override.js]
+[browser_bug1024133-switchtab-override-keynav.js]
+[browser_bug1025195_switchToTabHavingURI_aOpenParams.js]
+[browser_bug1070778.js]
+[browser_bug1225194-remotetab.js]
+[browser_bug304198.js]
+[browser_bug556061.js]
+[browser_bug562649.js]
+[browser_bug623155.js]
+support-files =
+  redirect_bug623155.sjs
+[browser_bug783614.js]
+[browser_canonizeURL.js]
+[browser_locationBarCommand.js]
+skip-if = os == "linux" # Linux: Intermittent failures, bug 917535
+[browser_locationBarExternalLoad.js]
 [browser_moz_action_link.js]
+[browser_removeUnsafeProtocolsFromURLBarPaste.js]
+[browser_search_favicon.js]
+[browser_tabMatchesInAwesomebar.js]
+support-files =
+  moz.png
+[browser_tabMatchesInAwesomebar_perwindowpb.js]
+skip-if = os == 'linux' # Bug 1104755
+[browser_urlbarAutoFillTrimURLs.js]
+[browser_urlbarCopying.js]
+support-files =
+  authenticate.sjs
+[browser_urlbarDecode.js]
+[browser_urlbarDelete.js]
+[browser_urlbarEnter.js]
+[browser_urlbarEnterAfterMouseOver.js]
+skip-if = os == "linux" # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
+[browser_urlbarRevert.js]
+[browser_urlbarSearchSingleWordNotification.js]
+[browser_urlbarSearchSuggestions.js]
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
+[browser_urlbarSearchSuggestionsNotification.js]
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
+[browser_urlbarSearchTelemetry.js]
+support-files =
+  searchSuggestionEngine.xml
+  searchSuggestionEngine.sjs
+[browser_urlbarStop.js]
+[browser_urlbarTrimURLs.js]
+[browser_urlbar_autoFill_backspaced.js]
 [browser_urlbar_blanking.js]
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
+[browser_urlbar_searchsettings.js]
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
 [browser_urlbar_remoteness_switch.js]
 run-if = e10s
-
+[browser_urlHighlight.js]
+[browser_wyciwyg_urlbarCopying.js]
+support-files =
+  test_wyciwyg_copying.html
rename from browser/base/content/test/general/browser_URLBarSetURI.js
rename to browser/base/content/test/urlbar/browser_URLBarSetURI.js
--- a/browser/base/content/test/general/browser_URLBarSetURI.js
+++ b/browser/base/content/test/urlbar/browser_URLBarSetURI.js
@@ -32,29 +32,27 @@ var tests = [
     loadTabInWindow(window, function (tab) {
       gURLBar.handleRevert();
       is(gURLBar.textValue, "example.com", "URL bar had user/pass stripped after reverting");
       gBrowser.removeTab(tab);
       next();
     });
   },
   function customize(next) {
-    whenNewWindowLoaded(undefined, function (win) {
-      // Need to wait for delayedStartup for the customization part of the test,
-      // since that's where BrowserToolboxCustomizeDone is set.
-      whenDelayedStartupFinished(win, function () {
-        loadTabInWindow(win, function () {
-          openToolbarCustomizationUI(function () {
-            closeToolbarCustomizationUI(function () {
-              is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped after customize");
-              win.close();
-              next();
-            }, win);
+    // Need to wait for delayedStartup for the customization part of the test,
+    // since that's where BrowserToolboxCustomizeDone is set.
+    BrowserTestUtils.openNewBrowserWindow().then(function(win) {
+      loadTabInWindow(win, function () {
+        openToolbarCustomizationUI(function () {
+          closeToolbarCustomizationUI(function () {
+            is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped after customize");
+            win.close();
+            next();
           }, win);
-        });
+        }, win);
       });
     });
   },
   function pageloaderror(next) {
     loadTabInWindow(window, function (tab) {
       // Load a new URL and then immediately stop it, to simulate a page load
       // error.
       tab.linkedBrowser.loadURI("http://test1.example.com");
@@ -71,8 +69,32 @@ function loadTabInWindow(win, callback) 
   let url = "http://user:pass@example.com/";
   let tab = win.gBrowser.selectedTab = win.gBrowser.addTab(url);
   BrowserTestUtils.browserLoaded(tab.linkedBrowser, false, url).then(() => {
     info("Tab loaded");
     is(win.gURLBar.textValue, "example.com", "URL bar had user/pass stripped initially");
     callback(tab);
   }, true);
 }
+
+function openToolbarCustomizationUI(aCallback, aBrowserWin) {
+  if (!aBrowserWin)
+    aBrowserWin = window;
+
+  aBrowserWin.gCustomizeMode.enter();
+
+  aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
+    aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
+    executeSoon(function() {
+      aCallback(aBrowserWin)
+    });
+  });
+}
+
+function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
+  aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
+    aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
+    executeSoon(aCallback);
+  });
+
+  aBrowserWin.gCustomizeMode.exit();
+}
+
rename from browser/base/content/test/general/browser_action_keyword.js
rename to browser/base/content/test/urlbar/browser_action_keyword.js
--- a/browser/base/content/test/general/browser_action_keyword.js
+++ b/browser/base/content/test/urlbar/browser_action_keyword.js
@@ -73,16 +73,16 @@ add_task(function*() {
   info("Middle-click on result");
   result = yield promise_first_result("keyword somethingmore");
   isnot(result, null, "Expect a keyword result");
   // We need to make a real URI out of this to ensure it's normalised for
   // comparison.
   uri = NetUtil.newURI(result.getAttribute("url"));
   is(uri.spec, makeActionURI("keyword", {url: "http://example.com/?q=somethingmore", input: "keyword somethingmore"}).spec, "Expect correct url");
 
-  tabPromise = promiseWaitForEvent(gBrowser.tabContainer, "TabOpen");
+  tabPromise = BrowserTestUtils.waitForEvent(gBrowser.tabContainer, "TabOpen");
   EventUtils.synthesizeMouseAtCenter(result, {button: 1});
   let tabOpenEvent = yield tabPromise;
   let newTab = tabOpenEvent.target;
   tabs.push(newTab);
   yield promiseTabLoadEvent(newTab);
   is(newTab.linkedBrowser.currentURI.spec, "http://example.com/?q=somethingmore", "Tab should have loaded from middle-clicking on result");
 });
rename from browser/base/content/test/general/browser_action_keyword_override.js
rename to browser/base/content/test/urlbar/browser_action_keyword_override.js
rename from browser/base/content/test/general/browser_action_searchengine.js
rename to browser/base/content/test/urlbar/browser_action_searchengine.js
rename from browser/base/content/test/general/browser_action_searchengine_alias.js
rename to browser/base/content/test/urlbar/browser_action_searchengine_alias.js
rename from browser/base/content/test/general/browser_autocomplete_a11y_label.js
rename to browser/base/content/test/urlbar/browser_autocomplete_a11y_label.js
rename from browser/base/content/test/general/browser_autocomplete_autoselect.js
rename to browser/base/content/test/urlbar/browser_autocomplete_autoselect.js
rename from browser/base/content/test/general/browser_autocomplete_cursor.js
rename to browser/base/content/test/urlbar/browser_autocomplete_cursor.js
rename from browser/base/content/test/general/browser_autocomplete_edit_completed.js
rename to browser/base/content/test/urlbar/browser_autocomplete_edit_completed.js
rename from browser/base/content/test/general/browser_autocomplete_enter_race.js
rename to browser/base/content/test/urlbar/browser_autocomplete_enter_race.js
rename from browser/base/content/test/general/browser_autocomplete_no_title.js
rename to browser/base/content/test/urlbar/browser_autocomplete_no_title.js
rename from browser/base/content/test/general/browser_autocomplete_oldschool_wrap.js
rename to browser/base/content/test/urlbar/browser_autocomplete_oldschool_wrap.js
rename from browser/base/content/test/general/browser_autocomplete_tag_star_visibility.js
rename to browser/base/content/test/urlbar/browser_autocomplete_tag_star_visibility.js
rename from browser/base/content/test/general/browser_bug1003461-switchtab-override.js
rename to browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js
--- a/browser/base/content/test/general/browser_bug1003461-switchtab-override.js
+++ b/browser/base/content/test/urlbar/browser_bug1003461-switchtab-override.js
@@ -5,17 +5,17 @@
 add_task(function* test_switchtab_override() {
   // This test is only relevant if UnifiedComplete is enabled.
   let ucpref = Services.prefs.getBoolPref("browser.urlbar.unifiedcomplete");
   Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", true);
   registerCleanupFunction(() => {
     Services.prefs.setBoolPref("browser.urlbar.unifiedcomplete", ucpref);
   });
 
-  let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+  let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
 
   info("Opening first tab");
   let tab = gBrowser.addTab(testURL);
   let deferred = Promise.defer();
   whenTabLoaded(tab, deferred.resolve);
   yield deferred.promise;
 
   info("Opening and selecting second tab");
rename from browser/base/content/test/general/browser_bug1024133-switchtab-override-keynav.js
rename to browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js
--- a/browser/base/content/test/general/browser_bug1024133-switchtab-override-keynav.js
+++ b/browser/base/content/test/urlbar/browser_bug1024133-switchtab-override-keynav.js
@@ -1,14 +1,14 @@
 /* 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/. */
 
 add_task(function* test_switchtab_override_keynav() {
-  let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+  let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
 
   info("Opening first tab");
   let tab = gBrowser.addTab(testURL);
   let tabLoadDeferred = Promise.defer();
   whenTabLoaded(tab, tabLoadDeferred.resolve);
   yield tabLoadDeferred.promise;
 
   info("Opening and selecting second tab");
rename from browser/base/content/test/general/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
rename to browser/base/content/test/urlbar/browser_bug1025195_switchToTabHavingURI_aOpenParams.js
rename from browser/base/content/test/general/browser_bug1070778.js
rename to browser/base/content/test/urlbar/browser_bug1070778.js
rename from browser/base/content/test/general/browser_bug1104165-switchtab-decodeuri.js
rename to browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
--- a/browser/base/content/test/general/browser_bug1104165-switchtab-decodeuri.js
+++ b/browser/base/content/test/urlbar/browser_bug1104165-switchtab-decodeuri.js
@@ -1,11 +1,11 @@
 add_task(function* test_switchtab_decodeuri() {
   info("Opening first tab");
-  let tab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/general/dummy_page.html#test%7C1");
+  let tab = gBrowser.addTab("http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#test%7C1");
   yield promiseTabLoadEvent(tab);
 
   info("Opening and selecting second tab");
   let newTab = gBrowser.selectedTab = gBrowser.addTab();
 
   info("Wait for autocomplete")
   yield promiseAutocompleteResultPopup("dummy_page");
 
rename from browser/base/content/test/general/browser_bug1225194-remotetab.js
rename to browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
--- a/browser/base/content/test/general/browser_bug1225194-remotetab.js
+++ b/browser/base/content/test/urlbar/browser_bug1225194-remotetab.js
@@ -1,10 +1,10 @@
 add_task(function* test_remotetab_opens() {
-  const url = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+  const url = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
   yield BrowserTestUtils.withNewTab({url: "about:robots", gBrowser}, function* () {
     // Set the urlbar to include the moz-action
     gURLBar.value = "moz-action:remotetab," + JSON.stringify({ url });
     // Focus the urlbar so we can press enter
     gURLBar.focus();
 
     // The URL is going to open in the current tab as it is currently about:blank
     let promiseTabLoaded = promiseTabLoadEvent(gBrowser.selectedTab);
rename from browser/base/content/test/general/browser_bug304198.js
rename to browser/base/content/test/urlbar/browser_bug304198.js
--- a/browser/base/content/test/general/browser_bug304198.js
+++ b/browser/base/content/test/urlbar/browser_bug304198.js
@@ -4,17 +4,17 @@
 
 add_task(function* () {
   let charsToDelete, deletedURLTab, fullURLTab, partialURLTab, testPartialURL, testURL;
 
   charsToDelete = 5;
   deletedURLTab = gBrowser.addTab();
   fullURLTab = gBrowser.addTab();
   partialURLTab = gBrowser.addTab();
-  testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+  testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
 
   let loaded1 = BrowserTestUtils.browserLoaded(deletedURLTab.linkedBrowser, testURL);
   let loaded2 = BrowserTestUtils.browserLoaded(fullURLTab.linkedBrowser, testURL);
   let loaded3 = BrowserTestUtils.browserLoaded(partialURLTab.linkedBrowser, testURL);
   deletedURLTab.linkedBrowser.loadURI(testURL);
   fullURLTab.linkedBrowser.loadURI(testURL);
   partialURLTab.linkedBrowser.loadURI(testURL);
   yield Promise.all([loaded1, loaded2, loaded3]);
rename from browser/base/content/test/general/browser_bug556061.js
rename to browser/base/content/test/urlbar/browser_bug556061.js
--- a/browser/base/content/test/general/browser_bug556061.js
+++ b/browser/base/content/test/urlbar/browser_bug556061.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+var testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
 var testActionURL = "moz-action:switchtab," + JSON.stringify({url: testURL});
 testURL = gURLBar.trimValue(testURL);
 var testTab;
 
 function runNextTest() {
   if (tests.length) {
     let t = tests.shift();
     waitForClipboard(t.expected, t.setup, function() {
rename from browser/base/content/test/general/browser_bug562649.js
rename to browser/base/content/test/urlbar/browser_bug562649.js
rename from browser/base/content/test/general/browser_bug623155.js
rename to browser/base/content/test/urlbar/browser_bug623155.js
--- a/browser/base/content/test/general/browser_bug623155.js
+++ b/browser/base/content/test/urlbar/browser_bug623155.js
@@ -1,12 +1,12 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/general/" +
+const REDIRECT_FROM = "https://example.com/browser/browser/base/content/test/urlbar/" +
                       "redirect_bug623155.sjs";
 
 const REDIRECT_TO = "https://www.bank1.com/"; // Bad-cert host.
 
 function isRedirectedURISpec(aURISpec) {
   return isRedirectedURI(Services.io.newURI(aURISpec, null, null));
 }
 
@@ -15,28 +15,28 @@ function isRedirectedURI(aURI) {
   return Services.io.newURI(REDIRECT_TO, null, null)
                  .equalsExceptRef(aURI);
 }
 
 /*
    Test.
 
 1. Load
-https://example.com/browser/browser/base/content/test/general/redirect_bug623155.sjs#BG
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#BG
    in a background tab.
 
 2. The redirected URI is <https://www.bank1.com/#BG>, which displayes a cert
    error page.
 
 3. Switch the tab to foreground.
 
 4. Check the URLbar's value, expecting <https://www.bank1.com/#BG>
 
 5. Load
-https://example.com/browser/browser/base/content/test/general/redirect_bug623155.sjs#FG
+https://example.com/browser/browser/base/content/test/urlbar/redirect_bug623155.sjs#FG
    in the foreground tab.
 
 6. The redirected URI is <https://www.bank1.com/#FG>. And this is also
    a cert-error page.
 
 7. Check the URLbar's value, expecting <https://www.bank1.com/#FG>
 
 8. End.
rename from browser/base/content/test/general/browser_bug783614.js
rename to browser/base/content/test/urlbar/browser_bug783614.js
rename from browser/base/content/test/general/browser_canonizeURL.js
rename to browser/base/content/test/urlbar/browser_canonizeURL.js
rename from browser/base/content/test/general/browser_locationBarCommand.js
rename to browser/base/content/test/urlbar/browser_locationBarCommand.js
--- a/browser/base/content/test/general/browser_locationBarCommand.js
+++ b/browser/base/content/test/urlbar/browser_locationBarCommand.js
@@ -30,17 +30,17 @@ add_task(function* alt_left_click_test()
   yield saveURLPromise;
   ok(true, "SaveURL was called");
   is(gURLBar.value, "", "Urlbar reverted to original value");
 });
 
 add_task(function* shift_left_click_test() {
   info("Running test: Shift left click");
 
-  let newWindowPromise = promiseWaitForNewWindow();
+  let newWindowPromise = BrowserTestUtils.waitForNewWindow();
   triggerCommand(true, {shiftKey: true});
   let win = yield newWindowPromise;
 
   // Wait for the initial browser to load.
   let browser = win.gBrowser.selectedBrowser;
   yield BrowserTestUtils.browserLoaded(browser);
 
   info("URL should be loaded in a new window");
@@ -194,35 +194,16 @@ function promiseNewTabSwitched() {
   return new Promise(resolve => {
     gBrowser.addEventListener("TabSwitchDone", function onSwitch() {
       gBrowser.removeEventListener("TabSwitchDone", onSwitch);
       executeSoon(resolve);
     });
   });
 }
 
-function promiseWaitForNewWindow() {
-  return new Promise(resolve => {
-    let listener = {
-      onOpenWindow(xulWindow) {
-        let win = xulWindow.QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindow);
-
-        Services.wm.removeListener(listener);
-        whenDelayedStartupFinished(win, () => resolve(win));
-      },
-
-      onCloseWindow() {},
-      onWindowTitleChange() {}
-    };
-
-    Services.wm.addListener(listener);
-  });
-}
-
 function promiseCheckChildNoFocusedElement(browser)
 {
   if (!gMultiProcessBrowser) {
     Assert.equal(Services.focus.focusedElement, null, "There should be no focused element");
     return;
   }
 
   return ContentTask.spawn(browser, { }, function* () {
rename from browser/base/content/test/general/browser_locationBarExternalLoad.js
rename to browser/base/content/test/urlbar/browser_locationBarExternalLoad.js
rename from browser/base/content/test/general/browser_removeUnsafeProtocolsFromURLBarPaste.js
rename to browser/base/content/test/urlbar/browser_removeUnsafeProtocolsFromURLBarPaste.js
rename from browser/base/content/test/general/browser_search_favicon.js
rename to browser/base/content/test/urlbar/browser_search_favicon.js
rename from browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
rename to browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
--- a/browser/base/content/test/general/browser_tabMatchesInAwesomebar.js
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar.js
@@ -2,18 +2,18 @@
  * vim:set ts=2 sw=2 sts=2 et:
  * 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/. */
 
 requestLongerTimeout(2);
 
 const TEST_URL_BASES = [
-  "http://example.org/browser/browser/base/content/test/general/dummy_page.html#tabmatch",
-  "http://example.org/browser/browser/base/content/test/general/moz.png#tabmatch"
+  "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html#tabmatch",
+  "http://example.org/browser/browser/base/content/test/urlbar/moz.png#tabmatch"
 ];
 
 var gController = Cc["@mozilla.org/autocomplete/controller;1"].
                   getService(Ci.nsIAutoCompleteController);
 
 var gTabWaitCount = 0;
 var gTabCounter = 0;
 
rename from browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
rename to browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
--- a/browser/base/content/test/general/browser_tabMatchesInAwesomebar_perwindowpb.js
+++ b/browser/base/content/test/urlbar/browser_tabMatchesInAwesomebar_perwindowpb.js
@@ -1,9 +1,9 @@
-let testURL = "http://example.org/browser/browser/base/content/test/general/dummy_page.html";
+let testURL = "http://example.org/browser/browser/base/content/test/urlbar/dummy_page.html";
 
 add_task(function*() {
   let normalWindow = yield BrowserTestUtils.openNewBrowserWindow();
   let privateWindow = yield BrowserTestUtils.openNewBrowserWindow({private: true});
   yield runTest(normalWindow, privateWindow, false);
   yield BrowserTestUtils.closeWindow(normalWindow);
   yield BrowserTestUtils.closeWindow(privateWindow);
 
@@ -22,17 +22,17 @@ add_task(function*() {
   yield BrowserTestUtils.closeWindow(normalWindow);
 });
 
 function* runTest(aSourceWindow, aDestWindow, aExpectSwitch, aCallback) {
   let baseTab = yield BrowserTestUtils.openNewForegroundTab(aSourceWindow.gBrowser, testURL);
   let testTab = yield BrowserTestUtils.openNewForegroundTab(aDestWindow.gBrowser);
 
   info("waiting for focus on the window");
-  yield promiseWaitForFocus(aDestWindow);
+  yield SimpleTest.promiseFocus(aDestWindow);
   info("got focus on the window");
 
   // Select the testTab
   aDestWindow.gBrowser.selectedTab = testTab;
 
   // Ensure that this tab has no history entries
   let sessionHistoryCount = yield new Promise(resolve => {
     SessionStore.getSessionHistory(gBrowser.selectedTab, function(sessionHistory) {
rename from browser/base/content/test/general/browser_urlHighlight.js
rename to browser/base/content/test/urlbar/browser_urlHighlight.js
rename from browser/base/content/test/general/browser_urlbarAutoFillTrimURLs.js
rename to browser/base/content/test/urlbar/browser_urlbarAutoFillTrimURLs.js
rename from browser/base/content/test/general/browser_urlbarCopying.js
rename to browser/base/content/test/urlbar/browser_urlbarCopying.js
--- a/browser/base/content/test/general/browser_urlbarCopying.js
+++ b/browser/base/content/test/urlbar/browser_urlbarCopying.js
@@ -65,19 +65,19 @@ var tests = [
   },
   {
     copyVal: "<example>.com/foo",
     copyExpected: "example"
   },
 
   // Test that userPass is stripped out
   {
-    loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass",
-    expectedURL: "mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass",
-    copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/general/authenticate.sjs?user=user&pass=pass"
+    loadURL: "http://user:pass@mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+    expectedURL: "mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass",
+    copyExpected: "http://mochi.test:8888/browser/browser/base/content/test/urlbar/authenticate.sjs?user=user&pass=pass"
   },
 
   // Test escaping
   {
     loadURL: "http://example.com/()%28%29%C3%A9",
     expectedURL: "example.com/()()\xe9",
     copyExpected: "http://example.com/()%28%29%C3%A9"
   },
rename from browser/base/content/test/general/browser_urlbarDecode.js
rename to browser/base/content/test/urlbar/browser_urlbarDecode.js
rename from browser/base/content/test/general/browser_urlbarDelete.js
rename to browser/base/content/test/urlbar/browser_urlbarDelete.js
rename from browser/base/content/test/general/browser_urlbarEnter.js
rename to browser/base/content/test/urlbar/browser_urlbarEnter.js
rename from browser/base/content/test/general/browser_urlbarEnterAfterMouseOver.js
rename to browser/base/content/test/urlbar/browser_urlbarEnterAfterMouseOver.js
rename from browser/base/content/test/general/browser_urlbarRevert.js
rename to browser/base/content/test/urlbar/browser_urlbarRevert.js
rename from browser/base/content/test/general/browser_urlbarSearchSingleWordNotification.js
rename to browser/base/content/test/urlbar/browser_urlbarSearchSingleWordNotification.js
rename from browser/base/content/test/general/browser_urlbarSearchSuggestions.js
rename to browser/base/content/test/urlbar/browser_urlbarSearchSuggestions.js
rename from browser/base/content/test/general/browser_urlbarSearchSuggestionsNotification.js
rename to browser/base/content/test/urlbar/browser_urlbarSearchSuggestionsNotification.js
rename from browser/base/content/test/general/browser_urlbarSearchTelemetry.js
rename to browser/base/content/test/urlbar/browser_urlbarSearchTelemetry.js
rename from browser/base/content/test/general/browser_urlbarStop.js
rename to browser/base/content/test/urlbar/browser_urlbarStop.js
rename from browser/base/content/test/general/browser_urlbarTrimURLs.js
rename to browser/base/content/test/urlbar/browser_urlbarTrimURLs.js
rename from browser/base/content/test/general/browser_urlbar_autoFill_backspaced.js
rename to browser/base/content/test/urlbar/browser_urlbar_autoFill_backspaced.js
rename from browser/base/content/test/general/browser_urlbar_searchsettings.js
rename to browser/base/content/test/urlbar/browser_urlbar_searchsettings.js
rename from browser/base/content/test/general/browser_wyciwyg_urlbarCopying.js
rename to browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
--- a/browser/base/content/test/general/browser_wyciwyg_urlbarCopying.js
+++ b/browser/base/content/test/urlbar/browser_wyciwyg_urlbarCopying.js
@@ -12,17 +12,17 @@ function testURLBarCopy(targetValue) {
     }, resolve, () => {
       ok(false, "Clipboard copy failed");
       reject();
     });
   });
 }
 
 add_task(function* () {
-  const url = "http://mochi.test:8888/browser/browser/base/content/test/general/test_wyciwyg_copying.html";
+  const url = "http://mochi.test:8888/browser/browser/base/content/test/urlbar/test_wyciwyg_copying.html";
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, url);
 
   yield BrowserTestUtils.synthesizeMouseAtCenter("#btn", {}, tab.linkedBrowser);
   let currentURL = gBrowser.currentURI.spec;
   ok(/^wyciwyg:\/\//i.test(currentURL), currentURL + " is a wyciwyg URI");
 
   yield testURLBarCopy(url);
 
copy from browser/base/content/test/general/dummy_page.html
copy to browser/base/content/test/urlbar/dummy_page.html
copy from browser/base/content/test/general/head.js
copy to browser/base/content/test/urlbar/head.js
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/urlbar/head.js
@@ -6,100 +6,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesTestUtils",
   "resource://testing-common/PlacesTestUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TabCrashHandler",
   "resource:///modules/ContentCrashHandlers.jsm");
 
-/**
- * Wait for a <notification> to be closed then call the specified callback.
- */
-function waitForNotificationClose(notification, cb) {
-  let parent = notification.parentNode;
-
-  let observer = new MutationObserver(function onMutatations(mutations) {
-    for (let mutation of mutations) {
-      for (let i = 0; i < mutation.removedNodes.length; i++) {
-        let node = mutation.removedNodes.item(i);
-        if (node != notification) {
-          continue;
-        }
-        observer.disconnect();
-        cb();
-      }
-    }
-  });
-  observer.observe(parent, {childList: true});
-}
-
-function closeAllNotifications () {
-  let notificationBox = document.getElementById("global-notificationbox");
-
-  if (!notificationBox || !notificationBox.currentNotification) {
-    return Promise.resolve();
-  }
-
-  let deferred = Promise.defer();
-  for (let notification of notificationBox.allNotifications) {
-    waitForNotificationClose(notification, function () {
-      if (notificationBox.allNotifications.length === 0) {
-        deferred.resolve();
-      }
-    });
-    notification.close();
-  }
-
-  return deferred.promise;
-}
-
-function whenDelayedStartupFinished(aWindow, aCallback) {
-  Services.obs.addObserver(function observer(aSubject, aTopic) {
-    if (aWindow == aSubject) {
-      Services.obs.removeObserver(observer, aTopic);
-      executeSoon(aCallback);
-    }
-  }, "browser-delayed-startup-finished", false);
-}
-
-function updateTabContextMenu(tab) {
-  let menu = document.getElementById("tabContextMenu");
-  if (!tab)
-    tab = gBrowser.selectedTab;
-  var evt = new Event("");
-  tab.dispatchEvent(evt);
-  menu.openPopup(tab, "end_after", 0, 0, true, false, evt);
-  is(TabContextMenu.contextTab, tab, "TabContextMenu context is the expected tab");
-  menu.hidePopup();
-}
-
-function openToolbarCustomizationUI(aCallback, aBrowserWin) {
-  if (!aBrowserWin)
-    aBrowserWin = window;
-
-  aBrowserWin.gCustomizeMode.enter();
-
-  aBrowserWin.gNavToolbox.addEventListener("customizationready", function UI_loaded() {
-    aBrowserWin.gNavToolbox.removeEventListener("customizationready", UI_loaded);
-    executeSoon(function() {
-      aCallback(aBrowserWin)
-    });
-  });
-}
-
-function closeToolbarCustomizationUI(aCallback, aBrowserWin) {
-  aBrowserWin.gNavToolbox.addEventListener("aftercustomization", function unloaded() {
-    aBrowserWin.gNavToolbox.removeEventListener("aftercustomization", unloaded);
-    executeSoon(aCallback);
-  });
-
-  aBrowserWin.gCustomizeMode.exit();
-}
-
 function waitForCondition(condition, nextTest, errorMsg, retryTimes) {
   retryTimes = typeof retryTimes !== 'undefined' ?  retryTimes : 30;
   var tries = 0;
   var interval = setInterval(function() {
     if (tries >= retryTimes) {
       ok(false, errorMsg);
       moveOn();
     }
@@ -132,112 +48,16 @@ function promiseWaitForEvent(object, eve
       resolve(event);
     }
 
     info("Waiting for " + eventName);
     object.addEventListener(eventName, listener, capturing, chrome);
   });
 }
 
-/**
- * Allows setting focus on a window, and waiting for that window to achieve
- * focus.
- *
- * @param aWindow
- *        The window to focus and wait for.
- *
- * @return {Promise}
- * @resolves When the window is focused.
- * @rejects Never.
- */
-function promiseWaitForFocus(aWindow) {
-  return new Promise((resolve) => {
-    waitForFocus(resolve, aWindow);
-  });
-}
-
-function getTestPlugin(aName) {
-  var pluginName = aName || "Test Plug-in";
-  var ph = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  var tags = ph.getPluginTags();
-
-  // Find the test plugin
-  for (var i = 0; i < tags.length; i++) {
-    if (tags[i].name == pluginName)
-      return tags[i];
-  }
-  ok(false, "Unable to find plugin");
-  return null;
-}
-
-// call this to set the test plugin(s) initially expected enabled state.
-// it will automatically be reset to it's previous value after the test
-// ends
-function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var plugin = getTestPlugin(pluginName);
-  var oldEnabledState = plugin.enabledState;
-  plugin.enabledState = newEnabledState;
-  SimpleTest.registerCleanupFunction(function() {
-    getTestPlugin(pluginName).enabledState = oldEnabledState;
-  });
-}
-
-// after a test is done using the plugin doorhanger, we should just clear
-// any permissions that may have crept in
-function clearAllPluginPermissions() {
-  clearAllPermissionsByPrefix("plugin");
-}
-
-function clearAllPermissionsByPrefix(aPrefix) {
-  let perms = Services.perms.enumerator;
-  while (perms.hasMoreElements()) {
-    let perm = perms.getNext();
-    if (perm.type.startsWith(aPrefix)) {
-      Services.perms.removePermission(perm);
-    }
-  }
-}
-
-function pushPrefs(...aPrefs) {
-  let deferred = Promise.defer();
-  SpecialPowers.pushPrefEnv({"set": aPrefs}, deferred.resolve);
-  return deferred.promise;
-}
-
-function updateBlocklist(aCallback) {
-  var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
-                          .getService(Ci.nsITimerCallback);
-  var observer = function() {
-    Services.obs.removeObserver(observer, "blocklist-updated");
-    SimpleTest.executeSoon(aCallback);
-  };
-  Services.obs.addObserver(observer, "blocklist-updated", false);
-  blocklistNotifier.notify(null);
-}
-
-var _originalTestBlocklistURL = null;
-function setAndUpdateBlocklist(aURL, aCallback) {
-  if (!_originalTestBlocklistURL)
-    _originalTestBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
-  Services.prefs.setCharPref("extensions.blocklist.url", aURL);
-  updateBlocklist(aCallback);
-}
-
-function resetBlocklist() {
-  Services.prefs.setCharPref("extensions.blocklist.url", _originalTestBlocklistURL);
-}
-
-function whenNewWindowLoaded(aOptions, aCallback) {
-  let win = OpenBrowserWindow(aOptions);
-  win.addEventListener("load", function onLoad() {
-    win.removeEventListener("load", onLoad, false);
-    aCallback(win);
-  }, false);
-}
-
 function promiseWindowWillBeClosed(win) {
   return new Promise((resolve, reject) => {
     Services.obs.addObserver(function observe(subject, topic) {
       if (subject == win) {
         Services.obs.removeObserver(observe, topic);
         resolve();
       }
     }, "domwindowclosed", false);
@@ -266,71 +86,16 @@ function promiseOpenAndLoadWindow(aOptio
     win.addEventListener("load", function onLoad() {
       win.removeEventListener("load", onLoad);
       deferred.resolve(win);
     });
   }
   return deferred.promise;
 }
 
-/**
- * Waits for all pending async statements on the default connection, before
- * proceeding with aCallback.
- *
- * @param aCallback
- *        Function to be called when done.
- * @param aScope
- *        Scope for the callback.
- * @param aArguments
- *        Arguments array for the callback.
- *
- * @note The result is achieved by asynchronously executing a query requiring
- *       a write lock.  Since all statements on the same connection are
- *       serialized, the end of this write operation means that all writes are
- *       complete.  Note that WAL makes so that writers don't block readers, but
- *       this is a problem only across different connections.
- */
-function waitForAsyncUpdates(aCallback, aScope, aArguments) {
-  let scope = aScope || this;
-  let args = aArguments || [];
-  let db = PlacesUtils.history.QueryInterface(Ci.nsPIPlacesDatabase)
-                              .DBConnection;
-  let begin = db.createAsyncStatement("BEGIN EXCLUSIVE");
-  begin.executeAsync();
-  begin.finalize();
-
-  let commit = db.createAsyncStatement("COMMIT");
-  commit.executeAsync({
-    handleResult: function() {},
-    handleError: function() {},
-    handleCompletion: function(aReason) {
-      aCallback.apply(scope, args);
-    }
-  });
-  commit.finalize();
-}
-
-/**
- * Asynchronously check a url is visited.
-
- * @param aURI The URI.
- * @param aExpectedValue The expected value.
- * @return {Promise}
- * @resolves When the check has been added successfully.
- * @rejects JavaScript exception.
- */
-function promiseIsURIVisited(aURI, aExpectedValue) {
-  let deferred = Promise.defer();
-  PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
-    deferred.resolve(aIsVisited);
-  });
-
-  return deferred.promise;
-}
-
 function whenNewTabLoaded(aWindow, aCallback) {
   aWindow.BrowserOpenTab();
 
   let browser = aWindow.gBrowser.selectedBrowser;
   if (browser.contentDocument.readyState === "complete") {
     aCallback();
     return;
   }
@@ -344,43 +109,16 @@ function whenTabLoaded(aTab, aCallback) 
 
 function promiseTabLoaded(aTab) {
   let deferred = Promise.defer();
   whenTabLoaded(aTab, deferred.resolve);
   return deferred.promise;
 }
 
 /**
- * Ensures that the specified URIs are either cleared or not.
- *
- * @param aURIs
- *        Array of page URIs
- * @param aShouldBeCleared
- *        True if each visit to the URI should be cleared, false otherwise
- */
-function promiseHistoryClearedState(aURIs, aShouldBeCleared) {
-  let deferred = Promise.defer();
-  let callbackCount = 0;
-  let niceStr = aShouldBeCleared ? "no longer" : "still";
-  function callbackDone() {
-    if (++callbackCount == aURIs.length)
-      deferred.resolve();
-  }
-  aURIs.forEach(function (aURI) {
-    PlacesUtils.asyncHistory.isURIVisited(aURI, function(aURI, aIsVisited) {
-      is(aIsVisited, !aShouldBeCleared,
-         "history visit " + aURI.spec + " should " + niceStr + " exist");
-      callbackDone();
-    });
-  });
-
-  return deferred.promise;
-}
-
-/**
  * Waits for the next top-level document load in the current browser.  The URI
  * of the document is compared against aExpectedURL.  The load is then stopped
  * before it actually starts.
  *
  * @param aExpectedURL
  *        The URL of the document that is expected to load.
  * @param aStopFromProgressListener
  *        Whether to cancel the load directly from the progress listener. Defaults to true.
@@ -449,157 +187,16 @@ function waitForDocLoadAndStopIt(aExpect
     let mm = aBrowser.messageManager;
     mm.loadFrameScript("data:,(" + content_script.toString() + ")(" + aStopFromProgressListener + ");", true);
     mm.addMessageListener("Test:WaitForDocLoadAndStopIt", complete);
     info("waitForDocLoadAndStopIt: Waiting for URL: " + aExpectedURL);
   });
 }
 
 /**
- * Waits for the next load to complete in any browser or the given browser.
- * If a <tabbrowser> is given it waits for a load in any of its browsers.
- *
- * @return promise
- */
-function waitForDocLoadComplete(aBrowser=gBrowser) {
-  return new Promise(resolve => {
-    let listener = {
-      onStateChange: function (webProgress, req, flags, status) {
-        let docStop = Ci.nsIWebProgressListener.STATE_IS_NETWORK |
-                      Ci.nsIWebProgressListener.STATE_STOP;
-        info("Saw state " + flags.toString(16) + " and status " + status.toString(16));
-
-        // When a load needs to be retargetted to a new process it is cancelled
-        // with NS_BINDING_ABORTED so ignore that case
-        if ((flags & docStop) == docStop && status != Cr.NS_BINDING_ABORTED) {
-          aBrowser.removeProgressListener(this);
-          waitForDocLoadComplete.listeners.delete(this);
-
-          let chan = req.QueryInterface(Ci.nsIChannel);
-          info("Browser loaded " + chan.originalURI.spec);
-          resolve();
-        }
-      },
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
-                                             Ci.nsISupportsWeakReference])
-    };
-    aBrowser.addProgressListener(listener);
-    waitForDocLoadComplete.listeners.add(listener);
-    info("Waiting for browser load");
-  });
-}
-
-// Keep a set of progress listeners for waitForDocLoadComplete() to make sure
-// they're not GC'ed before we saw the page load.
-waitForDocLoadComplete.listeners = new Set();
-registerCleanupFunction(() => waitForDocLoadComplete.listeners.clear());
-
-var FullZoomHelper = {
-
-  selectTabAndWaitForLocationChange: function selectTabAndWaitForLocationChange(tab) {
-    if (!tab)
-      throw new Error("tab must be given.");
-    if (gBrowser.selectedTab == tab)
-      return Promise.resolve();
-
-    return Promise.all([BrowserTestUtils.switchTab(gBrowser, tab),
-                        this.waitForLocationChange()]);
-  },
-
-  removeTabAndWaitForLocationChange: function removeTabAndWaitForLocationChange(tab) {
-    tab = tab || gBrowser.selectedTab;
-    let selected = gBrowser.selectedTab == tab;
-    gBrowser.removeTab(tab);
-    if (selected)
-      return this.waitForLocationChange();
-    return Promise.resolve();
-  },
-
-  waitForLocationChange: function waitForLocationChange() {
-    return new Promise(resolve => {
-      Services.obs.addObserver(function obs(subj, topic, data) {
-        Services.obs.removeObserver(obs, topic);
-        resolve();
-      }, "browser-fullZoom:location-change", false);
-    });
-  },
-
-  load: function load(tab, url) {
-    return new Promise(resolve => {
-      let didLoad = false;
-      let didZoom = false;
-
-      promiseTabLoadEvent(tab).then(event => {
-        didLoad = true;
-        if (didZoom)
-          resolve();
-      }, true);
-
-      this.waitForLocationChange().then(function () {
-        didZoom = true;
-        if (didLoad)
-          resolve();
-      });
-
-      tab.linkedBrowser.loadURI(url);
-    });
-  },
-
-  zoomTest: function zoomTest(tab, val, msg) {
-    is(ZoomManager.getZoomForBrowser(tab.linkedBrowser), val, msg);
-  },
-
-  enlarge: function enlarge() {
-    return new Promise(resolve => FullZoom.enlarge(resolve));
-  },
-
-  reduce: function reduce() {
-    return new Promise(resolve => FullZoom.reduce(resolve));
-  },
-
-  reset: function reset() {
-    return new Promise(resolve => FullZoom.reset(resolve));
-  },
-
-  BACK: 0,
-  FORWARD: 1,
-  navigate: function navigate(direction) {
-    return new Promise(resolve => {
-      let didPs = false;
-      let didZoom = false;
-
-      gBrowser.addEventListener("pageshow", function listener(event) {
-        gBrowser.removeEventListener("pageshow", listener, true);
-        didPs = true;
-        if (didZoom)
-          resolve();
-      }, true);
-
-      if (direction == this.BACK)
-        gBrowser.goBack();
-      else if (direction == this.FORWARD)
-        gBrowser.goForward();
-
-      this.waitForLocationChange().then(function () {
-        didZoom = true;
-        if (didPs)
-          resolve();
-      });
-    });
-  },
-
-  failAndContinue: function failAndContinue(func) {
-    return function (err) {
-      ok(false, err);
-      func();
-    };
-  },
-};
-
-/**
  * Waits for a load (or custom) event to finish in a given tab. If provided
  * load an uri into the tab.
  *
  * @param tab
  *        The tab to load into.
  * @param [optional] url
  *        The url to load, or the current url.
  * @return {Promise} resolved when the event is handled.
@@ -638,309 +235,16 @@ function promiseTabLoadEvent(tab, url)
     BrowserTestUtils.loadURI(tab.linkedBrowser, url);
 
   // Promise.all rejects if either promise rejects (i.e. if we time out) and
   // if our loaded promise resolves before the timeout, then we resolve the
   // timeout promise as well, causing the all promise to resolve.
   return Promise.all([deferred.promise, loaded]);
 }
 
-/**
- * Returns a Promise that resolves once a new tab has been opened in
- * a xul:tabbrowser.
- *
- * @param aTabBrowser
- *        The xul:tabbrowser to monitor for a new tab.
- * @return {Promise}
- *        Resolved when the new tab has been opened.
- * @resolves to the TabOpen event that was fired.
- * @rejects Never.
- */
-function waitForNewTabEvent(aTabBrowser) {
-  return promiseWaitForEvent(aTabBrowser.tabContainer, "TabOpen");
-}
-
-/**
- * Waits for a window with the given URL to exist.
- *
- * @param url
- *        The url of the window.
- * @return {Promise} resolved when the window exists.
- * @resolves to the window
- */
-function promiseWindow(url) {
-  info("expecting a " + url + " window");
-  return new Promise(resolve => {
-    Services.obs.addObserver(function obs(win) {
-      win.QueryInterface(Ci.nsIDOMWindow);
-      win.addEventListener("load", function loadHandler() {
-        win.removeEventListener("load", loadHandler);
-
-        if (win.location.href !== url) {
-          info("ignoring a window with this url: " + win.location.href);
-          return;
-        }
-
-        Services.obs.removeObserver(obs, "domwindowopened");
-        resolve(win);
-      });
-    }, "domwindowopened", false);
-  });
-}
-
-function promiseIndicatorWindow() {
-  // We don't show the indicator window on Mac.
-  if ("nsISystemStatusBar" in Ci)
-    return Promise.resolve();
-
-  return promiseWindow("chrome://browser/content/webrtcIndicator.xul");
-}
-
-function assertWebRTCIndicatorStatus(expected) {
-  let ui = Cu.import("resource:///modules/webrtcUI.jsm", {}).webrtcUI;
-  let expectedState = expected ? "visible" : "hidden";
-  let msg = "WebRTC indicator " + expectedState;
-  if (!expected && ui.showGlobalIndicator) {
-    // It seems the global indicator is not always removed synchronously
-    // in some cases.
-    info("waiting for the global indicator to be hidden");
-    yield promiseWaitForCondition(() => !ui.showGlobalIndicator);
-  }
-  is(ui.showGlobalIndicator, !!expected, msg);
-
-  let expectVideo = false, expectAudio = false, expectScreen = false;
-  if (expected) {
-    if (expected.video)
-      expectVideo = true;
-    if (expected.audio)
-      expectAudio = true;
-    if (expected.screen)
-      expectScreen = true;
-  }
-  is(ui.showCameraIndicator, expectVideo, "camera global indicator as expected");
-  is(ui.showMicrophoneIndicator, expectAudio, "microphone global indicator as expected");
-  is(ui.showScreenSharingIndicator, expectScreen, "screen global indicator as expected");
-
-  let windows = Services.wm.getEnumerator("navigator:browser");
-  while (windows.hasMoreElements()) {
-    let win = windows.getNext();
-    let menu = win.document.getElementById("tabSharingMenu");
-    is(menu && !menu.hidden, !!expected, "WebRTC menu should be " + expectedState);
-  }
-
-  if (!("nsISystemStatusBar" in Ci)) {
-    if (!expected) {
-      let win = Services.wm.getMostRecentWindow("Browser:WebRTCGlobalIndicator");
-      if (win) {
-        yield new Promise((resolve, reject) => {
-          win.addEventListener("unload", function listener(e) {
-            if (e.target == win.document) {
-              win.removeEventListener("unload", listener);
-              resolve();
-            }
-          }, false);
-        });
-      }
-    }
-    let indicator = Services.wm.getEnumerator("Browser:WebRTCGlobalIndicator");
-    let hasWindow = indicator.hasMoreElements();
-    is(hasWindow, !!expected, "popup " + msg);
-    if (hasWindow) {
-      let document = indicator.getNext().document;
-      let docElt = document.documentElement;
-
-      if (document.readyState != "complete") {
-        info("Waiting for the sharing indicator's document to load");
-        let deferred = Promise.defer();
-        document.addEventListener("readystatechange",
-                                  function onReadyStateChange() {
-          if (document.readyState != "complete")
-            return;
-          document.removeEventListener("readystatechange", onReadyStateChange);
-          deferred.resolve();
-        });
-        yield deferred.promise;
-      }
-
-      for (let item of ["video", "audio", "screen"]) {
-        let expectedValue = (expected && expected[item]) ? "true" : "";
-        is(docElt.getAttribute("sharing" + item), expectedValue,
-           item + " global indicator attribute as expected");
-      }
-
-      ok(!indicator.hasMoreElements(), "only one global indicator window");
-    }
-  }
-}
-
-/**
- * Test the state of the identity box and control center to make
- * sure they are correctly showing the expected mixed content states.
- *
- * @note The checks are done synchronously, but new code should wait on the
- *       returned Promise object to ensure the identity panel has closed.
- *       Bug 1221114 is filed to fix the existing code.
- *
- * @param tabbrowser
- * @param Object states
- *        MUST include the following properties:
- *        {
- *           activeLoaded: true|false,
- *           activeBlocked: true|false,
- *           passiveLoaded: true|false,
- *        }
- *
- * @return {Promise}
- * @resolves When the operation has finished and the identity panel has closed.
- */
-function assertMixedContentBlockingState(tabbrowser, states = {}) {
-  if (!tabbrowser || !("activeLoaded" in states) ||
-      !("activeBlocked" in states) || !("passiveLoaded" in states))  {
-    throw new Error("assertMixedContentBlockingState requires a browser and a states object");
-  }
-
-  let {passiveLoaded,activeLoaded,activeBlocked} = states;
-  let {gIdentityHandler} = tabbrowser.ownerGlobal;
-  let doc = tabbrowser.ownerDocument;
-  let identityBox = gIdentityHandler._identityBox;
-  let classList = identityBox.classList;
-  let connectionIcon = doc.getElementById("connection-icon");
-  let connectionIconImage = tabbrowser.ownerGlobal.getComputedStyle(connectionIcon, "").
-                         getPropertyValue("list-style-image");
-
-  let stateSecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_SECURE;
-  let stateBroken = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_BROKEN;
-  let stateInsecure = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_IS_INSECURE;
-  let stateActiveBlocked = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_BLOCKED_MIXED_ACTIVE_CONTENT;
-  let stateActiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_ACTIVE_CONTENT;
-  let statePassiveLoaded = gIdentityHandler._state & Ci.nsIWebProgressListener.STATE_LOADED_MIXED_DISPLAY_CONTENT;
-
-  is(activeBlocked, !!stateActiveBlocked, "Expected state for activeBlocked matches UI state");
-  is(activeLoaded, !!stateActiveLoaded, "Expected state for activeLoaded matches UI state");
-  is(passiveLoaded, !!statePassiveLoaded, "Expected state for passiveLoaded matches UI state");
-
-  if (stateInsecure) {
-    // HTTP request, there should be no MCB classes for the identity box and the non secure icon
-    // should always be visible regardless of MCB state.
-    ok(classList.contains("unknownIdentity"), "unknownIdentity on HTTP page");
-    is_element_hidden(connectionIcon);
-
-    ok(!classList.contains("mixedActiveContent"), "No MCB icon on HTTP page");
-    ok(!classList.contains("mixedActiveBlocked"), "No MCB icon on HTTP page");
-    ok(!classList.contains("mixedDisplayContent"), "No MCB icon on HTTP page");
-    ok(!classList.contains("mixedDisplayContentLoadedActiveBlocked"), "No MCB icon on HTTP page");
-  } else {
-    // Make sure the identity box UI has the correct mixedcontent states and icons
-    is(classList.contains("mixedActiveContent"), activeLoaded,
-        "identityBox has expected class for activeLoaded");
-    is(classList.contains("mixedActiveBlocked"), activeBlocked && !passiveLoaded,
-        "identityBox has expected class for activeBlocked && !passiveLoaded");
-    is(classList.contains("mixedDisplayContent"), passiveLoaded && !(activeLoaded || activeBlocked),
-       "identityBox has expected class for passiveLoaded && !(activeLoaded || activeBlocked)");
-    is(classList.contains("mixedDisplayContentLoadedActiveBlocked"), passiveLoaded && activeBlocked,
-       "identityBox has expected class for passiveLoaded && activeBlocked");
-
-    is_element_visible(connectionIcon);
-    if (activeLoaded) {
-      is(connectionIconImage, "url(\"chrome://browser/skin/identity-mixed-active-loaded.svg\")",
-        "Using active loaded icon");
-    }
-    if (activeBlocked && !passiveLoaded) {
-      is(connectionIconImage, "url(\"chrome://browser/skin/identity-mixed-active-blocked.svg\")",
-        "Using active blocked icon");
-    }
-    if (passiveLoaded && !(activeLoaded || activeBlocked)) {
-      is(connectionIconImage, "url(\"chrome://browser/skin/identity-mixed-passive-loaded.svg\")",
-        "Using passive loaded icon");
-    }
-    if (passiveLoaded && activeBlocked) {
-      is(connectionIconImage, "url(\"chrome://browser/skin/identity-mixed-passive-loaded.svg\")",
-        "Using active blocked and passive loaded icon");
-    }
-  }
-
-  // Make sure the identity popup has the correct mixedcontent states
-  gIdentityHandler._identityBox.click();
-  let popupAttr = doc.getElementById("identity-popup").getAttribute("mixedcontent");
-  let bodyAttr = doc.getElementById("identity-popup-securityView-body").getAttribute("mixedcontent");
-
-  is(popupAttr.includes("active-loaded"), activeLoaded,
-      "identity-popup has expected attr for activeLoaded");
-  is(bodyAttr.includes("active-loaded"), activeLoaded,
-      "securityView-body has expected attr for activeLoaded");
-
-  is(popupAttr.includes("active-blocked"), activeBlocked,
-      "identity-popup has expected attr for activeBlocked");
-  is(bodyAttr.includes("active-blocked"), activeBlocked,
-      "securityView-body has expected attr for activeBlocked");
-
-  is(popupAttr.includes("passive-loaded"), passiveLoaded,
-      "identity-popup has expected attr for passiveLoaded");
-  is(bodyAttr.includes("passive-loaded"), passiveLoaded,
-      "securityView-body has expected attr for passiveLoaded");
-
-  // Make sure the correct icon is visible in the Control Center.
-  // This logic is controlled with CSS, so this helps prevent regressions there.
-  let securityView = doc.getElementById("identity-popup-securityView");
-  let securityContent = doc.getElementById("identity-popup-security-content");
-  let securityViewBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
-                       getPropertyValue("background-image");
-  let securityContentBG = tabbrowser.ownerGlobal.getComputedStyle(securityView, "").
-                          getPropertyValue("background-image");
-
-  if (stateInsecure) {
-    is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
-      "CC using 'not secure' icon");
-    is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-not-secure.svg\")",
-      "CC using 'not secure' icon");
-  }
-
-  if (stateSecure) {
-    is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-secure.svg\")",
-      "CC using secure icon");
-    is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-secure.svg\")",
-      "CC using secure icon");
-  }
-
-  if (stateBroken) {
-    if (activeLoaded) {
-      is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
-        "CC using active loaded icon");
-      is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/mcb-disabled.svg\")",
-        "CC using active loaded icon");
-    } else if (activeBlocked || passiveLoaded) {
-      is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
-        "CC using degraded icon");
-      is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
-        "CC using degraded icon");
-    } else {
-      // There is a case here with weak ciphers, but no bc tests are handling this yet.
-      is(securityViewBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
-        "CC using degraded icon");
-      is(securityContentBG, "url(\"chrome://browser/skin/controlcenter/conn-degraded.svg\")",
-        "CC using degraded icon");
-    }
-  }
-
-  if (activeLoaded || activeBlocked || passiveLoaded) {
-    doc.getElementById("identity-popup-security-expander").click();
-    is(Array.filter(doc.querySelectorAll("[observes=identity-popup-mcb-learn-more]"),
-                    element => !is_hidden(element)).length, 1,
-       "The 'Learn more' link should be visible once.");
-  }
-
-  gIdentityHandler._identityPopup.hidden = true;
-
-  // Wait for the panel to be closed before continuing. The promisePopupHidden
-  // function cannot be used because it's unreliable unless promisePopupShown is
-  // also called before closing the panel. This cannot be done until all callers
-  // are made asynchronous (bug 1221114).
-  return new Promise(resolve => executeSoon(resolve));
-}
-
 function makeActionURI(action, params) {
   let encodedParams = {};
   for (let key in params) {
     encodedParams[key] = encodeURIComponent(params[key]);
   }
   let url = "moz-action:" + action + "," + JSON.stringify(encodedParams);
   return NetUtil.newURI(url);
 }
@@ -1006,26 +310,16 @@ function promisePopupEvent(popup, eventS
 function promisePopupShown(popup) {
   return promisePopupEvent(popup, "shown");
 }
 
 function promisePopupHidden(popup) {
   return promisePopupEvent(popup, "hidden");
 }
 
-function promiseNotificationShown(notification) {
-  let win = notification.browser.ownerDocument.defaultView;
-  if (win.PopupNotifications.panel.state == "open") {
-    return Promise.resolved();
-  }
-  let panelPromise = promisePopupShown(win.PopupNotifications.panel);
-  notification.reshow();
-  return panelPromise;
-}
-
 function promiseSearchComplete(win = window) {
   return promisePopupShown(win.gURLBar.popup).then(() => {
     function searchIsComplete() {
       return win.gURLBar.controller.searchStatus >=
         Ci.nsIAutoCompleteController.STATUS_COMPLETE_NO_MATCH;
     }
 
     // Wait until there are at least two matches.
@@ -1038,38 +332,16 @@ function promiseAutocompleteResultPopup(
     win.gURLBar.focus();
     win.gURLBar.value = inputText;
     win.gURLBar.controller.startSearch(inputText);
   }, win);
 
   return promiseSearchComplete(win);
 }
 
-/**
- * Allows waiting for an observer notification once.
- *
- * @param aTopic
- *        Notification topic to observe.
- *
- * @return {Promise}
- * @resolves An object with subject and data properties from the observed
- *           notification.
- * @rejects Never.
- */
-function promiseTopicObserved(aTopic)
-{
-  return new Promise((resolve) => {
-    Services.obs.addObserver(
-      function PTO_observe(aSubject, aTopic, aData) {
-        Services.obs.removeObserver(PTO_observe, aTopic);
-        resolve({subject: aSubject, data: aData});
-      }, aTopic, false);
-  });
-}
-
 function promiseNewSearchEngine(basename) {
   return new Promise((resolve, reject) => {
     info("Waiting for engine to be added: " + basename);
     let url = getRootDirectory(gTestPath) + basename;
     Services.search.addEngine(url, null, "", false, {
       onSuccess: function (engine) {
         info("Search engine added: " + basename);
         registerCleanupFunction(() => Services.search.removeEngine(engine));
@@ -1078,170 +350,8 @@ function promiseNewSearchEngine(basename
       onError: function (errCode) {
         Assert.ok(false, "addEngine failed with error code " + errCode);
         reject();
       },
     });
   });
 }
 
-// Compares the security state of the page with what is expected
-function isSecurityState(expectedState) {
-  let ui = gTestBrowser.securityUI;
-  if (!ui) {
-    ok(false, "No security UI to get the security state");
-    return;
-  }
-
-  const wpl = Components.interfaces.nsIWebProgressListener;
-
-  // determine the security state
-  let isSecure = ui.state & wpl.STATE_IS_SECURE;
-  let isBroken = ui.state & wpl.STATE_IS_BROKEN;
-  let isInsecure = ui.state & wpl.STATE_IS_INSECURE;
-
-  let actualState;
-  if (isSecure && !(isBroken || isInsecure)) {
-    actualState = "secure";
-  } else if (isBroken && !(isSecure || isInsecure)) {
-    actualState = "broken";
-  } else if (isInsecure && !(isSecure || isBroken)) {
-    actualState = "insecure";
-  } else {
-    actualState = "unknown";
-  }
-
-  is(expectedState, actualState, "Expected state " + expectedState + " and the actual state is " + actualState + ".");
-}
-
-/**
- * Resolves when a bookmark with the given uri is added.
- */
-function promiseOnBookmarkItemAdded(aExpectedURI) {
-  return new Promise((resolve, reject) => {
-    let bookmarksObserver = {
-      onItemAdded: function (aItemId, aFolderId, aIndex, aItemType, aURI) {
-        info("Added a bookmark to " + aURI.spec);
-        PlacesUtils.bookmarks.removeObserver(bookmarksObserver);
-        if (aURI.equals(aExpectedURI)) {
-          resolve();
-        }
-        else {
-          reject(new Error("Added an unexpected bookmark"));
-        }
-      },
-      onBeginUpdateBatch: function () {},
-      onEndUpdateBatch: function () {},
-      onItemRemoved: function () {},
-      onItemChanged: function () {},
-      onItemVisited: function () {},
-      onItemMoved: function () {},
-      QueryInterface: XPCOMUtils.generateQI([
-        Ci.nsINavBookmarkObserver,
-      ])
-    };
-    info("Waiting for a bookmark to be added");
-    PlacesUtils.bookmarks.addObserver(bookmarksObserver, false);
-  });
-}
-
-/**
- * For an nsIPropertyBag, returns the value for a given
- * key.
- *
- * @param bag
- *        The nsIPropertyBag to retrieve the value from
- * @param key
- *        The key that we want to get the value for from the
- *        bag
- * @returns The value corresponding to the key from the bag,
- *          or null if the value could not be retrieved (for
- *          example, if no value is set at that key).
-*/
-function getPropertyBagValue(bag, key) {
-  try {
-    let val = bag.getProperty(key);
-    return val;
-  } catch(e if e.result == Cr.NS_ERROR_FAILURE) {}
-
-  return null;
-}
-
-/**
- * Returns a Promise that resolves once a crash report has
- * been submitted. This function will also test the crash
- * reports extra data to see if it matches expectedExtra.
- *
- * @param expectedExtra (object)
- *        An Object whose key-value pairs will be compared
- *        against the key-value pairs in the extra data of the
- *        crash report. A test failure will occur if there is
- *        a mismatch.
- *
- *        If the value of the key-value pair is "null", this will
- *        be interpreted as "this key should not be included in the
- *        extra data", and will cause a test failure if it is detected
- *        in the crash report.
- *
- *        Note that this will ignore any keys that are not included
- *        in expectedExtra. It's possible that the crash report
- *        will contain other extra information that is not
- *        compared against.
- * @returns Promise
- */
-function promiseCrashReport(expectedExtra={}) {
-  return Task.spawn(function*() {
-    info("Starting wait on crash-report-status");
-    let [subject, data] =
-      yield TestUtils.topicObserved("crash-report-status", (subject, data) => {
-        return data == "success";
-      });
-    info("Topic observed!");
-
-    if (!(subject instanceof Ci.nsIPropertyBag2)) {
-      throw new Error("Subject was not a Ci.nsIPropertyBag2");
-    }
-
-    let remoteID = getPropertyBagValue(subject, "serverCrashID");
-    if (!remoteID) {
-      throw new Error("Report should have a server ID");
-    }
-
-    let file = Cc["@mozilla.org/file/local;1"]
-                 .createInstance(Ci.nsILocalFile);
-    file.initWithPath(Services.crashmanager._submittedDumpsDir);
-    file.append(remoteID + ".txt");
-    if (!file.exists()) {
-      throw new Error("Report should have been received by the server");
-    }
-
-    file.remove(false);
-
-    let extra = getPropertyBagValue(subject, "extra");
-    if (!(extra instanceof Ci.nsIPropertyBag2)) {
-      throw new Error("extra was not a Ci.nsIPropertyBag2");
-    }
-
-    info("Iterating crash report extra keys");
-    let enumerator = extra.enumerator;
-    while (enumerator.hasMoreElements()) {
-      let key = enumerator.getNext().QueryInterface(Ci.nsIProperty).name;
-      let value = extra.getPropertyAsAString(key);
-      if (key in expectedExtra) {
-        if (expectedExtra[key] == null) {
-          ok(false, `Got unexpected key ${key} with value ${value}`);
-        } else {
-          is(value, expectedExtra[key],
-             `Crash report had the right extra value for ${key}`);
-        }
-      }
-    }
-  });
-}
-
-function promiseErrorPageLoaded(browser) {
-  return new Promise(resolve => {
-    browser.addEventListener("DOMContentLoaded", function onLoad() {
-      browser.removeEventListener("DOMContentLoaded", onLoad, false, true);
-      resolve();
-    }, false, true);
-  });
-}
copy from browser/base/content/test/general/moz.png
copy to browser/base/content/test/urlbar/moz.png
rename from browser/base/content/test/general/redirect_bug623155.sjs
rename to browser/base/content/test/urlbar/redirect_bug623155.sjs
copy from browser/base/content/test/general/searchSuggestionEngine.sjs
copy to browser/base/content/test/urlbar/searchSuggestionEngine.sjs
copy from browser/base/content/test/general/searchSuggestionEngine.xml
copy to browser/base/content/test/urlbar/searchSuggestionEngine.xml
--- a/browser/base/content/test/general/searchSuggestionEngine.xml
+++ b/browser/base/content/test/urlbar/searchSuggestionEngine.xml
@@ -1,9 +1,9 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <!-- Any copyright is dedicated to the Public Domain.
    - http://creativecommons.org/publicdomain/zero/1.0/ -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>browser_searchSuggestionEngine searchSuggestionEngine.xml</ShortName>
-<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/general/searchSuggestionEngine.sjs?{searchTerms}"/>
+<Url type="application/x-suggestions+json" method="GET" template="http://mochi.test:8888/browser/browser/base/content/test/urlbar/searchSuggestionEngine.sjs?{searchTerms}"/>
 <Url type="text/html" method="GET" template="http://mochi.test:8888/" rel="searchform"/>
 </SearchPlugin>
rename from browser/base/content/test/general/test_wyciwyg_copying.html
rename to browser/base/content/test/urlbar/test_wyciwyg_copying.html
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -135,16 +135,21 @@ PrefObserver.register({
 ////////////////////////////////////////////////////////////////////////////////
 //// DownloadsCommon
 
 /**
  * This object is exposed directly to the consumers of this JavaScript module,
  * and provides shared methods for all the instances of the user interface.
  */
 this.DownloadsCommon = {
+  ATTENTION_NONE: "",
+  ATTENTION_SUCCESS: "success",
+  ATTENTION_WARNING: "warning",
+  ATTENTION_SEVERE: "severe",
+
   /**
    * Returns an object whose keys are the string names from the downloads string
    * bundle, and whose values are either the translated strings or functions
    * returning formatted strings.
    */
   get strings() {
     let strings = {};
     let sb = Services.strings.createBundle(kDownloadsStringBundleUrl);
@@ -583,26 +588,26 @@ this.DownloadsCommon = {
       default:
         Cu.reportError("Unexpected dialog type: " + dialogType);
         return "cancel";
     }
 
     let message;
     switch (verdict) {
       case Downloads.Error.BLOCK_VERDICT_UNCOMMON:
-        message = s.unblockTypeUncommon;
+        message = s.unblockTypeUncommon2;
         break;
       case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
-        message = s.unblockTypePotentiallyUnwanted;
+        message = s.unblockTypePotentiallyUnwanted2;
         break;
       default: // Assume Downloads.Error.BLOCK_VERDICT_MALWARE
         message = s.unblockTypeMalware;
         break;
     }
-    message += "\n\n" + s.unblockTip;
+    message += "\n\n" + s.unblockTip2;
 
     Services.ww.registerNotification(function onOpen(subj, topic) {
       if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
         // Make sure to listen for "DOMContentLoaded" because it is fired
         // before the "load" event.
         subj.addEventListener("DOMContentLoaded", function onLoad() {
           subj.removeEventListener("DOMContentLoaded", onLoad);
           if (subj.document.documentURI ==
@@ -717,17 +722,17 @@ DownloadsDataCtor.prototype = {
    */
   removeFinished() {
     let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
                                                         : Downloads.PUBLIC);
     promiseList.then(list => list.removeFinished())
                .then(null, Cu.reportError);
     let indicatorData = this._isPrivate ? PrivateDownloadsIndicatorData
                                         : DownloadsIndicatorData;
-    indicatorData.attention = false;
+    indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Integration with the asynchronous Downloads back-end
 
   onDownloadAdded(download) {
     // Download objects do not store the end time of downloads, as the Downloads
     // API does not need to persist this information for all platforms. Once a
@@ -1148,18 +1153,39 @@ DownloadsIndicatorDataCtor.prototype = {
   },
 
   onDownloadAdded(download, newest) {
     this._itemCount++;
     this._updateViews();
   },
 
   onDownloadStateChanged(download) {
-    if (download.succeeded || download.error) {
-      this.attention = true;
+    if (!download.succeeded && download.error && download.error.reputationCheckVerdict) {
+      switch (download.error.reputationCheckVerdict) {
+        case Downloads.Error.BLOCK_VERDICT_UNCOMMON: // fall-through
+        case Downloads.Error.BLOCK_VERDICT_POTENTIALLY_UNWANTED:
+          // Existing higher level attention indication trumps ATTENTION_WARNING.
+          if (this._attention != DownloadsCommon.ATTENTION_SEVERE) {
+            this.attention = DownloadsCommon.ATTENTION_WARNING;
+          }
+          break;
+        case Downloads.Error.BLOCK_VERDICT_MALWARE:
+          this.attention = DownloadsCommon.ATTENTION_SEVERE;
+          break;
+        default:
+          this.attention = DownloadsCommon.ATTENTION_SEVERE;
+          Cu.reportError("Unknown reputation verdict: " +
+                         download.error.reputationCheckVerdict);
+      }
+    } else if (download.succeeded || download.error) {
+      // Existing higher level attention indication trumps ATTENTION_SUCCESS.
+      if (this._attention != DownloadsCommon.ATTENTION_SEVERE &&
+          this._attention != DownloadsCommon.ATTENTION_WARNING) {
+        this.attention = DownloadsCommon.ATTENTION_SUCCESS;
+      }
     }
 
     // Since the state of a download changed, reset the estimated time left.
     this._lastRawTimeLeft = -1;
     this._lastTimeLeft = -1;
   },
 
   onDownloadChanged(download) {
@@ -1184,25 +1210,25 @@ DownloadsIndicatorDataCtor.prototype = {
   /**
    * Indicates whether the download indicators should be highlighted.
    */
   set attention(aValue) {
     this._attention = aValue;
     this._updateViews();
     return aValue;
   },
-  _attention: false,
+  _attention: DownloadsCommon.ATTENTION_NONE,
 
   /**
    * Indicates whether the user is interacting with downloads, thus the
    * attention indication should not be shown even if requested.
    */
   set attentionSuppressed(aValue) {
     this._attentionSuppressed = aValue;
-    this._attention = false;
+    this._attention = DownloadsCommon.ATTENTION_NONE;
     this._updateViews();
     return aValue;
   },
   _attentionSuppressed: false,
 
   /**
    * Computes aggregate values and propagates the changes to our views.
    */
@@ -1222,17 +1248,18 @@ DownloadsIndicatorDataCtor.prototype = {
    * @param aView
    *        DownloadsIndicatorView object to be updated.
    */
   _updateView(aView) {
     aView.hasDownloads = this._hasDownloads;
     aView.counter = this._counter;
     aView.percentComplete = this._percentComplete;
     aView.paused = this._paused;
-    aView.attention = this._attention && !this._attentionSuppressed;
+    aView.attention = this._attentionSuppressed ? DownloadsCommon.ATTENTION_NONE
+                                                : this._attention;
   },
 
   //////////////////////////////////////////////////////////////////////////////
   //// Property updating based on current download status
 
   /**
    * Number of download items that are available to be displayed.
    */
--- a/browser/components/downloads/content/allDownloadsViewOverlay.js
+++ b/browser/components/downloads/content/allDownloadsViewOverlay.js
@@ -497,17 +497,17 @@ function DownloadsPlacesView(aRichListBo
   // Register as a downloads view. The places data will be initialized by
   // the places setter.
   this._initiallySelectedElement = null;
   this._downloadsData = DownloadsCommon.getData(window.opener || window);
   this._downloadsData.addView(this);
 
   // Get the Download button out of the attention state since we're about to
   // view all downloads.
-  DownloadsCommon.getIndicatorData(window).attention = false;
+  DownloadsCommon.getIndicatorData(window).attention = DownloadsCommon.ATTENTION_NONE;
 
   // Make sure to unregister the view if the window is closed.
   window.addEventListener("unload", () => {
     window.controllers.removeController(this);
     this._downloadsData.removeView(this);
     this.result = null;
   }, true);
   // Resizing the window may change items visibility.
--- a/browser/components/downloads/content/download.xml
+++ b/browser/components/downloads/content/download.xml
@@ -69,19 +69,22 @@
         <xul:button class="downloadButton downloadChooseOpen downloadIconShow"
                     tooltiptext="&cmd.chooseOpen.label;"
                     oncommand="DownloadsView.onDownloadCommand(event, 'downloadsCmd_chooseOpen');"/>
       </xul:stack>
     </content>
   </binding>
 
   <binding id="download-toolbarbutton"
-           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton">
+           extends="chrome://global/content/bindings/toolbarbutton.xml#toolbarbutton-badged">
     <content>
-      <children />
-      <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label"/>
+      <xul:stack class="toolbarbutton-badge-stack">
+        <children />
+        <xul:image class="toolbarbutton-icon" xbl:inherits="validate,src=image,label,consumeanchor"/>
+        <xul:label class="toolbarbutton-badge" xbl:inherits="value=badge" top="0" end="0" crop="none"/>
+      </xul:stack>
       <xul:label class="toolbarbutton-text" crop="right" flex="1"
                  xbl:inherits="value=label,accesskey,crop,wrap"/>
       <xul:label class="toolbarbutton-multiline-text" flex="1"
                  xbl:inherits="xbl:text=label,accesskey,wrap"/>
     </content>
   </binding>
 </bindings>
--- a/browser/components/downloads/content/indicator.js
+++ b/browser/components/downloads/content/indicator.js
@@ -233,17 +233,17 @@ const DownloadsIndicatorView = {
     window.removeEventListener("unload", this.onWindowUnload, false);
     DownloadsCommon.getIndicatorData(window).removeView(this);
 
     // Reset the view properties, so that a neutral indicator is displayed if we
     // are visible only temporarily as an anchor.
     this.counter = "";
     this.percentComplete = 0;
     this.paused = false;
-    this.attention = false;
+    this.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   /**
    * Ensures that the user interface elements required to display the indicator
    * are loaded, then invokes the given callback.
    */
   _ensureOperational(aCallback) {
     if (this._operational) {
@@ -461,25 +461,38 @@ const DownloadsIndicatorView = {
    */
   set attention(aValue) {
     if (!this._operational) {
       return this._attention;
     }
 
     if (this._attention != aValue) {
       this._attention = aValue;
-      if (aValue) {
-        this.indicator.setAttribute("attention", "true");
+
+      // Check if the downloads button is in the menu panel, to determine which
+      // button needs to get a badge.
+      let widgetGroup = CustomizableUI.getWidget("downloads-button");
+      let inMenu = widgetGroup.areaType == CustomizableUI.TYPE_MENU_PANEL;
+
+      if (aValue == DownloadsCommon.ATTENTION_NONE) {
+        this.indicator.removeAttribute("attention");
+        if (inMenu) {
+          gMenuButtonBadgeManager.removeBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD);
+        }
       } else {
-        this.indicator.removeAttribute("attention");
+        this.indicator.setAttribute("attention", aValue);
+        if (inMenu) {
+          let badgeClass = "download-" + aValue;
+          gMenuButtonBadgeManager.addBadge(gMenuButtonBadgeManager.BADGEID_DOWNLOAD, badgeClass);
+        }
       }
     }
     return aValue;
   },
-  _attention: false,
+  _attention: DownloadsCommon.ATTENTION_NONE,
 
   //////////////////////////////////////////////////////////////////////////////
   //// User interface event functions
 
   onWindowUnload() {
     // This function is registered as an event listener, we can't use "this".
     DownloadsIndicatorView.ensureTerminated();
   },
--- a/browser/components/downloads/content/indicatorOverlay.xul
+++ b/browser/components/downloads/content/indicatorOverlay.xul
@@ -18,17 +18,17 @@
   <!-- We dynamically add the stack with the progress meter and notification icon,
        originally loaded lazily because of performance reasons, to the existing
        downloads-button. -->
   <toolbarbutton id="downloads-button" indicator="true">
     <!-- The panel's anchor area is smaller than the outer button, but must
          always be visible and must not move or resize when the indicator
          state changes, otherwise the panel could change its position or lose
          its arrow unexpectedly. -->
-    <stack id="downloads-indicator-anchor" class="toolbarbutton-icon"
+    <stack id="downloads-indicator-anchor"
            consumeanchor="downloads-button">
       <vbox id="downloads-indicator-progress-area" pack="center">
         <description id="downloads-indicator-counter"/>
         <progressmeter id="downloads-indicator-progress" class="plain"
                        min="0" max="100"/>
       </vbox>
       <vbox id="downloads-indicator-icon"/>
     </stack>
--- a/browser/locales/en-US/chrome/browser/downloads/downloads.properties
+++ b/browser/locales/en-US/chrome/browser/downloads/downloads.properties
@@ -45,29 +45,29 @@ stateDirty=Blocked: May contain a virus 
 # period.  You may need to adjust "downloadDetails.width" in "downloads.dtd" if
 # this turns out to be longer than the other existing status strings.
 # Note: These strings don't exist in the UI yet.  See bug 1053890.
 blockedMalware=This file contains a virus or malware.
 blockedPotentiallyUnwanted=This file may harm your computer.
 blockedUncommon2=This file is not commonly downloaded.
 
 # LOCALIZATION NOTE (unblockHeaderUnblock, unblockHeaderOpen,
-#                    unblockTypeMalware, unblockTypePotentiallyUnwanted,
-#                    unblockTypeUncommon, unblockTip, unblockButtonOpen,
+#                    unblockTypeMalware, unblockTypePotentiallyUnwanted2,
+#                    unblockTypeUncommon2, unblockTip2, unblockButtonOpen,
 #                    unblockButtonUnblock, unblockButtonConfirmBlock):
 # These strings are displayed in the dialog shown when the user asks a blocked
 # download to be unblocked.  The severity of the threat is expressed in
 # descending order by the unblockType strings, it is higher for files detected
 # as malware and lower for uncommon downloads.
 unblockHeaderUnblock=Are you sure you want to allow this download?
 unblockHeaderOpen=Are you sure you want to open this file?
 unblockTypeMalware=This file contains a virus or other malware that will harm your computer.
-unblockTypePotentiallyUnwanted=This file, disguised as a helpful download, will make unexpected changes to your programs and settings.
-unblockTypeUncommon=This file has been downloaded from an unfamiliar and potentially dangerous website and may not be safe to open.
-unblockTip=You can search for an alternate download source or try to download the file again later.
+unblockTypePotentiallyUnwanted2=This file is disguised as a helpful download, but it can make unexpected changes to your programs and settings.
+unblockTypeUncommon2=This file is not commonly downloaded and may not be safe to open. It may contain a virus or make unexpected changes to your programs and settings.
+unblockTip2=You can search for an alternate download source or try again later.
 unblockButtonOpen=Open
 unblockButtonUnblock=Allow download
 unblockButtonConfirmBlock=Remove file
 
 # LOCALIZATION NOTE (sizeWithUnits):
 # %1$S is replaced with the size number, and %2$S with the measurement unit.
 sizeWithUnits=%1$S %2$S
 sizeUnknown=Unknown size
--- a/browser/themes/linux/downloads/indicator.css
+++ b/browser/themes/linux/downloads/indicator.css
@@ -21,52 +21,76 @@
 
 #downloads-button[cui-areatype="toolbar"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
   min-width: 18px;
   min-height: 18px;
 }
 
-toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"]:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"),
                               0, 198, 18, 180) center no-repeat;
 }
 
-#downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[cui-areatype="toolbar"][attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
 }
 
-toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[cui-areatype="toolbar"][attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar-small.png"),
                               0, 16, 16, 0) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 18, 198, 36, 180);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 18, 198, 36, 180);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -1058,16 +1058,17 @@ toolbar .toolbarbutton-1 > .toolbarbutto
 }
 
 @media (-moz-mac-lion-theme) {
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-badge-stack > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1 > .toolbarbutton-menubutton-button[disabled="true"] > .toolbarbutton-icon,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menu-dropmarker,
   #main-window:not([customizing]) .toolbarbutton-1[disabled="true"] > .toolbarbutton-menubutton-dropmarker,
+  .toolbarbutton-1:not(:hover):-moz-window-inactive > #downloads-indicator-anchor,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-text,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-badge-stack > .toolbarbutton-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menu-dropmarker,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon,
   .toolbarbutton-1:not(:hover):-moz-window-inactive > .toolbarbutton-menubutton-button > .toolbarbutton-icon {
     opacity: .5;
   }
--- a/browser/themes/osx/downloads/indicator.css
+++ b/browser/themes/osx/downloads/indicator.css
@@ -28,47 +28,71 @@
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
 }
 
 toolbar[brighttext] #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
 }
 
-toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"),
                               0, 198, 18, 180) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 0, 198, 18, 180);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar.png"), 36, 198, 54, 180);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted.png"), 36, 198, 54, 180);
 }
 
 @media (min-resolution: 2dppx) {
   #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
     background-size: 18px;
   }
@@ -81,33 +105,33 @@ toolbar[brighttext] #downloads-button:no
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 0, 396, 36, 360);
   }
 
   toolbar[brighttext] #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"),
                                       0, 396, 36, 360);
   }
 
-  #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
   }
 
-  toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+  toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
   }
 
-  #downloads-button[cui-areatype="menu-panel"][attention] {
+  #downloads-button[cui-areatype="menu-panel"][attention="success"] {
     list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel@2x.png");
   }
 
-  #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar@2x.png"), 72, 396, 108, 360);
   }
 
-  toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+  toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
     background-image: -moz-image-rect(url("chrome://browser/skin/Toolbar-inverted@2x.png"), 72, 396, 108, 360);
   }
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -1,135 +1,79 @@
 /* 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/. */
 
-@import url("chrome://global/skin/in-content/common.css");
+@import url("chrome://browser/skin/error-pages.css");
 
 body {
-  display: flex;
-  box-sizing: border-box;
-  min-height: 100vh;
-  padding: 0 48px;
-  align-items: center;
-  justify-content: center;
-}
-
-ul, ol {
-  margin: 0;
-  padding: 0;
-  -moz-margin-start: 1em;
-}
-
-ul > li, ol > li {
-  margin-bottom: .5em;
-}
-
-ul {
-  list-style: disc;
+  background-image: linear-gradient(-45deg, #eeeeee,     #eeeeee 33%,
+                                            #fbfbfb 33%, #fbfbfb 66%,
+                                            #eeeeee 66%, #eeeeee);
 }
 
-#errorPageContainer {
-  position: relative;
-  min-width: 320px;
-  max-width: 512px;
-}
-
-#errorTitle {
-  background: url("chrome://global/skin/icons/info.svg") left 0 no-repeat;
-  background-size: 3em;
-  -moz-margin-start: -5em;
-  -moz-padding-start: 5em;
-}
-
-body.certerror #errorTitle {
-  background-image: url("chrome://browser/skin/cert-error.svg");
+body.certerror {
+  background-image: linear-gradient(-45deg, #f0d000,     #f0d000 33%,
+                                            #fedc00 33%, #fedc00 66%,
+                                            #f0d000 66%, #f0d000);
 }
 
-#errorTitleText {
-  border-bottom: 1px solid #C1C1C1;
-  padding-bottom: 0.4em;
-}
-
-#errorTitleText:-moz-dir(rtl) {
-  background-position: right 0;
-}
-
-#errorTitle[sslv3=true],
-#errorTitle[weakCrypto=true] {
+body.certerror .title {
   background-image: url("cert-error.svg");
 }
 
-#errorTryAgain {
-  margin-top: 1.2em;
-  min-width: 150px
-}
-
 #errorContainer {
   display: none;
 }
 
-@media (max-width: 675px) {
-  #errorTitle,
-  #errorTitle[sslv3=true],
-  #errorTitle[weakCrypto=true] {
-    padding-top: 0;
-    background-image: none;
-    -moz-padding-start: 0;
-    -moz-margin-start: 0;
-  }
-}
-
 /* Pressing the retry button will cause the cursor to flicker from a pointer to
  * not-allowed. Override the disabled cursor behaviour since we will never show
  * the button disabled as the initial state. */
 button:disabled {
   cursor: pointer;
 }
 
 #learnMoreContainer {
   display: none;
 }
 
-#buttonContainer {
+#certErrorButtonContainer {
   display: none;
-  flex-flow: row wrap;
+}
+
+body.certerror #certErrorButtonContainer {
+  display: flex;
 }
 
-#buttonSpacer {
-  flex: 1;
+body.certerror #netErrorButtonContainer {
+  display: none;
+}
+
+#errorTryAgain {
+  margin-top: 1.2em;
+  min-width: 150px;
 }
 
 #returnButton {
-  background-color: var(--in-content-primary-button-background);
-  border: none;
-  color: var(--in-content-selected-text);
   min-width: 250px;
-  margin-inline-start: 0;
-}
-
-#returnButton:hover {
-  background-color: var(--in-content-primary-button-background-hover) !important;
-}
-
-#returnButton:hover:active {
-  background-color: var(--in-content-primary-button-background-active) !important;
 }
 
 #advancedButton {
   display: none;
-  min-width: 150px;
+}
+
+body.certerror #advancedButton {
+  display: block;
 }
 
 #certificateErrorReporting {
   display: none;
 }
 
-#weakCryptoAdvancedPanel,
-#badCertAdvancedPanel {
+.advanced-panel {
   /* Hidden until the link is clicked */
   display: none;
   background-color: white;
   border: 1px lightgray solid;
   /* Don't use top padding because the default p style has top padding, and it
    * makes the overall div look uneven */
   padding: 0 12px 12px 12px;
   box-shadow: 0 0 4px #ddd;
@@ -150,23 +94,23 @@ button:disabled {
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
 }
 
-body:not(.certerror) #errorCode {
+#errorCode:not([href]) {
   color: var(--in-content-page-color);
   cursor: text;
   text-decoration: none;
 }
 
-body.certerror #errorCode {
+#errorCode[href] {
   white-space: nowrap;
 }
 
 #badCertTechnicalInfo {
   overflow: auto;
   white-space: pre-wrap;
 }
 
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/blockedSite.css
@@ -0,0 +1,65 @@
+/* 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/. */
+
+@import url("chrome://browser/skin/error-pages.css");
+
+body {
+  background-image: linear-gradient(-45deg, #9b2e2e,     #9b2e2e 33%,
+                                            #a83232 33%, #a83232 66%,
+                                            #9b2e2e 66%, #9b2e2e);
+  background-color: #b14646;
+  color: white;
+}
+
+.title {
+  background-image: url("chrome://global/skin/icons/blocked.svg");
+}
+
+.title-text {
+  color: white;
+}
+
+.button-container button:not(.primary) {
+  background-color: transparent;
+  color: white;
+  border: 1px solid #9b2e2e;
+  margin-inline-end: 0;
+}
+
+.button-container button:not(.primary):hover {
+  background-color: #a83232;
+}
+
+.button-container button:not(.primary):active {
+  background-color: #9b2e2e;
+}
+
+.button-container button {
+  margin-top: 1.2em;
+}
+
+/* Style warning button to look like a small text link in the
+   bottom right. This is preferable to just using a text link
+   since there is already a mechanism in browser.js for trapping
+   oncommand events from unprivileged chrome pages (BrowserOnCommand).*/
+#ignoreWarningButton {
+  -moz-appearance: none;
+  background: transparent;
+  border: none;
+  color: white;
+  text-decoration: underline;
+  margin: 4px 0 0 0;
+  padding: 0;
+  font-size: smaller;
+  min-width: 0;
+}
+
+#ignoreWarningButton:hover {
+  cursor: pointer;
+}
+
+#ignoreWarning {
+  margin-top: 1.2em;
+  text-align: end;
+}
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -106,28 +106,38 @@
   border: none;
 }
 
 #PanelUI-menu-button[badge-status="update-succeeded"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   background: #74BF43 url(chrome://browser/skin/update-badge.svg) no-repeat center;
   height: 13px;
 }
 
+#PanelUI-menu-button[badge-status="download-severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="update-failed"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
   background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
   height: 13px;
 }
 
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
 #PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
-  background: transparent url(chrome://browser/skin/warning.svg) no-repeat center;
   height: 13px;
   box-shadow: none;
   filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
 }
 
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: transparent url(chrome://browser/skin/warning.svg) no-repeat center;
+}
+
+#PanelUI-menu-button[badge-status="download-warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
 #PanelUI-menu-button[badge-status="fxa-needs-authentication"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
   filter: none;
 }
 
 .panel-subviews {
   padding: 4px;
   background-clip: padding-box;
   border-left: 1px solid hsla(210,4%,10%,.3);
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/error-pages.css
@@ -0,0 +1,76 @@
+/* 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/. */
+
+@import url("chrome://global/skin/in-content/info-pages.css");
+
+body {
+  background-size: 64px 32px;
+  background-repeat: repeat-x;
+  /* Top padding for when the window height is small.
+     Bottom padding to keep everything centered. */
+  padding: 75px 0;
+}
+
+.button-container {
+  display: flex;
+  flex-flow: row wrap;
+}
+
+.button-spacer {
+  flex: 1;
+}
+
+@media only screen and (max-width: 959px) {
+  body {
+    padding: 75px 48px;
+  }
+
+  .title {
+    background-image: none !important;
+    -moz-padding-start: 0;
+    -moz-margin-start: 0;
+  }
+
+  .title-text {
+    padding-top: 0;
+  }
+}
+
+@media only screen and (max-width: 640px) {
+  body {
+    justify-content: unset;
+    /* Now that everything is top-aligned, we don't need the
+     * bottom padding for centering - though it's added back
+     * when the viewport height is < 480px (see below). */
+    padding: 75px 20px 0;
+  }
+
+  .title-text {
+    padding-bottom: 0;
+    border-bottom: none;
+  }
+}
+
+@media only screen and (max-width: 480px) {
+  .button-container button {
+    /* Force buttons to display: block here to try and enforce collapsing margins */
+    display: block;
+    width: 100%;
+    margin: 0.66em 0 0;
+  }
+}
+
+/* For small window height, shift the stripes up by 10px.
+ * We could just change the background size, but that changes
+ * the angle of the stripes so just shifting up is easier. */
+@media only screen and (max-height: 480px) {
+  body {
+    background-position: 10px -10px;
+    padding-top: 38px;
+    /* We get rid of bottom padding for width < 640px, but
+     * for height < 480px a bit of space between the content
+     * and the viewport edge is nice. */
+    padding-bottom: 38px;
+  }
+}
\ No newline at end of file
--- a/browser/themes/shared/identity-block/identity-block.inc.css
+++ b/browser/themes/shared/identity-block/identity-block.inc.css
@@ -42,16 +42,21 @@
   background-color: var(--identity-box-hover-background-color);
 }
 
 #identity-box:hover:active,
 #identity-box[open=true] {
   background-color: var(--identity-box-selected-background-color);
 }
 
+#identity-box:hover > :not(#identity-icon),
+#identity-box[open=true] > :not(#identity-icon) {
+  filter: grayscale(100%);
+}
+
 #urlbar[pageproxystate="valid"] > #identity-box.verifiedIdentity {
   color: var(--identity-box-verified-color);
 }
 
 #urlbar[pageproxystate="valid"] > #identity-box.chromeUI {
   color: var(--identity-box-chrome-color);
 }
 
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -3,16 +3,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # This is not a complete / proper jar manifest. It is included by the
 # actual theme-specific manifests, so that shared resources need only
 # be specified once. As a result, the source file paths are relative
 # to the location of the actual manifest.
 
   skin/classic/browser/aboutNetError.css                       (../shared/aboutNetError.css)
+  skin/classic/browser/blockedSite.css                         (../shared/blockedSite.css)
+  skin/classic/browser/error-pages.css                         (../shared/error-pages.css)
 * skin/classic/browser/aboutProviderDirectory.css              (../shared/aboutProviderDirectory.css)
 * skin/classic/browser/aboutSessionRestore.css                 (../shared/aboutSessionRestore.css)
   skin/classic/browser/aboutSocialError.css                    (../shared/aboutSocialError.css)
   skin/classic/browser/aboutTabCrashed.css                     (../shared/aboutTabCrashed.css)
   skin/classic/browser/aboutWelcomeBack.css                    (../shared/aboutWelcomeBack.css)
   skin/classic/browser/addons/addon-install-blocked.svg        (../shared/addons/addon-install-blocked.svg)
   skin/classic/browser/addons/addon-install-confirm.svg        (../shared/addons/addon-install-confirm.svg)
   skin/classic/browser/addons/addon-install-downloading.svg    (../shared/addons/addon-install-downloading.svg)
--- a/browser/themes/windows/downloads/indicator.css
+++ b/browser/themes/windows/downloads/indicator.css
@@ -39,50 +39,74 @@
 
 #downloads-indicator-icon {
   background: var(--downloads-indicator-icon) center no-repeat;
   width: 18px;
   height: 18px;
   background-size: 18px;
 }
 
-toolbar[brighttext] #downloads-button:not([attention]) > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button:not([attention="success"]) > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-inverted);
 }
 
-#downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge,
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  display: -moz-box;
+  height: 13px;
+  width: 13px;
+  background-size: contain;
+  border: none;
+  box-shadow: none;
+  filter: drop-shadow(0 1px 0 hsla(206, 50%, 10%, .15));
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #D90000 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge {
+  background: #FFBF00 url(chrome://browser/skin/update-badge-failed.svg) no-repeat center;
+}
+
+#downloads-button[attention="severe"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive,
+#downloads-button[attention="warning"] > .toolbarbutton-badge-stack > .toolbarbutton-badge:-moz-window-inactive {
+  filter: none;
+}
+
+#downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-attention);
 }
 
-toolbar[brighttext] #downloads-button[attention] > #downloads-indicator-anchor > #downloads-indicator-icon {
+toolbar[brighttext] #downloads-button[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-icon {
   background-image: var(--downloads-indicator-icon-attention-inverted);
 }
 
-#downloads-button[cui-areatype="menu-panel"][attention] {
+#downloads-button[cui-areatype="menu-panel"][attention="success"] {
   list-style-image: url("chrome://browser/skin/downloads/download-glow-menuPanel.png");
   -moz-image-region: auto;
 }
 
 /* In the next few rules, we use :not([counter]) as a shortcut that is
    equivalent to -moz-any([progress], [paused]). */
 
 #downloads-button:not([counter]) > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background: var(--downloads-indicator-icon) center no-repeat;
   background-size: 12px;
 }
 
-toolbar[brighttext] #downloads-button:not([counter]):not([attention]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter]):not([attention="success"]) > #downloads-indicator-anchor > #downloads-button-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-inverted);
 }
 
-#downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+#downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-attention);
 }
 
-toolbar[brighttext] #downloads-button:not([counter])[attention] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
+toolbar[brighttext] #downloads-button:not([counter])[attention="success"] > #downloads-indicator-anchor > #downloads-indicator-progress-area > #downloads-indicator-counter {
   background-image: var(--downloads-indicator-icon-attention-inverted);
 }
 
 /*** Download notifications ***/
 
 #downloads-indicator-notification {
   opacity: 0;
   background-size: 16px;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-panels.js
@@ -21,17 +21,17 @@ function test() {
     // Store and enable all optional dev tools panels
     yield pushPrefs(...PREFS);
 
     let addon = yield addAddon(ADDON_URL);
     let addonDebugger = yield initAddonDebugger(ADDON_URL);
 
     // Check only valid tabs are shown
     let tabs = addonDebugger.frame.contentDocument.getElementById("toolbox-tabs").children;
-    let expectedTabs = ["webconsole", "jsdebugger", "scratchpad"];
+    let expectedTabs = ["webconsole", "jsdebugger", "scratchpad", "dom"];
 
     is(tabs.length, expectedTabs.length, "displaying only " + expectedTabs.length + " tabs in addon debugger");
     Array.forEach(tabs, (tab, i) => {
       let toolName = expectedTabs[i];
       is(tab.getAttribute("toolid"), toolName, "displaying " + toolName);
     });
 
     // Check no toolbox buttons are shown
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -18,45 +18,48 @@ loader.lazyGetter(this, "StyleEditorPane
 loader.lazyGetter(this, "ShaderEditorPanel", () => require("devtools/client/shadereditor/panel").ShaderEditorPanel);
 loader.lazyGetter(this, "CanvasDebuggerPanel", () => require("devtools/client/canvasdebugger/panel").CanvasDebuggerPanel);
 loader.lazyGetter(this, "WebAudioEditorPanel", () => require("devtools/client/webaudioeditor/panel").WebAudioEditorPanel);
 loader.lazyGetter(this, "MemoryPanel", () => require("devtools/client/memory/panel").MemoryPanel);
 loader.lazyGetter(this, "PerformancePanel", () => require("devtools/client/performance/panel").PerformancePanel);
 loader.lazyGetter(this, "NetMonitorPanel", () => require("devtools/client/netmonitor/panel").NetMonitorPanel);
 loader.lazyGetter(this, "StoragePanel", () => require("devtools/client/storage/panel").StoragePanel);
 loader.lazyGetter(this, "ScratchpadPanel", () => require("devtools/client/scratchpad/scratchpad-panel").ScratchpadPanel);
+loader.lazyGetter(this, "DomPanel", () => require("devtools/client/dom/dom-panel").DomPanel);
 
 // Strings
 const toolboxProps = "chrome://devtools/locale/toolbox.properties";
 const inspectorProps = "chrome://devtools/locale/inspector.properties";
 const webConsoleProps = "chrome://devtools/locale/webconsole.properties";
 const debuggerProps = "chrome://devtools/locale/debugger.properties";
 const styleEditorProps = "chrome://devtools/locale/styleeditor.properties";
 const shaderEditorProps = "chrome://devtools/locale/shadereditor.properties";
 const canvasDebuggerProps = "chrome://devtools/locale/canvasdebugger.properties";
 const webAudioEditorProps = "chrome://devtools/locale/webaudioeditor.properties";
 const performanceProps = "chrome://devtools/locale/performance.properties";
 const netMonitorProps = "chrome://devtools/locale/netmonitor.properties";
 const storageProps = "chrome://devtools/locale/storage.properties";
 const scratchpadProps = "chrome://devtools/locale/scratchpad.properties";
 const memoryProps = "chrome://devtools/locale/memory.properties";
+const domProps = "chrome://devtools/locale/dom.properties";
 
 loader.lazyGetter(this, "toolboxStrings", () => Services.strings.createBundle(toolboxProps));
 loader.lazyGetter(this, "performanceStrings", () => Services.strings.createBundle(performanceProps));
 loader.lazyGetter(this, "webConsoleStrings", () => Services.strings.createBundle(webConsoleProps));
 loader.lazyGetter(this, "debuggerStrings", () => Services.strings.createBundle(debuggerProps));
 loader.lazyGetter(this, "styleEditorStrings", () => Services.strings.createBundle(styleEditorProps));
 loader.lazyGetter(this, "shaderEditorStrings", () => Services.strings.createBundle(shaderEditorProps));
 loader.lazyGetter(this, "canvasDebuggerStrings", () => Services.strings.createBundle(canvasDebuggerProps));
 loader.lazyGetter(this, "webAudioEditorStrings", () => Services.strings.createBundle(webAudioEditorProps));
 loader.lazyGetter(this, "inspectorStrings", () => Services.strings.createBundle(inspectorProps));
 loader.lazyGetter(this, "netMonitorStrings", () => Services.strings.createBundle(netMonitorProps));
 loader.lazyGetter(this, "storageStrings", () => Services.strings.createBundle(storageProps));
 loader.lazyGetter(this, "scratchpadStrings", () => Services.strings.createBundle(scratchpadProps));
 loader.lazyGetter(this, "memoryStrings", () => Services.strings.createBundle(memoryProps));
+loader.lazyGetter(this, "domStrings", () => Services.strings.createBundle(domProps));
 
 var Tools = {};
 exports.Tools = Tools;
 
 // Definitions
 Tools.options = {
   id: "options",
   ordinal: 0,
@@ -392,30 +395,58 @@ Tools.scratchpad = {
     return target.hasActor("console");
   },
 
   build: function(iframeWindow, toolbox) {
     return new ScratchpadPanel(iframeWindow, toolbox);
   }
 };
 
+Tools.dom = {
+  id: "dom",
+  accesskey: l10n("dom.accesskey", domStrings),
+  key: l10n("dom.commandkey", domStrings),
+  ordinal: 13,
+  modifiers: osString == "Darwin" ? "accel,alt" : "accel,shift",
+  visibilityswitch: "devtools.dom.enabled",
+  icon: "chrome://devtools/skin/images/tool-dom.svg",
+  invertIconForLightTheme: true,
+  url: "chrome://devtools/content/dom/dom.html",
+  label: l10n("dom.label", domStrings),
+  panelLabel: l10n("dom.panelLabel", domStrings),
+  get tooltip() {
+    return l10n("dom.tooltip", domStrings,
+    (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
+  },
+  inMenu: true,
+
+  isTargetSupported: function(target) {
+    return target.getTrait("webConsoleCommands");
+  },
+
+  build: function(iframeWindow, toolbox) {
+    return new DomPanel(iframeWindow, toolbox);
+  }
+};
+
 var defaultTools = [
   Tools.options,
   Tools.webConsole,
   Tools.inspector,
   Tools.jsdebugger,
   Tools.styleEditor,
   Tools.shaderEditor,
   Tools.canvasDebugger,
   Tools.webAudioEditor,
   Tools.performance,
   Tools.netMonitor,
   Tools.storage,
   Tools.scratchpad,
   Tools.memory,
+  Tools.dom,
 ];
 
 exports.defaultTools = defaultTools;
 
 Tools.darkTheme = {
   id: "dark",
   label: l10n("options.darkTheme.label2", toolboxStrings),
   ordinal: 1,
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/.eslintrc
@@ -0,0 +1,15 @@
+{
+  "globals": {
+    "XMLHttpRequest": true,
+    "window": true,
+    "define": true,
+    "addEventListener": true,
+    "document": true,
+    "dispatchEvent": true,
+    "MessageEvent": true
+  },
+  "rules": {
+    "indent": 0,
+    "padded-blocks": 0,
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/filter.js
@@ -0,0 +1,21 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const constants = require("../constants");
+
+/**
+ * Used to filter DOM panel content.
+ */
+function setVisibilityFilter(filter) {
+  return {
+    filter: filter,
+    type: constants.SET_VISIBILITY_FILTER,
+  };
+}
+
+// Exports from this module
+exports.setVisibilityFilter = setVisibilityFilter;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/grips.js
@@ -0,0 +1,54 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+ /* globals DomProvider */
+"use strict";
+
+const constants = require("../constants");
+
+/**
+ * Used to fetch grip prototype and properties from the backend.
+ */
+function requestProperties(grip) {
+  return {
+    grip: grip,
+    type: constants.FETCH_PROPERTIES,
+    status: "start",
+    error: false
+  };
+}
+
+/**
+ * Executed when grip properties are received from the backend.
+ */
+function receiveProperties(grip, response, error) {
+  return {
+    grip: grip,
+    type: constants.FETCH_PROPERTIES,
+    status: "end",
+    response: response,
+    error: error
+  };
+}
+
+/**
+ * Used to get properties from the backend and fire an action
+ * when they are received.
+ */
+function fetchProperties(grip) {
+  return dispatch => {
+    // dispatch(requestProperties(grip));
+
+    // Use 'DomProvider' object exposed from the chrome scope.
+    return DomProvider.getPrototypeAndProperties(grip).then(response => {
+      dispatch(receiveProperties(grip, response));
+    });
+  };
+}
+
+// Exports from this module
+exports.requestProperties = requestProperties;
+exports.receiveProperties = receiveProperties;
+exports.fetchProperties = fetchProperties;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/actions/moz.build
@@ -0,0 +1,9 @@
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'filter.js',
+    'grips.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -0,0 +1,90 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+// Reps
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+const { Grip } = require("devtools/client/shared/components/reps/grip");
+
+// DOM Panel
+const { GripProvider } = require("../grip-provider");
+const { DomDecorator } = require("../dom-decorator");
+
+// Shortcuts
+const PropTypes = React.PropTypes;
+
+/**
+ * Renders DOM panel tree.
+ */
+var DomTree = React.createClass({
+  propTypes: {
+    object: PropTypes.any,
+    filter: PropTypes.string,
+    dispatch: PropTypes.func.isRequired,
+    grips: PropTypes.object,
+  },
+
+  displayName: "DomTree",
+
+  /**
+   * Filter DOM properties. Return true if the object
+   * should be visible in the tree.
+   */
+  onFilter: function(object) {
+    if (!this.props.filter) {
+      return true;
+    }
+
+    return (object.name && object.name.indexOf(this.props.filter) > -1);
+  },
+
+  /**
+   * Render DOM panel content
+   */
+  render: function() {
+    let columns = [{
+      "id": "value"
+    }];
+
+    // This is the integration point with Reps. The DomTree is using
+    // Reps to render all values. The code also specifies default rep
+    // used for data types that don't have its own specific template.
+    let renderValue = props => {
+      return Rep(Object.assign({}, props, {
+        defaultRep: Grip,
+      }));
+    };
+
+    return (
+      TreeView({
+        object: this.props.object,
+        provider: new GripProvider(this.props.grips, this.props.dispatch),
+        decorator: new DomDecorator(),
+        mode: "short",
+        columns: columns,
+        renderValue: renderValue,
+        onFilter: this.onFilter
+      })
+    );
+  }
+});
+
+const mapStateToProps = (state) => {
+  return {
+    grips: state.grips,
+    filter: state.filter
+  };
+};
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(DomTree);
+
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/main-frame.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+// DOM Panel
+const DomTree = React.createFactory(require("./dom-tree"));
+const MainToolbar = React.createFactory(require("./main-toolbar"));
+
+// Shortcuts
+const { div } = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * Renders basic layout of the DOM panel. The DOM panel cotent consists
+ * from two main parts: toolbar and tree.
+ */
+var MainFrame = React.createClass({
+  propTypes: {
+    object: PropTypes.any,
+    filter: PropTypes.string,
+    dispatch: PropTypes.func.isRequired,
+  },
+
+  displayName: "MainFrame",
+
+  /**
+   * Render DOM panel content
+   */
+  render: function() {
+    return (
+      div({className: "mainFrame"},
+        MainToolbar({
+          dispatch: this.props.dispatch,
+          object: this.props.object
+        }),
+        DomTree({
+          object: this.props.object,
+          filter: this.props.filter,
+        })
+      )
+    );
+  }
+});
+
+// Transform state into props
+// Note: use https://github.com/faassen/reselect for better performance.
+const mapStateToProps = (state) => {
+  return {
+    filter: state.filter
+  };
+};
+
+// Exports from this module
+module.exports = connect(mapStateToProps)(MainFrame);
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -0,0 +1,63 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// React
+const React = require("devtools/client/shared/vendor/react");
+const { l10n } = require("../utils");
+
+// Reps
+const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
+
+// DOM Panel
+const SearchBox = React.createFactory(require("../components/search-box"));
+
+// Actions
+const { fetchProperties } = require("../actions/grips");
+const { setVisibilityFilter } = require("../actions/filter");
+
+// Shortcuts
+const PropTypes = React.PropTypes;
+
+/**
+ * This template is responsible for rendering a toolbar
+ * within the 'Headers' panel.
+ */
+var MainToolbar = React.createClass({
+  propTypes: {
+    object: PropTypes.any.isRequired,
+    dispatch: PropTypes.func.isRequired,
+  },
+
+  displayName: "MainToolbar",
+
+  onRefresh: function() {
+    this.props.dispatch(fetchProperties(this.props.object));
+  },
+
+  onSearch: function(value) {
+    this.props.dispatch(setVisibilityFilter(value));
+  },
+
+  render: function() {
+    return (
+      Toolbar({},
+        ToolbarButton({
+          className: "btn refresh",
+          onClick: this.onRefresh},
+          l10n.getStr("dom.refresh")
+        ),
+        SearchBox({
+          onSearch: this.onSearch
+        })
+      )
+    );
+  }
+});
+
+// Exports from this module
+module.exports = MainToolbar;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/moz.build
@@ -0,0 +1,13 @@
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'dom-tree.js',
+    'main-frame.js',
+    'main-toolbar.js',
+    'search-box.css',
+    'search-box.js',
+    'search.svg',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search-box.css
@@ -0,0 +1,46 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/******************************************************************************/
+/* Search Box */
+
+.searchBox {
+  height: 18px;
+  font-size: 12px;
+  margin-top: 0;
+  border: 1px solid rgb(170, 188, 207);
+  width: 200px;
+  float: right;
+  background-image: url("./search.svg");
+  background-repeat: no-repeat;
+  background-position: 2px center;
+  padding-left: 20px;
+  margin-right: 5px;
+}
+
+/******************************************************************************/
+/* Light Theme & Dark Theme*/
+
+.theme-dark .searchBox,
+.theme-light .searchBox {
+  border: 1px solid rgb(170, 170, 170);
+  background-image: url("chrome://devtools/skin/images/magnifying-glass-light.png");
+  background-position: 8px center;
+  border-radius: 2px;
+  padding-left: 25px;
+  margin-top: 1px;
+  height: 16px;
+  font-style: italic;
+}
+
+/******************************************************************************/
+/* Dark Theme */
+
+.theme-dark .searchBox {
+  background-color: rgba(24, 29, 32, 1);
+  color: rgba(184, 200, 217, 1);
+  border-color: var(--theme-splitter-color);
+  background-image: url("chrome://devtools/skin/images/magnifying-glass.png");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search-box.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const { l10n } = require("../utils");
+
+// For smooth incremental searching (in case the user is typing quickly).
+const searchDelay = 250;
+
+// Shortcuts
+const { input } = React.DOM;
+const PropTypes = React.PropTypes;
+
+/**
+ * This object represents a search box located at the
+ * top right corner of the application.
+ */
+var SearchBox = React.createClass({
+  propTypes: {
+    onSearch: PropTypes.func,
+  },
+
+  displayName: "SearchBox",
+
+  componentWillUnmount: function() {
+    // Clean up an existing timeout.
+    if (this.searchTimeout) {
+      window.clearTimeout(this.searchTimeout);
+    }
+  },
+
+  onSearch: function(event) {
+    let searchBox = event.target;
+
+    // Clean up an existing timeout before creating a new one.
+    if (this.searchTimeout) {
+      window.clearTimeout(this.searchTimeout);
+    }
+
+    // Execute the search after a timeout. It makes the UX
+    // smoother if the user is typing quickly.
+    this.searchTimeout = window.setTimeout(() => {
+      this.searchTimeout = null;
+      this.props.onSearch(searchBox.value);
+    }, searchDelay);
+  },
+
+  render: function() {
+    return (
+      input({
+        className: "searchBox",
+        placeholder: l10n.getStr("dom.filterDOMPanel"),
+        onChange: this.onSearch
+      })
+    );
+  }
+});
+
+// Exports from this module
+module.exports = SearchBox;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/components/search.svg
@@ -0,0 +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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" xmlns:xlink="http://www.w3.org/1999/xlink">
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#427dc2"/>
+      <stop offset="1" stop-color="#5e9fce"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#2f5d93"/>
+      <stop offset="1" stop-color="#3a87bd"/>
+    </linearGradient>
+    <filter id="c" width="1.239" height="1.241" x="-.12" y="-.12" color-interpolation-filters="sRGB">
+      <feGaussianBlur stdDeviation=".637"/>
+    </filter>
+    <linearGradient id="d" x1="4.094" x2="4.094" y1="13.423" y2="2.743" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+    <linearGradient id="e" x1="8.711" x2="8.711" y1="13.58" y2="2.566" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path fill="#fff" stroke="#fff" stroke-width="1.5" d="M10.14 1.656c-2.35 0-4.25 1.9-4.25 4.25 0 .752.19 1.45.532 2.063L1.61 12.78l1.562 1.564 4.78-4.78c.64.384 1.387.592 2.19.592 2.35 0 4.25-1.9 4.25-4.25s-1.9-4.25-4.25-4.25zm0 1.532c1.504 0 2.72 1.214 2.72 2.718s-1.216 2.72-2.72 2.72c-1.503 0-2.718-1.216-2.718-2.72 0-1.504 1.215-2.718 2.72-2.718z" stroke-linejoin="round" filter="url(#c)"/>
+  <path fill="url(#d)" stroke="url(#e)" stroke-width=".6" d="M10 2C7.79 2 6 3.79 6 6c0 .828.256 1.612.688 2.25l-4.875 4.875 1.062 1.063L7.75 9.31C8.388 9.745 9.172 10 10 10c2.21 0 4-1.79 4-4s-1.79-4-4-4zm0 1c1.657 0 3 1.343 3 3s-1.343 3-3 3-3-1.343-3-3 1.343-3 3-3z" stroke-linejoin="round"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/constants.js
@@ -0,0 +1,9 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+exports.FETCH_PROPERTIES = "FETCH_PROPERTIES";
+exports.SET_VISIBILITY_FILTER = "SET_VISIBILITY_FILTER";
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-decorator.js
@@ -0,0 +1,50 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const { Property } = require("./reducers/grips");
+
+// Implementation
+
+function DomDecorator() {
+}
+
+/**
+ * Decorator for DOM panel tree component. It's responsible for
+ * appending an icon to read only properties.
+ */
+DomDecorator.prototype = {
+  getRowClass: function(object) {
+    if (object instanceof Property) {
+      let value = object.value;
+      let names = [];
+
+      if (value.enumerable) {
+        names.push("enumerable");
+      }
+      if (value.writable) {
+        names.push("writable");
+      }
+      if (value.configurable) {
+        names.push("configurable");
+      }
+
+      return names;
+    }
+
+    return null;
+  },
+
+  /**
+   * Return custom React template for specified object. The template
+   * might depend on specified column.
+   */
+  getValueRep: function(value, colId) {
+  }
+};
+
+// Exports from this module
+exports.DomDecorator = DomDecorator;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-view.css
@@ -0,0 +1,111 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+/******************************************************************************/
+/* General */
+
+body {
+  padding: 0;
+  margin: 0;
+  height: 100%;
+}
+
+/******************************************************************************/
+/* TreeView Customization */
+
+.treeTable {
+  width: 100%;
+}
+
+/* Space for read only properties icon */
+.treeTable td.treeValueCell {
+  padding-left: 16px;
+}
+
+/* Read only properties have a padlock icon */
+.treeTable tr:not(.writable) td.treeValueCell {
+  background: url("chrome://devtools/skin/images/firebug/read-only.svg") no-repeat;
+  background-position: 1px 5px;
+  background-size: 10px 10px;
+}
+
+/* Non-enumerable properties are grayed out */
+.treeTable tr:not(.enumerable) td.treeValueCell {
+  opacity: 0.7;
+}
+
+.treeTable > tbody > tr > td {
+  border-bottom: 1px solid #EFEFEF;
+}
+
+/* Label Types */
+.treeTable .userLabel,
+.treeTable .userClassLabel,
+.treeTable .userFunctionLabel {
+  font-weight: bold;
+}
+
+.treeTable .userLabel {
+  color: #000000;
+}
+
+.treeTable .userClassLabel {
+  color: #E90000;
+}
+
+.treeTable .userFunctionLabel {
+  color: #025E2A;
+}
+
+.treeTable .domLabel {
+  color: #000000;
+}
+
+.treeTable .domClassLabel {
+  color: #E90000;
+}
+
+.treeTable .domFunctionLabel {
+  color: #025E2A;
+}
+
+.treeTable .ordinalLabel {
+  color: SlateBlue;
+  font-weight: bold;
+}
+
+/******************************************************************************/
+/* Selection */
+
+.treeTable .treeRow:hover a,
+.treeTable .treeRow:hover span {
+  color: var(--theme-selection-color) !important;
+}
+
+/******************************************************************************/
+/* Toolbar */
+
+.toolbar {
+  position: fixed;
+  width: 100%;
+  top: 0;
+  z-index: 2;
+}
+
+.treeTable {
+  z-index: 1;
+  margin-top: 25px;
+}
+
+/******************************************************************************/
+/* Theme Dark */
+
+.theme-dark .treeTable > tbody > tr > td {
+  border-bottom: none;
+}
+
+.theme-dark body {
+  background-color: var(--theme-body-background);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/dom-view.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// React & Redux
+const React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+const { combineReducers } = require("devtools/client/shared/vendor/redux");
+
+// DOM Panel
+const MainFrame = React.createFactory(require("./components/main-frame"));
+
+// Store
+const createStore = require("devtools/client/shared/redux/create-store")({
+  log: false
+});
+
+const { reducers } = require("./reducers/index");
+const store = createStore(combineReducers(reducers));
+
+/**
+ * This object represents view of the DOM panel and is responsible
+ * for rendering the content. It renders the top level ReactJS
+ * component: the MainFrame.
+ */
+function DomView(localStore) {
+  addEventListener("devtools/chrome/message",
+    this.onMessage.bind(this), true);
+
+  // Make it local so, tests can access it.
+  this.store = localStore;
+}
+
+DomView.prototype = {
+  initialize: function(rootGrip) {
+    let content = document.querySelector("#content");
+    let mainFrame = MainFrame({
+      object: rootGrip,
+    });
+
+    // Render top level component
+    let provider = React.createElement(Provider, {
+      store: this.store
+    }, mainFrame);
+
+    this.mainFrame = ReactDOM.render(provider, content);
+  },
+
+  onMessage: function(event) {
+    let data = event.data;
+    let method = data.type;
+
+    if (typeof this[method] == "function") {
+      this[method](data.args);
+    }
+  },
+};
+
+// Construct DOM panel view object and expose it to tests.
+// Tests can access it throught: |panel.panelWin.view|
+window.view = new DomView(store);
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/grip-provider.js
@@ -0,0 +1,97 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const { fetchProperties } = require("./actions/grips");
+const { Property } = require("./reducers/grips");
+
+// Implementation
+function GripProvider(grips, dispatch) {
+  this.grips = grips;
+  this.dispatch = dispatch;
+}
+
+/**
+ * This object provides data for the tree displayed in the tooltip
+ * content.
+ */
+GripProvider.prototype = {
+  /**
+   * Fetches properties from the backend. These properties might be
+   * displayed as child objects in e.g. a tree UI widget.
+   */
+  getChildren: function(object) {
+    let grip = object;
+    if (object instanceof Property) {
+      grip = this.getValue(object);
+    }
+
+    if (!grip || !grip.actor) {
+      return [];
+    }
+
+    let props = this.grips.get(grip.actor);
+    if (!props) {
+      // Fetch missing data from the backend. Returning a promise
+      // from data provider causes the tree to show a spinner.
+      return this.dispatch(fetchProperties(grip));
+    }
+
+    return props;
+  },
+
+  hasChildren: function(object) {
+    if (object instanceof Property) {
+      let value = this.getValue(object);
+      if (!value) {
+        return false;
+      }
+
+      let hasChildren = value.ownPropertyLength > 0;
+
+      if (value.preview) {
+        hasChildren = hasChildren || value.preview.ownPropertiesLength > 0;
+      }
+
+      if (value.preview) {
+        let preview = value.preview;
+        let k = preview.kind;
+        let objectsWithProps = ["DOMNode", "ObjectWithURL"];
+        hasChildren = hasChildren || (objectsWithProps.indexOf(k) != -1);
+        hasChildren = hasChildren || (k == "ArrayLike" && preview.length > 0);
+      }
+
+      return (value.type == "object" && hasChildren);
+    }
+
+    return null;
+  },
+
+  getValue: function(object) {
+    if (object instanceof Property) {
+      let value = object.value;
+      return (typeof value.value != "undefined") ? value.value :
+        value.getterValue;
+    }
+
+    return object;
+  },
+
+  getLabel: function(object) {
+    return (object instanceof Property) ? object.name : null;
+  },
+
+  getKey: function(object) {
+    return (object instanceof Property) ? object.key : null;
+  },
+
+  getType: function(object) {
+    return object.class ? object.class : "";
+  },
+};
+
+// Exports from this module
+exports.GripProvider = GripProvider;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/moz.build
@@ -0,0 +1,19 @@
+# vim: set filetype=python:
+# 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/.
+
+DIRS += [
+    'actions',
+    'components',
+    'reducers',
+]
+
+DevToolsModules(
+    'constants.js',
+    'dom-decorator.js',
+    'dom-view.css',
+    'dom-view.js',
+    'grip-provider.js',
+    'utils.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/filter.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const constants = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return "";
+}
+
+/**
+ * Filter displayed object properties.
+ */
+function filter(state = getInitialState(), action) {
+  if (action.type == constants.SET_VISIBILITY_FILTER) {
+    return action.filter;
+  }
+
+  return state;
+}
+
+// Exports from this module
+exports.filter = filter;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/grips.js
@@ -0,0 +1,111 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const constants = require("../constants");
+
+/**
+ * Initial state definition
+ */
+function getInitialState() {
+  return new Map();
+}
+
+/**
+ * Maintain a cache of received grip responses from the backend.
+ */
+function grips(state = getInitialState(), action) {
+  // This reducer supports only one action, fetching actor properties
+  // from the backend so, bail out if we are dealing with any other
+  // action.
+  if (action.type != constants.FETCH_PROPERTIES) {
+    return state;
+  }
+
+  switch (action.status) {
+    case "start":
+      return onRequestProperties(state, action);
+    case "end":
+      return onReceiveProperties(state, action);
+  }
+
+  return state;
+}
+
+/**
+ * Handle requestProperties action
+ */
+function onRequestProperties(state, action) {
+  return state;
+}
+
+/**
+ * Handle receiveProperties action
+ */
+function onReceiveProperties(cache, action) {
+  let response = action.response;
+  let from = response.from;
+
+  // Properly deal with getters.
+  mergeProperties(response);
+
+  // Compute list of requested children.
+  let previewProps = response.preview ? response.preview.ownProperties : null;
+  let ownProps = response.ownProperties || previewProps || [];
+  let props = Object.keys(ownProps).map(key => {
+    return new Property(key, ownProps[key], key);
+  });
+
+  props.sort(sortName);
+
+  // Return new state/map.
+  let newCache = new Map(cache);
+  newCache.set(from, props);
+
+  return newCache;
+}
+
+// Helpers
+
+function mergeProperties(response) {
+  let { ownProperties } = response;
+
+  // 'safeGetterValues' is new and isn't necessary defined on old grips.
+  let safeGetterValues = response.safeGetterValues || {};
+
+  // Merge the safe getter values into one object such that we can use it
+  // in variablesView.
+  for (let name of Object.keys(safeGetterValues)) {
+    if (name in ownProperties) {
+      let { getterValue, getterPrototypeLevel } = safeGetterValues[name];
+      ownProperties[name].getterValue = getterValue;
+      ownProperties[name].getterPrototypeLevel = getterPrototypeLevel;
+    } else {
+      ownProperties[name] = safeGetterValues[name];
+    }
+  }
+}
+
+function sortName(a, b) {
+  // Display non-enumerable properties at the end.
+  if (!a.value.enumerable && b.value.enumerable) {
+    return 1;
+  }
+  if (a.value.enumerable && !b.value.enumerable) {
+    return -1;
+  }
+  return a.name > b.name ? 1 : -1;
+}
+
+function Property(name, value, key) {
+  this.name = name;
+  this.value = value;
+  this.key = key;
+}
+
+// Exports from this module
+exports.grips = grips;
+exports.Property = Property;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/index.js
@@ -0,0 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const { grips } = require("./grips");
+const { filter } = require("./filter");
+
+exports.reducers = {
+  grips,
+  filter,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/reducers/moz.build
@@ -0,0 +1,10 @@
+# vim: set filetype=python:
+# 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/.
+
+DevToolsModules(
+    'filter.js',
+    'grips.js',
+    'index.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/content/utils.js
@@ -0,0 +1,27 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+/**
+ * The default localization just returns the last part of the key
+ * (all after the last dot).
+ */
+const DefaultL10N = {
+  getStr: function(key) {
+    let index = key.lastIndexOf(".");
+    return key.substr(index + 1);
+  }
+};
+
+/**
+ * The 'l10n' object is set by main.js in case the DOM panel content
+ * runs within a scope with chrome privileges.
+ *
+ * Note that DOM panel content can also run within a scope with no chrome
+ * privileges, e.g. in an iframe with type 'content' or in a browser tab,
+ * which allows using our own tools for development.
+ */
+exports.l10n = window.l10n || DefaultL10N;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/dom-panel.js
@@ -0,0 +1,240 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const { Cu } = require("chrome");
+const { defer } = require("sdk/core/promise");
+const { ObjectClient } = require("devtools/shared/client/main");
+
+const promise = require("promise");
+const EventEmitter = require("devtools/shared/event-emitter");
+
+/**
+ * This object represents DOM panel. It's responsibility is to
+ * render Document Object Model of the current debugger target.
+ */
+function DomPanel(iframeWindow, toolbox) {
+  this.panelWin = iframeWindow;
+  this._toolbox = toolbox;
+
+  this.onTabNavigated = this.onTabNavigated.bind(this);
+  this.onContentMessage = this.onContentMessage.bind(this);
+  this.onPanelVisibilityChange = this.onPanelVisibilityChange.bind(this);
+
+  this.pendingRequests = new Map();
+
+  EventEmitter.decorate(this);
+}
+
+DomPanel.prototype = {
+  /**
+   * Open is effectively an asynchronous constructor.
+   *
+   * @return object
+   *         A promise that is resolved when the DOM panel completes opening.
+   */
+  open: Task.async(function* () {
+    if (this._opening) {
+      return this._opening;
+    }
+
+    let deferred = promise.defer();
+    this._opening = deferred.promise;
+
+    // Local monitoring needs to make the target remote.
+    if (!this.target.isRemote) {
+      yield this.target.makeRemote();
+    }
+
+    this.initialize();
+
+    this.isReady = true;
+    this.emit("ready");
+    deferred.resolve(this);
+
+    return this._opening;
+  }),
+
+  // Initialization
+
+  initialize: function() {
+    this.panelWin.addEventListener("devtools/content/message",
+      this.onContentMessage, true);
+
+    this.target.on("navigate", this.onTabNavigated);
+    this._toolbox.on("select", this.onPanelVisibilityChange);
+
+    let provider = {
+      getPrototypeAndProperties: this.getPrototypeAndProperties.bind(this)
+    };
+
+    exportIntoContentScope(this.panelWin, provider, "DomProvider");
+
+    this.shouldRefresh = true;
+  },
+
+  destroy: Task.async(function* () {
+    if (this._destroying) {
+      return this._destroying;
+    }
+
+    let deferred = promise.defer();
+    this._destroying = deferred.promise;
+
+    this.target.off("navigate", this.onTabNavigated);
+    this._toolbox.off("select", this.onPanelVisibilityChange);
+
+    this.emit("destroyed");
+
+    deferred.resolve();
+    return this._destroying;
+  }),
+
+  // Events
+
+  refresh: function() {
+    // Do not refresh if the panel isn't visible.
+    if (!this.isPanelVisible()) {
+      return;
+    }
+
+    // Do not refresh if it isn't necessary.
+    if (!this.shouldRefresh) {
+      return;
+    }
+
+    // Alright reset the flag we are about to refresh the panel.
+    this.shouldRefresh = false;
+
+    this.getRootGrip().then(rootGrip => {
+      this.postContentMessage("initialize", rootGrip);
+    });
+  },
+
+  /**
+   * Make sure the panel is refreshed when the page is reloaded.
+   * The panel is refreshed immediatelly if it's currently selected
+   * or lazily  when the user actually selects it.
+   */
+  onTabNavigated: function() {
+    this.shouldRefresh = true;
+    this.refresh();
+  },
+
+  /**
+   * Make sure the panel is refreshed (if needed) when it's selected.
+   */
+  onPanelVisibilityChange: function() {
+    this.refresh();
+  },
+
+  // Helpers
+
+  /**
+   * Return true if the DOM panel is currently selected.
+   */
+  isPanelVisible: function() {
+    return this._toolbox.currentToolId === "dom";
+  },
+
+  getPrototypeAndProperties: function(grip) {
+    let deferred = defer();
+
+    if (!grip.actor) {
+      console.error("No actor!", grip);
+      deferred.reject(new Error("Failed to get actor from grip."));
+      return deferred.promise;
+    }
+
+    // Bail out if target doesn't exist (toolbox maybe closed already).
+    if (!this.target) {
+      return deferred.promise;
+    }
+
+    // If a request for the grips is already in progress
+    // use the same promise.
+    let request = this.pendingRequests.get(grip.actor);
+    if (request) {
+      return request;
+    }
+
+    let client = new ObjectClient(this.target.client, grip);
+    client.getPrototypeAndProperties(response => {
+      this.pendingRequests.delete(grip.actor, deferred.promise);
+      deferred.resolve(response);
+
+      // Fire an event about not having any pending requests.
+      if (!this.pendingRequests.size) {
+        this.emit("no-pending-requests");
+      }
+    });
+
+    this.pendingRequests.set(grip.actor, deferred.promise);
+
+    return deferred.promise;
+  },
+
+  getRootGrip: function() {
+    let deferred = defer();
+
+    // Attach Console. It might involve RDP communication, so wait
+    // asynchronously for the result
+    this.target.activeConsole.evaluateJSAsync("window", res => {
+      deferred.resolve(res.result);
+    });
+
+    return deferred.promise;
+  },
+
+  postContentMessage: function(type, args) {
+    let data = {
+      type: type,
+      args: args,
+    };
+
+    let event = new this.panelWin.MessageEvent("devtools/chrome/message", {
+      bubbles: true,
+      cancelable: true,
+      data: data,
+    });
+
+    this.panelWin.dispatchEvent(event);
+  },
+
+  onContentMessage: function(event) {
+    let data = event.data;
+    let method = data.type;
+    if (typeof this[method] == "function") {
+      this[method](data.args);
+    }
+  },
+
+  get target() {
+    return this._toolbox.target;
+  },
+};
+
+// Helpers
+
+function exportIntoContentScope(win, obj, defineAs) {
+  let clone = Cu.createObjectIn(win, {
+    defineAs: defineAs
+  });
+
+  let props = Object.getOwnPropertyNames(obj);
+  for (let i = 0; i < props.length; i++) {
+    let propName = props[i];
+    let propValue = obj[propName];
+    if (typeof propValue == "function") {
+      Cu.exportFunction(propValue, clone, {
+        defineAs: propName
+      });
+    }
+  }
+}
+
+// Exports from this module
+exports.DomPanel = DomPanel;
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/dom.html
@@ -0,0 +1,22 @@
+<?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 html>
+<html>
+<head>
+  <meta charset="utf-8"/>
+
+  <link href="resource://devtools/client/dom/content/dom-view.css" rel="stylesheet" />
+  <link href="resource://devtools/client/jsonview/css/toolbar.css" rel="stylesheet" />
+  <link href="resource://devtools/client/shared/components/tree/tree-view.css" rel="stylesheet" />
+  <link href="resource://devtools/client/dom/content/components/search-box.css" rel="stylesheet" />
+
+  <script type="text/javascript;version=1.8"
+          src="chrome://devtools/content/shared/theme-switching.js"></script>
+</head>
+<body class="theme-body devtools-monospace" role="application">
+  <div id="content"></div>
+  <script type="text/javascript" src="./main.js"></script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/main.js
@@ -0,0 +1,26 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+const { utils: Cu } = Components;
+
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+// Module Loader
+const require = BrowserLoader({
+  baseURI: "resource://devtools/client/dom/",
+  window: this
+}).require;
+
+XPCOMUtils.defineConstant(this, "require", require);
+
+// Localization
+const { LocalizationHelper } = require("devtools/client/shared/l10n");
+this.l10n = new LocalizationHelper("chrome://devtools/locale/dom.properties");
+
+// Load DOM panel content
+require("./content/dom-view.js");
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/moz.build
@@ -0,0 +1,16 @@
+# vim: set filetype=python:
+# 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/.
+
+BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
+
+DIRS += [
+    'content',
+]
+
+DevToolsModules(
+    'dom-panel.js',
+    'dom.html',
+    'main.js',
+)
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/.eslintrc
@@ -0,0 +1,4 @@
+{
+  // Extend from the shared list of defined globals for mochitests.
+  "extends": "../../../.eslintrc.mochitests",
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+tags = devtools
+subsuite = devtools
+support-files =
+  head.js
+  page_basic.html
+  !/devtools/client/framework/test/shared-head.js
+
+[browser_dom_basic.js]
+[browser_dom_refresh.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_basic.js
@@ -0,0 +1,24 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+
+/**
+ * Basic test that checks content of the DOM panel.
+ */
+add_task(function* () {
+  info("Test DOM panel basic started");
+
+  let { panel } = yield addTestTab(TEST_PAGE_URL);
+
+  // Expand specified row and wait till children are displayed.
+  yield expandRow(panel, "_a");
+
+  // Verify that child is displayed now.
+  let childRow = getRowByLabel(panel, "_data");
+  ok(childRow, "Child row must exist");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/browser_dom_refresh.js
@@ -0,0 +1,25 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PAGE_URL = URL_ROOT + "page_basic.html";
+
+/**
+ * Basic test that checks the Refresh action in DOM panel.
+ */
+add_task(function* () {
+  info("Test DOM panel basic started");
+
+  let { panel } = yield addTestTab(TEST_PAGE_URL);
+
+  // Create a new variable in the page scope and refresh the panel.
+  yield evaluateJSAsync(panel, "var _b = 10");
+  yield refreshPanel(panel);
+
+  // Verify that the variable is displayed now.
+  let row = getRowByLabel(panel, "_b");
+  ok(row, "New variable must be displayed");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/head.js
@@ -0,0 +1,167 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+/* import-globals-from ../../framework/test/shared-head.js */
+
+"use strict";
+
+const FRAME_SCRIPT_UTILS_URL =
+  "chrome://devtools/content/shared/frame-script-utils.js";
+
+// shared-head.js handles imports, constants, and utility functions
+Services.scriptloader.loadSubScript(
+  "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
+
+// DOM panel actions.
+const constants = require("devtools/client/dom/content/constants");
+
+// Uncomment this pref to dump all devtools emitted events to the console.
+// Services.prefs.setBoolPref("devtools.dump.emit", true);
+
+registerCleanupFunction(() => {
+  info("finish() was called, cleaning up...");
+  Services.prefs.clearUserPref("devtools.dump.emit");
+});
+
+/**
+ * Add a new test tab in the browser and load the given url.
+ * @param {String} url
+ *        The url to be loaded in the new tab
+ * @return a promise that resolves to the tab object when
+ *        the url is loaded
+ */
+function addTestTab(url) {
+  info("Adding a new test tab with URL: '" + url + "'");
+
+  return new Promise(resolve => {
+    addTab(url).then(tab => {
+      // Load devtools/shared/frame-script-utils.js
+      getFrameScript();
+
+      // Select the DOM panel and wait till it's initialized.
+      initDOMPanel(tab).then(panel => {
+        waitForDispatch(panel, "FETCH_PROPERTIES").then(() => {
+          resolve({
+            tab: tab,
+            browser: tab.linkedBrowser,
+            panel: panel
+          });
+        });
+      });
+    });
+  });
+}
+
+/**
+ * Open the DOM panel for the given tab.
+ *
+ * @param {nsIDOMElement} tab
+ *        Optional tab element for which you want open the DOM panel.
+ *        The default tab is taken from the global variable |tab|.
+ * @return a promise that is resolved once the web console is open.
+ */
+function initDOMPanel(tab) {
+  return new Promise(resolve => {
+    let target = TargetFactory.forTab(tab || gBrowser.selectedTab);
+    gDevTools.showToolbox(target, "dom").then(toolbox => {
+      let panel = toolbox.getCurrentPanel();
+      resolve(panel);
+    });
+  });
+}
+
+/**
+ * Synthesize asynchronous click event (with clean stack trace).
+ */
+function synthesizeMouseClickSoon(panel, element) {
+  return new Promise(resolve => {
+    executeSoon(() => {
+      EventUtils.synthesizeMouse(element, 2, 2, {}, panel.panelWin);
+      resolve();
+    });
+  });
+}
+
+/**
+ * Returns tree row with specified label.
+ */
+function getRowByLabel(panel, text) {
+  let doc = panel.panelWin.document;
+  let labels = [...doc.querySelectorAll(".treeLabel")];
+  let label = labels.find(node => node.textContent == text);
+  return label ? label.closest(".treeRow") : null;
+}
+
+/**
+ * Expands elements with given label and waits till
+ * children are received from the backend.
+ */
+function expandRow(panel, labelText) {
+  let row = getRowByLabel(panel, labelText);
+  return synthesizeMouseClickSoon(panel, row).then(() => {
+    // Wait till children (properties) are fetched
+    // from the backend.
+    return waitForDispatch(panel, "FETCH_PROPERTIES");
+  });
+}
+
+function evaluateJSAsync(panel, expression) {
+  return new Promise(resolve => {
+    panel.target.activeConsole.evaluateJSAsync(expression, res => {
+      resolve(res);
+    });
+  });
+}
+
+function refreshPanel(panel) {
+  let doc = panel.panelWin.document;
+  let button = doc.querySelector(".btn.refresh");
+  return synthesizeMouseClickSoon(panel, button).then(() => {
+    // Wait till children (properties) are fetched
+    // from the backend.
+    return waitForDispatch(panel, "FETCH_PROPERTIES");
+  });
+}
+
+// Redux related API, use from shared location
+// as soon as bug 1261076 is fixed.
+
+// Wait until an action of `type` is dispatched. If it's part of an
+// async operation, wait until the `status` field is "done" or "error"
+function _afterDispatchDone(store, type) {
+  return new Promise(resolve => {
+    store.dispatch({
+      // Normally we would use `services.WAIT_UNTIL`, but use the
+      // internal name here so tests aren't forced to always pass it
+      // in
+      type: "@@service/waitUntil",
+      predicate: action => {
+        if (action.type === type) {
+          return action.status ?
+            (action.status === "end" || action.status === "error") :
+            true;
+        }
+        return false;
+      },
+      run: (dispatch, getState, action) => {
+        resolve(action);
+      }
+    });
+  });
+}
+
+function waitForDispatch(panel, type, eventRepeat = 1) {
+  const store = panel.panelWin.view.mainFrame.store;
+  const actionType = constants[type];
+  let count = 0;
+
+  return Task.spawn(function* () {
+    info("Waiting for " + type + " to dispatch " + eventRepeat + " time(s)");
+    while (count < eventRepeat) {
+      yield _afterDispatchDone(store, actionType);
+      count++;
+      info(type + " dispatched " + count + " time(s)");
+    }
+  });
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/dom/test/page_basic.html
@@ -0,0 +1,15 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>DOM test page</title>
+  </head>
+  <body>
+  <script type="text/javascript">
+    "use strict";
+    window._a = {_data: "test"};
+  </script>
+  </body>
+</html>
--- a/devtools/client/framework/test/browser_toolbox_tool_ready.js
+++ b/devtools/client/framework/test/browser_toolbox_tool_ready.js
@@ -1,15 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
+requestLongerTimeout(5);
+
 /**
  * Whitelisting this test.
  * As part of bug 1077403, the leaking uncaught rejection should be fixed.
  */
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Shader Editor is " +
   "still waiting for a WebGL context to be created.");
 
 function performChecks(target) {
--- a/devtools/client/framework/test/browser_toolbox_window_reload_target.js
+++ b/devtools/client/framework/test/browser_toolbox_window_reload_target.js
@@ -1,14 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-requestLongerTimeout(2);
+requestLongerTimeout(10);
 
 const TEST_URL = "data:text/html;charset=utf-8,"+
                  "<html><head><title>Test reload</title></head>"+
                  "<body><h1>Testing reload from devtools</h1></body></html>";
 
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 var target, toolbox, description, reloadsSent, toolIDs;
--- a/devtools/client/framework/test/browser_toolbox_window_title_changes.js
+++ b/devtools/client/framework/test/browser_toolbox_window_title_changes.js
@@ -1,13 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+requestLongerTimeout(5);
+
 var {Toolbox} = require("devtools/client/framework/toolbox");
 
 function test() {
   const URL_1 = "data:text/plain;charset=UTF-8,abcde";
   const URL_2 = "data:text/plain;charset=UTF-8,12345";
   const URL_3 = URL_ROOT + "browser_toolbox_window_title_changes_page.html";
   const TITLE_URL_3 = "Toolbox test for title update";
 
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -137,16 +137,19 @@ devtools.jar:
     content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
     content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
     content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/responsive.html/index.js (responsive.html/index.js)
+    content/dom/dom.html (dom/dom.html)
+    content/dom/content/dom-view.css (dom/content/dom-view.css)
+    content/dom/main.js (dom/main.js)
 %   skin devtools classic/1.0 %skin/
     skin/devtools-browser.css (themes/devtools-browser.css)
     skin/common.css (themes/common.css)
     skin/splitters.css (themes/splitters.css)
     skin/dark-theme.css (themes/dark-theme.css)
     skin/light-theme.css (themes/light-theme.css)
     skin/firebug-theme.css (themes/firebug-theme.css)
     skin/toolbars.css (themes/toolbars.css)
@@ -191,16 +194,17 @@ devtools.jar:
     skin/images/breadcrumbs-divider@2x.png (themes/images/breadcrumbs-divider@2x.png)
     skin/images/breadcrumbs-scrollbutton.png (themes/images/breadcrumbs-scrollbutton.png)
     skin/images/breadcrumbs-scrollbutton@2x.png (themes/images/breadcrumbs-scrollbutton@2x.png)
     skin/animationinspector.css (themes/animationinspector.css)
     skin/eyedropper.css (themes/eyedropper.css)
     skin/canvasdebugger.css (themes/canvasdebugger.css)
     skin/debugger.css (themes/debugger.css)
     skin/netmonitor.css (themes/netmonitor.css)
+    skin/dom.css (themes/dom.css)
     skin/performance.css (themes/performance.css)
     skin/memory.css (themes/memory.css)
     skin/promisedebugger.css (themes/promisedebugger.css)
     skin/images/timeline-filter.svg (themes/images/timeline-filter.svg)
     skin/scratchpad.css (themes/scratchpad.css)
     skin/shadereditor.css (themes/shadereditor.css)
     skin/storage.css (themes/storage.css)
     skin/splitview.css (themes/splitview.css)
@@ -274,16 +278,17 @@ devtools.jar:
     skin/images/emojis/emoji-tool-shadereditor.svg (themes/images/emojis/emoji-tool-shadereditor.svg)
     skin/images/emojis/emoji-tool-styleeditor.svg (themes/images/emojis/emoji-tool-styleeditor.svg)
     skin/images/emojis/emoji-tool-storage.svg (themes/images/emojis/emoji-tool-storage.svg)
     skin/images/emojis/emoji-tool-profiler.svg (themes/images/emojis/emoji-tool-profiler.svg)
     skin/images/emojis/emoji-tool-network.svg (themes/images/emojis/emoji-tool-network.svg)
     skin/images/emojis/emoji-tool-scratchpad.svg (themes/images/emojis/emoji-tool-scratchpad.svg)
     skin/images/emojis/emoji-tool-webaudio.svg (themes/images/emojis/emoji-tool-webaudio.svg)
     skin/images/emojis/emoji-tool-memory.svg (themes/images/emojis/emoji-tool-memory.svg)
+    skin/images/emojis/emoji-tool-dom.svg (themes/images/emojis/emoji-tool-dom.svg)
     skin/images/tool-options.svg (themes/images/tool-options.svg)
     skin/images/tool-webconsole.svg (themes/images/tool-webconsole.svg)
     skin/images/tool-canvas.svg (themes/images/tool-canvas.svg)
     skin/images/tool-debugger.svg (themes/images/tool-debugger.svg)
     skin/images/tool-debugger-paused.svg (themes/images/tool-debugger-paused.svg)
     skin/images/debugging-addons.svg (themes/images/debugging-addons.svg)
     skin/images/debugging-devices.svg (themes/images/debugging-devices.svg)
     skin/images/debugging-workers.svg (themes/images/debugging-workers.svg)
@@ -293,16 +298,17 @@ devtools.jar:
     skin/images/tool-storage.svg (themes/images/tool-storage.svg)
     skin/images/tool-profiler.svg (themes/images/tool-profiler.svg)
     skin/images/tool-profiler-active.svg (themes/images/tool-profiler-active.svg)
     skin/images/tool-network.svg (themes/images/tool-network.svg)
     skin/images/tool-scratchpad.svg (themes/images/tool-scratchpad.svg)
     skin/images/tool-webaudio.svg (themes/images/tool-webaudio.svg)
     skin/images/tool-memory.svg (themes/images/tool-memory.svg)
     skin/images/tool-memory-active.svg (themes/images/tool-memory-active.svg)
+    skin/images/tool-dom.svg (themes/images/tool-dom.svg)
     skin/images/close.svg (themes/images/close.svg)
     skin/images/clear.svg (themes/images/clear.svg)
     skin/images/vview-delete.png (themes/images/vview-delete.png)
     skin/images/vview-delete@2x.png (themes/images/vview-delete@2x.png)
     skin/images/vview-edit.png (themes/images/vview-edit.png)
     skin/images/vview-edit@2x.png (themes/images/vview-edit@2x.png)
     skin/images/vview-lock.png (themes/images/vview-lock.png)
     skin/images/vview-lock@2x.png (themes/images/vview-lock@2x.png)
@@ -354,17 +360,27 @@ devtools.jar:
     skin/images/firebug/breadcrumbs-divider.svg (themes/images/firebug/breadcrumbs-divider.svg)
     skin/images/firebug/breakpoint.svg (themes/images/firebug/breakpoint.svg)
     skin/images/firebug/tool-options.svg (themes/images/firebug/tool-options.svg)
     skin/images/firebug/debugger-step-in.svg (themes/images/firebug/debugger-step-in.svg)
     skin/images/firebug/debugger-step-out.svg (themes/images/firebug/debugger-step-out.svg)
     skin/images/firebug/debugger-step-over.svg (themes/images/firebug/debugger-step-over.svg)
     skin/images/firebug/pane-collapse.svg (themes/images/firebug/pane-collapse.svg)
     skin/images/firebug/pane-expand.svg (themes/images/firebug/pane-expand.svg)
-    skin/images/firebug/command-pick.svg (themes/images/firebug/command-pick.svg)
     skin/images/firebug/dock-undock.svg (themes/images/firebug/dock-undock.svg)
     skin/images/firebug/dock-side.svg (themes/images/firebug/dock-side.svg)
     skin/images/firebug/dock-bottom.svg (themes/images/firebug/dock-bottom.svg)
     skin/images/firebug/commandline-icon.svg (themes/images/firebug/commandline-icon.svg)
     skin/images/firebug/debugger-blackbox.svg (themes/images/firebug/debugger-blackbox.svg)
     skin/images/firebug/debugger-prettyprint.svg (themes/images/firebug/debugger-prettyprint.svg)
     skin/images/firebug/debugger-toggleBreakpoints.svg (themes/images/firebug/debugger-toggleBreakpoints.svg)
     skin/images/firebug/tool-debugger-paused.svg (themes/images/firebug/tool-debugger-paused.svg)
+    skin/images/firebug/command-pick.svg (themes/images/firebug/command-pick.svg)
+    skin/images/firebug/command-console.svg (themes/images/firebug/command-console.svg)
+    skin/images/firebug/command-eyedropper.svg (themes/images/firebug/command-eyedropper.svg)
+    skin/images/firebug/command-frames.svg (themes/images/firebug/command-frames.svg)
+    skin/images/firebug/command-paintflashing.svg (themes/images/firebug/command-paintflashing.svg)
+    skin/images/firebug/command-responsivemode.svg (themes/images/firebug/command-responsivemode.svg)
+    skin/images/firebug/command-scratchpad.svg (themes/images/firebug/command-scratchpad.svg)
+    skin/images/firebug/command-screenshot.svg (themes/images/firebug/command-screenshot.svg)
+    skin/images/firebug/command-measure.svg (themes/images/firebug/command-measure.svg)
+    skin/images/firebug/command-rulers.svg (themes/images/firebug/command-rulers.svg)
+    skin/images/firebug/command-noautohide.svg (themes/images/firebug/command-noautohide.svg)
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/en-US/dom.properties
@@ -0,0 +1,39 @@
+# 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 These strings are used inside the DOM panel
+# which is available from the Web Developer sub-menu -> 'DOM'.
+# The correct localization of this file might be to keep it in
+# 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 (dom.label):
+# This string is displayed in the title of the tab when the DOM panel is
+# displayed inside the developer tools window and in the Developer Tools Menu.
+dom.label=DOM
+
+# LOCALIZATION NOTE (dom.panelLabel):
+# This is used as the label for the toolbox panel.
+dom.panelLabel=DOM Panel
+
+# LOCALIZATION NOTE (dom.commandkey, dom.accesskey)
+# Used for the menuitem in the tool menu
+dom.commandkey=W
+dom.accesskey=D
+
+# LOCALIZATION NOTE (dom.tooltip):
+# This string is displayed in the tooltip of the tab when the DOM is
+# displayed inside the developer tools window.
+# Keyboard shortcut for DOM panel will be shown inside the brackets.
+dom.tooltip=DOM (%S)
+
+# LOCALIZATION NOTE (dom.filterDOMPanel): A placeholder text used for
+# DOM panel search box.
+dom.filterDOMPanel=Filter DOM Panel
+
+# LOCALIZATION NOTE (dom.refresh): A label for Refresh button in
+# DOM panel toolbar
+dom.refresh=Refresh
\ No newline at end of file
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -7,16 +7,17 @@
 include('../templates.mozbuild')
 
 DIRS += [
     'aboutdebugging',
     'animationinspector',
     'canvasdebugger',
     'commandline',
     'debugger',
+    'dom',
     'eyedropper',
     'framework',
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/attribute.js
@@ -0,0 +1,70 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { StringRep } = require("./string");
+
+  // Shortcuts
+  const { span } = React.DOM;
+  const { rep: StringRepFactory } = createFactories(StringRep);
+
+  /**
+   * Renders DOM attribute
+   */
+  let Attribute = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Attr",
+
+    getTitle: function(grip) {
+      return grip.preview.nodeName;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let value = grip.preview.value;
+
+      return (
+        ObjectLink({className: "Attr"},
+          span({},
+            span({className: "attrTitle"},
+              this.getTitle(grip)
+            ),
+            span({className: "attrEqual"},
+              "="
+            ),
+            StringRepFactory({object: value})
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "Attr" && grip.preview);
+  }
+
+  exports.Attribute = {
+    rep: Attribute,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/date-time.js
@@ -0,0 +1,61 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Used to render JS built-in Date() object.
+   */
+  let DateTime = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Date",
+
+    getTitle: function(grip) {
+      return new Date(grip.preview.timestamp).toString();
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "Date"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "Date" && grip.preview);
+  }
+
+  // Exports from this module
+  exports.DateTime = {
+    rep: DateTime,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/document.js
@@ -0,0 +1,72 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { getFileName } = require("./url");
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders DOM document object.
+   */
+  let Document = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Document",
+
+    getLocation: function(grip) {
+      let location = grip.preview.location;
+      return location ? getFileName(location) : "";
+    },
+
+    getTitle: function(win, context) {
+      return "document";
+    },
+
+    getTooltip: function(doc) {
+      return doc.location.href;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && type == "HTMLDocument");
+  }
+
+  // Exports from this module
+  exports.Document = {
+    rep: Document,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/event.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  /**
+   * Renders DOM event objects.
+   */
+  let Event = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "event",
+
+    summarizeEvent: function(grip) {
+      let info = [grip.preview.type, " "];
+
+      let eventFamily = grip.class;
+      let props = grip.preview.properties;
+
+      if (eventFamily == "MouseEvent") {
+        info.push("clientX=", props.clientX, ", clientY=", props.clientY);
+      } else if (eventFamily == "KeyboardEvent") {
+        info.push("charCode=", props.charCode, ", keyCode=", props.keyCode);
+      } else if (eventFamily == "MessageEvent") {
+        info.push("origin=", props.origin, ", data=", props.data);
+      }
+
+      return info.join("");
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "event"},
+          this.summarizeEvent(grip)
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "DOMEvent");
+  }
+
+  // Exports from this module
+  exports.Event = {
+    rep: Event,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/function.js
@@ -0,0 +1,60 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { cropString } = require("./string");
+
+  /**
+   * This component represents a template for Function objects.
+   */
+  let Func = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired
+    },
+
+    displayName: "Func",
+
+    summarizeFunction: function(grip) {
+      let name = grip.displayName || grip.name || "function";
+      return cropString(name + "()", 100);
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectLink({className: "function"},
+          this.summarizeFunction(grip)
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return (type == "function");
+    }
+
+    return (type == "Function");
+  }
+
+  // Exports from this module
+
+  exports.Func = {
+    rep: Func,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -0,0 +1,209 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // Dependencies
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { a, span } = React.DOM;
+
+  /**
+   * Renders an array. The array is enclosed by left and right bracket
+   * and the max number of rendered items depends on the current mode.
+   */
+  let GripArray = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+      provider: React.PropTypes.object,
+    },
+
+    displayName: "GripArray",
+
+    getLength: function(grip) {
+      return grip.preview ? grip.preview.length : 0;
+    },
+
+    getTitle: function(object, context) {
+      return "[" + object.length + "]";
+    },
+
+    arrayIterator: function(grip, max) {
+      let items = [];
+
+      if (!grip.preview || !grip.preview.length) {
+        return items;
+      }
+
+      let array = grip.preview.items;
+      if (!array) {
+        return items;
+      }
+
+      let provider = this.props.provider;
+      if (!provider) {
+        return items;
+      }
+
+      let delim;
+
+      for (let i = 0; i < array.length && i <= max; i++) {
+        try {
+          let value = provider.getValue(array[i]);
+
+          delim = (i == array.length - 1 ? "" : ", ");
+
+          if (value === array) {
+            items.push(Reference({
+              key: i,
+              object: value,
+              delim: delim}
+            ));
+          } else {
+            items.push(GripArrayItem(Object.assign({}, this.props, {
+              key: i,
+              object: value,
+              delim: delim}
+            )));
+          }
+        } catch (exc) {
+          items.push(GripArrayItem(Object.assign({}, this.props, {
+            object: exc,
+            delim: delim,
+            key: i}
+          )));
+        }
+      }
+
+      if (array.length > max + 1) {
+        items.pop();
+        items.push(Caption({
+          key: "more",
+          object: "more..."}
+        ));
+      }
+
+      return items;
+    },
+
+    hasSpecialProperties: function(array) {
+      return false;
+    },
+
+    // Event Handlers
+
+    onToggleProperties: function(event) {
+    },
+
+    onClickBracket: function(event) {
+    },
+
+    render: function() {
+      let mode = this.props.mode || "short";
+      let object = this.props.object;
+
+      let items;
+
+      if (mode == "tiny") {
+        items = span({className: "length"}, this.getLength(object));
+      } else {
+        let max = (mode == "short") ? 3 : 300;
+        items = this.arrayIterator(object, max);
+      }
+
+      return (
+        ObjectBox({
+          className: "array",
+          onClick: this.onToggleProperties},
+          a({
+            className: "objectLink",
+            onclick: this.onClickBracket},
+            span({
+              className: "arrayLeftBracket",
+              role: "presentation"},
+              "["
+            )
+          ),
+          items,
+          a({
+            className: "objectLink",
+            onclick: this.onClickBracket},
+            span({
+              className: "arrayRightBracket",
+              role: "presentation"},
+              "]"
+            )
+          ),
+          span({
+            className: "arrayProperties",
+            role: "group"}
+          )
+        )
+      );
+    },
+  });
+
+  /**
+   * Renders array item. Individual values are separated by
+   * a delimiter (a comma by default).
+   */
+  let GripArrayItem = React.createFactory(React.createClass({
+    propTypes: {
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "GripArrayItem",
+
+    render: function() {
+      let { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          Rep(Object.assign({}, this.props, {
+            mode: "tiny"
+          })),
+          this.props.delim
+        )
+      );
+    }
+  }));
+
+  /**
+   * Renders cycle references in an array.
+   */
+  let Reference = React.createFactory(React.createClass({
+    displayName: "Reference",
+
+    render: function() {
+      return (
+        span({title: "Circular reference"},
+          "[...]"
+        )
+      );
+    }
+  }));
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ArrayLike");
+  }
+
+  // Exports from this module
+  exports.GripArray = {
+    rep: GripArray,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/grip.js
@@ -0,0 +1,217 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Dependencies
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * @template TODO docs
+   */
+  const Grip = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "Grip",
+
+    getTitle: function() {
+      return "";
+    },
+
+    longPropIterator: function(object) {
+      try {
+        return this.propIterator(object, 100);
+      } catch (err) {
+        console.error(err);
+      }
+      return [];
+    },
+
+    shortPropIterator: function(object) {
+      try {
+        return this.propIterator(object, 3);
+      } catch (err) {
+        console.error(err);
+      }
+      return [];
+    },
+
+    propIterator: function(object, max) {
+      // Property filter. Show only interesting properties to the user.
+      let isInterestingProp = (type, value) => {
+        return (
+          type == "boolean" ||
+          type == "number" ||
+          type == "string" ||
+          type == "object"
+        );
+      };
+
+      // Object members with non-empty values are preferred since it gives the
+      // user a better overview of the object.
+      let props = this.getProps(object, max, isInterestingProp);
+
+      if (props.length <= max) {
+        // There are not enough props yet (or at least, not enough props to
+        // be able to know whether we should print "more..." or not).
+        // Let's display also empty members and functions.
+        props = props.concat(this.getProps(object, max, (t, value) => {
+          return !isInterestingProp(t, value);
+        }));
+      }
+
+      // getProps() can return max+1 properties (it can't return more)
+      // to indicate that there is more props than allowed. Remove the last
+      // one and append 'more...' postfix in such case.
+      if (props.length > max) {
+        props.pop();
+        props.push(Caption({
+          key: "more",
+          object: "more...",
+        }));
+      } else if (props.length > 0) {
+        // Remove the last comma.
+        // NOTE: do not change comp._store.props directly to update a property,
+        // it should be re-rendered or cloned with changed props
+        let last = props.length - 1;
+        props[last] = React.cloneElement(props[last], {
+          delim: ""
+        });
+      }
+
+      return props;
+    },
+
+    getProps: function(object, max, filter) {
+      let props = [];
+
+      max = max || 3;
+      if (!object) {
+        return props;
+      }
+
+      try {
+        let ownProperties = object.preview ? object.preview.ownProperties : [];
+        for (let name in ownProperties) {
+          if (props.length > max) {
+            return props;
+          }
+
+          let prop = ownProperties[name];
+          let value = prop.value || {};
+
+          // Type is specified in grip's "class" field and for primitive
+          // values use typeof.
+          let type = (value.class || typeof value);
+          type = type.toLowerCase();
+
+          // Show only interesting properties.
+          if (filter(type, value)) {
+            props.push(PropRep(Object.assign({}, this.props, {
+              key: name,
+              mode: "tiny",
+              name: name,
+              object: value,
+              equal: ": ",
+              delim: ", ",
+            })));
+          }
+        }
+      } catch (err) {
+        console.error(err);
+      }
+
+      return props;
+    },
+
+    render: function() {
+      let object = this.props.object;
+      let props = this.shortPropIterator(object);
+
+      if (this.props.mode == "tiny" || !props.length) {
+        return (
+          ObjectBox({className: "object"},
+            span({className: "objectTitle"}, this.getTitle(object)),
+            span({className: "objectLeftBrace", role: "presentation"}, "{}")
+          )
+        );
+      }
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectTitle"}, this.getTitle(object)),
+          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          props,
+          span({className: "objectRightBrace"}, "}")
+        )
+      );
+    },
+  });
+
+  /**
+   * Property for a grip object.
+   */
+  let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      name: React.PropTypes.string,
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "PropRep",
+
+    render: function() {
+      let { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          span({
+            "className": "nodeName"},
+            this.props.name),
+          span({
+            "className": "objectEqual",
+            role: "presentation"},
+            this.props.equal
+          ),
+          Rep(this.props),
+          span({
+            "className": "objectComma",
+            role: "presentation"},
+            this.props.delim
+          )
+        )
+      );
+    }
+  }));
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && object.preview.ownProperties);
+  }
+
+  // Exports from this module
+  exports.Grip = {
+    rep: Grip,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -1,21 +1,35 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DevToolsModules(
     'array.js',
+    'attribute.js',
     'caption.js',
+    'date-time.js',
+    'document.js',
+    'event.js',
+    'function.js',
+    'grip-array.js',
+    'grip.js',
+    'named-node-map.js',
     'null.js',
     'number.js',
     'object-box.js',
     'object-link.js',
+    'object-with-text.js',
+    'object-with-url.js',
     'object.js',
+    'regexp.js',
     'rep-utils.js',
     'rep.js',
     'reps.css',
     'string.js',
+    'stylesheet.js',
+    'text-node.js',
     'undefined.js',
     'url.js',
+    'window.js',
 )
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/named-node-map.js
@@ -0,0 +1,172 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { Caption } = createFactories(require("./caption"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Used to render a map of values provided as a grip.
+   */
+  let NamedNodeMap = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+      provider: React.PropTypes.object,
+    },
+
+    className: "NamedNodeMap",
+
+    getLength: function(object) {
+      return object.preview.length;
+    },
+
+    getTitle: function(object) {
+      return object.class ? object.class : "";
+    },
+
+    getItems: function(array, max) {
+      let items = this.propIterator(array, max);
+
+      items = items.map(item => PropRep(item));
+
+      if (items.length > max + 1) {
+        items.pop();
+        items.push(Caption({
+          key: "more",
+          object: "more...",
+        }));
+      }
+
+      return items;
+    },
+
+    propIterator: function(grip, max) {
+      max = max || 3;
+
+      let props = [];
+
+      let provider = this.props.provider;
+      if (!provider) {
+        return props;
+      }
+
+      let ownProperties = grip.preview ? grip.preview.ownProperties : [];
+      for (let name in ownProperties) {
+        if (props.length > max) {
+          break;
+        }
+
+        let item = ownProperties[name];
+        let label = provider.getLabel(item);
+        let value = provider.getValue(item);
+
+        props.push(Object.assign({}, this.props, {
+          name: label,
+          object: value,
+          equal: ": ",
+          delim: ", ",
+        }));
+      }
+
+      return props;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let mode = this.props.mode;
+
+      let items;
+      if (mode == "tiny") {
+        items = this.getLength(grip);
+      } else {
+        let max = (mode == "short") ? 3 : 100;
+        items = this.getItems(grip, max);
+      }
+
+      return (
+        ObjectLink({className: "NamedNodeMap"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          ),
+          span({
+            className: "arrayLeftBracket",
+            role: "presentation"},
+            "["
+          ),
+          items,
+          span({
+            className: "arrayRightBracket",
+            role: "presentation"},
+            "]"
+          )
+        )
+      );
+    },
+  });
+
+  /**
+   * Property for a grip object.
+   */
+  let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
+    displayName: "PropRep",
+
+    render: function() {
+      const { Rep } = createFactories(require("./rep"));
+
+      return (
+        span({},
+          span({
+            className: "nodeName"},
+            "$prop.name"
+          ),
+          span({
+            className: "objectEqual",
+            role: "presentation"},
+            this.props.equal
+          ),
+          Rep(this.props),
+          span({
+            className: "objectComma",
+            role: "presentation"},
+            this.props.delim
+          )
+        )
+      );
+    }
+  }));
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (type == "NamedNodeMap" && grip.preview);
+  }
+
+  // Exports from this module
+  exports.NamedNodeMap = {
+    rep: NamedNodeMap,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/object-with-text.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with textual data.
+   */
+  let ObjectWithText = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "ObjectWithText",
+
+    getType: function(grip) {
+      return grip.class;
+    },
+
+    getDescription: function(grip) {
+      return (grip.preview.kind == "ObjectWithText") ? grip.preview.text : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: this.getType(grip)},
+          span({className: "objectPropValue"},
+            this.getDescription(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ObjectWithText");
+  }
+
+  // Exports from this module
+  exports.ObjectWithText = {
+    rep: ObjectWithText,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/object-with-url.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with URL data.
+   */
+  let ObjectWithURL = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "ObjectWithURL",
+
+    getType: function(grip) {
+      return grip.class;
+    },
+
+    getDescription: function(grip) {
+      return (grip.preview.kind == "ObjectWithURL") ? grip.preview.url : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: this.getType(grip)},
+          span({className: "objectPropValue"},
+            this.getDescription(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.preview.kind == "ObjectWithURL");
+  }
+
+  // Exports from this module
+  exports.ObjectWithURL = {
+    rep: ObjectWithURL,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/object.js
+++ b/devtools/client/shared/components/reps/object.js
@@ -10,38 +10,29 @@
 define(function(require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
   const { createFactories } = require("./rep-utils");
   const { ObjectBox } = createFactories(require("./object-box"));
   const { Caption } = createFactories(require("./caption"));
 
   // Shortcuts
-  const DOM = React.DOM;
+  const { span } = React.DOM;
 
   /**
    * Renders an object. An object is represented by a list of its
    * properties enclosed in curly brackets.
    */
   const Obj = React.createClass({
-    displayName: "Obj",
-
-    render: function() {
-      let object = this.props.object;
-      let props = this.shortPropIterator(object);
+    propTypes: {
+      object: React.PropTypes.object,
+      mode: React.PropTypes.string,
+    },
 
-      return (
-        ObjectBox({className: "object"},
-          DOM.span({className: "objectTitle"}, this.getTitle(object)),
-          DOM.span({className: "objectLeftBrace", role: "presentation"}, "{"),
-          props,
-          DOM.span({className: "objectRightBrace"}, "}")
-        )
-      );
-    },
+    displayName: "Obj",
 
     getTitle: function() {
       return "";
     },
 
     longPropIterator: function(object) {
       try {
         return this.propIterator(object, 100);
@@ -56,38 +47,37 @@ define(function(require, exports, module
         return this.propIterator(object, 3);
       } catch (err) {
         console.error(err);
       }
       return [];
     },
 
     propIterator: function(object, max) {
-      function isInterestingProp(t, value) {
-        return (t == "boolean" || t == "number" || (t == "string" && value) ||
-          (t == "object" && value && value.toString));
-      }
+      let isInterestingProp = (t, value) => {
+        // Do not pick objects, it could cause recursion.
+        return (t == "boolean" || t == "number" || (t == "string" && value));
+      };
 
       // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=945377
       if (Object.prototype.toString.call(object) === "[object Generator]") {
         object = Object.getPrototypeOf(object);
       }
 
       // Object members with non-empty values are preferred since it gives the
       // user a better overview of the object.
-      let props = [];
-      this.getProps(props, object, max, isInterestingProp);
+      let props = this.getProps(object, max, isInterestingProp);
 
       if (props.length <= max) {
         // There are not enough props yet (or at least, not enough props to
         // be able to know whether we should print "more..." or not).
         // Let's display also empty members and functions.
-        this.getProps(props, object, max, function(t, value) {
+        props = props.concat(this.getProps(object, max, (t, value) => {
           return !isInterestingProp(t, value);
-        });
+        }));
       }
 
       if (props.length > max) {
         props.pop();
         props.push(Caption({
           key: "more",
           object: "more...",
         }));
@@ -95,28 +85,30 @@ define(function(require, exports, module
         // Remove the last comma.
         props[props.length - 1] = React.cloneElement(
           props[props.length - 1], { delim: "" });
       }
 
       return props;
     },
 
-    getProps: function(props, object, max, filter) {
+    getProps: function(object, max, filter) {
+      let props = [];
+
       max = max || 3;
       if (!object) {
-        return [];
+        return props;
       }
 
       let mode = this.props.mode;
 
       try {
         for (let name in object) {
           if (props.length > max) {
-            return [];
+            return props;
           }
 
           let value;
           try {
             value = object[name];
           } catch (exc) {
             continue;
           }
@@ -132,47 +124,69 @@ define(function(require, exports, module
               delim: ", ",
             }));
           }
         }
       } catch (err) {
         console.error(err);
       }
 
-      return [];
+      return props;
+    },
+
+    render: function() {
+      let object = this.props.object;
+      let props = this.shortPropIterator(object);
+
+      return (
+        ObjectBox({className: "object"},
+          span({className: "objectTitle"}, this.getTitle(object)),
+          span({className: "objectLeftBrace", role: "presentation"}, "{"),
+          props,
+          span({className: "objectRightBrace"}, "}")
+        )
+      );
     },
   });
 
   /**
    * Renders object property, name-value pair.
    */
   let PropRep = React.createFactory(React.createClass({
+    propTypes: {
+      object: React.PropTypes.any,
+      mode: React.PropTypes.string,
+      name: React.PropTypes.string,
+      equal: React.PropTypes.string,
+      delim: React.PropTypes.string,
+    },
+
     displayName: "PropRep",
 
     render: function() {
       let { Rep } = createFactories(require("./rep"));
       let object = this.props.object;
       let mode = this.props.mode;
 
       return (
-        DOM.span({},
-          DOM.span({
+        span({},
+          span({
             "className": "nodeName"},
             this.props.name
           ),
-          DOM.span({
+          span({
             "className": "objectEqual",
             role: "presentation"},
             this.props.equal
           ),
           Rep({
             object: object,
             mode: mode
           }),
-          DOM.span({
+          span({
             "className": "objectComma",
             role: "presentation"},
             this.props.delim
           )
         )
       );
     }
   }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/regexp.js
@@ -0,0 +1,69 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+
+  // Shortcuts
+  const { span } = React.DOM;
+
+  /**
+   * Renders a grip object with regular expression.
+   */
+  let RegExp = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "regexp",
+
+    getTitle: function(grip) {
+      return grip.class;
+    },
+
+    getSource: function(grip) {
+      return grip.displayString;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      return (
+        ObjectLink({className: "regexp"},
+          span({className: "objectTitle"},
+            this.getTitle(grip)
+          ),
+          span(" "),
+          span({className: "regexpSource"},
+            this.getSource(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (type == "RegExp");
+  }
+
+  // Exports from this module
+  exports.RegExp = {
+    rep: RegExp,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/rep-utils.js
+++ b/devtools/client/shared/components/reps/rep-utils.js
@@ -19,11 +19,19 @@ define(function(require, exports, module
   function createFactories(args) {
     let result = {};
     for (let p in args) {
       result[p] = React.createFactory(args[p]);
     }
     return result;
   }
 
+  /**
+   * Returns true if the given object is a grip (see RDP protocol)
+   */
+  function isGrip(object) {
+    return object && object.actor;
+  }
+
   // Exports from this module
   exports.createFactories = createFactories;
+  exports.isGrip = isGrip;
 });
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -6,56 +6,101 @@
 
 "use strict";
 
 // Make this available to both AMD and CJS environments
 define(function(require, exports, module) {
   // Dependencies
   const React = require("devtools/client/shared/vendor/react");
 
+  const { isGrip } = require("./rep-utils");
+
   // Load all existing rep templates
   const { Undefined } = require("./undefined");
   const { Null } = require("./null");
   const { StringRep } = require("./string");
   const { Number } = require("./number");
   const { ArrayRep } = require("./array");
   const { Obj } = require("./object");
 
+  // DOM types (grips)
+  const { Attribute } = require("./attribute");
+  const { DateTime } = require("./date-time");
+  const { Document } = require("./document");
+  const { Event } = require("./event");
+  const { Func } = require("./function");
+  const { NamedNodeMap } = require("./named-node-map");
+  const { RegExp } = require("./regexp");
+  const { StyleSheet } = require("./stylesheet");
+  const { TextNode } = require("./text-node");
+  const { Window } = require("./window");
+  const { ObjectWithText } = require("./object-with-text");
+  const { ObjectWithURL } = require("./object-with-url");
+  const { GripArray } = require("./grip-array");
+  const { Grip } = require("./grip");
+
   // List of all registered template.
   // XXX there should be a way for extensions to register a new
   // or modify an existing rep.
-  let reps = [Undefined, Null, StringRep, Number, ArrayRep, Obj];
-  let defaultRep;
+  let reps = [
+    RegExp,
+    StyleSheet,
+    Event,
+    DateTime,
+    TextNode,
+    NamedNodeMap,
+    Attribute,
+    Func,
+    ArrayRep,
+    Document,
+    Window,
+    ObjectWithText,
+    ObjectWithURL,
+    GripArray,
+    Grip,
+    Undefined,
+    Null,
+    StringRep,
+    Number,
+  ];
 
   /**
    * Generic rep that is using for rendering native JS types or an object.
    * The right template used for rendering is picked automatically according
    * to the current value type. The value must be passed is as 'object'
    * property.
    */
   const Rep = React.createClass({
+    propTypes: {
+      object: React.PropTypes.any,
+      defaultRep: React.PropTypes.object,
+    },
+
     displayName: "Rep",
 
     render: function() {
-      let rep = getRep(this.props.object);
+      let rep = getRep(this.props.object, this.props.defaultRep);
       return rep(this.props);
     },
   });
 
   // Helpers
 
   /**
    * Return a rep object that is responsible for rendering given
    * object.
    *
    * @param object {Object} Object to be rendered in the UI. This
    * can be generic JS object as well as a grip (handle to a remote
    * debuggee object).
+   *
+   * @param defaultObject {React.Component} The default template
+   * that should be used to render given object if none is found.
    */
-  function getRep(object) {
+  function getRep(object, defaultRep = Obj) {
     let type = typeof object;
     if (type == "object" && object instanceof String) {
       type = "string";
     }
 
     if (isGrip(object)) {
       type = object.class;
     }
@@ -65,22 +110,18 @@ define(function(require, exports, module
       try {
         // supportsObject could return weight (not only true/false
         // but a number), which would allow to priorities templates and
         // support better extensibility.
         if (rep.supportsObject(object, type)) {
           return React.createFactory(rep.rep);
         }
       } catch (err) {
-        console.error("reps.getRep; EXCEPTION ", err, err);
+        console.error(err);
       }
     }
 
     return React.createFactory(defaultRep.rep);
   }
 
-  function isGrip(object) {
-    return object && object.actor;
-  }
-
   // Exports from this module
   exports.Rep = Rep;
 });
--- a/devtools/client/shared/components/reps/string.js
+++ b/devtools/client/shared/components/reps/string.js
@@ -91,11 +91,14 @@ define(function(require, exports, module
     return (type == "string");
   }
 
   // Exports from this module
 
   exports.StringRep = {
     rep: StringRep,
     supportsObject: supportsObject,
-    isCropped: isCropped
   };
+
+  exports.isCropped = isCropped;
+  exports.cropString = cropString;
+  exports.cropMultipleLines = cropMultipleLines;
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/stylesheet.js
@@ -0,0 +1,67 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { getFileName } = require("./url");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders a grip representing CSSStyleSheet
+   */
+  let StyleSheet = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "object",
+
+    getLocation: function(grip) {
+      // Embedded stylesheets don't have URL and so, no preview.
+      let url = grip.preview ? grip.preview.url : "";
+      return url ? getFileName(url) : "";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "object"},
+          "StyleSheet ",
+          DOM.span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (type == "CSSStyleSheet");
+  }
+
+  // Exports from this module
+
+  exports.StyleSheet = {
+    rep: StyleSheet,
+    supportsObject: supportsObject
+  };
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/text-node.js
@@ -0,0 +1,82 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectLink } = createFactories(require("./object-link"));
+  const { cropMultipleLines } = require("./string");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders DOM #text node.
+   */
+  let TextNode = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "TextNode",
+
+    getTextContent: function(grip) {
+      return cropMultipleLines(grip.preview.textContent);
+    },
+
+    getTitle: function(win, context) {
+      return "textNode";
+    },
+
+    render: function() {
+      let grip = this.props.object;
+      let mode = this.props.mode || "short";
+
+      if (mode == "short" || mode == "tiny") {
+        return (
+          ObjectLink({className: "textNode"},
+            "\"" + this.getTextContent(grip) + "\""
+          )
+        );
+      }
+
+      return (
+        ObjectLink({className: "textNode"},
+          "<",
+          DOM.span({className: "nodeTag"}, "TextNode"),
+          " textContent=\"",
+          DOM.span({className: "nodeValue"},
+            this.getTextContent(grip)
+          ),
+          "\"",
+          ">;"
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(grip, type) {
+    if (!isGrip(grip)) {
+      return false;
+    }
+
+    return (grip.preview && grip.class == "Text");
+  }
+
+  // Exports from this module
+  exports.TextNode = {
+    rep: TextNode,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/shared/components/reps/url.js
+++ b/devtools/client/shared/components/reps/url.js
@@ -27,12 +27,55 @@ define(function(require, exports, module
     return entries.map(entry => {
       return {
         name: entry[0],
         value: entry[1]
       };
     });
   }
 
+  function getFileName(url) {
+    let split = splitURLBase(url);
+    return split.name;
+  }
+
+  function splitURLBase(url) {
+    if (!isDataURL(url)) {
+      return splitURLTrue(url);
+    }
+    return {};
+  }
+
+  function isDataURL(url) {
+    return (url && url.substr(0, 5) == "data:");
+  }
+
+  function splitURLTrue(url) {
+    const reSplitFile = /(.*?):\/{2,3}([^\/]*)(.*?)([^\/]*?)($|\?.*)/;
+    let m = reSplitFile.exec(url);
+
+    if (!m) {
+      return {
+        name: url,
+        path: url
+      };
+    } else if (m[4] == "" && m[5] == "") {
+      return {
+        protocol: m[1],
+        domain: m[2],
+        path: m[3],
+        name: m[3] != "/" ? m[3] : m[2]
+      };
+    }
+
+    return {
+      protocol: m[1],
+      domain: m[2],
+      path: m[2] + m[3],
+      name: m[4] + m[5]
+    };
+  }
+
   // Exports from this module
   exports.parseURLParams = parseURLParams;
   exports.parseURLEncodedText = parseURLEncodedText;
+  exports.getFileName = getFileName;
 });
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/reps/window.js
@@ -0,0 +1,71 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* 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/. */
+"use strict";
+
+// Make this available to both AMD and CJS environments
+define(function(require, exports, module) {
+  // ReactJS
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Reps
+  const { createFactories, isGrip } = require("./rep-utils");
+  const { ObjectBox } = createFactories(require("./object-box"));
+  const { cropString } = require("./string");
+
+  // Shortcuts
+  const DOM = React.DOM;
+
+  /**
+   * Renders a grip representing a window.
+   */
+  let Window = React.createClass({
+    propTypes: {
+      object: React.PropTypes.object.isRequired,
+    },
+
+    displayName: "Window",
+
+    getLocation: function(grip) {
+      return cropString(grip.preview.url);
+    },
+
+    getTitle: function(grip, context) {
+      return grip.class;
+    },
+
+    getTooltip: function(grip) {
+      return grip.preview.url;
+    },
+
+    render: function() {
+      let grip = this.props.object;
+
+      return (
+        ObjectBox({className: "Window"},
+          DOM.span({className: "objectPropValue"},
+            this.getLocation(grip)
+          )
+        )
+      );
+    },
+  });
+
+  // Registration
+
+  function supportsObject(object, type) {
+    if (!isGrip(object)) {
+      return false;
+    }
+
+    return (object.preview && type == "Window");
+  }
+
+  // Exports from this module
+  exports.Window = {
+    rep: Window,
+    supportsObject: supportsObject
+  };
+});
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -29,9 +29,10 @@ support-files =
 [browser_storage_delete_tree.js]
 [browser_storage_dynamic_updates.js]
 [browser_storage_empty_objectstores.js]
 [browser_storage_localstorage_edit.js]
 [browser_storage_overflow.js]
 [browser_storage_search.js]
 [browser_storage_sessionstorage_edit.js]
 [browser_storage_sidebar.js]
+[browser_storage_sidebar_update.js]
 [browser_storage_values.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_sidebar_update.js
@@ -0,0 +1,41 @@
+/* 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/. */
+
+// Test to verify that the sidebar is not broken when several updates
+// come in quick succession. See bug 1260380 - it could happen that the
+// "Parsed Value" section gets duplicated.
+
+"use strict";
+
+add_task(function* () {
+  const ITEM_NAME = "ls1";
+  const UPDATE_COUNT = 3;
+
+  yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-complex-values.html");
+
+  let updated = gUI.once("sidebar-updated");
+  yield selectTreeItem(["localStorage", "http://test1.example.org"]);
+  yield selectTableItem(ITEM_NAME);
+  yield updated;
+
+  is(gUI.sidebar.hidden, false, "sidebar is visible");
+
+  // do several updates in a row and wait for them to finish
+  let updates = [];
+  for (let i = 0; i < UPDATE_COUNT; i++) {
+    info(`Performing update #${i}`);
+    updates.push(gUI.once("sidebar-updated"));
+    gUI.displayObjectSidebar();
+  }
+  yield promise.all(updates);
+
+  info("Updates performed, going to verify result");
+  let parsedScope = gUI.view.getScopeAtIndex(1);
+  let elements = parsedScope.target.querySelectorAll(
+    `.name[value="${ITEM_NAME}"]`);
+  is(elements.length, 1,
+    `There is only one displayed variable named '${ITEM_NAME}'`);
+
+  yield finishTests();
+});
--- a/devtools/client/storage/test/browser_storage_values.js
+++ b/devtools/client/storage/test/browser_storage_values.js
@@ -9,16 +9,18 @@
 //     null do click nothing,
 //   null to skip checking value in variables view or a key value pair object
 //     which will be asserted to exist in the storage sidebar,
 //   true if the check is to be made in the parsed value section
 // ]
 
 "use strict";
 
+const LONG_WORD = "a".repeat(1000);
+
 const testCases = [
   ["cs2", [
     {name: "cs2", value: "sessionCookie"},
     {name: "cs2.path", value: "/"},
     {name: "cs2.isDomain", value: "true"},
     {name: "cs2.isHttpOnly", value: "false"},
     {name: "cs2.host", value: ".example.org"},
     {name: "cs2.expires", value: "Session"},
@@ -88,16 +90,33 @@ const testCases = [
     {name: "ss2.3", value: "array"},
   ], true],
   ["ss3", [
     {name: "ss3", value: "Object"},
     {name: "ss3.this", value: "is"},
     {name: "ss3.an", value: "object"},
     {name: "ss3.foo", value: "bar"},
   ], true],
+  ["ss4", [
+    {name: "ss4", value: "Array"},
+    {name: "ss4.0", value: ""},
+    {name: "ss4.1", value: "array"},
+    {name: "ss4.2", value: ""},
+    {name: "ss4.3", value: "with"},
+    {name: "ss4.4", value: "empty"},
+    {name: "ss4.5", value: "items"},
+  ], true],
+  ["ss5", [
+    {name: "ss5", value: "Array"},
+    {name: "ss5.0", value: LONG_WORD},
+    {name: "ss5.1", value: LONG_WORD},
+    {name: "ss5.2", value: LONG_WORD},
+    {name: "ss5.3", value: `${LONG_WORD}&${LONG_WORD}`},
+    {name: "ss5.4", value: `${LONG_WORD}&${LONG_WORD}`},
+  ], true],
   [["indexedDB", "http://test1.example.org", "idb1", "obj1"]],
   [1, [
     {name: 1, value: JSON.stringify({id: 1, name: "foo", email: "foo@bar.com"})}
   ]],
   [null, [
     {name: "1.id", value: "1"},
     {name: "1.name", value: "foo"},
     {name: "1.email", value: "foo@bar.com"},
--- a/devtools/client/storage/test/storage-complex-values.html
+++ b/devtools/client/storage/test/storage-complex-values.html
@@ -27,16 +27,21 @@ localStorage.setItem("ls1", JSON.stringi
     nobody: "cares"
   }]}));
 localStorage.setItem("ls2", "foobar-2");
 localStorage.setItem("ls3", "http://foobar.com/baz.php");
 // ... and finally some session storage items too
 sessionStorage.setItem("ss1", "This#is#an#array");
 sessionStorage.setItem("ss2", "This~is~another~array");
 sessionStorage.setItem("ss3", "this#is~an#object~foo#bar");
+sessionStorage.setItem("ss4", "#array##with#empty#items");
+// long string that is almost an object and might trigger exponential
+// regexp backtracking
+let s = "a".repeat(1000);
+sessionStorage.setItem("ss5", `${s}=${s}=${s}=${s}&${s}=${s}&${s}`);
 console.log("added cookies and stuff from main page");
 
 let idbGenerator = function*() {
   let request = indexedDB.open("idb1", 1);
   request.onerror = function() {
     throw new Error("error opening db connection");
   };
   let db = yield new Promise(done => {
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -473,71 +473,71 @@ StorageUI.prototype = {
       }
     }
   },
 
   /**
    * Populates the selected entry from teh table in the sidebar for a more
    * detailed view.
    */
-  displayObjectSidebar: function() {
+  displayObjectSidebar: Task.async(function* () {
     let item = this.table.selectedRow;
     if (!item) {
       // Make sure that sidebar is hidden and return
       this.sidebar.hidden = true;
       return;
     }
+
+    // Get the string value (async action) and the update the UI synchronously.
+    let value;
+    if (item.name && item.valueActor) {
+      value = yield item.valueActor.string();
+    }
+
+    // Start updating the UI. Everything is sync beyond this point.
     this.sidebar.hidden = false;
     this.view.empty();
     let mainScope = this.view.addScope(L10N.getStr("storage.data.label"));
     mainScope.expanded = true;
 
-    if (item.name && item.valueActor) {
+    if (value) {
       let itemVar = mainScope.addItem(item.name + "", {}, {relaxed: true});
 
-      item.valueActor.string().then(value => {
-        // The main area where the value will be displayed
-        itemVar.setGrip(value);
-
-        // May be the item value is a json or a key value pair itself
-        this.parseItemValue(item.name, value);
+      // The main area where the value will be displayed
+      itemVar.setGrip(value);
 
-        // By default the item name and value are shown. If this is the only
-        // information available, then nothing else is to be displayed.
-        let itemProps = Object.keys(item);
-        if (itemProps.length == 3) {
-          this.emit("sidebar-updated");
-          return;
-        }
+      // May be the item value is a json or a key value pair itself
+      this.parseItemValue(item.name, value);
 
+      // By default the item name and value are shown. If this is the only
+      // information available, then nothing else is to be displayed.
+      let itemProps = Object.keys(item);
+      if (itemProps.length > 3) {
         // Display any other information other than the item name and value
         // which may be available.
         let rawObject = Object.create(null);
-        let otherProps =
-          itemProps.filter(e => e != "name" &&
-                                e != "value" &&
-                                e != "valueActor");
+        let otherProps = itemProps.filter(
+          e => !["name", "value", "valueActor"].includes(e));
         for (let prop of otherProps) {
           rawObject[prop] = item[prop];
         }
         itemVar.populate(rawObject, {sorted: true});
         itemVar.twisty = true;
         itemVar.expanded = true;
-        this.emit("sidebar-updated");
-      });
-      return;
+      }
+    } else {
+      // Case when displaying IndexedDB db/object store properties.
+      for (let key in item) {
+        mainScope.addItem(key, {}, true).setGrip(item[key]);
+        this.parseItemValue(key, item[key]);
+      }
     }
 
-    // Case when displaying IndexedDB db/object store properties.
-    for (let key in item) {
-      mainScope.addItem(key, {}, true).setGrip(item[key]);
-      this.parseItemValue(key, item[key]);
-    }
     this.emit("sidebar-updated");
-  },
+  }),
 
   /**
    * Tries to parse a string value into either a json or a key-value separated
    * object and populates the sidebar with the parsed value. The value can also
    * be a key separated array.
    *
    * @param {string} name
    *        The key corresponding to the `value` string in the object
@@ -602,27 +602,31 @@ StorageUI.prototype = {
     // Testing for object
     for (let i = 0; i < separators.length; i++) {
       let kv = separators[i];
       for (let j = 0; j < separators.length; j++) {
         if (i == j) {
           continue;
         }
         let p = separators[j];
-        let regex = new RegExp("^([^" + kv + p + "]*" + kv + "+[^" + kv + p +
-                               "]*" + p + "*)+$", "g");
+        let word = `[^${kv}${p}]*`;
+        let keyValue = `${word}${kv}${word}`;
+        let keyValueList = `${keyValue}(${p}${keyValue})*`;
+        let regex = new RegExp(`^${keyValueList}$`);
         if (value.match && value.match(regex) && value.includes(kv) &&
             (value.includes(p) || value.split(kv).length == 2)) {
           return makeObject(kv, p);
         }
       }
     }
     // Testing for array
     for (let p of separators) {
-      let regex = new RegExp("^[^" + p + "]+(" + p + "+[^" + p + "]*)+$", "g");
+      let word = `[^${p}]*`;
+      let wordList = `(${word}${p})+${word}`;
+      let regex = new RegExp(`^${wordList}$`);
       if (value.match && value.match(regex)) {
         return value.split(p.replace(/\\*/g, ""));
       }
     }
     return null;
   },
 
   /**
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/dom.css
@@ -0,0 +1,9 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* 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/. */
+
+:root.theme-dark {
+}
+:root.theme-light {
+}
--- a/devtools/client/themes/firebug-theme.css
+++ b/devtools/client/themes/firebug-theme.css
@@ -7,16 +7,31 @@
 @import url(common.css);
 @import url(light-theme.css);
 
 :root {
   font-size: 11px;
   font-family: var(--proportional-font-family);
 }
 
+/* Remove filters on firebug specific images */
+
+.theme-firebug #toolbox-dock-buttons > toolbarbutton > image,
+.theme-firebug .devtools-closebutton > image,
+.theme-firebug .devtools-option-toolbarbutton > image,
+.theme-firebug .command-button-invertable > image,
+.theme-firebug #sources-toolbar image,
+.theme-firebug [id$="pane-toggle"] > image,
+.theme-firebug #global-toolbar .devtools-button::before,
+.theme-firebug #element-picker::before,
+.theme-firebug #debugger-controls .toolbarbutton-icon,
+.theme-firebug #filter-button .toolbarbutton-icon {
+  filter: none !important;
+}
+
 /* CodeMirror Color Syntax */
 
 .theme-firebug .cm-keyword {color: BlueViolet; font-weight: bold;}
 .theme-firebug .cm-atom {color: #219;}
 .theme-firebug .cm-number {color: #164;}
 .theme-firebug .cm-def {color: #00f;}
 .theme-firebug .cm-variable {color: black;}
 .theme-firebug .cm-variable-2 {color: black;}
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/emojis/emoji-tool-dom.svg
@@ -0,0 +1,11 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
+  <path fill="#51BA7B" d="M487.819 258.669H439.1c-10.041 0-18.181-8.14-18.181-18.181s8.14-18.181 18.181-18.181h48.719c10.041 0 18.181 8.14 18.181 18.181s-8.14 18.181-18.181 18.181z"/>
+  <path fill="#BADEBE" d="M415.747 69.674s-.387.603-1.059 1.667a8.05 8.05 0 0 1-.638.799 59.208 59.208 0 0 0-1.057 1.933c-.357.806-.812 1.618-1.199 2.599a55.875 55.875 0 0 0-1.233 3.083c-.433 1.086-.783 2.248-1.19 3.426-.364 1.183-.742 2.395-1.064 3.614-.316 1.219-.645 2.445-.884 3.643-.286 1.219-.475 2.381-.679 3.523-.188 1.142-.308 2.214-.434 3.243-.041.505-.077.988-.118 1.457-.043.483-.07.939-.063 1.338-.007.812-.063 1.646 0 2.221.014.315.034.609.048.882 0 .14.007.273.015.399.014.105.027.209.041.301.113.777.161 1.26.161 1.26l.19 2.025a7.085 7.085 0 0 1-6.396 7.712c-2.829.267-5.421-1.177-6.774-3.467 0 0-.489-.826-1.308-2.353-.099-.189-.204-.392-.316-.603-.084-.203-.181-.413-.274-.63-.195-.448-.398-.932-.616-1.45-.484-1.058-.806-2.164-1.233-3.432a23.41 23.41 0 0 1-.561-1.933c-.174-.665-.349-1.352-.532-2.066-.301-1.387-.622-2.879-.876-4.393-.231-1.506-.491-3.089-.638-4.659-.195-1.583-.308-3.18-.419-4.784-.106-1.604-.147-3.201-.19-4.791 0-1.576-.027-3.145.043-4.665.034-1.513.125-2.998.238-4.42.077-1.408.28-2.802.414-4.063.188-1.31.371-2.48.588-3.622.301-1.31.622-2.529.853-3.411.31-1.212.477-1.913.477-1.913 1.968-7.887 9.967-12.692 17.856-10.724 7.894 1.975 12.691 9.968 10.725 17.862a14.708 14.708 0 0 1-1.815 4.266l-.083.126zm-56.35 17.01a14.892 14.892 0 0 0 1.036-4.518c.559-8.111-5.562-15.145-13.681-15.705-8.118-.56-15.151 5.562-15.711 13.681 0 0-.05.715-.133 1.976a70.1 70.1 0 0 0-.258 4.133c.014.665.055 1.233.091 1.912.048.638.063 1.366.154 2.025.174 1.331.308 2.823.595 4.245l.407 2.221c.168.729.335 1.478.511 2.234.322 1.527.783 3.033 1.19 4.575.477 1.526.91 3.068 1.457 4.574a88.52 88.52 0 0 0 1.654 4.483c.552 1.464 1.211 2.893 1.806 4.259a121.46 121.46 0 0 0 1.905 3.936c.694 1.254 1.255 2.41 1.948 3.496.665 1.086 1.226 2.052 1.864 2.936.622.882 1.079 1.611 1.618 2.248l1.59 1.941c1.849 2.255 4.973 3.243 7.887 2.234 3.74-1.289 5.715-5.373 4.426-9.107l-.469-1.338s-.168-.497-.469-1.366c-.162-.386-.303-1.051-.498-1.688-.204-.631-.357-1.478-.554-2.347-.217-.833-.344-1.891-.532-2.906a93.503 93.503 0 0 1-.428-3.362c-.097-1.198-.231-2.396-.274-3.664a61.991 61.991 0 0 1-.118-3.797c-.029-1.268.041-2.543.063-3.775.091-1.219.111-2.438.251-3.566.063-.56.12-1.121.174-1.661.077-.518.162-1.03.233-1.526.132-1.016.371-1.829.525-2.627.077-.407.21-.693.301-1.016.097-.294.181-.645.267-.841.188-.127.258-.147.342-.294l.751-1.829.079-.176z"/>
+  <path fill="#8ACCA0" d="M456.112 143.24c-11.449-34.634-48.8-53.434-83.441-41.983-34.634 11.449-53.431 48.807-41.982 83.442 17.717 53.598 16.873 94.849-2.59 126.04-1.716 2.393-3.661 5.076-4.907 6.585a66.228 66.228 0 0 0-7.289 6.104 66.535 66.535 0 0 0-3.758 2.035l-2.851 1.674c-.113.077-.154.112-.246.189-.041.035-.077.07-.125.112-.022.021-.029.035-.07.07l-.233.127c-.301.182-.629.371-.973.575-.364.203-.749.413-1.156.637a56.07 56.07 0 0 1-6.494 2.956c-2.711 1.044-6.03 2.109-10.03 3.04a104.442 104.442 0 0 1-13.996 2.179c-5.317.483-11.27.672-17.694.476a202.645 202.645 0 0 1-4.912-.224c-1.597-.105-3.208-.21-4.826-.322-4.134-.393-8.308-.784-12.517-1.191-1.907-.168-3.875-.42-5.821-.623-1.962-.217-3.776-.469-5.681-.693-1.828-.259-3.656-.504-5.437-.777-1.765-.287-3.537-.553-5.288-.882-1.738-.294-3.538-.673-5.331-1.023l-2.774-.603c-.925-.203-1.864-.406-2.865-.652l-2.943-.693-3.095-.77c-2.136-.539-4.26-1.078-6.382-1.618-8.713-2.241-17.861-4.617-26.604-6.732l-1.64-.398-.202-.05-.099-.028c-1.233-.357-.398-.104-.699-.189l-.4-.07-.792-.147a365.722 365.722 0 0 1-3.152-.56c-2.634-.476-5.24-.953-7.824-1.415a44.945 44.945 0 0 0-1.955-.322c-.631-.097-1.26-.196-1.891-.287-1.254-.189-2.5-.371-3.74-.56-2.516-.316-4.966-.658-7.439-.924-9.863-1.1-19.447-1.646-28.545-1.619-9.107.05-17.682.61-25.54 1.661-7.839 1.023-14.949 2.522-21.051 4.175a137.419 137.419 0 0 0-8.419 2.578 129.198 129.198 0 0 0-6.851 2.592c-2.032.847-3.79 1.646-5.204 2.326l-2.039 1.001c-.982.505-1.479.757-1.479.757-26.17 13.548-36.403 45.75-22.857 71.92 13.555 26.178 45.756 36.405 71.927 22.857l-1.45.735c-.301.14-.742.351-1.324.63-.344.155-.848.386-1.507.693-.251.133-.342.203-.21.21.169.015.504-.014 1.072-.063 1.163-.091 3.138-.259 5.955-.231 2.774 0 6.41.231 10.661.757 4.272.54 9.225 1.457 14.668 2.767 1.358.321 2.767.721 4.175 1.071.715.203 1.437.413 2.165.617l1.086.308c.378.105.735.203 1.023.302 1.394.441 2.808.882 4.231 1.331 1.765.575 3.544 1.149 5.337 1.731 7.824 2.472 15.711 5.092 24.357 7.978 2.227.743 4.462 1.478 6.709 2.221l3.496 1.142 3.692 1.17 1.864.589 1.946.588 3.923 1.177c2.704.77 5.387 1.555 8.175 2.269 2.76.75 5.527 1.415 8.294 2.087 2.745.658 5.464 1.248 8.168 1.843 2.634.54 5.344 1.121 7.901 1.604 2.584.476 5.107.981 7.704 1.429 2.543.441 5.072.876 7.586 1.31 2.208.364 4.407.722 6.6 1.086 1.443.217 2.885.427 4.315.644 1.366.182 2.724.371 4.077.553 2.711.351 5.428.666 8.139.946 10.851 1.128 21.682 1.647 32.23 1.513a239.007 239.007 0 0 0 30.479-2.291c9.666-1.366 18.689-3.313 26.716-5.548a191.408 191.408 0 0 0 20.791-7.061c1.428-.595 2.781-1.162 4.055-1.695 1.269-.568 2.459-1.093 3.566-1.59l.812-.364.912-.434c.602-.295 1.177-.568 1.731-.834 1.086-.54 2.073-1.023 2.955-1.464 1.919-.981 2.943-1.5 2.943-1.5l1.975-1.008a66.247 66.247 0 0 0 15.966-11.503 66.576 66.576 0 0 0 6.185-3.588c18.693-12.238 30.142-28.256 38.55-40.018l.926-1.294.862-1.338c23.738-36.839 36.169-79.029 36.947-125.397.602-35.782-5.867-74.417-19.227-114.833z"/>
+  <path fill="#FFF" d="M379.069 155.928l4.301 16.062h-.021c.007.028.027.028.034.042 1.688 6.311-2.059 12.798-8.363 14.486-6.312 1.688-12.799-2.059-14.487-8.364-.007-.014-.007-.021-.014-.049l-.05.014-4.301-16.062.14-.035c-1.057-5.989 2.55-11.887 8.532-13.492 5.969-1.597 12.048 1.709 14.116 7.425l.113-.027zm48.159-20.587c-2.697-5.45-9.107-8.048-14.858-5.792-5.765 2.263-8.693 8.532-6.971 14.367l-.132.049 6.08 15.481.05-.021c.007.021.007.027.014.042 2.387 6.08 9.254 9.071 15.334 6.683 6.08-2.381 9.071-9.254 6.682-15.334 0-.008-.027-.008-.034-.028l.021-.015-6.08-15.474-.106.042z"/>
+  <path fill="#51BA7B" d="M386.722 311.106l40.557 17.233c9.247 3.93 13.555 14.612 9.632 23.859-3.93 9.253-14.619 13.561-23.866 9.632a21.165 21.165 0 0 1-1.24-.581l-39.143-20.223c-8.118-4.196-11.292-14.171-7.104-22.29 3.994-7.728 13.284-10.95 21.164-7.63"/>
+  <path fill="#74C48D" d="M394.574 405.513c-12.623 0-25.31-3.56-36.185-10.523a5.688 5.688 0 0 1 6.134-9.581c15.343 9.826 35.001 11.501 51.304 4.37a5.69 5.69 0 0 1 4.558 10.424c-8.137 3.557-16.959 5.31-25.811 5.31zm-52.133 40.603a5.69 5.69 0 0 0-5.396-5.966c-14.406-.721-28.217-7.446-37.895-18.452a5.689 5.689 0 0 0-8.542 7.513c11.694 13.299 28.412 21.428 45.868 22.301a5.686 5.686 0 0 0 5.965-5.396zm81.464-133.345c15.644-.293 30.09-5.857 41.777-16.091a5.688 5.688 0 0 0-7.496-8.558c-9.641 8.443-21.568 13.033-34.493 13.275a5.69 5.69 0 0 0-5.581 5.794 5.687 5.687 0 0 0 5.684 5.581l.109-.001z"/>
+</svg>
--- a/devtools/client/themes/images/firebug/arrow-down.svg
+++ b/devtools/client/themes/images/firebug/arrow-down.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="7" height="7">
-    <path d="M1.774 4.486L.257 1.86-1.259-.768h6.067L3.29 1.859z" transform="matrix(.88859 0 0 1.0498 1.923 1.549)" stroke-linejoin="round" fill="#39424a" stroke="#39424a"/>
+  <path d="M1.774 4.486L.257 1.86-1.259-.768h6.067L3.29 1.859z" transform="matrix(.88859 0 0 1.0498 1.923 1.549)" stroke-linejoin="round" fill="#39424a" stroke="#39424a"/>
 </svg>
--- a/devtools/client/themes/images/firebug/arrow-up.svg
+++ b/devtools/client/themes/images/firebug/arrow-up.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="7" height="7">
-    <path d="M1.774 4.486L.257 1.86-1.259-.768h6.067L3.29 1.859z" transform="matrix(.88859 0 0 -1.0498 1.923 5.452)" stroke-linejoin="round" fill="#39424a" stroke="#39424a"/>
+  <path d="M1.774 4.486L.257 1.86-1.259-.768h6.067L3.29 1.859z" transform="matrix(.88859 0 0 -1.0498 1.923 5.452)" stroke-linejoin="round" fill="#39424a" stroke="#39424a"/>
 </svg>
--- a/devtools/client/themes/images/firebug/breadcrumbs-divider.svg
+++ b/devtools/client/themes/images/firebug/breadcrumbs-divider.svg
@@ -1,18 +1,18 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="5" height="7">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#9a9aba"/>
-            <stop offset="1" stop-color="#a6a6c2"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#8e8eb2"/>
-            <stop offset="1" stop-color="#9a9aba"/>
-        </linearGradient>
-        <linearGradient x1="3.616" y1="3.893" x2="1.285" y2="-.757" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 .8684 0 1046.257)"/>
-        <linearGradient x1="2.232" y1="4.162" x2=".629" y2=".966" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 .8684 0 1046.257)"/>
-    </defs>
-    <path d="M.2 1045.562l4.6 3.3-4.6 3.3 2-3.3z" fill="url(#c)" stroke="url(#d)" stroke-width=".4" stroke-linejoin="round" transform="translate(0 -1045.362)"/>
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#9a9aba"/>
+      <stop offset="1" stop-color="#a6a6c2"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#8e8eb2"/>
+      <stop offset="1" stop-color="#9a9aba"/>
+    </linearGradient>
+    <linearGradient x1="3.616" y1="3.893" x2="1.285" y2="-.757" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 .8684 0 1046.257)"/>
+    <linearGradient x1="2.232" y1="4.162" x2=".629" y2=".966" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1 0 0 .8684 0 1046.257)"/>
+  </defs>
+  <path d="M.2 1045.562l4.6 3.3-4.6 3.3 2-3.3z" fill="url(#c)" stroke="url(#d)" stroke-width=".4" stroke-linejoin="round" transform="translate(0 -1045.362)"/>
 </svg>
--- a/devtools/client/themes/images/firebug/breakpoint.svg
+++ b/devtools/client/themes/images/firebug/breakpoint.svg
@@ -1,13 +1,13 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="12" height="12">
-    <defs>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#c80000"/>
-            <stop offset="1" stop-color="#780000"/>
-        </linearGradient>
-        <radialGradient cx="4.8" cy="4.665" r="5.59" fx="4.8" fy="4.665" id="b" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
-    </defs>
-    <path d="M11.553 5.995a5.59 5.59 0 1 1-11.18 0 5.59 5.59 0 1 1 11.18 0z" transform="translate(-.4 -.435) scale(1.0734)" fill="url(#b)"/>
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#c80000"/>
+      <stop offset="1" stop-color="#780000"/>
+    </linearGradient>
+    <radialGradient cx="4.8" cy="4.665" r="5.59" fx="4.8" fy="4.665" id="b" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M11.553 5.995a5.59 5.59 0 1 1-11.18 0 5.59 5.59 0 1 1 11.18 0z" transform="translate(-.4 -.435) scale(1.0734)" fill="url(#b)"/>
 </svg>
--- a/devtools/client/themes/images/firebug/close.svg
+++ b/devtools/client/themes/images/firebug/close.svg
@@ -1,25 +1,25 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#f2451d"/>
-            <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
-            <stop offset=".897" stop-color="#de8493"/>
-            <stop offset="1" stop-color="#efc3cc"/>
-        </linearGradient>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#520e0d"/>
-            <stop offset="1" stop-color="#c4181d"/>
-        </linearGradient>
-        <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <filter x="-.24" y="-.24" width="1.48" height="1.48" color-interpolation-filters="sRGB" id="e">
-            <feGaussianBlur stdDeviation=".713"/>
-        </filter>
-    </defs>
-    <rect width="13" height="13" rx="2" ry="2" x="1.5" y="1.5" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
-    <path d="M6.5 5.437L4.938 7l2 2-2 2L6.5 12.562l2-2 2 2L12.064 11l-2-2 2-2L10.5 5.437l-2 2-2-2z" opacity=".4" filter="url(#e)"/>
-    <path d="M6 4.438L4.437 6l2 2-2 2L6 11.563l2-2 2 2L11.563 10l-2-2 2-2L10 4.437l-2 2-2-2z" fill="#fff"/>
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#f2451d"/>
+      <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
+      <stop offset=".897" stop-color="#de8493"/>
+      <stop offset="1" stop-color="#efc3cc"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#520e0d"/>
+      <stop offset="1" stop-color="#c4181d"/>
+    </linearGradient>
+    <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <filter x="-.24" y="-.24" width="1.48" height="1.48" color-interpolation-filters="sRGB" id="e">
+      <feGaussianBlur stdDeviation=".713"/>
+    </filter>
+  </defs>
+  <rect width="13" height="13" rx="2" ry="2" x="1.5" y="1.5" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
+  <path d="M6.5 5.437L4.938 7l2 2-2 2L6.5 12.562l2-2 2 2L12.064 11l-2-2 2-2L10.5 5.437l-2 2-2-2z" opacity=".4" filter="url(#e)"/>
+  <path d="M6 4.438L4.437 6l2 2-2 2L6 11.563l2-2 2 2L11.563 10l-2-2 2-2L10 4.437l-2 2-2-2z" fill="#fff"/>
 </svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-console.svg
@@ -0,0 +1,31 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#325de6"/>
+      <stop offset="1" stop-color="#8ba3f1"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#1a47d6"/>
+      <stop offset="1" stop-color="#6786ed"/>
+    </linearGradient>
+    <linearGradient x1="7.771" y1="13.61" x2="7.771" y2="2.16" id="e" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.70047 0 0 .8 8.145 1037.962)"/>
+    <linearGradient x1="7.21" y1="14.919" x2="7.21" y2="1.081" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.70047 0 0 .8 8.145 1037.962)"/>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#282828"/>
+      <stop offset="1" stop-color="#505050"/>
+    </linearGradient>
+    <linearGradient id="d">
+      <stop offset="0"/>
+      <stop offset="1" stop-color="#3c3c3c"/>
+    </linearGradient>
+    <linearGradient x1="7.771" y1="15.451" x2="7.771" y2="3.941" id="g" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.9204 0 0 .56 15.804 1039.472)"/>
+    <linearGradient x1="7.21" y1="16.411" x2="7.21" y2="3.037" id="h" xlink:href="#d" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-.9204 0 0 .56 15.804 1039.472)"/>
+  </defs>
+  <g stroke-linejoin="round">
+    <path d="M1.14 1039.162l4.56 5.167-4.56 5.233-.84-.8 3.166-4.433-3.166-4.367z" fill="url(#e)" stroke="url(#f)" stroke-width=".6" transform="translate(0 -1036.362)"/>
+    <path d="M5.688 1040.05v1.125h10.125v-1.125H5.687zm2.5 3.75v1.125h7.624v-1.125H8.189zm-2.5 3.75v1.125h10.125v-1.125H5.687z" style="marker:none" color="#000" fill="url(#g)" stroke="url(#h)" stroke-width=".4" overflow="visible" transform="translate(0 -1036.362)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-eyedropper.svg
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="e">
+      <stop offset="0" stop-color="#e97f7f"/>
+      <stop offset="1" stop-color="#efa1a1"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#e2e9ea"/>
+      <stop offset="1" stop-color="#fff"/>
+    </linearGradient>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#65888b"/>
+      <stop offset="1" stop-color="#91adaf"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#746b54"/>
+      <stop offset="1" stop-color="#454033"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#8c7f64"/>
+      <stop offset="1" stop-color="#5d5543"/>
+    </linearGradient>
+    <radialGradient xlink:href="#a" id="h" cx="8.847" cy="1.845" fx="8.847" fy="1.845" r="1.587" gradientTransform="matrix(1.27453 .37742 -.46407 1.55272 -1.03 -4.65)" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(1.11829 0 0 1.11313 -.505 -.258)" xlink:href="#b" id="i" x1="8.531" y1=".95" x2="9.908" y2="4.42" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(1.11794 0 0 1.11348 .554 .12)" xlink:href="#b" id="k" x1="-6.81" y1="-1.866" x2="-12.495" y2="-1.812" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(1.11794 0 0 1.11348 .554 .12)" xlink:href="#a" id="j" x1="-7.216" y1="-2.356" x2="-12.008" y2="-2.36" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(1.11829 0 0 1.11313 -.505 -.258)" xlink:href="#c" id="f" x1="7.153" y1="11.831" x2="6.271" y2="11.424" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(1.11829 0 0 1.11313 -.505 -.258)" xlink:href="#d" id="g" x1="8.328" y1="9.463" x2="6.703" y2="8.785" gradientUnits="userSpaceOnUse"/>
+    <linearGradient xlink:href="#e" id="l" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.11829 0 0 1.11313 -.505 -.258)" x1="7.153" y1="11.831" x2="6.271" y2="11.424"/>
+  </defs>
+  <path d="M6.705 15.7c1.12-1.51 1.556-2.841 1.973-4.387.502-1.793.766-3.668 1.251-5.471l-1.73-.462c-.485 1.804-1.194 3.562-1.665 5.363-.406 1.532-.717 2.951-.495 4.78z" fill="url(#f)" stroke="url(#g)" stroke-width=".4" stroke-linejoin="round"/>
+  <path d="M10.647 4.88a1.677 2.784 15.142 0 0 .76-1.525 1.677 2.784 15.142 0 0-.896-3.12 1.677 2.784 15.142 0 0-2.344 2.256 1.677 2.784 15.142 0 0-.109 1.7l2.589.69z" fill="url(#h)" stroke="url(#i)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect width="6.708" height="1.113" x="-13.565" y="-3.086" rx=".5" ry=".5" transform="rotate(-165.066) skewX(-.132)" fill="url(#j)" stroke="url(#k)" stroke-width=".4" stroke-linejoin="round"/>
+  <path d="M8.558 10.973l-2.003.496c-.315 1.252-.487 2.44-.332 3.901l.406.107c1.03-1.428 1.416-2.582 1.86-4.226.028-.107.05-.199.07-.278z" fill="url(#l)"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-frames.svg
@@ -0,0 +1,25 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#fff"/>
+      <stop offset="1" stop-color="#f0f0f0"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#e1e8ff"/>
+      <stop offset="1" stop-color="#b9c9ff"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#505050"/>
+      <stop offset="1" stop-color="#787878"/>
+    </linearGradient>
+    <linearGradient gradientTransform="translate(.078 -1.018) scale(1.07692)" gradientUnits="userSpaceOnUse" y2="2.767" x2="1.624" y1="14.154" x1="13.01" id="d" xlink:href="#a"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="12.503" x2="12.396" y1="3.285" x1="3.179" id="e" xlink:href="#b"/>
+    <linearGradient y2="12.503" x2="12.396" y1="3.285" x1="3.179" gradientUnits="userSpaceOnUse" id="f" xlink:href="#c"/>
+  </defs>
+  <rect ry="1" rx="1" y="1" x="1" height="14" width="14" fill="url(#d)"/>
+  <path d="M7 2h7v3H7zM2 2h4v12H2z" fill="url(#e)"/>
+  <path d="M7 6h7v8H7z" fill="url(#f)"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-measure.svg
@@ -0,0 +1,26 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#505424"/>
+      <stop offset="1" stop-color="#6c6f31"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#e8eace"/>
+      <stop offset="1" stop-color="#f5f6ea"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#484a2e"/>
+      <stop offset="1" stop-color="#61633d"/>
+    </linearGradient>
+    <linearGradient xlink:href="#a" id="d" x1="-.831" y1="11.595" x2="2.378" y2="1.336" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.2174 0 0 1 -11.66 -4.6)"/>
+    <linearGradient xlink:href="#b" id="e" x1="1.536" y1="14.334" x2="5.493" y2="1.678" gradientUnits="userSpaceOnUse" gradientTransform="matrix(1.2174 0 0 1 -11.66 -4.6)"/>
+    <linearGradient xlink:href="#c" id="f" x1="1.695" y1="14.28" x2="6.421" y2="1.672" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-90 3.4 8)"/>
+  </defs>
+  <g transform="translate(4.6 .6)">
+    <rect transform="rotate(-90)" ry=".5" rx=".5" y="-3.4" x="-10.2" height="13.6" width="5.6" fill="url(#d)" stroke="url(#e)" stroke-width=".4" stroke-linejoin="round"/>
+    <path d="M-2.1 10.4v-3m11 3v-3m-2.75 3V8.9M3.4 10.4v-3m-2.75 3V8.9" fill="none" stroke="url(#f)"/>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-noautohide.svg
@@ -0,0 +1,47 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#747254"/>
+      <stop offset="1" stop-color="#8b8965"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#e1e1d7"/>
+      <stop offset="1" stop-color="#f2f2ee"/>
+    </linearGradient>
+    <linearGradient gradientTransform="translate(-2.38 -2.38) scale(1.17241)" gradientUnits="userSpaceOnUse" y2="4.847" x2="2.94" y1="12.978" x1="13.037" id="g" xlink:href="#a"/>
+    <linearGradient gradientTransform="translate(-2.38 -2.38) scale(1.17241)" gradientUnits="userSpaceOnUse" y2="2.729" x2="4.832" y1="11.063" x1="14.997" id="h" xlink:href="#b"/>
+    <linearGradient gradientTransform="matrix(.64 0 0 .6988 .88 .987)" gradientUnits="userSpaceOnUse" xlink:href="#c" id="i" y2=".583" x2="6.34" y1="4.311" x1="8.637"/>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#c8c8c8"/>
+      <stop offset="1" stop-color="#dcdcdc"/>
+    </linearGradient>
+    <linearGradient gradientTransform="matrix(.64 0 0 .6988 .88 .987)" gradientUnits="userSpaceOnUse" xlink:href="#d" id="j" y2="1.392" x2="4.956" y1="5.078" x1="7.188"/>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#a0a0a0"/>
+      <stop offset="1" stop-color="#c8c8c8"/>
+    </linearGradient>
+    <linearGradient gradientTransform="matrix(.62152 0 0 .5895 1.028 -609.403)" gradientUnits="userSpaceOnUse" xlink:href="#c" id="k" y2="1040.666" x2="4.559" y1="1052.085" x1="11.377"/>
+    <linearGradient gradientTransform="matrix(.62152 0 0 .5895 1.028 -609.403)" gradientUnits="userSpaceOnUse" xlink:href="#d" id="l" y2="1041.923" x2="1.917" y1="1053.385" x1="8.842"/>
+    <linearGradient gradientTransform="matrix(.71429 0 0 .71492 .286 .276)" gradientUnits="userSpaceOnUse" xlink:href="#e" id="m" y2="7.825" x2="6.608" y1="12.498" x1="8.54"/>
+    <linearGradient id="e">
+      <stop offset="0" stop-color="#505050"/>
+      <stop offset="1" stop-color="#787878"/>
+    </linearGradient>
+    <linearGradient gradientTransform="matrix(.71429 0 0 .71492 .286 .276)" gradientUnits="userSpaceOnUse" xlink:href="#f" id="n" y2="7.414" x2="7.402" y1="12.116" x1="9.392"/>
+    <linearGradient id="f">
+      <stop offset="0" stop-color="#787878"/>
+      <stop offset="1" stop-color="#b4b4b4"/>
+    </linearGradient>
+  </defs>
+  <g stroke-linejoin="round">
+    <path d="M9.351.2l1.603 1.575.833.819h.84c.65 0 1.173.52 1.173 1.167v8.872c0 .646-.523 1.167-1.172 1.167H1.372C.722 13.8.2 13.28.2 12.633V3.76c0-.647.523-1.167 1.172-1.167h5.543l.834-.819L9.35.2z" fill="url(#g)" stroke="url(#h)" stroke-width=".4"/>
+    <g transform="matrix(.9821 0 0 .98213 5.107 5.107)">
+      <path d="M6 1.215a2.982 2.982 0 0 0-2.991 2.904c.114-.019.238-.045.357-.045h.938C4.386 3.194 5.1 2.421 6 2.421c.899 0 1.614.773 1.696 1.653h.938c.119 0 .243.026.357.045A2.982 2.982 0 0 0 6 1.215z" fill="url(#i)" stroke="url(#j)" stroke-width=".611"/>
+      <rect y="4.065" x="1.214" ry=".787" rx=".787" height="6.72" width="9.571" fill="url(#k)" stroke="url(#l)" stroke-width=".611"/>
+      <path d="M6 5.504A1.2 1.2 0 0 0 4.795 6.71c0 .562.375 1.023.893 1.162v1.475h.625V7.872c.517-.139.892-.6.892-1.162A1.2 1.2 0 0 0 6 5.504z" fill="url(#m)" stroke="url(#n)" stroke-width=".407"/>
+    </g>
+  </g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-paintflashing.svg
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#8c8c8c"/>
+      <stop offset="1" stop-color="#b4b4b4"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#544024"/>
+      <stop offset="1" stop-color="#8b6b3d"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#a0a0a0"/>
+      <stop offset="1" stop-color="#a0a0a0" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient id="e">
+      <stop offset="0" stop-color="#a0a0a0"/>
+      <stop offset="1" stop-color="#c8c8c8"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#382b18"/>
+      <stop offset="1" stop-color="#6f5631"/>
+    </linearGradient>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="3.409" x2="6.735" y1="3.859" x1="9.123" id="f" xlink:href="#a"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="8.347" x2="2.4" y1="9.586" x1="13.352" id="j" xlink:href="#a"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="11.675" x2="7.974" y1="14.423" x1="7.974" id="l" xlink:href="#b" gradientTransform="translate(0 -.2)"/>
+    <linearGradient xlink:href="#c" id="g" x1="9.32" y1="7.243" x2="6.728" y2="6.716" gradientUnits="userSpaceOnUse"/>
+    <linearGradient xlink:href="#c" id="k" x1="12.451" y1="11.469" x2="1.56" y2="10.342" gradientUnits="userSpaceOnUse"/>
+    <linearGradient xlink:href="#d" id="i" x1="13.218" y1="13.627" x2="2.686" y2="13.627" gradientUnits="userSpaceOnUse"/>
+    <linearGradient xlink:href="#e" id="h" x1="12.607" y1="12.021" x2="3.321" y2="12.021" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M8 1c-.828 0-1.5 1.343-1.5 3v3.5c0 .554.446 1 1 1h1c.554 0 1-.446 1-1V4c0-1.657-.672-3-1.5-3z" fill="url(#f)" stroke="url(#g)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect width="11" height="5" x="2.5" y="10" rx=".5" ry=".5" fill="url(#h)" stroke="url(#i)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect width="12" height="2" x="2" y="8" rx=".5" ry=".5" fill="url(#j)" stroke="url(#k)" stroke-width=".4" stroke-linejoin="round"/>
+  <path d="M11.5 10.8h1v4h-1zm-2 0h1v4h-1zm-2 0h1v4h-1zm-2 0h1v4h-1zm-2 0h1v4h-1z" fill="url(#l)"/>
+</svg>
--- a/devtools/client/themes/images/firebug/command-pick.svg
+++ b/devtools/client/themes/images/firebug/command-pick.svg
@@ -1,24 +1,20 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#6f9fdf"/>
-            <stop offset="1" stop-color="#b8d0f1"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#133cb8"/>
-            <stop offset="1" stop-color="#7faae8"/>
-        </linearGradient>
-        <linearGradient x1="11.304" y1="9.268" x2="7.065" y2="4.197" id="e" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#6f9fdf"/>
-            <stop offset="1" stop-color="#b8d0f1"/>
-        </linearGradient>
-        <linearGradient x1="6.587" y1="7.594" x2="2.992" y2=".487" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
-    </defs>
-    <path d="M.5.813c-.273.04-.502.354-.5.687v5.625c0 .36.265.688.563.688h4.406v-1h-3.5a.52.52 0 0 1-.5-.5V2.218a.517.517 0 0 1 .437-.5h3.75c.227-.4.634-.711 1.094-.75.415-.031.84.134 1.125.437l.313.313h6.843c.262 0 .5.238.5.5v4.094a.52.52 0 0 1-.5.5h-1.969l.938 1h1.938c.297 0 .562-.328.562-.688V1.5c0-.36-.265-.688-.563-.688H.5z" fill="#fff"/>
-    <path d="M.5 0C.227.041-.002.355 0 .688v5.625C0 6.673.265 7 .563 7h4.406V6h-3.5a.52.52 0 0 1-.5-.5V1.406a.517.517 0 0 1 .437-.5h13.125c.262 0 .5.238.5.5V5.5a.52.52 0 0 1-.5.5h-2.75l.969 1h2.688c.297 0 .562-.328.562-.688V.688c0-.36-.265-.687-.563-.687H.5z" fill="url(#d)"/>
-    <path d="M6.375 2.375v9.906l2.094-.844 1.625 3.844 2.531-1.062-1.594-3.781 2.188-.876L9.813 5.97 6.374 2.375z" fill="url(#e)" stroke="#4673ce" stroke-linejoin="round"/>
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#6f9fdf"/>
+      <stop offset="1" stop-color="#b8d0f1"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#133cb8"/>
+      <stop offset="1" stop-color="#7faae8"/>
+    </linearGradient>
+    <linearGradient x1="11.304" y1="9.268" x2="7.065" y2="4.197" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+    <linearGradient x1="6.587" y1="7.594" x2="2.992" y2=".487" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M.5.813c-.273.04-.502.354-.5.687v5.625c0 .36.265.688.563.688h4.406v-1h-3.5a.52.52 0 0 1-.5-.5V2.218a.517.517 0 0 1 .437-.5h3.75c.227-.4.634-.711 1.094-.75.415-.031.84.134 1.125.437l.313.313h6.843c.262 0 .5.238.5.5v4.094a.52.52 0 0 1-.5.5h-1.969l.938 1h1.938c.297 0 .562-.328.562-.688V1.5c0-.36-.265-.688-.563-.688H.5z" fill="#fff"/>
+  <path d="M.5 0C.227.041-.002.355 0 .688v5.625C0 6.673.265 7 .563 7h4.406V6h-3.5a.52.52 0 0 1-.5-.5V1.406a.517.517 0 0 1 .437-.5h13.125c.262 0 .5.238.5.5V5.5a.52.52 0 0 1-.5.5h-2.75l.969 1h2.688c.297 0 .562-.328.562-.688V.688c0-.36-.265-.687-.563-.687H.5z" fill="url(#c)"/>
+  <path d="M6.375 2.375v9.906l2.094-.844 1.625 3.844 2.531-1.062-1.594-3.781 2.188-.876L9.813 5.97 6.374 2.375z" fill="url(#d)" stroke="#4673ce" stroke-linejoin="round"/>
 </svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-responsivemode.svg
@@ -0,0 +1,39 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#141414"/>
+      <stop offset="1" stop-color="#3c3c3c"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#cdf0ff"/>
+      <stop offset="1" stop-color="#f5fcff"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#787878"/>
+      <stop offset="1" stop-color="#505050"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#282828"/>
+      <stop offset="1" stop-color="#505050"/>
+    </linearGradient>
+    <linearGradient gradientTransform="matrix(1.04615 0 0 1.06667 -.415 .667)" gradientUnits="userSpaceOnUse" y2="1.576" x2="3.337" y1="8.108" x1="14.078" id="e" xlink:href="#a"/>
+    <linearGradient gradientTransform="matrix(2 0 0 2 -13 -4)" gradientUnits="userSpaceOnUse" y2="4.752" x2="13.239" y1="5.261" x1="13.748" id="h" xlink:href="#b"/>
+    <linearGradient gradientTransform="translate(0 1)" gradientUnits="userSpaceOnUse" y2="1.854" x2="3.829" y1="8.432" x1="12.299" id="g" xlink:href="#c"/>
+    <linearGradient gradientTransform="translate(-.5 -.5)" gradientUnits="userSpaceOnUse" y2="5.946" x2=".987" y1="15.102" x1="5.989" id="i" xlink:href="#a"/>
+    <linearGradient gradientTransform="matrix(1.10989 0 0 .79278 -.4 .958)" gradientUnits="userSpaceOnUse" y2="10.774" x2="4.865" y1="10.774" x1="1.261" id="k" xlink:href="#c"/>
+    <linearGradient gradientTransform="matrix(1.5 0 0 1.5 -17.25 6)" gradientUnits="userSpaceOnUse" y2="4.752" x2="13.239" y1="5.261" x1="13.748" id="l" xlink:href="#b"/>
+    <linearGradient gradientTransform="matrix(.683 0 0 .6166 .985 2.273)" gradientUnits="userSpaceOnUse" y2="5.597" x2="2.826" y1="6.539" x1="3.079" id="m" xlink:href="#b"/>
+    <linearGradient xlink:href="#d" id="f" x1="13.677" y1="12.328" x2=".512" y2="4.058" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.97143 0 0 .96 .257 .24)"/>
+    <linearGradient xlink:href="#d" id="j" x1="5.489" y1="14.602" x2=".487" y2="5.446" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <rect ry="1" rx="1" y="1.2" x="2.2" height="9.6" width="13.6" fill="url(#e)" stroke="url(#f)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect ry=".5" rx=".5" y="2.5" x="3.5" height="7" width="9" fill="url(#g)"/>
+  <circle r="1" cy="6" cx="14" fill="url(#h)"/>
+  <rect ry="1" rx="1" y="5" height="10" width="6" fill="url(#i)" stroke="url(#j)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect ry=".5" rx=".5" y="7" x="1" height="5" width="4" fill="url(#k)"/>
+  <circle r=".75" cy="13.5" cx="3" fill="url(#l)"/>
+  <rect ry=".5" rx=".5" y="5.75" x="2" height=".5" width="2" fill="url(#m)"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-rulers.svg
@@ -0,0 +1,20 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#e2e6ea"/>
+      <stop offset="1" stop-color="#f9fafb"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#323b46"/>
+      <stop offset="1" stop-color="#546374"/>
+    </linearGradient>
+    <linearGradient gradientUnits="userSpaceOnUse" y2=".822" x2="4.016" y1="13.198" x1="8.663" id="d" xlink:href="#a"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="1.975" x2="2.064" y1="13.044" x1="6.113" id="c" xlink:href="#b"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="1.511" x2="3.948" y1="13.163" x1="7.795" id="e" xlink:href="#a"/>
+  </defs>
+  <path d="M1.7 1.2c-.278 0-.5.222-.5.5v12.6c0 .278.222.5.5.5h3.6c.278 0 .5-.222.5-.5V5.8h8.5c.278 0 .5-.222.5-.5V1.7c0-.278-.222-.5-.5-.5H1.7z" fill="url(#c)" stroke="url(#d)" stroke-width=".4" stroke-linejoin="round"/>
+  <path d="M1 4.5h1.5M1 7.5h3m-3 3h1.5m-1.5 3h3M4.5 1v1.5m3-1.5v3m3-3v1.5m3-1.5v3" fill="none" stroke="url(#e)"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-scratchpad.svg
@@ -0,0 +1,38 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 16 16" height="16" width="16">
+  <defs>
+    <linearGradient id="e">
+      <stop offset="0" stop-color="#434f5d"/>
+      <stop offset="1" stop-color="#65788b"/>
+    </linearGradient>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#787878"/>
+      <stop offset="1" stop-color="#8c8c8c"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#8c8c8c"/>
+      <stop offset="1" stop-color="#a0a0a0"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#b6b38a"/>
+      <stop offset="1" stop-color="#d3d2bd"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#ecebe0"/>
+      <stop offset="1" stop-color="#fbfbf9" stop-opacity="0"/>
+    </linearGradient>
+    <linearGradient gradientTransform="translate(-1.018 -.726)" gradientUnits="userSpaceOnUse" y2="4.549" x2="4.08" y1="14.382" x1="13.934" id="f" xlink:href="#a"/>
+    <linearGradient gradientTransform="translate(-1.018 -.726)" gradientUnits="userSpaceOnUse" y2="4.836" x2="1.893" y1="16.614" x1="13.78" id="g" xlink:href="#b"/>
+    <linearGradient y2="2.41" x2="4.751" y1="4.023" x1="5.458" gradientTransform="translate(-1.018 -1.026)" gradientUnits="userSpaceOnUse" id="h" xlink:href="#c"/>
+    <linearGradient y2=".94" x2="4.252" y1="3.313" x1="5.323" gradientTransform="translate(0 -.3)" gradientUnits="userSpaceOnUse" id="i" xlink:href="#d"/>
+    <linearGradient gradientUnits="userSpaceOnUse" y2="9.29" x2="11.377" y1="9.29" x1="4.575" id="j" xlink:href="#e"/>
+  </defs>
+  <path style="marker:none" color="#000" overflow="visible" fill="url(#f)" stroke="url(#g)" stroke-linejoin="round" d="M2 2.75h12v12H2z"/>
+  <path style="marker:none" d="M4 .75c-.553 0-1 .672-1 1.5 0 .829.399 1.474 1 1.5.106.006.2-.08.2-.25 0-.168-.11-.25-.2-.25-.277 0-.5-.447-.5-1 0-.552.223-1 .5-1 .275 0 .5.448.5 1H5c0-.828-.448-1.5-1-1.5z" id="k" color="#000" overflow="visible" fill="url(#h)" stroke="url(#i)" stroke-width=".2" stroke-linejoin="round"/>
+  <path d="M4 11.45h7v1H4zm1-5.321h5v1H5zm1 2.66h6v1H6z" style="marker:none" color="#000" overflow="visible" fill="url(#j)"/>
+  <use height="100%" width="100%" transform="translate(2.667)" id="l" xlink:href="#k"/>
+  <use height="100%" width="100%" transform="translate(2.667)" id="m" xlink:href="#l"/>
+  <use height="100%" width="100%" transform="translate(2.667)" xlink:href="#m"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/images/firebug/command-screenshot.svg
@@ -0,0 +1,39 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
+  <defs>
+    <linearGradient id="e">
+      <stop offset="0" stop-color="#65808b"/>
+      <stop offset="1" stop-color="#7c939c"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#43555d"/>
+      <stop offset="1" stop-color="#566a72"/>
+    </linearGradient>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#54a0ec"/>
+      <stop offset="1" stop-color="#99c5f7"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#f6fafe"/>
+      <stop offset="1" stop-color="#99c5f7"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#324046"/>
+      <stop offset="1" stop-color="#45555b"/>
+    </linearGradient>
+    <linearGradient xlink:href="#a" id="h" x1="13.661" y1="12.474" x2="2.358" y2="5.025" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.97143 0 0 .96 .229 .612)"/>
+    <linearGradient xlink:href="#b" id="i" x1="16.505" y1="11.096" x2="2.974" y2="2.807" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.97143 0 0 .96 .229 .612)"/>
+    <linearGradient xlink:href="#b" id="k" x1="10.3" y1="11.02" x2="5.662" y2="6.382" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 .236)"/>
+    <linearGradient xlink:href="#a" id="j" x1="10.582" y1="9.815" x2="6.843" y2="6.172" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 .236)"/>
+    <radialGradient xlink:href="#c" id="l" cx="7.075" cy="7.944" fx="7.075" fy="7.944" r="2.5" gradientUnits="userSpaceOnUse" gradientTransform="rotate(45 9.338 9.078) scale(1.00392 1.2642)"/>
+    <linearGradient xlink:href="#d" id="m" x1="8.873" y1="11.096" x2="5.628" y2="7.85" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 .236)"/>
+    <linearGradient xlink:href="#a" id="g" x1="10.226" y1="3.728" x2="6.522" y2="2.07" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 .314)"/>
+    <linearGradient xlink:href="#e" id="f" x1="9.212" y1="4.437" x2="6.127" y2="3.047" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 .314)"/>
+  </defs>
+  <path d="M6.5 2.225c-.277 0-.641.14-.816.316L4.316 3.908c-.175.175-.093.317.184.317h7c.277 0 .359-.142.184-.317l-1.368-1.367c-.175-.175-.539-.316-.816-.316h-2z" fill="url(#f)" stroke="url(#g)" stroke-width=".4" stroke-linejoin="round"/>
+  <rect ry="1" rx="1" y="4.2" x="1.2" height="9.6" width="13.6" fill="url(#h)" stroke="url(#i)" stroke-width=".4" stroke-linejoin="round"/>
+  <circle r="3.5" cy="9" cx="8" fill="url(#j)" stroke="url(#k)" stroke-width=".4" stroke-linejoin="round"/>
+  <circle cx="8" cy="9" r="2.5" fill="url(#l)" stroke="url(#m)" stroke-width=".4" stroke-linejoin="round"/>
+</svg>
--- a/devtools/client/themes/images/firebug/commandline-icon.svg
+++ b/devtools/client/themes/images/firebug/commandline-icon.svg
@@ -1,18 +1,18 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="14" height="14">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#234ccd"/>
-            <stop offset="1" stop-color="#5d7de3"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#1e3faa"/>
-            <stop offset="1" stop-color="#3a61de"/>
-        </linearGradient>
-        <linearGradient x1="2.002" y1="12.252" x2="-.099" y2="6.755" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(5.841 1034.646)"/>
-        <linearGradient x1="3.309" y1="11.177" x2="1.468" y2="6.456" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(5.841 1034.646)"/>
-    </defs>
-    <path d="M6.841 1040.052l-.437.406 2.469 3.688-2.47 3.687.438.407 3.438-4.094z" fill="url(#c)" stroke="url(#d)" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round" transform="translate(-1.341 -1037.146)"/>
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#234ccd"/>
+      <stop offset="1" stop-color="#5d7de3"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#1e3faa"/>
+      <stop offset="1" stop-color="#3a61de"/>
+    </linearGradient>
+    <linearGradient x1="2.002" y1="12.252" x2="-.099" y2="6.755" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(5.841 1034.646)"/>
+    <linearGradient x1="3.309" y1="11.177" x2="1.468" y2="6.456" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(5.841 1034.646)"/>
+  </defs>
+  <path d="M6.841 1040.052l-.437.406 2.469 3.688-2.47 3.687.438.407 3.438-4.094z" fill="url(#c)" stroke="url(#d)" stroke-width=".4" stroke-linecap="round" stroke-linejoin="round" transform="translate(-1.341 -1037.146)"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-blackbox.svg
+++ b/devtools/client/themes/images/firebug/debugger-blackbox.svg
@@ -1,30 +1,30 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-    <defs>
-        <linearGradient id="d">
-            <stop offset="0" stop-color="#323232"/>
-            <stop offset="1" stop-color="#646464"/>
-        </linearGradient>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#b4b4b4"/>
-            <stop offset="1" stop-color="#dcdcdc"/>
-        </linearGradient>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#3c3c3c"/>
-            <stop offset="1" stop-color="#646464"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#505050"/>
-            <stop offset="1" stop-color="#8c8c8c"/>
-        </linearGradient>
-        <linearGradient gradientTransform="matrix(.97143 0 0 1.08571 .229 -1125.879)" xlink:href="#a" id="e" x1="10.803" y1="1047.39" x2="4.726" y2="1041.559" gradientUnits="userSpaceOnUse"/>
-        <linearGradient gradientTransform="matrix(.97143 0 0 1.08571 .229 -1125.879)" xlink:href="#b" id="f" x1="12.563" y1="1046.633" x2="5.974" y2="1040.229" gradientUnits="userSpaceOnUse"/>
-        <linearGradient gradientTransform="matrix(1.16667 0 0 1.16667 -1.333 -1210.423)" xlink:href="#c" id="g" x1="9.698" y1="1046.429" x2="5.893" y2="1042.623" gradientUnits="userSpaceOnUse"/>
-        <linearGradient gradientTransform="translate(0 -1036.362)" xlink:href="#d" id="h" x1="9.023" y1="1045.897" x2="6.49" y2="1043.363" gradientUnits="userSpaceOnUse"/>
-    </defs>
-    <path d="M14.8 8c0 1.085-3.044 3.8-6.8 3.8S1.2 9.085 1.2 8 4.244 4.2 8 4.2s6.8 2.715 6.8 3.8z" style="marker:none" color="#000" overflow="visible" fill="url(#e)" stroke="url(#f)" stroke-width=".4" stroke-linejoin="round"/>
-    <circle r="3.5" cy="8" cx="8" style="marker:none" color="#000" overflow="visible" fill="url(#g)"/>
-    <circle r="2" cy="8" cx="8" style="marker:none" color="#000" overflow="visible" fill="url(#h)"/>
+  <defs>
+    <linearGradient id="d">
+      <stop offset="0" stop-color="#323232"/>
+      <stop offset="1" stop-color="#646464"/>
+    </linearGradient>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#b4b4b4"/>
+      <stop offset="1" stop-color="#dcdcdc"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#3c3c3c"/>
+      <stop offset="1" stop-color="#646464"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#505050"/>
+      <stop offset="1" stop-color="#8c8c8c"/>
+    </linearGradient>
+    <linearGradient gradientTransform="matrix(.97143 0 0 1.08571 .229 -1125.879)" xlink:href="#a" id="e" x1="10.803" y1="1047.39" x2="4.726" y2="1041.559" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="matrix(.97143 0 0 1.08571 .229 -1125.879)" xlink:href="#b" id="f" x1="12.563" y1="1046.633" x2="5.974" y2="1040.229" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="translate(-1.333 -1210.423) scale(1.16667)" xlink:href="#c" id="g" x1="9.698" y1="1046.429" x2="5.893" y2="1042.623" gradientUnits="userSpaceOnUse"/>
+    <linearGradient gradientTransform="translate(0 -1036.362)" xlink:href="#d" id="h" x1="9.023" y1="1045.897" x2="6.49" y2="1043.363" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M14.8 8c0 1.085-3.044 3.8-6.8 3.8S1.2 9.085 1.2 8 4.244 4.2 8 4.2s6.8 2.715 6.8 3.8z" style="marker:none" color="#000" overflow="visible" fill="url(#e)" stroke="url(#f)" stroke-width=".4" stroke-linejoin="round"/>
+  <circle r="3.5" cy="8" cx="8" style="marker:none" color="#000" overflow="visible" fill="url(#g)"/>
+  <circle r="2" cy="8" cx="8" style="marker:none" color="#000" overflow="visible" fill="url(#h)"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-prettyprint.svg
+++ b/devtools/client/themes/images/firebug/debugger-prettyprint.svg
@@ -1,32 +1,18 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#1c3b5c"/>
-            <stop offset="1" stop-color="#285078"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#285a8c"/>
-            <stop offset="1" stop-color="#508cc8"/>
-        </linearGradient>
-        <linearGradient x1="12.425" y1="1050.223" x2="3.575" y2="1038.481" id="e" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
-        <linearGradient x1="11.258" y1="1051.389" x2="2.071" y2="1039.269" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#285a8c"/>
-            <stop offset="1" stop-color="#508cc8"/>
-        </linearGradient>
-        <linearGradient id="d">
-            <stop offset="0" stop-color="#1c3b5c"/>
-            <stop offset="1" stop-color="#285078"/>
-        </linearGradient>
-        <linearGradient x1="12.425" y1="1050.223" x2="3.575" y2="1038.481" id="i" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
-        <linearGradient x1="11.258" y1="1051.389" x2="2.071" y2="1039.269" id="j" xlink:href="#d" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
-        <linearGradient x1="12.425" y1="1050.223" x2="3.575" y2="1038.481" id="g" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 8.814 0)"/>
-        <linearGradient x1="11.258" y1="1051.389" x2="2.071" y2="1039.269" id="h" xlink:href="#d" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 8.814 0)"/>
-        <linearGradient x1="17.286" y1="1046.293" x2="-18.065" y2="1003.191" id="k" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
-        <linearGradient x1="12.826" y1="1050.761" x2="-24.272" y2="1006.022" id="l" xlink:href="#d" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
-    </defs>
-    <path d="M21.734 1045.673v5.125h-1.816c-4.864 0-9.028-.723-10.688-2.168-1.64-1.445-2.46-4.326-2.46-8.643v-6.098c0-2.95-.528-4.99-1.583-6.123-1.054-1.133-2.968-1.7-5.742-1.7h-1.787v-4.189h1.787c2.793 0 4.707-.556 5.742-1.67 1.055-1.133 1.582-3.154 1.582-6.064v-6.127c0-4.317.82-7.188 2.461-8.613 1.66-1.446 5.824-2.168 10.688-2.168h1.816v5.093h-1.992c-2.754 0-4.55.43-5.39 1.29-.84.859-1.26 1.761-1.26 4.515v6.361c0 3.067-1.352 5.293-2.25 6.68-.88 1.387-1.49 2.324-3.64 2.813 2.169.527 2.79 1.484 3.669 2.87.879 1.387 2.22 3.604 2.22 6.65v6.363c0 2.754.42 3.654 1.26 4.514.84.859 2.96 1.289 5.391 1.289zm12.579 0v5.125h1.816c4.864 0 9.028-.723 10.688-2.168 1.64-1.445 2.46-4.326 2.46-8.643v-6.098c0-2.95.528-4.99 1.583-6.123 1.054-1.133 2.968-1.7 5.742-1.7h1.787v-4.189h-1.787c-2.793 0-4.707-.556-5.742-1.67-1.055-1.133-1.582-3.154-1.582-6.064v-6.127c0-4.317-.82-7.188-2.461-8.613-1.66-1.446-5.824-2.168-10.688-2.168h-1.816v5.093h1.992c2.754 0 4.55.43 5.39 1.29.84.859 1.26 1.761 1.26 4.515v6.361c0 3.067 1.352 5.293 2.25 6.68.88 1.387 1.49 2.324 3.64 2.813-2.169.527-2.79 1.484-3.669 2.87-.879 1.387-2.22 3.604-2.22 6.65v6.363c0 2.754-.42 3.654-1.26 4.514-.84.859-2.96 1.289-5.391 1.289z" style="-inkscape-font-specification:Fixedsys" font-size="60" fill="url(#k)" stroke="url(#l)" font-family="Fixedsys" transform="matrix(-.22157 0 0 .22103 14.138 -218.338)" font-weight="400" letter-spacing="0" word-spacing="0" stroke-width="1.807" stroke-linejoin="round"/>
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#285a8c"/>
+      <stop offset="1" stop-color="#508cc8"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#1c3b5c"/>
+      <stop offset="1" stop-color="#285078"/>
+    </linearGradient>
+    <linearGradient x1="17.286" y1="1046.293" x2="-18.065" y2="1003.191" id="c" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
+    <linearGradient x1="12.826" y1="1050.761" x2="-24.272" y2="1006.022" id="d" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-1 0 0 1 28.42 0)"/>
+  </defs>
+  <path d="M21.734 1045.673v5.125h-1.816c-4.864 0-9.028-.723-10.688-2.168-1.64-1.445-2.46-4.326-2.46-8.643v-6.098c0-2.95-.528-4.99-1.583-6.123-1.054-1.133-2.968-1.7-5.742-1.7h-1.787v-4.189h1.787c2.793 0 4.707-.556 5.742-1.67 1.055-1.133 1.582-3.154 1.582-6.064v-6.127c0-4.317.82-7.188 2.461-8.613 1.66-1.446 5.824-2.168 10.688-2.168h1.816v5.093h-1.992c-2.754 0-4.55.43-5.39 1.29-.84.859-1.26 1.761-1.26 4.515v6.361c0 3.067-1.352 5.293-2.25 6.68-.88 1.387-1.49 2.324-3.64 2.813 2.169.527 2.79 1.484 3.669 2.87.879 1.387 2.22 3.604 2.22 6.65v6.363c0 2.754.42 3.654 1.26 4.514.84.859 2.96 1.289 5.391 1.289zm12.579 0v5.125h1.816c4.864 0 9.028-.723 10.688-2.168 1.64-1.445 2.46-4.326 2.46-8.643v-6.098c0-2.95.528-4.99 1.583-6.123 1.054-1.133 2.968-1.7 5.742-1.7h1.787v-4.189h-1.787c-2.793 0-4.707-.556-5.742-1.67-1.055-1.133-1.582-3.154-1.582-6.064v-6.127c0-4.317-.82-7.188-2.461-8.613-1.66-1.446-5.824-2.168-10.688-2.168h-1.816v5.093h1.992c2.754 0 4.55.43 5.39 1.29.84.859 1.26 1.761 1.26 4.515v6.361c0 3.067 1.352 5.293 2.25 6.68.88 1.387 1.49 2.324 3.64 2.813-2.169.527-2.79 1.484-3.669 2.87-.879 1.387-2.22 3.604-2.22 6.65v6.363c0 2.754-.42 3.654-1.26 4.514-.84.859-2.96 1.289-5.391 1.289z" style="-inkscape-font-specification:Fixedsys" font-size="60" fill="url(#c)" stroke="url(#d)" font-family="Fixedsys" transform="matrix(-.22157 0 0 .22103 14.138 -218.338)" font-weight="400" letter-spacing="0" word-spacing="0" stroke-width="1.807" stroke-linejoin="round"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-step-in.svg
+++ b/devtools/client/themes/images/firebug/debugger-step-in.svg
@@ -1,26 +1,26 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#dd8506"/>
-            <stop offset="1" stop-color="#f4a24b"/>
-        </linearGradient>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#e68507"/>
-            <stop offset="1" stop-color="#f4b65f"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#f3a952"/>
-            <stop offset="1" stop-color="#fadbba"/>
-        </linearGradient>
-        <linearGradient x1="9.06" y1="13.305" x2="9.06" y2="1.704" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
-        <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -1.01 -.02)"/>
-        <linearGradient x1="14.005" y1="14.902" x2="14.005" y2="13.07" id="g" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -.959 -.02)"/>
-        <linearGradient x1="10.576" y1="11.641" x2=".835" y2="1.901" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse"/>
-    </defs>
-    <path d="M.534 2.46h3.864C7.45 2.46 9.5 4.288 9.5 7.028v3.565h1.961L8.076 13.5l-3.382-2.914h1.899V7.74c0-1.554-.866-2.492-2.966-2.492H.534z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
-    <path fill="url(#f)" d="M1 13h4v2H1z"/>
-    <path fill="url(#g)" d="M11 13h4v2h-4z"/>
+  <defs>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#dd8506"/>
+      <stop offset="1" stop-color="#f4a24b"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#e68507"/>
+      <stop offset="1" stop-color="#f4b65f"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#f3a952"/>
+      <stop offset="1" stop-color="#fadbba"/>
+    </linearGradient>
+    <linearGradient x1="9.06" y1="13.305" x2="9.06" y2="1.704" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+    <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -1.01 -.02)"/>
+    <linearGradient x1="14.005" y1="14.902" x2="14.005" y2="13.07" id="g" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -.959 -.02)"/>
+    <linearGradient x1="10.576" y1="11.641" x2=".835" y2="1.901" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M.534 2.46h3.864C7.45 2.46 9.5 4.288 9.5 7.028v3.565h1.961L8.076 13.5l-3.382-2.914h1.899V7.74c0-1.554-.866-2.492-2.966-2.492H.534z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
+  <path fill="url(#f)" d="M1 13h4v2H1z"/>
+  <path fill="url(#g)" d="M11 13h4v2h-4z"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-step-out.svg
+++ b/devtools/client/themes/images/firebug/debugger-step-out.svg
@@ -1,26 +1,26 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#dd8506"/>
-            <stop offset="1" stop-color="#f4a24b"/>
-        </linearGradient>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#e68507"/>
-            <stop offset="1" stop-color="#f4b65f"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#f3a952"/>
-            <stop offset="1" stop-color="#fadbba"/>
-        </linearGradient>
-        <linearGradient x1="-.161" y1="7.678" x2="12.316" y2="7.678" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-90 7.756 5.205)"/>
-        <linearGradient x1="14.005" y1="14.902" x2="14.005" y2="13.07" id="g" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -.959 -.02)"/>
-        <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -1.01 -.02)"/>
-        <linearGradient x1="11.034" y1="9.145" x2="6.593" y2="4.703" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1.116)"/>
-    </defs>
-    <path d="M6.486 12.5V7.009c0-3.051 1.555-4.548 4.295-4.548h1.73V.5l2.907 3.385-2.914 3.382V5.368H11.7c-1.554 0-2.222.518-2.222 2.618V12.5z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
-    <path fill="url(#f)" d="M1 13h4v2H1z"/>
-    <path fill="url(#g)" d="M11 13h4v2h-4z"/>
+  <defs>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#dd8506"/>
+      <stop offset="1" stop-color="#f4a24b"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#e68507"/>
+      <stop offset="1" stop-color="#f4b65f"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#f3a952"/>
+      <stop offset="1" stop-color="#fadbba"/>
+    </linearGradient>
+    <linearGradient x1="-.161" y1="7.678" x2="12.316" y2="7.678" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="rotate(-90 7.756 5.205)"/>
+    <linearGradient x1="14.005" y1="14.902" x2="14.005" y2="13.07" id="g" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -.959 -.02)"/>
+    <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="matrix(.9974 0 0 1.0026 -1.01 -.02)"/>
+    <linearGradient x1="11.034" y1="9.145" x2="6.593" y2="4.703" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1.116)"/>
+  </defs>
+  <path d="M6.486 12.5V7.009c0-3.051 1.555-4.548 4.295-4.548h1.73V.5l2.907 3.385-2.914 3.382V5.368H11.7c-1.554 0-2.222.518-2.222 2.618V12.5z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
+  <path fill="url(#f)" d="M1 13h4v2H1z"/>
+  <path fill="url(#g)" d="M11 13h4v2h-4z"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-step-over.svg
+++ b/devtools/client/themes/images/firebug/debugger-step-over.svg
@@ -1,24 +1,24 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="c">
-            <stop offset="0" stop-color="#dd8506"/>
-            <stop offset="1" stop-color="#f4a24b"/>
-        </linearGradient>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#e68507"/>
-            <stop offset="1" stop-color="#f4b65f"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#f3a952"/>
-            <stop offset="1" stop-color="#fadbba"/>
-        </linearGradient>
-        <linearGradient x1="9.06" y1="13.305" x2="9.06" y2="1.704" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(3)"/>
-        <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(3)"/>
-        <linearGradient x1="12.911" y1="12.657" x2="2.554" y2="2.3" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse"/>
-    </defs>
-    <path d="M4.698 2.46h3.7c3.052 0 5.102 1.828 5.102 4.568v3.565h1.962L12.077 13.5l-3.383-2.914h1.9V7.74c0-1.793-.486-2.454-2.047-2.492-.791-.02-1.842 0-2.647 0-1.821 0-2.368.81-2.368 2.488V12.5H.522S.518 9.04.518 7.03c0-2.72 2.209-4.57 4.179-4.57z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
-    <path fill="url(#f)" d="M5.016 12.987h4.01v1.995h-4.01z"/>
+  <defs>
+    <linearGradient id="c">
+      <stop offset="0" stop-color="#dd8506"/>
+      <stop offset="1" stop-color="#f4a24b"/>
+    </linearGradient>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#e68507"/>
+      <stop offset="1" stop-color="#f4b65f"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#f3a952"/>
+      <stop offset="1" stop-color="#fadbba"/>
+    </linearGradient>
+    <linearGradient x1="9.06" y1="13.305" x2="9.06" y2="1.704" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(3)"/>
+    <linearGradient x1="3.865" y1="14.919" x2="3.865" y2="13.049" id="f" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(3)"/>
+    <linearGradient x1="12.911" y1="12.657" x2="2.554" y2="2.3" id="e" xlink:href="#c" gradientUnits="userSpaceOnUse"/>
+  </defs>
+  <path d="M4.698 2.46h3.7c3.052 0 5.102 1.828 5.102 4.568v3.565h1.962L12.077 13.5l-3.383-2.914h1.9V7.74c0-1.793-.486-2.454-2.047-2.492-.791-.02-1.842 0-2.647 0-1.821 0-2.368.81-2.368 2.488V12.5H.522S.518 9.04.518 7.03c0-2.72 2.209-4.57 4.179-4.57z" fill="url(#d)" stroke="url(#e)" stroke-linejoin="round"/>
+  <path fill="url(#f)" d="M5.016 12.987h4.01v1.995h-4.01z"/>
 </svg>
--- a/devtools/client/themes/images/firebug/debugger-toggleBreakpoints.svg
+++ b/devtools/client/themes/images/firebug/debugger-toggleBreakpoints.svg
@@ -1,13 +1,13 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16" width="16">
-    <defs>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#c80000"/>
-            <stop offset="1" stop-color="#780000"/>
-        </linearGradient>
-        <radialGradient gradientUnits="userSpaceOnUse" xlink:href="#a" id="b" fy="4.665" fx="4.8" r="5.59" cy="4.665" cx="4.8"/>
-    </defs>
-    <path transform="translate(1.599 1.565) scale(1.07342)" d="M11.553 5.995a5.59 5.59 0 1 1-11.179 0 5.59 5.59 0 1 1 11.18 0z" fill="url(#b)"/>
+  <defs>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#c80000"/>
+      <stop offset="1" stop-color="#780000"/>
+    </linearGradient>
+    <radialGradient gradientUnits="userSpaceOnUse" xlink:href="#a" id="b" fy="4.665" fx="4.8" r="5.59" cy="4.665" cx="4.8"/>
+  </defs>
+  <path transform="translate(1.599 1.565) scale(1.07342)" d="M11.553 5.995a5.59 5.59 0 1 1-11.179 0 5.59 5.59 0 1 1 11.18 0z" fill="url(#b)"/>
 </svg>
--- a/devtools/client/themes/images/firebug/disable.svg
+++ b/devtools/client/themes/images/firebug/disable.svg
@@ -1,6 +1,6 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="12" height="12">
-    <path d="M5.563 0A6 6 0 0 0 0 6a6 6 0 0 0 12 0 6 6 0 0 0-6.438-6zm.156 2a4 4 0 0 1 2.25.5L2.5 7.97A4 4 0 0 1 2 6a4 4 0 0 1 3.72-4zm3.685 1.906A4 4 0 0 1 10 6a4 4 0 0 1-6.094 3.406l5.5-5.5z" fill="red"/>
+  <path d="M5.563 0A6 6 0 0 0 0 6a6 6 0 0 0 12 0 6 6 0 0 0-6.438-6zm.156 2a4 4 0 0 1 2.25.5L2.5 7.97A4 4 0 0 1 2 6a4 4 0 0 1 3.72-4zm3.685 1.906A4 4 0 0 1 10 6a4 4 0 0 1-6.094 3.406l5.5-5.5z" fill="red"/>
 </svg>
--- a/devtools/client/themes/images/firebug/dock-bottom.svg
+++ b/devtools/client/themes/images/firebug/dock-bottom.svg
@@ -1,25 +1,25 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#f2451d"/>
-            <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
-            <stop offset=".897" stop-color="#de8493"/>
-            <stop offset="1" stop-color="#efc3cc"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#520e0d"/>
-            <stop offset="1" stop-color="#c4181d"/>
-        </linearGradient>
-        <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <filter height="1.48" y="-.24" width="1.48" x="-.24" id="e" color-interpolation-filters="sRGB">
-            <feGaussianBlur stdDeviation=".8"/>
-        </filter>
-    </defs>
-    <rect y="1.5" x="1.5" ry="2" rx="2" height="13" width="13" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
-    <path style="marker:none" d="M4.5 5v8h8V5zm1 1h6v4h-6z" color="#000" overflow="visible" opacity=".4" filter="url(#e)"/>
-    <path style="marker:none" d="M4 4v8h8V4zm1 1h6v4H5z" color="#000" overflow="visible" fill="#fff"/>
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#f2451d"/>
+      <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
+      <stop offset=".897" stop-color="#de8493"/>
+      <stop offset="1" stop-color="#efc3cc"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#520e0d"/>
+      <stop offset="1" stop-color="#c4181d"/>
+    </linearGradient>
+    <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <filter height="1.48" y="-.24" width="1.48" x="-.24" id="e" color-interpolation-filters="sRGB">
+      <feGaussianBlur stdDeviation=".8"/>
+    </filter>
+  </defs>
+  <rect y="1.5" x="1.5" ry="2" rx="2" height="13" width="13" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
+  <path style="marker:none" d="M4.5 5v8h8V5zm1 1h6v4h-6z" color="#000" overflow="visible" opacity=".4" filter="url(#e)"/>
+  <path style="marker:none" d="M4 4v8h8V4zm1 1h6v4H5z" color="#000" overflow="visible" fill="#fff"/>
 </svg>
--- a/devtools/client/themes/images/firebug/dock-side.svg
+++ b/devtools/client/themes/images/firebug/dock-side.svg
@@ -1,25 +1,25 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#f2451d"/>
-            <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
-            <stop offset=".897" stop-color="#de8493"/>
-            <stop offset="1" stop-color="#efc3cc"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#520e0d"/>
-            <stop offset="1" stop-color="#c4181d"/>
-        </linearGradient>
-        <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
-        <filter height="1.48" y="-.24" width="1.48" x="-.24" id="e" color-interpolation-filters="sRGB">
-            <feGaussianBlur stdDeviation=".8"/>
-        </filter>
-    </defs>
-    <rect y="1.5" x="1.5" ry="2" rx="2" height="13" width="13" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
-    <path style="marker:none" d="M4.5 5v8h8V5zm1 1h6v4h-6z" transform="rotate(-90 8.5 9)" color="#000" overflow="visible" opacity=".4" filter="url(#e)"/>
-    <path style="marker:none" d="M4 12h8V4H4zm1-1V5h4v6z" color="#000" overflow="visible" fill="#fff"/>
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#f2451d"/>
+      <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
+      <stop offset=".897" stop-color="#de8493"/>
+      <stop offset="1" stop-color="#efc3cc"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#520e0d"/>
+      <stop offset="1" stop-color="#c4181d"/>
+    </linearGradient>
+    <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse" gradientTransform="translate(0 -1036.362)"/>
+    <filter height="1.48" y="-.24" width="1.48" x="-.24" id="e" color-interpolation-filters="sRGB">
+      <feGaussianBlur stdDeviation=".8"/>
+    </filter>
+  </defs>
+  <rect y="1.5" x="1.5" ry="2" rx="2" height="13" width="13" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
+  <path style="marker:none" d="M4.5 5v8h8V5zm1 1h6v4h-6z" transform="rotate(-90 8.5 9)" color="#000" overflow="visible" opacity=".4" filter="url(#e)"/>
+  <path style="marker:none" d="M4 12h8V4H4zm1-1V5h4v6z" color="#000" overflow="visible" fill="#fff"/>
 </svg>
--- a/devtools/client/themes/images/firebug/dock-undock.svg
+++ b/devtools/client/themes/images/firebug/dock-undock.svg
@@ -1,27 +1,27 @@
 <!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16">
-    <defs>
-        <linearGradient id="b">
-            <stop offset="0" stop-color="#f2451d"/>
-            <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
-            <stop offset=".897" stop-color="#de8493"/>
-            <stop offset="1" stop-color="#efc3cc"/>
-        </linearGradient>
-        <linearGradient id="a">
-            <stop offset="0" stop-color="#520e0d"/>
-            <stop offset="1" stop-color="#c4181d"/>
-        </linearGradient>
-        <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
-        <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
-        <filter x="-.24" y="-.24" width="1.48" height="1.48" color-interpolation-filters="sRGB" id="e">
-            <feGaussianBlur stdDeviation=".8"/>
-        </filter>
-    </defs>
-    <g transform="translate(0 -1036.362)">
-        <rect width="13" height="13" rx="2" ry="2" x="1.5" y="1037.862" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
-        <path d="M6.5 1041.362v2h-2v6h6v-2h2v-6h-6zm1 1h4v4h-1v-3h-3v-1zm-2 2h4v4h-4v-4z" opacity=".4" filter="url(#e)"/>
-        <path d="M6 1040.362v2H4v6h6v-2h2v-6H6zm1 1h4v4h-1v-3H7v-1zm-2 2h4v4H5v-4z" fill="#fff"/>
-    </g>
+  <defs>
+    <linearGradient id="b">
+      <stop offset="0" stop-color="#f2451d"/>
+      <stop offset=".101" stop-color="#f01428" stop-opacity=".8"/>
+      <stop offset=".897" stop-color="#de8493"/>
+      <stop offset="1" stop-color="#efc3cc"/>
+    </linearGradient>
+    <linearGradient id="a">
+      <stop offset="0" stop-color="#520e0d"/>
+      <stop offset="1" stop-color="#c4181d"/>
+    </linearGradient>
+    <linearGradient x1="7.231" y1="1051.323" x2="7.231" y2="1037.401" id="d" xlink:href="#a" gradientUnits="userSpaceOnUse"/>
+    <linearGradient x1="8.769" y1="1049.931" x2="8.769" y2="1038.668" id="c" xlink:href="#b" gradientUnits="userSpaceOnUse"/>
+    <filter x="-.24" y="-.24" width="1.48" height="1.48" color-interpolation-filters="sRGB" id="e">
+      <feGaussianBlur stdDeviation=".8"/>
+    </filter>
+  </defs>
+  <g transform="translate(0 -1036.362)">
+    <rect width="13" height="13" rx="2" ry="2" x="1.5" y="1037.862" fill="url(#c)" stroke="url(#d)" stroke-linejoin="round"/>
+    <path d="M6.5 1041.362v2h-2v6h6v-2h2v-6h-6zm1 1h4v4h-1v-3h-3v-1zm-2 2h4v4h-4v-4z" opacity=".4" filter="url(#e)"/>
+    <path d="M6 1040.362v2H4v6h6v-2h2v-6H6zm1 1h4v4h-1v-3H7v-1zm-2 2h4v4H5v-4z" fill="#fff"/>
+  </g>
 </svg>
--- a/devtools/client/themes/images/firebug/filter.