Merge fx-team to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Sun, 07 Aug 2016 00:16:15 -0700
changeset 308505 d42aacfe34af25e2f5110e2ca3d24a210eabeb33
parent 308482 70948f120f5f8fe002ffe36ff6e962b128a67b23 (current diff)
parent 308504 fa0934fbb4d5e3a189aba0b82b4f3ebb9cb5ee96 (diff)
child 308506 e486ecf87731d4d5c1924660a95796a12c5b1eab
child 308507 cd11a204ee54928cafec147b8d11e180ded9c585
child 308510 019bbc497f9ed565fda0de3479d2b6af015f5f83
push id80347
push userkwierso@gmail.com
push dateSun, 07 Aug 2016 07:17:22 +0000
treeherdermozilla-inbound@d42aacfe34af [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone51.0a1
first release with
nightly linux32
d42aacfe34af / 51.0a1 / 20160807030201 / files
nightly linux64
d42aacfe34af / 51.0a1 / 20160807030201 / files
nightly mac
d42aacfe34af / 51.0a1 / 20160807030201 / files
nightly win32
d42aacfe34af / 51.0a1 / 20160807030201 / files
nightly win64
d42aacfe34af / 51.0a1 / 20160807030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge fx-team to m-c a=merge
.eslintignore
devtools/.eslintrc
devtools/client/performance/components/test/head.js
devtools/client/performance/legacy/front.js
devtools/client/performance/modules/logic/telemetry.js
devtools/client/performance/test/head.js
devtools/client/scratchpad/scratchpad.js
devtools/client/webconsole/test/head.js
devtools/server/actors/inspector.js
devtools/shared/builtin-modules.js
devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
modules/libpref/init/all.js
toolkit/themes/windows/global/printpreview/arrow-left-XP.png
toolkit/themes/windows/global/printpreview/arrow-left-end-XP.png
toolkit/themes/windows/global/printpreview/arrow-left-end.png
toolkit/themes/windows/global/printpreview/arrow-left.png
toolkit/themes/windows/global/printpreview/arrow-right-XP.png
toolkit/themes/windows/global/printpreview/arrow-right-end-XP.png
toolkit/themes/windows/global/printpreview/arrow-right-end.png
toolkit/themes/windows/global/printpreview/arrow-right.png
--- a/.eslintignore
+++ b/.eslintignore
@@ -105,16 +105,17 @@ devtools/client/webconsole/**
 !devtools/client/webconsole/console-commands.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
 devtools/server/*.js
 devtools/server/*.jsm
 !devtools/server/child.js
 !devtools/server/css-logic.js
 !devtools/server/main.js
+!devtools/server/websocket-server.js
 devtools/server/actors/**
 !devtools/server/actors/inspector.js
 !devtools/server/actors/highlighters/eye-dropper.js
 !devtools/server/actors/webbrowser.js
 !devtools/server/actors/webextension.js
 !devtools/server/actors/styles.js
 !devtools/server/actors/string.js
 !devtools/server/actors/csscoverage.js
@@ -142,16 +143,17 @@ devtools/shared/performance/**
 devtools/shared/qrcode/**
 devtools/shared/security/**
 devtools/shared/shims/**
 devtools/shared/tests/**
 !devtools/shared/tests/unit/test_csslexer.js
 devtools/shared/touch/**
 devtools/shared/transport/**
 !devtools/shared/transport/transport.js
+!devtools/shared/transport/websocket-transport.js
 devtools/shared/webconsole/test/**
 devtools/shared/worker/**
 !devtools/shared/worker/worker.js
 
 # Ignore devtools pre-processed files
 devtools/client/framework/toolbox-process-window.js
 devtools/client/performance/system.js
 devtools/client/webide/webide-prefs.js
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1286,16 +1286,17 @@ file, You can obtain one at http://mozil
       </xul:hbox>
       <xul:richlistbox anonid="richlistbox" class="autocomplete-richlistbox"
                        flex="1"/>
       <xul:hbox anonid="footer">
         <children/>
         <xul:vbox anonid="one-off-search-buttons"
                   class="search-one-offs"
                   compact="true"
+                  includecurrentengine="true"
                   flex="1"/>
       </xul:hbox>
     </content>
 
     <implementation>
       <field name="_maxResults">0</field>
 
       <field name="_bundle" readonly="true">
--- a/browser/components/downloads/content/downloadsOverlay.xul
+++ b/browser/components/downloads/content/downloadsOverlay.xul
@@ -122,17 +122,18 @@
                        onmouseout="DownloadsView.onDownloadMouseOut(event);"
                        oncontextmenu="DownloadsView.onDownloadContextMenu(event);"
                        ondragstart="DownloadsView.onDownloadDragStart(event);"/>
           <description id="emptyDownloads"
                        mousethrough="always">
              &downloadsPanelEmpty.label;
           </description>
           <spacer flex="1"/>
-          <vbox id="downloadsFooter">
+          <vbox id="downloadsFooter"
+                class="downloadsPanelFooter">
             <hbox id="downloadsSummary"
                   align="center"
                   orient="horizontal"
                   onkeydown="DownloadsSummary.onKeyDown(event);"
                   onclick="DownloadsSummary.onClick(event);">
               <image class="downloadTypeIcon" />
               <vbox id="downloadsSummaryChildBox">
                 <description id="downloadsSummaryDescription"
@@ -143,38 +144,40 @@
                                max="100"
                                mode="normal" />
                 <description id="downloadsSummaryDetails"
                              style="width: &downloadDetails.width;"
                              crop="end"/>
               </vbox>
             </hbox>
             <button id="downloadsHistory"
-                    class="plain"
+                    class="plain downloadsPanelFooterButton"
                     label="&downloadsHistory.label;"
                     accesskey="&downloadsHistory.accesskey;"
                     oncommand="DownloadsPanel.showDownloadsHistory();"/>
           </vbox>
         </panelview>
 
         <panelview id="downloadsPanel-blockedSubview"
                    orient="vertical"
                    flex="1">
           <description id="downloadsPanel-blockedSubview-title"/>
           <description id="downloadsPanel-blockedSubview-details1"/>
           <description id="downloadsPanel-blockedSubview-details2"/>
           <spacer flex="1"/>
           <hbox id="downloadsPanel-blockedSubview-buttons"
+                class="downloadsPanelFooter"
                 align="stretch">
             <button id="downloadsPanel-blockedSubview-openButton"
-                    class="plain"
+                    class="plain downloadsPanelFooterButton"
                     command="downloadsCmd_chooseOpen"
                     flex="1"/>
+            <toolbarseparator/>
             <button id="downloadsPanel-blockedSubview-deleteButton"
-                    class="plain"
+                    class="plain downloadsPanelFooterButton"
                     oncommand="DownloadsBlockedSubview.confirmBlock();"
                     default="true"
                     flex="1"/>
           </hbox>
         </panelview>
 
       </panelmultiview>
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -1406,19 +1406,21 @@
             settingsButton.nextSibling.remove();
 
           let Preferences =
             Cu.import("resource://gre/modules/Preferences.jsm", {}).Preferences;
           let pref = Preferences.get("browser.search.hiddenOneOffs");
           let hiddenList = pref ? pref.split(",") : [];
 
           let currentEngineName = Services.search.currentEngine.name;
-          let engines = Services.search.getVisibleEngines()
-                                .filter(e => e.name != currentEngineName &&
-                                             hiddenList.indexOf(e.name) == -1);
+          let includeCurrentEngine = this.getAttribute("includecurrentengine");
+          let engines = Services.search.getVisibleEngines().filter(e => {
+            return (includeCurrentEngine || e.name != currentEngineName) &&
+                   !hiddenList.includes(e.name);
+          });
 
           let header = document.getAnonymousElementByAttribute(this, "anonid",
                                                                "search-panel-one-offs-header")
           // header is a xul:deck so collapsed doesn't work on it, see bug 589569.
           header.hidden = list.collapsed = !engines.length;
 
           if (!engines.length)
             return;
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1753,20 +1753,20 @@ toolbarbutton.chevron > .toolbarbutton-i
   margin-top: .5em;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
-  background: linear-gradient(#fff, #ddd);
-  border: 1px none #ccc;
+  background: -moz-dialog;
+  border: 1px none ThreeDShadow;
   border-top-style: solid;
-  color: #333;
+  color: -moz-dialogText;
   text-shadow: none;
 }
 
 .statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
 .statuspanel-label:-moz-locale-dir(rtl)[mirror] {
   border-right-style: solid;
   border-top-right-radius: .3em;
   margin-right: 1em;
--- a/browser/themes/linux/downloads/downloads.css
+++ b/browser/themes/linux/downloads/downloads.css
@@ -1,33 +1,19 @@
 /* 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/. */
 
 %include ../../shared/downloads/downloads.inc.css
 
 /*** Panel and outer controls ***/
 
-#downloadsFooter,
-#downloadsPanel-blockedSubview-buttons {
-  border-top: 1px solid ThreeDShadow;
-  background-image: linear-gradient(hsla(0,0%,0%,.15), hsla(0,0%,0%,.08) 6px);
-}
-
-#downloadsHistory,
-#downloadsPanel-blockedSubview-openButton {
-  color: -moz-nativehyperlinktext;
-}
-
 @keyfocus@ #downloadsSummary:focus,
-@keyfocus@ #downloadsHistory:focus > .button-box {
+@keyfocus@ .downloadsPanelFooterButton:focus {
   outline: 1px -moz-dialogtext dotted;
-}
-
-@keyfocus@ #downloadsSummary:focus {
   outline-offset: -5px;
 }
 
 /*** List items and similar elements in the summary ***/
 
 :root {
   --downloads-item-height: 6em;
   --downloads-item-border-top-color: hsla(0,0%,100%,.2);
--- a/browser/themes/linux/places/organizer.css
+++ b/browser/themes/linux/places/organizer.css
@@ -1,16 +1,28 @@
 /* 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/. */
 
 
 /* Toolbar */
 #placesToolbar {
-  border: none;
+  -moz-appearance: menubar;
+}
+
+#placesToolbar:-moz-system-metric(menubar-drag) {
+  -moz-binding: url("chrome://global/content/bindings/toolbar.xml#toolbar-drag");
+}
+
+#placesToolbar > toolbarbutton {
+  color: -moz-menubartext;
+}
+
+#placesToolbar > toolbarbutton:hover {
+  color: ButtonText;
 }
 
 /* back button */
 
 #back-button {
   list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=toolbar");
 }
 #back-button[disabled="true"] {
@@ -45,21 +57,22 @@
   -moz-appearance: none;
   border: none;
 }
 
 #placesMenu > menu {
   padding-inline-start: 4px;
   -moz-binding: url("chrome://browser/skin/places/organizer.xml#toolbarbutton-dropdown");
   -moz-appearance: toolbarbutton;
-  color: -moz-DialogText;
 }
 
-#placesMenu > menu:hover:not(:active):not([open="true"]) {
-  color: -moz-buttonhovertext;
+#placesMenu > menu:active,
+#placesMenu > menu:hover,
+#placesMenu > menu[open] {
+  color: ButtonText;
 }
 
 #placesMenu > menu > .menubar-right {
   -moz-appearance: toolbarbutton-dropdown;
   width: 12px;
   height: 12px;
 }
 
--- a/browser/themes/osx/downloads/downloads.css
+++ b/browser/themes/osx/downloads/downloads.css
@@ -5,55 +5,22 @@
 %include ../../shared/downloads/downloads.inc.css
 
 /*** Panel and outer controls ***/
 
 #downloadsPanel {
   margin-top: -1px;
 }
 
-#downloadsFooter,
-#downloadsPanel-blockedSubview-buttons {
-  background: #e5e5e5;
-  border-top: 1px solid hsla(0,0%,0%,.1);
-  box-shadow: 0 -1px hsla(0,0%,100%,.5) inset, 0 1px 1px hsla(0,0%,0%,.03) inset;
-}
-
-#downloadsFooter,
-#downloadsPanel-multiView .panel-subviews {
-  border-bottom-left-radius: 4px;
-  border-bottom-right-radius: 4px;
-}
-
-#downloadsHistory,
-#downloadsPanel-blockedSubview-buttons > button {
-  color: #808080;
-}
-
 @keyfocus@ #downloadsSummary:focus,
-@keyfocus@ #downloadsHistory:focus,
-@keyfocus@ #downloadsPanel-blockedSubview-buttons > button:focus {
+@keyfocus@ .downloadsPanelFooterButton:focus {
   outline: 2px -moz-mac-focusring solid;
   outline-offset: -2px;
 }
 
-@keyfocus@ #downloadsSummary:focus,
-@keyfocus@ #downloadsHistory:focus {
-  -moz-outline-radius-bottomleft: 4px;
-  -moz-outline-radius-bottomright: 4px;
-}
-
-@keyfocus@ #downloadsPanel-blockedSubview-deleteButton:-moz-locale-dir(ltr):focus {
-  -moz-outline-radius-bottomright: 4px;
-}
-
-@keyfocus@ #downloadsPanel-blockedSubview-deleteButton:-moz-locale-dir(rtl):focus {
-  -moz-outline-radius-bottomleft: 4px;
-}
-
 /*** List items and similar elements in the summary ***/
 
 :root {
   --downloads-item-height: 7em;
   --downloads-item-border-top-color: hsla(0,0%,100%,.07);
   --downloads-item-border-bottom-color: hsla(0,0%,0%,.2);
   --downloads-item-font-size-factor: 0.95;
   --downloads-item-target-margin-bottom: 6px;
--- a/browser/themes/shared/downloads/downloads.inc.css
+++ b/browser/themes/shared/downloads/downloads.inc.css
@@ -8,16 +8,21 @@
 %define notKeyfocus #downloadsPanel:not([keyfocus])
 %define item richlistitem[type="download"]
 %define itemFinished @item@[state="1"]
 %define itemNotFinished @item@:not([state="1"])
 %define itemFocused #downloadsListBox:focus > @item@[selected]
 
 /*** Panel and outer controls ***/
 
+#downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent {
+  overflow: hidden;
+  display: block;
+}
+
 #downloadsPanel > .panel-arrowcontainer > .panel-arrowcontent,
 #downloadsPanel-multiView > .panel-viewcontainer > .panel-viewstack > .panel-subviews {
   padding: 0;
 }
 
 #downloadsListBox {
   background: transparent;
   padding: 4px;
@@ -26,16 +31,62 @@
 
 #emptyDownloads {
   padding: 10px 20px;
   /* The panel can be wider than this description after the blocked subview is
      shown, so center the text. */
   text-align: center;
 }
 
+.downloadsPanelFooter {
+  background-color: hsla(210,4%,10%,.07);
+  border-top: 1px solid hsla(210,4%,10%,.14);
+}
+
+.downloadsPanelFooter > toolbarseparator {
+  margin: 0;
+  border: 0;
+  min-width: 0;
+  border-left: 1px solid hsla(210,4%,10%,.14);
+  -moz-appearance: none !important;
+}
+
+.downloadsPanelFooterButton {
+  -moz-appearance: none;
+  background-color: transparent;
+  color: black;
+  margin: 0;
+  padding: 0;
+  min-height: 40px;
+}
+
+.downloadsPanelFooterButton:hover {
+  outline: 1px solid hsla(210,4%,10%,.07);
+  background-color: hsla(210,4%,10%,.07);
+}
+
+.downloadsPanelFooterButton:hover:active {
+  outline: 1px solid hsla(210,4%,10%,.12);
+  background-color: hsla(210,4%,10%,.12);
+  box-shadow: 0 1px 0 hsla(210,4%,10%,.05) inset;
+}
+
+.downloadsPanelFooterButton[default] {
+  background-color: #0996f8;
+  color: white;
+}
+
+.downloadsPanelFooterButton[default]:hover {
+  background-color: #0675d3;
+}
+
+.downloadsPanelFooterButton[default]:hover:active {
+  background-color: #0568ba;
+}
+
 #downloadsSummary {
   --summary-padding-end: 38px;
   --summary-padding-start: 12px;
   padding: 8px var(--summary-padding-end) 8px var(--summary-padding-start);
   cursor: pointer;
   -moz-user-focus: normal;
 }
 
@@ -60,25 +111,16 @@
   }
 }
 %endif
 
 #downloadsSummaryDescription {
   color: -moz-nativehyperlinktext;
 }
 
-#downloadsHistory {
-  background: transparent;
-  cursor: pointer;
-}
-
-#downloadsHistory > .button-box {
-  margin: 1em;
-}
-
 /*** List items and similar elements in the summary ***/
 
 #downloadsSummary,
 richlistitem[type="download"] {
   height: var(--downloads-item-height);
   padding-inline-end: 0;
   color: inherit;
 }
@@ -226,25 +268,8 @@ richlistitem[type="download"]:last-child
 }
 
 #downloadsPanel-blockedSubview-title,
 #downloadsPanel-blockedSubview-details1,
 #downloadsPanel-blockedSubview-details2 {
   -moz-margin-start: 64px;
   -moz-margin-end: 16px;
 }
-
-#downloadsPanel-blockedSubview-buttons {
-  height: 41px;
-  cursor: pointer;
-}
-
-#downloadsPanel-blockedSubview-buttons > button {
-  -moz-appearance: none;
-  padding: 0;
-  margin: 0;
-  background-color: transparent;
-}
-
-#downloadsPanel-blockedSubview-buttons > button[default] {
-  background-color: Highlight;
-  color: HighlightText;
-}
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2403,23 +2403,31 @@ notification[value="translation"] {
   margin-top: .5em;
 }
 
 /* Status panel */
 
 .statuspanel-label {
   margin: 0;
   padding: 2px 4px;
-  background: linear-gradient(#fff, #ddd);
-  border: 1px none #ccc;
+  background: -moz-dialog;
+  border: 1px none ThreeDShadow;
   border-top-style: solid;
-  color: #333;
+  color: -moz-dialogText;
   text-shadow: none;
 }
 
+@media (-moz-windows-default-theme) {
+  .statuspanel-label {
+    background: linear-gradient(#fff, #ddd);
+    border-color: #ccc;
+    color: #333;
+  }
+}
+
 .statuspanel-label:-moz-locale-dir(ltr):not([mirror]),
 .statuspanel-label:-moz-locale-dir(rtl)[mirror] {
   border-right-style: solid;
   /* disabled for triggering grayscale AA (bug 659213)
   border-top-right-radius: .3em;
   */
   margin-right: 1em;
 }
--- a/browser/themes/windows/downloads/downloads.css
+++ b/browser/themes/windows/downloads/downloads.css
@@ -1,86 +1,66 @@
 /* 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/. */
 
 %include ../../shared/downloads/downloads.inc.css
 
 /*** Panel and outer controls ***/
 
-#downloadsFooter,
-#downloadsPanel-blockedSubview-openButton {
+#downloadsSummary {
   background-color: hsla(210,4%,10%,.04);
   box-shadow: 0 1px 0 hsla(210,4%,10%,.08) inset;
   transition-duration: 150ms;
   transition-property: background-color;
 }
 
-#downloadsFooter:hover,
-#downloadsPanel-blockedSubview-openButton:hover {
+#downloadsSummary:hover {
   background-color: hsla(210,4%,10%,.05);
 }
 
-#downloadsFooter:hover:active,
-#downloadsPanel-blockedSubview-openButton:hover:active {
+#downloadsSummary:hover:active {
   background-color: hsla(210,4%,10%,.1);
   box-shadow: 0 2px 0 0 hsla(210,4%,10%,.1) inset;
 }
 
 @media (-moz-os-version: windows-xp),
        (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
-  #downloadsHistory,
-  #downloadsPanel-blockedSubview-buttons > button {
-    color: -moz-nativehyperlinktext;
-  }
-
   @media (-moz-windows-default-theme) {
-    #downloadsFooter,
-    #downloadsPanel-multiView .panel-subviews {
-      border-bottom-left-radius: 3px;
-      border-bottom-right-radius: 3px;
-      transition-duration: 0s;
-    }
-
-    #downloadsFooter,
-    #downloadsFooter:hover,
-    #downloadsFooter:hover:active,
-    #downloadsPanel-blockedSubview-openButton,
-    #downloadsPanel-blockedSubview-openButton:hover,
-    #downloadsPanel-blockedSubview-openButton:hover:active {
+    #downloadsSummary,
+    #downloadsSummary:hover,
+    #downloadsSummary:hover:active {
       background-color: #f1f5fb;
       box-shadow: 0px 1px 2px rgb(204,214,234) inset;
+      transition-duration: 0s;
+      cursor: pointer;
     }
 
     @media (-moz-os-version: windows-xp) {
-      #downloadsFooter,
-      #downloadsFooter:hover,
-      #downloadsFooter:hover:active,
-      #downloadsPanel-blockedSubview-openButton,
-      #downloadsPanel-blockedSubview-openButton:hover,
-      #downloadsPanel-blockedSubview-openButton:hover:active {
+      #downloadsSummary,
+      #downloadsSummary:hover,
+      #downloadsSummary:hover:active {
         background-color: hsla(216,45%,88%,.98);
       }
     }
   }
 }
 
 @keyfocus@ #downloadsSummary:focus,
-@keyfocus@ #downloadsHistory:focus {
+@keyfocus@ .downloadsPanelFooterButton:focus {
   outline: 1px -moz-dialogtext dotted;
   outline-offset: -1px;
 }
 
 @keyfocus@ #downloadsSummary:focus {
   outline-offset: -5px;
 }
 
-#downloadsHistory > .button-box,
-#downloadsPanel-blockedSubview-buttons > button > .button-box {
+.downloadsPanelFooterButton > .button-box {
   /* Hide the border so we don't display an inner focus ring. */
   border: none;
 }
 
 /*** List items and similar elements in the summary ***/
 
 :root {
   --downloads-item-height: 7em;
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -18,16 +18,17 @@
     "loader": true,
     "module": true,
     "reportError": true,
     "require": true,
     "setInterval": true,
     "setTimeout": true,
     "uneval": true,
     "URL": true,
+    "WebSocket": true,
     "XMLHttpRequest": true,
     "_Iterator": true,
   },
   "rules": {
     // These are the rules that have been configured so far to match the
     // devtools coding style.
 
     // Rules from the mozilla plugin
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -14,20 +14,19 @@
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 const { AddonManager } = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 const { Management } = Cu.import("resource://gre/modules/Extension.jsm", {});
 
-DevToolsUtils.testing = true;
-
+flags.testing = true;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 function* openAboutDebugging(page, win) {
   info("opening about:debugging");
   let url = "about:debugging";
   if (page) {
     url += "#" + page;
   }
--- a/devtools/client/canvasdebugger/canvasdebugger.js
+++ b/devtools/client/canvasdebugger/canvasdebugger.js
@@ -9,21 +9,22 @@ const { require } = Cu.import("resource:
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 const { SideMenuWidget } = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 const promise = require("promise");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
 const { CanvasFront } = require("devtools/shared/fronts/canvas");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const { LocalizationHelper } = require("devtools/client/shared/l10n");
 const { Heritage, WidgetMethods, setNamedTimeout, clearNamedTimeout,
         setConditionalTimeout } = require("devtools/client/shared/widgets/view-helpers");
 
-const CANVAS_ACTOR_RECORDING_ATTEMPT = DevToolsUtils.testing ? 500 : 5000;
+const CANVAS_ACTOR_RECORDING_ATTEMPT = flags.testing ? 500 : 5000;
 
 const { Task } = require("devtools/shared/task");
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
   "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
   "resource://gre/modules/FileUtils.jsm");
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -11,16 +11,17 @@ var Services = require("Services");
 var promise = require("promise");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var { DebuggerServer } = require("devtools/server/main");
 var { CallWatcherFront } = require("devtools/shared/fronts/call-watcher");
 var { CanvasFront } = require("devtools/shared/fronts/canvas");
 var { setTimeout } = require("sdk/timers");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
 var mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/canvasdebugger/test/";
 const SET_TIMEOUT_URL = EXAMPLE_URL + "doc_settimeout.html";
@@ -41,21 +42,21 @@ const RAF_BEGIN_URL = EXAMPLE_URL + "doc
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 var gToolEnabled = Services.prefs.getBoolPref("devtools.canvasdebugger.enabled");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.canvasdebugger.enabled", gToolEnabled);
 
   // Some of yhese tests use a lot of memory due to GL contexts, so force a GC
   // to help fragmentation.
   info("Forcing GC after canvas debugger test.");
   Cu.forceGC();
 });
--- a/devtools/client/commandline/test/head.js
+++ b/devtools/client/commandline/test/head.js
@@ -2,26 +2,26 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const TEST_BASE_HTTP = "http://example.com/browser/devtools/client/commandline/test/";
 const TEST_BASE_HTTPS = "https://example.com/browser/devtools/client/commandline/test/";
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { console } = require("resource://gre/modules/Console.jsm");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 
 // Import the GCLI test helper
 var testDir = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 Services.scriptloader.loadSubScript(testDir + "/helpers.js", this);
 Services.scriptloader.loadSubScript(testDir + "/mockCommands.js", this);
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 SimpleTest.registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 function whenDelayedStartupFinished(aWindow, aCallback) {
   Services.obs.addObserver(function observer(aSubject, aTopic) {
     if (aWindow == aSubject) {
       Services.obs.removeObserver(observer, aTopic);
       executeSoon(aCallback);
     }
--- a/devtools/client/devtools-startup.js
+++ b/devtools/client/devtools-startup.js
@@ -125,24 +125,52 @@ DevToolsStartup.prototype = {
     const { BrowserToolboxProcess } = Cu.import("resource://devtools/client/framework/ToolboxProcess.jsm", {});
     BrowserToolboxProcess.init();
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
+  /**
+   * Handle the --start-debugger-server command line flag. The options are:
+   * --start-debugger-server
+   *   The portOrPath parameter is boolean true in this case. Reads and uses the defaults
+   *   from devtools.debugger.remote-port and devtools.debugger.remote-websocket prefs.
+   *   The default values of these prefs are port 6000, WebSocket disabled.
+   *
+   * --start-debugger-server 6789
+   *   Start the non-WebSocket server on port 6789.
+   *
+   * --start-debugger-server /path/to/filename
+   *   Start the server on a Unix domain socket.
+   *
+   * --start-debugger-server ws:6789
+   *   Start the WebSocket server on port 6789.
+   *
+   * --start-debugger-server ws:
+   *   Start the WebSocket server on the default port (taken from d.d.remote-port)
+   */
   handleDebuggerServerFlag: function (cmdLine, portOrPath) {
     if (!this._isRemoteDebuggingEnabled()) {
       return;
     }
+
+    let webSocket = false;
+    let defaultPort = Services.prefs.getIntPref("devtools.debugger.remote-port");
     if (portOrPath === true) {
-      // Default to TCP port 6000 if no value given
-      portOrPath = 6000;
+      // Default to pref values if no values given on command line
+      webSocket = Services.prefs.getBoolPref("devtools.debugger.remote-websocket");
+      portOrPath = defaultPort;
+    } else if (portOrPath.startsWith("ws:")) {
+      webSocket = true;
+      let port = portOrPath.slice(3);
+      portOrPath = Number(port) ? port : defaultPort;
     }
+
     let { DevToolsLoader } =
       Cu.import("resource://devtools/shared/Loader.jsm", {});
 
     try {
       // Create a separate loader instance, so that we can be sure to receive
       // a separate instance of the DebuggingServer from the rest of the
       // devtools.  This allows us to safely use the tools against even the
       // actors and DebuggingServer itself, especially since we can mark
@@ -153,32 +181,34 @@ DevToolsStartup.prototype = {
       let { DebuggerServer: debuggerServer } =
         serverLoader.require("devtools/server/main");
       debuggerServer.init();
       debuggerServer.addBrowserActors();
       debuggerServer.allowChromeProcess = true;
 
       let listener = debuggerServer.createListener();
       listener.portOrPath = portOrPath;
+      listener.webSocket = webSocket;
       listener.open();
       dump("Started debugger server on " + portOrPath + "\n");
     } catch (e) {
       dump("Unable to start debugger server on " + portOrPath + ": " + e);
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
   },
 
   helpInfo: "  --jsconsole        Open the Browser Console.\n" +
             "  --jsdebugger       Open the Browser Toolbox.\n" +
             "  --devtools         Open DevTools on initial load.\n" +
-            "  --start-debugger-server [port|path] " +
+            "  --start-debugger-server [ws:][ <port> | <path> ] " +
             "Start the debugger server on a TCP port or " +
-            "Unix domain socket path.  Defaults to TCP port 6000.\n",
+            "Unix domain socket path.  Defaults to TCP port 6000. " +
+            "Use WebSocket protocol if ws: prefix is specified.\n",
 
   classID: Components.ID("{9e9a9283-0ce9-4e4a-8f1c-ba129a032c32}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICommandLineHandler]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory(
   [DevToolsStartup]);
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -136,18 +136,21 @@ BrowserToolboxProcess.prototype = {
 
     this.debuggerServer.init();
     this.debuggerServer.addBrowserActors();
     this.debuggerServer.allowChromeProcess = true;
     dumpn("initialized and added the browser actors for the DebuggerServer.");
 
     let chromeDebuggingPort =
       Services.prefs.getIntPref("devtools.debugger.chrome-debugging-port");
+    let chromeDebuggingWebSocket =
+      Services.prefs.getBoolPref("devtools.debugger.chrome-debugging-websocket");
     let listener = this.debuggerServer.createListener();
     listener.portOrPath = chromeDebuggingPort;
+    listener.webSocket = chromeDebuggingWebSocket;
     listener.open();
 
     dumpn("Finished initializing the chrome toolbox server.");
     dumpn("Started listening on port: " + chromeDebuggingPort);
   },
 
   /**
    * Initializes a profile for the remote debugger process.
--- a/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
+++ b/devtools/client/framework/test/browser_ignore_toolbox_network_requests.js
@@ -7,27 +7,27 @@
 
 // Test that network requests originating from the toolbox don't get recorded in
 // the network panel.
 
 add_task(function* () {
   // TODO: This test tries to verify the normal behavior of the netmonitor and
   // therefore needs to avoid the explicit check for tests. Bug 1167188 will
   // allow us to remove this workaround.
-  let isTesting = DevToolsUtils.testing;
-  DevToolsUtils.testing = false;
+  let isTesting = flags.testing;
+  flags.testing = false;
 
   let tab = yield addTab(URL_ROOT + "doc_viewsource.html");
   let target = TargetFactory.forTab(tab);
   let toolbox = yield gDevTools.showToolbox(target, "styleeditor");
   let panel = toolbox.getPanel("styleeditor");
 
   is(panel.UI.editors.length, 1, "correct number of editors opened");
 
   let monitor = yield toolbox.selectTool("netmonitor");
   let { RequestsMenu } = monitor.panelWin.NetMonitorView;
   is(RequestsMenu.itemCount, 0, "No network requests appear in the network panel");
 
   yield gDevTools.closeToolbox(target);
   tab = target = toolbox = panel = null;
   gBrowser.removeCurrentTab();
-  DevToolsUtils.testing = isTesting;
+  flags.testing = isTesting;
 });
--- a/devtools/client/framework/test/shared-head.js
+++ b/devtools/client/framework/test/shared-head.js
@@ -21,16 +21,17 @@ function scopedCuImport(path) {
 
 const {console} = scopedCuImport("resource://gre/modules/Console.jsm");
 const {ScratchpadManager} = scopedCuImport("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 const {loader, require} = scopedCuImport("resource://devtools/shared/Loader.jsm");
 
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {TargetFactory} = require("devtools/client/framework/target");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 let promise = require("promise");
 let defer = require("devtools/shared/defer");
 const Services = require("Services");
 const {Task} = require("devtools/shared/task");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 
 const TEST_DIR = gTestPath.substr(0, gTestPath.lastIndexOf("/"));
 const CHROME_URL_ROOT = TEST_DIR + "/";
@@ -84,19 +85,19 @@ function getFrameScript() {
   let frameURL = "chrome://devtools/content/shared/frame-script-utils.js";
   mm.loadFrameScript(frameURL, false);
   SimpleTest.registerCleanupFunction(() => {
     mm = null;
   });
   return mm;
 }
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.clearUserPref("devtools.dump.emit");
   Services.prefs.clearUserPref("devtools.toolbox.host");
   Services.prefs.clearUserPref("devtools.toolbox.previousHost");
 });
 
 registerCleanupFunction(function* cleanup() {
   while (gBrowser.tabs.length > 1) {
     yield closeTabAndToolbox(gBrowser.selectedTab);
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -3,17 +3,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/. */
 
 "use strict";
 
 const promise = require("promise");
 const {Task} = require("devtools/shared/task");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
 /**
  * Client-side highlighter shared module.
  * To be used by toolbox panels that need to highlight DOM elements.
  *
  * Highlighting and selecting elements is common enough that it needs to be at
  * toolbox level, accessible by any panel that needs it.
  * That's why the toolbox is the one that initializes the inspector and
@@ -245,25 +245,25 @@ exports.getHighlighterUtils = function (
   let gripToNodeFront = exported.gripToNodeFront = requireInspector(
   function* (grip) {
     return yield toolbox.walker.getNodeActorFromObjectActor(grip.actor);
   });
 
   /**
    * Hide the highlighter.
    * @param {Boolean} forceHide Only really matters in test mode (when
-   * DevToolsUtils.testing is true). In test mode, hovering over several nodes
+   * flags.testing is true). In test mode, hovering over several nodes
    * in the markup view doesn't hide/show the highlighter to ease testing. The
    * highlighter stays visible at all times, except when the mouse leaves the
    * markup view, which is when this param is passed to true
    * @return a promise that resolves when the highlighter is hidden
    */
   let unhighlight = exported.unhighlight = Task.async(
   function* (forceHide = false) {
-    forceHide = forceHide || !DevToolsUtils.testing;
+    forceHide = forceHide || !flags.testing;
 
     // Note that if isRemoteHighlightable is true, there's no need to hide the
     // highlighter as the walker uses setTimeout to hide it after some time
     if (isNodeFrontHighlighted && forceHide && toolbox.highlighter && isRemoteHighlightable()) {
       isNodeFrontHighlighted = false;
       yield toolbox.highlighter.hideBoxModel();
     }
 
--- a/devtools/client/framework/toolbox-process-window.js
+++ b/devtools/client/framework/toolbox-process-window.js
@@ -20,47 +20,49 @@ var { DebuggerClient } = require("devtoo
 var { PrefsHelper } = require("devtools/client/shared/prefs");
 var { Task } = require("devtools/shared/task");
 
 /**
  * Shortcuts for accessing various debugger preferences.
  */
 var Prefs = new PrefsHelper("devtools.debugger", {
   chromeDebuggingHost: ["Char", "chrome-debugging-host"],
-  chromeDebuggingPort: ["Int", "chrome-debugging-port"]
+  chromeDebuggingPort: ["Int", "chrome-debugging-port"],
+  chromeDebuggingWebSocket: ["Bool", "chrome-debugging-websocket"],
 });
 
 var gToolbox, gClient;
 
 var connect = Task.async(function*() {
   window.removeEventListener("load", connect);
   // Initiate the connection
   let transport = yield DebuggerClient.socketConnect({
     host: Prefs.chromeDebuggingHost,
-    port: Prefs.chromeDebuggingPort
+    port: Prefs.chromeDebuggingPort,
+    webSocket: Prefs.chromeDebuggingWebSocket,
   });
   gClient = new DebuggerClient(transport);
-  gClient.connect().then(() => {
-    let addonID = getParameterByName("addonID");
+  yield gClient.connect();
+  let addonID = getParameterByName("addonID");
 
-    if (addonID) {
-      gClient.listAddons(({addons}) => {
-        let addonActor = addons.filter(addon => addon.id === addonID).pop();
-        openToolbox({
-          form: addonActor,
-          chrome: true,
-          isTabActor: addonActor.isWebExtension ? true : false
-        });
-      });
-    } else {
-      gClient.getProcess().then(aResponse => {
-        openToolbox({ form: aResponse.form, chrome: true });
-      });
-    }
-  });
+  if (addonID) {
+    let { addons } = yield gClient.listAddons();
+    let addonActor = addons.filter(addon => addon.id === addonID).pop();
+    openToolbox({
+      form: addonActor,
+      chrome: true,
+      isTabActor: addonActor.isWebExtension ? true : false
+    });
+  } else {
+    let response = yield gClient.getProcess();
+    openToolbox({
+      form: response.form,
+      chrome: true
+    });
+  }
 });
 
 // Certain options should be toggled since we can assume chrome debugging here
 function setPrefDefaults() {
   Services.prefs.setBoolPref("devtools.inspector.showUserAgentStyles", true);
   Services.prefs.setBoolPref("devtools.performance.ui.show-platform-data", true);
   Services.prefs.setBoolPref("devtools.inspector.showAllAnonymousContent", true);
   Services.prefs.setBoolPref("browser.dom.window.dump.enabled", true);
@@ -72,17 +74,17 @@ function setPrefDefaults() {
 
 window.addEventListener("load", function() {
   let cmdClose = document.getElementById("toolbox-cmd-close");
   cmdClose.addEventListener("command", onCloseCommand);
   setPrefDefaults();
   connect().catch(e => {
     let errorMessageContainer = document.getElementById("error-message-container");
     let errorMessage = document.getElementById("error-message");
-    errorMessage.value = e;
+    errorMessage.value = e.message || e;
     errorMessageContainer.hidden = false;
     console.error(e);
   });
 });
 
 function onCloseCommand(event) {
   window.close();
 }
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -51,30 +51,32 @@ loader.lazyRequireGetter(this, "CommandU
 loader.lazyRequireGetter(this, "getHighlighterUtils",
   "devtools/client/framework/toolbox-highlighter-utils", true);
 loader.lazyRequireGetter(this, "Hosts",
   "devtools/client/framework/toolbox-hosts", true);
 loader.lazyRequireGetter(this, "Selection",
   "devtools/client/framework/selection", true);
 loader.lazyRequireGetter(this, "InspectorFront",
   "devtools/shared/fronts/inspector", true);
-loader.lazyRequireGetter(this, "DevToolsUtils",
-  "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "flags",
+  "devtools/shared/flags");
 loader.lazyRequireGetter(this, "showDoorhanger",
   "devtools/client/shared/doorhanger", true);
 loader.lazyRequireGetter(this, "createPerformanceFront",
   "devtools/shared/fronts/performance", true);
 loader.lazyRequireGetter(this, "system",
   "devtools/shared/system");
 loader.lazyRequireGetter(this, "getPreferenceFront",
   "devtools/shared/fronts/preference", true);
 loader.lazyRequireGetter(this, "KeyShortcuts",
   "devtools/client/shared/key-shortcuts", true);
 loader.lazyRequireGetter(this, "ZoomKeys",
   "devtools/client/shared/zoom-keys");
+loader.lazyRequireGetter(this, "settleAll",
+  "devtools/shared/ThreadSafeDevToolsUtils", "settleAll");
 
 loader.lazyGetter(this, "registerHarOverlay", () => {
   return require("devtools/client/netmonitor/har/toolbox-overlay").register;
 });
 
 // White-list buttons that can be toggled to prevent adding prefs for
 // addons that have manually inserted toolbarbuttons into DOM.
 // (By default, supported target is only local tab)
@@ -472,17 +474,17 @@ Toolbox.prototype = {
 
       // Lazily connect to the profiler here and don't wait for it to complete,
       // used to intercept console.profile calls before the performance tools are open.
       let performanceFrontConnection = this.initPerformance();
 
       // If in testing environment, wait for performance connection to finish,
       // so we don't have to explicitly wait for this in tests; ideally, all tests
       // will handle this on their own, but each have their own tear down function.
-      if (DevToolsUtils.testing) {
+      if (flags.testing) {
         yield performanceFrontConnection;
       }
 
       this.emit("ready");
     }.bind(this)).then(null, console.error.bind(console));
   },
 
   /**
@@ -1934,17 +1936,17 @@ Toolbox.prototype = {
           {showAllAnonymousContent: Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent")}
         );
         this._selection = new Selection(this._walker);
 
         if (this.highlighterUtils.isRemoteHighlightable()) {
           this.walker.on("highlighter-ready", this._highlighterReady);
           this.walker.on("highlighter-hide", this._highlighterHidden);
 
-          let autohide = !DevToolsUtils.testing;
+          let autohide = !flags.testing;
           this._highlighter = yield this._inspector.getHighlighter(autohide);
         }
       }.bind(this));
     }
     return this._initInspector;
   },
 
   /**
@@ -2113,49 +2115,49 @@ Toolbox.prototype = {
       CommandUtils.destroyRequisition(this._requisition, this.target);
     }
     this._telemetry.toolClosed("toolbox");
     this._telemetry.destroy();
 
     // Finish all outstanding tasks (which means finish destroying panels and
     // then destroying the host, successfully or not) before destroying the
     // target.
-    this._destroyer = DevToolsUtils.settleAll(outstanding)
-                                   .catch(console.error)
-                                   .then(() => this.destroyHost())
-                                   .catch(console.error)
-                                   .then(() => {
-      // Targets need to be notified that the toolbox is being torn down.
-      // This is done after other destruction tasks since it may tear down
-      // fronts and the debugger transport which earlier destroy methods may
-      // require to complete.
-                                     if (!this._target) {
-                                       return null;
-                                     }
-                                     let target = this._target;
-                                     this._target = null;
-                                     this.highlighterUtils.release();
-                                     target.off("close", this.destroy);
-                                     return target.destroy();
-                                   }, console.error).then(() => {
-                                     this.emit("destroyed");
+    this._destroyer = settleAll(outstanding)
+        .catch(console.error)
+        .then(() => this.destroyHost())
+        .catch(console.error)
+        .then(() => {
+          // Targets need to be notified that the toolbox is being torn down.
+          // This is done after other destruction tasks since it may tear down
+          // fronts and the debugger transport which earlier destroy methods may
+          // require to complete.
+          if (!this._target) {
+            return null;
+          }
+          let target = this._target;
+          this._target = null;
+          this.highlighterUtils.release();
+          target.off("close", this.destroy);
+          return target.destroy();
+        }, console.error).then(() => {
+          this.emit("destroyed");
 
-      // Free _host after the call to destroyed in order to let a chance
-      // to destroyed listeners to still query toolbox attributes
-                                     this._host = null;
-                                     this._toolPanels.clear();
+          // Free _host after the call to destroyed in order to let a chance
+          // to destroyed listeners to still query toolbox attributes
+          this._host = null;
+          this._toolPanels.clear();
 
-      // Force GC to prevent long GC pauses when running tests and to free up
-      // memory in general when the toolbox is closed.
-                                     if (DevToolsUtils.testing) {
-                                       win.QueryInterface(Ci.nsIInterfaceRequestor)
-           .getInterface(Ci.nsIDOMWindowUtils)
-           .garbageCollect();
-                                     }
-                                   }).then(null, console.error);
+          // Force GC to prevent long GC pauses when running tests and to free up
+          // memory in general when the toolbox is closed.
+          if (flags.testing) {
+            win.QueryInterface(Ci.nsIInterfaceRequestor)
+              .getInterface(Ci.nsIDOMWindowUtils)
+              .garbageCollect();
+          }
+        }).then(null, console.error);
 
     let leakCheckObserver = ({wrappedJSObject: barrier}) => {
       // Make the leak detector wait until this toolbox is properly destroyed.
       barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
                                 this._destroyer);
     };
 
     let topic = "shutdown-leaks-before-check";
--- a/devtools/client/inspector/breadcrumbs.js
+++ b/devtools/client/inspector/breadcrumbs.js
@@ -7,17 +7,16 @@
 "use strict";
 
 /* eslint-disable mozilla/reject-some-requires */
 const {Ci} = require("chrome");
 /* eslint-enable mozilla/reject-some-requires */
 const Services = require("Services");
 const promise = require("promise");
 const FocusManager = Services.focus;
-const {waitForTick} = require("devtools/shared/DevToolsUtils");
 
 const ELLIPSIS = Services.prefs.getComplexValue(
     "intl.ellipsis",
     Ci.nsIPrefLocalizedString).data;
 const MAX_LABEL_LENGTH = 40;
 
 const NS_XHTML = "http://www.w3.org/1999/xhtml";
 const SCROLL_REPEAT_MS = 100;
@@ -877,20 +876,22 @@ HTMLBreadcrumbs.prototype = {
       this.setCursor(idx);
     }
 
     let doneUpdating = this.inspector.updating("breadcrumbs");
 
     this.updateSelectors();
 
     // Make sure the selected node and its neighbours are visible.
-    waitForTick().then(() => {
-      this.scroll();
-      this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
-      doneUpdating();
-    }, e => {
-      // Only log this as an error if we haven't been destroyed in the meantime.
-      if (!this.isDestroyed) {
-        console.error(e);
+    setTimeout(() => {
+      try {
+        this.scroll();
+        this.inspector.emit("breadcrumbs-updated", this.selection.nodeFront);
+        doneUpdating();
+      } catch (e) {
+        // Only log this as an error if we haven't been destroyed in the meantime.
+        if (!this.isDestroyed) {
+          console.error(e);
+        }
       }
-    });
+    }, 0);
   }
 };
--- a/devtools/client/inspector/layout/test/browser.ini
+++ b/devtools/client/inspector/layout/test/browser.ini
@@ -18,13 +18,14 @@ support-files =
 # Disabled for too many intermittent failures (bug 1009322)
 [browser_layout_editablemodel_bluronclick.js]
 [browser_layout_editablemodel_border.js]
 [browser_layout_editablemodel_stylerules.js]
 [browser_layout_guides.js]
 [browser_layout_rotate-labels-on-sides.js]
 [browser_layout_sync.js]
 [browser_layout_tooltips.js]
-[browser_layout_update-after-navigation.js]
+# [browser_layout_update-after-navigation.js]
+# Disabled for too many intermittent failures (bug 1288213)
 # [browser_layout_update-after-reload.js]
 # Disabled for too many intermittent failures (bug 1287745)
 # [browser_layout_update-in-iframes.js]
 # Bug 1020038 layout-view updates for iframe elements changes
--- a/devtools/client/inspector/markup/test/head.js
+++ b/devtools/client/inspector/markup/test/head.js
@@ -14,19 +14,19 @@ var {getInplaceEditorForSpan: inplaceEdi
 var clipboard = require("sdk/clipboard");
 var {ActorRegistryFront} = require("devtools/shared/fronts/actor-registry");
 
 // If a test times out we want to see the complete log and not just the last few
 // lines.
 SimpleTest.requestCompleteLog();
 
 // Set the testing flag on DevToolsUtils and reset it when the test ends
-DevToolsUtils.testing = true;
+flags.testing = true;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 // Clear preferences that may be set during the course of tests.
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.inspector.htmlPanelOpen");
   Services.prefs.clearUserPref("devtools.inspector.sidebarOpen");
   Services.prefs.clearUserPref("devtools.markup.pagesize");
   Services.prefs.clearUserPref("dom.webcomponents.enabled");
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -16,16 +16,17 @@ support-files =
   doc_inspector_highlighter_dom.html
   doc_inspector_highlighter_inline.html
   doc_inspector_highlighter.html
   doc_inspector_highlighter_rect.html
   doc_inspector_highlighter_rect_iframe.html
   doc_inspector_highlighter_xbl.xul
   doc_inspector_infobar_01.html
   doc_inspector_infobar_02.html
+  doc_inspector_infobar_03.html
   doc_inspector_menu.html
   doc_inspector_outerhtml.html
   doc_inspector_remove-iframe-during-load.html
   doc_inspector_search.html
   doc_inspector_search-reserved.html
   doc_inspector_search-suggestions.html
   doc_inspector_search-svg.html
   doc_inspector_select-last-selected-01.html
@@ -92,16 +93,17 @@ subsuite = clipboard
 [browser_inspector_highlighter-rulers_02.js]
 [browser_inspector_highlighter-selector_01.js]
 [browser_inspector_highlighter-selector_02.js]
 [browser_inspector_highlighter-xbl.js]
 [browser_inspector_highlighter-zoom.js]
 [browser_inspector_iframe-navigation.js]
 [browser_inspector_infobar_01.js]
 [browser_inspector_infobar_02.js]
+[browser_inspector_infobar_03.js]
 [browser_inspector_initialization.js]
 skip-if = (e10s && debug) # Bug 1250058 - Docshell leak on debug e10s
 [browser_inspector_inspect-object-element.js]
 [browser_inspector_invalidate.js]
 [browser_inspector_keyboard-shortcuts-copy-outerhtml.js]
 subsuite = clipboard
 [browser_inspector_keyboard-shortcuts.js]
 [browser_inspector_menu-01-sensitivity.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_infobar_03.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/. */
+
+"use strict";
+
+// Bug 1102269 - Make sure info-bar never gets outside of visible area after scrolling
+
+const TEST_URI = URL_ROOT + "doc_inspector_infobar_03.html";
+
+add_task(function* () {
+  let {inspector, testActor} = yield openInspectorForURL(TEST_URI);
+
+  let testData = {
+    selector: "body",
+    position: "overlap",
+    style: "top:0px",
+  };
+
+  yield testPositionAndStyle(testData, inspector, testActor);
+});
+
+function* testPositionAndStyle(test, inspector, testActor) {
+  info("Testing " + test.selector);
+
+  yield selectAndHighlightNode(test.selector, inspector);
+
+  let style = yield testActor.getHighlighterNodeAttribute(
+    "box-model-nodeinfobar-container", "style");
+
+  is(style.split(";")[0], test.style,
+    "Infobar shows on top of the page when page isn't scrolled");
+
+  yield testActor.scrollWindow(0, 500);
+
+  style = yield testActor.getHighlighterNodeAttribute(
+    "box-model-nodeinfobar-container", "style");
+
+  is(style.split(";")[0], test.style,
+    "Infobar shows on top of the page even if the page is scrolled");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/doc_inspector_infobar_03.html
@@ -0,0 +1,14 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="utf-8">
+
+  <style>
+    body {
+      height: 300vh;
+    }
+   </style>
+ </head>
+ <body>
+ </body>
+ </html>
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -29,19 +29,19 @@ Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/shared/test/test-actor-registry.js",
   this);
 
 // Import helpers for the inspector that are also shared with others
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/shared-head.js",
   this);
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.inspector.activeSidebar");
 });
 
 registerCleanupFunction(function* () {
   let target = TargetFactory.forTab(gBrowser.selectedTab);
--- a/devtools/client/memory/store.js
+++ b/devtools/client/memory/store.js
@@ -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/. */
 
 const { combineReducers } = require("../shared/vendor/redux");
 const createStore = require("../shared/redux/create-store");
 const reducers = require("./reducers");
 const { viewState } = require("./constants");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
 module.exports = function () {
   let shouldLog = false;
   let history;
 
   // If testing, store the action history in an array
   // we'll later attach to the store
-  if (DevToolsUtils.testing) {
+  if (flags.testing) {
     history = [];
     // Uncomment this for TONS of logging in tests.
     // shouldLog = true;
   }
 
   let store = createStore({
     log: shouldLog,
     history
--- a/devtools/client/memory/test/chrome/head.js
+++ b/devtools/client/memory/test/chrome/head.js
@@ -18,18 +18,19 @@ var EXPECTED_DTU_ASSERT_FAILURE_COUNT = 
 SimpleTest.registerCleanupFunction(function () {
   if (DevToolsUtils.assertionFailureCount !== EXPECTED_DTU_ASSERT_FAILURE_COUNT) {
     ok(false, "Should have had the expected number of DevToolsUtils.assert() failures. Expected " +
        EXPECTED_DTU_ASSERT_FAILURE_COUNT + ", got " + DevToolsUtils.assertionFailureCount);
   }
 });
 
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-DevToolsUtils.testing = true;
 var { immutableUpdate } = DevToolsUtils;
+var flags = require("devtools/shared/flags");
+flags.testing = true;
 
 var constants = require("devtools/client/memory/constants");
 var {
   censusDisplays,
   diffingState,
   labelDisplays,
   dominatorTreeState,
   snapshotState,
--- a/devtools/client/memory/test/unit/head.js
+++ b/devtools/client/memory/test/unit/head.js
@@ -4,19 +4,20 @@
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 var { console } = Cu.import("resource://gre/modules/Console.jsm", {});
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var Services = require("Services");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-DevToolsUtils.testing = true;
-DevToolsUtils.dumpn.wantLogging = true;
-DevToolsUtils.dumpv.wantVerbose = false;
+var flags = require("devtools/shared/flags");
+flags.testing = true;
+flags.wantLogging = true;
+flags.wantVerbose = false;
 
 var { OS } = require("resource://gre/modules/osfile.jsm");
 var { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
 var { TargetFactory } = require("devtools/client/framework/target");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var { Task } = require("devtools/shared/task");
 var { expectState } = require("devtools/server/actors/common");
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -20,17 +20,17 @@ XPCOMUtils.defineLazyGetter(this, "Netwo
 
 const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 const {VariablesView} = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
 const {VariablesViewController} = require("resource://devtools/client/shared/widgets/VariablesViewController.jsm");
 const {ToolSidebar} = require("devtools/client/framework/sidebar");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {setImageTooltip, getImageDimensions} =
   require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { testing: isTesting } = require("devtools/shared/flags");
 const {LocalizationHelper} = require("devtools/client/shared/l10n");
 const {PrefsHelper} = require("devtools/client/shared/prefs");
 const {ViewHelpers, Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
 const {gDevTools} = require("devtools/client/framework/devtools");
 
 /**
  * Localization convenience methods.
@@ -43,17 +43,17 @@ const WEBCONSOLE_L10N = new Localization
 // ms
 const WDA_DEFAULT_VERIFY_INTERVAL = 50;
 
 // Use longer timeout during testing as the tests need this process to succeed
 // and two seconds is quite short on slow debug builds. The timeout here should
 // be at least equal to the general mochitest timeout of 45 seconds so that this
 // never gets hit during testing.
 // ms
-const WDA_DEFAULT_GIVE_UP_TIMEOUT = DevToolsUtils.testing ? 45000 : 2000;
+const WDA_DEFAULT_GIVE_UP_TIMEOUT = isTesting ? 45000 : 2000;
 
 /**
  * Shortcuts for accessing various network monitor preferences.
  */
 var Prefs = new PrefsHelper("devtools.netmonitor", {
   networkDetailsWidth: ["Int", "panes-network-details-width"],
   networkDetailsHeight: ["Int", "panes-network-details-height"],
   statistics: ["Bool", "statistics"],
@@ -337,17 +337,17 @@ var NetMonitorView = {
         // • The response content size and request total time are necessary for
         // populating the statistics view.
         // • The response mime type is used for categorization.
         yield whenDataAvailable(requestsView.attachments, [
           "responseHeaders", "status", "contentSize", "mimeType", "totalTime"
         ]);
       } catch (ex) {
         // Timed out while waiting for data. Continue with what we have.
-        DevToolsUtils.reportException("showNetworkStatisticsView", ex);
+        console.error(ex);
       }
 
       statisticsView.createPrimedCacheChart(requestsView.items);
       statisticsView.createEmptyCacheChart(requestsView.items);
     });
   },
 
   reloadPage: function () {
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -7,16 +7,17 @@ var { classes: Cc, interfaces: Ci, utils
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { Task } = require("devtools/shared/task");
 var { CurlUtils } = Cu.import("resource://devtools/client/shared/Curl.jsm", {});
 var Services = require("Services");
 var promise = require("promise");
 var NetworkHelper = require("devtools/shared/webconsole/network-helper");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
 
 const API_CALLS_URL = EXAMPLE_URL + "html_api-calls-test-page.html";
 const SIMPLE_URL = EXAMPLE_URL + "html_simple-test-page.html";
 const NAVIGATE_URL = EXAMPLE_URL + "html_navigate-test-page.html";
@@ -55,19 +56,19 @@ const HSTS_SJS = EXAMPLE_URL + "sjs_hsts
 const HSTS_BASE_URL = EXAMPLE_URL;
 const HSTS_PAGE_URL = CUSTOM_GET_URL;
 
 const TEST_IMAGE = EXAMPLE_URL + "test-image.png";
 const TEST_IMAGE_DATA_URI = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAHWSURBVHjaYvz//z8DJQAggJiQOe/fv2fv7Oz8rays/N+VkfG/iYnJfyD/1+rVq7ffu3dPFpsBAAHEAHIBCJ85c8bN2Nj4vwsDw/8zQLwKiO8CcRoQu0DxqlWrdsHUwzBAAIGJmTNnPgYa9j8UqhFElwPxf2MIDeIrKSn9FwSJoRkAEEAM0DD4DzMAyPi/G+QKY4hh5WAXGf8PDQ0FGwJ22d27CjADAAIIrLmjo+MXA9R2kAHvGBA2wwx6B8W7od6CeQcggKCmCEL8bgwxYCbUIGTDVkHDBia+CuotgACCueD3TDQN75D4xmAvCoK9ARMHBzAw0AECiBHkAlC0Mdy7x9ABNA3obAZXIAa6iKEcGlMVQHwWyjYuL2d4v2cPg8vZswx7gHyAAAK7AOif7SAbOqCmn4Ha3AHFsIDtgPq/vLz8P4MSkJ2W9h8ggBjevXvHDo4FQUQg/kdypqCg4H8lUIACnQ/SOBMYI8bAsAJFPcj1AAEEjwVQqLpAbXmH5BJjqI0gi9DTAAgDBBCcAVLkgmQ7yKCZxpCQxqUZhAECCJ4XgMl493ug21ZD+aDAXH0WLM4A9MZPXJkJIIAwTAR5pQMalaCABQUULttBGCCAGCnNzgABBgAMJ5THwGvJLAAAAABJRU5ErkJggg==";
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 SimpleTest.registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 const gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
--- a/devtools/client/performance/components/test/head.js
+++ b/devtools/client/performance/components/test/head.js
@@ -7,22 +7,22 @@
 /* exported Cc, Ci, Cu, Cr, Assert, Task, TargetFactory, Toolbox, browserRequire,
    forceRender, setProps, dumpn, checkOptimizationHeader, checkOptimizationTree */
 let { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 let { require } = Cu.import("resource://gre/modules/devtools/shared/Loader.jsm", {});
 let { Assert } = require("resource://testing-common/Assert.jsm");
 let { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 let defer = require("devtools/shared/defer");
-let DevToolsUtils = require("devtools/shared/DevToolsUtils");
+let flags = require("devtools/shared/flags");
 let { Task } = require("devtools/shared/task");
 let { TargetFactory } = require("devtools/client/framework/target");
 let { Toolbox } = require("devtools/client/framework/toolbox");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 let { require: browserRequire } = BrowserLoader({
   baseURI: "resource://devtools/client/performance/",
   window: this
 });
 
 let $ = (selector, scope = document) => scope.querySelector(selector);
 let $$ = (selector, scope = document) => scope.querySelectorAll(selector);
 
--- a/devtools/client/performance/legacy/front.js
+++ b/devtools/client/performance/legacy/front.js
@@ -8,17 +8,17 @@ const { Task } = require("devtools/share
 const Services = require("Services");
 const promise = require("promise");
 const { extend } = require("sdk/util/object");
 
 const Actors = require("devtools/client/performance/legacy/actors");
 const { LegacyPerformanceRecording } = require("devtools/client/performance/legacy/recording");
 const { importRecording } = require("devtools/client/performance/legacy/recording");
 const { normalizePerformanceFeatures } = require("devtools/shared/performance/recording-utils");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const { getDeviceFront } = require("devtools/shared/device/device");
 const { getSystemInfo } = require("devtools/shared/system");
 const events = require("sdk/event/core");
 const { EventTarget } = require("sdk/event/target");
 const { Class } = require("sdk/core/heritage");
 
 /**
  * A connection to underlying actors (profiler, framerate, etc.)
@@ -435,17 +435,17 @@ const LegacyPerformanceFront = Class({
     events.emit(this, eventName, ...args);
   },
 
   /**
    * Helper method to interface with the underlying actors directly.
    * Used only in tests.
    */
   _request: function (actorName, method, ...args) {
-    if (!DevToolsUtils.testing) {
+    if (!flags.testing) {
       throw new Error("LegacyPerformanceFront._request may only be used in tests.");
     }
     let actor = this[`_${actorName}`];
     return actor[method].apply(actor, args);
   },
 
   /**
    * Sets how often the "profiler-status" event should be emitted.
--- a/devtools/client/performance/modules/logic/telemetry.js
+++ b/devtools/client/performance/modules/logic/telemetry.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const Telemetry = require("devtools/client/shared/telemetry");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const EVENTS = require("devtools/client/performance/events");
 
 const EVENT_MAP_FLAGS = new Map([
   [EVENTS.RECORDING_IMPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_IMPORT_FLAG"],
   [EVENTS.RECORDING_EXPORTED, "DEVTOOLS_PERFTOOLS_RECORDING_EXPORT_FLAG"],
 ]);
 
 const RECORDING_FEATURES = [
@@ -27,17 +27,17 @@ function PerformanceTelemetry(emitter) {
 
   for (let [event] of EVENT_MAP_FLAGS) {
     this._emitter.on(event, this.onFlagEvent);
   }
 
   this._emitter.on(EVENTS.RECORDING_STATE_CHANGE, this.onRecordingStateChange);
   this._emitter.on(EVENTS.UI_DETAILS_VIEW_SELECTED, this.onViewSelected);
 
-  if (DevToolsUtils.testing) {
+  if (flags.testing) {
     this.recordLogs();
   }
 }
 
 PerformanceTelemetry.prototype.destroy = function () {
   if (this._previousView) {
     this._telemetry.stopTimer(SELECTED_VIEW_HISTOGRAM_NAME, this._previousView);
   }
@@ -85,17 +85,17 @@ PerformanceTelemetry.prototype.onViewSel
   this._telemetry.startTimer(SELECTED_VIEW_HISTOGRAM_NAME);
 };
 
 /**
  * Utility to record histogram calls to this instance.
  * Should only be used in testing mode; throws otherwise.
  */
 PerformanceTelemetry.prototype.recordLogs = function () {
-  if (!DevToolsUtils.testing) {
+  if (!flags.testing) {
     throw new Error("Can only record telemetry logs in tests.");
   }
 
   let originalLog = this._telemetry.log;
   let originalLogKeyed = this._telemetry.logKeyed;
   this._log = {};
 
   this._telemetry.log = (function (histo, data) {
@@ -107,16 +107,16 @@ PerformanceTelemetry.prototype.recordLog
   this._telemetry.logKeyed = (function (histo, key, data) {
     let results = this._log[histo] = this._log[histo] || [];
     results.push([key, data]);
     originalLogKeyed(histo, key, data);
   }).bind(this);
 };
 
 PerformanceTelemetry.prototype.getLogs = function () {
-  if (!DevToolsUtils.testing) {
+  if (!flags.testing) {
     throw new Error("Can only get telemetry logs in tests.");
   }
 
   return this._log;
 };
 
 exports.PerformanceTelemetry = PerformanceTelemetry;
--- a/devtools/client/performance/performance-controller.js
+++ b/devtools/client/performance/performance-controller.js
@@ -30,16 +30,17 @@ Object.defineProperty(this, "EVENTS", {
    DevToolsUtils, system */
 var React = require("devtools/client/shared/vendor/react");
 var ReactDOM = require("devtools/client/shared/vendor/react-dom");
 var JITOptimizationsView = React.createFactory(require("devtools/client/performance/components/jit-optimizations"));
 var Services = require("Services");
 var promise = require("promise");
 var EventEmitter = require("devtools/shared/event-emitter");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var system = require("devtools/shared/system");
 
 // Logic modules
 /* exported L10N, PerformanceTelemetry, TIMELINE_BLUEPRINT, RecordingUtils,
    OptimizationsGraph, GraphsController, WaterfallHeader, MarkerView, MarkerDetails,
    MarkerBlueprintUtils, WaterfallUtils, FrameUtils, CallView, ThreadNode, FrameNode */
 var { L10N } = require("devtools/client/performance/modules/global");
 var { PerformanceTelemetry } = require("devtools/client/performance/modules/logic/telemetry");
@@ -511,17 +512,17 @@ var PerformanceController = {
    *
    * @return {object}
    */
   getMultiprocessStatus: function () {
     // If testing, set both supported and enabled to true so we
     // have realtime rendering tests in non-e10s. This function is
     // overridden wholesale in tests when we want to test multiprocess support
     // specifically.
-    if (DevToolsUtils.testing) {
+    if (flags.testing) {
       return { supported: true, enabled: true };
     }
     let supported = system.constants.E10S_TESTING_ONLY;
     // This is only checked on tool startup -- requires a restart if
     // e10s subsequently enabled.
     let enabled = this._e10s;
     return { supported, enabled };
   },
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -47,36 +47,36 @@ const rightMousedown = (node, win = wind
 
 // Shortcut for firing a key event, like "VK_UP", "VK_DOWN", etc.
 const key = (id, win = window) => {
   EventUtils.synthesizeKey(id, {}, win);
 };
 
 // Don't pollute global scope.
 (() => {
-  const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+  const flags = require("devtools/shared/flags");
   const PrefUtils = require("devtools/client/performance/test/helpers/prefs");
 
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   // Make sure all the prefs are reverted to their defaults once tests finish.
   let stopObservingPrefs = PrefUtils.whenUnknownPrefChanged("devtools.performance",
     pref => {
       ok(false, `Unknown pref changed: ${pref}. Please add it to test/helpers/prefs.js ` +
         "to make sure it's reverted to its default value when the tests finishes, " +
         "and avoid interfering with future tests.\n");
     });
 
   // By default, enable memory flame graphs for tests for now.
   // TODO: remove when we have flame charts via bug 1148663.
   Services.prefs.setBoolPref(PrefUtils.UI_ENABLE_MEMORY_FLAME_CHART, true);
 
   registerCleanupFunction(() => {
     info("finish() was called, cleaning up...");
-    DevToolsUtils.testing = false;
+    flags.testing = false;
 
     PrefUtils.rollbackPrefsToDefault();
     stopObservingPrefs();
 
     // Manually stop the profiler module at the end of all tests, to hopefully
     // avoid at least some leaks on OSX. Theoretically the module should never
     // be active at this point. We shouldn't have to do this, but rather
     // find and fix the leak in the module itself. Bug 1257439.
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -76,16 +76,17 @@ pref("devtools.defaultColorUnit", "autho
 
 // Enable the Responsive UI tool
 pref("devtools.responsiveUI.no-reload-notification", false);
 
 // Enable the Debugger
 pref("devtools.debugger.enabled", true);
 pref("devtools.debugger.chrome-debugging-host", "localhost");
 pref("devtools.debugger.chrome-debugging-port", 6080);
+pref("devtools.debugger.chrome-debugging-websocket", false);
 pref("devtools.debugger.remote-host", "localhost");
 pref("devtools.debugger.remote-timeout", 20000);
 pref("devtools.debugger.pause-on-exceptions", false);
 pref("devtools.debugger.ignore-caught-exceptions", true);
 pref("devtools.debugger.source-maps-enabled", true);
 pref("devtools.debugger.pretty-print-enabled", true);
 pref("devtools.debugger.auto-pretty-print", false);
 pref("devtools.debugger.auto-black-box", true);
--- a/devtools/client/projecteditor/test/head.js
+++ b/devtools/client/projecteditor/test/head.js
@@ -6,31 +6,32 @@ var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {TargetFactory} = require("devtools/client/framework/target");
 const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 const promise = require("promise");
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const ProjectEditor = require("devtools/client/projecteditor/lib/projecteditor");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
 const TEST_URL_ROOT = "http://mochi.test:8888/browser/devtools/client/projecteditor/test/";
 const SAMPLE_WEBAPP_URL = TEST_URL_ROOT + "/helper_homepage.html";
 var TEMP_PATH;
 var TEMP_FOLDER_NAME = "ProjectEditor" + (new Date().getTime());
 
 // All test are asynchronous
 waitForExplicitFinish();
 
 // Uncomment this pref to dump all devtools emitted events to the console.
 // Services.prefs.setBoolPref("devtools.dump.emit", true);
 
-// Set the testing flag on DevToolsUtils and reset it when the test ends
-DevToolsUtils.testing = true;
-registerCleanupFunction(() => DevToolsUtils.testing = false);
+// Set the testing flag and reset it when the test ends
+flags.testing = true;
+registerCleanupFunction(() => flags.testing = false);
 
 // Clear preferences that may be set during the course of tests.
 registerCleanupFunction(() => {
   // Services.prefs.clearUserPref("devtools.dump.emit");
   TEMP_PATH = null;
   TEMP_FOLDER_NAME = null;
 });
 
--- a/devtools/client/responsive.html/components/browser.js
+++ b/devtools/client/responsive.html/components/browser.js
@@ -2,17 +2,17 @@
  * 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/. */
 
 /* eslint-env browser */
 
 "use strict";
 
 const { Task } = require("devtools/shared/task");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const { getToplevelWindow } = require("sdk/window/utils");
 const { DOM: dom, createClass, addons, PropTypes } =
   require("devtools/client/shared/vendor/react");
 
 const Types = require("../types");
 const e10s = require("../utils/e10s");
 const message = require("../utils/message");
 
@@ -91,17 +91,17 @@ module.exports = createClass({
 
     let browserWindow = getToplevelWindow(window);
     let requiresFloatingScrollbars =
       !browserWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
 
     yield e10s.request(mm, "Start", {
       requiresFloatingScrollbars,
       // Tests expect events on resize to yield on various size changes
-      notifyOnResize: DevToolsUtils.testing,
+      notifyOnResize: flags.testing,
     });
   }),
 
   stopFrameScript: Task.async(function* () {
     let { onContentResize } = this;
     let browser = this.refs.browserContainer.querySelector("iframe.browser");
     let mm = browser.frameLoader.messageManager;
     e10s.off(mm, "OnContentResize", onContentResize);
--- a/devtools/client/responsive.html/store.js
+++ b/devtools/client/responsive.html/store.js
@@ -2,25 +2,25 @@
  * 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 { combineReducers } = require("devtools/client/shared/vendor/redux");
 const createStore = require("devtools/client/shared/redux/create-store");
 const reducers = require("./reducers");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
 module.exports = function () {
   let shouldLog = false;
   let history;
 
   // If testing, store the action history in an array
   // we'll later attach to the store
-  if (DevToolsUtils.testing) {
+  if (flags.testing) {
     history = [];
     shouldLog = true;
   }
 
   let store = createStore({
     log: shouldLog,
     history
   })(combineReducers(reducers), {});
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -36,24 +36,24 @@ const OPEN_DEVICE_MODAL_VALUE = "OPEN_DE
 
 const { _loadPreferredDevices } = require("devtools/client/responsive.html/actions/devices");
 const { getOwnerWindow } = require("sdk/tabs/utils");
 const asyncStorage = require("devtools/shared/async-storage");
 
 SimpleTest.requestCompleteLog();
 SimpleTest.waitForExplicitFinish();
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
 Services.prefs.setCharPref("devtools.devices.url",
   TEST_URI_ROOT + "devices.json");
 Services.prefs.setBoolPref("devtools.responsive.html.enabled", true);
 
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.clearUserPref("devtools.devices.url");
   Services.prefs.clearUserPref("devtools.responsive.html.enabled");
   Services.prefs.clearUserPref("devtools.responsive.html.displayedDeviceList");
   asyncStorage.removeItem("devtools.devices.url_cache");
 });
 
 // This depends on the "devtools.responsive.html.enabled" pref
 const { ResponsiveUIManager } = require("resource://devtools/client/responsivedesign/responsivedesign.jsm");
--- a/devtools/client/responsive.html/test/unit/head.js
+++ b/devtools/client/responsive.html/test/unit/head.js
@@ -8,12 +8,14 @@
 const { utils: Cu } = Components;
 const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 const promise = require("promise");
 const { Task } = require("devtools/shared/task");
 const Store = require("devtools/client/responsive.html/store");
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-DevToolsUtils.testing = true;
+
+const flags = require("devtools/shared/flags");
+flags.testing = true;
 do_register_cleanup(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
--- a/devtools/client/responsivedesign/responsivedesign.jsm
+++ b/devtools/client/responsivedesign/responsivedesign.jsm
@@ -9,16 +9,17 @@ const Cu = Components.utils;
 
 var {loader, require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var Telemetry = require("devtools/client/shared/telemetry");
 var {showDoorhanger} = require("devtools/client/shared/doorhanger");
 var {TouchEventSimulator} = require("devtools/shared/touch/simulator");
 var {Task} = require("devtools/shared/task");
 var promise = require("promise");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var Services = require("Services");
 var EventEmitter = require("devtools/shared/event-emitter");
 var {ViewHelpers} = require("devtools/client/shared/widgets/view-helpers");
 var { LocalizationHelper } = require("devtools/client/shared/l10n");
 
 loader.lazyImporter(this, "SystemAppProxy",
                     "resource://gre/modules/SystemAppProxy.jsm");
 loader.lazyRequireGetter(this, "DebuggerClient",
@@ -220,17 +221,17 @@ ResponsiveUI.prototype = {
 
     let requiresFloatingScrollbars =
       !this.mainWindow.matchMedia("(-moz-overlay-scrollbars)").matches;
     let started = this.waitForMessage("ResponsiveMode:Start:Done");
     debug("SEND START");
     this.mm.sendAsyncMessage("ResponsiveMode:Start", {
       requiresFloatingScrollbars,
       // Tests expect events on resize to yield on various size changes
-      notifyOnResize: DevToolsUtils.testing,
+      notifyOnResize: flags.testing,
     });
     yield started;
 
     // Load Presets
     this.loadPresets();
 
     // Setup the UI
     this.container.setAttribute("responsivemode", "true");
@@ -342,17 +343,17 @@ ResponsiveUI.prototype = {
                 "min-width: 0;" +
                 "max-height: none;" +
                 "min-height: 0;";
     debug("RESET STACK SIZE");
     this.stack.setAttribute("style", style);
 
     // Wait for resize message before stopping in the child when testing,
     // but only if we should expect to still get a message.
-    if (DevToolsUtils.testing && this.tab.linkedBrowser.messageManager) {
+    if (flags.testing && this.tab.linkedBrowser.messageManager) {
       yield this.waitForMessage("ResponsiveMode:OnContentResize");
     }
 
     if (this.isResizing)
       this.stopResizing();
 
     // Remove listeners.
     this.menulist.removeEventListener("select", this.bound_presetSelected, true);
--- a/devtools/client/responsivedesign/test/head.js
+++ b/devtools/client/responsivedesign/test/head.js
@@ -7,19 +7,19 @@ let testDir = gTestPath.substr(0, gTestP
 // shared-head.js handles imports, constants, and utility functions
 let sharedHeadURI = testDir + "../../../framework/test/shared-head.js";
 Services.scriptloader.loadSubScript(sharedHeadURI, this);
 
 // Import the GCLI test helper
 let gcliHelpersURI = testDir + "../../../commandline/test/helpers.js";
 Services.scriptloader.loadSubScript(gcliHelpersURI, this);
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.clearUserPref("devtools.responsiveUI.currentPreset");
   Services.prefs.clearUserPref("devtools.responsiveUI.customHeight");
   Services.prefs.clearUserPref("devtools.responsiveUI.customWidth");
   Services.prefs.clearUserPref("devtools.responsiveUI.presets");
   Services.prefs.clearUserPref("devtools.responsiveUI.rotate");
 });
 
 SimpleTest.requestCompleteLog();
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -45,16 +45,17 @@ const VARIABLES_VIEW_URL = "chrome://dev
 
 const {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 const Editor = require("devtools/client/sourceeditor/editor");
 const TargetFactory = require("devtools/client/framework/target").TargetFactory;
 const EventEmitter = require("devtools/shared/event-emitter");
 const {DevToolsWorker} = require("devtools/shared/worker/worker");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const promise = require("promise");
 const Services = require("Services");
 const {gDevTools} = require("devtools/client/framework/devtools");
 const {Heritage} = require("devtools/client/shared/widgets/view-helpers");
 
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
 const {ScratchpadManager} = require("resource://devtools/client/scratchpad/scratchpad-manager.jsm");
@@ -666,17 +667,17 @@ var Scratchpad = {
   /**
    * Get or create the worker that handles pretty printing.
    */
   get prettyPrintWorker() {
     if (!this._prettyPrintWorker) {
       this._prettyPrintWorker = new DevToolsWorker(
         "resource://devtools/server/actors/pretty-print-worker.js",
         { name: "pretty-print",
-          verbose: DevToolsUtils.dumpn.wantLogging }
+          verbose: flags.wantLogging }
       );
     }
     return this._prettyPrintWorker;
   },
 
   /**
    * Pretty print the source text inside the scratchpad.
    *
--- a/devtools/client/scratchpad/test/head.js
+++ b/devtools/client/scratchpad/test/head.js
@@ -6,24 +6,25 @@
 
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
 const {ScratchpadManager} = Cu.import("resource://devtools/client/scratchpad/scratchpad-manager.jsm", {});
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Services = require("Services");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const promise = require("promise");
 
 
 var gScratchpadWindow; // Reference to the Scratchpad chrome window object
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 SimpleTest.registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 /**
  * Open a Scratchpad window.
  *
  * @param function aReadyCallback
  *        Optional. The function you want invoked when the Scratchpad instance
  *        is ready.
--- a/devtools/client/shadereditor/test/head.js
+++ b/devtools/client/shadereditor/test/head.js
@@ -9,16 +9,17 @@ var { Task } = require("devtools/shared/
 
 var Services = require("Services");
 var promise = require("promise");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var { DebuggerServer } = require("devtools/server/main");
 var { WebGLFront } = require("devtools/shared/fronts/webgl");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 var { isWebGLSupported } = require("devtools/client/shared/webgl-utils");
 var mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/shadereditor/test/";
 const SIMPLE_CANVAS_URL = EXAMPLE_URL + "doc_simple-canvas.html";
@@ -31,21 +32,21 @@ var gEnableLogging = Services.prefs.getB
 // To enable logging for try runs, just set the pref to true.
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 var gToolEnabled = Services.prefs.getBoolPref("devtools.shadereditor.enabled");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 
 registerCleanupFunction(() => {
   info("finish() was called, cleaning up...");
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.shadereditor.enabled", gToolEnabled);
 
   // These tests use a lot of memory due to GL contexts, so force a GC to help
   // fragmentation.
   info("Forcing GC after shadereditor test.");
   Cu.forceGC();
 });
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -11,21 +11,22 @@ var { Assert } = require("resource://tes
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var Services = require("Services");
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/main");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var { Task } = require("devtools/shared/task");
 var { TargetFactory } = require("devtools/client/framework/target");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 var { require: browserRequire } = BrowserLoader({
   baseURI: "resource://devtools/client/shared/",
   window: this
 });
 
 let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
 let React = browserRequire("devtools/client/shared/vendor/react");
 var TestUtils = React.addons.TestUtils;
--- a/devtools/client/shared/redux/middleware/history.js
+++ b/devtools/client/shared/redux/middleware/history.js
@@ -1,22 +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/. */
 "use strict";
 
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
 /**
  * A middleware that stores every action coming through the store in the passed
  * in logging object. Should only be used for tests, as it collects all
  * action information, which will cause memory bloat.
  */
 exports.history = (log = []) => ({ dispatch, getState }) => {
-  if (!DevToolsUtils.testing) {
+  if (!flags.testing) {
     console.warn("Using history middleware stores all actions in state for " +
                  "testing and devtools is not currently running in test " +
                  "mode. Be sure this is intentional.");
   }
   return next => action => {
     log.push(action);
     next(action);
   };
--- a/devtools/client/shared/redux/middleware/test/head.js
+++ b/devtools/client/shared/redux/middleware/test/head.js
@@ -1,19 +1,19 @@
 /* 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 { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 
 function waitUntilState(store, predicate) {
   let deferred = defer();
   let unsubscribe = store.subscribe(check);
 
   function check() {
     if (predicate(store.getState())) {
       unsubscribe();
--- a/devtools/client/shared/widgets/view-helpers.js
+++ b/devtools/client/shared/widgets/view-helpers.js
@@ -1,18 +1,16 @@
 /* -*- 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 DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { Cu, Ci } = require("chrome");
-let { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { Ci } = require("chrome");
 
 const PANE_APPEARANCE_DELAY = 50;
 const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
 const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
 
 /**
  * Inheritance helpers from the addon SDK's core/heritage.
  * Remove these when all devtools are loadered.
@@ -337,16 +335,17 @@ const ViewHelpers = exports.ViewHelpers 
  * @param any attachment
  *        Some attached primitive/object.
  */
 function Item(ownerView, element, value, attachment) {
   this.ownerView = ownerView;
   this.attachment = attachment;
   this._value = value + "";
   this._prebuiltNode = element;
+  this._itemsByElement = new Map();
 }
 
 Item.prototype = {
   get value() {
     return this._value;
   },
   get target() {
     return this._target;
@@ -459,21 +458,16 @@ Item.prototype = {
 
   _value: "",
   _target: null,
   _prebuiltNode: null,
   finalize: null,
   attachment: null
 };
 
-// Creating maps thousands of times for widgets with a large number of children
-// fills up a lot of memory. Make sure these are instantiated only if needed.
-DevToolsUtils.defineLazyPrototypeGetter(Item.prototype, "_itemsByElement",
-                                        () => new Map());
-
 /**
  * Some generic Widget methods handling Item instances.
  * Iterable via "for (let childItem of wrappedView) { }".
  *
  * Usage:
  *   function MyView() {
  *     this.widget = new MyWidget(document.querySelector(".my-node"));
  *   }
@@ -526,19 +520,19 @@ const WidgetMethods = exports.WidgetMeth
    * Sets the element node or widget associated with this container.
    * @param nsIDOMNode | object widget
    */
   set widget(widget) {
     this._widget = widget;
 
     // Can't use a WeakMap for _itemsByValue because keys are strings, and
     // can't use one for _itemsByElement either, since it needs to be iterable.
-    XPCOMUtils.defineLazyGetter(this, "_itemsByValue", () => new Map());
-    XPCOMUtils.defineLazyGetter(this, "_itemsByElement", () => new Map());
-    XPCOMUtils.defineLazyGetter(this, "_stagedItems", () => []);
+    this._itemsByValue = new Map();
+    this._itemsByElement = new Map();
+    this._stagedItems = [];
 
     // Handle internal events emitted by the widget if necessary.
     if (ViewHelpers.isEventEmitter(widget)) {
       widget.on("keyPress", this._onWidgetKeyPress.bind(this));
       widget.on("mousePress", this._onWidgetMousePress.bind(this));
     }
   },
 
--- a/devtools/client/sourceeditor/test/head.js
+++ b/devtools/client/sourceeditor/test/head.js
@@ -4,20 +4,21 @@
 
 "use strict";
 
 const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const Editor = require("devtools/client/sourceeditor/editor");
 const promise = require("promise");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 SimpleTest.registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 /**
  * Open a new tab at a URL and call a callback on load
  */
 function addTab(url, callback) {
   waitForExplicitFinish();
 
--- a/devtools/client/webaudioeditor/test/head.js
+++ b/devtools/client/webaudioeditor/test/head.js
@@ -11,16 +11,17 @@ var { gDevTools } = require("devtools/cl
 var { TargetFactory } = require("devtools/client/framework/target");
 var { DebuggerServer } = require("devtools/server/main");
 var { generateUUID } = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
 
 var Promise = require("promise");
 var Services = require("Services");
 var { WebAudioFront } = require("devtools/shared/fronts/webaudio");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var audioNodes = require("devtools/server/actors/utils/audionodes.json");
 var mm = null;
 
 const FRAME_SCRIPT_UTILS_URL = "chrome://devtools/content/shared/frame-script-utils.js";
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/webaudioeditor/test/";
 const SIMPLE_CONTEXT_URL = EXAMPLE_URL + "doc_simple-context.html";
 const COMPLEX_CONTEXT_URL = EXAMPLE_URL + "doc_complex-context.html";
 const SIMPLE_NODES_URL = EXAMPLE_URL + "doc_simple-node-creation.html";
@@ -37,20 +38,20 @@ const AUTOMATION_URL = EXAMPLE_URL + "do
 var gEnableLogging = Services.prefs.getBoolPref("devtools.debugger.log");
 Services.prefs.setBoolPref("devtools.debugger.log", false);
 
 // All tests are asynchronous.
 waitForExplicitFinish();
 
 var gToolEnabled = Services.prefs.getBoolPref("devtools.webaudioeditor.enabled");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   info("finish() was called, cleaning up...");
   Services.prefs.setBoolPref("devtools.debugger.log", gEnableLogging);
   Services.prefs.setBoolPref("devtools.webaudioeditor.enabled", gToolEnabled);
   Cu.forceGC();
 });
 
 /**
  * Call manually in tests that use frame script utils after initializing
--- a/devtools/client/webaudioeditor/views/context.js
+++ b/devtools/client/webaudioeditor/views/context.js
@@ -1,16 +1,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/. */
 "use strict";
 
 /* import-globals-from ../includes.js */
 
 const { debounce } = require("sdk/lang/functional");
+const flags = require("devtools/shared/flags");
 
 // Globals for d3 stuff
 // Default properties of the graph on rerender
 const GRAPH_DEFAULTS = {
   translate: [20, 20],
   scale: 1
 };
 
@@ -224,17 +225,17 @@ var ContextView = {
       let currentNode = InspectorView.getCurrentAudioNode();
       if (currentNode) {
         this.focusNode(currentNode.id);
       }
 
       // Fire an event upon completed rendering, with extra information
       // if in testing mode only.
       let info = {};
-      if (DevToolsUtils.testing) {
+      if (flags.testing) {
         info = gAudioNodes.getInfo();
       }
       window.emit(EVENTS.UI_GRAPH_RENDERED, info.nodes, info.edges, info.paramEdges);
     });
 
     let layout = dagreD3.layout().rankDir("LR");
     renderer.layout(layout).run(graph, d3.select("#graph-target"));
 
--- a/devtools/client/webconsole/new-console-output/test/actions/head.js
+++ b/devtools/client/webconsole/new-console-output/test/actions/head.js
@@ -2,19 +2,20 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var { utils: Cu } = Components;
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-DevToolsUtils.testing = true;
-DevToolsUtils.dumpn.wantLogging = true;
-DevToolsUtils.dumpv.wantVerbose = false;
+var flags = require("devtools/shared/flags");
+flags.testing = true;
+flags.wantLogging = true;
+flags.wantVerbose = false;
 
 // @TODO consolidate once we have a shared head. See #16
 const testPackets = new Map();
 testPackets.set("console.log", {
   "from": "server1.conn4.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
     "arguments": [
--- a/devtools/client/webconsole/new-console-output/test/components/head.js
+++ b/devtools/client/webconsole/new-console-output/test/components/head.js
@@ -7,23 +7,24 @@
 "use strict";
 
 var { utils: Cu } = Components;
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { Assert } = require("resource://testing-common/Assert.jsm");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
+var flags = require("devtools/shared/flags");
 var { Task } = require("devtools/shared/task");
 var { DebuggerServer } = require("devtools/server/main");
 var { DebuggerClient } = require("devtools/shared/client/main");
 
 const Services = require("Services");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 var { require: browserRequire } = BrowserLoader({
   baseURI: "resource://devtools/client/webconsole/",
   window: this
 });
 
 let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
 let React = browserRequire("devtools/client/shared/vendor/react");
 var TestUtils = React.addons.TestUtils;
--- a/devtools/client/webconsole/new-console-output/test/store/head.js
+++ b/devtools/client/webconsole/new-console-output/test/store/head.js
@@ -5,19 +5,20 @@
 
 "use strict";
 
 var { utils: Cu } = Components;
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Services = require("Services");
 
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
-DevToolsUtils.testing = true;
-DevToolsUtils.dumpn.wantLogging = true;
-DevToolsUtils.dumpv.wantVerbose = false;
+var flags = require("devtools/shared/flags");
+flags.testing = true;
+flags.wantLogging = true;
+flags.wantVerbose = false;
 
 const { storeFactory } = require("devtools/client/webconsole/new-console-output/store");
 
 const testPackets = new Map();
 testPackets.set("console.log", {
   "from": "server1.conn4.child1/consoleActor2",
   "type": "consoleAPICall",
   "message": {
--- a/devtools/client/webconsole/test/head.js
+++ b/devtools/client/webconsole/test/head.js
@@ -40,17 +40,17 @@ const GROUP_INDENT = 12;
 const WEBCONSOLE_STRINGS_URI = "chrome://devtools/locale/" +
                                "webconsole.properties";
 var WCUL10n = new WebConsoleUtils.L10n(WEBCONSOLE_STRINGS_URI);
 
 const DOCS_GA_PARAMS = "?utm_source=mozilla" +
                        "&utm_medium=firefox-console-errors" +
                        "&utm_campaign=default";
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 
 function loadTab(url) {
   let deferred = promise.defer();
 
   let tab = gBrowser.selectedTab = gBrowser.addTab(url);
   let browser = gBrowser.getBrowserForTab(tab);
 
   browser.addEventListener("load", function onLoad() {
@@ -318,17 +318,17 @@ var finishTest = Task.async(function* ()
 
   let target = TargetFactory.forTab(gBrowser.selectedTab);
   yield gDevTools.closeToolbox(target);
 
   finish();
 });
 
 registerCleanupFunction(function* () {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 
   // Remove stored console commands in between tests
   yield asyncStorage.removeItem("webConsoleHistory");
 
   dumpConsoles();
 
   let browserConsole = HUDService.getBrowserConsole();
   if (browserConsole) {
--- a/devtools/client/webide/test/head.js
+++ b/devtools/client/webide/test/head.js
@@ -9,17 +9,18 @@ const { require } = Cu.import("resource:
 const { FileUtils } = require("resource://gre/modules/FileUtils.jsm");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const promise = require("promise");
 const Services = require("Services");
 const { Task } = require("devtools/shared/task");
 const { AppProjects } = require("devtools/client/webide/modules/app-projects");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { DebuggerServer } = require("devtools/server/main");
-DevToolsUtils.testing = true;
+const flags = require("devtools/shared/flags");
+flags.testing = true;
 
 var TEST_BASE;
 if (window.location === "chrome://browser/content/browser.xul") {
   TEST_BASE = "chrome://mochitests/content/browser/devtools/client/webide/test/";
 } else {
   TEST_BASE = "chrome://mochitests/content/chrome/devtools/client/webide/test/";
 }
 
@@ -31,17 +32,17 @@ Services.prefs.setCharPref("devtools.web
 Services.prefs.setCharPref("devtools.webide.adbAddonURL", TEST_BASE + "addons/adbhelper-#OS#.xpi");
 Services.prefs.setCharPref("devtools.webide.adaptersAddonURL", TEST_BASE + "addons/fxdt-adapters-#OS#.xpi");
 Services.prefs.setCharPref("devtools.webide.templatesURL", TEST_BASE + "templates.json");
 Services.prefs.setCharPref("devtools.devices.url", TEST_BASE + "browser_devices.json");
 
 var registerCleanupFunction = registerCleanupFunction ||
                               SimpleTest.registerCleanupFunction;
 registerCleanupFunction(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
   Services.prefs.clearUserPref("devtools.webide.enabled");
   Services.prefs.clearUserPref("devtools.webide.enableLocalRuntime");
   Services.prefs.clearUserPref("devtools.webide.autoinstallADBHelper");
   Services.prefs.clearUserPref("devtools.webide.autoinstallFxdtAdapters");
   Services.prefs.clearUserPref("devtools.webide.busyTimeout");
   Services.prefs.clearUserPref("devtools.webide.lastSelectedProject");
   Services.prefs.clearUserPref("devtools.webide.lastConnectedRuntime");
 });
--- a/devtools/server/actors/highlighters/box-model.js
+++ b/devtools/server/actors/highlighters/box-model.js
@@ -687,30 +687,37 @@ BoxModelHighlighter.prototype = extend(A
 
   /**
    * Move the Infobar to the right place in the highlighter.
    */
   _moveInfobar: function () {
     let bounds = this._getOuterBounds();
     let winHeight = this.win.innerHeight * getCurrentZoom(this.win);
     let winWidth = this.win.innerWidth * getCurrentZoom(this.win);
+    let winScrollY = this.win.scrollY;
 
     // Ensure that containerBottom and containerTop are at least zero to avoid
     // showing tooltips outside the viewport.
     let containerBottom = Math.max(0, bounds.bottom) + NODE_INFOBAR_ARROW_SIZE;
     let containerTop = Math.min(winHeight, bounds.top);
     let container = this.getElement("nodeinfobar-container");
 
     // Can the bar be above the node?
     let top;
     if (containerTop < NODE_INFOBAR_HEIGHT) {
       // No. Can we move the bar under the node?
       if (containerBottom + NODE_INFOBAR_HEIGHT > winHeight) {
-        // No. Let's move it inside.
-        top = containerTop;
+        // No. Let's move it inside. Can we show it at the top of the element?
+        if (containerTop < winScrollY) {
+          // No. Window is scrolled past the top of the element.
+          top = 0;
+        } else {
+          // Yes. Show it at the top of the element
+          top = containerTop;
+        }
         container.setAttribute("position", "overlap");
       } else {
         // Yes. Let's move it under the node.
         top = containerBottom;
         container.setAttribute("position", "bottom");
       }
     } else {
       // Yes. Let's move it on top of the node.
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -127,16 +127,18 @@ var HELPER_SHEET = `
   }
 
   :-moz-devtools-highlighted {
     outline: 2px dashed #F06!important;
     outline-offset: -2px !important;
   }
 `;
 
+const flags = require("devtools/shared/flags");
+
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/shared/DevToolsUtils");
 
 loader.lazyRequireGetter(this, "AsyncUtils", "devtools/shared/async-utils");
 
 loader.lazyGetter(this, "DOMParser", function () {
   return Cc["@mozilla.org/xmlextras/domparser;1"]
            .createInstance(Ci.nsIDOMParser);
@@ -2995,17 +2997,17 @@ function allAnonymousContentTreeWalkerFi
 }
 
 /**
  * Returns a promise that is settled once the given HTMLImageElement has
  * finished loading.
  *
  * @param {HTMLImageElement} image - The image element.
  * @param {Number} timeout - Maximum amount of time the image is allowed to load
- * before the waiting is aborted. Ignored if DevToolsUtils.testing is set.
+ * before the waiting is aborted. Ignored if flags.testing is set.
  *
  * @return {Promise} that is fulfilled once the image has loaded. If the image
  * fails to load or the load takes too long, the promise is rejected.
  */
 function ensureImageLoaded(image, timeout) {
   let { HTMLImageElement } = image.ownerDocument.defaultView;
   if (!(image instanceof HTMLImageElement)) {
     return promise.reject("image must be an HTMLImageELement");
@@ -3022,17 +3024,17 @@ function ensureImageLoaded(image, timeou
   // Reject if loading fails.
   let onError = AsyncUtils.listenOnce(image, "error").then(() => {
     return promise.reject("Image '" + image.src + "' failed to load.");
   });
 
   // Don't timeout when testing. This is never settled.
   let onAbort = new Promise(() => {});
 
-  if (!DevToolsUtils.testing) {
+  if (!flags.testing) {
     // Tests are not running. Reject the promise after given timeout.
     onAbort = DevToolsUtils.waitForTime(timeout).then(() => {
       return promise.reject("Image '" + image.src + "' took too long to load.");
     });
   }
 
   // See which happens first.
   return promise.race([onLoad, onError, onAbort]);
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -12,16 +12,17 @@ const { ActorPool, OriginalLocation, Gen
 const { BreakpointActor, setBreakpointAtEntryPoints } = require("devtools/server/actors/breakpoint");
 const { EnvironmentActor } = require("devtools/server/actors/environment");
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { SourceActor, getSourceURL } = require("devtools/server/actors/source");
 const { DebuggerServer } = require("devtools/server/main");
 const { ActorClassWithSpec } = require("devtools/shared/protocol");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const promise = require("promise");
 const xpcInspector = require("xpcInspector");
 const { DevToolsWorker } = require("devtools/shared/worker/worker");
 const object = require("sdk/util/object");
 const { threadSpec } = require("devtools/shared/specs/script");
 
 const { defer, resolve, reject, all } = promise;
@@ -504,17 +505,17 @@ const ThreadActor = ActorClassWithSpec(t
   },
 
   _prettyPrintWorker: null,
   get prettyPrintWorker() {
     if (!this._prettyPrintWorker) {
       this._prettyPrintWorker = new DevToolsWorker(
         "resource://devtools/server/actors/pretty-print-worker.js",
         { name: "pretty-print",
-          verbose: dumpn.wantLogging }
+          verbose: flags.wantLogging }
       );
     }
     return this._prettyPrintWorker;
   },
 
   /**
    * Keep track of all of the nested event loops we use to pause the debuggee
    * when we hit a breakpoint/debugger statement/etc in one place so we can
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -11,16 +11,17 @@
 var { Ci, Cc, CC, Cu, Cr } = require("chrome");
 var Services = require("Services");
 var { ActorPool, OriginalLocation, RegisteredActorFactory,
       ObservedActorFactory } = require("devtools/server/actors/common");
 var { LocalDebuggerTransport, ChildDebuggerTransport, WorkerDebuggerTransport } =
   require("devtools/shared/transport/transport");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { dumpn, dumpv } = DevToolsUtils;
+var flags = require("devtools/shared/flags");
 var EventEmitter = require("devtools/shared/event-emitter");
 var Promise = require("promise");
 var SyncPromise = require("devtools/shared/deprecated-sync-thenables");
 
 DevToolsUtils.defineLazyGetter(this, "DebuggerSocket", () => {
   let { DebuggerSocket } = require("devtools/shared/security/socket");
   return DebuggerSocket;
 });
@@ -51,24 +52,24 @@ this.dumpv = dumpv;
 // object usage
 Object.defineProperty(this, "Components", {
   get() {
     return require("chrome").components;
   }
 });
 
 if (isWorker) {
-  dumpn.wantLogging = true;
-  dumpv.wantVerbose = true;
+  flags.wantLogging = true;
+  flags.wantVerbose = true;
 } else {
   const LOG_PREF = "devtools.debugger.log";
   const VERBOSE_PREF = "devtools.debugger.log.verbose";
 
-  dumpn.wantLogging = Services.prefs.getBoolPref(LOG_PREF);
-  dumpv.wantVerbose =
+  flags.wantLogging = Services.prefs.getBoolPref(LOG_PREF);
+  flags.wantVerbose =
     Services.prefs.getPrefType(VERBOSE_PREF) !== Services.prefs.PREF_INVALID &&
     Services.prefs.getBoolPref(VERBOSE_PREF);
 }
 
 function loadSubScript(url) {
   try {
     let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
                    .getService(Ci.mozIJSSubScriptLoader);
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -32,10 +32,11 @@ DevToolsModules(
     'child.js',
     'content-globals.js',
     'content-server.jsm',
     'css-logic.js',
     'event-parsers.js',
     'main.js',
     'primitive.js',
     'service-worker-child.js',
+    'websocket-server.js',
     'worker.js'
 )
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -108,8 +108,10 @@ skip-if = buildapp == 'mulet'
 [test_setupInParentChild.html]
 [test_styles-applied.html]
 [test_styles-computed.html]
 [test_styles-layout.html]
 [test_styles-matched.html]
 [test_styles-modify.html]
 [test_styles-svg.html]
 [test_unsafeDereference.html]
+[test_websocket-server.html]
+skip-if = false
--- a/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
+++ b/devtools/server/tests/mochitest/test_inspector_getImageData-wait-for-load.html
@@ -13,19 +13,19 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug 1192536</title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const wasTesting = DevToolsUtils.testing;
-SimpleTest.registerCleanupFunction(() => DevToolsUtils.testing = wasTesting);
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
 
 const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
 const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
 const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
 const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
 const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
 
 window.onload = function() {
@@ -54,42 +54,42 @@ addTest(function setup() {
       });
     }).then(runNextTest));
   });
 });
 
 addTest(function testTimeout() {
   info("Testing that the method aborts if the image takes too long to load.");
 
-  // imageToImageData() only times out when DTU.testing is not set.
-  DevToolsUtils.testing = false;
+  // imageToImageData() only times out when flags.testing is not set.
+  flags.testing = false;
 
   gImg.src = TIMEOUT_IMAGE;
 
   info("Calling getImageData().");
   ensureRejects(gNodeFront.getImageData(), "Timeout image").then(runNextTest);
 });
 
 addTest(function testNonExistentImage() {
   info("Testing that non-existent image causes a rejection.");
 
   // This test shouldn't hit the timeout.
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   gImg.src = NONEXISTENT_IMAGE;
 
   info("Calling getImageData().");
   ensureRejects(gNodeFront.getImageData(), "Non-existent image").then(runNextTest);
 });
 
 addTest(function testDelayedImage() {
   info("Testing that the method waits for an image to load.");
 
   // This test shouldn't hit the timeout.
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   gImg.src = DELAYED_IMAGE;
 
   info("Calling getImageData().");
   checkImageData(gNodeFront.getImageData()).then(runNextTest);
 });
 
 addTest(function cleanup() {
--- a/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
+++ b/devtools/server/tests/mochitest/test_inspector_getImageDataFromURL.html
@@ -12,19 +12,19 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug 1192536</title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const wasTesting = DevToolsUtils.testing;
-SimpleTest.registerCleanupFunction(() => DevToolsUtils.testing = wasTesting);
+const flags = require("devtools/shared/flags");
+const wasTesting = flags.testing;
+SimpleTest.registerCleanupFunction(() => flags.testing = wasTesting);
 
 const PATH = "http://mochi.test:8888/chrome/devtools/server/tests/mochitest/";
 const BASE_IMAGE = PATH + "inspector-delay-image-response.sjs";
 const DELAYED_IMAGE = BASE_IMAGE + "?delay=300";
 const TIMEOUT_IMAGE = BASE_IMAGE + "?delay=50000";
 const NONEXISTENT_IMAGE = PATH + "this-does-not-exist.png";
 
 window.onload = function() {
@@ -41,38 +41,38 @@ addTest(function setup() {
     gInspector = InspectorFront(client, tab);
     runNextTest();
   });
 });
 
 addTest(function testTimeout() {
   info("Testing that the method aborts if the image takes too long to load.");
 
-  // imageToImageData() only times out when DTU.testing is not set.
-  DevToolsUtils.testing = false;
+  // imageToImageData() only times out when flags.testing is not set.
+  flags.testing = false;
 
   ensureRejects(gInspector.getImageDataFromURL(TIMEOUT_IMAGE),
     "Image that loads for too long").then(runNextTest);
 });
 
 addTest(function testNonExistentImage() {
   info("Testing that non-existent image causes a rejection.");
 
   // This test shouldn't hit the timeout.
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   ensureRejects(gInspector.getImageDataFromURL(NONEXISTENT_IMAGE),
     "Non-existent image").then(runNextTest);
 });
 
 addTest(function testNormalImage() {
   info("Testing that the method waits for an image to load.");
 
   // This test shouldn't hit the timeout.
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   checkImageData(gInspector.getImageDataFromURL(DELAYED_IMAGE)).then(runNextTest);
 });
 
 addTest(function cleanup() {
   delete gInspector;
   runNextTest();
 });
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_websocket-server.html
@@ -0,0 +1,82 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Mozilla Bug</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+window.onload = function() {
+  const { Constructor: CC, utils: Cu } = Components;
+  const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+  const { Task } = require("devtools/shared/task");
+  const WebSocketServer = require("devtools/server/websocket-server");
+
+  const ServerSocket = CC("@mozilla.org/network/server-socket;1",
+    "nsIServerSocket", "init");
+
+  add_task(function* () {
+    // Create a TCP server on auto-assigned port
+    let server = new ServerSocket(-1, true, -1);
+    ok(server, `Launched WebSocket server on port ${server.port}`);
+    server.asyncListen({
+      onSocketAccepted: Task.async(function* (socket, transport) {
+        info("Accepted incoming connection");
+        let input = transport.openInputStream(0, 0, 0);
+        let output = transport.openOutputStream(0, 0, 0);
+
+        // Perform the WebSocket handshake
+        let webSocket = yield WebSocketServer.accept(transport, input, output);
+
+        // Echo the received message back to the sender
+        webSocket.onmessage = ({ data }) => {
+          info("Server received message, echoing back");
+          webSocket.send(data);
+        };
+      }),
+
+      onStopListening(socket, status) {
+        info(`Server stopped listening with status: ${status}`);
+      }
+    });
+
+    SimpleTest.registerCleanupFunction(() => {
+      server.close();
+    });
+
+    // Create client connection
+    let client = yield new Promise((resolve, reject) => {
+      let socket = new WebSocket(`ws://localhost:${server.port}`);
+      socket.onopen = () => resolve(socket);
+      socket.onerror = reject;
+    });
+    ok(client, `Created WebSocket connection to port ${server.port}`);
+
+    // Create a promise that resolves when the WebSocket closes
+    let closed = new Promise(resolve => {
+      client.onclose = resolve;
+    });
+
+    // Send a message
+    let message = "hello there";
+    client.send(message);
+    info("Sent a message to server");
+    // Check that it was echoed
+    let echoedMessage = yield new Promise((resolve, reject) => {
+      client.onmessage = ({ data }) => resolve(data);
+      client.onerror = reject;
+    });
+
+    is(echoedMessage, message, "Echoed message matches");
+
+    // Close the connection
+    client.close();
+    yield closed;
+  });
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/devtools/server/websocket-server.js
@@ -0,0 +1,221 @@
+/* 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 { Cc, CC } = require("chrome");
+const Promise = require("promise");
+const { Task } = require("devtools/shared/task");
+const { executeSoon } = require("devtools/shared/DevToolsUtils");
+const { delimitedRead } = require("devtools/shared/transport/stream-utils");
+const CryptoHash = CC("@mozilla.org/security/hash;1", "nsICryptoHash", "initWithString");
+const threadManager = Cc["@mozilla.org/thread-manager;1"].getService();
+
+// Limit the header size to put an upper bound on allocated memory
+const HEADER_MAX_LEN = 8000;
+
+/**
+ * Read a line from async input stream and return promise that resolves to the line once
+ * it has been read. If the line is longer than HEADER_MAX_LEN, will throw error.
+ */
+function readLine(input) {
+  return new Promise((resolve, reject) => {
+    let line = "";
+    let wait = () => {
+      input.asyncWait(stream => {
+        try {
+          let amountToRead = HEADER_MAX_LEN - line.length;
+          line += delimitedRead(input, "\n", amountToRead);
+
+          if (line.endsWith("\n")) {
+            resolve(line.trimRight());
+            return;
+          }
+
+          if (line.length >= HEADER_MAX_LEN) {
+            throw new Error(
+              `Failed to read HTTP header longer than ${HEADER_MAX_LEN} bytes`);
+          }
+
+          wait();
+        } catch (ex) {
+          reject(ex);
+        }
+      }, 0, 0, threadManager.currentThread);
+    };
+
+    wait();
+  });
+}
+
+/**
+ * Write a string of bytes to async output stream and return promise that resolves once
+ * all data has been written. Doesn't do any utf-16/utf-8 conversion - the string is
+ * treated as an array of bytes.
+ */
+function writeString(output, data) {
+  return new Promise((resolve, reject) => {
+    let wait = () => {
+      if (data.length === 0) {
+        resolve();
+        return;
+      }
+
+      output.asyncWait(stream => {
+        try {
+          let written = output.write(data, data.length);
+          data = data.slice(written);
+          wait();
+        } catch (ex) {
+          reject(ex);
+        }
+      }, 0, 0, threadManager.currentThread);
+    };
+
+    wait();
+  });
+}
+
+/**
+ * Read HTTP request from async input stream.
+ * @return Request line (string) and Map of header names and values.
+ */
+const readHttpRequest = Task.async(function* (input) {
+  let requestLine = "";
+  let headers = new Map();
+
+  while (true) {
+    let line = yield readLine(input);
+    if (line.length == 0) {
+      break;
+    }
+
+    if (!requestLine) {
+      requestLine = line;
+    } else {
+      let colon = line.indexOf(":");
+      if (colon == -1) {
+        throw new Error(`Malformed HTTP header: ${line}`);
+      }
+
+      let name = line.slice(0, colon).toLowerCase();
+      let value = line.slice(colon + 1).trim();
+      headers.set(name, value);
+    }
+  }
+
+  return { requestLine, headers };
+});
+
+/**
+ * Write HTTP response (array of strings) to async output stream.
+ */
+function writeHttpResponse(output, response) {
+  let responseString = response.join("\r\n") + "\r\n\r\n";
+  return writeString(output, responseString);
+}
+
+/**
+ * Process the WebSocket handshake headers and return the key to be sent in
+ * Sec-WebSocket-Accept response header.
+ */
+function processRequest({ requestLine, headers }) {
+  let [ method, path ] = requestLine.split(" ");
+  if (method !== "GET") {
+    throw new Error("The handshake request must use GET method");
+  }
+
+  if (path !== "/") {
+    throw new Error("The handshake request has unknown path");
+  }
+
+  let upgrade = headers.get("upgrade");
+  if (!upgrade || upgrade !== "websocket") {
+    throw new Error("The handshake request has incorrect Upgrade header");
+  }
+
+  let connection = headers.get("connection");
+  if (!connection || !connection.split(",").map(t => t.trim()).includes("Upgrade")) {
+    throw new Error("The handshake request has incorrect Connection header");
+  }
+
+  let version = headers.get("sec-websocket-version");
+  if (!version || version !== "13") {
+    throw new Error("The handshake request must have Sec-WebSocket-Version: 13");
+  }
+
+  // Compute the accept key
+  let key = headers.get("sec-websocket-key");
+  if (!key) {
+    throw new Error("The handshake request must have a Sec-WebSocket-Key header");
+  }
+
+  return { acceptKey: computeKey(key) };
+}
+
+function computeKey(key) {
+  let str = key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
+
+  let data = Array.from(str, ch => ch.charCodeAt(0));
+  let hash = new CryptoHash("sha1");
+  hash.update(data, data.length);
+  return hash.finish(true);
+}
+
+/**
+ * Perform the server part of a WebSocket opening handshake on an incoming connection.
+ */
+const serverHandshake = Task.async(function* (input, output) {
+  // Read the request
+  let request = yield readHttpRequest(input);
+
+  try {
+    // Check and extract info from the request
+    let { acceptKey } = processRequest(request);
+
+    // Send response headers
+    yield writeHttpResponse(output, [
+      "HTTP/1.1 101 Switching Protocols",
+      "Upgrade: websocket",
+      "Connection: Upgrade",
+      `Sec-WebSocket-Accept: ${acceptKey}`,
+    ]);
+  } catch (error) {
+    // Send error response in case of error
+    yield writeHttpResponse(output, [ "HTTP/1.1 400 Bad Request" ]);
+    throw error;
+  }
+});
+
+/**
+ * Accept an incoming WebSocket server connection.
+ * Takes an established nsISocketTransport in the parameters.
+ * Performs the WebSocket handshake and waits for the WebSocket to open.
+ * Returns Promise with a WebSocket ready to send and receive messages.
+ */
+const accept = Task.async(function* (transport, input, output) {
+  yield serverHandshake(input, output);
+
+  let transportProvider = {
+    setListener(upgradeListener) {
+      // The onTransportAvailable callback shouldn't be called synchronously.
+      executeSoon(() => {
+        upgradeListener.onTransportAvailable(transport, input, output);
+      });
+    }
+  };
+
+  return new Promise((resolve, reject) => {
+    let socket = WebSocket.createServerWebSocket(null, [], transportProvider, "");
+    socket.addEventListener("close", () => {
+      input.close();
+      output.close();
+    });
+
+    socket.onopen = () => resolve(socket);
+    socket.onerror = err => reject(err);
+  });
+});
+
+exports.accept = accept;
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -5,16 +5,17 @@
 "use strict";
 
 /* General utilities used throughout devtools. */
 
 var { Ci, Cu, Cc, components } = require("chrome");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
+var flags = require("./flags");
 
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
 
 // Re-export the thread-safe utils.
 const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js");
 for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
   exports[key] = ThreadSafeDevToolsUtils[key];
@@ -25,17 +26,17 @@ for (let key of Object.keys(ThreadSafeDe
  */
 exports.executeSoon = function executeSoon(aFn) {
   if (isWorker) {
     setImmediate(aFn);
   } else {
     let executor;
     // Only enable async stack reporting when DEBUG_JS_MODULES is set
     // (customized local builds) to avoid a performance penalty.
-    if (AppConstants.DEBUG_JS_MODULES || exports.testing) {
+    if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
       let stack = components.stack;
       executor = () => {
         Cu.callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
       };
     } else {
       executor = aFn;
     }
     Services.tm.mainThread.dispatch({
@@ -229,38 +230,30 @@ exports.isSafeJSObject = function isSafe
   if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
     return true; // allow chrome objects
   }
 
   return Cu.isXrayWrapper(aObj);
 };
 
 exports.dumpn = function dumpn(str) {
-  if (exports.dumpn.wantLogging) {
+  if (flags.wantLogging) {
     dump("DBG-SERVER: " + str + "\n");
   }
 };
 
-// We want wantLogging to be writable. The exports object is frozen by the
-// loader, so define it on dumpn instead.
-exports.dumpn.wantLogging = false;
-
 /**
  * A verbose logger for low-level tracing.
  */
 exports.dumpv = function (msg) {
-  if (exports.dumpv.wantVerbose) {
+  if (flags.wantVerbose) {
     exports.dumpn(msg);
   }
 };
 
-// We want wantLogging to be writable. The exports object is frozen by the
-// loader, so define it on dumpn instead.
-exports.dumpv.wantVerbose = false;
-
 /**
  * Defines a getter on a specified object that will be created upon first use.
  *
  * @param aObject
  *        The object to define the lazy getter on.
  * @param aName
  *        The name of the getter to define on aObject.
  * @param aLambda
@@ -313,25 +306,25 @@ function reallyAssert(condition, message
  * DevToolsUtils.assert(condition, message)
  *
  * @param Boolean condition
  * @param String message
  *
  * Assertions are enabled when any of the following are true:
  *   - This is a DEBUG_JS_MODULES build
  *   - This is a DEBUG build
- *   - DevToolsUtils.testing is set to true
+ *   - flags.testing is set to true
  *
  * If assertions are enabled, then `condition` is checked and if false-y, the
  * assertion failure is logged and then an error is thrown.
  *
  * If assertions are not enabled, then this function is a no-op.
  */
 Object.defineProperty(exports, "assert", {
-  get: () => (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES || this.testing)
+  get: () => (AppConstants.DEBUG || AppConstants.DEBUG_JS_MODULES || flags.testing)
     ? reallyAssert
     : exports.noop,
 });
 
 /**
  * Defines a getter on a specified object for a module.  The module will not
  * be imported until first use.
  *
@@ -571,103 +564,16 @@ if (!this.isWorker) {
   // to fetch a URL. We need to enlist the help from the main thread here, by
   // issuing an rpc request, to fetch the URL on our behalf.
   exports.fetch = function (url, options) {
     return rpc("fetch", url, options);
   };
 }
 
 /**
- * Returns a promise that is resolved or rejected when all promises have settled
- * (resolved or rejected).
- *
- * This differs from Promise.all, which will reject immediately after the first
- * rejection, instead of waiting for the remaining promises to settle.
- *
- * @param values
- *        Iterable of promises that may be pending, resolved, or rejected. When
- *        when all promises have settled (resolved or rejected), the returned
- *        promise will be resolved or rejected as well.
- *
- * @return A new promise that is fulfilled when all values have settled
- *         (resolved or rejected). Its resolution value will be an array of all
- *         resolved values in the given order, or undefined if values is an
- *         empty array. The reject reason will be forwarded from the first
- *         promise in the list of given promises to be rejected.
- */
-exports.settleAll = values => {
-  if (values === null || typeof (values[Symbol.iterator]) != "function") {
-    throw new Error("settleAll() expects an iterable.");
-  }
-
-  let deferred = defer();
-
-  values = Array.isArray(values) ? values : [...values];
-  let countdown = values.length;
-  let resolutionValues = new Array(countdown);
-  let rejectionValue;
-  let rejectionOccurred = false;
-
-  if (!countdown) {
-    deferred.resolve(resolutionValues);
-    return deferred.promise;
-  }
-
-  function checkForCompletion() {
-    if (--countdown > 0) {
-      return;
-    }
-    if (!rejectionOccurred) {
-      deferred.resolve(resolutionValues);
-    } else {
-      deferred.reject(rejectionValue);
-    }
-  }
-
-  for (let i = 0; i < values.length; i++) {
-    let index = i;
-    let value = values[i];
-    let resolver = result => {
-      resolutionValues[index] = result;
-      checkForCompletion();
-    };
-    let rejecter = error => {
-      if (!rejectionOccurred) {
-        rejectionValue = error;
-        rejectionOccurred = true;
-      }
-      checkForCompletion();
-    };
-
-    if (value && typeof (value.then) == "function") {
-      value.then(resolver, rejecter);
-    } else {
-      // Given value is not a promise, forward it as a resolution value.
-      resolver(value);
-    }
-  }
-
-  return deferred.promise;
-};
-
-/**
- * When the testing flag is set, various behaviors may be altered from
- * production mode, typically to enable easier testing or enhanced debugging.
- */
-var testing = false;
-Object.defineProperty(exports, "testing", {
-  get: function () {
-    return testing;
-  },
-  set: function (state) {
-    testing = state;
-  }
-});
-
-/**
  * Open the file at the given path for reading.
  *
  * @param {String} filePath
  *
  * @returns Promise<nsIInputStream>
  */
 exports.openFileStream = function (filePath) {
   return new Promise((resolve, reject) => {
@@ -680,8 +586,34 @@ exports.openFileStream = function (fileP
           return;
         }
 
         resolve(stream);
       }
     );
   });
 };
+
+/*
+ * All of the flags have been moved to a different module. Make sure
+ * nobody is accessing them anymore, and don't write new code using
+ * them. We can remove this code after a while.
+ */
+function errorOnFlag(exports, name) {
+  Object.defineProperty(exports, name, {
+    get: () => {
+      const msg = `Cannot get the flag ${name}. ` +
+            `Use the "devtools/shared/flags" module instead`;
+      console.error(msg);
+      throw new Error(msg);
+    },
+    set: () => {
+      const msg = `Cannot set the flag ${name}. ` +
+            `Use the "devtools/shared/flags" module instead`;
+      console.error(msg);
+      throw new Error(msg);
+    }
+  });
+}
+
+errorOnFlag(exports, "testing");
+errorOnFlag(exports, "wantLogging");
+errorOnFlag(exports, "wantVerbose");
--- a/devtools/shared/ThreadSafeDevToolsUtils.js
+++ b/devtools/shared/ThreadSafeDevToolsUtils.js
@@ -256,8 +256,79 @@ exports.isSet = function (thing) {
  * recursively flatten all levels.
  *
  * @param {Array<Array<Any>>} lists
  * @return {Array<Any>}
  */
 exports.flatten = function (lists) {
   return Array.prototype.concat.apply([], lists);
 };
+
+/**
+ * Returns a promise that is resolved or rejected when all promises have settled
+ * (resolved or rejected).
+ *
+ * This differs from Promise.all, which will reject immediately after the first
+ * rejection, instead of waiting for the remaining promises to settle.
+ *
+ * @param values
+ *        Iterable of promises that may be pending, resolved, or rejected. When
+ *        when all promises have settled (resolved or rejected), the returned
+ *        promise will be resolved or rejected as well.
+ *
+ * @return A new promise that is fulfilled when all values have settled
+ *         (resolved or rejected). Its resolution value will be an array of all
+ *         resolved values in the given order, or undefined if values is an
+ *         empty array. The reject reason will be forwarded from the first
+ *         promise in the list of given promises to be rejected.
+ */
+exports.settleAll = values => {
+  if (values === null || typeof (values[Symbol.iterator]) != "function") {
+    throw new Error("settleAll() expects an iterable.");
+  }
+
+  return new Promise((resolve, reject) => {
+    values = Array.isArray(values) ? values : [...values];
+    let countdown = values.length;
+    let resolutionValues = new Array(countdown);
+    let rejectionValue;
+    let rejectionOccurred = false;
+
+    if (!countdown) {
+      resolve(resolutionValues);
+      return deferred.promise;
+    }
+
+    function checkForCompletion() {
+      if (--countdown > 0) {
+        return;
+      }
+      if (!rejectionOccurred) {
+        resolve(resolutionValues);
+      } else {
+        reject(rejectionValue);
+      }
+    }
+
+    for (let i = 0; i < values.length; i++) {
+      let index = i;
+      let value = values[i];
+      let resolver = result => {
+        resolutionValues[index] = result;
+        checkForCompletion();
+      };
+      let rejecter = error => {
+        if (!rejectionOccurred) {
+          rejectionValue = error;
+          rejectionOccurred = true;
+        }
+        checkForCompletion();
+      };
+
+      if (value && typeof (value.then) == "function") {
+        value.then(resolver, rejecter);
+      } else {
+        // Given value is not a promise, forward it as a resolution value.
+        resolver(value);
+      }
+    }
+  });
+};
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -287,8 +287,11 @@ defineLazyGetter(globals, "DOMParser", (
   return CC("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
 });
 defineLazyGetter(globals, "CSS", () => {
   let sandbox
     = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
                  {wantGlobalProperties: ["CSS"]});
   return sandbox.CSS;
 });
+defineLazyGetter(globals, "WebSocket", () => {
+  return Services.appShell.hiddenDOMWindow.WebSocket;
+});
new file mode 100644
--- /dev/null
+++ b/devtools/shared/flags.js
@@ -0,0 +1,21 @@
+
+/*
+ * Create a writable property by tracking it with a private variable.
+ * We cannot make a normal property writeable on `exports` because
+ * the module system freezes it.
+ */
+function makeWritableFlag(exports, name) {
+  let flag = false;
+  Object.defineProperty(exports, name, {
+    get: function () { return flag; },
+    set: function (state) { flag = state; }
+  });
+}
+
+makeWritableFlag(exports, "wantLogging");
+makeWritableFlag(exports, "wantVerbose");
+
+// When the testing flag is set, various behaviors may be altered from
+// production mode, typically to enable easier testing or enhanced
+// debugging.
+makeWritableFlag(exports, "testing");
--- a/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
+++ b/devtools/shared/heapsnapshot/tests/unit/head_heapsnapshot.js
@@ -12,32 +12,33 @@ var CC = Components.Constructor;
 const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const { Match } = Cu.import("resource://test/Match.jsm", {});
 const { Census } = Cu.import("resource://test/Census.jsm", {});
 const { addDebuggerToGlobal } =
   Cu.import("resource://gre/modules/jsdebugger.jsm", {});
 const { Task } = require("devtools/shared/task");
 
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 const HeapAnalysesClient =
   require("devtools/shared/heapsnapshot/HeapAnalysesClient");
 const Services = require("Services");
 const { censusReportToCensusTreeNode } = require("devtools/shared/heapsnapshot/census-tree-node");
 const CensusUtils = require("devtools/shared/heapsnapshot/CensusUtils");
 const DominatorTreeNode = require("devtools/shared/heapsnapshot/DominatorTreeNode");
 const { deduplicatePaths } = require("devtools/shared/heapsnapshot/shortest-paths");
 const { LabelAndShallowSizeVisitor } = DominatorTreeNode;
 
 
 // Always log packets when running tests. runxpcshelltests.py will throw
 // the output away anyway, unless you give it the --verbose flag.
 if (Services.appinfo.processType == Services.appinfo.PROCESS_TYPE_DEFAULT) {
   Services.prefs.setBoolPref("devtools.debugger.log", true);
 }
-DevToolsUtils.dumpn.wantLogging = true;
+flags.wantLogging = true;
 
 const SYSTEM_PRINCIPAL = Cc["@mozilla.org/systemprincipal;1"]
   .createInstance(Ci.nsIPrincipal);
 
 function dumpn(msg) {
   dump("HEAPSNAPSHOT-TEST: " + msg + "\n");
 }
 
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -48,16 +48,17 @@ DevToolsModules(
     'css-parsing-utils.js',
     'css-properties-db.js',
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
     'event-emitter.js',
+    'flags.js',
     'indentation.js',
     'loader-plugin-raw.jsm',
     'Loader.jsm',
     'Parser.jsm',
     'path.js',
     'protocol.js',
     'system.js',
     'task.js',
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1,16 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 var { Cu, components } = require("chrome");
-var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var {Class} = require("sdk/core/heritage");
 var {EventTarget} = require("sdk/event/target");
 var events = require("sdk/event/core");
 var object = require("sdk/util/object");
 
@@ -1198,17 +1197,17 @@ var Front = Class({
    */
   send: function (packet) {
     if (packet.to) {
       this.conn._transport.send(packet);
     } else {
       this.actor().then(actorID => {
         packet.to = actorID;
         this.conn._transport.send(packet);
-      }).then(null, e => DevToolsUtils.reportException("Front.prototype.send", e));
+      }).then(null, e => console.error(e));
     }
   },
 
   /**
    * Send a two-way request on the connection.
    */
   request: function (packet) {
     let deferred = defer();
--- a/devtools/shared/security/auth.js
+++ b/devtools/shared/security/auth.js
@@ -93,16 +93,23 @@ var Prompt = Authenticators.Prompt = {};
 Prompt.mode = "PROMPT";
 
 Prompt.Client = function () {};
 Prompt.Client.prototype = {
 
   mode: Prompt.mode,
 
   /**
+   * When client is about to make a new connection, verify that the connection settings
+   * are compatible with this authenticator.
+   * @throws if validation requirements are not met
+   */
+  validateSettings() {},
+
+  /**
    * When client has just made a new socket connection, validate the connection
    * to ensure it meets the authenticator's policies.
    *
    * @param host string
    *        The host name or IP address of the debugger server.
    * @param port number
    *        The port number of the debugger server.
    * @param encryption boolean (optional)
@@ -257,16 +264,27 @@ var OOBCert = Authenticators.OOBCert = {
 OOBCert.mode = "OOB_CERT";
 
 OOBCert.Client = function () {};
 OOBCert.Client.prototype = {
 
   mode: OOBCert.mode,
 
   /**
+   * When client is about to make a new connection, verify that the connection settings
+   * are compatible with this authenticator.
+   * @throws if validation requirements are not met
+   */
+  validateSettings({ encryption }) {
+    if (!encryption) {
+      throw new Error(`${OOBCert.mode} authentication requires encryption.`);
+    }
+  },
+
+  /**
    * When client has just made a new socket connection, validate the connection
    * to ensure it meets the authenticator's policies.
    *
    * @param host string
    *        The host name or IP address of the debugger server.
    * @param port number
    *        The port number of the debugger server.
    * @param encryption boolean (optional)
--- a/devtools/shared/security/moz.build
+++ b/devtools/shared/security/moz.build
@@ -1,14 +1,15 @@
 # -*- Mode: python; 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/.
 
+MOCHITEST_CHROME_MANIFESTS += ['tests/chrome/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 DevToolsModules(
     'auth.js',
     'cert.js',
     'prompt.js',
     'socket.js',
 )
--- a/devtools/shared/security/socket.js
+++ b/devtools/shared/security/socket.js
@@ -11,18 +11,22 @@ var { Ci, Cc, CC, Cr, Cu } = require("ch
 // Ensure PSM is initialized to support TLS sockets
 Cc["@mozilla.org/psm;1"].getService(Ci.nsISupports);
 
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var DevToolsUtils = require("devtools/shared/DevToolsUtils");
 var { dumpn, dumpv } = DevToolsUtils;
+loader.lazyRequireGetter(this, "WebSocketServer",
+  "devtools/server/websocket-server");
 loader.lazyRequireGetter(this, "DebuggerTransport",
   "devtools/shared/transport/transport", true);
+loader.lazyRequireGetter(this, "WebSocketDebuggerTransport",
+  "devtools/shared/transport/websocket-transport");
 loader.lazyRequireGetter(this, "DebuggerServer",
   "devtools/server/main", true);
 loader.lazyRequireGetter(this, "discovery",
   "devtools/shared/discovery/discovery");
 loader.lazyRequireGetter(this, "cert",
   "devtools/shared/security/cert");
 loader.lazyRequireGetter(this, "Authenticators",
   "devtools/shared/security/auth", true);
@@ -56,66 +60,91 @@ var DebuggerSocket = {};
  * Connects to a debugger server socket.
  *
  * @param host string
  *        The host name or IP address of the debugger server.
  * @param port number
  *        The port number of the debugger server.
  * @param encryption boolean (optional)
  *        Whether the server requires encryption.  Defaults to false.
+ * @param webSocket boolean (optional)
+ *        Whether to use WebSocket protocol to connect. Defaults to false.
  * @param authenticator Authenticator (optional)
  *        |Authenticator| instance matching the mode in use by the server.
  *        Defaults to a PROMPT instance if not supplied.
  * @param cert object (optional)
  *        The server's cert details.  Used with OOB_CERT authentication.
  * @return promise
  *         Resolved to a DebuggerTransport instance.
  */
 DebuggerSocket.connect = Task.async(function* (settings) {
   // Default to PROMPT |Authenticator| instance if not supplied
   if (!settings.authenticator) {
     settings.authenticator = new (Authenticators.get().Client)();
   }
+  _validateSettings(settings);
   let { host, port, encryption, authenticator, cert } = settings;
   let transport = yield _getTransport(settings);
   yield authenticator.authenticate({
     host,
     port,
     encryption,
     cert,
     transport
   });
   return transport;
 });
 
 /**
+ * Validate that the connection settings have been set to a supported configuration.
+ */
+function _validateSettings(settings) {
+  let { encryption, webSocket, authenticator } = settings;
+
+  if (webSocket && encryption) {
+    throw new Error("Encryption not supported on WebSocket transport");
+  }
+  authenticator.validateSettings(settings);
+}
+
+/**
  * Try very hard to create a DevTools transport, potentially making several
  * connect attempts in the process.
  *
  * @param host string
  *        The host name or IP address of the debugger server.
  * @param port number
  *        The port number of the debugger server.
  * @param encryption boolean (optional)
  *        Whether the server requires encryption.  Defaults to false.
+ * @param webSocket boolean (optional)
+ *        Whether to use WebSocket protocol to connect to the server. Defaults to false.
  * @param authenticator Authenticator
  *        |Authenticator| instance matching the mode in use by the server.
  *        Defaults to a PROMPT instance if not supplied.
  * @param cert object (optional)
  *        The server's cert details.  Used with OOB_CERT authentication.
  * @return transport DebuggerTransport
  *         A possible DevTools transport (if connection succeeded and streams
  *         are actually alive and working)
- * @return certError boolean
- *         Flag noting if cert trouble caused the streams to fail
- * @return s nsISocketTransport
- *         Underlying socket transport, in case more details are needed.
  */
 var _getTransport = Task.async(function* (settings) {
-  let { host, port, encryption } = settings;
+  let { host, port, encryption, webSocket } = settings;
+
+  if (webSocket) {
+    // Establish a connection and wait until the WebSocket is ready to send and receive
+    let socket = yield new Promise((resolve, reject) => {
+      let s = new WebSocket(`ws://${host}:${port}`);
+      s.onopen = () => resolve(s);
+      s.onerror = err => reject(err);
+    });
+
+    return new WebSocketDebuggerTransport(socket);
+  }
+
   let attempt = yield _attemptTransport(settings);
   if (attempt.transport) {
     return attempt.transport; // Success
   }
 
   // If the server cert failed validation, store a temporary override and make
   // a second attempt.
   if (encryption && attempt.certError) {
@@ -373,16 +402,19 @@ SocketListener.prototype = {
    */
   _validateOptions: function () {
     if (this.portOrPath === null) {
       throw new Error("Must set a port / path to listen on.");
     }
     if (this.discoverable && !Number(this.portOrPath)) {
       throw new Error("Discovery only supported for TCP sockets.");
     }
+    if (this.encryption && this.webSocket) {
+      throw new Error("Encryption not supported on WebSocket transport");
+    }
     this.authenticator.validateOptions(this);
   },
 
   /**
    * Listens on the given port or socket file for remote debugger connections.
    */
   open: function () {
     this._validateOptions();
@@ -591,39 +623,46 @@ ServerSocketConnection.prototype = {
    * the connection is denied.  If the entire process resolves successfully,
    * the connection is finally handed off to the |DebuggerServer|.
    */
   _handle() {
     dumpn("Debugging connection starting authentication on " + this.address);
     let self = this;
     Task.spawn(function* () {
       self._listenForTLSHandshake();
-      self._createTransport();
+      yield self._createTransport();
       yield self._awaitTLSHandshake();
       yield self._authenticate();
     }).then(() => this.allow()).catch(e => this.deny(e));
   },
 
   /**
    * We need to open the streams early on, as that is required in the case of
    * TLS sockets to keep the handshake moving.
    */
-  _createTransport() {
+  _createTransport: Task.async(function* () {
     let input = this._socketTransport.openInputStream(0, 0, 0);
     let output = this._socketTransport.openOutputStream(0, 0, 0);
-    this._transport = new DebuggerTransport(input, output);
+
+    if (this._listener.webSocket) {
+      let socket = yield WebSocketServer.accept(this._socketTransport, input, output);
+      this._transport = new WebSocketDebuggerTransport(socket);
+    } else {
+      this._transport = new DebuggerTransport(input, output);
+    }
+
     // Start up the transport to observe the streams in case they are closed
     // early.  This allows us to clean up our state as well.
     this._transport.hooks = {
       onClosed: reason => {
         this.deny(reason);
       }
     };
     this._transport.ready();
-  },
+  }),
 
   /**
    * Set the socket's security observer, which receives an event via the
    * |onHandshakeDone| callback when the TLS handshake completes.
    */
   _setSecurityObserver(observer) {
     if (!this._socketTransport || !this._socketTransport.securityInfo) {
       return;
@@ -712,18 +751,20 @@ ServerSocketConnection.prototype = {
     for (let name in Cr) {
       if (Cr[name] === result) {
         errorName = name;
         break;
       }
     }
     dumpn("Debugging connection denied on " + this.address +
           " (" + errorName + ")");
-    this._transport.hooks = null;
-    this._transport.close(result);
+    if (this._transport) {
+      this._transport.hooks = null;
+      this._transport.close(result);
+    }
     this._socketTransport.close(result);
     this.destroy();
   },
 
   allow() {
     if (this._destroyed) {
       return;
     }
new file mode 100644
--- /dev/null
+++ b/devtools/shared/security/tests/chrome/chrome.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+tags = devtools
+
+[test_websocket-transport.html]
new file mode 100644
--- /dev/null
+++ b/devtools/shared/security/tests/chrome/test_websocket-transport.html
@@ -0,0 +1,76 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test the WebSocket debugger transport</title>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+</head>
+<body>
+<script>
+window.onload = function() {
+  const {require} = Components.utils.import("resource://devtools/shared/Loader.jsm", {});
+  const Services = require("Services");
+  const {DebuggerClient} = require("devtools/shared/client/main");
+  const {DebuggerServer} = require("devtools/server/main");
+
+  Services.prefs.setBoolPref("devtools.debugger.remote-enabled", true);
+  Services.prefs.setBoolPref("devtools.debugger.prompt-connection", false);
+
+  SimpleTest.registerCleanupFunction(() => {
+    Services.prefs.clearUserPref("devtools.debugger.remote-enabled");
+    Services.prefs.clearUserPref("devtools.debugger.prompt-connection");
+  });
+
+  add_task(function* () {
+    if (!DebuggerServer.initialized) {
+      DebuggerServer.init();
+      DebuggerServer.addBrowserActors();
+    }
+
+    is(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+    let listener = DebuggerServer.createListener();
+    ok(listener, "Socket listener created");
+    listener.portOrPath = -1;
+    listener.webSocket = true;
+    yield listener.open();
+    is(DebuggerServer.listeningSockets, 1, "1 listening socket");
+
+    let transport = yield DebuggerClient.socketConnect({
+      host: "127.0.0.1",
+      port: listener.port,
+      webSocket: true
+    });
+    ok(transport, "Client transport created");
+
+    let client = new DebuggerClient(transport);
+    let onUnexpectedClose = () => {
+      do_throw("Closed unexpectedly");
+    };
+    client.addListener("closed", onUnexpectedClose);
+
+    yield client.connect();
+    yield client.listTabs();
+
+    // Send a message the server that will echo back
+    let message = "message";
+    let reply = yield client.request({
+      to: "root",
+      type: "echo",
+      message
+    });
+    is(reply.message, message, "Echo message matches");
+
+    client.removeListener("closed", onUnexpectedClose);
+    transport.close();
+    listener.close();
+    is(DebuggerServer.listeningSockets, 0, "0 listening sockets");
+
+    DebuggerServer.destroy();
+  });
+}
+</script>
+</body>
+</html>
--- a/devtools/shared/tests/unit/head_devtools.js
+++ b/devtools/shared/tests/unit/head_devtools.js
@@ -4,20 +4,21 @@
 "use strict";
 var Cc = Components.classes;
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cr = Components.results;
 
 const {require, DevToolsLoader, devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const flags = require("devtools/shared/flags");
 
-DevToolsUtils.testing = true;
+flags.testing = true;
 do_register_cleanup(() => {
-  DevToolsUtils.testing = false;
+  flags.testing = false;
 });
 
 // Register a console listener, so console messages don't just disappear
 // into the ether.
 
 // If for whatever reason the test needs to post console errors that aren't
 // failures, set this to true.
 var ALLOW_CONSOLE_ERRORS = false;
--- a/devtools/shared/tests/unit/test_assert.js
+++ b/devtools/shared/tests/unit/test_assert.js
@@ -3,17 +3,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test DevToolsUtils.assert
 
 ALLOW_CONSOLE_ERRORS = true;
 
 function run_test() {
   // Enable assertions.
-  DevToolsUtils.testing = true;
+  flags.testing = true;
 
   const { assert } = DevToolsUtils;
   equal(typeof assert, "function");
 
   try {
     assert(true, "this assertion should not fail");
   } catch (e) {
     // If you catch assertion failures in practice, I will hunt you down. I get
--- a/devtools/shared/transport/moz.build
+++ b/devtools/shared/transport/moz.build
@@ -4,10 +4,11 @@
 # 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/.
 
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 DevToolsModules(
     'packets.js',
     'stream-utils.js',
-    'transport.js'
+    'transport.js',
+    'websocket-transport.js',
 )
--- a/devtools/shared/transport/packets.js
+++ b/devtools/shared/transport/packets.js
@@ -22,16 +22,17 @@
  *     Returns true once the packet is done being read / written
  *   * destroy()
  *     Called to clean up at the end of use
  */
 
 const { Cc, Ci, Cu } = require("chrome");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
+const flags = require("devtools/shared/flags");
 const StreamUtils = require("devtools/shared/transport/stream-utils");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 
 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
   const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   unicodeConverter.charset = "UTF-8";
@@ -170,17 +171,17 @@ JSONPacket.prototype.read = function (st
     dumpn(msg);
     return;
   }
 
   this._transport._onJSONObjectReady(this._object);
 };
 
 JSONPacket.prototype._readData = function (stream, scriptableStream) {
-  if (dumpv.wantVerbose) {
+  if (flags.wantVerbose) {
     dumpv("Reading JSON data: _l: " + this.length + " dL: " +
           this._data.length + " sA: " + stream.available());
   }
   let bytesToRead = Math.min(this.length - this._data.length,
                              stream.available());
   this._data += scriptableStream.readBytes(bytesToRead);
   this._done = this._data.length === this.length;
 };
--- a/devtools/shared/transport/transport.js
+++ b/devtools/shared/transport/transport.js
@@ -20,16 +20,17 @@
     const Cu = Components.utils;
     const { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
     factory.call(this, require, this);
   }
 }).call(this, function (require, exports) {
   const { Cc, Cr, CC } = require("chrome");
   const DevToolsUtils = require("devtools/shared/DevToolsUtils");
   const { dumpn, dumpv } = DevToolsUtils;
+  const flags = require("devtools/shared/flags");
   const StreamUtils = require("devtools/shared/transport/stream-utils");
   const { Packet, JSONPacket, BulkPacket } =
   require("devtools/shared/transport/packets");
   const promise = require("promise");
   const defer = require("devtools/shared/defer");
   const EventEmitter = require("devtools/shared/event-emitter");
 
   DevToolsUtils.defineLazyGetter(this, "Pipe", () => {
@@ -433,22 +434,22 @@
      * PACKET_HEADER_MAX characters.
      * @return boolean
      *         True if we now have a complete header.
      */
     _readHeader: function () {
       let amountToRead = PACKET_HEADER_MAX - this._incomingHeader.length;
       this._incomingHeader +=
       StreamUtils.delimitedRead(this._scriptableInput, ":", amountToRead);
-      if (dumpv.wantVerbose) {
+      if (flags.wantVerbose) {
         dumpv("Header read: " + this._incomingHeader);
       }
 
       if (this._incomingHeader.endsWith(":")) {
-        if (dumpv.wantVerbose) {
+        if (flags.wantVerbose) {
           dumpv("Found packet header successfully: " + this._incomingHeader);
         }
         return true;
       }
 
       if (this._incomingHeader.length >= PACKET_HEADER_MAX) {
         throw new Error("Failed to parse packet header!");
       }
@@ -459,17 +460,17 @@
 
     /**
      * If the incoming packet is done, log it as needed and clear the buffer.
      */
     _flushIncoming: function () {
       if (!this._incoming.done) {
         return;
       }
-      if (dumpn.wantLogging) {
+      if (flags.wantLogging) {
         dumpn("Got: " + this._incoming);
       }
       this._destroyIncoming();
     },
 
     /**
      * Handler triggered by an incoming JSONPacket completing it's |read| method.
      * Delivers the packet to this.hooks.onPacket.
@@ -543,30 +544,30 @@
     /**
      * Transmit a message by directly calling the onPacket handler of the other
      * endpoint.
      */
     send: function (packet) {
       this.emit("send", packet);
 
       let serial = this._serial.count++;
-      if (dumpn.wantLogging) {
+      if (flags.wantLogging) {
         // Check 'from' first, as 'echo' packets have both.
         if (packet.from) {
           dumpn("Packet " + serial + " sent from " + uneval(packet.from));
         } else if (packet.to) {
           dumpn("Packet " + serial + " sent to " + uneval(packet.to));
         }
       }
       this._deepFreeze(packet);
       let other = this.other;
       if (other) {
         DevToolsUtils.executeSoon(DevToolsUtils.makeInfallible(() => {
           // Avoid the cost of JSON.stringify() when logging is disabled.
-          if (dumpn.wantLogging) {
+          if (flags.wantLogging) {
             dumpn("Received packet " + serial + ": " + JSON.stringify(packet, null, 2));
           }
           if (other.hooks) {
             other.emit("onPacket", packet);
             other.hooks.onPacket(packet);
           }
         }, "LocalDebuggerTransport instance's this.other.hooks.onPacket"));
       }
new file mode 100644
--- /dev/null
+++ b/devtools/shared/transport/websocket-transport.js
@@ -0,0 +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/. */
+
+"use strict";
+
+const EventEmitter = require("devtools/shared/event-emitter");
+
+function WebSocketDebuggerTransport(socket) {
+  EventEmitter.decorate(this);
+
+  this.active = false;
+  this.hooks = null;
+  this.socket = socket;
+}
+
+WebSocketDebuggerTransport.prototype = {
+  ready() {
+    if (this.active) {
+      return;
+    }
+
+    this.socket.addEventListener("message", this);
+    this.socket.addEventListener("close", this);
+
+    this.active = true;
+  },
+
+  send(object) {
+    this.emit("send", object);
+    if (this.socket) {
+      this.socket.send(JSON.stringify(object));
+    }
+  },
+
+  startBulkSend() {
+    throw new Error("Bulk send is not supported by WebSocket transport");
+  },
+
+  close() {
+    this.emit("onClosed");
+    this.active = false;
+
+    this.socket.removeEventListener("message", this);
+    this.socket.removeEventListener("close", this);
+    this.socket.close();
+    this.socket = null;
+
+    if (this.hooks) {
+      this.hooks.onClosed();
+      this.hooks = null;
+    }
+  },
+
+  handleEvent(event) {
+    switch (event.type) {
+      case "message":
+        this.onMessage(event);
+        break;
+      case "close":
+        this.close();
+        break;
+    }
+  },
+
+  onMessage({ data }) {
+    if (typeof data !== "string") {
+      throw new Error("Binary messages are not supported by WebSocket transport");
+    }
+
+    let object = JSON.parse(data);
+    this.emit("onPacket", object);
+    if (this.hooks) {
+      this.hooks.onPacket(object);
+    }
+  },
+};
+
+module.exports = WebSocketDebuggerTransport;
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -9,16 +9,18 @@
 const {Cc, Ci, Cm, Cu, Cr, components} = require("chrome");
 const Services = require("Services");
 const { XPCOMUtils } = require("resource://gre/modules/XPCOMUtils.jsm");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
                          "devtools/shared/webconsole/network-helper");
 loader.lazyRequireGetter(this, "DevToolsUtils",
                          "devtools/shared/DevToolsUtils");
+loader.lazyRequireGetter(this, "flags",
+                         "devtools/shared/flags");
 loader.lazyImporter(this, "NetUtil", "resource://gre/modules/NetUtil.jsm");
 loader.lazyServiceGetter(this, "gActivityDistributor",
                          "@mozilla.org/network/http-activity-distributor;1",
                          "nsIHttpActivityDistributor");
 
 // /////////////////////////////////////////////////////////////////////////////
 // Network logging
 // /////////////////////////////////////////////////////////////////////////////
@@ -50,19 +52,19 @@ function matchRequest(channel, filters) 
   // Log everything if no filter is specified
   if (!filters.topFrame && !filters.window && !filters.appId) {
     return true;
   }
 
   // Ignore requests from chrome or add-on code when we are monitoring
   // content.
   // TODO: one particular test (browser_styleeditor_fetch-from-cache.js) needs
-  // the DevToolsUtils.testing check. We will move to a better way to serve
+  // the flags.testing check. We will move to a better way to serve
   // its needs in bug 1167188, where this check should be removed.
-  if (!DevToolsUtils.testing && channel.loadInfo &&
+  if (!flags.testing && channel.loadInfo &&
       channel.loadInfo.loadingDocument === null &&
       channel.loadInfo.loadingPrincipal ===
       Services.scriptSecurityManager.getSystemPrincipal()) {
     return false;
   }
 
   if (filters.window) {
     // Since frames support, this.window may not be the top level content
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -985,16 +985,28 @@ WebSocket::Constructor(const GlobalObjec
                        const nsAString& aUrl,
                        const Sequence<nsString>& aProtocols,
                        ErrorResult& aRv)
 {
   return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, nullptr,
                                       EmptyCString(), aRv);
 }
 
+already_AddRefed<WebSocket>
+WebSocket::CreateServerWebSocket(const GlobalObject& aGlobal,
+                                 const nsAString& aUrl,
+                                 const Sequence<nsString>& aProtocols,
+                                 nsITransportProvider* aTransportProvider,
+                                 const nsAString& aNegotiatedExtensions,
+                                 ErrorResult& aRv)
+{
+  return WebSocket::ConstructorCommon(aGlobal, aUrl, aProtocols, aTransportProvider,
+                                      NS_ConvertUTF16toUTF8(aNegotiatedExtensions), aRv);
+}
+
 namespace {
 
 // This class is used to clear any exception.
 class MOZ_STACK_CLASS ClearException
 {
 public:
   explicit ClearException(JSContext* aCx)
     : mCx(aCx)
--- a/dom/base/WebSocket.h
+++ b/dom/base/WebSocket.h
@@ -77,16 +77,23 @@ public: // WebIDL interface:
                                                  const nsAString& aProtocol,
                                                  ErrorResult& rv);
 
   static already_AddRefed<WebSocket> Constructor(const GlobalObject& aGlobal,
                                                  const nsAString& aUrl,
                                                  const Sequence<nsString>& aProtocols,
                                                  ErrorResult& rv);
 
+  static already_AddRefed<WebSocket> CreateServerWebSocket(const GlobalObject& aGlobal,
+                                                           const nsAString& aUrl,
+                                                           const Sequence<nsString>& aProtocols,
+                                                           nsITransportProvider* aTransportProvider,
+                                                           const nsAString& aNegotiatedExtensions,
+                                                           ErrorResult& rv);
+
   static already_AddRefed<WebSocket> ConstructorCommon(const GlobalObject& aGlobal,
                                                        const nsAString& aUrl,
                                                        const Sequence<nsString>& aProtocols,
                                                        nsITransportProvider* aTransportProvider,
                                                        const nsACString& aNegotiatedExtensions,
                                                        ErrorResult& rv);
 
   // webIDL: readonly attribute DOMString url
@@ -130,17 +137,17 @@ public: // WebIDL interface:
             ErrorResult& aRv);
   void Send(Blob& aData,
             ErrorResult& aRv);
   void Send(const ArrayBuffer& aData,
             ErrorResult& aRv);
   void Send(const ArrayBufferView& aData,
             ErrorResult& aRv);
 
-private: // constructor && distructor
+private: // constructor && destructor
   explicit WebSocket(nsPIDOMWindowInner* aOwnerWindow);
   virtual ~WebSocket();
 
   void SetReadyState(uint16_t aReadyState);
 
   // These methods actually do the dispatch for various events.
   nsresult CreateAndDispatchSimpleEvent(const nsAString& aName);
   nsresult CreateAndDispatchMessageEvent(const nsACString& aData,
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1864,16 +1864,17 @@ addExternalIface('nsIInputStreamCallback
                  headerFile='nsIAsyncInputStream.h')
 addExternalIface('nsIFile', nativeType='nsIFile', notflattened=True)
 addExternalIface('nsILoadGroup', nativeType='nsILoadGroup',
                  headerFile='nsILoadGroup.h', notflattened=True)
 addExternalIface('nsIMessageBroadcaster', nativeType='nsIMessageBroadcaster',
                  headerFile='nsIMessageManager.h', notflattened=True)
 addExternalIface('nsISelectionListener', nativeType='nsISelectionListener')
 addExternalIface('nsIStreamListener', nativeType='nsIStreamListener', notflattened=True)
+addExternalIface('nsITransportProvider', nativeType='nsITransportProvider')
 addExternalIface('nsISupports', nativeType='nsISupports')
 addExternalIface('nsIDocShell', nativeType='nsIDocShell', notflattened=True)
 addExternalIface('nsIEditor', nativeType='nsIEditor', notflattened=True)
 addExternalIface('nsIVariant', nativeType='nsIVariant', notflattened=True)
 addExternalIface('nsIScriptableRegion', nativeType='nsIScriptableRegion', notflattened=True)
 addExternalIface('OutputStream', nativeType='nsIOutputStream',
                  notflattened=True)
 addExternalIface('Principal', nativeType='nsIPrincipal',
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -252,21 +252,22 @@ HttpServer::TransportProvider::SetListen
 
   mListener = aListener;
 
   MaybeNotify();
 
   return NS_OK;
 }
 
-NS_IMETHODIMP_(PTransportProviderChild*)
-HttpServer::TransportProvider::GetIPCChild()
+NS_IMETHODIMP
+HttpServer::TransportProvider::GetIPCChild(PTransportProviderChild** aChild)
 {
   MOZ_CRASH("Don't call this in parent process");
-  return nullptr;
+  *aChild = nullptr;
+  return NS_OK;
 }
 
 void
 HttpServer::TransportProvider::SetTransport(nsISocketTransport* aTransport,
                                             nsIAsyncInputStream* aInput,
                                             nsIAsyncOutputStream* aOutput)
 {
   MOZ_ASSERT(!mTransport);
--- a/dom/webidl/WebSocket.webidl
+++ b/dom/webidl/WebSocket.webidl
@@ -58,8 +58,20 @@ interface WebSocket : EventTarget {
   void send(Blob data);
 
   [Throws]
   void send(ArrayBuffer data);
 
   [Throws]
   void send(ArrayBufferView data);
 };
+
+// Support for creating server-side chrome-only WebSocket. Used in FlyWeb and in
+// devtools remote debugging server.
+interface nsITransportProvider;
+
+partial interface WebSocket {
+  [ChromeOnly, NewObject, Throws]
+  static WebSocket createServerWebSocket(DOMString url,
+                                         sequence<DOMString> protocols,
+                                         nsITransportProvider transportProvider,
+                                         DOMString negotiatedExtensions);
+};
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
@@ -582,24 +582,25 @@ public final class IntentHelper implemen
 
         public ResultHandler(JSONObject message) {
             this.message = message;
         }
 
         @Override
         public void onActivityResult(int resultCode, Intent data) {
             JSONObject response = new JSONObject();
-
             try {
                 if (data != null) {
-                    response.put("extras", JSONUtils.bundleToJSON(data.getExtras()));
-                    response.put("uri", data.getData().toString());
+                    if (data.getExtras() != null) {
+                        response.put("extras", JSONUtils.bundleToJSON(data.getExtras()));
+                    }
+                    if (data.getData() != null) {
+                        response.put("uri", data.getData().toString());
+                    }
                 }
-
                 response.put("resultCode", resultCode);
             } catch (JSONException e) {
                 Log.w(LOGTAG, "Error building JSON response.", e);
             }
-
             EventDispatcher.sendResponse(message, response);
         }
     }
 }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -915,16 +915,17 @@ pref("devtools.chrome.enabled", false);
 // Disable remote debugging protocol logging
 pref("devtools.debugger.log", false);
 pref("devtools.debugger.log.verbose", false);
 
 // Disable remote debugging connections
 pref("devtools.debugger.remote-enabled", false);
 
 pref("devtools.debugger.remote-port", 6000);
+pref("devtools.debugger.remote-websocket", false);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 // Display a prompt when a new connection starts to accept/reject it
 pref("devtools.debugger.prompt-connection", true);
 // Block tools from seeing / interacting with certified apps
 pref("devtools.debugger.forbid-certified-apps", true);
 // List of permissions that a sideloaded app can't ask for
 pref("devtools.apps.forbidden-permissions", "embed-apps,embed-widgets");
@@ -4288,16 +4289,17 @@ pref("font.name.monospace.x-unicode", "d
 pref("signon.rememberSignons",              true);
 #ifdef NIGHTLY_BUILD
 pref("signon.rememberSignons.visibilityToggle", true);
 #else
 pref("signon.rememberSignons.visibilityToggle", false);
 #endif
 pref("signon.autofillForms",                true);
 pref("signon.autologin.proxy",              false);
+pref("signon.formlessCapture.enabled",      true);
 pref("signon.storeWhenAutocompleteOff",     true);
 pref("signon.ui.experimental",              false);
 pref("signon.debug",                        false);
 pref("signon.recipes.path",                 "chrome://passwordmgr/content/recipes.json");
 pref("signon.schemeUpgrades",               false);
 
 // Satchel (Form Manager) prefs
 pref("browser.formfill.debug",            false);
--- a/netwerk/protocol/websocket/IPCTransportProvider.cpp
+++ b/netwerk/protocol/websocket/IPCTransportProvider.cpp
@@ -33,21 +33,22 @@ TransportProviderParent::SetListener(nsI
   MOZ_ASSERT(aListener);
   mListener = aListener;
 
   MaybeNotify();
 
   return NS_OK;
 }
 
-NS_IMETHODIMP_(mozilla::net::PTransportProviderChild*)
-TransportProviderParent::GetIPCChild()
+NS_IMETHODIMP
+TransportProviderParent::GetIPCChild(mozilla::net::PTransportProviderChild** aChild)
 {
   MOZ_CRASH("Don't call this in parent process");
-  return nullptr;
+  *aChild = nullptr;
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 TransportProviderParent::OnTransportAvailable(nsISocketTransport* aTransport,
                                               nsIAsyncInputStream* aSocketIn,
                                               nsIAsyncOutputStream* aSocketOut)
 {
   MOZ_ASSERT(aTransport && aSocketOut && aSocketOut);
@@ -87,16 +88,17 @@ TransportProviderChild::~TransportProvid
 
 NS_IMETHODIMP
 TransportProviderChild::SetListener(nsIHttpUpgradeListener* aListener)
 {
   MOZ_CRASH("Don't call this in child process");
   return NS_OK;
 }
 
-NS_IMETHODIMP_(mozilla::net::PTransportProviderChild*)
-TransportProviderChild::GetIPCChild()
+NS_IMETHODIMP
+TransportProviderChild::GetIPCChild(mozilla::net::PTransportProviderChild** aChild)
 {
-  return this;
+  *aChild = this;
+  return NS_OK;
 }
 
 } // net
 } // mozilla
--- a/netwerk/protocol/websocket/WebSocketChannelChild.cpp
+++ b/netwerk/protocol/websocket/WebSocketChannelChild.cpp
@@ -467,17 +467,21 @@ WebSocketChannelChild::AsyncOpen(nsIURI 
     NS_ENSURE_SUCCESS(rv, rv);
 
     transportProvider = void_t();
   } else {
     uri = void_t();
     loadInfoArgs = void_t();
 
     MOZ_ASSERT(mServerTransportProvider);
-    transportProvider = mServerTransportProvider->GetIPCChild();
+    PTransportProviderChild *ipcChild;
+    nsresult rv = mServerTransportProvider->GetIPCChild(&ipcChild);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    transportProvider = ipcChild;
   }
 
   gNeckoChild->SendPWebSocketConstructor(this, tabChild,
                                          IPC::SerializedLoadContext(this),
                                          mSerial);
   if (!SendAsyncOpen(uri, nsCString(aOrigin), aInnerWindowID, mProtocol,
                      mEncrypted, mPingInterval, mClientSetPingInterval,
                      mPingResponseTimeout, mClientSetPingTimeout, loadInfoArgs,
--- a/netwerk/protocol/websocket/nsITransportProvider.idl
+++ b/netwerk/protocol/websocket/nsITransportProvider.idl
@@ -17,20 +17,20 @@ class PTransportProviderChild;
 %}
 
 [ptr] native PTransportProviderChild(mozilla::net::PTransportProviderChild);
 
 /**
  * An interface which can be used to asynchronously request a nsITransport
  * together with the input and output streams that go together with it.
  */
-[uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
+[scriptable, uuid(6fcec704-cfd2-46ef-a394-a64d5cb1475c)]
 interface nsITransportProvider : nsISupports
 {
     // This must not be called in a child process since transport
     // objects are not accessible there. Call getIPCChild instead.
     void setListener(in nsIHttpUpgradeListener listener);
 
     // This must be implemented by nsITransportProvider objects running
     // in the child process. It must return null when called in the parent
     // process.
-    [noscript, notxpcom] PTransportProviderChild getIPCChild();
+    [noscript] PTransportProviderChild getIPCChild();
 };
--- a/toolkit/components/passwordmgr/LoginHelper.jsm
+++ b/toolkit/components/passwordmgr/LoginHelper.jsm
@@ -30,16 +30,17 @@ Cu.import("resource://gre/modules/XPCOMU
 /**
  * Contains functions shared by different Login Manager components.
  */
 this.LoginHelper = {
   /**
    * Warning: these only update if a logger was created.
    */
   debug: Services.prefs.getBoolPref("signon.debug"),
+  formlessCaptureEnabled: Services.prefs.getBoolPref("signon.formlessCapture.enabled"),
   schemeUpgrades: Services.prefs.getBoolPref("signon.schemeUpgrades"),
 
   createLogger(aLogPrefix) {
     let getMaxLogLevel = () => {
       return this.debug ? "debug" : "warn";
     };
 
     // Create a new instance of the ConsoleAPI so we can control the maxLogLevel with a pref.
@@ -48,16 +49,17 @@ this.LoginHelper = {
       maxLogLevel: getMaxLogLevel(),
       prefix: aLogPrefix,
     };
     let logger = new ConsoleAPI(consoleOptions);
 
     // Watch for pref changes and update this.debug and the maxLogLevel for created loggers
     Services.prefs.addObserver("signon.", () => {
       this.debug = Services.prefs.getBoolPref("signon.debug");
+      this.formlessCaptureEnabled = Services.prefs.getBoolPref("signon.formlessCapture.enabled");
       this.schemeUpgrades = Services.prefs.getBoolPref("signon.schemeUpgrades");
       logger.maxLogLevel = getMaxLogLevel();
     }, false);
 
     return logger;
   },
 
   /**
--- a/toolkit/components/passwordmgr/LoginManagerContent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerContent.jsm
@@ -39,16 +39,17 @@ XPCOMUtils.defineLazyGetter(this, "log",
 });
 
 // These mirror signon.* prefs.
 var gEnabled, gAutofillForms, gStoreWhenAutocompleteOff;
 
 var observer = {
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIObserver,
                                           Ci.nsIFormSubmitObserver,
+                                          Ci.nsIWebProgressListener,
                                           Ci.nsISupportsWeakReference]),
 
   // nsIFormSubmitObserver
   notify(formElement, aWindow, actionURI) {
     log("observer notified for form submission.");
 
     // We're invoked before the content's |onsubmit| handlers, so we
     // can grab form data before it might be modified (see bug 257781).
@@ -64,16 +65,55 @@ var observer = {
     return true; // Always return true, or form submit will be canceled.
   },
 
   onPrefChange() {
     gEnabled = Services.prefs.getBoolPref("signon.rememberSignons");
     gAutofillForms = Services.prefs.getBoolPref("signon.autofillForms");
     gStoreWhenAutocompleteOff = Services.prefs.getBoolPref("signon.storeWhenAutocompleteOff");
   },
+
+  // nsIWebProgressListener
+  onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+    // Only handle pushState/replaceState here.
+    if (!(aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_SAME_DOCUMENT) ||
+        !(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_PUSHSTATE)) {
+      return;
+    }
+
+    log("onLocationChange handled:", aLocation.spec, aWebProgress.DOMWindow.document);
+
+    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
+  },
+
+  onStateChange(aWebProgress, aRequest, aState, aStatus) {
+    if (!(aState & Ci.nsIWebProgressListener.STATE_START)) {
+      return;
+    }
+
+    // We only care about when a page triggered a load, not the user. For example:
+    // clicking refresh/back/forward, typing a URL and hitting enter, and loading a bookmark aren't
+    // likely to be when a user wants to save a login.
+    let channel = aRequest.QueryInterface(Ci.nsIChannel);
+    let triggeringPrincipal = channel.loadInfo.triggeringPrincipal;
+    if (triggeringPrincipal.isNullPrincipal ||
+        triggeringPrincipal.equals(Services.scriptSecurityManager.getSystemPrincipal())) {
+      return;
+    }
+
+    // Don't handle history navigation, reload, or pushState not triggered via chrome UI.
+    // e.g. history.go(-1), location.reload(), history.replaceState()
+    if (!(aWebProgress.loadType & Ci.nsIDocShell.LOAD_CMD_NORMAL)) {
+      log("onStateChange: loadType isn't LOAD_CMD_NORMAL:", aWebProgress.loadType);
+      return;
+    }
+
+    log("onStateChange handled:", channel);
+    LoginManagerContent._onNavigation(aWebProgress.DOMWindow.document);
+  },
 };
 
 Services.obs.addObserver(observer, "earlyformsubmit", false);
 var prefBranch = Services.prefs.getBranch("signon.");
 prefBranch.addObserver("", observer.onPrefChange, false);
 
 observer.onPrefChange(); // read initial values
 
@@ -274,32 +314,53 @@ var LoginManagerContent = {
                         rect: aRect,
                         remote: remote };
 
     return this._sendRequest(messageManager, requestData,
                              "RemoteLogins:autoCompleteLogins",
                              messageData);
   },
 
+  setupProgressListener(window) {
+    if (!LoginHelper.formlessCaptureEnabled) {
+      return;
+    }
+
+    try {
+      let webProgress = window.QueryInterface(Ci.nsIInterfaceRequestor).
+                        getInterface(Ci.nsIWebNavigation).
+                        QueryInterface(Ci.nsIDocShell).
+                        QueryInterface(Ci.nsIInterfaceRequestor).
+                        getInterface(Ci.nsIWebProgress);
+      webProgress.addProgressListener(observer,
+                                      Ci.nsIWebProgress.NOTIFY_STATE_DOCUMENT |
+                                      Ci.nsIWebProgress.NOTIFY_LOCATION);
+    } catch (ex) {
+      // Ignore NS_ERROR_FAILURE if the progress listener was already added
+    }
+  },
+
   onDOMFormHasPassword(event, window) {
     if (!event.isTrusted) {
       return;
     }
 
     let form = event.target;
     let formLike = FormLikeFactory.createFromForm(form);
     log("onDOMFormHasPassword:", form, formLike);
     this._fetchLoginsFromParentAndFillForm(formLike, window);
   },
 
   onDOMInputPasswordAdded(event, window) {
     if (!event.isTrusted) {
       return;
     }
 
+    this.setupProgressListener(window);
+
     let pwField = event.target;
     if (pwField.form) {
       // Handled by onDOMFormHasPassword which is already throttled.
       return;
     }
 
     let formLike = FormLikeFactory.createFromField(pwField);
     log("onDOMInputPasswordAdded:", pwField, formLike);
@@ -311,17 +372,16 @@ var LoginManagerContent = {
 
       deferredTask = new DeferredTask(function* deferredInputProcessing() {
         // Get the updated formLike instead of the one at the time of creating the DeferredTask via
         // a closure since it could be stale since FormLike.elements isn't live.
         let formLike2 = this._formLikeByRootElement.get(formLike.rootElement);
         log("Running deferred processing of onDOMInputPasswordAdded", formLike2);
         this._deferredPasswordAddedTasksByRootElement.delete(formLike2.rootElement);
         this._fetchLoginsFromParentAndFillForm(formLike2, window);
-        this._formLikeByRootElement.delete(formLike.rootElement);
       }.bind(this), PASSWORD_INPUT_ADDED_COALESCING_THRESHOLD_MS);
 
       this._deferredPasswordAddedTasksByRootElement.set(formLike.rootElement, deferredTask);
     }
 
     if (deferredTask.isArmed) {
       log("DeferredTask is already armed so just updating the FormLike");
       // We update the FormLike so it (most important .elements) is fresh when the task eventually
@@ -343,19 +403,16 @@ var LoginManagerContent = {
 
   /**
    * Fetch logins from the parent for a given form and then attempt to fill it.
    *
    * @param {FormLike} form to fetch the logins for then try autofill.
    * @param {Window} window
    */
   _fetchLoginsFromParentAndFillForm(form, window) {
-    // Always record the most recently added form with a password field.
-    this.stateForDocument(form.ownerDocument).loginForm = form;
-
     this._updateLoginFormPresence(window);
 
     let messageManager = messageManagerFromWindow(window);
     messageManager.sendAsyncMessage("LoginStats:LoginEncountered");
 
     if (!gEnabled) {
       return;
     }
@@ -372,45 +429,48 @@ var LoginManagerContent = {
   /**
    * Maps all DOM content documents in this content process, including those in
    * frames, to the current state used by the Login Manager.
    */
   loginFormStateByDocument: new WeakMap(),
 
   /**
    * Retrieves a reference to the state object associated with the given
-   * document. This is initialized to an empty object.
+   * document. This is initialized to an object with default values.
    */
   stateForDocument(document) {
     let loginFormState = this.loginFormStateByDocument.get(document);
     if (!loginFormState) {
-      loginFormState = {};
+      loginFormState = {
+        loginFormRootElements: new Set(),
+      };
       this.loginFormStateByDocument.set(document, loginFormState);
     }
     return loginFormState;
   },
 
   /**
    * Compute whether there is a login form on any frame of the current page, and
    * notify the parent process. This is one of the factors used to control the
    * visibility of the password fill doorhanger anchor.
    */
   _updateLoginFormPresence(topWindow) {
+    log("_updateLoginFormPresence", topWindow.location.href);
     // For the login form presence notification, we currently support only one
     // origin for each browser, so the form origin will always match the origin
     // of the top level document.
     let loginFormOrigin =
         LoginUtils._getPasswordOrigin(topWindow.document.documentURI);
 
     // Returns the first known loginForm present in this window or in any
     // same-origin subframes. Returns null if no loginForm is currently present.
     let getFirstLoginForm = thisWindow => {
-      let loginForm = this.stateForDocument(thisWindow.document).loginForm;
-      if (loginForm) {
-        return loginForm;
+      let loginForms = this.stateForDocument(thisWindow.document).loginFormRootElements;
+      if (loginForms.size) {
+        return [...loginForms][0];
       }
       for (let i = 0; i < thisWindow.frames.length; i++) {
         let frame = thisWindow.frames[i];
         if (LoginUtils._getPasswordOrigin(frame.document.documentURI) !=
             loginFormOrigin) {
           continue;
         }
         let loginForm = getFirstLoginForm(frame);
@@ -420,25 +480,26 @@ var LoginManagerContent = {
       }
       return null;
     };
 
     // Returns true if this window or any subframes have insecure login forms.
     let hasInsecureLoginForms = (thisWindow, parentIsInsecure) => {
       let doc = thisWindow.document;
       let isInsecure = parentIsInsecure || !this.isDocumentSecure(doc);
-      let hasLoginForm = !!this.stateForDocument(doc).loginForm;
+      let hasLoginForm = this.stateForDocument(doc).loginFormRootElements.size > 0;
       return (hasLoginForm && isInsecure) ||
              Array.some(thisWindow.frames,
                         frame => hasInsecureLoginForms(frame, isInsecure));
     };
 
     // Store the actual form to use on the state for the top-level document.
     let topState = this.stateForDocument(topWindow.document);
     topState.loginFormForFill = getFirstLoginForm(topWindow);
+    log("_updateLoginFormPresence: topState.loginFormForFill", topState.loginFormForFill);
 
     // Determine whether to show the anchor icon for the current tab.
     let messageManager = messageManagerFromWindow(topWindow);
     messageManager.sendAsyncMessage("RemoteLogins:updateLoginFormPresence", {
       loginFormOrigin,
       loginFormPresent: !!topState.loginFormForFill,
       hasInsecureLoginForms: hasInsecureLoginForms(topWindow, false),
     });
@@ -734,39 +795,74 @@ var LoginManagerContent = {
       } else {
         // Just assume that the 2nd password is the new password
         oldPasswordField = pwFields[0].element;
         newPasswordField = pwFields[1].element;
       }
     }
 
     log("Password field (new) id/name is: ", newPasswordField.id, " / ", newPasswordField.name);
-    if (oldPasswordField)
+    if (oldPasswordField) {
       log("Password field (old) id/name is: ", oldPasswordField.id, " / ", oldPasswordField.name);
+    } else {
+      log("Password field (old):", oldPasswordField);
+    }
     return [usernameField, newPasswordField, oldPasswordField];
   },
 
 
   /**
    * @return true if the page requests autocomplete be disabled for the
    *              specified element.
    */
   _isAutocompleteDisabled(element) {
     return element && element.autocomplete == "off";
   },
 
   /**
+   * Trigger capture on any relevant FormLikes due to a navigation alone (not
+   * necessarily due to an actual form submission). This method is used to
+   * capture logins for cases where form submit events are not used.
+   *
+   * To avoid multiple notifications for the same FormLike, this currently
+   * avoids capturing when dealing with a real <form> which are ideally already
+   * using a submit event.
+   *
+   * @param {Document} document being navigated
+   */
+  _onNavigation(aDocument) {
+    let state = this.stateForDocument(aDocument);
+    let loginFormRootElements = state.loginFormRootElements;
+    log("_onNavigation: state:", state, "loginFormRootElements size:", loginFormRootElements.size,
+        "document:", aDocument);
+
+    for (let formRoot of state.loginFormRootElements) {
+      if (formRoot instanceof Ci.nsIDOMHTMLFormElement) {
+        // For now only perform capture upon navigation for FormLike's without
+        // a <form> to avoid capture from both an earlyformsubmit and
+        // navigation for the same "form".
+        log("Ignoring navigation for the form root to avoid multiple prompts " +
+            "since it was for a real <form>");
+        continue;
+      }
+      let formLike = this._formLikeByRootElement.get(formRoot);
+      this._onFormSubmit(formLike);
+    }
+  },
+
+  /**
    * Called by our observer when notified of a form submission.
    * [Note that this happens before any DOM onsubmit handlers are invoked.]
    * Looks for a password change in the submitted form, so we can update
    * our stored password.
    *
    * @param {FormLike} form
    */
   _onFormSubmit(form) {
+    log("_onFormSubmit", form);
     var doc = form.ownerDocument;
     var win = doc.defaultView;
 
     if (PrivateBrowsingUtils.isContentWindowPrivate(win)) {
       // We won't do anything in private browsing mode anyway,
       // so there's no need to perform further checks.
       log("(form submission ignored in private browsing mode)");
       return;
@@ -851,16 +947,17 @@ var LoginManagerContent = {
    *                             the user
    * @param {nsILoginInfo[]} foundLogins is an array of nsILoginInfo that could be used for the form
    * @param {Set} recipes that could be used to affect how the form is filled
    * @param {Object} [options = {}] is a list of options for this method.
             - [inputElement] is an optional target input element we want to fill
    */
   _fillForm(form, autofillForm, clobberUsername, clobberPassword,
                         userTriggered, foundLogins, recipes, {inputElement} = {}) {
+    log("_fillForm", form.elements);
     let ignoreAutocomplete = true;
     const AUTOFILL_RESULT = {
       FILLED: 0,
       NO_PASSWORD_FIELD: 1,
       PASSWORD_DISABLED_READONLY: 2,
       NO_LOGINS_FIT: 3,
       NO_SAVED_LOGINS: 4,
       EXISTING_PASSWORD: 5,
@@ -1158,16 +1255,17 @@ var LoginUtils = {
   _getPasswordOrigin(uriString, allowJS) {
     var realm = "";
     try {
       var uri = Services.io.newURI(uriString, null, null);
 
       if (allowJS && uri.scheme == "javascript")
         return "javascript:";
 
+      // Build this manually instead of using prePath to avoid including the userPass portion.
       realm = uri.scheme + "://" + uri.hostPort;
     } catch (e) {
       // bug 159484 - disallow url types that don't support a hostPort.
       // (although we handle "javascript:..." as a special case above.)
       log("Couldn't parse origin for", uriString, e);
       realm = null;
     }
 
@@ -1308,16 +1406,22 @@ var FormLikeFactory = {
       rootElement: aForm,
     };
 
     for (let prop of this._propsFromForm) {
       formLike[prop] = aForm[prop];
     }
 
     this._addToJSONProperty(formLike);
+
+    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
+    state.loginFormRootElements.add(formLike.rootElement);
+    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
+
+    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
     return formLike;
   },
 
   /**
    * Create a FormLike object from a password or username field.
    *
    * If the field is in a <form>, construct the FormLike from the form.
    * Otherwise, create a FormLike with a rootElement (wrapper) according to
@@ -1356,16 +1460,23 @@ var FormLikeFactory = {
       autocomplete: "on",
       // Exclude elements inside the rootElement that are already in a <form> as
       // they will be handled by their own FormLike.
       elements,
       ownerDocument: doc,
       rootElement: doc.documentElement,
     };
 
+    let state = LoginManagerContent.stateForDocument(formLike.ownerDocument);
+    state.loginFormRootElements.add(formLike.rootElement);
+    log("adding", formLike.rootElement, "to loginFormRootElements for", formLike.ownerDocument);
+
+
+    LoginManagerContent._formLikeByRootElement.set(formLike.rootElement, formLike);
+
     this._addToJSONProperty(formLike);
     return formLike;
   },
 
   /**
    * Add a `toJSON` property to a FormLike so logging which ends up going
    * through dump doesn't include usless garbage from DOM objects.
    */
--- a/toolkit/components/passwordmgr/LoginManagerParent.jsm
+++ b/toolkit/components/passwordmgr/LoginManagerParent.jsm
@@ -504,37 +504,37 @@ var LoginManagerParent = {
         () => this.updateLoginAnchor(browser),
         ANCHOR_DELAY_MS
       );
     }
     state.anchorDeferredTask.arm();
   },
 
   updateLoginAnchor: Task.async(function* (browser) {
+    // Once this preference is removed, this version of the fill doorhanger
+    // should be enabled for Desktop only, and not for Android or B2G.
+    if (!Services.prefs.getBoolPref("signon.ui.experimental")) {
+      return;
+    }
+
     // Copy the state to use for this execution of the task. These will not
     // change during this execution of the asynchronous function, but in case a
     // change happens in the state, the function will be retriggered.
     let { loginFormOrigin, loginFormPresent } = this.stateForBrowser(browser);
 
     yield Services.logins.initializationPromise;
 
     // Check if there are form logins for the site, ignoring formSubmitURL.
     let hasLogins = loginFormOrigin &&
                     LoginHelper.searchLoginsWithObject({
-                      formSubmitURL: "",
+                      httpRealm: null,
                       hostname: loginFormOrigin,
                       schemeUpgrades: LoginHelper.schemeUpgrades,
                     }).length > 0;
 
-    // Once this preference is removed, this version of the fill doorhanger
-    // should be enabled for Desktop only, and not for Android or B2G.
-    if (!Services.prefs.getBoolPref("signon.ui.experimental")) {
-      return;
-    }
-
     let showLoginAnchor = loginFormPresent || hasLogins;
 
     let fillDoorhanger = LoginDoorhangers.FillDoorhanger.find({ browser });
     if (fillDoorhanger) {
       if (!showLoginAnchor) {
         fillDoorhanger.remove();
         return;
       }
--- a/toolkit/components/passwordmgr/test/browser/browser.ini
+++ b/toolkit/components/passwordmgr/test/browser/browser.ini
@@ -35,16 +35,17 @@ support-files =
   subtst_notifications_11.html
   subtst_notifications_11_popup.html
 [browser_username_select_dialog.js]
 support-files =
   subtst_notifications_change_p.html
 [browser_DOMFormHasPassword.js]
 [browser_DOMInputPasswordAdded.js]
 [browser_filldoorhanger.js]
+[browser_formless_submit_chrome.js]
 [browser_hasInsecureLoginForms.js]
 [browser_hasInsecureLoginForms_streamConverter.js]
 [browser_insecurePasswordWarning.js]
 [browser_notifications.js]
 [browser_notifications_2.js]
 [browser_passwordmgr_editing.js]
 skip-if = os == "linux"
 [browser_context_menu.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/passwordmgr/test/browser/browser_formless_submit_chrome.js
@@ -0,0 +1,122 @@
+/*
+ * Test that browser chrome UI interactions don't trigger a capture doorhanger.
+ */
+
+"use strict";
+
+function* fillTestPage(aBrowser) {
+  yield ContentTask.spawn(aBrowser, null, function*() {
+    content.document.getElementById("form-basic-username").value = "my_username";
+    content.document.getElementById("form-basic-password").value = "my_password";
+  });
+  info("fields filled");
+}
+
+function* withTestPage(aTaskFn) {
+  return BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: "https://example.com" + DIRECTORY_PATH + "formless_basic.html",
+  }, function*(aBrowser) {
+    info("tab opened");
+    yield fillTestPage(aBrowser);
+    yield* aTaskFn(aBrowser);
+
+    // Give a chance for the doorhanger to appear
+    yield new Promise(resolve => SimpleTest.executeSoon(resolve));
+    ok(!getCaptureDoorhanger("any"), "No doorhanger should be present");
+  });
+}
+
+add_task(function* setup() {
+  yield SimpleTest.promiseFocus(window);
+});
+
+add_task(function* test_urlbar_new_URL() {
+  yield withTestPage(function*(aBrowser) {
+    gURLBar.value = "";
+    let focusPromise = BrowserTestUtils.waitForEvent(gURLBar, "focus");
+    gURLBar.focus();
+    yield focusPromise;
+    info("focused");
+    EventUtils.sendString("http://mochi.test:8888/");
+    EventUtils.synthesizeKey("VK_RETURN", {});
+    yield BrowserTestUtils.browserLoaded(aBrowser, false, "http://mochi.test:8888/");
+  });
+});
+
+add_task(function* test_urlbar_fragment_enter() {
+  yield withTestPage(function*(aBrowser) {
+    gURLBar.focus();
+    EventUtils.synthesizeKey("VK_RIGHT", {});
+    EventUtils.sendString("#fragment");
+    EventUtils.synthesizeKey("VK_RETURN", {});
+  });
+});
+
+add_task(function* test_backButton_forwardButton() {
+  yield withTestPage(function*(aBrowser) {
+    // Load a new page in the tab so we can test going back
+    aBrowser.loadURI("https://example.com" + DIRECTORY_PATH + "formless_basic.html?second");
+    yield BrowserTestUtils.browserLoaded(aBrowser, false,
+                                         "https://example.com" + DIRECTORY_PATH +
+                                         "formless_basic.html?second");
+    yield fillTestPage(aBrowser);
+
+    let backPromise = BrowserTestUtils.browserStopped(aBrowser);
+    EventUtils.synthesizeMouseAtCenter(document.getElementById("back-button"), {});
+    yield backPromise;
+
+    // Give a chance for the doorhanger to appear
+    yield new Promise(resolve => SimpleTest.executeSoon(resolve));
+    ok(!getCaptureDoorhanger("any"), "No doorhanger should be present");
+
+    // Now go forward again after filling
+    yield fillTestPage(aBrowser);
+
+    let forwardButton = document.getElementById("forward-button");
+    yield BrowserTestUtils.waitForEvent(forwardButton, "transitionend");
+    info("transition done");
+    yield BrowserTestUtils.waitForCondition(() => {
+      return forwardButton.disabled == false;
+    });
+    let forwardPromise = BrowserTestUtils.browserStopped(aBrowser);
+    info("click the forward button");
+    EventUtils.synthesizeMouseAtCenter(forwardButton, {});
+    yield forwardPromise;
+  });
+});
+
+
+add_task(function* test_reloadButton() {
+  yield withTestPage(function*(aBrowser) {
+    let reloadButton = document.getElementById("urlbar-reload-button");
+    let loadPromise = BrowserTestUtils.browserLoaded(aBrowser, false,
+                                                     "https://example.com" + DIRECTORY_PATH +
+                                                     "formless_basic.html");
+
+    yield BrowserTestUtils.waitForCondition(() => {
+      return reloadButton.disabled == false;
+    });
+    EventUtils.synthesizeMouseAtCenter(reloadButton, {});
+    yield loadPromise;
+  });
+});
+
+add_task(function* test_back_keyboard_shortcut() {
+  if (Services.prefs.getIntPref("browser.backspace_action") != 0) {
+    ok(true, "Skipped testing backspace to go back since it's disabled");
+    return;
+  }
+  yield withTestPage(function*(aBrowser) {
+    // Load a new page in the tab so we can test going back
+    aBrowser.loadURI("https://example.com" + DIRECTORY_PATH + "formless_basic.html?second");
+    yield BrowserTestUtils.browserLoaded(aBrowser, false,
+                                         "https://example.com" + DIRECTORY_PATH +
+                                         "formless_basic.html?second");
+    yield fillTestPage(aBrowser);
+
+    let backPromise = BrowserTestUtils.browserStopped(aBrowser);
+    EventUtils.synthesizeKey("VK_BACK_SPACE", {});
+    yield backPromise;
+  });
+});
--- a/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
+++ b/toolkit/components/passwordmgr/test/mochitest/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g'
 support-files =
   ../../../prompts/test/chromeScript.js
   ../../../prompts/test/prompt_common.js
   ../../../satchel/test/parent_utils.js
   ../../../satchel/test/satchel_common.js
   ../authenticate.sjs
+  ../blank.html
   ../browser/form_basic.html
   ../browser/form_cross_origin_secure_action.html
   ../notification_common.js
   ../pwmgr_common.js
   auth2/authenticate.sjs
 
 [test_autocomplete_https_upgrade.html]
 skip-if = toolkit == 'android' # autocomplete
@@ -34,16 +35,18 @@ skip-if = toolkit == 'android' # Tests d
 [test_bug_776171.html]
 [test_case_differences.html]
 skip-if = toolkit == 'android' # autocomplete
 [test_form_action_1.html]
 [test_form_action_2.html]
 [test_form_action_javascript.html]
 [test_formless_autofill.html]
 [test_formless_submit.html]
+[test_formless_submit_navigation.html]
+[test_formless_submit_navigation_negative.html]
 [test_input_events.html]
 [test_input_events_for_identical_values.html]
 [test_maxlength.html]
 [test_passwords_in_type_password.html]
 [test_prompt.html]
 skip-if = e10s || os == "linux" || toolkit == 'android' # Tests desktop prompts
 [test_recipe_login_fields.html]
 [test_xhr_2.html]
copy from toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
copy to toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation.html
@@ -1,13 +1,13 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Test capturing of fields outside of a form</title>
+  <title>Test capturing of fields outside of a form due to navigation</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
 const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm");
@@ -19,107 +19,106 @@ let loadPromise = new Promise(resolve =>
   document.addEventListener("DOMContentLoaded", () => {
     document.getElementById("loginFrame").addEventListener("load", (evt) => {
       resolve();
     });
   });
 });
 
 add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [
+      ["signon.formlessCapture.enabled", true],
+    ],
+  });
+
   info("Waiting for page and frame loads");
   yield loadPromise;
 
   yield loadRecipes({
     siteRecipes: [{
-      hosts: ["mochi.test:8888"],
+      hosts: ["test1.mochi.test:8888"],
       usernameSelector: "input[name='recipeuname']",
       passwordSelector: "input[name='recipepword']",
     }],
   });
 });
 
-const DEFAULT_ORIGIN = "http://mochi.test:8888";
+const DEFAULT_ORIGIN = "http://test1.mochi.test:8888";
+const SCRIPTS = {
+  PUSHSTATE: `history.pushState({}, "Pushed state", "?pushed");`,
+  WINDOW_LOCATION: `window.location = "data:text/html;charset=utf-8,window.location";`,
+};
 const TESTCASES = [
   {
     // Inputs
     document: `<input type=password value="pass1">`,
-    inputIndexForFormLike: 0,
 
     // Expected outputs similar to RemoteLogins:onFormSubmit
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: null,
     newPasswordFieldValue: "pass1",
     oldPasswordFieldValue: null,
   },
   {
     document: `<input value="user1">
       <input type=password value="pass1">`,
-    inputIndexForFormLike: 0,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass1",
-    oldPasswordFieldValue: null,
-  },
-  {
-    document: `<input value="user1">
-      <input type=password value="pass1">`,
-    inputIndexForFormLike: 1,
+
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: "user1",
     newPasswordFieldValue: "pass1",
     oldPasswordFieldValue: null,
   },
   {
     document: `<input value="user1">
       <input type=password value="pass1">
       <input type=password value="pass2">`,
-    inputIndexForFormLike: 2,
+
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: "user1",
     newPasswordFieldValue: "pass2",
     oldPasswordFieldValue: "pass1",
   },
   {
     document: `<input value="user1">
       <input type=password value="pass1">
       <input type=password value="pass2">
       <input type=password value="pass2">`,
-    inputIndexForFormLike: 3,
+
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: "user1",
     newPasswordFieldValue: "pass2",
     oldPasswordFieldValue: "pass1",
   },
   {
     document: `<input value="user1">
       <input type=password value="user2" form="form1">
       <input type=password value="pass1">
       <form id="form1">
         <input value="user3">
         <input type=password value="pass2">
       </form>`,
-    inputIndexForFormLike: 2,
+
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: "user1",
     newPasswordFieldValue: "pass1",
     oldPasswordFieldValue: null,
   },
   {
     document: `<!-- recipe field override -->
       <input name="recipeuname" value="username from recipe">
       <input value="default field username">
       <input type=password value="pass1">
       <input name="recipepword" type=password value="pass2">`,
-    inputIndexForFormLike: 2,
+
     hostname: DEFAULT_ORIGIN,
     formSubmitURL: DEFAULT_ORIGIN,
     usernameFieldValue: "username from recipe",
     newPasswordFieldValue: "pass2",
     oldPasswordFieldValue: null,
   },
 ];
 
@@ -131,53 +130,62 @@ function getSubmitMessage() {
       chromeScript.removeMessageListener("formSubmissionProcessed", processed);
       resolve(...args);
     });
   });
 }
 
 add_task(function* test() {
   let loginFrame = document.getElementById("loginFrame");
-  let frameDoc = loginFrame.contentWindow.document;
 
   for (let tc of TESTCASES) {
-    info("Starting testcase: " + JSON.stringify(tc));
-    frameDoc.documentElement.innerHTML = tc.document;
-    let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike];
-
-    let formLike = FormLikeFactory.createFromField(inputForFormLike);
+    for (let scriptName of Object.keys(SCRIPTS)) {
+      info("Starting testcase with script " + scriptName + ": " + JSON.stringify(tc));
+      let loadedPromise = new Promise((resolve) => {
+        loginFrame.addEventListener("load", function frameLoaded() {
+          loginFrame.removeEventListener("load", frameLoaded);
+          resolve();
+        });
+      });
+      loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
+      yield loadedPromise;
 
-    info("Calling _onFormSubmit with FormLike");
-    let processedPromise = getSubmitMessage();
-    LoginManagerContent._onFormSubmit(formLike);
-
-    let submittedResult = yield processedPromise;
+      let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
+      frameDoc.documentElement.innerHTML = tc.document;
+      // Wait for the form to be processed before trying to submit.
+      yield promiseFormsProcessed();
+      let processedPromise = getSubmitMessage();
+      info("Running " + scriptName + " script to cause a submission");
+      frameDoc.defaultView.eval(SCRIPTS[scriptName]);
 
-    // Check data sent via RemoteLogins:onFormSubmit
-    is(submittedResult.hostname, tc.hostname, "Check hostname");
-    is(submittedResult.formSubmitURL, tc.formSubmitURL, "Check formSubmitURL");
+      let submittedResult = yield processedPromise;
+
+      // Check data sent via RemoteLogins:onFormSubmit
+      is(submittedResult.hostname, tc.hostname, "Check hostname");
+      is(submittedResult.formSubmitURL, tc.formSubmitURL, "Check formSubmitURL");
 
-    if (tc.usernameFieldValue === null) {
-      is(submittedResult.usernameField, tc.usernameFieldValue, "Check usernameField");
-    } else {
-      is(submittedResult.usernameField.value, tc.usernameFieldValue, "Check usernameField");
-    }
+      if (tc.usernameFieldValue === null) {
+        is(submittedResult.usernameField, tc.usernameFieldValue, "Check usernameField");
+      } else {
+        is(submittedResult.usernameField.value, tc.usernameFieldValue, "Check usernameField");
+      }
 
-    is(submittedResult.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue");
+      is(submittedResult.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue");
 
-    if (tc.oldPasswordFieldValue === null) {
-      is(submittedResult.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
-    } else {
-      is(submittedResult.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
+      if (tc.oldPasswordFieldValue === null) {
+        is(submittedResult.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
+      } else {
+        is(submittedResult.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
+      }
     }
   }
 });
 
 </script>
 
 <p id="display"></p>
 
 <div id="content">
-  <iframe id="loginFrame" src="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/blank.html"></iframe>
+  <iframe id="loginFrame" src="http://test1.mochi.test:8888/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"></iframe>
 </div>
 <pre id="test"></pre>
 </body>
 </html>
copy from toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
copy to toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
--- a/toolkit/components/passwordmgr/test/mochitest/test_formless_submit.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_formless_submit_navigation_negative.html
@@ -1,183 +1,121 @@
 <!DOCTYPE html>
 <html>
 <head>
   <meta charset="utf-8">
-  <title>Test capturing of fields outside of a form</title>
+  <title>Test no capturing of fields outside of a form due to navigation</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script src="/tests/SimpleTest/SpawnTask.js"></script>
   <script src="pwmgr_common.js"></script>
   <link rel="stylesheet" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 <script type="application/javascript;version=1.8">
 const LMCBackstagePass = SpecialPowers.Cu.import("resource://gre/modules/LoginManagerContent.jsm");
 const { LoginManagerContent, FormLikeFactory } = LMCBackstagePass;
 
+SimpleTest.requestFlakyTimeout("Testing that a message doesn't arrive");
+
 let chromeScript = runInParent(SimpleTest.getTestFileURL("pwmgr_common.js"));
 
 let loadPromise = new Promise(resolve => {
   document.addEventListener("DOMContentLoaded", () => {
     document.getElementById("loginFrame").addEventListener("load", (evt) => {
       resolve();
     });
   });
 });
 
+function submissionProcessed(...args) {
+  ok(false, "No formSubmissionProcessed should occur in this test");
+  info("got: " + JSON.stringify(args));
+}
+
 add_task(function* setup() {
+  yield SpecialPowers.pushPrefEnv({
+    set: [
+      ["signon.formlessCapture.enabled", true],
+    ],
+  });
+
   info("Waiting for page and frame loads");
   yield loadPromise;
 
-  yield loadRecipes({
-    siteRecipes: [{
-      hosts: ["mochi.test:8888"],
-      usernameSelector: "input[name='recipeuname']",
-      passwordSelector: "input[name='recipepword']",
-    }],
+  chromeScript.addMessageListener("formSubmissionProcessed", submissionProcessed);
+
+  SimpleTest.registerCleanupFunction(() => {
+    chromeScript.removeMessageListener("formSubmissionProcessed", submissionProcessed);
   });
 });
 
-const DEFAULT_ORIGIN = "http://mochi.test:8888";
+const DEFAULT_ORIGIN = "http://test1.mochi.test:8888";
+const SCRIPTS = {
+  PUSHSTATE: `history.pushState({}, "Pushed state", "?pushed");`,
+  WINDOW_LOCATION: `window.location = "data:text/html;charset=utf-8,window.location";`,
+  WINDOW_LOCATION_RELOAD: `window.location.reload();`,
+  HISTORY_BACK: `history.back();`,
+  HISTORY_GO_MINUS1: `history.go(-1);`,
+};
 const TESTCASES = [
-  {
-    // Inputs
-    document: `<input type=password value="pass1">`,
-    inputIndexForFormLike: 0,
-
-    // Expected outputs similar to RemoteLogins:onFormSubmit
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: null,
-    newPasswordFieldValue: "pass1",
-    oldPasswordFieldValue: null,
-  },
+  // Begin test cases that shouldn't trigger capture.
   {
-    document: `<input value="user1">
-      <input type=password value="pass1">`,
-    inputIndexForFormLike: 0,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass1",
-    oldPasswordFieldValue: null,
-  },
-  {
-    document: `<input value="user1">
-      <input type=password value="pass1">`,
-    inputIndexForFormLike: 1,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass1",
-    oldPasswordFieldValue: null,
+    // For now we don't trigger upon navigation if <form> is used.
+    document: `<form><input type=password value="pass1"></form>`,
   },
   {
-    document: `<input value="user1">
-      <input type=password value="pass1">
-      <input type=password value="pass2">`,
-    inputIndexForFormLike: 2,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass2",
-    oldPasswordFieldValue: "pass1",
-  },
-  {
-    document: `<input value="user1">
-      <input type=password value="pass1">
-      <input type=password value="pass2">
-      <input type=password value="pass2">`,
-    inputIndexForFormLike: 3,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass2",
-    oldPasswordFieldValue: "pass1",
+    // Empty password field
+    document: `<input type=password value="">`,
   },
   {
-    document: `<input value="user1">
-      <input type=password value="user2" form="form1">
-      <input type=password value="pass1">
-      <form id="form1">
-        <input value="user3">
-        <input type=password value="pass2">
-      </form>`,
-    inputIndexForFormLike: 2,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "user1",
-    newPasswordFieldValue: "pass1",
-    oldPasswordFieldValue: null,
-  },
-  {
-    document: `<!-- recipe field override -->
-      <input name="recipeuname" value="username from recipe">
-      <input value="default field username">
-      <input type=password value="pass1">
-      <input name="recipepword" type=password value="pass2">`,
-    inputIndexForFormLike: 2,
-    hostname: DEFAULT_ORIGIN,
-    formSubmitURL: DEFAULT_ORIGIN,
-    usernameFieldValue: "username from recipe",
-    newPasswordFieldValue: "pass2",
-    oldPasswordFieldValue: null,
+    // Test with an input that would normally be captured but with SCRIPTS that
+    // shouldn't trigger capture.
+    document: `<input type=password value="pass2">`,
+    wouldCapture: true,
   },
 ];
 
-function getSubmitMessage() {
-  info("getSubmitMessage");
-  return new Promise((resolve, reject) => {
-    chromeScript.addMessageListener("formSubmissionProcessed", function processed(...args) {
-      info("got formSubmissionProcessed");
-      chromeScript.removeMessageListener("formSubmissionProcessed", processed);
-      resolve(...args);
-    });
-  });
-}
-
 add_task(function* test() {
   let loginFrame = document.getElementById("loginFrame");
-  let frameDoc = loginFrame.contentWindow.document;
 
   for (let tc of TESTCASES) {
-    info("Starting testcase: " + JSON.stringify(tc));
-    frameDoc.documentElement.innerHTML = tc.document;
-    let inputForFormLike = frameDoc.querySelectorAll("input")[tc.inputIndexForFormLike];
-
-    let formLike = FormLikeFactory.createFromField(inputForFormLike);
-
-    info("Calling _onFormSubmit with FormLike");
-    let processedPromise = getSubmitMessage();
-    LoginManagerContent._onFormSubmit(formLike);
-
-    let submittedResult = yield processedPromise;
+    for (let scriptName of Object.keys(SCRIPTS)) {
+      if (tc.wouldCapture && ["PUSHSTATE", "WINDOW_LOCATION"].includes(scriptName)) {
+        // Don't run scripts that should actually capture for this testcase.
+        continue;
+      }
 
-    // Check data sent via RemoteLogins:onFormSubmit
-    is(submittedResult.hostname, tc.hostname, "Check hostname");
-    is(submittedResult.formSubmitURL, tc.formSubmitURL, "Check formSubmitURL");
+      info("Starting testcase with script " + scriptName + ": " + JSON.stringify(tc));
+      let loadedPromise = new Promise((resolve) => {
+        loginFrame.addEventListener("load", function frameLoaded() {
+          loginFrame.removeEventListener("load", frameLoaded);
+          resolve();
+        });
+      });
+      loginFrame.src = DEFAULT_ORIGIN + "/tests/toolkit/components/passwordmgr/test/mochitest/blank.html";
+      yield loadedPromise;
 
-    if (tc.usernameFieldValue === null) {
-      is(submittedResult.usernameField, tc.usernameFieldValue, "Check usernameField");
-    } else {
-      is(submittedResult.usernameField.value, tc.usernameFieldValue, "Check usernameField");
-    }
+      let frameDoc = SpecialPowers.wrap(loginFrame.contentWindow).document;
+      frameDoc.documentElement.innerHTML = tc.document;
+
+      // Wait for the form to be processed before trying to submit.
+      yield promiseFormsProcessed();
 
-    is(submittedResult.newPasswordField.value, tc.newPasswordFieldValue, "Check newPasswordFieldValue");
+      info("Running " + scriptName + " script to check for a submission");
+      frameDoc.defaultView.eval(SCRIPTS[scriptName]);
 
-    if (tc.oldPasswordFieldValue === null) {
-      is(submittedResult.oldPasswordField, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
-    } else {
-      is(submittedResult.oldPasswordField.value, tc.oldPasswordFieldValue, "Check oldPasswordFieldValue");
+      // Wait for 500ms to see if the promise above resolves.
+      yield new Promise(resolve => setTimeout(resolve, 500));
+      ok(true, "Done waiting for captures");
     }
   }
 });
 
 </script>
 
 <p id="display"></p>
 
 <div id="content">
-  <iframe id="loginFrame" src="http://mochi.test:8888/tests/toolkit/components/passwordmgr/test/blank.html"></iframe>
+  <iframe id="loginFrame" src="http://test1.mochi.test:8888/tests/toolkit/components/passwordmgr/test/mochitest/blank.html"></iframe>
 </div>
 <pre id="test"></pre>
 </body>
 </html>
--- a/toolkit/components/printing/content/printPreviewBindings.xml
+++ b/toolkit/components/printing/content/printPreviewBindings.xml
@@ -26,29 +26,29 @@
         oncommand="this.parentNode.print();" icon="print"/>
 
       <xul:button label="&pageSetup.label;" accesskey="&pageSetup.accesskey;"
         oncommand="this.parentNode.doPageSetup();"/>
 
       <xul:vbox align="center" pack="center">
         <xul:label value="&page.label;" accesskey="&page.accesskey;" control="pageNumber"/>
       </xul:vbox>
-      <xul:toolbarbutton class="home-arrow tabbable"
+      <xul:toolbarbutton anonid="navigateHome" class="navigate-button tabbable"
         oncommand="parentNode.navigate(0, 0, 'home');" tooltiptext="&homearrow.tooltip;"/>
-      <xul:toolbarbutton class="previous-arrow tabbable"
+      <xul:toolbarbutton anonid="navigatePrevious" class="navigate-button tabbable"
         oncommand="parentNode.navigate(-1, 0, 0);" tooltiptext="&previousarrow.tooltip;"/>
       <xul:hbox align="center" pack="center">
         <xul:textbox id="pageNumber" size="3" value="1" min="1" type="number"
           hidespinbuttons="true" onchange="navigate(0, this.valueNumber, 0);"/>
         <xul:label value="&of.label;"/>
         <xul:label value="1"/>
       </xul:hbox>
-      <xul:toolbarbutton class="next-arrow tabbable"
+      <xul:toolbarbutton anonid="navigateNext" class="navigate-button tabbable"
         oncommand="parentNode.navigate(1, 0, 0);" tooltiptext="&nextarrow.tooltip;"/>
-      <xul:toolbarbutton class="end-arrow tabbable"
+      <xul:toolbarbutton anonid="navigateEnd" class="navigate-button tabbable"
         oncommand="parentNode.navigate(0, 0, 'end');" tooltiptext="&endarrow.tooltip;"/>
 
       <xul:toolbarseparator class="toolbarseparator-primary"/>
       <xul:vbox align="center" pack="center">
         <xul:label value="&scale.label;" accesskey="&scale.accesskey;" control="scale"/>
       </xul:vbox>
 
       <xul:hbox align="center" pack="center">
@@ -152,16 +152,23 @@
           if (!Services.prefs.getBoolPref("print.use_simplify_page")) {
             this.mSimplifyPageCheckbox.hidden = true;
             this.mSimplifyPageToolbarSeparator.hidden = true;
           }
           this.mPPBrowser = aPPBrowser;
           this.mMessageManager = aPPBrowser.messageManager;
           this.mMessageManager.addMessageListener("Printing:Preview:UpdatePageCount", this);
           this.updateToolbar();
+
+          let $ = id => document.getAnonymousElementByAttribute(this, "anonid", id);
+          let ltr = document.documentElement.matches(":root:-moz-locale-dir(ltr)");
+          $("navigateHome").label = ltr ? "⏮" : "⏭";
+          $("navigatePrevious").label = ltr ? "◂" : "▸";
+          $("navigateNext").label = ltr ? "▸" : "◂";
+          $("navigateEnd").label = ltr ? "⏭" : "⏮";
         ]]>
         </body>
       </method>
 
       <method name="doPageSetup">
         <body>
         <![CDATA[
           var didOK = PrintUtils.showPageSetup();
--- a/toolkit/themes/linux/global/printPreview.css
+++ b/toolkit/themes/linux/global/printPreview.css
@@ -1,47 +1,19 @@
 /* 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/. */
 
-/* ..... page navigation ..... */
-
-.home-arrow {
-  list-style-image: url("moz-icon://stock/gtk-goto-first-ltr?size=menu");
-}
-
-.home-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("moz-icon://stock/gtk-goto-first-rtl?size=menu");
-}
-
-.end-arrow {
-  list-style-image: url("moz-icon://stock/gtk-goto-last-ltr?size=menu");
-}
-
-.end-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("moz-icon://stock/gtk-goto-last-rtl?size=menu");
+.navigate-button {
+  min-width: 1.9em;
 }
 
-.previous-arrow {
-  list-style-image: url("moz-icon://stock/gtk-go-back-ltr?size=menu");
-}
-
-.previous-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("moz-icon://stock/gtk-go-back-rtl?size=menu");
+.navigate-button > .toolbarbutton-icon {
+  display: none;
 }
 
-.next-arrow {
-  list-style-image: url("moz-icon://stock/gtk-go-forward-ltr?size=menu");
-}
-
-.next-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("moz-icon://stock/gtk-go-forward-rtl?size=menu");
-}
-
-/* ..... orientation ..... */
-
 .toolbar-portrait-page {
   list-style-image: url("moz-icon://stock/gtk-orientation-portrait?size=button");
 }
 
 .toolbar-landscape-page {
   list-style-image: url("moz-icon://stock/gtk-orientation-landscape?size=button");
 }
--- a/toolkit/themes/shared/non-mac.jar.inc.mn
+++ b/toolkit/themes/shared/non-mac.jar.inc.mn
@@ -83,20 +83,16 @@
   skin/classic/global/icons/Search-close.png               (../../windows/global/icons/Search-close.png)
   skin/classic/global/icons/Search-glass.png               (../../windows/global/icons/Search-glass.png)
   skin/classic/global/icons/tabprompts-bgtexture.png       (../../windows/global/icons/tabprompts-bgtexture.png)
   skin/classic/global/icons/Warning.png                    (../../windows/global/icons/Warning.png)
   skin/classic/global/icons/warning-large.png              (../../windows/global/icons/warning-large.png)
   skin/classic/global/icons/warning-16.png                 (../../windows/global/icons/warning-16.png)
   skin/classic/global/icons/warning-64.png                 (../../windows/global/icons/warning-64.png)
   skin/classic/global/icons/windowControls.png             (../../windows/global/icons/windowControls.png)
-  skin/classic/global/printpreview/arrow-left.png          (../../windows/global/printpreview/arrow-left.png)
-  skin/classic/global/printpreview/arrow-left-end.png      (../../windows/global/printpreview/arrow-left-end.png)
-  skin/classic/global/printpreview/arrow-right.png         (../../windows/global/printpreview/arrow-right.png)
-  skin/classic/global/printpreview/arrow-right-end.png     (../../windows/global/printpreview/arrow-right-end.png)
   skin/classic/global/radio/radio-check.gif                (../../windows/global/radio/radio-check.gif)
   skin/classic/global/radio/radio-check-dis.gif            (../../windows/global/radio/radio-check-dis.gif)
   skin/classic/global/scrollbar/slider.gif                 (../../windows/global/scrollbar/slider.gif)
   skin/classic/global/splitter/grip-bottom.gif             (../../windows/global/splitter/grip-bottom.gif)
   skin/classic/global/splitter/grip-top.gif                (../../windows/global/splitter/grip-top.gif)
   skin/classic/global/splitter/grip-left.gif               (../../windows/global/splitter/grip-left.gif)
   skin/classic/global/splitter/grip-right.gif              (../../windows/global/splitter/grip-right.gif)
   skin/classic/global/toolbar/chevron.gif                  (../../windows/global/toolbar/chevron.gif)
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -75,20 +75,16 @@ toolkit.jar:
   skin/classic/global/icons/Search-close-XP.png                  (icons/Search-close-XP.png)
   skin/classic/global/icons/Search-glass-XP.png                  (icons/Search-glass-XP.png)
   skin/classic/global/icons/sslWarning-XP.png                    (icons/sslWarning-XP.png)
   skin/classic/global/icons/Warning-XP.png                       (icons/Warning-XP.png)
   skin/classic/global/icons/warning-large-XP.png                 (icons/warning-large-XP.png)
   skin/classic/global/icons/warning-16-XP.png                    (icons/warning-16-XP.png)
   skin/classic/global/icons/warning-64-XP.png                    (icons/warning-64-XP.png)
   skin/classic/global/icons/windowControls-XP.png                (icons/windowControls-XP.png)
-  skin/classic/global/printpreview/arrow-left-XP.png             (printpreview/arrow-left-XP.png)
-  skin/classic/global/printpreview/arrow-left-end-XP.png         (printpreview/arrow-left-end-XP.png)
-  skin/classic/global/printpreview/arrow-right-XP.png            (printpreview/arrow-right-XP.png)
-  skin/classic/global/printpreview/arrow-right-end-XP.png        (printpreview/arrow-right-end-XP.png)
   skin/classic/global/toolbar/spring-XP.png                      (toolbar/spring-XP.png)
   skin/classic/global/tree/sort-asc-XP.png                       (tree/sort-asc-XP.png)
   skin/classic/global/tree/sort-dsc-XP.png                       (tree/sort-dsc-XP.png)
   skin/classic/global/tree/twisty.svg                            (tree/twisty.svg)
   skin/classic/global/tree/twisty-XP.svg                         (tree/twisty-XP.svg)
   skin/classic/global/tree/twisty-Vista78.svg                    (tree/twisty-Vista78.svg)
 
 #if MOZ_BUILD_APP == browser
@@ -119,20 +115,16 @@ toolkit.jar:
 % override chrome://global/skin/icons/Search-close.png            chrome://global/skin/icons/Search-close-XP.png           osversion<6
 % override chrome://global/skin/icons/Search-glass.png            chrome://global/skin/icons/Search-glass-XP.png           osversion<6
 % override chrome://global/skin/icons/sslWarning.png              chrome://global/skin/icons/sslWarning-XP.png             osversion<6
 % override chrome://global/skin/icons/Warning.png                 chrome://global/skin/icons/Warning-XP.png                osversion<6
 % override chrome://global/skin/icons/warning-large.png           chrome://global/skin/icons/warning-large-XP.png          osversion<6
 % override chrome://global/skin/icons/warning-16.png              chrome://global/skin/icons/warning-16-XP.png             osversion<6
 % override chrome://global/skin/icons/warning-64.png              chrome://global/skin/icons/warning-64-XP.png             osversion<6
 % override chrome://global/skin/icons/windowControls.png          chrome://global/skin/icons/windowControls-XP.png         osversion<6
-% override chrome://global/skin/printpreview/arrow-left.png       chrome://global/skin/printpreview/arrow-left-XP.png      osversion<6
-% override chrome://global/skin/printpreview/arrow-left-end.png   chrome://global/skin/printpreview/arrow-left-end-XP.png  osversion<6
-% override chrome://global/skin/printpreview/arrow-right.png      chrome://global/skin/printpreview/arrow-right-XP.png     osversion<6
-% override chrome://global/skin/printpreview/arrow-right-end.png  chrome://global/skin/printpreview/arrow-right-end-XP.png osversion<6
 % override chrome://global/skin/toolbar/spring.png                chrome://global/skin/toolbar/spring-XP.png               osversion<6
 % override chrome://global/skin/tree/sort-asc.png                 chrome://global/skin/tree/sort-asc-XP.png                osversion<6
 % override chrome://global/skin/tree/sort-dsc.png                 chrome://global/skin/tree/sort-dsc-XP.png                osversion<6
 
 % override chrome://global/skin/icons/close.png                   chrome://global/skin/icons/close-XPVista7.png            osversion<=6.1
 % override chrome://global/skin/icons/close@2x.png                chrome://global/skin/icons/close-XPVista7@2x.png         osversion<=6.1
 % override chrome://global/skin/icons/close-inverted.png          chrome://global/skin/icons/close-inverted-XPVista7.png   osversion<=6.1
 % override chrome://global/skin/icons/close-inverted@2x.png       chrome://global/skin/icons/close-inverted-XPVista7@2x.png osversion<=6.1
--- a/toolkit/themes/windows/global/printPreview.css
+++ b/toolkit/themes/windows/global/printPreview.css
@@ -1,71 +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/. */
 
-/* ..... page navigation ..... */
-
-.home-arrow,
-.end-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://global/skin/printpreview/arrow-left-end.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.home-arrow:hover,
-.end-arrow:-moz-locale-dir(rtl):hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-.end-arrow,
-.home-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://global/skin/printpreview/arrow-right-end.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.end-arrow:hover,
-.home-arrow:-moz-locale-dir(rtl):hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
+.navigate-button {
+  min-width: 1.9em;
 }
 
-.previous-arrow,
-.next-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://global/skin/printpreview/arrow-left.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
-}
-
-.previous-arrow:hover,
-.next-arrow:-moz-locale-dir(rtl):hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-.next-arrow,
-.previous-arrow:-moz-locale-dir(rtl) {
-  list-style-image: url("chrome://global/skin/printpreview/arrow-right.png");
-  -moz-image-region: rect(0, 16px, 16px, 0);
+.navigate-button > .toolbarbutton-icon {
+  display: none;
 }
 
-.next-arrow:hover,
-.previous-arrow:-moz-locale-dir(rtl):hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-/* ...... scale in/decrement ..... */
-
-.up-arrow {
-  list-style-image: url("chrome://global/skin/arrow/arrow-up-sharp.gif");
-}
-
-.down-arrow {
-  list-style-image: url("chrome://global/skin/arrow/arrow-dn-sharp.gif");
-}
-
-
-/* ..... orientation ..... */
-
 .toolbar-portrait-page {
   list-style-image: url("chrome://global/skin/icons/Print-preview.png");
   -moz-image-region: rect(0px 16px 16px 0px);
 }
 
 .toolbar-landscape-page {
   list-style-image: url("chrome://global/skin/icons/Print-preview.png");
   -moz-image-region: rect(0px 32px 16px 16px);
deleted file mode 100644
index 7ed1d7ded0a92470bbdaee66c2bbc851cd6efa7b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 642327a4f059021185e35be9951105f2d72b04c2..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e4e60a07520501a77523c1b91d4183b13a495417..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 02fc98d45f4fa142314bc708cc38c5713ebacc87..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f779f6c8958a6e28a7b43ce16a69abe8797d6fe6..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d5d7a50a2f77332fd11152a26cbad649ff5298a1..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5efbe3a706d3cad09f9e8dfd0c780662419fa2a0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index e9d68e836d9df946650c0ec0b3336a7e27ac9fd6..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001