Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Tue, 21 Feb 2017 10:58:47 -0500
changeset 373030 c75a80093d4a2567a2da7673fa7d6214b6cc8215
parent 373029 56c981ae61b0ef4b39eceac8bd2df237bc4b651f (current diff)
parent 372984 b69d4316561aa125612509a202922455d2de03e5 (diff)
child 373031 e4e9dcee968361244f840c6ef64b035e33e5855b
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.0a1
Merge m-c to graphics MozReview-Commit-ID: EyM1zEhgtGP
devtools/client/netmonitor/events.js
devtools/client/netmonitor/filter-predicates.js
devtools/client/netmonitor/l10n.js
devtools/client/netmonitor/prefs.js
devtools/client/netmonitor/request-utils.js
devtools/client/netmonitor/sort-predicates.js
devtools/client/netmonitor/utils/filter-predicates.js
devtools/client/netmonitor/utils/l10n.js
devtools/client/netmonitor/utils/prefs.js
devtools/client/netmonitor/utils/request-utils.js
devtools/client/netmonitor/utils/sort-predicates.js
devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.css
devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js
dom/canvas/test/chrome/nonchrome_webgl_debug_renderer_info.html
dom/media/platforms/wrappers/MediaDataDecoderProxy.cpp
dom/media/platforms/wrappers/MediaDataDecoderProxy.h
dom/presentation/provider/DisplayDeviceProvider.cpp
dom/presentation/provider/DisplayDeviceProvider.h
gfx/thebes/gfxPrefs.h
js/src/jit-test/tests/wasm/spec/soft-fail.wast
js/src/jit-test/tests/wasm/spec/soft-fail.wast.js
js/src/jit-test/tests/wasm/unreachable.js
servo/support/android/apk/jni/main.c
testing/talos/talos/gecko_profile.py
testing/talos/talos/profiler/profiling.py
testing/talos/talos/profiler/sps.py
testing/talos/talos/sps_profile.py
testing/web-platform/meta/IndexedDB/idbdatabase_deleteObjectStore.htm.ini
third_party/rust/app_units-0.3.0/.cargo-checksum.json
third_party/rust/app_units-0.3.0/Cargo.toml
third_party/rust/app_units-0.3.0/src/app_unit.rs
third_party/rust/euclid-0.10.5/.cargo-checksum.json
third_party/rust/euclid-0.10.5/.cargo-ok
third_party/rust/euclid-0.10.5/.gitignore
third_party/rust/euclid-0.10.5/.travis.yml
third_party/rust/euclid-0.10.5/COPYRIGHT
third_party/rust/euclid-0.10.5/Cargo.toml
third_party/rust/euclid-0.10.5/LICENSE-APACHE
third_party/rust/euclid-0.10.5/LICENSE-MIT
third_party/rust/euclid-0.10.5/README.md
third_party/rust/euclid-0.10.5/src/approxeq.rs
third_party/rust/euclid-0.10.5/src/length.rs
third_party/rust/euclid-0.10.5/src/lib.rs
third_party/rust/euclid-0.10.5/src/macros.rs
third_party/rust/euclid-0.10.5/src/matrix2d.rs
third_party/rust/euclid-0.10.5/src/matrix4d.rs
third_party/rust/euclid-0.10.5/src/num.rs
third_party/rust/euclid-0.10.5/src/point.rs
third_party/rust/euclid-0.10.5/src/rect.rs
third_party/rust/euclid-0.10.5/src/scale_factor.rs
third_party/rust/euclid-0.10.5/src/side_offsets.rs
third_party/rust/euclid-0.10.5/src/size.rs
third_party/rust/euclid-0.10.5/src/trig.rs
third_party/rust/euclid/.cargo-checksum.json
third_party/rust/euclid/Cargo.toml
third_party/rust/euclid/src/length.rs
toolkit/library/gtest/rust/Cargo.lock
toolkit/library/rust/Cargo.lock
toolkit/locales/en-US/chrome/global/dateFormat.properties
tools/profiler/core/platform-linux-android.cpp
tools/profiler/core/platform-linux.cc
tools/profiler/core/platform-macos.cc
tools/profiler/core/platform-macos.cpp
tools/profiler/core/platform-win32.cc
tools/profiler/core/platform-win32.cpp
--- a/accessible/atk/AccessibleWrap.cpp
+++ b/accessible/atk/AccessibleWrap.cpp
@@ -609,18 +609,17 @@ getNameCB(AtkObject* aAtkObj)
 
   return aAtkObj->name;
 }
 
 static void
 MaybeFireNameChange(AtkObject* aAtkObj, const nsString& aNewName)
 {
   NS_ConvertUTF16toUTF8 newNameUTF8(aNewName);
-  if (aAtkObj->name &&
-      !strncmp(aAtkObj->name, newNameUTF8.get(), newNameUTF8.Length()))
+  if (aAtkObj->name && !strcmp(aAtkObj->name, newNameUTF8.get()))
     return;
 
   // Below we duplicate the functionality of atk_object_set_name(),
   // but without calling atk_object_get_name(). Instead of
   // atk_object_get_name() we directly access aAtkObj->name. This is because
   // atk_object_get_name() would call getNameCB() which would call
   // MaybeFireNameChange() (or atk_object_set_name() before this problem was
   // fixed) and we would get an infinite recursion.
--- a/accessible/ipc/win/ProxyAccessible.cpp
+++ b/accessible/ipc/win/ProxyAccessible.cpp
@@ -334,16 +334,17 @@ ConvertBSTRAttributesToArray(const nsASt
         tokens[eName].Truncate();
         tokens[eValue].Truncate();
         ++itr;
         continue;
       default:
         break;
     }
     tokens[state] += *itr;
+    ++itr;
   }
   return true;
 }
 
 void
 ProxyAccessible::Attributes(nsTArray<Attribute>* aAttrs) const
 {
   aAttrs->Clear();
--- a/accessible/tests/browser/e10s/browser.ini
+++ b/accessible/tests/browser/e10s/browser.ini
@@ -10,67 +10,42 @@ support-files =
   doc_treeupdate_whitespace.html
   !/accessible/tests/browser/shared-head.js
   !/accessible/tests/mochitest/*.js
   !/accessible/tests/mochitest/letters.gif
   !/accessible/tests/mochitest/moz.png
 
 # Caching tests
 [browser_caching_attributes.js]
-skip-if = e10s && os == 'win' # Bug 1288839
 [browser_caching_description.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_caching_name.js]
 skip-if = e10s && os == 'win' && debug # Bug 1338034, leaks
 [browser_caching_relations.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_caching_states.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_caching_value.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 
 # Events tests
 [browser_events_caretmove.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_events_hide.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_events_show.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_events_statechange.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_events_textchange.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 
 # Tree update tests
 [browser_treeupdate_ariadialog.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_ariaowns.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_canvas.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_cssoverflow.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_doc.js]
 skip-if = e10s && os == 'win' # Bug 1288839
 [browser_treeupdate_gencontent.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_hidden.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_imagemap.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_list.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_list_editabledoc.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_listener.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_optgroup.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_removal.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_table.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_textleaf.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_visibility.js]
-skip-if = e10s && os == 'win' && os_version == '5.1'
 [browser_treeupdate_whitespace.js]
 skip-if = true # Failing due to incorrect index of test container children on document load.
--- a/accessible/tests/mochitest/attributes.js
+++ b/accessible/tests/mochitest/attributes.js
@@ -264,17 +264,17 @@ function fontFamily(aComputedStyle)
  * @param aFontSize    [in] font size
  * @param aFontWeight  [in, optional] kBoldFontWeight or kNormalFontWeight,
  *                      default value is kNormalFontWeight
  */
 function buildDefaultTextAttrs(aID, aFontSize, aFontWeight, aFontFamily)
 {
   var elm = getNode(aID);
   var computedStyle = document.defaultView.getComputedStyle(elm);
-  var bgColor = computedStyle.backgroundColor == "transparent" ?
+  var bgColor = computedStyle.backgroundColor == "rgba(0, 0, 0, 0)" ?
     "rgb(255, 255, 255)" : computedStyle.backgroundColor;
 
   var defAttrs = {
     "font-style": computedStyle.fontStyle,
     "font-size": aFontSize,
     "background-color": bgColor,
     "font-weight": aFontWeight ? aFontWeight : kNormalFontWeight,
     "color": computedStyle.color,
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1530,18 +1530,19 @@ pref("browser.esedbreader.loglevel", "Er
 pref("browser.laterrun.enabled", false);
 
 pref("browser.migrate.automigrate.enabled", false);
 // 4 here means the suggestion notification will be automatically
 // hidden the 4th day, so it will actually be shown on 3 different days.
 pref("browser.migrate.automigrate.daysToOfferUndo", 4);
 pref("browser.migrate.automigrate.ui.enabled", true);
 
-pref("browser.migrate.chrome.history.limit", 0);
-pref("browser.migrate.chrome.history.maxAgeInDays", 0);
+// See comments in bug 1340115 on how we got to these numbers.
+pref("browser.migrate.chrome.history.limit", 2000);
+pref("browser.migrate.chrome.history.maxAgeInDays", 180);
 
 // Enable browser frames for use on desktop.  Only exposed to chrome callers.
 pref("dom.mozBrowserFramesEnabled", true);
 
 pref("extensions.pocket.enabled", true);
 
 pref("signon.schemeUpgrades", true);
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -6458,59 +6458,62 @@ var MailIntegration = {
          .getService(Ci.nsIExternalProtocolService);
     if (extProtocolSvc)
       extProtocolSvc.loadUrl(aURL);
   }
 };
 
 function BrowserOpenAddonsMgr(aView) {
   return new Promise(resolve => {
-    if (aView) {
-      let emWindow;
-      let browserWindow;
-
-      var receivePong = function(aSubject, aTopic, aData) {
-        let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIWebNavigation)
-                                 .QueryInterface(Ci.nsIDocShellTreeItem)
-                                 .rootTreeItem
-                                 .QueryInterface(Ci.nsIInterfaceRequestor)
-                                 .getInterface(Ci.nsIDOMWindow);
-        if (!emWindow || browserWin == window /* favor the current window */) {
-          emWindow = aSubject;
-          browserWindow = browserWin;
-        }
+    let emWindow;
+    let browserWindow;
+
+    var receivePong = function(aSubject, aTopic, aData) {
+      let browserWin = aSubject.QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIWebNavigation)
+                               .QueryInterface(Ci.nsIDocShellTreeItem)
+                               .rootTreeItem
+                               .QueryInterface(Ci.nsIInterfaceRequestor)
+                               .getInterface(Ci.nsIDOMWindow);
+      if (!emWindow || browserWin == window /* favor the current window */) {
+        emWindow = aSubject;
+        browserWindow = browserWin;
       }
-      Services.obs.addObserver(receivePong, "EM-pong", false);
-      Services.obs.notifyObservers(null, "EM-ping", "");
-      Services.obs.removeObserver(receivePong, "EM-pong");
-
-      if (emWindow) {
+    }
+    Services.obs.addObserver(receivePong, "EM-pong", false);
+    Services.obs.notifyObservers(null, "EM-ping", "");
+    Services.obs.removeObserver(receivePong, "EM-pong");
+
+    if (emWindow) {
+      if (aView) {
         emWindow.loadView(aView);
-        browserWindow.gBrowser.selectedTab =
-          browserWindow.gBrowser._getTabForContentWindow(emWindow);
-        emWindow.focus();
-        resolve(emWindow);
-        return;
       }
-    }
-
-    switchToTabHavingURI("about:addons", true);
-
-    if (aView) {
-      // This must be a new load, else the ping/pong would have
-      // found the window above.
-      Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
-        Services.obs.removeObserver(observer, aTopic);
+      browserWindow.gBrowser.selectedTab =
+        browserWindow.gBrowser._getTabForContentWindow(emWindow);
+      emWindow.focus();
+      resolve(emWindow);
+      return;
+    }
+
+    // This must be a new load, else the ping/pong would have
+    // found the window above.
+    let whereToOpen = (window.gBrowser && isTabEmpty(gBrowser.selectedTab)) ?
+                      "current" :
+                      "tab";
+    openUILinkIn("about:addons", whereToOpen);
+
+    Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
+      Services.obs.removeObserver(observer, aTopic);
+      if (aView) {
         aSubject.loadView(aView);
-        resolve(aSubject);
-      }, "EM-loaded", false);
-    } else {
-      resolve();
-    }
+      }
+      aSubject.QueryInterface(Ci.nsIDOMWindow);
+      aSubject.focus();
+      resolve(aSubject);
+    }, "EM-loaded", false);
   });
 }
 
 function AddKeywordForSearchField() {
   let mm = gBrowser.selectedBrowser.messageManager;
 
   let onMessage = (message) => {
     mm.removeMessageListener("ContextMenu:SearchFieldBookmarkData:Result", onMessage);
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -85,30 +85,30 @@ const PAGECONTENT_COLORS =
   "  .green { color: #800080; background-color: green; }" +
   "  .defaultColor { color: -moz-ComboboxText; }" +
   "  .defaultBackground { background-color: -moz-Combobox; }" +
   "</style>" +
   "<body><select id='one'>" +
   '  <option value="One" style="color: #fff; background-color: #f00;">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(255, 0, 0)"}</option>' +
   '  <option value="Two" class="blue">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgb(0, 0, 255)"}</option>' +
   '  <option value="Three" class="green">{"color": "rgb(128, 0, 128)", "backgroundColor": "rgb(0, 128, 0)"}</option>' +
-  '  <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
-  '  <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
-  '  <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "transparent", "unstyled": "true"}</option>' +
+  '  <option value="Four" class="defaultColor defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
+  '  <option value="Five" class="defaultColor">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
+  '  <option value="Six" class="defaultBackground">{"color": "-moz-ComboboxText", "backgroundColor": "rgba(0, 0, 0, 0)", "unstyled": "true"}</option>' +
   '  <option value="Seven" selected="true">{"unstyled": "true"}</option>' +
   "</select></body></html>";
 
 const PAGECONTENT_COLORS_ON_SELECT =
   "<html><head><style>" +
   "  #one { background-color: #7E3A3A; color: #fff }" +
   "</style>" +
   "<body><select id='one'>" +
-  '  <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
-  '  <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
-  '  <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "transparent"}</option>' +
+  '  <option value="One">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Two">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
+  '  <option value="Three">{"color": "rgb(255, 255, 255)", "backgroundColor": "rgba(0, 0, 0, 0)"}</option>' +
   '  <option value="Four" selected="true">{"end": "true"}</option>' +
   "</select></body></html>";
 
 const TRANSPARENT_SELECT =
   "<html><head><style>" +
   "  #one { background-color: transparent; }" +
   "</style>" +
   "<body><select id='one'>" +
--- a/browser/base/content/test/general/browser_windowactivation.js
+++ b/browser/base/content/test/general/browser_windowactivation.js
@@ -43,42 +43,42 @@ function reallyRunTests() {
   // while another window is focused. The third check is done after that window
   // is closed and the main window focused again. The fourth check is done after
   // switching to the second tab.
   window.messageManager.addMessageListener("Test:BackgroundColorChanged", function(message) {
     colorChangeNotifications++;
 
     switch (colorChangeNotifications) {
       case 1:
-        is(message.data.color, "transparent", "first window initial");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "first window initial");
         break;
       case 2:
-        is(message.data.color, "transparent", "second window initial");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "second window initial");
         runOtherWindowTests();
         break;
       case 3:
         is(message.data.color, "rgb(255, 0, 0)", "first window lowered");
         break;
       case 4:
         is(message.data.color, "rgb(255, 0, 0)", "second window lowered");
         sendGetBackgroundRequest(true);
         otherWindow.close();
         break;
       case 5:
-        is(message.data.color, "transparent", "first window raised");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "first window raised");
         break;
       case 6:
-        is(message.data.color, "transparent", "second window raised");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "second window raised");
         gBrowser.selectedTab = tab2;
         break;
       case 7:
-        is(message.data.color, "transparent", "first window after tab switch");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "first window after tab switch");
         break;
       case 8:
-        is(message.data.color, "transparent", "second window after tab switch");
+        is(message.data.color, "rgba(0, 0, 0, 0)", "second window after tab switch");
         finishTest();
         break;
       case 9:
         ok(false, "too many color change notifications");
         break;
     }
   });
 
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -190,16 +190,17 @@ var tests = [
       this.notification = showNotification(notifyObj);
       yield shown;
 
       ok(notifyObj.shownCallbackTriggered, "Should have triggered the shown event");
       ok(notifyObj.showingCallbackTriggered, "Should have triggered the showing event");
       // Reset to false so that we can ensure these are not fired a second time.
       notifyObj.shownCallbackTriggered = false;
       notifyObj.showingCallbackTriggered = false;
+      let timeShown = this.notification.timeShown;
 
       let promiseWin = BrowserTestUtils.waitForNewWindow();
       gBrowser.replaceTabWithWindow(firstTab);
       let win = yield promiseWin;
 
       let anchor = win.document.getElementById("default-notification-icon");
       win.PopupNotifications._reshowNotifications(anchor);
       ok(win.PopupNotifications.panel.childNodes.length == 0,
@@ -213,16 +214,18 @@ var tests = [
       ok(PopupNotifications.isPanelOpen,
          "Should have kept the popup on the first window");
       ok(!notifyObj.dismissalCallbackTriggered,
          "Should not have triggered a dismissed event");
       ok(!notifyObj.shownCallbackTriggered,
          "Should not have triggered a second shown event");
       ok(!notifyObj.showingCallbackTriggered,
          "Should not have triggered a second showing event");
+      ok(this.notification.timeShown > timeShown,
+         "should have updated timeShown to restart the security delay");
 
       this.notification.remove();
       gBrowser.removeTab(gBrowser.selectedTab);
       gBrowser.selectedTab = this.oldSelectedTab;
 
       goNext();
     }
   },
--- a/browser/components/extensions/ext-c-omnibox.js
+++ b/browser/components/extensions/ext-c-omnibox.js
@@ -9,18 +9,17 @@ var {
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("omnibox", "addon_child", context => {
   return {
     omnibox: {
       onInputChanged: new SingletonEventManager(context, "omnibox.onInputChanged", fire => {
         let listener = (text, id) => {
           fire.asyncWithoutClone(text, suggestions => {
-            // TODO: Switch to using callParentFunctionNoReturn once bug 1314903 is fixed.
-            context.childManager.callParentAsyncFunction("omnibox_internal.addSuggestions", [
+            context.childManager.callParentFunctionNoReturn("omnibox_internal.addSuggestions", [
               id,
               suggestions,
             ]);
           });
         };
         context.childManager.getParentEvent("omnibox_internal.onInputChanged").addListener(listener);
         return () => {
           context.childManager.getParentEvent("omnibox_internal.onInputChanged").removeListener(listener);
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -17,17 +17,17 @@ add_task(function* testPageActionPopup()
       },
     },
 
     files: {
       "popup-a.html": scriptPage("popup-a.js"),
       "popup-a.js": function() {
         window.onload = () => {
           let background = window.getComputedStyle(document.body).backgroundColor;
-          browser.test.assertEq("transparent", background);
+          browser.test.assertEq("rgba(0, 0, 0, 0)", background);
           browser.runtime.sendMessage("from-popup-a");
         };
         browser.runtime.onMessage.addListener(msg => {
           if (msg == "close-popup") {
             window.close();
           }
         });
       },
--- a/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_insertCSS.js
@@ -8,17 +8,17 @@ add_task(function* testExecuteScript() {
   let messageManagersSize = MessageChannel.messageManagers.size;
   let responseManagersSize = MessageChannel.responseManagers.size;
 
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
   async function background() {
     let tasks = [
       {
-        background: "transparent",
+        background: "rgba(0, 0, 0, 0)",
         foreground: "rgb(0, 113, 4)",
         promise: () => {
           return browser.tabs.insertCSS({
             file: "file2.css",
           });
         },
       },
       {
--- a/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_removeCSS.js
@@ -4,17 +4,17 @@
 
 add_task(function* testExecuteScript() {
   let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/", true);
 
   async function background() {
     let tasks = [
       // Insert CSS file.
       {
-        background: "transparent",
+        background: "rgba(0, 0, 0, 0)",
         foreground: "rgb(0, 113, 4)",
         promise: () => {
           return browser.tabs.insertCSS({
             file: "file2.css",
           });
         },
       },
       // Insert CSS code.
@@ -24,27 +24,27 @@ add_task(function* testExecuteScript() {
         promise: () => {
           return browser.tabs.insertCSS({
             code: "* { background: rgb(42, 42, 42) }",
           });
         },
       },
       // Remove CSS code again.
       {
-        background: "transparent",
+        background: "rgba(0, 0, 0, 0)",
         foreground: "rgb(0, 113, 4)",
         promise: () => {
           return browser.tabs.removeCSS({
             code: "* { background: rgb(42, 42, 42) }",
           });
         },
       },
       // Remove CSS file again.
       {
-        background: "transparent",
+        background: "rgba(0, 0, 0, 0)",
         foreground: "rgb(0, 0, 0)",
         promise: () => {
           return browser.tabs.removeCSS({
             file: "file2.css",
           });
         },
       },
       // Insert CSS code.
@@ -55,17 +55,17 @@ add_task(function* testExecuteScript() {
           return browser.tabs.insertCSS({
             code: "* { background: rgb(42, 42, 42) }",
             cssOrigin: "user",
           });
         },
       },
       // Remove CSS code again.
       {
-        background: "transparent",
+        background: "rgba(0, 0, 0, 0)",
         foreground: "rgb(0, 0, 0)",
         promise: () => {
           return browser.tabs.removeCSS({
             code: "* { background: rgb(42, 42, 42) }",
             cssOrigin: "user",
           });
         },
       },
--- a/browser/components/extensions/test/browser/head.js
+++ b/browser/components/extensions/test/browser/head.js
@@ -21,17 +21,16 @@ const {AppConstants} = Cu.import("resour
 const {CustomizableUI} = Cu.import("resource:///modules/CustomizableUI.jsm", {});
 
 // We run tests under two different configurations, from browser.ini and
 // browser-remote.ini. When running from browser-remote.ini, the tests are
 // copied to the sub-directory "test-oop-extensions", which we detect here, and
 // use to select our configuration.
 if (gTestPath.includes("test-oop-extensions")) {
   SpecialPowers.pushPrefEnv({set: [
-    ["dom.ipc.processCount.extension", 1],
     ["extensions.webextensions.remote", true],
   ]});
   // We don't want to reset this at the end of the test, so that we don't have
   // to spawn a new extension child process for each test unit.
   SpecialPowers.setIntPref("dom.ipc.keepProcessesAlive.extension", 1);
 }
 
 // Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable
--- a/browser/components/migration/MigrationUtils.jsm
+++ b/browser/components/migration/MigrationUtils.jsm
@@ -23,16 +23,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "BookmarkHTMLUtils",
                                   "resource://gre/modules/BookmarkHTMLUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
                                   "resource://gre/modules/LoginHelper.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
                                   "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ResponsivenessMonitor",
+                                  "resource://gre/modules/ResponsivenessMonitor.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Sqlite",
                                   "resource://gre/modules/Sqlite.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "TelemetryStopwatch",
                                   "resource://gre/modules/TelemetryStopwatch.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "WindowsRegistry",
                                   "resource://gre/modules/WindowsRegistry.jsm");
 
 var gMigrators = null;
@@ -207,17 +209,17 @@ this.MigratorPrototype = {
     let resources = this._getMaybeCachedResources(aProfile);
     if (!resources) {
       return [];
     }
     let types = resources.map(r => r.type);
     return types.reduce((a, b) => { a |= b; return a }, 0);
   },
 
-  getKey: function MP_getKey() {
+  getBrowserKey: function MP_getBrowserKey() {
     return this.contractID.match(/\=([^\=]+)$/)[1];
   },
 
   /**
    * DO NOT OVERRIDE - After deCOMing migration, the UI will just call
    * migrate for each resource.
    *
    * @see nsIBrowserProfileMigrator
@@ -232,50 +234,74 @@ this.MigratorPrototype = {
 
     // Used to periodically give back control to the main-thread loop.
     let unblockMainThread = function() {
       return new Promise(resolve => {
         Services.tm.mainThread.dispatch(resolve, Ci.nsIThread.DISPATCH_NORMAL);
       });
     };
 
-    let getHistogramForResourceType = resourceType => {
+    let getHistogramIdForResourceType = (resourceType, template) => {
       if (resourceType == MigrationUtils.resourceTypes.HISTORY) {
-        return "FX_MIGRATION_HISTORY_IMPORT_MS";
+        return template.replace("*", "HISTORY");
       }
       if (resourceType == MigrationUtils.resourceTypes.BOOKMARKS) {
-        return "FX_MIGRATION_BOOKMARKS_IMPORT_MS";
+        return template.replace("*", "BOOKMARKS");
       }
       if (resourceType == MigrationUtils.resourceTypes.PASSWORDS) {
-        return "FX_MIGRATION_LOGINS_IMPORT_MS";
+        return template.replace("*", "LOGINS");
       }
       return null;
     };
-    let maybeStartTelemetryStopwatch = (resourceType) => {
-      let histogram = getHistogramForResourceType(resourceType);
-      if (histogram) {
-        TelemetryStopwatch.startKeyed(histogram, this.getKey());
+
+    let browserKey = this.getBrowserKey();
+
+    let maybeStartTelemetryStopwatch = resourceType => {
+      let histogramId = getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_IMPORT_MS");
+      if (histogramId) {
+        TelemetryStopwatch.startKeyed(histogramId, browserKey);
       }
+      return histogramId;
     };
-    let maybeStopTelemetryStopwatch = (resourceType) => {
-      let histogram = getHistogramForResourceType(resourceType);
-      if (histogram) {
-        TelemetryStopwatch.finishKeyed(histogram, this.getKey());
+
+    let maybeStartResponsivenessMonitor = resourceType => {
+      let responsivenessMonitor;
+      let responsivenessHistogramId =
+        getHistogramIdForResourceType(resourceType, "FX_MIGRATION_*_JANK_MS");
+      if (responsivenessHistogramId) {
+        responsivenessMonitor = new ResponsivenessMonitor();
+      }
+      return {responsivenessMonitor, responsivenessHistogramId};
+    };
+
+    let maybeFinishResponsivenessMonitor = (responsivenessMonitor, histogramId) => {
+      if (responsivenessMonitor) {
+        let accumulatedDelay = responsivenessMonitor.finish();
+        if (histogramId) {
+          try {
+            Services.telemetry.getKeyedHistogramById(histogramId)
+                    .add(browserKey, accumulatedDelay);
+          } catch (ex) {
+            Cu.reportError(histogramId + ": " + ex);
+          }
+        }
       }
     };
 
     let collectQuantityTelemetry = () => {
-      try {
-        for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
-          let histogramId =
-            "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
-          let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
-          histogram.add(this.getKey(), MigrationUtils._importQuantities[resourceType]);
+      for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
+        let histogramId =
+          "FX_MIGRATION_" + resourceType.toUpperCase() + "_QUANTITY";
+        try {
+          Services.telemetry.getKeyedHistogramById(histogramId)
+                  .add(browserKey, MigrationUtils._importQuantities[resourceType]);
+        } catch (ex) {
+          Cu.reportError(histogramId + ": " + ex);
         }
-      } catch (ex) { /* Telemetry is exception-happy */ }
+      }
     };
 
     // Called either directly or through the bookmarks import callback.
     let doMigrate = Task.async(function*() {
       let resourcesGroupedByItems = new Map();
       resources.forEach(function(resource) {
         if (!resourcesGroupedByItems.has(resource.type)) {
           resourcesGroupedByItems.set(resource.type, new Set());
@@ -292,31 +318,38 @@ this.MigratorPrototype = {
 
       for (let resourceType of Object.keys(MigrationUtils._importQuantities)) {
         MigrationUtils._importQuantities[resourceType] = 0;
       }
       notify("Migration:Started");
       for (let [migrationType, itemResources] of resourcesGroupedByItems) {
         notify("Migration:ItemBeforeMigrate", migrationType);
 
-        maybeStartTelemetryStopwatch(migrationType);
+        let stopwatchHistogramId = maybeStartTelemetryStopwatch(migrationType);
+
+        let {responsivenessMonitor, responsivenessHistogramId} =
+          maybeStartResponsivenessMonitor(migrationType);
 
         let itemSuccess = false;
         for (let res of itemResources) {
           let completeDeferred = PromiseUtils.defer();
           let resourceDone = function(aSuccess) {
             itemResources.delete(res);
             itemSuccess |= aSuccess;
             if (itemResources.size == 0) {
               notify(itemSuccess ?
                      "Migration:ItemAfterMigrate" : "Migration:ItemError",
                      migrationType);
               resourcesGroupedByItems.delete(migrationType);
 
-              maybeStopTelemetryStopwatch(migrationType);
+              if (stopwatchHistogramId) {
+                TelemetryStopwatch.finishKeyed(stopwatchHistogramId, browserKey);
+              }
+
+              maybeFinishResponsivenessMonitor(responsivenessMonitor, responsivenessHistogramId);
 
               if (resourcesGroupedByItems.size == 0) {
                 collectQuantityTelemetry();
                 notify("Migration:Ended");
               }
             }
             completeDeferred.resolve();
           };
--- a/browser/components/migration/tests/marionette/test_refresh_firefox.py
+++ b/browser/components/migration/tests/marionette/test_refresh_firefox.py
@@ -1,13 +1,14 @@
 import os
 import shutil
 import time
 
 from marionette_harness import MarionetteTestCase
+from marionette_driver.errors import NoAlertPresentException
 
 
 class TestFirefoxRefresh(MarionetteTestCase):
     _username = "marionette-test-login"
     _password = "marionette-test-password"
     _bookmarkURL = "about:mozilla"
     _bookmarkText = "Some bookmark from Marionette"
 
@@ -264,28 +265,45 @@ class TestFirefoxRefresh(MarionetteTestC
         self.assertEqual(cookieInfo['name'], self._cookieName)
 
     def checkSession(self):
         tabURIs = self.runCode("""
           return [... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec)
         """)
         self.assertSequenceEqual(tabURIs, ["about:welcomeback"])
 
+        # Dismiss modal dialog if any. This is mainly to dismiss the check for
+        # default browser dialog if it shows up.
+        try:
+          alert = self.marionette.switch_to_alert()
+          alert.dismiss()
+        except NoAlertPresentException:
+          pass
+
         tabURIs = self.runAsyncCode("""
           let mm = gBrowser.selectedBrowser.messageManager;
-          let fs = function() {
-            content.document.getElementById("errorTryAgain").click();
-          };
+
           let {TabStateFlusher} = Cu.import("resource:///modules/sessionstore/TabStateFlusher.jsm", {});
           window.addEventListener("SSWindowStateReady", function testSSPostReset() {
             window.removeEventListener("SSWindowStateReady", testSSPostReset, false);
             Promise.all(gBrowser.browsers.map(b => TabStateFlusher.flush(b))).then(function() {
               marionetteScriptFinished([... gBrowser.browsers].map(b => b.currentURI && b.currentURI.spec));
             });
           }, false);
+
+          let fs = function() {
+            if (content.document.readyState === "complete") {
+              content.document.getElementById("errorTryAgain").click();
+            } else {
+              content.window.addEventListener("load", function(event) {
+                content.document.getElementById("errorTryAgain").click();
+              }, { once: true });
+            }
+          };
+
           mm.loadFrameScript("data:application/javascript,(" + fs.toString() + ")()", true);
         """)
         self.assertSequenceEqual(tabURIs, self._expectedURLs)
 
     def checkProfile(self, hasMigrated=False):
         self.checkPassword()
         self.checkBookmark()
         self.checkHistory()
--- a/browser/components/originattributes/test/browser/browser.ini
+++ b/browser/components/originattributes/test/browser/browser.ini
@@ -27,16 +27,18 @@ support-files =
   file_thirdPartyChild.sharedworker.js
   file_thirdPartyChild.track.vtt
   file_thirdPartyChild.video.ogv
   file_thirdPartyChild.worker.fetch.html
   file_thirdPartyChild.worker.js
   file_thirdPartyChild.worker.request.html
   file_thirdPartyChild.worker.xhr.html
   file_thirdPartyChild.xhr.html
+  file_windowOpenerRestriction.html
+  file_windowOpenerRestrictionTarget.html
   head.js
   test.js
   test.js^headers^
   test.html
   test2.html
   test2.js
   test2.js^headers^
   test_firstParty.html
@@ -66,8 +68,9 @@ support-files =
 [browser_blobURLIsolation.js]
 [browser_imageCacheIsolation.js]
 [browser_sharedworker.js]
 [browser_httpauth.js]
 [browser_clientAuth.js]
 [browser_cacheAPI.js]
 [browser_permissions.js]
 [browser_sanitize.js]
+[browser_windowOpenerRestriction.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/browser_windowOpenerRestriction.js
@@ -0,0 +1,98 @@
+/**
+ * Bug 1339336 - A test case for testing pref 'privacy.firstparty.isolate.restrict_opener_access'
+ */
+
+const { classes: Cc, Constructor: CC, interfaces: Ci, utils: Cu } = Components;
+
+const FIRST_PARTY_OPENER = "example.com";
+const FIRST_PARTY_TARGET = "example.org";
+const OPENER_PAGE = "http://" + FIRST_PARTY_OPENER + "/browser/browser/components/" +
+                    "originattributes/test/browser/file_windowOpenerRestriction.html";
+const TARGET_PAGE = "http://" + FIRST_PARTY_TARGET + "/browser/browser/components/" +
+                    "originattributes/test/browser/file_windowOpenerRestrictionTarget.html";
+
+function* testPref(aIsPrefEnabled) {
+  // Use a random key so we don't access it in later tests.
+  let cookieStr = "key" + Math.random().toString() + "=" + Math.random().toString();
+
+  // Open the tab for the opener page.
+  let tab = gBrowser.addTab(OPENER_PAGE);
+
+  // Select this tab and make sure its browser is loaded and focused.
+  gBrowser.selectedTab = tab;
+  tab.ownerGlobal.focus();
+
+  let browser = gBrowser.getBrowserForTab(tab);
+  yield BrowserTestUtils.browserLoaded(browser);
+
+  yield ContentTask.spawn(browser, {cookieStr,
+                                    page: TARGET_PAGE,
+                                    isPrefEnabled: aIsPrefEnabled}, function* (obj) {
+    // Acquire the iframe element.
+    let childFrame = content.document.getElementById("child");
+
+    // Insert a cookie into this iframe.
+    childFrame.contentDocument.cookie = obj.cookieStr;
+
+    // Open the tab here and focus on it.
+    let openedPath = obj.page;
+    if (!obj.isPrefEnabled) {
+      // If the pref is not enabled, we pass the cookie value through the query string
+      // to tell the target page that it should check the cookie value.
+      openedPath += "?" + obj.cookieStr;
+    }
+
+    // Issue the opener page to open the target page and focus on it.
+    this.openedWindow = content.open(openedPath);
+    this.openedWindow.focus();
+  });
+
+  // Wait until the target page is loaded.
+  let targetBrowser = gBrowser.getBrowserForTab(gBrowser.selectedTab);
+  yield BrowserTestUtils.browserLoaded(targetBrowser);
+
+  // The target page will do the check and show the result through its title.
+  is(targetBrowser.contentTitle, "pass", "The behavior of window.opener is correct.");
+
+  // Close Tabs.
+  yield ContentTask.spawn(browser, null, function* () {
+    this.openedWindow.close();
+  });
+  yield BrowserTestUtils.removeTab(tab);
+
+  // Reset cookies
+  Services.cookies.removeAll();
+}
+
+add_task(function* runTests() {
+  let tests = [true, false];
+
+  // First, we test the scenario that the first party isolation is enabled.
+  yield SpecialPowers.pushPrefEnv({"set":
+    [["privacy.firstparty.isolate", true]]
+  });
+
+  for (let enabled of tests) {
+    yield SpecialPowers.pushPrefEnv({"set":
+      [["privacy.firstparty.isolate.restrict_opener_access", enabled]]
+    });
+
+    yield testPref(enabled);
+  }
+
+  // Second, we test the scenario that the first party isolation is disabled.
+  yield SpecialPowers.pushPrefEnv({"set":
+    [["privacy.firstparty.isolate", false]]
+  });
+
+  for (let enabled of tests) {
+    yield SpecialPowers.pushPrefEnv({"set":
+      [["privacy.firstparty.isolate.restrict_opener_access", enabled]]
+    });
+
+    // When first party isolation is disabled, this pref will not affect the behavior of
+    // window.opener. And the correct behavior here is to allow access since the iframe in
+    // the opener page has the same origin with the target page.
+    yield testPref(false);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_windowOpenerRestriction.html
@@ -0,0 +1,10 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8">
+  <title>Test page for window.opener accessibility</title>
+</head>
+<body>
+  <iframe id="child" name="child" src="http://example.org/browser/browser/components/originattributes/test/browser/file_firstPartyBasic.html"></iframe>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/originattributes/test/browser/file_windowOpenerRestrictionTarget.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta http-equiv="content-type" content="text/html; charset=utf-8">
+  <title>title not set</title>
+  <script>
+    // If the query string is given, we are expecting the window.opener can be accessed
+    // across different first party domains, so we will match the cookie value.
+    // Otherwise, the access of window.opener should be treated as cross-origin.
+    // Therefore, it should fail at this setting.
+    let openerRestriction = true;
+    let cookieValue;
+    if (window.location.search.length > 0) {
+      cookieValue = window.location.search.substr(1);
+      openerRestriction = false;
+    }
+
+    try {
+      let openerFrame = window.opener.frames["child"];
+      let result = openerFrame.document.cookie === cookieValue;
+      if (result && !openerRestriction) {
+        document.title = "pass";
+      }
+    } catch (e) {
+      if (openerRestriction) {
+        document.title = "pass";
+      }
+    }
+  </script>
+</head>
+<body>
+</body>
+</html>
--- a/browser/components/preferences/siteDataSettings.xul
+++ b/browser/components/preferences/siteDataSettings.xul
@@ -22,17 +22,17 @@
 
   <stringbundle id="bundlePreferences"
                 src="chrome://browser/locale/preferences/preferences.properties"/>
 
   <vbox flex="1">
     <description>&settings.description;</description>
     <separator class="thin"/>
 
-    <hbox id="searchBoxContainer">
+    <hbox id="searchBoxContainer" align="center">
       <label accesskey="&search.accesskey;" control="searchBox">&search.label;</label>
       <textbox id="searchBox" type="search" flex="1"/>
     </hbox>
     <separator class="thin"/>
 
     <richlistbox id="sitesList" orient="vertical" flex="1">
       <listheader>
         <treecol flex="4" width="50" label="&hostCol.label;" id="hostCol"/>
--- a/browser/components/sessionstore/SessionHistory.jsm
+++ b/browser/components/sessionstore/SessionHistory.jsm
@@ -405,24 +405,16 @@ var SessionHistoryInternal = {
         docIdentMap[entry.docIdentifier] = matchingEntry;
       }
       else {
         shEntry.adoptBFCacheEntry(matchingEntry.shEntry);
         childDocIdents = matchingEntry.childDocIdents;
       }
     }
 
-    // The field entry.owner_b64 got renamed to entry.triggeringPricipal_b64 in
-    // Bug 1286472. To remain backward compatible we still have to support that
-    // field for a few cycles before we can remove it within Bug 1289785.
-    if (entry.owner_b64) {
-      entry.triggeringPricipal_b64 = entry.owner_b64;
-      delete entry.owner_b64;
-    }
-
     // Before introducing the concept of principalToInherit we only had
     // a triggeringPrincipal within every entry which basically is the
     // equivalent of the new principalToInherit. To avoid compatibility
     // issues, we first check if the entry has entries for
     // triggeringPrincipal_base64 and principalToInherit_base64. If not
     // we fall back to using the principalToInherit (which is stored
     // as triggeringPrincipal_b64) as the triggeringPrincipal and
     // the principalToInherit.
--- a/browser/extensions/aushelper/bootstrap.js
+++ b/browser/extensions/aushelper/bootstrap.js
@@ -3,32 +3,58 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 const APP_UPDATE_URL_PREF = "app.update.url";
 const REPLACE_KEY = "%OS_VERSION%";
 
+const AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_RESULT_CODE";
+// The system is not vulnerable to Bug 1296630.
+const CPU_NO_BUG1296630 = 1;
+// The system is vulnerable to Bug 1296630.
+const CPU_YES_BUG1296630 = 2;
+// An error occured when checking if the system is vulnerable to Bug 1296630.
+const CPU_ERR_BUG1296630 = 3;
+// It is unknown whether the system is vulnerable to Bug 1296630 (should never happen).
+const CPU_UNKNOWN_BUG1296630 = 4;
+
+const AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_CPU_ERROR_CODE";
+const CPU_SUCCESS = 0;
+const CPU_REG_OPEN_ERROR = 1;
+const CPU_VENDOR_ID_ERROR = 2;
+const CPU_ID_ERROR = 4;
+const CPU_REV_ERROR = 8;
+
+const AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME = "aushelper.websense_reg_version";
+const AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_REG_EXISTS";
+
+const AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID = "AUSHELPER_WEBSENSE_ERROR_CODE";
+const WEBSENSE_SUCCESS = 0;
+const WEBSENSE_REG_OPEN_ERROR = 1;
+const WEBSENSE_REG_READ_ERROR = 2;
+const WEBSENSE_ALREADY_MODIFIED = 4;
+
 Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://gre/modules/TelemetryLog.jsm");
 
 function startup() {
   if (Services.appinfo.OS != "WINNT") {
     return;
   }
 
   const regCPUPath = "HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0";
   let wrk;
+  let cpuErrorCode = CPU_SUCCESS;
   try {
     wrk = Cc["@mozilla.org/windows-registry-key;1"].createInstance(Ci.nsIWindowsRegKey);
     wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, regCPUPath, wrk.ACCESS_READ);
   } catch (e) {
-    Cu.reportError("Unable to open registry. Exception: " + e);
-    TelemetryLog.log("AUSHELPER_FATAL_ERROR", [e]);
+    Cu.reportError("AUSHelper - unable to open registry. Exception: " + e);
+    cpuErrorCode |= CPU_REG_OPEN_ERROR;
   }
 
   // If any of the following values are successfully retrieved and they don't
   // match the condition for that value then it is safe to update. Hence why the
   // following checks are somewhat convoluted. The possible values for the
   // variable set by each check is as follows:
   //
   //          | Match | No Match | Error |
@@ -36,44 +62,44 @@ function startup() {
 
   let cpuVendorIDMatch = false;
   try {
     let cpuVendorID = wrk.readStringValue("VendorIdentifier");
     if (cpuVendorID.toLowerCase() == "genuineintel") {
       cpuVendorIDMatch = true;
     }
   } catch (e) {
+    Cu.reportError("AUSHelper - error getting CPU vendor indentifier. Exception: " + e);
     cpuVendorIDMatch = null;
-    Cu.reportError("Error getting CPU vendor indentifier. Exception: " + e);
-    TelemetryLog.log("AUSHELPER_CPU_VENDOR_ID_ERROR", [e]);
+    cpuErrorCode |= CPU_VENDOR_ID_ERROR;
   }
 
   let cpuIDMatch = false;
   try {
     let cpuID = wrk.readStringValue("Identifier");
     if (cpuID.toLowerCase().indexOf("family 6 model 61 stepping 4") != -1) {
       cpuIDMatch = true;
     }
   } catch (e) {
+    Cu.reportError("AUSHelper - error getting CPU indentifier. Exception: " + e);
     cpuIDMatch = null;
-    Cu.reportError("Error getting CPU indentifier. Exception: " + e);
-    TelemetryLog.log("AUSHELPER_CPU_ID_ERROR", [e]);
+    cpuErrorCode |= CPU_ID_ERROR;
   }
 
   let microCodeVersions = [0xe, 0x11, 0x12, 0x13, 0x16, 0x18, 0x19];
   let cpuRevMatch = null;
   try {
     let keyNames = ["Update Revision", "Update Signature"];
     for (let i = 0; i < keyNames.length; ++i) {
       try {
         let regVal = wrk.readBinaryValue(keyNames[i]);
         if (regVal.length == 8) {
           let hexVal = [];
-          // We are only inyterested in the highest byte and return the little
-          // endian value for it.
+          // We are only inyterested in the upper 4 bytes and the little endian
+          // value for it.
           for (let j = 4; j < 8; j++) {
             let c = regVal.charCodeAt(j).toString(16);
             if (c.length == 1) {
               c = "0" + c;
             }
             hexVal.unshift(c);
           }
           cpuRevMatch = false;
@@ -81,49 +107,83 @@ function startup() {
             cpuRevMatch = true;
           }
           break;
         }
       } catch (e) {
         if (i == keyNames.length - 1) {
           // The registry key name's value was not successfully queried.
           cpuRevMatch = null;
-          TelemetryLog.log("AUSHELPER_CPU_REV_ERROR", [e]);
+          cpuErrorCode |= CPU_REV_ERROR;
         }
       }
     }
+    wrk.close();
   } catch (ex) {
+    Cu.reportError("AUSHelper - error getting CPU revision. Exception: " + ex);
     cpuRevMatch = null;
-    Cu.reportError("Error getting CPU revision. Exception: " + ex);
-    TelemetryLog.log("AUSHELPER_CPU_REV_ERROR", [ex]);
+    cpuErrorCode |= CPU_REV_ERROR;
   }
 
-  let resultCode = 3;
-  let newValue = "(unkBug1296630v1)";
+  let cpuResult = CPU_UNKNOWN_BUG1296630;
+  let cpuValue = "(unkBug1296630v1)";
   // The following uses strict equality checks since the values can be true,
   // false, or null.
   if (cpuVendorIDMatch === false || cpuIDMatch === false || cpuRevMatch === false) {
     // Since one of the values is false then the system won't be affected by
     // bug 1296630 according to the conditions set out in bug 1311515.
-    newValue = "(noBug1296630v1)";
-    resultCode = 0;
+    cpuValue = "(noBug1296630v1)";
+    cpuResult = CPU_NO_BUG1296630;
   } else if (cpuVendorIDMatch === null || cpuIDMatch === null || cpuRevMatch === null) {
     // Since one of the values is null we can't say for sure if the system will
     // be affected by bug 1296630.
-    newValue = "(errBug1296630v1)";
-    resultCode = 2;
+    cpuValue = "(errBug1296630v1)";
+    cpuResult = CPU_ERR_BUG1296630;
   } else if (cpuVendorIDMatch === true && cpuIDMatch === true && cpuRevMatch === true) {
     // Since all of the values are true we can say that the system will be
     // affected by bug 1296630.
-    newValue = "(yesBug1296630v1)";
-    resultCode = 1;
+    cpuValue = "(yesBug1296630v1)";
+    cpuResult = CPU_YES_BUG1296630;
   }
 
-  let defaultBranch = Services.prefs.getDefaultBranch("");
-  let curPrefValue = defaultBranch.getCharPref(APP_UPDATE_URL_PREF);
-  let newPrefValue = curPrefValue.replace(REPLACE_KEY + "/", REPLACE_KEY + newValue + "/");
-  defaultBranch.setCharPref(APP_UPDATE_URL_PREF, newPrefValue);
-  TelemetryLog.log("AUSHELPER_RESULT", [resultCode]);
+  Services.telemetry.getHistogramById(AUSHELPER_CPU_RESULT_CODE_HISTOGRAM_ID).add(cpuResult);
+  Services.telemetry.getHistogramById(AUSHELPER_CPU_ERROR_CODE_HISTOGRAM_ID).add(cpuErrorCode);
+
+  const regWebsensePath = "Websense\\Agent";
+  let websenseErrorCode = WEBSENSE_SUCCESS;
+  let websenseVersion = "";
+  try {
+    let regModes = [wrk.ACCESS_READ, wrk.ACCESS_READ | wrk.WOW64_64];
+    for (let i = 0; i < regModes.length; ++i) {
+      wrk.open(wrk.ROOT_KEY_LOCAL_MACHINE, "SOFTWARE", regModes[i]);
+      try {
+        if (wrk.hasChild(regWebsensePath)) {
+          let childKey = wrk.openChild(regWebsensePath, wrk.ACCESS_READ);
+          websenseVersion = childKey.readStringValue("InstallVersion");
+          Services.telemetry.scalarSet(AUSHELPER_WEBSENSE_REG_VERSION_SCALAR_NAME, websenseVersion);
+        }
+        wrk.close();
+      } catch (e) {
+        Cu.reportError("AUSHelper - unable to read registry. Exception: " + e);
+        websenseErrorCode |= WEBSENSE_REG_READ_ERROR;
+      }
+    }
+  } catch (ex) {
+    Cu.reportError("AUSHelper - unable to open registry. Exception: " + ex);
+    websenseErrorCode |= WEBSENSE_REG_OPEN_ERROR;
+  }
+
+  Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_REG_EXISTS_HISTOGRAM_ID).add(!!websenseVersion);
+  let websenseValue = "(" + (websenseVersion ? "websense-" + websenseVersion : "nowebsense") + ")";
+
+  let branch = Services.prefs.getDefaultBranch("");
+  let curValue = branch.getCharPref(APP_UPDATE_URL_PREF);
+  if (curValue.indexOf(REPLACE_KEY + "/") > -1) {
+    let newValue = curValue.replace(REPLACE_KEY + "/", REPLACE_KEY + cpuValue + websenseValue + "/");
+    branch.setCharPref(APP_UPDATE_URL_PREF, newValue);
+  } else {
+    websenseErrorCode |= WEBSENSE_ALREADY_MODIFIED;
+  }
+  Services.telemetry.getHistogramById(AUSHELPER_WEBSENSE_ERROR_CODE_HISTOGRAM_ID).add(websenseErrorCode);
 }
-
 function shutdown() {}
 function install() {}
 function uninstall() {}
--- a/browser/extensions/aushelper/install.rdf.in
+++ b/browser/extensions/aushelper/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>aushelper@mozilla.org</em:id>
-    <em:version>1.0</em:version>
+    <em:version>2.0</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this extension can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.7.290
+Current extension version is: 1.7.297
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -3627,18 +3627,18 @@ var _UnsupportedManager = function Unsup
   },
   notify: function (featureId) {
    for (var i = 0, ii = listeners.length; i < ii; i++) {
     listeners[i](featureId);
    }
   }
  };
 }();
-exports.version = '1.7.290';
-exports.build = 'b509a3f8';
+exports.version = '1.7.297';
+exports.build = '425ad309';
 exports.getDocument = getDocument;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports._UnsupportedManager = _UnsupportedManager;
 
 /***/ }),
@@ -4645,18 +4645,18 @@ var deprecated = sharedUtil.deprecated;
 var warn = sharedUtil.warn;
 var LinkTarget = displayDOMUtils.LinkTarget;
 var DEFAULT_LINK_REL = displayDOMUtils.DEFAULT_LINK_REL;
 var isWorker = typeof window === 'undefined';
 if (!globalScope.PDFJS) {
  globalScope.PDFJS = {};
 }
 var PDFJS = globalScope.PDFJS;
-PDFJS.version = '1.7.290';
-PDFJS.build = 'b509a3f8';
+PDFJS.version = '1.7.297';
+PDFJS.build = '425ad309';
 PDFJS.pdfBug = false;
 if (PDFJS.verbosity !== undefined) {
  sharedUtil.setVerbosityLevel(PDFJS.verbosity);
 }
 delete PDFJS.verbosity;
 Object.defineProperty(PDFJS, 'verbosity', {
  get: function () {
   return sharedUtil.getVerbosityLevel();
@@ -7138,18 +7138,18 @@ exports.getShadingPatternFromIR = getSha
 exports.TilingPattern = TilingPattern;
 
 /***/ }),
 /* 13 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
-var pdfjsVersion = '1.7.290';
-var pdfjsBuild = 'b509a3f8';
+var pdfjsVersion = '1.7.297';
+var pdfjsBuild = '425ad309';
 var pdfjsSharedUtil = __w_pdfjs_require__(0);
 var pdfjsDisplayGlobal = __w_pdfjs_require__(8);
 var pdfjsDisplayAPI = __w_pdfjs_require__(3);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(5);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(2);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(1);
 var pdfjsDisplaySVG = __w_pdfjs_require__(4);
 exports.PDFJS = pdfjsDisplayGlobal.PDFJS;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -83,17 +83,17 @@ return /******/ (function(modules) { // 
 
 /******/ 	// Object.prototype.hasOwnProperty.call
 /******/ 	__w_pdfjs_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
 
 /******/ 	// __webpack_public_path__
 /******/ 	__w_pdfjs_require__.p = "";
 
 /******/ 	// Load entry module and return exports
-/******/ 	return __w_pdfjs_require__(__w_pdfjs_require__.s = 36);
+/******/ 	return __w_pdfjs_require__(__w_pdfjs_require__.s = 35);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 /* WEBPACK VAR INJECTION */(function(global) {
@@ -1727,18 +1727,18 @@ exports.isStream = isStream;
 /***/ }),
 /* 2 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
-var coreJbig2 = __w_pdfjs_require__(28);
-var coreJpg = __w_pdfjs_require__(29);
+var coreJbig2 = __w_pdfjs_require__(27);
+var coreJpg = __w_pdfjs_require__(28);
 var coreJpx = __w_pdfjs_require__(13);
 var Util = sharedUtil.Util;
 var error = sharedUtil.error;
 var info = sharedUtil.info;
 var isInt = sharedUtil.isInt;
 var isArray = sharedUtil.isArray;
 var createObjectURL = sharedUtil.createObjectURL;
 var shadow = sharedUtil.shadow;
@@ -12352,17 +12352,17 @@ exports.Parser = Parser;
 /***/ }),
 /* 6 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
-var corePsParser = __w_pdfjs_require__(34);
+var corePsParser = __w_pdfjs_require__(33);
 var error = sharedUtil.error;
 var info = sharedUtil.info;
 var isArray = sharedUtil.isArray;
 var isBool = sharedUtil.isBool;
 var isDict = corePrimitives.isDict;
 var isStream = corePrimitives.isStream;
 var PostScriptLexer = corePsParser.PostScriptLexer;
 var PostScriptParser = corePsParser.PostScriptParser;
@@ -18274,17 +18274,17 @@ exports.ArithmeticDecoder = ArithmeticDe
 
 /***/ }),
 /* 9 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
-var coreCharsets = __w_pdfjs_require__(22);
+var coreCharsets = __w_pdfjs_require__(21);
 var coreEncodings = __w_pdfjs_require__(4);
 var error = sharedUtil.error;
 var info = sharedUtil.info;
 var bytesToString = sharedUtil.bytesToString;
 var warn = sharedUtil.warn;
 var isArray = sharedUtil.isArray;
 var Util = sharedUtil.Util;
 var stringToBytes = sharedUtil.stringToBytes;
@@ -24763,25 +24763,25 @@ exports.calculateSHA512 = calculateSHA51
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreStream = __w_pdfjs_require__(2);
 var coreParser = __w_pdfjs_require__(5);
-var coreImage = __w_pdfjs_require__(27);
+var coreImage = __w_pdfjs_require__(26);
 var coreColorSpace = __w_pdfjs_require__(3);
-var coreMurmurHash3 = __w_pdfjs_require__(31);
-var coreFonts = __w_pdfjs_require__(26);
+var coreMurmurHash3 = __w_pdfjs_require__(30);
+var coreFonts = __w_pdfjs_require__(25);
 var coreFunction = __w_pdfjs_require__(6);
-var corePattern = __w_pdfjs_require__(32);
-var coreCMap = __w_pdfjs_require__(23);
-var coreMetrics = __w_pdfjs_require__(30);
-var coreBidi = __w_pdfjs_require__(21);
+var corePattern = __w_pdfjs_require__(31);
+var coreCMap = __w_pdfjs_require__(22);
+var coreMetrics = __w_pdfjs_require__(29);
+var coreBidi = __w_pdfjs_require__(20);
 var coreEncodings = __w_pdfjs_require__(4);
 var coreStandardFonts = __w_pdfjs_require__(15);
 var coreUnicode = __w_pdfjs_require__(16);
 var coreGlyphList = __w_pdfjs_require__(7);
 var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
 var IDENTITY_MATRIX = sharedUtil.IDENTITY_MATRIX;
 var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
 var ImageKind = sharedUtil.ImageKind;
@@ -33901,20 +33901,20 @@ exports.getUnicodeRangeFor = getUnicodeR
 exports.getNormalizedUnicodes = getNormalizedUnicodes;
 exports.getUnicodeForGlyph = getUnicodeForGlyph;
 
 /***/ }),
 /* 17 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
-/* WEBPACK VAR INJECTION */(function(module) {
+
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
-var corePdfManager = __w_pdfjs_require__(33);
+var corePdfManager = __w_pdfjs_require__(32);
 var UNSUPPORTED_FEATURES = sharedUtil.UNSUPPORTED_FEATURES;
 var InvalidPDFException = sharedUtil.InvalidPDFException;
 var MessageHandler = sharedUtil.MessageHandler;
 var MissingPDFException = sharedUtil.MissingPDFException;
 var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
 var PasswordException = sharedUtil.PasswordException;
 var UnknownErrorException = sharedUtil.UnknownErrorException;
 var XRefParseException = sharedUtil.XRefParseException;
@@ -34583,23 +34583,28 @@ var WorkerMessageHandler = {
   return workerHandlerName;
  }
 };
 function initializeWorker() {
  var handler = new MessageHandler('worker', 'main', self);
  WorkerMessageHandler.setup(handler, self);
  handler.send('ready', null);
 }
-if (typeof window === 'undefined' && !(typeof module !== 'undefined' && module.require)) {
+function isNodeJS() {
+ if (typeof __pdfjsdev_webpack__ === 'undefined') {
+  return typeof process === 'object' && process + '' === '[object process]';
+ }
+ return false;
+}
+if (typeof window === 'undefined' && !isNodeJS()) {
  initializeWorker();
 }
 exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
 exports.WorkerTask = WorkerTask;
 exports.WorkerMessageHandler = WorkerMessageHandler;
-/* WEBPACK VAR INJECTION */}.call(exports, __w_pdfjs_require__(19)(module)))
 
 /***/ }),
 /* 18 */
 /***/ (function(module, exports) {
 
 var g;
 g = function () {
  return this;
@@ -34609,44 +34614,16 @@ try {
 } catch (e) {
  if (typeof window === "object")
   g = window;
 }
 module.exports = g;
 
 /***/ }),
 /* 19 */
-/***/ (function(module, exports) {
-
-module.exports = function (module) {
- if (!module.webpackPolyfill) {
-  module.deprecate = function () {
-  };
-  module.paths = [];
-  if (!module.children)
-   module.children = [];
-  Object.defineProperty(module, "loaded", {
-   enumerable: true,
-   get: function () {
-    return module.l;
-   }
-  });
-  Object.defineProperty(module, "id", {
-   enumerable: true,
-   get: function () {
-    return module.i;
-   }
-  });
-  module.webpackPolyfill = 1;
- }
- return module;
-};
-
-/***/ }),
-/* 20 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreStream = __w_pdfjs_require__(2);
 var coreColorSpace = __w_pdfjs_require__(3);
@@ -34662,16 +34639,17 @@ var isArray = sharedUtil.isArray;
 var isInt = sharedUtil.isInt;
 var stringToBytes = sharedUtil.stringToBytes;
 var stringToPDFString = sharedUtil.stringToPDFString;
 var warn = sharedUtil.warn;
 var Dict = corePrimitives.Dict;
 var isDict = corePrimitives.isDict;
 var isName = corePrimitives.isName;
 var isRef = corePrimitives.isRef;
+var isStream = corePrimitives.isStream;
 var Stream = coreStream.Stream;
 var ColorSpace = coreColorSpace.ColorSpace;
 var Catalog = coreObj.Catalog;
 var ObjectLoader = coreObj.ObjectLoader;
 var FileSpec = coreObj.FileSpec;
 var OperatorList = coreEvaluator.OperatorList;
 function AnnotationFactory() {
 }
@@ -34755,40 +34733,23 @@ var Annotation = function AnnotationClos
    xRatio,
    0,
    0,
    yRatio,
    rect[0] - minX * xRatio,
    rect[1] - minY * yRatio
   ];
  }
- function getDefaultAppearance(dict) {
-  var appearanceState = dict.get('AP');
-  if (!isDict(appearanceState)) {
-   return;
-  }
-  var appearance;
-  var appearances = appearanceState.get('N');
-  if (isDict(appearances)) {
-   var as = dict.get('AS');
-   if (as && appearances.has(as.name)) {
-    appearance = appearances.get(as.name);
-   }
-  } else {
-   appearance = appearances;
-  }
-  return appearance;
- }
  function Annotation(params) {
   var dict = params.dict;
   this.setFlags(dict.get('F'));
   this.setRectangle(dict.getArray('Rect'));
   this.setColor(dict.getArray('C'));
   this.setBorderStyle(dict);
-  this.appearance = getDefaultAppearance(dict);
+  this.setAppearance(dict);
   this.data = {};
   this.data.id = params.id;
   this.data.subtype = params.subtype;
   this.data.annotationFlags = this.flags;
   this.data.rect = this.rectangle;
   this.data.color = this.color;
   this.data.borderStyle = this.borderStyle;
   this.data.hasAppearance = !!this.appearance;
@@ -34882,16 +34843,36 @@ var Annotation = function AnnotationClos
      if (array.length === 4) {
       this.borderStyle.setDashArray(array[3]);
      }
     }
    } else {
     this.borderStyle.setWidth(0);
    }
   },
+  setAppearance: function Annotation_setAppearance(dict) {
+   this.appearance = null;
+   var appearanceStates = dict.get('AP');
+   if (!isDict(appearanceStates)) {
+    return;
+   }
+   var normalAppearanceState = appearanceStates.get('N');
+   if (isStream(normalAppearanceState)) {
+    this.appearance = normalAppearanceState;
+    return;
+   }
+   if (!isDict(normalAppearanceState)) {
+    return;
+   }
+   var as = dict.get('AS');
+   if (!isName(as) || !normalAppearanceState.has(as.name)) {
+    return;
+   }
+   this.appearance = normalAppearanceState.get(as.name);
+  },
   _preparePopup: function Annotation_preparePopup(dict) {
    if (!dict.has('C')) {
     this.data.color = null;
    }
    this.data.hasPopup = dict.has('Popup');
    this.data.title = stringToPDFString(dict.get('T') || '');
    this.data.contents = stringToPDFString(dict.get('Contents') || '');
   },
@@ -35327,17 +35308,17 @@ var FileAttachmentAnnotation = function 
  Util.inherit(FileAttachmentAnnotation, Annotation, {});
  return FileAttachmentAnnotation;
 }();
 exports.Annotation = Annotation;
 exports.AnnotationBorderStyle = AnnotationBorderStyle;
 exports.AnnotationFactory = AnnotationFactory;
 
 /***/ }),
-/* 21 */
+/* 20 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var warn = sharedUtil.warn;
 var baseTypes = [
  'BN',
@@ -36080,17 +36061,17 @@ function bidi(str, startLevel, vertical)
    chars[i] = '';
   }
  }
  return createBidiText(chars.join(''), isLTR);
 }
 exports.bidi = bidi;
 
 /***/ }),
-/* 22 */
+/* 21 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var ISOAdobeCharset = [
  '.notdef',
  'space',
  'exclam',
@@ -36578,17 +36559,17 @@ var ExpertSubsetCharset = [
  'periodinferior',
  'commainferior'
 ];
 exports.ISOAdobeCharset = ISOAdobeCharset;
 exports.ExpertCharset = ExpertCharset;
 exports.ExpertSubsetCharset = ExpertSubsetCharset;
 
 /***/ }),
-/* 23 */
+/* 22 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreStream = __w_pdfjs_require__(2);
 var coreParser = __w_pdfjs_require__(5);
@@ -37483,29 +37464,29 @@ var CMapFactory = function CMapFactoryCl
   }
  };
 }();
 exports.CMap = CMap;
 exports.CMapFactory = CMapFactory;
 exports.IdentityCMap = IdentityCMap;
 
 /***/ }),
-/* 24 */
+/* 23 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreStream = __w_pdfjs_require__(2);
 var coreObj = __w_pdfjs_require__(14);
 var coreParser = __w_pdfjs_require__(5);
 var coreCrypto = __w_pdfjs_require__(11);
 var coreEvaluator = __w_pdfjs_require__(12);
-var coreAnnotation = __w_pdfjs_require__(20);
+var coreAnnotation = __w_pdfjs_require__(19);
 var MissingDataException = sharedUtil.MissingDataException;
 var Util = sharedUtil.Util;
 var assert = sharedUtil.assert;
 var error = sharedUtil.error;
 var info = sharedUtil.info;
 var isArray = sharedUtil.isArray;
 var isArrayBuffer = sharedUtil.isArrayBuffer;
 var isNum = sharedUtil.isNum;
@@ -37983,17 +37964,17 @@ var PDFDocument = function PDFDocumentCl
   }
  };
  return PDFDocument;
 }();
 exports.Page = Page;
 exports.PDFDocument = PDFDocument;
 
 /***/ }),
-/* 25 */
+/* 24 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var coreStream = __w_pdfjs_require__(2);
 var coreGlyphList = __w_pdfjs_require__(7);
 var coreEncodings = __w_pdfjs_require__(4);
@@ -38769,30 +38750,30 @@ var FontRendererFactory = function FontR
    }
    return new Type2Compiled(cff, cmap, font.fontMatrix, font.glyphNameMap);
   }
  };
 }();
 exports.FontRendererFactory = FontRendererFactory;
 
 /***/ }),
-/* 26 */
+/* 25 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreStream = __w_pdfjs_require__(2);
 var coreGlyphList = __w_pdfjs_require__(7);
-var coreFontRenderer = __w_pdfjs_require__(25);
+var coreFontRenderer = __w_pdfjs_require__(24);
 var coreEncodings = __w_pdfjs_require__(4);
 var coreStandardFonts = __w_pdfjs_require__(15);
 var coreUnicode = __w_pdfjs_require__(16);
-var coreType1Parser = __w_pdfjs_require__(35);
+var coreType1Parser = __w_pdfjs_require__(34);
 var coreCFFParser = __w_pdfjs_require__(9);
 var FONT_IDENTITY_MATRIX = sharedUtil.FONT_IDENTITY_MATRIX;
 var FontType = sharedUtil.FontType;
 var assert = sharedUtil.assert;
 var bytesToString = sharedUtil.bytesToString;
 var error = sharedUtil.error;
 var info = sharedUtil.info;
 var isArray = sharedUtil.isArray;
@@ -39998,17 +39979,17 @@ var Font = function FontClosure() {
      var platformId = font.getUint16();
      var encodingId = font.getUint16();
      var offset = font.getInt32() >>> 0;
      var useTable = false;
      if (platformId === 0 && encodingId === 0) {
       useTable = true;
      } else if (platformId === 1 && encodingId === 0) {
       useTable = true;
-     } else if (platformId === 3 && encodingId === 1 && (!isSymbolicFont && hasEncoding || !potentialTable)) {
+     } else if (platformId === 3 && encodingId === 1 && (hasEncoding || !potentialTable)) {
       useTable = true;
       if (!isSymbolicFont) {
        canBreak = true;
       }
      } else if (isSymbolicFont && platformId === 3 && encodingId === 0) {
       useTable = true;
       canBreak = true;
      }
@@ -41735,17 +41716,17 @@ var CFFFont = function CFFFontClosure() 
 exports.ErrorFont = ErrorFont;
 exports.Font = Font;
 exports.FontFlags = FontFlags;
 exports.IdentityToUnicodeMap = IdentityToUnicodeMap;
 exports.ToUnicodeMap = ToUnicodeMap;
 exports.getFontType = getFontType;
 
 /***/ }),
-/* 27 */
+/* 26 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreColorSpace = __w_pdfjs_require__(3);
 var coreStream = __w_pdfjs_require__(2);
@@ -42226,17 +42207,17 @@ var PDFImage = function PDFImageClosure(
    return this.image.getBytes(length);
   }
  };
  return PDFImage;
 }();
 exports.PDFImage = PDFImage;
 
 /***/ }),
-/* 28 */
+/* 27 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var coreArithmeticDecoder = __w_pdfjs_require__(8);
 var error = sharedUtil.error;
 var log2 = sharedUtil.log2;
@@ -43427,17 +43408,17 @@ var Jbig2Image = function Jbig2ImageClos
    return parseJbig2Chunks(chunks);
   }
  };
  return Jbig2Image;
 }();
 exports.Jbig2Image = Jbig2Image;
 
 /***/ }),
-/* 29 */
+/* 28 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var error = sharedUtil.error;
 var JpegImage = function JpegImageClosure() {
  var dctZigZag = new Uint8Array([
@@ -44344,17 +44325,17 @@ var JpegImage = function JpegImageClosur
    return data;
   }
  };
  return JpegImage;
 }();
 exports.JpegImage = JpegImage;
 
 /***/ }),
-/* 30 */
+/* 29 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var getLookupTableFactory = sharedUtil.getLookupTableFactory;
 var getMetrics = getLookupTableFactory(function (t) {
  t['Courier'] = 600;
@@ -47292,17 +47273,17 @@ var getMetrics = getLookupTableFactory(f
   t['a189'] = 927;
   t['a190'] = 970;
   t['a191'] = 918;
  });
 });
 exports.getMetrics = getMetrics;
 
 /***/ }),
-/* 31 */
+/* 30 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var Uint32ArrayView = sharedUtil.Uint32ArrayView;
 var MurmurHash3_64 = function MurmurHash3_64Closure(seed) {
  var MASK_HIGH = 0xffff0000;
@@ -47413,17 +47394,17 @@ var MurmurHash3_64 = function MurmurHash
    return str;
   }
  };
  return MurmurHash3_64;
 }();
 exports.MurmurHash3_64 = MurmurHash3_64;
 
 /***/ }),
-/* 32 */
+/* 31 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var coreFunction = __w_pdfjs_require__(6);
 var coreColorSpace = __w_pdfjs_require__(3);
@@ -48252,25 +48233,25 @@ function getTilingPatternIR(operatorList
   paintType,
   tilingType
  ];
 }
 exports.Pattern = Pattern;
 exports.getTilingPatternIR = getTilingPatternIR;
 
 /***/ }),
-/* 33 */
+/* 32 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var coreStream = __w_pdfjs_require__(2);
 var coreChunkedStream = __w_pdfjs_require__(10);
-var coreDocument = __w_pdfjs_require__(24);
+var coreDocument = __w_pdfjs_require__(23);
 var warn = sharedUtil.warn;
 var createValidAbsoluteUrl = sharedUtil.createValidAbsoluteUrl;
 var shadow = sharedUtil.shadow;
 var NotImplementedException = sharedUtil.NotImplementedException;
 var MissingDataException = sharedUtil.MissingDataException;
 var createPromiseCapability = sharedUtil.createPromiseCapability;
 var Util = sharedUtil.Util;
 var Stream = coreStream.Stream;
@@ -48438,17 +48419,17 @@ var NetworkPdfManager = function Network
   }
  });
  return NetworkPdfManager;
 }();
 exports.LocalPdfManager = LocalPdfManager;
 exports.NetworkPdfManager = NetworkPdfManager;
 
 /***/ }),
-/* 34 */
+/* 33 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var corePrimitives = __w_pdfjs_require__(1);
 var error = sharedUtil.error;
 var isSpace = sharedUtil.isSpace;
@@ -48637,17 +48618,17 @@ var PostScriptLexer = function PostScrip
   }
  };
  return PostScriptLexer;
 }();
 exports.PostScriptLexer = PostScriptLexer;
 exports.PostScriptParser = PostScriptParser;
 
 /***/ }),
-/* 35 */
+/* 34 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 var sharedUtil = __w_pdfjs_require__(0);
 var coreStream = __w_pdfjs_require__(2);
 var coreEncodings = __w_pdfjs_require__(4);
 var warn = sharedUtil.warn;
@@ -49183,22 +49164,22 @@ var Type1Parser = function Type1ParserCl
    }
   }
  };
  return Type1Parser;
 }();
 exports.Type1Parser = Type1Parser;
 
 /***/ }),
-/* 36 */
+/* 35 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
-var pdfjsVersion = '1.7.290';
-var pdfjsBuild = 'b509a3f8';
+var pdfjsVersion = '1.7.297';
+var pdfjsBuild = '425ad309';
 var pdfjsCoreWorker = __w_pdfjs_require__(17);
 ;
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ })
 /******/ ]);
 });
\ No newline at end of file
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -728,16 +728,17 @@
 @BINPATH@/wow_helper.exe
 #endif
 #endif
 #endif
 
 #if defined(MOZ_SANDBOX)
 #if defined(XP_LINUX)
 @BINPATH@/@DLL_PREFIX@mozsandbox@DLL_SUFFIX@
+@RESPATH@/components/sandbox.xpt
 #endif
 #endif
 
 ; for Solaris SPARC
 #ifdef SOLARIS
 bin/libfreebl_32fpu_3.so
 bin/libfreebl_32int_3.so
 bin/libfreebl_32int64_3.so
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -133,16 +133,17 @@ this.ExtensionsUI = {
         reply(true);
       } else {
         info.permissions = perms;
         let strings = this._buildStrings(info);
         this.showPermissionsPrompt(target, strings, info.icon).then(reply);
       }
     } else if (topic == "webextension-update-permissions") {
       let info = subject.wrappedJSObject;
+      info.type = "update";
       let strings = this._buildStrings(info);
 
       // If we don't prompt for any new permissions, just apply it
       if (strings.msgs.length == 0) {
         info.resolve();
         return;
       }
 
--- a/build/build-clang/clang-tidy-linux64.json
+++ b/build/build-clang/clang-tidy-linux64.json
@@ -1,10 +1,10 @@
 {
-    "llvm_revision": "292415",
+    "llvm_revision": "295482",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/trunk",
--- a/build/build-clang/clang-tidy-macosx64.json
+++ b/build/build-clang/clang-tidy-macosx64.json
@@ -1,10 +1,10 @@
 {
-    "llvm_revision": "292415",
+    "llvm_revision": "295482",
     "stages": "1",
     "build_libcxx": true,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "osx_cross_compile": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
--- a/build/build-clang/clang-tidy-win32.json
+++ b/build/build-clang/clang-tidy-win32.json
@@ -1,10 +1,10 @@
 {
-    "llvm_revision": "292415",
+    "llvm_revision": "295482",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/trunk",
--- a/build/build-clang/clang-tidy-win64.json
+++ b/build/build-clang/clang-tidy-win64.json
@@ -1,10 +1,10 @@
 {
-    "llvm_revision": "292415",
+    "llvm_revision": "295482",
     "stages": "1",
     "build_libcxx": false,
     "build_type": "Release",
     "assertions": false,
     "build_clang_tidy": true,
     "llvm_repo": "https://llvm.org/svn/llvm-project/llvm/trunk",
     "clang_repo": "https://llvm.org/svn/llvm-project/cfe/trunk",
     "extra_repo": "https://llvm.org/svn/llvm-project/clang-tools-extra/trunk",
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -38,34 +38,16 @@ def cargo_info(cargo):
         if not m:
             die('Could not determine cargo version from output: %s', out)
         version = m.group(1)
 
     return namespace(
         version=Version(version),
     )
 
-@depends_if(cargo)
-@checking('cargo support for --frozen')
-@imports('subprocess')
-@imports('os')
-def cargo_supports_frozen(cargo):
-    try:
-        lines = subprocess.check_output(
-            [cargo, 'help', 'build']
-        ).splitlines()
-        supported = any('    --frozen' in l for l in lines)
-        if 'MOZ_AUTOMATION' in os.environ and not supported:
-            die('cargo in automation must support --frozen')
-        return supported
-    except subprocess.CalledProcessError as e:
-        die('Failed to call cargo: %s', e.message)
-
-set_config('MOZ_CARGO_SUPPORTS_FROZEN', cargo_supports_frozen)
-
 @depends(rustc, cargo, rustc_info)
 @imports(_from='textwrap', _import='dedent')
 def rust_compiler(rustc, cargo, rustc_info):
     if not rustc:
         die(dedent('''\
         Rust compiler not found.
         To compile rust language sources, you must have 'rustc' in your path.
         See https//www.rust-lang.org/ for more information.
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -894,21 +894,19 @@ endif
 
 ifdef MOZ_RUST
 cargo_host_flag := --target=$(RUST_HOST_TARGET)
 cargo_target_flag := --target=$(RUST_TARGET)
 
 # Permit users to pass flags to cargo from their mozconfigs (e.g. --color=always).
 cargo_build_flags = $(CARGOFLAGS)
 ifndef MOZ_DEBUG
-cargo_build_flags = --release
+cargo_build_flags += --release
 endif
-ifdef MOZ_CARGO_SUPPORTS_FROZEN
 cargo_build_flags += --frozen
-endif
 
 cargo_build_flags += --manifest-path $(CARGO_FILE)
 ifdef BUILD_VERBOSE_LOG
 cargo_build_flags += --verbose
 endif
 
 # Enable color output if original stdout was a TTY and color settings
 # aren't already present. This essentially restores the default behavior
--- a/devtools/client/commandline/test/browser_cmd_highlight_04.js
+++ b/devtools/client/commandline/test/browser_cmd_highlight_04.js
@@ -2,17 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* eslint key-spacing: 0 */
 
 // Tests the various highlight command parameters and options
 
-requestLongerTimeout(3);
+requestLongerTimeout(4);
 
 // Creating a test page with many elements to test the --showall option
 var TEST_PAGE = "data:text/html;charset=utf-8,<body><ul>";
 for (let i = 0; i < 101; i++) {
   TEST_PAGE += "<li class='item'>" + i + "</li>";
 }
 TEST_PAGE += "</ul></body>";
 
--- a/devtools/client/dom/content/components/dom-tree.js
+++ b/devtools/client/dom/content/components/dom-tree.js
@@ -7,17 +7,17 @@
 
 // React & Redux
 const React = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
 
 // Reps
-const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const Rep = React.createFactory(REPS.Rep);
 const Grip = REPS.Grip;
 
 // DOM Panel
 const { GripProvider } = require("../grip-provider");
 const { DomDecorator } = require("../dom-decorator");
 
 // Shortcuts
--- a/devtools/client/dom/content/components/main-toolbar.js
+++ b/devtools/client/dom/content/components/main-toolbar.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const { l10n } = require("../utils");
 
 // Reps
-const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+const { createFactories } = require("devtools/client/shared/components/reps/reps");
 const { Toolbar, ToolbarButton } = createFactories(require("devtools/client/jsonview/components/reps/toolbar"));
 
 // DOM Panel
 const SearchBox = React.createFactory(require("devtools/client/shared/components/search-box"));
 
 // Actions
 const { fetchProperties } = require("../actions/grips");
 const { setVisibilityFilter } = require("../actions/filter");
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1089,60 +1089,19 @@ Inspector.prototype = {
 
       menu.append(menuitem);
     }
 
     menu.append(new MenuItem({
       type: "separator",
     }));
 
-    let copySubmenu = new Menu();
-    copySubmenu.append(new MenuItem({
-      id: "node-menu-copyinner",
-      label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"),
-      accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"),
-      disabled: !isSelectionElement,
-      click: () => this.copyInnerHTML(),
-    }));
-    copySubmenu.append(new MenuItem({
-      id: "node-menu-copyouter",
-      label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"),
-      accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"),
-      disabled: !isSelectionElement,
-      click: () => this.copyOuterHTML(),
-    }));
-    copySubmenu.append(new MenuItem({
-      id: "node-menu-copyuniqueselector",
-      label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
-      accesskey:
-        INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
-      disabled: !isSelectionElement,
-      hidden: !this.canGetUniqueSelector,
-      click: () => this.copyUniqueSelector(),
-    }));
-    copySubmenu.append(new MenuItem({
-      id: "node-menu-copycsspath",
-      label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
-      accesskey:
-        INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
-      disabled: !isSelectionElement,
-      hidden: !this.canGetCssPath,
-      click: () => this.copyCssPath(),
-    }));
-    copySubmenu.append(new MenuItem({
-      id: "node-menu-copyimagedatauri",
-      label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"),
-      disabled: !isSelectionElement || !markupContainer ||
-                !markupContainer.isPreviewable(),
-      click: () => this.copyImageDataUri(),
-    }));
-
     menu.append(new MenuItem({
       label: INSPECTOR_L10N.getStr("inspectorCopyHTMLSubmenu.label"),
-      submenu: copySubmenu,
+      submenu: this._getCopySubmenu(markupContainer, isSelectionElement),
     }));
 
     menu.append(new MenuItem({
       label: INSPECTOR_L10N.getStr("inspectorPasteHTMLSubmenu.label"),
       submenu: this._getPasteSubmenu(isEditableElement),
     }));
 
     menu.append(new MenuItem({
@@ -1205,16 +1164,61 @@ Inspector.prototype = {
     for (let menuitem of nodeLinkMenuItems) {
       menu.append(menuitem);
     }
 
     menu.popup(screenX, screenY, this._toolbox);
     return menu;
   },
 
+  _getCopySubmenu: function (markupContainer, isSelectionElement) {
+    let copySubmenu = new Menu();
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyinner",
+      label: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.label"),
+      accesskey: INSPECTOR_L10N.getStr("inspectorCopyInnerHTML.accesskey"),
+      disabled: !isSelectionElement,
+      click: () => this.copyInnerHTML(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyouter",
+      label: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.label"),
+      accesskey: INSPECTOR_L10N.getStr("inspectorCopyOuterHTML.accesskey"),
+      disabled: !isSelectionElement,
+      click: () => this.copyOuterHTML(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyuniqueselector",
+      label: INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.label"),
+      accesskey:
+        INSPECTOR_L10N.getStr("inspectorCopyCSSSelector.accesskey"),
+      disabled: !isSelectionElement,
+      hidden: !this.canGetUniqueSelector,
+      click: () => this.copyUniqueSelector(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copycsspath",
+      label: INSPECTOR_L10N.getStr("inspectorCopyCSSPath.label"),
+      accesskey:
+        INSPECTOR_L10N.getStr("inspectorCopyCSSPath.accesskey"),
+      disabled: !isSelectionElement,
+      hidden: !this.canGetCssPath,
+      click: () => this.copyCssPath(),
+    }));
+    copySubmenu.append(new MenuItem({
+      id: "node-menu-copyimagedatauri",
+      label: INSPECTOR_L10N.getStr("inspectorImageDataUri.label"),
+      disabled: !isSelectionElement || !markupContainer ||
+                !markupContainer.isPreviewable(),
+      click: () => this.copyImageDataUri(),
+    }));
+
+    return copySubmenu;
+  },
+
   _getPasteSubmenu: function (isEditableElement) {
     let isPasteable = isEditableElement && this._getClipboardContentForPaste();
     let disableAdjacentPaste = !isPasteable ||
           !this.canPasteInnerOrAdjacentHTML || this.selection.isRoot() ||
           this.selection.isBodyNode() || this.selection.isHeadNode();
     let disableFirstLastPaste = !isPasteable ||
           !this.canPasteInnerOrAdjacentHTML || (this.selection.isHTMLNode() &&
           this.selection.isRoot());
@@ -1279,30 +1283,36 @@ Inspector.prototype = {
     attributesSubmenu.append(new MenuItem({
       id: "node-menu-add-attribute",
       label: INSPECTOR_L10N.getStr("inspectorAddAttribute.label"),
       accesskey: INSPECTOR_L10N.getStr("inspectorAddAttribute.accesskey"),
       disabled: !isEditableElement,
       click: () => this.onAddAttribute(),
     }));
     attributesSubmenu.append(new MenuItem({
+      id: "node-menu-copy-attribute",
+      label: INSPECTOR_L10N.getFormatStr("inspectorCopyAttributeValue.label",
+                                        isAttributeClicked ? `"${nodeInfo.value}"` : ""),
+      accesskey: INSPECTOR_L10N.getStr("inspectorCopyAttributeValue.accesskey"),
+      disabled: !isAttributeClicked,
+      click: () => this.onCopyAttributeValue(),
+    }));
+    attributesSubmenu.append(new MenuItem({
       id: "node-menu-edit-attribute",
       label: INSPECTOR_L10N.getFormatStr("inspectorEditAttribute.label",
                                         isAttributeClicked ? `"${nodeInfo.name}"` : ""),
       accesskey: INSPECTOR_L10N.getStr("inspectorEditAttribute.accesskey"),
       disabled: !isAttributeClicked,
       click: () => this.onEditAttribute(),
     }));
-
     attributesSubmenu.append(new MenuItem({
       id: "node-menu-remove-attribute",
       label: INSPECTOR_L10N.getFormatStr("inspectorRemoveAttribute.label",
                                         isAttributeClicked ? `"${nodeInfo.name}"` : ""),
-      accesskey:
-        INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
+      accesskey: INSPECTOR_L10N.getStr("inspectorRemoveAttribute.accesskey"),
       disabled: !isAttributeClicked,
       click: () => this.onRemoveAttribute(),
     }));
 
     return attributesSubmenu;
   },
 
   /**
@@ -1813,16 +1823,24 @@ Inspector.prototype = {
    * Used for node context menu and shouldn't be called directly.
    */
   onAddAttribute: function () {
     let container = this.markup.getContainer(this.selection.nodeFront);
     container.addAttribute();
   },
 
   /**
+   * Copy attribute value for node.
+   * Used for node context menu and shouldn't be called directly.
+   */
+  onCopyAttributeValue: function () {
+    clipboardHelper.copyString(this.nodeMenuTriggerInfo.value);
+  },
+
+  /**
    * Edit attribute for node.
    * Used for node context menu and shouldn't be called directly.
    */
   onEditAttribute: function () {
     let container = this.markup.getContainer(this.selection.nodeFront);
     container.editAttribute(this.nodeMenuTriggerInfo.name);
   },
 
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -210,16 +210,17 @@ subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_rules_selector-highlighter-on-navigate.js]
 [browser_rules_selector-highlighter_01.js]
 [browser_rules_selector-highlighter_02.js]
 [browser_rules_selector-highlighter_03.js]
 [browser_rules_selector-highlighter_04.js]
 [browser_rules_selector-highlighter_05.js]
 [browser_rules_selector_highlight.js]
+[browser_rules_shorthand-overridden-lists.js]
 [browser_rules_strict-search-filter-computed-list_01.js]
 [browser_rules_strict-search-filter_01.js]
 [browser_rules_strict-search-filter_02.js]
 [browser_rules_strict-search-filter_03.js]
 [browser_rules_style-editor-link.js]
 [browser_rules_urls-clickable.js]
 [browser_rules_user-agent-styles.js]
 [browser_rules_user-agent-styles-uneditable.js]
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
@@ -51,17 +51,17 @@ add_task(function* () {
   yield togglePropStatus(view, secondProp);
 
   is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)",
      "After disabling second property, first value should be used");
 
   info("Disabling the first property too and checking the applied style");
   yield togglePropStatus(view, firstProp);
 
-  is((yield getValue("#testid", "background-color")), "transparent",
+  is((yield getValue("#testid", "background-color")), "rgba(0, 0, 0, 0)",
      "After disabling both properties, value should be empty.");
 
   info("Re-enabling the second propertyt and checking the applied style");
   yield togglePropStatus(view, secondProp);
 
   is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
      "Value should be set correctly after re-enabling");
 
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/rules/test/browser_rules_shorthand-overridden-lists.js
@@ -0,0 +1,70 @@
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that the rule view shorthand overridden list works correctly,
+// can be shown and hidden correctly, and contain the right subproperties.
+
+var TEST_URI = `
+  <style type="text/css">
+    div {
+      margin: 0px 1px 2px 3px;
+      top: 0px;
+    }
+    #testid {
+        margin-left: 10px;
+        margin-right: 10px;
+    }
+  </style>
+  <div id="testid">Styled Node</div>
+`;
+
+add_task(function* () {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
+  yield selectNode("#testid", inspector);
+  yield testComputedList(inspector, view);
+});
+
+function* testComputedList(inspector, view) {
+  let rule = getRuleViewRuleEditor(view, 2).rule;
+  let propEditor = rule.textProps[0].editor;
+  let expander = propEditor.expander;
+  let overriddenItems = propEditor.shorthandOverridden.children;
+  let propNames = [
+    "margin-right",
+    "margin-left"
+  ];
+
+  ok(!expander.hasAttribute("open"), "margin computed list is closed.");
+  ok(!propEditor.shorthandOverridden.hasAttribute("hidden"),
+      "The shorthandOverridden list should be open.");
+
+  is(overriddenItems.length, propNames.length,
+      "There should be 2 overridden shorthand value.");
+  for (let i = 0; i < propNames.length; i++) {
+    let overriddenItem = overriddenItems[i].querySelector(".ruleview-propertyname");
+    is(overriddenItem.textContent, propNames[i],
+        "The overridden item #" + i + " should be " + propNames[i]);
+  }
+
+  info("Opening the computed list of margin property.");
+  expander.click();
+  ok(expander.hasAttribute("open"), "margin computed list is open.");
+  ok(propEditor.shorthandOverridden.hasAttribute("hidden"),
+      "The shorthandOverridden list should be hidden.");
+
+  info("Closing the computed list of margin property.");
+  expander.click();
+  ok(!expander.hasAttribute("open"), "margin computed list is closed.");
+  ok(!propEditor.shorthandOverridden.hasAttribute("hidden"),
+      "The shorthandOverridden list should be open.");
+
+  for (let i = 0; i < propNames.length; i++) {
+    let overriddenItem = overriddenItems[i].querySelector(".ruleview-propertyname");
+    is(overriddenItem.textContent, propNames[i],
+        "The overridden item #" + i + " should still be " + propNames[i]);
+  }
+}
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -192,16 +192,22 @@ TextPropertyEditor.prototype = {
     });
 
     // Holds the viewers for the computed properties.
     // will be populated in |_updateComputed|.
     this.computed = createChild(this.element, "ul", {
       class: "ruleview-computedlist",
     });
 
+    // Holds the viewers for the overridden shorthand properties.
+    // will be populated in |_updateShorthandOverridden|.
+    this.shorthandOverridden = createChild(this.element, "ul", {
+      class: "ruleview-overridden-items",
+    });
+
     // Only bind event handlers if the rule is editable.
     if (this.ruleEditor.isEditable) {
       this.enable.addEventListener("click", this._onEnableClicked, true);
 
       this.nameContainer.addEventListener("click", (event) => {
         // Clicks within the name shouldn't propagate any further.
         event.stopPropagation();
 
@@ -449,32 +455,33 @@ TextPropertyEditor.prototype = {
       }
 
       if (!elToClick) {
         elToClick = this.valueSpan;
       }
       elToClick.click();
     }
 
-    // Populate the computed styles.
+    // Populate the computed styles and shorthand overridden styles.
     this._updateComputed();
+    this._updateShorthandOverridden();
 
     // Update the rule property highlight.
     this.ruleView._updatePropertyHighlight(this);
   },
 
   _onStartEditing: function () {
     this.element.classList.remove("ruleview-overridden");
     this.filterProperty.hidden = true;
     this.enable.style.visibility = "hidden";
   },
 
   /**
    * Update the visibility of the enable checkbox, the warning indicator and
-   * the filter property, as well as the overriden state of the property.
+   * the filter property, as well as the overridden state of the property.
    */
   updatePropertyState: function () {
     if (this.prop.enabled) {
       this.enable.style.removeProperty("visibility");
       this.enable.setAttribute("checked", "");
     } else {
       this.enable.style.visibility = "visible";
       this.enable.removeAttribute("checked");
@@ -522,53 +529,94 @@ TextPropertyEditor.prototype = {
 
     for (let computed of this.prop.computed) {
       // Don't bother to duplicate information already
       // shown in the text property.
       if (computed.name === this.prop.name) {
         continue;
       }
 
-      let li = createChild(this.computed, "li", {
-        class: "ruleview-computed"
-      });
+      // Store the computed style element for easy access when highlighting
+      // styles
+      computed.element = this._createComputedListItem(this.computed, computed,
+        "ruleview-computed");
+    }
+  },
+
+  /**
+   * Update the indicator for overridden shorthand styles. The shorthand
+   * overridden styles themselves are populated on demand, when they
+   * become visible.
+   */
+  _updateShorthandOverridden: function () {
+    this.shorthandOverridden.innerHTML = "";
 
-      if (computed.overridden) {
-        li.classList.add("ruleview-overridden");
+    this._populatedShorthandOverridden = false;
+    this._populateShorthandOverridden();
+  },
+
+  /**
+   * Populate the list of overridden shorthand styles.
+   */
+  _populateShorthandOverridden: function () {
+    if (this._populatedShorthandOverridden || this.prop.overridden) {
+      return;
+    }
+    this._populatedShorthandOverridden = true;
+
+    for (let computed of this.prop.computed) {
+      // Don't display duplicate information or show properties
+      // that are completely overridden.
+      if (computed.name === this.prop.name || !computed.overridden) {
+        continue;
       }
 
-      createChild(li, "span", {
-        class: "ruleview-propertyname theme-fg-color5",
-        textContent: computed.name
-      });
-      appendText(li, ": ");
+      this._createComputedListItem(this.shorthandOverridden, computed,
+        "ruleview-overridden-item");
+    }
+  },
 
-      let outputParser = this.ruleView._outputParser;
-      let frag = outputParser.parseCssProperty(
-        computed.name, computed.value, {
-          colorSwatchClass: "ruleview-swatch ruleview-colorswatch",
-          urlClass: "theme-link",
-          baseURI: this.sheetHref
-        }
-      );
+  /**
+   * Creates and populates a list item with the computed CSS property.
+   */
+  _createComputedListItem: function (parentEl, computed, className) {
+    let li = createChild(parentEl, "li", {
+      class: className
+    });
+
+    if (computed.overridden) {
+      li.classList.add("ruleview-overridden");
+    }
 
-      // Store the computed property value that was parsed for output
-      computed.parsedValue = frag.textContent;
+    createChild(li, "span", {
+      class: "ruleview-propertyname theme-fg-color5",
+      textContent: computed.name
+    });
+    appendText(li, ": ");
 
-      createChild(li, "span", {
-        class: "ruleview-propertyvalue theme-fg-color1",
-        child: frag
-      });
+    let outputParser = this.ruleView._outputParser;
+    let frag = outputParser.parseCssProperty(
+      computed.name, computed.value, {
+        colorSwatchClass: "ruleview-swatch ruleview-colorswatch",
+        urlClass: "theme-link",
+        baseURI: this.sheetHref
+      }
+    );
 
-      appendText(li, ";");
+    // Store the computed property value that was parsed for output
+    computed.parsedValue = frag.textContent;
 
-      // Store the computed style element for easy access when highlighting
-      // styles
-      computed.element = li;
-    }
+    createChild(li, "span", {
+      class: "ruleview-propertyvalue theme-fg-color1",
+      child: frag
+    });
+
+    appendText(li, ";");
+
+    return li;
   },
 
   /**
    * Handles clicks on the disabled property.
    */
   _onEnableClicked: function (event) {
     let checked = this.enable.hasAttribute("checked");
     if (checked) {
@@ -588,19 +636,22 @@ TextPropertyEditor.prototype = {
    * expanded by manually by the user.
    */
   _onExpandClicked: function (event) {
     if (this.computed.hasAttribute("filter-open") ||
         this.computed.hasAttribute("user-open")) {
       this.expander.removeAttribute("open");
       this.computed.removeAttribute("filter-open");
       this.computed.removeAttribute("user-open");
+      this.shorthandOverridden.removeAttribute("hidden");
+      this._populateShorthandOverridden();
     } else {
       this.expander.setAttribute("open", "true");
       this.computed.setAttribute("user-open", "");
+      this.shorthandOverridden.setAttribute("hidden", "true");
       this._populateComputed();
     }
 
     event.stopPropagation();
   },
 
   /**
    * Expands the computed list when a computed property is matched by the style
--- a/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-01-sensitivity.js
@@ -30,16 +30,17 @@ const ALL_MENU_ITEMS = [
   "node-menu-copyimagedatauri",
   "node-menu-delete",
   "node-menu-pseudo-hover",
   "node-menu-pseudo-active",
   "node-menu-pseudo-focus",
   "node-menu-scrollnodeintoview",
   "node-menu-screenshotnode",
   "node-menu-add-attribute",
+  "node-menu-copy-attribute",
   "node-menu-edit-attribute",
   "node-menu-remove-attribute"
 ].concat(PASTE_MENU_ITEMS, ACTIVE_ON_DOCTYPE_ITEMS);
 
 const INACTIVE_ON_DOCTYPE_ITEMS =
   ALL_MENU_ITEMS.filter(item => ACTIVE_ON_DOCTYPE_ITEMS.indexOf(item) === -1);
 
 /**
@@ -65,145 +66,157 @@ const TEST_CASES = [
     disabled: INACTIVE_ON_DOCTYPE_ITEMS,
   },
   {
     desc: "element node HTML on the clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     disabled: [
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
     selector: "#sensitivity",
   },
   {
     desc: "<html> element",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "html",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-pastefirstchild",
       "node-menu-pastelastchild",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<body> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "body",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<img> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "img",
     disabled: [
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<head> with HTML on clipboard",
     clipboardData: "<p>some text</p>",
     clipboardDataType: "html",
     selector: "head",
     disabled: [
       "node-menu-copyimagedatauri",
       "node-menu-pastebefore",
       "node-menu-pasteafter",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ],
   },
   {
     desc: "<head> with no html on clipboard",
     selector: "head",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with text on clipboard",
     clipboardData: "some text",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: [
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]
   },
   {
     desc: "<element> with base64 encoded image data uri on clipboard",
     clipboardData:
       "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABC" +
       "AAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with empty string on clipboard",
     clipboardData: "",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with whitespace only on clipboard",
     clipboardData: " \n\n\t\n\n  \n",
     clipboardDataType: undefined,
     selector: "#paste-area",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> that isn't visible on the page, empty clipboard",
     selector: "#hiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> nested in another hidden element, empty clipboard",
     selector: "#nestedHiddenElement",
     disabled: PASTE_MENU_ITEMS.concat([
       "node-menu-copyimagedatauri",
       "node-menu-screenshotnode",
+      "node-menu-copy-attribute",
       "node-menu-edit-attribute",
       "node-menu-remove-attribute"
     ]),
   },
   {
     desc: "<element> with context menu triggered on attribute, empty clipboard",
     selector: "#attributes",
     disabled: PASTE_MENU_ITEMS.concat(["node-menu-copyimagedatauri"]),
--- a/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
+++ b/devtools/client/inspector/test/browser_inspector_menu-05-attribute-items.js
@@ -7,16 +7,17 @@ http://creativecommons.org/publicdomain/
 
 const TEST_URL = URL_ROOT + "doc_inspector_menu.html";
 
 add_task(function* () {
   let { inspector, testActor } = yield openInspectorForURL(TEST_URL);
   yield selectNode("#attributes", inspector);
 
   yield testAddAttribute();
+  yield testCopyAttributeValue();
   yield testEditAttribute();
   yield testRemoveAttribute();
 
   function* testAddAttribute() {
     info("Triggering 'Add Attribute' and waiting for mutation to occur");
     let addAttribute = getMenuItem("node-menu-add-attribute");
     addAttribute.click();
 
@@ -24,16 +25,30 @@ add_task(function* () {
     let onMutation = inspector.once("markupmutation");
     EventUtils.synthesizeKey("VK_RETURN", {});
     yield onMutation;
 
     let hasAttribute = testActor.hasNode("#attributes.u-hidden");
     ok(hasAttribute, "attribute was successfully added");
   }
 
+  function* testCopyAttributeValue() {
+    info("Testing 'Copy Attribute Value' and waiting for clipboard promise to resolve");
+    let copyAttributeValue = getMenuItem("node-menu-copy-attribute");
+
+    info("Triggering 'Copy Attribute Value' and waiting for clipboard to copy the value");
+    inspector.nodeMenuTriggerInfo = {
+      type: "attribute",
+      name: "data-edit",
+      value: "the"
+    };
+
+    yield waitForClipboardPromise(() => copyAttributeValue.click(), "the");
+  }
+
   function* testEditAttribute() {
     info("Testing 'Edit Attribute' menu item");
     let editAttribute = getMenuItem("node-menu-edit-attribute");
 
     info("Triggering 'Edit Attribute' and waiting for mutation to occur");
     inspector.nodeMenuTriggerInfo = {
       type: "attribute",
       name: "data-edit"
--- a/devtools/client/inspector/test/doc_inspector_menu.html
+++ b/devtools/client/inspector/test/doc_inspector_menu.html
@@ -18,12 +18,12 @@
       <p class="duplicate">This will be duplicated</p>
       <p id="delete">This has to be deleted</p>
       <img id="copyimage" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAAAAAA6fptVAAAACklEQVQYV2P4DwABAQEAWk1v8QAAAABJRU5ErkJggg==" />
       <div id="hiddenElement" style="display: none;">
         <p id="nestedHiddenElement">Visible element nested inside a non-visible element</p>
       </div>
       <p id="console-var">Paragraph for testing console variables</p>
       <p id="console-var-multi">Paragraph for testing multiple console variables</p>
-      <p id="attributes" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
+      <p id="attributes" data-copy="the" data-edit="original" data-remove="thing">Attributes are going to be changed here</p>
     </div>
   </body>
 </html>
--- a/devtools/client/jsonview/components/headers-panel.js
+++ b/devtools/client/jsonview/components/headers-panel.js
@@ -4,17 +4,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/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
-  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+  const { createFactories } = require("devtools/client/shared/components/reps/reps");
 
   const { Headers } = createFactories(require("./headers"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
   const { div } = dom;
 
   /**
    * This template represents the 'Headers' panel
--- a/devtools/client/jsonview/components/json-panel.js
+++ b/devtools/client/jsonview/components/json-panel.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
   const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
 
-  const { REPS, createFactories, MODE } = require("devtools/client/shared/components/reps/load-reps");
+  const { REPS, createFactories, MODE } = require("devtools/client/shared/components/reps/reps");
   const Rep = createFactory(REPS.Rep);
 
   const { SearchBox } = createFactories(require("./search-box"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
   const { div } = dom;
   const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
   const AUTO_EXPAND_MAX_LEVEL = 7;
--- a/devtools/client/jsonview/components/main-tabbed-area.js
+++ b/devtools/client/jsonview/components/main-tabbed-area.js
@@ -4,17 +4,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/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
-  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+  const { createFactories } = require("devtools/client/shared/components/reps/reps");
   const { JsonPanel } = createFactories(require("./json-panel"));
   const { TextPanel } = createFactories(require("./text-panel"));
   const { HeadersPanel } = createFactories(require("./headers-panel"));
   const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
 
   /**
    * This object represents the root application template
    * responsible for rendering the basic tab layout.
--- a/devtools/client/jsonview/components/text-panel.js
+++ b/devtools/client/jsonview/components/text-panel.js
@@ -5,17 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
 
   // we'll need to make load-reps define friendly aka UMD
-  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+  const { createFactories } = require("devtools/client/shared/components/reps/reps");
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
   const { div, pre } = dom;
 
   /**
    * This template represents the 'Raw Data' panel displaying
    * JSON as a text received from the server.
    */
   let TextPanel = createClass({
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.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";
 
 define(function (require, exports, module) {
   const { render } = require("devtools/client/shared/vendor/react-dom");
-  const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+  const { createFactories } = require("devtools/client/shared/components/reps/reps");
   const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
 
   const json = document.getElementById("json");
   const headers = document.getElementById("headers");
 
   let jsonData;
 
   try {
--- a/devtools/client/locales/en-US/inspector.properties
+++ b/devtools/client/locales/en-US/inspector.properties
@@ -112,16 +112,23 @@ inspectorEditAttribute.accesskey=E
 
 # LOCALIZATION NOTE (inspectorRemoveAttribute.label): This is the label of a
 # sub-menu "Attribute" in the inspector contextual-menu that appears
 # when the user right-clicks on the attribute of a node in the inspector,
 # and that allows to remove this attribute.
 inspectorRemoveAttribute.label=Remove Attribute %S
 inspectorRemoveAttribute.accesskey=R
 
+# LOCALIZATION NOTE (inspectorCopyAttributeValue.label): This is the label of a
+# sub-menu "Attribute" in the inspector contextual-menu that appears
+# when the user right-clicks on the attribute of a node in the inspector,
+# and that allows to copy the attribute value to clipboard.
+inspectorCopyAttributeValue.label=Copy Attribute Value %S
+inspectorCopyAttributeValue.accesskey=V
+
 # LOCALIZATION NOTE (inspector.nodePreview.selectNodeLabel):
 # This string is displayed in a tooltip that is shown when hovering over a DOM
 # node preview (e.g. something like "div#foo.bar").
 # DOM node previews can be displayed in places like the animation-inspector, the
 # console or the object inspector.
 # The tooltip invites the user to click on the node in order to select it in the
 # inspector panel.
 inspector.nodePreview.selectNodeLabel=Click to select this node in the Inspector
--- a/devtools/client/netmonitor/components/monitor-panel.js
+++ b/devtools/client/netmonitor/components/monitor-panel.js
@@ -8,18 +8,18 @@ const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { findDOMNode } = require("devtools/client/shared/vendor/react-dom");
 const Actions = require("../actions/index");
-const { Prefs } = require("../prefs");
-const { getFormDataSections } = require("../request-utils");
+const { Prefs } = require("../utils/prefs");
+const { getFormDataSections } = require("../utils/request-utils");
 const { getSelectedRequest } = require("../selectors/index");
 
 // Components
 const SplitBox = createFactory(require("devtools/client/shared/components/splitter/split-box"));
 const NetworkDetailsPanel = createFactory(require("../shared/components/network-details-panel"));
 const RequestList = createFactory(require("./request-list"));
 const Toolbar = createFactory(require("./toolbar"));
 
--- a/devtools/client/netmonitor/components/request-list-empty.js
+++ b/devtools/client/netmonitor/components/request-list-empty.js
@@ -7,17 +7,17 @@
 const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../actions/index");
 const { ACTIVITY_TYPE } = require("../constants");
-const { L10N } = require("../l10n");
+const { L10N } = require("../utils/l10n");
 
 const { button, div, span } = DOM;
 
 /**
  * UI displayed when the request list is empty. Contains instructions on reloading
  * the page and on triggering performance analysis of the page.
  */
 const RequestListEmptyNotice = createClass({
--- a/devtools/client/netmonitor/components/request-list-header.js
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
 const { div, button } = DOM;
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
-const { L10N } = require("../l10n");
+const { L10N } = require("../utils/l10n");
 const { getWaterfallScale } = require("../selectors/index");
 const Actions = require("../actions/index");
 const WaterfallBackground = require("../waterfall-background");
 const { getFormattedTime } = require("../utils/format-utils");
 
 // ms
 const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
 // px
--- a/devtools/client/netmonitor/components/request-list-item.js
+++ b/devtools/client/netmonitor/components/request-list-item.js
@@ -5,18 +5,18 @@
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../l10n");
-const { getAbbreviatedMimeType } = require("../request-utils");
+const { L10N } = require("../utils/l10n");
+const { getAbbreviatedMimeType } = require("../utils/request-utils");
 const { getFormattedSize } = require("../utils/format-utils");
 
 const { div, img, span } = DOM;
 
 /**
  * Compare two objects on a subset of their properties
  */
 function propertiesEqual(props, item1, item2) {
--- a/devtools/client/netmonitor/components/statistics-panel.js
+++ b/devtools/client/netmonitor/components/statistics-panel.js
@@ -8,18 +8,18 @@ const {
   createClass,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { Chart } = require("devtools/client/shared/widgets/Chart");
 const { PluralForm } = require("devtools/shared/plural-form");
 const Actions = require("../actions/index");
-const { Filters } = require("../filter-predicates");
-const { L10N } = require("../l10n");
+const { Filters } = require("../utils/filter-predicates");
+const { L10N } = require("../utils/l10n");
 const {
   getSizeWithDecimals,
   getTimeWithDecimals
 } = require("../utils/format-utils");
 
 const { button, div } = DOM;
 const MediaQueryList = window.matchMedia("(min-width: 700px)");
 
--- a/devtools/client/netmonitor/components/toolbar.js
+++ b/devtools/client/netmonitor/components/toolbar.js
@@ -8,18 +8,18 @@ const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const { PluralForm } = require("devtools/shared/plural-form");
 const Actions = require("../actions/index");
-const { L10N } = require("../l10n");
-const { Prefs } = require("../prefs");
+const { L10N } = require("../utils/l10n");
+const { Prefs } = require("../utils/prefs");
 const {
   getDisplayedRequestsSummary,
   getRequestFilterTypes,
   isNetworkDetailsToggleButtonDisabled,
 } = require("../selectors/index");
 const {
   getSizeWithDecimals,
   getTimeWithDecimals,
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -1,20 +1,14 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const general = {
-  FILTER_SEARCH_DELAY: 200,
-  // 100 KB in bytes
-  SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
-};
-
 const actionTypes = {
   ADD_REQUEST: "ADD_REQUEST",
   ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
   BATCH_ACTIONS: "BATCH_ACTIONS",
   BATCH_ENABLE: "BATCH_ENABLE",
   CLEAR_REQUESTS: "CLEAR_REQUESTS",
   CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
   CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
@@ -44,9 +38,83 @@ const ACTIVITY_TYPE = {
     WITH_CACHE_DEFAULT: 3
   },
 
   // Enabling or disabling the cache without triggering a reload.
   ENABLE_CACHE: 3,
   DISABLE_CACHE: 4
 };
 
-module.exports = Object.assign({ ACTIVITY_TYPE }, general, actionTypes);
+// The panel's window global is an EventEmitter firing the following events:
+const EVENTS = {
+  // When the monitored target begins and finishes navigating.
+  TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
+  TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
+
+  // When a network or timeline event is received.
+  // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
+  // more information about what each packet is supposed to deliver.
+  NETWORK_EVENT: "NetMonitor:NetworkEvent",
+  TIMELINE_EVENT: "NetMonitor:TimelineEvent",
+
+  // When a network event is added to the view
+  REQUEST_ADDED: "NetMonitor:RequestAdded",
+
+  // When request headers begin and finish receiving.
+  UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
+  RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
+
+  // When request cookies begin and finish receiving.
+  UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
+  RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
+
+  // When request post data begins and finishes receiving.
+  UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
+  RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
+
+  // When security information begins and finishes receiving.
+  UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
+  RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
+
+  // When response headers begin and finish receiving.
+  UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
+  RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
+
+  // When response cookies begin and finish receiving.
+  UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
+  RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
+
+  // When event timings begin and finish receiving.
+  UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
+  RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
+
+  // When response content begins, updates and finishes receiving.
+  STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
+  UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
+  RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
+
+  // When the request post params are displayed in the UI.
+  REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
+
+  // When the image response thumbnail is displayed in the UI.
+  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
+    "NetMonitor:ResponseImageThumbnailAvailable",
+
+  // Fired when charts have been displayed in the PerformanceStatisticsView.
+  PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
+  PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
+  EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
+
+  // Fired once the NetMonitorController establishes a connection to the debug
+  // target.
+  CONNECTED: "connected",
+};
+
+const general = {
+  ACTIVITY_TYPE,
+  EVENTS,
+  FILTER_SEARCH_DELAY: 200,
+  // 100 KB in bytes
+  SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE: 102400,
+};
+
+// flatten constants
+module.exports = Object.assign({}, general, actionTypes);
deleted file mode 100644
--- a/devtools/client/netmonitor/events.js
+++ /dev/null
@@ -1,72 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-// The panel's window global is an EventEmitter firing the following events:
-const EVENTS = {
-  // When the monitored target begins and finishes navigating.
-  TARGET_WILL_NAVIGATE: "NetMonitor:TargetWillNavigate",
-  TARGET_DID_NAVIGATE: "NetMonitor:TargetNavigate",
-
-  // When a network or timeline event is received.
-  // See https://developer.mozilla.org/docs/Tools/Web_Console/remoting for
-  // more information about what each packet is supposed to deliver.
-  NETWORK_EVENT: "NetMonitor:NetworkEvent",
-  TIMELINE_EVENT: "NetMonitor:TimelineEvent",
-
-  // When a network event is added to the view
-  REQUEST_ADDED: "NetMonitor:RequestAdded",
-
-  // When request headers begin and finish receiving.
-  UPDATING_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdating:RequestHeaders",
-  RECEIVED_REQUEST_HEADERS: "NetMonitor:NetworkEventUpdated:RequestHeaders",
-
-  // When request cookies begin and finish receiving.
-  UPDATING_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdating:RequestCookies",
-  RECEIVED_REQUEST_COOKIES: "NetMonitor:NetworkEventUpdated:RequestCookies",
-
-  // When request post data begins and finishes receiving.
-  UPDATING_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdating:RequestPostData",
-  RECEIVED_REQUEST_POST_DATA: "NetMonitor:NetworkEventUpdated:RequestPostData",
-
-  // When security information begins and finishes receiving.
-  UPDATING_SECURITY_INFO: "NetMonitor::NetworkEventUpdating:SecurityInfo",
-  RECEIVED_SECURITY_INFO: "NetMonitor::NetworkEventUpdated:SecurityInfo",
-
-  // When response headers begin and finish receiving.
-  UPDATING_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdating:ResponseHeaders",
-  RECEIVED_RESPONSE_HEADERS: "NetMonitor:NetworkEventUpdated:ResponseHeaders",
-
-  // When response cookies begin and finish receiving.
-  UPDATING_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdating:ResponseCookies",
-  RECEIVED_RESPONSE_COOKIES: "NetMonitor:NetworkEventUpdated:ResponseCookies",
-
-  // When event timings begin and finish receiving.
-  UPDATING_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdating:EventTimings",
-  RECEIVED_EVENT_TIMINGS: "NetMonitor:NetworkEventUpdated:EventTimings",
-
-  // When response content begins, updates and finishes receiving.
-  STARTED_RECEIVING_RESPONSE: "NetMonitor:NetworkEventUpdating:ResponseStart",
-  UPDATING_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdating:ResponseContent",
-  RECEIVED_RESPONSE_CONTENT: "NetMonitor:NetworkEventUpdated:ResponseContent",
-
-  // When the request post params are displayed in the UI.
-  REQUEST_POST_PARAMS_DISPLAYED: "NetMonitor:RequestPostParamsAvailable",
-
-  // When the image response thumbnail is displayed in the UI.
-  RESPONSE_IMAGE_THUMBNAIL_DISPLAYED:
-    "NetMonitor:ResponseImageThumbnailAvailable",
-
-  // Fired when charts have been displayed in the PerformanceStatisticsView.
-  PLACEHOLDER_CHARTS_DISPLAYED: "NetMonitor:PlaceholderChartsDisplayed",
-  PRIMED_CACHE_CHART_DISPLAYED: "NetMonitor:PrimedChartsDisplayed",
-  EMPTY_CACHE_CHART_DISPLAYED: "NetMonitor:EmptyChartsDisplayed",
-
-  // Fired once the NetMonitorController establishes a connection to the debug
-  // target.
-  CONNECTED: "connected",
-};
-
-exports.EVENTS = EVENTS;
--- a/devtools/client/netmonitor/har/har-builder.js
+++ b/devtools/client/netmonitor/har/har-builder.js
@@ -7,17 +7,17 @@
 const Services = require("Services");
 const appInfo = Services.appinfo;
 const { LocalizationHelper } = require("devtools/shared/l10n");
 const { CurlUtils } = require("devtools/client/shared/curl");
 const {
   getFormDataSections,
   getUrlQuery,
   parseQueryString,
-} = require("devtools/client/netmonitor/request-utils");
+} = require("devtools/client/netmonitor/utils/request-utils");
 
 const L10N = new LocalizationHelper("devtools/client/locales/har.properties");
 const HAR_VERSION = "1.1";
 
 /**
  * This object is responsible for building HAR file. See HAR spec:
  * https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/HAR/Overview.html
  * http://www.softwareishard.com/blog/har-12-spec/
--- a/devtools/client/netmonitor/middleware/prefs.js
+++ b/devtools/client/netmonitor/middleware/prefs.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   ENABLE_REQUEST_FILTER_TYPE_ONLY,
   TOGGLE_REQUEST_FILTER_TYPE,
 } = require("../constants");
-const { Prefs } = require("../prefs");
+const { Prefs } = require("../utils/prefs");
 const { getRequestFilterTypes } = require("../selectors/index");
 
 /**
   * Whenever the User clicks on a filter in the network monitor, save the new
   * filters for future tabs
   */
 function prefsMiddleware(store) {
   return next => action => {
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -10,26 +10,20 @@ DIRS += [
     'reducers',
     'selectors',
     'shared',
     'utils',
 ]
 
 DevToolsModules(
     'constants.js',
-    'events.js',
-    'filter-predicates.js',
-    'l10n.js',
     'netmonitor-controller.js',
     'panel.js',
-    'prefs.js',
     'request-list-context-menu.js',
     'request-list-tooltip.js',
-    'request-utils.js',
-    'sort-predicates.js',
     'store.js',
     'waterfall-background.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
 
 with Files('**'):
     BUG_COMPONENT = ('Firefox', 'Developer Tools: Netmonitor')
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -3,24 +3,23 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const { TimelineFront } = require("devtools/shared/fronts/timeline");
 const { CurlUtils } = require("devtools/client/shared/curl");
-const { ACTIVITY_TYPE } = require("./constants");
-const { EVENTS } = require("./events");
+const { ACTIVITY_TYPE, EVENTS } = require("./constants");
 const { configureStore } = require("./store");
 const Actions = require("./actions/index");
 const {
   fetchHeaders,
   formDataURI,
-} = require("./request-utils");
+} = require("./utils/request-utils");
 const {
   getRequestById,
   getDisplayedRequestById,
 } = require("./selectors/index");
 
 const gStore = window.gStore = configureStore();
 
 /**
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const I = require("devtools/client/shared/vendor/immutable");
-const { getUrlDetails } = require("../request-utils");
+const { getUrlDetails } = require("../utils/request-utils");
 const {
   ADD_REQUEST,
   CLEAR_REQUESTS,
   CLONE_SELECTED_REQUEST,
   OPEN_NETWORK_DETAILS,
   REMOVE_SELECTED_CUSTOM_REQUEST,
   SELECT_REQUEST,
   SEND_CUSTOM_REQUEST,
--- a/devtools/client/netmonitor/request-list-context-menu.js
+++ b/devtools/client/netmonitor/request-list-context-menu.js
@@ -6,23 +6,23 @@
 
 const Services = require("Services");
 const { Curl } = require("devtools/client/shared/curl");
 const { gDevTools } = require("devtools/client/framework/devtools");
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 const clipboardHelper = require("devtools/shared/platform/clipboard");
 const { HarExporter } = require("./har/har-exporter");
-const { L10N } = require("./l10n");
+const { L10N } = require("./utils/l10n");
 const {
   formDataURI,
   getFormDataSections,
   getUrlQuery,
   parseQueryString,
-} = require("./request-utils");
+} = require("./utils/request-utils");
 const {
   getSelectedRequest,
   getSortedRequests,
 } = require("./selectors/index");
 
 function RequestListContextMenu({
   cloneSelectedRequest,
   openStatistics,
--- a/devtools/client/netmonitor/request-list-tooltip.js
+++ b/devtools/client/netmonitor/request-list-tooltip.js
@@ -3,18 +3,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {
   setImageTooltip,
   getImageDimensions,
 } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-const { WEBCONSOLE_L10N } = require("./l10n");
-const { formDataURI } = require("./request-utils");
+const { WEBCONSOLE_L10N } = require("./utils/l10n");
+const { formDataURI } = require("./utils/request-utils");
 
 // px
 const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
 // px
 const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
--- a/devtools/client/netmonitor/selectors/requests.js
+++ b/devtools/client/netmonitor/selectors/requests.js
@@ -1,17 +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";
 
 const { createSelector } = require("devtools/client/shared/vendor/reselect");
-const { Filters, isFreetextMatch } = require("../filter-predicates");
-const { Sorters } = require("../sort-predicates");
+const { Filters, isFreetextMatch } = require("../utils/filter-predicates");
+const { Sorters } = require("../utils/sort-predicates");
 
 /**
  * Take clones into account when sorting.
  * If a request is a clone, use the original request for comparison.
  * If one of the compared request is a clone of the other, sort them next to each other.
  */
 function sortWithClones(requests, sorter, a, b) {
   const aId = a.id, bId = b.id;
--- a/devtools/client/netmonitor/shared/components/cookies-panel.js
+++ b/devtools/client/netmonitor/shared/components/cookies-panel.js
@@ -4,17 +4,17 @@
 
 "use strict";
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
+const { L10N } = require("../../utils/l10n");
 
 // Component
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div } = DOM;
 
 const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
 const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
--- a/devtools/client/netmonitor/shared/components/custom-request-panel.js
+++ b/devtools/client/netmonitor/shared/components/custom-request-panel.js
@@ -4,24 +4,24 @@
 
 "use strict";
 
 const {
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
-const { L10N } = require("../../l10n");
+const { L10N } = require("../../utils/l10n");
 const Actions = require("../../actions/index");
 const { getSelectedRequest } = require("../../selectors/index");
 const {
   getUrlQuery,
   parseQueryString,
   writeHeaderText,
-} = require("../../request-utils");
+} = require("../../utils/request-utils");
 
 const {
   button,
   div,
   input,
   textarea,
 } = DOM;
 
--- a/devtools/client/netmonitor/shared/components/headers-panel.js
+++ b/devtools/client/netmonitor/shared/components/headers-panel.js
@@ -5,21 +5,21 @@
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
-const { writeHeaderText } = require("../../request-utils");
+const { L10N } = require("../../utils/l10n");
+const { writeHeaderText } = require("../../utils/request-utils");
 const { getHeadersURL } = require("../../utils/mdn-utils");
 const { getFormattedSize } = require("../../utils/format-utils");
-const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const Rep = createFactory(REPS.Rep);
 
 // Components
 const MDNLink = createFactory(require("./mdn-link"));
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { button, div, input, textarea } = DOM;
 
--- a/devtools/client/netmonitor/shared/components/mdn-link.js
+++ b/devtools/client/netmonitor/shared/components/mdn-link.js
@@ -5,17 +5,17 @@
 "use strict";
 
 const Services = require("Services");
 const {
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { gDevTools } = require("devtools/client/framework/devtools");
-const { L10N } = require("../../l10n");
+const { L10N } = require("../../utils/l10n");
 
 const { a } = DOM;
 
 const LEARN_MORE = L10N.getStr("netmonitor.headers.learnMore");
 
 function MDNLink({ url }) {
   return (
     a({
--- a/devtools/client/netmonitor/shared/components/params-panel.js
+++ b/devtools/client/netmonitor/shared/components/params-panel.js
@@ -4,18 +4,18 @@
 
 "use strict";
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
-const { getUrlQuery, parseQueryString } = require("../../request-utils");
+const { L10N } = require("../../utils/l10n");
+const { getUrlQuery, parseQueryString } = require("../../utils/request-utils");
 
 // Components
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div } = DOM;
 
 const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
 const PARAMS_EMPTY_TEXT = L10N.getStr("paramsEmptyText");
--- a/devtools/client/netmonitor/shared/components/properties-view.js
+++ b/devtools/client/netmonitor/shared/components/properties-view.js
@@ -8,17 +8,17 @@
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 
-const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const Rep = createFactory(REPS.Rep);
 
 const { FILTER_SEARCH_DELAY } = require("../../constants");
 
 // Components
 const Editor = createFactory(require("devtools/client/netmonitor/shared/components/editor"));
 const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
 const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
--- a/devtools/client/netmonitor/shared/components/response-panel.js
+++ b/devtools/client/netmonitor/shared/components/response-panel.js
@@ -5,18 +5,18 @@
 "use strict";
 
 const {
   createClass,
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
-const { formDataURI, getUrlBaseName } = require("../../request-utils");
+const { L10N } = require("../../utils/l10n");
+const { formDataURI, getUrlBaseName } = require("../../utils/request-utils");
 
 // Components
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div, img } = DOM;
 const JSON_SCOPE_NAME = L10N.getStr("jsonScopeName");
 const JSON_FILTER_TEXT = L10N.getStr("jsonFilterText");
 const RESPONSE_IMG_NAME = L10N.getStr("netmonitor.response.name");
--- a/devtools/client/netmonitor/shared/components/security-panel.js
+++ b/devtools/client/netmonitor/shared/components/security-panel.js
@@ -4,18 +4,18 @@
 
 "use strict";
 
 const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
-const { getUrlHost } = require("../../request-utils");
+const { L10N } = require("../../utils/l10n");
+const { getUrlHost } = require("../../utils/request-utils");
 
 // Components
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div, input, span } = DOM;
 
 /*
  * Security panel component
--- a/devtools/client/netmonitor/shared/components/tabbox-panel.js
+++ b/devtools/client/netmonitor/shared/components/tabbox-panel.js
@@ -5,18 +5,18 @@
 "use strict";
 
 const {
   createFactory,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { connect } = require("devtools/client/shared/vendor/react-redux");
 const Actions = require("../../actions/index");
-const { Filters } = require("../../filter-predicates");
-const { L10N } = require("../../l10n");
+const { Filters } = require("../../utils/filter-predicates");
+const { L10N } = require("../../utils/l10n");
 const { getSelectedRequest } = require("../../selectors/index");
 
 // Components
 const Tabbar = createFactory(require("devtools/client/shared/components/tabs/tabbar"));
 const TabPanel = createFactory(require("devtools/client/shared/components/tabs/tabs").TabPanel);
 const CookiesPanel = createFactory(require("./cookies-panel"));
 const HeadersPanel = createFactory(require("./headers-panel"));
 const ParamsPanel = createFactory(require("./params-panel"));
--- a/devtools/client/netmonitor/shared/components/timings-panel.js
+++ b/devtools/client/netmonitor/shared/components/timings-panel.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
-const { L10N } = require("../../l10n");
+const { L10N } = require("../../utils/l10n");
 
 const { div, span } = DOM;
 const types = ["blocked", "dns", "connect", "send", "wait", "receive"];
 const TIMINGS_END_PADDING = "80px";
 
 /*
  * Timings panel component
  * Display timeline bars that shows the total wait time for various stages
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -6,17 +6,17 @@
 const BROTLI_URL = HTTPS_EXAMPLE_URL + "html_brotli-test-page.html";
 const BROTLI_REQUESTS = 1;
 
 /**
  * Test brotli encoded response is handled correctly on HTTPS.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(BROTLI_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_charts-02.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-02.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Makes sure Pie Charts have the right internal structure when
  * initialized with empty data.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let pie = Chart.Pie(document, {
--- a/devtools/client/netmonitor/test/browser_net_charts-03.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-03.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Makes sure Table Charts have the right internal structure.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let table = Chart.Table(document, {
--- a/devtools/client/netmonitor/test/browser_net_charts-04.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-04.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Makes sure Pie Charts have the right internal structure when
  * initialized with empty data.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let table = Chart.Table(document, {
--- a/devtools/client/netmonitor/test/browser_net_charts-05.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-05.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Makes sure Pie+Table Charts have the right internal structure.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let chart = Chart.PieTable(document, {
--- a/devtools/client/netmonitor/test/browser_net_charts-06.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-06.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Makes sure Pie Charts correctly handle empty source data.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let pie = Chart.Pie(document, {
--- a/devtools/client/netmonitor/test/browser_net_charts-07.js
+++ b/devtools/client/netmonitor/test/browser_net_charts-07.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Makes sure Table Charts correctly handle empty source data.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
   let { Chart } = windowRequire("devtools/client/shared/widgets/Chart");
 
   let table = Chart.Table(document, {
--- a/devtools/client/netmonitor/test/browser_net_clear.js
+++ b/devtools/client/netmonitor/test/browser_net_clear.js
@@ -8,17 +8,17 @@
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let detailsPane = document.querySelector("#details-pane");
   let detailsPanelToggleButton = document.querySelector(".network-details-panel-toggle");
   let clearButton = document.querySelector("#requests-menu-clear-button");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   // Make sure we start in a sane state
   assertNoRequestState();
--- a/devtools/client/netmonitor/test/browser_net_complex-params.js
+++ b/devtools/client/netmonitor/test/browser_net_complex-params.js
@@ -9,17 +9,17 @@
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(PARAMS_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { L10N } = windowRequire("devtools/client/netmonitor/l10n");
+  let { L10N } = windowRequire("devtools/client/netmonitor/utils/l10n");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1, 6);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if different response content types are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_filter-04.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-04.js
@@ -32,17 +32,17 @@ const REQUESTS_WITH_MEDIA_AND_FLASH_AND_
 add_task(function* () {
   Services.prefs.setCharPref("devtools.netmonitor.filters", '["bogus", "js", "alsobogus"]');
 
   let { monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   let { gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
+  let { Prefs } = windowRequire("devtools/client/netmonitor/utils/prefs");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   is(Prefs.filters.length, 1,
     "Only the valid filter types should be loaded, the others should be ignored");
   is(Prefs.filters[0], "js",
     "The only filter type is correct.");
 
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -12,17 +12,17 @@ add_task(function* () {
 
   let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { getDisplayedRequestsSummary } =
     windowRequire("devtools/client/netmonitor/selectors/index");
-  let { L10N } = windowRequire("devtools/client/netmonitor/l10n");
+  let { L10N } = windowRequire("devtools/client/netmonitor/utils/l10n");
   let { PluralForm } = windowRequire("devtools/shared/plural-form");
 
   gStore.dispatch(Actions.batchEnable(false));
   testStatus();
 
   for (let i = 0; i < 2; i++) {
     info(`Performing requests in batch #${i}`);
     let wait = waitForNetworkEvents(monitor, 8);
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -9,18 +9,17 @@
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CONTENT_TYPE_WITHOUT_CACHE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire, NetMonitorController } =
     monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForEvents();
   yield performRequests();
   yield wait;
 
   info("Checking the image thumbnail when all items are shown.");
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -10,18 +10,17 @@ const IMAGE_TOOLTIP_REQUESTS = 1;
  * Tests if image responses show a popup in the requests menu when hovered.
  */
 add_task(function* test() {
   let { tab, monitor } = yield initNetMonitor(IMAGE_TOOLTIP_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { ACTIVITY_TYPE, EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
   let toolboxDoc = monitor.toolbox.doc;
 
   gStore.dispatch(Actions.batchEnable(false));
 
--- a/devtools/client/netmonitor/test/browser_net_json-b64.js
+++ b/devtools/client/netmonitor/test/browser_net_json-b64.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if JSON responses encoded in base64 are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
   let { tab, monitor } = yield initNetMonitor(JSON_B64_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if very long JSON responses are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
   info("Starting test... ");
 
   // This is receiving over 80 KB of json and will populate over 6000 items
   // in a variables view instance. Debug builds are slow.
   requestLongerTimeout(4);
 
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if malformed JSON responses are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
   let { tab, monitor } = yield initNetMonitor(JSON_MALFORMED_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
     getSortedRequests,
--- a/devtools/client/netmonitor/test/browser_net_json-null.js
+++ b/devtools/client/netmonitor/test/browser_net_json-null.js
@@ -1,14 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const { L10N } = require("devtools/client/netmonitor/l10n");
+const { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
 /**
  * Tests if JSON responses containing null values are properly displayed.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(JSON_BASIC_URL + "?name=null");
   info("Starting test... ");
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if JSON responses with unusal/custom MIME types are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_CUSTOM_MIME_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if JSON responses with unusal/custom MIME types are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_TEXT_MIME_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if JSONP responses are handled correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSONP_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_page-nav.js
+++ b/devtools/client/netmonitor/test/browser_net_page-nav.js
@@ -8,17 +8,17 @@
  * action in the network monitor.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { windowRequire } = monitor.panelWin;
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
 
   yield testNavigate();
   yield testNavigateBack();
   yield testClose();
 
   function* testNavigate() {
     info("Navigating forward...");
 
--- a/devtools/client/netmonitor/test/browser_net_pane-collapse.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-collapse.js
@@ -7,17 +7,17 @@
  * Tests if the network monitor panes collapse properly.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
-  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
+  let { Prefs } = windowRequire("devtools/client/netmonitor/utils/prefs");
   let detailsPaneToggleButton = document.querySelector(".network-details-panel-toggle");
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
   ok(!document.querySelector(".network-details-panel") &&
      detailsPaneToggleButton.classList.contains("pane-collapsed"),
--- a/devtools/client/netmonitor/test/browser_net_pane-toggle.js
+++ b/devtools/client/netmonitor/test/browser_net_pane-toggle.js
@@ -8,17 +8,17 @@
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getSelectedRequest,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let toggleButton = document.querySelector(".network-details-panel-toggle");
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if the POST requests display the correct information in the UI.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   // Set a higher panel height in order to get full CodeMirror content
   Services.prefs.setIntPref("devtools.toolbox.footer.height", 400);
 
   let { tab, monitor } = yield initNetMonitor(POST_DATA_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
--- a/devtools/client/netmonitor/test/browser_net_post-data-02.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-02.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Tests if the POST requests display the correct information in the UI,
  * for raw payloads with attached content-type headers.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_post-data-03.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-03.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Tests if the POST requests display the correct information in the UI,
  * for raw payloads with content-type headers attached to the upload stream.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_RAW_WITH_HEADERS_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
   gStore.dispatch(Actions.batchEnable(false));
--- a/devtools/client/netmonitor/test/browser_net_post-data-04.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-04.js
@@ -4,17 +4,17 @@
 "use strict";
 
 /**
  * Tests if the POST requests display the correct information in the UI,
  * for JSON payloads.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(POST_JSON_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
 
   gStore.dispatch(Actions.batchEnable(false));
--- a/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-and-l10n.js
@@ -3,23 +3,23 @@
 
 "use strict";
 
 /**
  * Tests if the preferences and localization objects work correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { windowRequire } = monitor.panelWin;
-  let { Prefs } = windowRequire("devtools/client/netmonitor/prefs");
+  let { Prefs } = windowRequire("devtools/client/netmonitor/utils/prefs");
 
   testL10N();
   testPrefs();
 
   return teardown(monitor);
 
   function testL10N() {
     is(typeof L10N.getStr("netmonitor.security.enabled"), "string",
--- a/devtools/client/netmonitor/test/browser_net_prefs-reload.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -17,17 +17,17 @@ add_task(function* () {
   // This test reopens the network monitor a bunch of times, for different
   // hosts (bottom, side, window). This seems to be slow on debug builds.
   requestLongerTimeout(3);
 
   // Use these getters instead of caching instances inside the panel win,
   // since the tool is reopened a bunch of times during this test
   // and the instances will differ.
   let getDoc = () => monitor.panelWin.document;
-  let getPrefs = () => monitor.panelWin.windowRequire("devtools/client/netmonitor/prefs").Prefs;
+  let getPrefs = () => monitor.panelWin.windowRequire("devtools/client/netmonitor/utils/prefs").Prefs;
   let getStore = () => monitor.panelWin.gStore;
   let getState = () => getStore().getState();
 
   let prefsToCheck = {
     filters: {
       // A custom new value to be used for the verified preference.
       newValue: ["html", "css"],
       // Getter used to retrieve the current value from the frontend, in order
--- a/devtools/client/netmonitor/test/browser_net_reload-markers.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-markers.js
@@ -7,17 +7,17 @@
  * Tests if the empty-requests reload button works.
  */
 
 add_task(function* () {
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, windowRequire } = monitor.panelWin;
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let button = document.querySelector("#requests-menu-reload-notice-button");
   button.click();
 
   let markers = [];
 
   monitor.panelWin.on(EVENTS.TIMELINE_EVENT, (_, marker) => {
     markers.push(marker);
   });
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Test if request and response body logging stays on after opening the console.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(JSON_LONG_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
     getDisplayedRequests,
--- a/devtools/client/netmonitor/test/browser_net_security-error.js
+++ b/devtools/client/netmonitor/test/browser_net_security-error.js
@@ -6,17 +6,17 @@
 /**
  * Test that Security details tab shows an error message with broken connections.
  */
 
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(CUSTOM_GET_URL);
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   info("Requesting a resource that has a certificate problem.");
 
   let wait = waitForSecurityBrokenNetworkEvent();
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.performRequests(1, "https://nocert.example.com");
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -3,24 +3,24 @@
 
 "use strict";
 
 /**
  * Tests if requests render correct information in the menu UI.
  */
 
 function test() {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   initNetMonitor(SIMPLE_SJS).then(({ tab, monitor }) => {
     info("Starting test... ");
 
     let { document, gStore, windowRequire } = monitor.panelWin;
     let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-    let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+    let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
     let {
       getDisplayedRequests,
       getSelectedRequest,
       getSortedRequests,
     } = windowRequire("devtools/client/netmonitor/selectors/index");
 
     gStore.dispatch(Actions.batchEnable(false));
 
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -3,24 +3,24 @@
 
 "use strict";
 
 /**
  * Tests if requests render correct information in the details UI.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(SIMPLE_SJS);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   let wait = waitForNetworkEvents(monitor, 1);
--- a/devtools/client/netmonitor/test/browser_net_simple-request.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -13,17 +13,17 @@
  * 4) Number of requests displayed
  */
 add_task(function* () {
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getDisplayedRequests,
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   gStore.dispatch(Actions.batchEnable(false));
 
   is(document.querySelector(".network-details-panel-toggle").hasAttribute("disabled"),
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Test if sorting columns in the network table works correctly with new requests.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SORTING_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on debug builds. This could be because
   // of the heavy dom manipulation associated with sorting.
   requestLongerTimeout(2);
 
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Test if sorting columns in the network table works correctly.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { monitor } = yield initNetMonitor(SORTING_URL);
   info("Starting test... ");
 
   // It seems that this test may be slow on debug builds. This could be because
   // of the heavy dom manipulation associated with sorting.
   requestLongerTimeout(2);
 
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if requests display the correct status code and text in the UI.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(STATUS_CODES_URL);
 
   info("Starting test... ");
 
   let { document, gStore, windowRequire } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let {
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -12,17 +12,17 @@ add_task(function* () {
 
 function* throttleTest(actuallyThrottle) {
   requestLongerTimeout(2);
 
   let { monitor } = yield initNetMonitor(SIMPLE_URL);
   let { document, gStore, windowRequire, NetMonitorController } = monitor.panelWin;
   let Actions = windowRequire("devtools/client/netmonitor/actions/index");
   let { ACTIVITY_TYPE } = windowRequire("devtools/client/netmonitor/constants");
-  let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+  let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
   let {
     getSortedRequests,
   } = windowRequire("devtools/client/netmonitor/selectors/index");
 
   info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
 
   // When throttling, must be smaller than the length of the content
   // of SIMPLE_URL in bytes.
--- a/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
+++ b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
@@ -3,17 +3,17 @@
 
 "use strict";
 
 /**
  * Tests if timeline correctly displays interval divisions.
  */
 
 add_task(function* () {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
 
   let { tab, monitor } = yield initNetMonitor(SIMPLE_URL);
   info("Starting test... ");
 
   let { $, $all, NetMonitorView, NetMonitorController } = monitor.panelWin;
   let { RequestsMenu } = NetMonitorView;
 
   // Disable transferred size column support for this test.
--- a/devtools/client/netmonitor/test/browser_net_truncate.js
+++ b/devtools/client/netmonitor/test/browser_net_truncate.js
@@ -3,30 +3,30 @@
 
 "use strict";
 
 /**
  * Verifies that truncated response bodies still have the correct reported size.
  */
 
 function test() {
-  let { L10N } = require("devtools/client/netmonitor/l10n");
+  let { L10N } = require("devtools/client/netmonitor/utils/l10n");
   const { RESPONSE_BODY_LIMIT } = require("devtools/shared/webconsole/network-monitor");
 
   const URL = EXAMPLE_URL + "sjs_truncate-test-server.sjs?limit=" + RESPONSE_BODY_LIMIT;
 
   // Another slow test on Linux debug.
   requestLongerTimeout(2);
 
   initNetMonitor(URL).then(({ tab, monitor }) => {
     info("Starting test... ");
 
     let { document, gStore, windowRequire } = monitor.panelWin;
     let Actions = windowRequire("devtools/client/netmonitor/actions/index");
-    let { EVENTS } = windowRequire("devtools/client/netmonitor/events");
+    let { EVENTS } = windowRequire("devtools/client/netmonitor/constants");
     let {
       getDisplayedRequests,
       getSortedRequests,
     } = windowRequire("devtools/client/netmonitor/selectors/index");
 
     gStore.dispatch(Actions.batchEnable(false));
 
     waitForNetworkEvents(monitor, 1)
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -5,24 +5,24 @@
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
-const { EVENTS } = require("devtools/client/netmonitor/events");
+const { EVENTS } = require("devtools/client/netmonitor/constants");
 var { Toolbox } = require("devtools/client/framework/toolbox");
 const {
   decodeUnicodeUrl,
   getUrlBaseName,
   getUrlQuery,
   getUrlHost,
-} = require("devtools/client/netmonitor/request-utils");
+} = require("devtools/client/netmonitor/utils/request-utils");
 
 const EXAMPLE_URL = "http://example.com/browser/devtools/client/netmonitor/test/";
 const HTTPS_EXAMPLE_URL = "https://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";
 const CONTENT_TYPE_URL = EXAMPLE_URL + "html_content-type-test-page.html";
rename from devtools/client/netmonitor/filter-predicates.js
rename to devtools/client/netmonitor/utils/filter-predicates.js
--- a/devtools/client/netmonitor/utils/format-utils.js
+++ b/devtools/client/netmonitor/utils/format-utils.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 { L10N } = require("../l10n");
+const { L10N } = require("./l10n");
 
 // Constants for formatting bytes.
 const BYTES_IN_KB = 1024;
 const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
 const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
 const MAX_BYTES_SIZE = 1000;
 const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
 const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
rename from devtools/client/netmonitor/l10n.js
rename to devtools/client/netmonitor/utils/l10n.js
--- a/devtools/client/netmonitor/utils/moz.build
+++ b/devtools/client/netmonitor/utils/moz.build
@@ -1,9 +1,14 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
+    'filter-predicates.js',
     'format-utils.js',
+    'l10n.js',
     'mdn-utils.js',
+    'prefs.js',
+    'request-utils.js',
+    'sort-predicates.js',
 )
rename from devtools/client/netmonitor/prefs.js
rename to devtools/client/netmonitor/utils/prefs.js
rename from devtools/client/netmonitor/request-utils.js
rename to devtools/client/netmonitor/utils/request-utils.js
rename from devtools/client/netmonitor/sort-predicates.js
rename to devtools/client/netmonitor/utils/sort-predicates.js
--- a/devtools/client/shared/components/reps/load-reps.js
+++ b/devtools/client/shared/components/reps/load-reps.js
@@ -9,35 +9,38 @@
 
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   let REPS;
   let MODE;
   let createFactories;
   let parseURLEncodedText;
   let parseURLParams;
+  let getSelectableInInspectorGrips;
 
   // useRepsBundle hardcoded to true to use the bundle from github. Switch back to rely
   // on individual reps files by flipping it to false.
   let useRepsBundle = true;
   if (useRepsBundle) {
     const bundle = require("devtools/client/shared/components/reps/reps");
     REPS = bundle.REPS;
     MODE = bundle.MODE;
     createFactories = bundle.createFactories;
     parseURLEncodedText = bundle.parseURLEncodedText;
     parseURLParams = bundle.parseURLParams;
+    getSelectableInInspectorGrips = bundle.getSelectableInInspectorGrips;
   } else {
     // Commenting out all requires, otherwise requirejs will agressively load all
     // dependencies when loading load-reps.js, which will fail because files have been
     // removed.
     // ({ createFactories, parseURLEncodedText, parseURLParams } =
     //   require("devtools/client/shared/components/reps/rep-utils"));
     // REPS = require("devtools/client/shared/components/reps/rep").REPS;
     // MODE = require("devtools/client/shared/components/reps/constants").MODE;
   }
 
   exports.REPS = REPS;
   exports.MODE = MODE;
   exports.createFactories = createFactories;
   exports.parseURLEncodedText = parseURLEncodedText;
   exports.parseURLParams = parseURLParams;
+  exports.getSelectableInInspectorGrips = getSelectableInInspectorGrips;
 });
--- a/devtools/client/shared/components/reps/reps.css
+++ b/devtools/client/shared/components/reps/reps.css
@@ -176,8 +176,26 @@
   font-weight: normal;
   white-space: pre-wrap;
 }
 
 .theme-dark .caption,
 .theme-light .caption {
   font-weight: normal;
 }
+
+/******************************************************************************/
+/* Open DOMNode in inspector button */
+
+.open-inspector svg {
+  fill: rgb(215, 215, 215);
+  height: 16px;
+  width: 16px;
+  margin-left: .25em;
+  cursor: pointer;
+  vertical-align: middle;
+}
+
+.objectBox-node:hover .open-inspector svg,
+.objectBox-textNode:hover .open-inspector svg,
+.open-inspector svg:hover {
+  fill: rgb(65, 175, 230);
+}
--- a/devtools/client/shared/components/reps/reps.js
+++ b/devtools/client/shared/components/reps/reps.js
@@ -2,17 +2,17 @@
 	if(typeof exports === 'object' && typeof module === 'object')
 		module.exports = factory(require("devtools/client/shared/vendor/react"));
 	else if(typeof define === 'function' && define.amd)
 		define(["devtools/client/shared/vendor/react"], factory);
 	else {
 		var a = typeof exports === 'object' ? factory(require("devtools/client/shared/vendor/react")) : factory(root["devtools/client/shared/vendor/react"]);
 		for(var i in a) (typeof exports === 'object' ? exports : root)[i] = a[i];
 	}
-})(this, function(__WEBPACK_EXTERNAL_MODULE_1__) {
+})(this, function(__WEBPACK_EXTERNAL_MODULE_3__) {
 return /******/ (function(modules) { // webpackBootstrap
 /******/ 	// The module cache
 /******/ 	var installedModules = {};
 /******/
 /******/ 	// The require function
 /******/ 	function __webpack_require__(moduleId) {
 /******/
 /******/ 		// Check if module is in cache
@@ -49,88 +49,86 @@ return /******/ (function(modules) { // 
 /******/ 	// Load entry module and return exports
 /******/ 	return __webpack_require__(0);
 /******/ })
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ function(module, exports, __webpack_require__) {
 
-	const React = __webpack_require__(1);
-	
-	const { MODE } = __webpack_require__(2);
-	const { REPS } = __webpack_require__(3);
-	const { createFactories, parseURLEncodedText, parseURLParams } = __webpack_require__(4);
+	const { MODE } = __webpack_require__(1);
+	const { REPS } = __webpack_require__(2);
+	const {
+	  createFactories,
+	  parseURLEncodedText,
+	  parseURLParams,
+	  getSelectableInInspectorGrips
+	} = __webpack_require__(4);
 	
 	module.exports = {
 	  REPS,
 	  MODE,
 	  createFactories,
 	  parseURLEncodedText,
-	  parseURLParams
+	  parseURLParams,
+	  getSelectableInInspectorGrips
 	};
 
 /***/ },
 /* 1 */
 /***/ function(module, exports) {
 
-	module.exports = __WEBPACK_EXTERNAL_MODULE_1__;
-
-/***/ },
-/* 2 */
-/***/ function(module, exports) {
-
 	module.exports = {
 	  MODE: {
 	    TINY: Symbol("TINY"),
 	    SHORT: Symbol("SHORT"),
 	    LONG: Symbol("LONG")
 	  }
 	};
 
 /***/ },
-/* 3 */
+/* 2 */
 /***/ function(module, exports, __webpack_require__) {
 
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { isGrip } = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
+	const { MODE } = __webpack_require__(1);
 	
 	// Load all existing rep templates
-	const Undefined = __webpack_require__(5);
-	const Null = __webpack_require__(6);
-	const StringRep = __webpack_require__(7);
-	const LongStringRep = __webpack_require__(8);
-	const Number = __webpack_require__(9);
-	const ArrayRep = __webpack_require__(10);
-	const Obj = __webpack_require__(12);
-	const SymbolRep = __webpack_require__(15);
-	const InfinityRep = __webpack_require__(16);
-	const NaNRep = __webpack_require__(17);
+	const Undefined = __webpack_require__(6);
+	const Null = __webpack_require__(7);
+	const StringRep = __webpack_require__(8);
+	const LongStringRep = __webpack_require__(9);
+	const Number = __webpack_require__(10);
+	const ArrayRep = __webpack_require__(11);
+	const Obj = __webpack_require__(13);
+	const SymbolRep = __webpack_require__(16);
+	const InfinityRep = __webpack_require__(17);
+	const NaNRep = __webpack_require__(18);
 	
 	// DOM types (grips)
-	const Attribute = __webpack_require__(18);
-	const DateTime = __webpack_require__(19);
-	const Document = __webpack_require__(20);
-	const Event = __webpack_require__(21);
-	const Func = __webpack_require__(22);
-	const PromiseRep = __webpack_require__(23);
-	const RegExp = __webpack_require__(24);
-	const StyleSheet = __webpack_require__(25);
-	const CommentNode = __webpack_require__(26);
+	const Attribute = __webpack_require__(19);
+	const DateTime = __webpack_require__(20);
+	const Document = __webpack_require__(21);
+	const Event = __webpack_require__(22);
+	const Func = __webpack_require__(23);
+	const PromiseRep = __webpack_require__(24);
+	const RegExp = __webpack_require__(25);
+	const StyleSheet = __webpack_require__(26);
+	const CommentNode = __webpack_require__(27);
 	const ElementNode = __webpack_require__(28);
-	const TextNode = __webpack_require__(29);
-	const ErrorRep = __webpack_require__(30);
-	const Window = __webpack_require__(31);
-	const ObjectWithText = __webpack_require__(32);
-	const ObjectWithURL = __webpack_require__(33);
-	const GripArray = __webpack_require__(34);
-	const GripMap = __webpack_require__(35);
-	const Grip = __webpack_require__(14);
+	const TextNode = __webpack_require__(32);
+	const ErrorRep = __webpack_require__(33);
+	const Window = __webpack_require__(34);
+	const ObjectWithText = __webpack_require__(35);
+	const ObjectWithURL = __webpack_require__(36);
+	const GripArray = __webpack_require__(37);
+	const GripMap = __webpack_require__(38);
+	const Grip = __webpack_require__(15);
 	
 	// List of all registered template.
 	// XXX there should be a way for extensions to register a new
 	// or modify an existing rep.
 	let reps = [RegExp, StyleSheet, Event, DateTime, CommentNode, ElementNode, TextNode, Attribute, LongStringRep, Func, PromiseRep, ArrayRep, Document, Window, ObjectWithText, ObjectWithURL, ErrorRep, GripArray, GripMap, Grip, Undefined, Null, StringRep, Number, SymbolRep, InfinityRep, NaNRep];
 	
 	/**
 	 * Generic rep that is using for rendering native JS types or an object.
@@ -227,21 +225,30 @@ return /******/ (function(modules) { // 
 	    SymbolRep,
 	    TextNode,
 	    Undefined,
 	    Window
 	  }
 	};
 
 /***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+	module.exports = __WEBPACK_EXTERNAL_MODULE_3__;
+
+/***/ },
 /* 4 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
+	
+	// Utils
+	const nodeConstants = __webpack_require__(5);
 	
 	/**
 	 * Create React factories for given arguments.
 	 * Example:
 	 *   const { Rep } = createFactories(require("./rep"));
 	 */
 	function createFactories(args) {
 	  let result = {};
@@ -386,35 +393,153 @@ return /******/ (function(modules) { // 
 	        title: "This object could not be rendered, " + "please file a bug on bugzilla.mozilla.org"
 	      },
 	      /* Labels have to be hardcoded for reps, see Bug 1317038. */
 	      "Invalid object");
 	    }
 	  };
 	}
 	
+	/**
+	 * Get an array of all the items from the grip in parameter (including the grip itself)
+	 * which can be selected in the inspector.
+	 *
+	 * @param {Object} Grip
+	 * @return {Array} Flat array of the grips which can be selected in the inspector
+	 */
+	function getSelectableInInspectorGrips(grip) {
+	  let grips = new Set(getFlattenedGrips([grip]));
+	  return [...grips].filter(isGripSelectableInInspector);
+	}
+	
+	/**
+	 * Indicate if a Grip can be selected in the inspector,
+	 * i.e. if it represents a node element.
+	 *
+	 * @param {Object} Grip
+	 * @return {Boolean}
+	 */
+	function isGripSelectableInInspector(grip) {
+	  return grip && typeof grip === "object" && grip.preview && [nodeConstants.TEXT_NODE, nodeConstants.ELEMENT_NODE].includes(grip.preview.nodeType);
+	}
+	
+	/**
+	 * Get a flat array of all the grips and their preview items.
+	 *
+	 * @param {Array} Grips
+	 * @return {Array} Flat array of the grips and their preview items
+	 */
+	function getFlattenedGrips(grips) {
+	  return grips.reduce((res, grip) => {
+	    let previewItems = getGripPreviewItems(grip);
+	    let flatPreviewItems = previewItems.length > 0 ? getFlattenedGrips(previewItems) : [];
+	
+	    return [...res, grip, ...flatPreviewItems];
+	  }, []);
+	}
+	
+	/**
+	 * Get preview items from a Grip.
+	 *
+	 * @param {Object} Grip from which we want the preview items
+	 * @return {Array} Array of the preview items of the grip, or an empty array
+	 *                 if the grip does not have preview items
+	 */
+	function getGripPreviewItems(grip) {
+	  if (!grip) {
+	    return [];
+	  }
+	
+	  // Promise resolved value Grip
+	  if (grip.promiseState && grip.promiseState.value) {
+	    return [grip.promiseState.value];
+	  }
+	
+	  // Array Grip
+	  if (grip.preview && grip.preview.items) {
+	    return grip.preview.items;
+	  }
+	
+	  // Node Grip
+	  if (grip.preview && grip.preview.childNodes) {
+	    return grip.preview.childNodes;
+	  }
+	
+	  // Set or Map Grip
+	  if (grip.preview && grip.preview.entries) {
+	    return grip.preview.entries.reduce((res, entry) => res.concat(entry), []);
+	  }
+	
+	  // Event Grip
+	  if (grip.preview && grip.preview.target) {
+	    return [grip.preview.target];
+	  }
+	
+	  // Generic Grip
+	  if (grip.preview && grip.preview.ownProperties) {
+	    let propertiesValues = Object.values(grip.preview.ownProperties).map(property => property.value || property);
+	
+	    // ArrayBuffer Grip
+	    if (grip.preview.safeGetterValues) {
+	      propertiesValues = propertiesValues.concat(Object.values(grip.preview.safeGetterValues).map(property => property.getterValue || property));
+	    }
+	
+	    return propertiesValues;
+	  }
+	
+	  return [];
+	}
+	
 	module.exports = {
 	  createFactories,
 	  isGrip,
 	  cropString,
 	  sanitizeString,
 	  wrapRender,
 	  cropMultipleLines,
 	  parseURLParams,
 	  parseURLEncodedText,
 	  getFileName,
-	  getURLDisplayString
+	  getURLDisplayString,
+	  getSelectableInInspectorGrips
 	};
 
 /***/ },
 /* 5 */
+/***/ function(module, exports) {
+
+	module.exports = {
+	  ELEMENT_NODE: 1,
+	  ATTRIBUTE_NODE: 2,
+	  TEXT_NODE: 3,
+	  CDATA_SECTION_NODE: 4,
+	  ENTITY_REFERENCE_NODE: 5,
+	  ENTITY_NODE: 6,
+	  PROCESSING_INSTRUCTION_NODE: 7,
+	  COMMENT_NODE: 8,
+	  DOCUMENT_NODE: 9,
+	  DOCUMENT_TYPE_NODE: 10,
+	  DOCUMENT_FRAGMENT_NODE: 11,
+	  NOTATION_NODE: 12,
+	
+	  // DocumentPosition
+	  DOCUMENT_POSITION_DISCONNECTED: 0x01,
+	  DOCUMENT_POSITION_PRECEDING: 0x02,
+	  DOCUMENT_POSITION_FOLLOWING: 0x04,
+	  DOCUMENT_POSITION_CONTAINS: 0x08,
+	  DOCUMENT_POSITION_CONTAINED_BY: 0x10,
+	  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
+	};
+
+/***/ },
+/* 6 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders undefined value
@@ -438,21 +563,21 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: Undefined,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 6 */
+/* 7 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders null value
@@ -476,21 +601,21 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: Null,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 7 */
+/* 8 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const {
 	  cropString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
@@ -544,21 +669,21 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: StringRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 8 */
+/* 9 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  sanitizeString,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
 	// Shortcuts
 	const { span } = React.DOM;
 	
@@ -616,21 +741,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: LongStringRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 9 */
+/* 10 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a number
@@ -662,27 +787,27 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: Number,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 10 */
+/* 11 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  createFactories,
 	  wrapRender
 	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(11));
-	const { MODE } = __webpack_require__(2);
+	const Caption = React.createFactory(__webpack_require__(12));
+	const { MODE } = __webpack_require__(1);
 	
 	const ModePropType = React.PropTypes.oneOf(
 	// @TODO Change this to Object.values once it's supported in Node's version of V8
 	Object.keys(MODE).map(key => MODE[key]));
 	
 	// Shortcuts
 	const DOM = React.DOM;
 	
@@ -829,17 +954,17 @@ return /******/ (function(modules) { // 
 	
 	  propTypes: {
 	    object: React.PropTypes.any.isRequired,
 	    delim: React.PropTypes.string.isRequired,
 	    mode: ModePropType
 	  },
 	
 	  render: wrapRender(function () {
-	    const { Rep } = createFactories(__webpack_require__(3));
+	    const { Rep } = createFactories(__webpack_require__(2));
 	
 	    let object = this.props.object;
 	    let delim = this.props.delim;
 	    let mode = this.props.mode;
 	    return DOM.span({}, Rep({ object: object, mode: mode }), delim);
 	  })
 	}));
 	
@@ -849,21 +974,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: ArrayRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 11 */
+/* 12 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const DOM = React.DOM;
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	/**
 	 * Renders a caption. This template is used by other components
 	 * that needs to distinguish between a simple text/value and a label.
 	 */
@@ -878,27 +1003,27 @@ return /******/ (function(modules) { // 
 	    return DOM.span({ "className": "caption" }, this.props.object);
 	  })
 	});
 	
 	// Exports from this module
 	module.exports = Caption;
 
 /***/ },
-/* 12 */
+/* 13 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  wrapRender
 	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(11));
-	const PropRep = React.createFactory(__webpack_require__(13));
-	const { MODE } = __webpack_require__(2);
+	const Caption = React.createFactory(__webpack_require__(12));
+	const PropRep = React.createFactory(__webpack_require__(14));
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	/**
 	 * Renders an object. An object is represented by a list of its
 	 * properties enclosed in curly brackets.
 	 */
 	const Obj = React.createClass({
 	  displayName: "Obj",
@@ -1038,26 +1163,26 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: Obj,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 13 */
+/* 14 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  createFactories,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Property for Obj (local JS objects), Grip (remote JS objects)
 	 * and GripMap (remote JS maps and weakmaps) reps.
 	 * It's used to render object properties.
 	 */
@@ -1068,63 +1193,72 @@ return /******/ (function(modules) { // 
 	    // Property name.
 	    name: React.PropTypes.oneOfType([React.PropTypes.string, React.PropTypes.object]).isRequired,
 	    // Equal character rendered between property name and value.
 	    equal: React.PropTypes.string,
 	    // Delimiter character used to separate individual properties.
 	    delim: React.PropTypes.string,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-	    objectLink: React.PropTypes.func
+	    objectLink: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  render: wrapRender(function () {
-	    const Grip = __webpack_require__(14);
-	    let { Rep } = createFactories(__webpack_require__(3));
+	    const Grip = __webpack_require__(15);
+	    let { Rep } = createFactories(__webpack_require__(2));
+	    let {
+	      name,
+	      mode,
+	      equal,
+	      delim
+	    } = this.props;
 	
 	    let key;
 	    // The key can be a simple string, for plain objects,
 	    // or another object for maps and weakmaps.
 	    if (typeof this.props.name === "string") {
 	      key = span({ "className": "nodeName" }, this.props.name);
 	    } else {
-	      key = Rep({
-	        object: this.props.name,
-	        mode: this.props.mode || MODE.TINY,
-	        defaultRep: Grip,
-	        objectLink: this.props.objectLink
-	      });
+	      key = Rep(Object.assign({}, this.props, {
+	        object: name,
+	        mode: mode || MODE.TINY,
+	        defaultRep: Grip
+	      }));
 	    }
 	
 	    return span({}, key, span({
 	      "className": "objectEqual"
-	    }, this.props.equal), Rep(this.props), span({
+	    }, equal), Rep(Object.assign({}, this.props)), span({
 	      "className": "objectComma"
-	    }, this.props.delim));
+	    }, delim));
 	  })
 	});
 	
 	// Exports from this module
 	module.exports = PropRep;
 
 /***/ },
-/* 14 */
+/* 15 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	// Dependencies
 	const {
 	  createFactories,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(11));
-	const PropRep = React.createFactory(__webpack_require__(13));
-	const { MODE } = __webpack_require__(2);
+	const Caption = React.createFactory(__webpack_require__(12));
+	const PropRep = React.createFactory(__webpack_require__(14));
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders generic grip. Grip is client representation
 	 * of remote JS object and is used as an input object
 	 * for this rep component.
 	 */
@@ -1132,17 +1266,21 @@ return /******/ (function(modules) { // 
 	  displayName: "Grip",
 	
 	  propTypes: {
 	    object: React.PropTypes.object.isRequired,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    isInterestingProp: React.PropTypes.func,
 	    title: React.PropTypes.string,
-	    objectLink: React.PropTypes.func
+	    objectLink: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTitle: function (object) {
 	    let title = this.props.title || object.class || "Object";
 	    if (this.props.objectLink) {
 	      return this.props.objectLink({
 	        object: object
 	      }, title);
@@ -1157,17 +1295,17 @@ return /******/ (function(modules) { // 
 	    } catch (err) {
 	      console.error(err);
 	    }
 	    return [];
 	  },
 	
 	  propIterator: function (object, max) {
 	    if (object.preview && Object.keys(object.preview).includes("wrappedValue")) {
-	      const { Rep } = createFactories(__webpack_require__(3));
+	      const { Rep } = createFactories(__webpack_require__(2));
 	
 	      return [Rep({
 	        object: object.preview.wrappedValue,
 	        mode: this.props.mode || MODE.TINY,
 	        defaultRep: Grip
 	      })];
 	    }
 	
@@ -1229,17 +1367,19 @@ return /******/ (function(modules) { // 
 	      let value = this.getPropValue(properties[name]);
 	
 	      props.push(PropRep(Object.assign({}, this.props, {
 	        mode: MODE.TINY,
 	        name: name,
 	        object: value,
 	        equal: ": ",
 	        delim: i !== indexes.length - 1 || truncate ? ", " : "",
-	        defaultRep: Grip
+	        defaultRep: Grip,
+	        // Do not propagate title to properties reps
+	        title: undefined
 	      })));
 	    });
 	
 	    return props;
 	  },
 	
 	  /**
 	   * Get the indexes of props in the object.
@@ -1330,21 +1470,21 @@ return /******/ (function(modules) { // 
 	  rep: GripRep,
 	  supportsObject: supportsObject
 	};
 	
 	// Exports from this module
 	module.exports = Grip;
 
 /***/ },
-/* 15 */
+/* 16 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a symbol.
@@ -1370,21 +1510,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: SymbolRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 16 */
+/* 17 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a Infinity object
@@ -1407,21 +1547,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: InfinityRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 17 */
+/* 18 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	const { wrapRender } = __webpack_require__(4);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a NaN object
@@ -1440,29 +1580,29 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: NaNRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 18 */
+/* 19 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  createFactories,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const StringRep = __webpack_require__(7);
+	const StringRep = __webpack_require__(8);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	const { rep: StringRepFactory } = createFactories(StringRep);
 	
 	/**
 	 * Renders DOM attribute
 	 */
@@ -1498,21 +1638,21 @@ return /******/ (function(modules) { // 
 	}
 	
 	module.exports = {
 	  rep: Attribute,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 19 */
+/* 20 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
 	
 	// Shortcuts
@@ -1563,21 +1703,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: DateTime,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 20 */
+/* 21 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
@@ -1632,95 +1772,104 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: Document,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 21 */
+/* 22 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  createFactories,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { rep } = createFactories(__webpack_require__(14));
+	
+	const { rep } = createFactories(__webpack_require__(15));
+	const { MODE } = __webpack_require__(1);
 	
 	/**
 	 * Renders DOM event objects.
 	 */
 	let Event = React.createClass({
 	  displayName: "event",
 	
 	  propTypes: {
-	    object: React.PropTypes.object.isRequired
+	    object: React.PropTypes.object.isRequired,
+	    objectLink: React.PropTypes.func,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTitle: function (props) {
 	    let preview = props.object.preview;
 	    let title = preview.type;
 	
 	    if (preview.eventKind == "key" && preview.modifiers && preview.modifiers.length) {
 	      title = `${title} ${preview.modifiers.join("-")}`;
 	    }
 	    return title;
 	  },
 	
 	  render: wrapRender(function () {
 	    // Use `Object.assign` to keep `this.props` without changes because:
 	    // 1. JSON.stringify/JSON.parse is slow.
 	    // 2. Immutable.js is planned for the future.
-	    let props = Object.assign({
+	    let gripProps = Object.assign({}, this.props, {
 	      title: this.getTitle(this.props)
-	    }, this.props);
-	    props.object = Object.assign({}, this.props.object);
-	    props.object.preview = Object.assign({}, this.props.object.preview);
-	
-	    props.object.preview.ownProperties = {};
-	    if (props.object.preview.target) {
-	      Object.assign(props.object.preview.ownProperties, {
-	        target: props.object.preview.target
+	    });
+	    gripProps.object = Object.assign({}, this.props.object);
+	    gripProps.object.preview = Object.assign({}, this.props.object.preview);
+	
+	    gripProps.object.preview.ownProperties = {};
+	    if (gripProps.object.preview.target) {
+	      Object.assign(gripProps.object.preview.ownProperties, {
+	        target: gripProps.object.preview.target
 	      });
 	    }
-	    Object.assign(props.object.preview.ownProperties, props.object.preview.properties);
-	
-	    delete props.object.preview.properties;
-	    props.object.ownPropertyLength = Object.keys(props.object.preview.ownProperties).length;
-	
-	    switch (props.object.class) {
+	    Object.assign(gripProps.object.preview.ownProperties, gripProps.object.preview.properties);
+	
+	    delete gripProps.object.preview.properties;
+	    gripProps.object.ownPropertyLength = Object.keys(gripProps.object.preview.ownProperties).length;
+	
+	    switch (gripProps.object.class) {
 	      case "MouseEvent":
-	        props.isInterestingProp = (type, value, name) => {
+	        gripProps.isInterestingProp = (type, value, name) => {
 	          return ["target", "clientX", "clientY", "layerX", "layerY"].includes(name);
 	        };
 	        break;
 	      case "KeyboardEvent":
-	        props.isInterestingProp = (type, value, name) => {
+	        gripProps.isInterestingProp = (type, value, name) => {
 	          return ["target", "key", "charCode", "keyCode"].includes(name);
 	        };
 	        break;
 	      case "MessageEvent":
-	        props.isInterestingProp = (type, value, name) => {
+	        gripProps.isInterestingProp = (type, value, name) => {
 	          return ["target", "isTrusted", "data"].includes(name);
 	        };
 	        break;
 	      default:
-	        props.isInterestingProp = (type, value, name) => {
+	        gripProps.isInterestingProp = (type, value, name) => {
 	          // We want to show the properties in the order they are declared.
-	          return Object.keys(props.object.preview.ownProperties).includes(name);
+	          return Object.keys(gripProps.object.preview.ownProperties).includes(name);
 	        };
 	    }
 	
-	    return rep(props);
+	    return rep(gripProps);
 	  })
 	});
 	
 	// Registration
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
@@ -1731,21 +1880,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: Event,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 22 */
+/* 23 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  cropString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
@@ -1810,44 +1959,48 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: Func,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 23 */
+/* 24 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	// Dependencies
 	const {
 	  createFactories,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
 	
-	const PropRep = React.createFactory(__webpack_require__(13));
-	const { MODE } = __webpack_require__(2);
+	const PropRep = React.createFactory(__webpack_require__(14));
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders a DOM Promise object.
 	 */
 	const PromiseRep = React.createClass({
 	  displayName: "Promise",
 	
 	  propTypes: {
 	    object: React.PropTypes.object.isRequired,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
-	    objectLink: React.PropTypes.func
+	    objectLink: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTitle: function (object) {
 	    const title = object.class;
 	    if (this.props.objectLink) {
 	      return this.props.objectLink({
 	        object: object
 	      }, title);
@@ -1857,33 +2010,34 @@ return /******/ (function(modules) { // 
 	
 	  getProps: function (promiseState) {
 	    const keys = ["state"];
 	    if (Object.keys(promiseState).includes("value")) {
 	      keys.push("value");
 	    }
 	
 	    return keys.map((key, i) => {
+	      let object = promiseState[key];
 	      return PropRep(Object.assign({}, this.props, {
 	        mode: MODE.TINY,
 	        name: `<${key}>`,
-	        object: promiseState[key],
+	        object,
 	        equal: ": ",
 	        delim: i < keys.length - 1 ? ", " : ""
 	      }));
 	    });
 	  },
 	
 	  render: wrapRender(function () {
 	    const object = this.props.object;
 	    const { promiseState } = object;
 	    let objectLink = this.props.objectLink || span;
 	
 	    if (this.props.mode === MODE.TINY) {
-	      let { Rep } = createFactories(__webpack_require__(3));
+	      let { Rep } = createFactories(__webpack_require__(2));
 	
 	      return span({ className: "objectBox objectBox-object" }, this.getTitle(object), objectLink({
 	        className: "objectLeftBrace",
 	        object: object
 	      }, " { "), Rep({ object: promiseState.state }), objectLink({
 	        className: "objectRightBrace",
 	        object: object
 	      }, " }"));
@@ -1910,21 +2064,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: PromiseRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 24 */
+/* 25 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
 	
 	// Shortcuts
@@ -1968,21 +2122,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: RegExp,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 25 */
+/* 26 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
@@ -2036,29 +2190,29 @@ return /******/ (function(modules) { // 
 	// Exports from this module
 	
 	module.exports = {
 	  rep: StyleSheet,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 26 */
+/* 27 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  isGrip,
 	  cropString,
 	  cropMultipleLines,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
-	const nodeConstants = __webpack_require__(27);
+	const { MODE } = __webpack_require__(1);
+	const nodeConstants = __webpack_require__(5);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders DOM comment node.
 	 */
 	const CommentNode = React.createClass({
@@ -2097,72 +2251,48 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: CommentNode,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 27 */
-/***/ function(module, exports) {
-
-	module.exports = {
-	  ELEMENT_NODE: 1,
-	  ATTRIBUTE_NODE: 2,
-	  TEXT_NODE: 3,
-	  CDATA_SECTION_NODE: 4,
-	  ENTITY_REFERENCE_NODE: 5,
-	  ENTITY_NODE: 6,
-	  PROCESSING_INSTRUCTION_NODE: 7,
-	  COMMENT_NODE: 8,
-	  DOCUMENT_NODE: 9,
-	  DOCUMENT_TYPE_NODE: 10,
-	  DOCUMENT_FRAGMENT_NODE: 11,
-	  NOTATION_NODE: 12,
-	
-	  // DocumentPosition
-	  DOCUMENT_POSITION_DISCONNECTED: 0x01,
-	  DOCUMENT_POSITION_PRECEDING: 0x02,
-	  DOCUMENT_POSITION_FOLLOWING: 0x04,
-	  DOCUMENT_POSITION_CONTAINS: 0x08,
-	  DOCUMENT_POSITION_CONTAINED_BY: 0x10,
-	  DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC: 0x20
-	};
-
-/***/ },
 /* 28 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Utils
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
-	const nodeConstants = __webpack_require__(27);
+	const { MODE } = __webpack_require__(1);
+	const nodeConstants = __webpack_require__(5);
+	const Svg = __webpack_require__(29);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders DOM element node.
 	 */
 	const ElementNode = React.createClass({
 	  displayName: "ElementNode",
 	
 	  propTypes: {
 	    object: React.PropTypes.object.isRequired,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
 	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func,
 	    objectLink: React.PropTypes.func
 	  },
 	
 	  getElements: function (grip, mode) {
 	    let { attributes, nodeName } = grip.preview;
 	    const nodeNameElement = span({
 	      className: "tag-name theme-fg-color3"
 	    }, nodeName);
@@ -2197,36 +2327,53 @@ return /******/ (function(modules) { // 
 	
 	    return ["<", nodeNameElement, ...attributeElements, ">"];
 	  },
 	
 	  render: wrapRender(function () {
 	    let {
 	      object,
 	      mode,
+	      attachedActorIds,
 	      onDOMNodeMouseOver,
-	      onDOMNodeMouseOut
+	      onDOMNodeMouseOut,
+	      onInspectIconClick
 	    } = this.props;
 	    let elements = this.getElements(object, mode);
 	    let objectLink = this.props.objectLink || span;
 	
+	    let isInTree = attachedActorIds ? attachedActorIds.includes(object.actor) : true;
+	
 	    let baseConfig = { className: "objectBox objectBox-node" };
-	    if (onDOMNodeMouseOver) {
-	      Object.assign(baseConfig, {
-	        onMouseOver: _ => onDOMNodeMouseOver(object)
-	      });
+	    let inspectIcon;
+	    if (isInTree) {
+	      if (onDOMNodeMouseOver) {
+	        Object.assign(baseConfig, {
+	          onMouseOver: _ => onDOMNodeMouseOver(object)
+	        });
+	      }
+	
+	      if (onDOMNodeMouseOut) {
+	        Object.assign(baseConfig, {
+	          onMouseOut: onDOMNodeMouseOut
+	        });
+	      }
+	
+	      if (onInspectIconClick) {
+	        inspectIcon = Svg("open-inspector", {
+	          element: "a",
+	          draggable: false,
+	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
+	          title: "Click to select the node in the inspector",
+	          onClick: () => onInspectIconClick(object)
+	        });
+	      }
 	    }
 	
-	    if (onDOMNodeMouseOut) {
-	      Object.assign(baseConfig, {
-	        onMouseOut: onDOMNodeMouseOut
-	      });
-	    }
-	
-	    return objectLink({ object }, span(baseConfig, ...elements));
+	    return span(baseConfig, objectLink({ object }, ...elements), inspectIcon);
 	  })
 	});
 	
 	// Registration
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
 	  }
@@ -2238,43 +2385,231 @@ return /******/ (function(modules) { // 
 	  rep: ElementNode,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
 /* 29 */
 /***/ function(module, exports, __webpack_require__) {
 
+	const React = __webpack_require__(3);
+	const InlineSVG = __webpack_require__(30);
+	
+	const svg = {
+	  "open-inspector": __webpack_require__(31)
+	};
+	
+	module.exports = function (name, props) {
+	  // eslint-disable-line
+	  if (!svg[name]) {
+	    throw new Error("Unknown SVG: " + name);
+	  }
+	  let className = name;
+	  if (props && props.className) {
+	    className = `${name} ${props.className}`;
+	  }
+	  if (name === "subSettings") {
+	    className = "";
+	  }
+	  props = Object.assign({}, props, { className, src: svg[name] });
+	  return React.createElement(InlineSVG, props);
+	};
+
+/***/ },
+/* 30 */
+/***/ function(module, exports, __webpack_require__) {
+
+	'use strict';
+	
+	Object.defineProperty(exports, '__esModule', {
+	    value: true
+	});
+	
+	var _extends = Object.assign || function (target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i]; for (var key in source) { if (Object.prototype.hasOwnProperty.call(source, key)) { target[key] = source[key]; } } } return target; };
+	
+	var _createClass = (function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ('value' in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; })();
+	
+	var _get = function get(_x, _x2, _x3) { var _again = true; _function: while (_again) { var object = _x, property = _x2, receiver = _x3; _again = false; if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { _x = parent; _x2 = property; _x3 = receiver; _again = true; desc = parent = undefined; continue _function; } } else if ('value' in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } } };
+	
+	function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { 'default': obj }; }
+	
+	function _objectWithoutProperties(obj, keys) { var target = {}; for (var i in obj) { if (keys.indexOf(i) >= 0) continue; if (!Object.prototype.hasOwnProperty.call(obj, i)) continue; target[i] = obj[i]; } return target; }
+	
+	function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError('Cannot call a class as a function'); } }
+	
+	function _inherits(subClass, superClass) { if (typeof superClass !== 'function' && superClass !== null) { throw new TypeError('Super expression must either be null or a function, not ' + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }
+	
+	var _react = __webpack_require__(3);
+	
+	var _react2 = _interopRequireDefault(_react);
+	
+	var DOMParser = typeof window !== 'undefined' && window.DOMParser;
+	var process = process || {};
+	process.env = process.env || {};
+	var parserAvailable = typeof DOMParser !== 'undefined' && DOMParser.prototype != null && DOMParser.prototype.parseFromString != null;
+	
+	function isParsable(src) {
+	    // kinda naive but meh, ain't gonna use full-blown parser for this
+	    return parserAvailable && typeof src === 'string' && src.trim().substr(0, 4) === '<svg';
+	}
+	
+	// parse SVG string using `DOMParser`
+	function parseFromSVGString(src) {
+	    var parser = new DOMParser();
+	    return parser.parseFromString(src, "image/svg+xml");
+	}
+	
+	// Transform DOM prop/attr names applicable to `<svg>` element but react-limited
+	function switchSVGAttrToReactProp(propName) {
+	    switch (propName) {
+	        case 'class':
+	            return 'className';
+	        default:
+	            return propName;
+	    }
+	}
+	
+	var InlineSVG = (function (_React$Component) {
+	    _inherits(InlineSVG, _React$Component);
+	
+	    _createClass(InlineSVG, null, [{
+	        key: 'defaultProps',
+	        value: {
+	            element: 'i',
+	            raw: false,
+	            src: ''
+	        },
+	        enumerable: true
+	    }, {
+	        key: 'propTypes',
+	        value: {
+	            src: _react2['default'].PropTypes.string.isRequired,
+	            element: _react2['default'].PropTypes.string,
+	            raw: _react2['default'].PropTypes.bool
+	        },
+	        enumerable: true
+	    }]);
+	
+	    function InlineSVG(props) {
+	        _classCallCheck(this, InlineSVG);
+	
+	        _get(Object.getPrototypeOf(InlineSVG.prototype), 'constructor', this).call(this, props);
+	        this._extractSVGProps = this._extractSVGProps.bind(this);
+	    }
+	
+	    // Serialize `Attr` objects in `NamedNodeMap`
+	
+	    _createClass(InlineSVG, [{
+	        key: '_serializeAttrs',
+	        value: function _serializeAttrs(map) {
+	            var ret = {};
+	            var prop = undefined;
+	            for (var i = 0; i < map.length; i++) {
+	                prop = switchSVGAttrToReactProp(map[i].name);
+	                ret[prop] = map[i].value;
+	            }
+	            return ret;
+	        }
+	
+	        // get <svg /> element props
+	    }, {
+	        key: '_extractSVGProps',
+	        value: function _extractSVGProps(src) {
+	            var map = parseFromSVGString(src).documentElement.attributes;
+	            return map.length > 0 ? this._serializeAttrs(map) : null;
+	        }
+	
+	        // get content inside <svg> element.
+	    }, {
+	        key: '_stripSVG',
+	        value: function _stripSVG(src) {
+	            return parseFromSVGString(src).documentElement.innerHTML;
+	        }
+	    }, {
+	        key: 'componentWillReceiveProps',
+	        value: function componentWillReceiveProps(_ref) {
+	            var children = _ref.children;
+	
+	            if ("production" !== process.env.NODE_ENV && children != null) {
+	                console.info('<InlineSVG />: `children` prop will be ignored.');
+	            }
+	        }
+	    }, {
+	        key: 'render',
+	        value: function render() {
+	            var Element = undefined,
+	                __html = undefined,
+	                svgProps = undefined;
+	            var _props = this.props;
+	            var element = _props.element;
+	            var raw = _props.raw;
+	            var src = _props.src;
+	
+	            var otherProps = _objectWithoutProperties(_props, ['element', 'raw', 'src']);
+	
+	            if (raw === true && isParsable(src)) {
+	                Element = 'svg';
+	                svgProps = this._extractSVGProps(src);
+	                __html = this._stripSVG(src);
+	            }
+	            __html = __html || src;
+	            Element = Element || element;
+	            svgProps = svgProps || {};
+	
+	            return _react2['default'].createElement(Element, _extends({}, svgProps, otherProps, { src: null, children: null,
+	                dangerouslySetInnerHTML: { __html: __html } }));
+	        }
+	    }]);
+	
+	    return InlineSVG;
+	})(_react2['default'].Component);
+	
+	exports['default'] = InlineSVG;
+	module.exports = exports['default'];
+
+/***/ },
+/* 31 */
+/***/ function(module, exports) {
+
+	module.exports = "<!-- This Source Code Form is subject to the terms of the Mozilla Public - License, v. 2.0. If a copy of the MPL was not distributed with this - file, You can obtain one at http://mozilla.org/MPL/2.0/. --><svg , viewBox=\"0 0 16 16\" xmlns=\"http://www.w3.org/2000/svg\"><path d=\"M8,3L12,3L12,7L14,7L14,8L12,8L12,12L8,12L8,14L7,14L7,12L3,12L3,8L1,8L1,7L3,7L3,3L7,3L7,1L8,1L8,3ZM10,10L10,5L5,5L5,10L10,10Z\"></path></svg>"
+
+/***/ },
+/* 32 */
+/***/ function(module, exports, __webpack_require__) {
+
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  cropString,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
+	const { MODE } = __webpack_require__(1);
+	const Svg = __webpack_require__(29);
 	
 	// Shortcuts
 	const DOM = React.DOM;
 	
 	/**
 	 * Renders DOM #text node.
 	 */
 	let TextNode = React.createClass({
 	  displayName: "TextNode",
 	
 	  propTypes: {
 	    object: React.PropTypes.object.isRequired,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    objectLink: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
 	    onDOMNodeMouseOver: React.PropTypes.func,
-	    onDOMNodeMouseOut: React.PropTypes.func
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getTextContent: function (grip) {
 	    return cropString(grip.preview.textContent);
 	  },
 	
 	  getTitle: function (grip) {
 	    const title = "#text";
@@ -2284,37 +2619,56 @@ return /******/ (function(modules) { // 
 	      }, title);
 	    }
 	    return title;
 	  },
 	
 	  render: wrapRender(function () {
 	    let {
 	      object: grip,
-	      mode = MODE.SHORT
+	      mode = MODE.SHORT,
+	      attachedActorIds,
+	      onDOMNodeMouseOver,
+	      onDOMNodeMouseOut,
+	      onInspectIconClick
 	    } = this.props;
 	
 	    let baseConfig = { className: "objectBox objectBox-textNode" };
-	    if (this.props.onDOMNodeMouseOver) {
-	      Object.assign(baseConfig, {
-	        onMouseOver: _ => this.props.onDOMNodeMouseOver(grip)
-	      });
-	    }
-	
-	    if (this.props.onDOMNodeMouseOut) {
-	      Object.assign(baseConfig, {
-	        onMouseOut: this.props.onDOMNodeMouseOut
-	      });
+	    let inspectIcon;
+	    let isInTree = attachedActorIds ? attachedActorIds.includes(grip.actor) : true;
+	
+	    if (isInTree) {
+	      if (onDOMNodeMouseOver) {
+	        Object.assign(baseConfig, {
+	          onMouseOver: _ => onDOMNodeMouseOver(grip)
+	        });
+	      }
+	
+	      if (onDOMNodeMouseOut) {
+	        Object.assign(baseConfig, {
+	          onMouseOut: onDOMNodeMouseOut
+	        });
+	      }
+	
+	      if (onInspectIconClick) {
+	        inspectIcon = Svg("open-inspector", {
+	          element: "a",
+	          draggable: false,
+	          // TODO: Localize this with "openNodeInInspector" when Bug 1317038 lands
+	          title: "Click to select the node in the inspector",
+	          onClick: () => onInspectIconClick(grip)
+	        });
+	      }
 	    }
 	
 	    if (mode === MODE.TINY) {
-	      return DOM.span(baseConfig, this.getTitle(grip));
+	      return DOM.span(baseConfig, this.getTitle(grip), inspectIcon);
 	    }
 	
-	    return DOM.span(baseConfig, this.getTitle(grip), DOM.span({ className: "nodeValue" }, " ", `"${this.getTextContent(grip)}"`));
+	    return DOM.span(baseConfig, this.getTitle(grip), DOM.span({ className: "nodeValue" }, " ", `"${this.getTextContent(grip)}"`), inspectIcon);
 	  })
 	});
 	
 	// Registration
 	
 	function supportsObject(grip, type) {
 	  if (!isGrip(grip)) {
 	    return false;
@@ -2325,27 +2679,27 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: TextNode,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 30 */
+/* 33 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	// Utils
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const { MODE } = __webpack_require__(2);
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders Error objects.
 	 */
 	const ErrorRep = React.createClass({
 	  displayName: "Error",
@@ -2388,60 +2742,72 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: ErrorRep,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 31 */
+/* 34 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
+	const { MODE } = __webpack_require__(1);
+	
 	// Shortcuts
 	const DOM = React.DOM;
 	
 	/**
 	 * Renders a grip representing a window.
 	 */
 	let Window = React.createClass({
 	  displayName: "Window",
 	
 	  propTypes: {
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    object: React.PropTypes.object.isRequired,
 	    objectLink: React.PropTypes.func
 	  },
 	
-	  getTitle: function (grip) {
+	  getTitle: function (object) {
+	    let title = object.displayClass || object.class || "Window";
 	    if (this.props.objectLink) {
 	      return DOM.span({ className: "objectBox" }, this.props.objectLink({
-	        object: grip
-	      }, grip.class + " "));
+	        object
+	      }, title));
 	    }
-	    return "";
+	    return title;
 	  },
 	
-	  getLocation: function (grip) {
-	    return getURLDisplayString(grip.preview.url);
+	  getLocation: function (object) {
+	    return getURLDisplayString(object.preview.url);
 	  },
 	
 	  render: wrapRender(function () {
-	    let grip = this.props.object;
-	
-	    return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(grip), DOM.span({ className: "objectPropValue" }, this.getLocation(grip)));
+	    let {
+	      mode,
+	      object
+	    } = this.props;
+	
+	    if (mode === MODE.TINY) {
+	      return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(object));
+	    }
+	
+	    return DOM.span({ className: "objectBox objectBox-Window" }, this.getTitle(object), " ", DOM.span({ className: "objectPropValue" }, this.getLocation(object)));
 	  })
 	});
 	
 	// Registration
 	
 	function supportsObject(object, type) {
 	  if (!isGrip(object)) {
 	    return false;
@@ -2452,21 +2818,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: Window,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 32 */
+/* 35 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
 	
 	// Shortcuts
@@ -2518,21 +2884,21 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: ObjectWithText,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 33 */
+/* 36 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// ReactJS
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	
 	// Reps
 	const {
 	  isGrip,
 	  getURLDisplayString,
 	  wrapRender
 	} = __webpack_require__(4);
 	
@@ -2585,61 +2951,66 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: ObjectWithURL,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 34 */
+/* 37 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  createFactories,
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(11));
-	const { MODE } = __webpack_require__(2);
+	const Caption = React.createFactory(__webpack_require__(12));
+	const { MODE } = __webpack_require__(1);
 	
 	// Shortcuts
 	const { span } = React.DOM;
 	
 	/**
 	 * Renders an array. The array is enclosed by left and right bracket
 	 * and the max number of rendered items depends on the current mode.
 	 */
 	let GripArray = React.createClass({
 	  displayName: "GripArray",
 	
 	  propTypes: {
 	    object: React.PropTypes.object.isRequired,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    provider: React.PropTypes.object,
-	    objectLink: React.PropTypes.func
+	    objectLink: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  getLength: function (grip) {
 	    if (!grip.preview) {
 	      return 0;
 	    }
 	
 	    return grip.preview.length || grip.preview.childNodesLength || 0;
 	  },
 	
 	  getTitle: function (object, context) {
 	    let objectLink = this.props.objectLink || span;
 	    if (this.props.mode !== MODE.TINY) {
+	      let title = this.props.title || object.class || "Array";
 	      return objectLink({
 	        object: object
-	      }, object.class + " ");
+	      }, title, " ");
 	    }
 	    return "";
 	  },
 	
 	  getPreviewItems: function (grip) {
 	    if (!grip.preview) {
 	      return null;
 	    }
@@ -2670,22 +3041,26 @@ return /******/ (function(modules) { // 
 	      try {
 	        let itemGrip = previewItems[i];
 	        let value = provider ? provider.getValue(itemGrip) : itemGrip;
 	
 	        delim = i == delimMax ? "" : ", ";
 	
 	        items.push(GripArrayItem(Object.assign({}, this.props, {
 	          object: value,
-	          delim: delim
+	          delim: delim,
+	          // Do not propagate title to array items reps
+	          title: undefined
 	        })));
 	      } catch (exc) {
 	        items.push(GripArrayItem(Object.assign({}, this.props, {
 	          object: exc,
-	          delim: delim
+	          delim: delim,
+	          // Do not propagate title to array items reps
+	          title: undefined
 	        })));
 	      }
 	    }
 	    if (previewItems.length > max || gripLength > previewItems.length) {
 	      let objectLink = this.props.objectLink || span;
 	      let leftItemNum = gripLength - max > 0 ? gripLength - max : gripLength - previewItems.length;
 	      items.push(Caption({
 	        object: objectLink({
@@ -2739,21 +3114,30 @@ return /******/ (function(modules) { // 
 	/**
 	 * Renders array item. Individual values are separated by
 	 * a delimiter (a comma by default).
 	 */
 	let GripArrayItem = React.createFactory(React.createClass({
 	  displayName: "GripArrayItem",
 	
 	  propTypes: {
-	    delim: React.PropTypes.string
+	    delim: React.PropTypes.string,
+	    object: React.PropTypes.object.isRequired,
+	    objectLink: React.PropTypes.func,
+	    // @TODO Change this to Object.values once it's supported in Node's version of V8
+	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
+	    provider: React.PropTypes.object,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func
 	  },
 	
 	  render: function () {
-	    let { Rep } = createFactories(__webpack_require__(3));
+	    let { Rep } = createFactories(__webpack_require__(2));
 	
 	    return span({}, Rep(Object.assign({}, this.props, {
 	      mode: MODE.TINY
 	    })), this.props.delim);
 	  }
 	}));
 	
 	function supportsObject(grip, type) {
@@ -2766,47 +3150,52 @@ return /******/ (function(modules) { // 
 	
 	// Exports from this module
 	module.exports = {
 	  rep: GripArray,
 	  supportsObject: supportsObject
 	};
 
 /***/ },
-/* 35 */
+/* 38 */
 /***/ function(module, exports, __webpack_require__) {
 
 	// Dependencies
-	const React = __webpack_require__(1);
+	const React = __webpack_require__(3);
 	const {
 	  isGrip,
 	  wrapRender
 	} = __webpack_require__(4);
-	const Caption = React.createFactory(__webpack_require__(11));
-	const PropRep = React.createFactory(__webpack_require__(13));
-	const { MODE } = __webpack_require__(2);
+	const Caption = React.createFactory(__webpack_require__(12));
+	const PropRep = React.createFactory(__webpack_require__(14));
+	const { MODE } = __webpack_require__(1);
 	// Shortcuts
 	const { span } = React.DOM;
 	/**
 	 * Renders an map. A map is represented by a list of its
 	 * entries enclosed in curly brackets.
 	 */
 	const GripMap = React.createClass({
 	  displayName: "GripMap",
 	
 	  propTypes: {
 	    object: React.PropTypes.object,
 	    // @TODO Change this to Object.values once it's supported in Node's version of V8
 	    mode: React.PropTypes.oneOf(Object.keys(MODE).map(key => MODE[key])),
 	    objectLink: React.PropTypes.func,
-	    isInterestingEntry: React.PropTypes.func
+	    isInterestingEntry: React.PropTypes.func,
+	    attachedActorIds: React.PropTypes.array,
+	    onDOMNodeMouseOver: React.PropTypes.func,
+	    onDOMNodeMouseOut: React.PropTypes.func,
+	    onInspectIconClick: React.PropTypes.func,
+	    title: React.PropTypes.string
 	  },
 	
 	  getTitle: function (object) {
-	    let title = object && object.class ? object.class : "Map";
+	    let title = this.props.title || (object && object.class ? object.class : "Map");
 	    if (this.props.objectLink) {
 	      return this.props.objectLink({
 	        object: object
 	      }, title);
 	    }
 	    return title;
 	  },
 	
@@ -2855,16 +3244,24 @@ return /******/ (function(modules) { // 
 	  /**
 	   * Get entries ordered by index.
 	   *
 	   * @param {Array} entries Entries array.
 	   * @param {Array} indexes Indexes of entries.
 	   * @return {Array} Array of PropRep.
 	   */
 	  getEntries: function (entries, indexes) {
+	    let {
+	      objectLink,
+	      attachedActorIds,
+	      onDOMNodeMouseOver,
+	      onDOMNodeMouseOut,
+	      onInspectIconClick
+	    } = this.props;
+	
 	    // Make indexes ordered by ascending.
 	    indexes.sort(function (a, b) {
 	      return a - b;
 	    });
 	
 	    return indexes.map((index, i) => {
 	      let [key, entryValue] = entries[index];
 	      let value = entryValue.value !== undefined ? entryValue.value : entryValue;
@@ -2873,17 +3270,21 @@ return /******/ (function(modules) { // 
 	        // key,
 	        name: key,
 	        equal: ": ",
 	        object: value,
 	        // Do not add a trailing comma on the last entry
 	        // if there won't be a "more..." item.
 	        delim: i < indexes.length - 1 || indexes.length < entries.length ? ", " : "",
 	        mode: MODE.TINY,
-	        objectLink: this.props.objectLink
+	        objectLink,
+	        attachedActorIds,
+	        onDOMNodeMouseOver,
+	        onDOMNodeMouseOut,
+	        onInspectIconClick
 	      });
 	    });
 	  },
 	
 	  /**
 	   * Get the indexes of entries in the map.
 	   *
 	   * @param {Array} entries Entries array.
--- a/devtools/client/shared/components/reps/test/mochitest/head.js
+++ b/devtools/client/shared/components/reps/test/mochitest/head.js
@@ -1,13 +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/. */
-/* eslint no-unused-vars: [2, {"vars": "local"}] */
 
+/* Entirely disable no-unused-vars, because the second line here
+   doesn't seem to work with eslint 3.15.0 -- it doesn't suppress
+   other no-unused-vars errors.  */
+/* eslint-disable no-unused-vars */
+/* eslint no-unused-vars: ["error", {"vars": "local"}] */
+
+/* globals is */
+
+/* Not really a module.  */
+/* eslint-disable strict */
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { Assert } = require("resource://testing-common/Assert.jsm");
 var { gDevTools } = require("devtools/client/framework/devtools");
 var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
@@ -42,21 +51,25 @@ function shallowRenderComponent(componen
   return renderer.getRenderOutput();
 }
 
 /**
  * Test that a rep renders correctly across different modes.
  */
 function testRepRenderModes(modeTests, testName, componentUnderTest, gripStub,
   props = {}) {
-  modeTests.forEach(({mode, expectedOutput, message}) => {
+  modeTests.forEach(({mode, expectedOutput, message, title}) => {
     const modeString = typeof mode === "undefined" ? "no mode" : mode.toString();
     if (!message) {
       message = `${testName}: ${modeString} renders correctly.`;
     }
 
     const rendered = renderComponent(
       componentUnderTest.rep,
-      Object.assign({}, { object: gripStub, mode }, props)
+      Object.assign({}, { object: gripStub, mode, title }, props)
     );
     is(rendered.textContent, expectedOutput, message);
   });
 }
+
+function getStubAttachedActorIds(gripStubs) {
+  return gripStubs.map(gripStub => gripStub.actor);
+}
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_element-node.html
@@ -14,31 +14,36 @@ Test Element node rep
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, ElementNode } = REPS;
 
   try {
     yield testBodyNode();
     yield testDocumentElement();
     yield testNode();
     yield testNodeWithLeadingAndTrailingSpacesClassName();
     yield testNodeWithoutAttributes();
     yield testLotsOfAttributes();
     yield testSvgNode();
     yield testSvgNodeInXHTML();
 
     yield testOnMouseOver();
     yield testOnMouseOut();
+    yield testOnInspectIconClick();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBodyNode() {
     const stub = getGripStub("testBodyNode");
@@ -171,47 +176,102 @@ window.onload = Task.async(function* () 
     const tinyRenderedComponent = renderComponent(
       ElementNode.rep, { object: stub, mode: MODE.TINY });
     is(tinyRenderedComponent.textContent, `svg:circle.svg-element`,
       "Element node rep has expected text content for XHTML SVG element in tiny mode");
   }
 
   function testOnMouseOver() {
     const stub = getGripStub("testNode");
+  debugger;
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
-    const renderedComponent = renderComponent(
-      ElementNode.rep, {object: stub, onDOMNodeMouseOver});
+    const renderedComponent = renderComponent(ElementNode.rep, {
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
+    });
 
-    const node = renderedComponent.querySelector(".objectBox-node");
-    TestUtils.Simulate.mouseOver(node);
+    TestUtils.Simulate.mouseOver(renderedComponent);
 
-    is(mouseOverValue, stub, "onDOMNodeMouseOver is called with the expected argument " +
-      "when mouseover is fired on the Rep");
+    is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called " +
+      "with the expected argument when mouseover is fired on the Rep");
   }
 
   function testOnMouseOut() {
     const stub = getGripStub("testNode");
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
-    const renderedComponent = renderComponent(
-      ElementNode.rep, {object: stub, onDOMNodeMouseOut});
+    const renderedComponent = renderComponent(ElementNode.rep, {
+      object: stub,
+      onDOMNodeMouseOut,
+      attachedActorIds,
+    });
 
-    const node = renderedComponent.querySelector(".objectBox-node");
-    TestUtils.Simulate.mouseOut(node);
+    TestUtils.Simulate.mouseOut(renderedComponent);
 
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
 
+  function testOnInspectIconClick() {
+    const stub = getGripStub("testNode");
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
+
+    const renderedComponentWithoutInspectIcon = renderComponent(ElementNode.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"]
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when actor is not in attachedActorIds");
+
+    let renderedComponent = renderComponent(ElementNode.rep, {
+      object: stub,
+      onInspectIconClick,
+    });
+    let inspectIconNode = renderedComponent.querySelector(".open-inspector");
+    ok(inspectIconNode !== null,
+      "There is an inspect icon when attachedActorIds is not specified");
+
+    renderedComponent = renderComponent(ElementNode.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds,
+    });
+
+    inspectIconNode = renderedComponent.querySelector(".open-inspector");
+    ok(inspectIconNode !== null, "There is an inspect icon as expected");
+    TestUtils.Simulate.click(inspectIconNode);
+
+    is(inspectIconClickedValue, grips[0],
+      "onInspectIconClick is called with expected value when inspect icon is clicked");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testBodyNode":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj30",
           "class": "HTMLBodyElement",
           "ownPropertyLength": 0,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_event.html
@@ -12,45 +12,50 @@ Test Event rep
   <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">
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, Event } = REPS;
 
   try {
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: getGripStub("testEvent") });
     is(renderedRep.type, Event.rep, `Rep correctly selects ${Event.rep.displayName}`);
 
     yield testEvent();
     yield testMouseEvent();
     yield testKeyboardEvent();
     yield testKeyboardEventWithModifiers();
     yield testMessageEvent();
 
-    yield testOnMouseOver();
-    yield testOnMouseOut();
-  } catch(e) {
+    yield testOnDomNodeMouseOver();
+    yield testOnDomNodeMouseOut();
+    yield testOnDomNodeInspectIconClick();
+  } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testEvent() {
     const renderedComponent = renderComponent(Event.rep, {
       object: getGripStub("testEvent"),
     });
     is(renderedComponent.textContent,
-      "beforeprint { target: http://example.com, isTrusted: true, " +
-      "currentTarget: http://example.com, 8 more… }",
+      "beforeprint { target: Window, isTrusted: true, " +
+      "currentTarget: Window, 8 more… }",
       "Event rep has expected text content for an event");
   }
 
   function testMouseEvent() {
     const renderedComponent = renderComponent(Event.rep, {
       object: getGripStub("testMouseEvent")
     });
     is(renderedComponent.textContent,
@@ -96,67 +101,120 @@ window.onload = Task.async(function* () 
        "in long mode");
   }
 
   function testMessageEvent() {
     const renderedComponent = renderComponent(Event.rep, {
       object: getGripStub("testMessageEvent")
     });
     is(renderedComponent.textContent,
-       `message { target: http://example.com, isTrusted: false, data: "test data", ` +
+       `message { target: Window, isTrusted: false, data: "test data", ` +
        "8 more… }",
        "Event rep has expected text content for a message event");
 
     const longRenderedComponent = renderComponent(Event.rep, {
       object: getGripStub("testMessageEvent"),
       mode: MODE.LONG,
     });
     is(longRenderedComponent.textContent,
-       `message { target: http://example.com, isTrusted: false, data: "test data", ` +
-       `origin: "null", lastEventId: "", source: , ports: message, currentTarget: , ` +
-       `eventPhase: 2, bubbles: false, 1 more… }`,
+       `message { target: Window, isTrusted: false, data: "test data", ` +
+       `origin: "null", lastEventId: "", source: Window, ports: Array, ` +
+       `currentTarget: Window, eventPhase: 2, bubbles: false, 1 more… }`,
        "Event rep has expected text content for a message event in long mode");
   }
 
-  function testOnMouseOver() {
+  function testOnDomNodeMouseOver() {
     const stub = getGripStub("testMouseEvent");
+    const grips = getSelectableInInspectorGrips(stub);
+
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
     const renderedComponent = renderComponent(Event.rep, {
-      object: stub, onDOMNodeMouseOver
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOver(node);
 
-    is(mouseOverValue, stub.preview.target, "onDOMNodeMouseOver is called with " +
+    is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called with " +
       "the expected argument when mouseover is fired on the Rep");
   }
 
-  function testOnMouseOut() {
+  function testOnDomNodeMouseOut() {
     const stub = getGripStub("testMouseEvent");
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
     const renderedComponent = renderComponent(Event.rep, {
       object: stub,
-      onDOMNodeMouseOut
+      onDOMNodeMouseOut,
+      attachedActorIds
     });
 
     const node = renderedComponent.querySelector(".objectBox-node");
     TestUtils.Simulate.mouseOut(node);
 
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
 
+  function testOnDomNodeInspectIconClick() {
+    const stub = getGripStub("testMouseEvent");
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
+
+    let renderedComponentWithoutInspectIcon = renderComponent(Event.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"]
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
+
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when attachedActorIds does not have keys " +
+      "matching grip event's target item");
+
+    const renderedComponent = renderComponent(Event.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds
+    });
+
+    const icon = renderedComponent.querySelector(".open-inspector");
+    ok(icon !== null, "There is an icon as expected when passing a matching " +
+      "attachedActorIds item");
+
+    TestUtils.Simulate.click(icon);
+
+    is(inspectIconClickedValue, grips[0],
+      "onInspectIconClick is called with the expected argument " +
+      "when the inspect icon is clicked");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testEvent":
         return {
           "type": "object",
           "class": "Event",
           "actor": "server1.conn23.obj35",
           "extensible": true,
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-array.html
@@ -12,17 +12,21 @@ Test GripArray rep
   <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">
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, GripArray } = REPS;
 
   let componentUnderTest = GripArray;
   const maxLength = {
     short: 3,
     long: 10
   };
 
@@ -34,18 +38,19 @@ window.onload = Task.async(function* () 
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
     yield testPreviewLimit();
     yield testNamedNodeMap();
     yield testNodeList();
     yield testDocumentFragment();
 
-    yield testOnMouseOver();
-    yield testOnMouseOut();
+    yield testOnDomNodeMouseOver();
+    yield testOnDomNodeMouseOut();
+    yield testOnDomNodeInspectIconClick();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test array: `[]`
@@ -98,16 +103,23 @@ window.onload = Task.async(function* () 
       },
       {
         mode: MODE.SHORT,
         expectedOutput: defaultOutput,
       },
       {
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
+      },
+      {
+        // Check the custom title with nested objects to make sure nested objects are not
+        // displayed with their parent's title.
+        mode: MODE.LONG,
+        title: "CustomTitle",
+        expectedOutput: `CustomTitle [ 1, "foo", Object ]`,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanShortMaxProps() {
     // Test array = `["test string"…] //4 items`
@@ -303,51 +315,108 @@ window.onload = Task.async(function* () 
         mode: MODE.LONG,
         expectedOutput: longOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
-
+  function testOnDomNodeMouseOver() {
+    const stub = getGripStub("testNodeList");
+    const grips = getSelectableInInspectorGrips(stub);
 
-  function testOnMouseOver() {
-    const stub = getGripStub("testNodeList");
+    is(grips.length, 3, "the stub has three node grips");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
 
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
-    const renderedComponent = renderComponent(
-      GripArray.rep, {object: stub, onDOMNodeMouseOver});
+    const renderedComponent = renderComponent(GripArray.rep, {
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
+    });
 
     const nodes = renderedComponent.querySelectorAll(".objectBox-node");
+    is(nodes.length, 3, "There are three node elements");
     nodes.forEach((node, index) => {
       TestUtils.Simulate.mouseOver(node);
 
-      is(mouseOverValue, stub.preview.items[index], "onDOMNodeMouseOver is called with " +
-        "the expected argument when mouseover is fired on the Rep");
+      is(mouseOverValue, grips[index],
+        "onDOMNodeMouseOver is called with the expected argument " +
+        "when mouseover is fired on the Rep");
     });
   }
 
-  function testOnMouseOut() {
+  function testOnDomNodeMouseOut() {
     const stub = getGripStub("testNodeList");
+    const grips = getSelectableInInspectorGrips(stub);
+
+    is(grips.length, 3, "the stub has three node grips");
 
-    let called = false;
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let called = 0;
     let onDOMNodeMouseOut = (object) => {
-      called = true;
+      called++;
     };
-    const renderedComponent = renderComponent(
-      GripArray.rep, {object: stub, onDOMNodeMouseOut});
+    const renderedComponent = renderComponent(GripArray.rep, {
+      object: stub,
+      onDOMNodeMouseOut,
+      attachedActorIds,
+    });
+
+    const nodes = renderedComponent.querySelectorAll(".objectBox-node");
+    info("Simulating mouseout on each node");
+    Array.from(nodes).forEach(node => TestUtils.Simulate.mouseOut(node));
+
+    is(called, 3, "onDOMNodeMouseOut is called when mouseout is fired on each NodeRep");
+  }
+
+  function testOnDomNodeInspectIconClick() {
+    const stub = getGripStub("testNodeList");
+    const grips = getSelectableInInspectorGrips(stub);
+
+    is(grips.length, 3, "the stub has three node grips");
+
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
 
-    const node = renderedComponent.querySelector(".objectBox-node");
-    TestUtils.Simulate.mouseOut(node);
+    let renderedComponentWithoutInspectIcon = renderComponent(GripArray.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"],
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
 
-    is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
+    const renderedComponent = renderComponent(GripArray.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds,
+    });
+
+    const icons = renderedComponent.querySelectorAll(".open-inspector");
+    is(icons.length, grips.length,
+      "There is an icon for each grip array item with a matching attachedNodeFront");
+
+    icons.forEach((icon, index) => {
+      TestUtils.Simulate.click(icon);
+
+      is(inspectIconClickedValue, grips[index],
+        "onInspectIconClick is called with the expected argument " +
+        "when the inspect icon is clicked");
+    });
   }
 
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Array",
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip-map.html
@@ -14,30 +14,38 @@ Test GripMap rep
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, GripMap } = REPS;
 
   const componentUnderTest = GripMap;
 
   try {
     yield testEmptyMap();
     yield testSymbolKeyedMap();
     yield testWeakMap();
 
     // // Test entries iterator
     yield testMaxEntries();
     yield testMoreThanMaxEntries();
     yield testUninterestingEntries();
+
+    yield testOnDomNodeMouseOver();
+    yield testOnDomNodeMouseOut();
+    yield testOnDomNodeInspectIconClick();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testEmptyMap() {
     // Test object: `new Map()`
@@ -125,16 +133,23 @@ window.onload = Task.async(function* () 
       },
       {
         mode: MODE.SHORT,
         expectedOutput: defaultOutput,
       },
       {
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
+      },
+      {
+        // Check the custom title with nested objects to make sure nested objects are not
+        // displayed with their parent's title.
+        mode: MODE.LONG,
+        title: "CustomTitle",
+        expectedOutput: `CustomTitle { Object: "value-a" }`,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMaxEntries() {
     // Test object:
@@ -226,16 +241,170 @@ window.onload = Task.async(function* () 
         mode: MODE.LONG,
         expectedOutput: longOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testOnDomNodeMouseOver() {
+    const nodeValuedStub = getGripStub("testNodeValuedMap");
+    const nodeKeyedStub = getGripStub("testNodeKeyedMap");
+
+    const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
+    is(valuesGrips.length, 3, "the stub has three node grips");
+    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
+
+    const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
+    is(keysGrips.length, 3, "the stub has three node grips");
+    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
+
+    let mouseOverValue;
+    let onDOMNodeMouseOver = (object) => {
+      mouseOverValue = object;
+    };
+
+    info("Testing onDOMNodeMouseOver on node valued Map");
+    const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeValuedStub,
+      onDOMNodeMouseOver,
+      attachedActorIds: valuesattachedActorIds,
+    });
+
+    let nodes = nodeValuedRenderedComponent.querySelectorAll(".objectBox-node");
+    nodes.forEach((node, index) => {
+      TestUtils.Simulate.mouseOver(node);
+      is(mouseOverValue, valuesGrips[index],
+        "onDOMNodeMouseOver is called with the expected argument " +
+        "when mouseover is fired on the Rep");
+    });
+
+    info("Testing onDOMNodeMouseOver on node keyed Map");
+    const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeKeyedStub,
+      onDOMNodeMouseOver,
+      attachedActorIds: keysAttachedActorIds,
+    });
+
+    nodes = nodeKeyedRenderedComponent.querySelectorAll(".objectBox-node");
+    nodes.forEach((node, index) => {
+      TestUtils.Simulate.mouseOver(node);
+      is(mouseOverValue, keysGrips[index],
+        "onDOMNodeMouseOver is called with the expected argument " +
+        "when mouseover is fired on the Rep");
+    });
+  }
+
+  function testOnDomNodeMouseOut() {
+    const nodeValuedStub = getGripStub("testNodeValuedMap");
+    const nodeKeyedStub = getGripStub("testNodeKeyedMap");
+
+    const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
+    is(valuesGrips.length, 3, "the stub has three node grips");
+    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
+
+    const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
+    is(keysGrips.length, 3, "the stub has three node grips");
+    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
+
+    let called = 0;
+    let onDOMNodeMouseOut = (object) => {
+      called++;
+    };
+
+    info("Testing onDOMNodeMouseOut on node valued Map");
+    const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeValuedStub,
+      onDOMNodeMouseOut,
+      attachedActorIds: valuesattachedActorIds,
+    });
+
+    let nodes = nodeValuedRenderedComponent.querySelectorAll(".objectBox-node");
+    info("Simulating mouseout on each value node");
+    nodes.forEach((node, index) => TestUtils.Simulate.mouseOut(node));
+    is(called, 3,
+      "onDOMNodeMouseOut is called when mouseout is fired on each value NodeRep");
+
+    info("Testing onDOMNodeMouseOut on node keyed Map");
+    const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeKeyedStub,
+      onDOMNodeMouseOut,
+      attachedActorIds: keysAttachedActorIds,
+    });
+
+    nodes = nodeKeyedRenderedComponent.querySelectorAll(".objectBox-node");
+    // Resets counter
+    called = 0;
+    info("Simulating mouseout on each key node");
+    nodes.forEach((node, index) => TestUtils.Simulate.mouseOut(node));
+    is(called, 3,
+      "onDOMNodeMouseOut is called when mouseout is fired on each key NodeRep");
+  }
+
+  function testOnDomNodeInspectIconClick() {
+    const nodeValuedStub = getGripStub("testNodeValuedMap");
+    const nodeKeyedStub = getGripStub("testNodeKeyedMap");
+
+    const valuesGrips = getSelectableInInspectorGrips(nodeValuedStub);
+    is(valuesGrips.length, 3, "the stub has three node grips");
+    const valuesattachedActorIds = getStubAttachedActorIds(valuesGrips);
+
+    const keysGrips = getSelectableInInspectorGrips(nodeKeyedStub);
+    is(keysGrips.length, 3, "the stub has three node grips");
+    const keysAttachedActorIds = getStubAttachedActorIds(keysGrips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
+
+    const renderedComponentWithoutInspectIcon = renderComponent(GripMap.rep, {
+      object: nodeValuedStub,
+      onInspectIconClick,
+      attachedActorIds: [],
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
+
+    info("Testing onInspectIconClick on node valued Map");
+    const nodeValuedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeValuedStub,
+      onInspectIconClick,
+      attachedActorIds: valuesattachedActorIds,
+    });
+
+    let icons = nodeValuedRenderedComponent.querySelectorAll(".open-inspector");
+    is(icons.length, valuesGrips.length,
+      "There is an icon for each map value with a matching attachedNodeFront");
+
+    icons.forEach((icon, index) => {
+      TestUtils.Simulate.click(icon);
+      is(inspectIconClickedValue, valuesGrips[index], "onInspectIconClick is called " +
+        "with the expected argument when the inspect icon is clicked");
+    });
+
+    info("Testing onInspectIconClick on node keyed Map");
+    const nodeKeyedRenderedComponent = renderComponent(GripMap.rep, {
+      object: nodeKeyedStub,
+      onInspectIconClick,
+      attachedActorIds: keysAttachedActorIds,
+    });
+
+    icons = nodeKeyedRenderedComponent.querySelectorAll(".open-inspector");
+    is(icons.length, keysGrips.length,
+      "There is an icon for each map key with a matching attachedNodeFront");
+
+    icons.forEach((icon, index) => {
+      TestUtils.Simulate.click(icon);
+      is(inspectIconClickedValue, keysGrips[index], "onInspectIconClick is called " +
+        "with the expected argument when the inspect icon is clicked");
+    });
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testEmptyMap":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj97",
           "class": "Map",
           "extensible": true,
@@ -391,15 +560,181 @@ window.onload = Task.async(function* () 
               ],
               [
                 "key-d",
                 4
               ]
             ]
           }
         };
+
+      case "testNodeValuedMap":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj213",
+          "class": "Map",
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "MapLike",
+            "size": 3,
+            "entries": [
+              [
+                "item-0",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj214",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-1",
+                      "class": "btn btn-log",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ],
+              [
+                "item-1",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj215",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-2",
+                      "class": "btn btn-err",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ],
+              [
+                "item-2",
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj216",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-3",
+                      "class": "btn btn-count",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              ]
+            ]
+          }
+        };
+
+      case "testNodeKeyedMap":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj223",
+          "class": "WeakMap",
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "MapLike",
+            "size": 3,
+            "entries": [
+              [
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj224",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-1",
+                      "class": "btn btn-log",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                },
+                "item-0"
+              ],
+              [
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj225",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-3",
+                      "class": "btn btn-count",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                },
+                "item-2"
+              ],
+              [
+                {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj226",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-2",
+                      "class": "btn btn-err",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                },
+                "item-1"
+              ]
+            ]
+          }
+        };
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_grip.html
@@ -12,17 +12,21 @@ Test grip rep
   <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">
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, Grip } = REPS;
 
   const componentUnderTest = Grip;
 
   try {
     yield testBasic();
     yield testBooleanObject();
     yield testNumberObject();
@@ -39,16 +43,20 @@ window.onload = Task.async(function* () 
     yield testNonEnumerableProps();
 
     // Test that properties are rendered as expected by PropRep
     yield testNestedObject();
     yield testNestedArray();
 
     // Test that 'more' property doesn't clobber the caption.
     yield testMoreProp();
+
+    yield testOnDomNodeMouseOver();
+    yield testOnDomNodeMouseOut();
+    yield testOnDomNodeInspectIconClick();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test object: `{}`
@@ -447,16 +455,23 @@ window.onload = Task.async(function* () 
       },
       {
         mode: MODE.SHORT,
         expectedOutput: defaultOutput,
       },
       {
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
+      },
+      {
+        // Check the custom title with nested objects to make sure nested objects are not
+        // displayed with their parent's title.
+        mode: MODE.LONG,
+        title: "CustomTitle",
+        expectedOutput: `CustomTitle { objProp: Object, strProp: "test string" }`,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testNestedArray() {
     // Test object: `{arrProp: ["foo", "bar", "baz"]}`
@@ -510,16 +525,114 @@ window.onload = Task.async(function* () 
         mode: MODE.LONG,
         expectedOutput: longOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testOnDomNodeMouseOver() {
+    const stub = getGripStub("testObjectWithNodes");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 2, "the stub has two node grips");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let mouseOverValue;
+    let called = 0;
+    let onDOMNodeMouseOver = (object) => {
+      mouseOverValue = object;
+      called++;
+    };
+
+    const renderedComponent = renderComponent(Grip.rep, {
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
+    });
+
+    const nodes = renderedComponent.querySelectorAll(".objectBox-node");
+    nodes.forEach((node, index) => {
+      TestUtils.Simulate.mouseOver(node);
+      is(mouseOverValue, grips[index],
+        "onDOMNodeMouseOver is called with the expected argument " +
+        "when mouseover is fired on the Rep");
+    });
+    is(called, 2,
+      "onDOMNodeMouseOver is called when mouseOverValue is fired on each NodeRep");
+  }
+
+  function testOnDomNodeMouseOut() {
+    const stub = getGripStub("testObjectWithNodes");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 2, "the stub has two node grips");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let called = 0;
+    let onDOMNodeMouseOut = (object) => {
+      called++;
+    };
+
+    const renderedComponent = renderComponent(Grip.rep, {
+      object: stub,
+      onDOMNodeMouseOut,
+      attachedActorIds,
+    });
+
+    const nodes = renderedComponent.querySelectorAll(".objectBox-node");
+    info("Simulating mouseout on each node");
+    Array.from(nodes).forEach(node => TestUtils.Simulate.mouseOut(node));
+
+    is(called, 2, "onDOMNodeMouseOut is called when mouseout is fired on each NodeRep");
+  }
+
+  function testOnDomNodeInspectIconClick() {
+    const stub = getGripStub("testObjectWithNodes");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 2, "the stub has two node grips");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
+
+    let renderedComponentWithoutInspectIcon = renderComponent(Grip.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"],
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
+
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when attachedActorIds does not have keys " +
+      "matching grip properties");
+
+    const renderedComponent = renderComponent(Grip.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds,
+    });
+
+    const icons = renderedComponent.querySelectorAll(".open-inspector");
+    is(icons.length, 2,
+      "There is an icon for each grip property matching an attachedNodeFront");
+
+    icons.forEach((icon, index) => {
+      TestUtils.Simulate.click(icon);
+      is(inspectIconClickedValue, grips[index],
+        "onInspectIconClick is called with the expected argument " +
+        "when the inspect icon is clicked");
+    });
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Object",
           "actor": "server1.conn0.obj304",
           "extensible": true,
@@ -1012,15 +1125,79 @@ window.onload = Task.async(function* () 
                 },
                 "getterPrototypeLevel": 1,
                 "enumerable": true,
                 "writable": true
               }
             }
           }
         };
+      case "testObjectWithNodes":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj214",
+          "class": "Object",
+          "ownPropertyLength": 2,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {
+              "foo": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj215",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-1",
+                      "class": "btn btn-log",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              },
+              "bar": {
+                "configurable": true,
+                "enumerable": true,
+                "writable": true,
+                "value": {
+                  "type": "object",
+                  "actor": "server1.conn1.child1/obj216",
+                  "class": "HTMLButtonElement",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 1,
+                    "nodeName": "button",
+                    "attributes": {
+                      "id": "btn-2",
+                      "class": "btn btn-err",
+                      "type": "button"
+                    },
+                    "attributesLength": 3
+                  }
+                }
+              }
+            },
+            "ownPropertiesLength": 2,
+            "safeGetterValues": {}
+          }
+        };
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_promise.html
@@ -14,27 +14,35 @@ Test Promise rep
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, PromiseRep } = REPS;
 
   const componentUnderTest = PromiseRep;
 
   try {
     yield testPending();
     yield testFulfilledWithNumber();
     yield testFulfilledWithString();
     yield testFulfilledWithObject();
     yield testFulfilledWithArray();
+
+    yield testOnDomNodeMouseOver();
+    yield testOnDomNodeMouseOut();
+    yield testOnDomNodeInspectIconClick();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testPending() {
     // Test object = `new Promise((resolve, reject) => true)`
@@ -203,16 +211,100 @@ window.onload = Task.async(function* () 
         mode: MODE.LONG,
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, "testFulfilledWithArray", componentUnderTest, stub);
   }
 
+  function testOnDomNodeMouseOver() {
+    const stub = getGripStub("testFulfilledWithNode");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let mouseOverValue;
+    let onDOMNodeMouseOver = (object) => {
+      mouseOverValue = object;
+    };
+
+    const renderedComponent = renderComponent(PromiseRep.rep, {
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
+    });
+
+    const node = renderedComponent.querySelector(".objectBox-node");
+    TestUtils.Simulate.mouseOver(node);
+
+    is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called with " +
+      "the expected argument when mouseover is fired on the node element");
+  }
+
+  function testOnDomNodeMouseOut() {
+    const stub = getGripStub("testFulfilledWithNode");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let called = false;
+    let onDOMNodeMouseOut = (object) => {
+      called = true;
+    };
+    const renderedComponent = renderComponent(PromiseRep.rep, {
+      object: stub,
+      onDOMNodeMouseOut,
+      attachedActorIds,
+    });
+
+    const node = renderedComponent.querySelector(".objectBox-node");
+    TestUtils.Simulate.mouseOut(node);
+
+    is(called, true,
+      "onDOMNodeMouseOut is called when mouseout is fired on the node element");
+  }
+
+  function testOnDomNodeInspectIconClick() {
+    const stub = getGripStub("testFulfilledWithNode");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValues = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValues = object;
+    };
+
+    let renderedComponentWithoutInspectIcon = renderComponent(PromiseRep.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"],
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
+
+    const renderedComponent = renderComponent(PromiseRep.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds,
+    });
+
+    const icon = renderedComponent.querySelector(".open-inspector");
+    ok(icon !== null, "There is an inspect icon as expected");
+
+    TestUtils.Simulate.click(icon);
+    is(inspectIconClickedValues, grips[0],
+      "onInspectIconClick is called with the expected argument " +
+      "when the inspect icon is clicked");
+  }
+
   function getGripStub(name) {
     switch (name) {
       case "testPending":
         return {
           "type": "object",
           "actor": "server1.conn1.child1/obj54",
           "class": "Promise",
           "promiseState": {
@@ -318,16 +410,54 @@ window.onload = Task.async(function* () 
           "ownPropertyLength": 0,
           "preview": {
             "kind": "Object",
             "ownProperties": {},
             "ownPropertiesLength": 0,
             "safeGetterValues": {}
           }
         };
+      case "testFulfilledWithNode":
+        return {
+          "type": "object",
+          "actor": "server1.conn1.child1/obj217",
+          "class": "Promise",
+          "promiseState": {
+            "state": "fulfilled",
+            "value": {
+              "type": "object",
+              "actor": "server1.conn1.child1/obj218",
+              "class": "HTMLButtonElement",
+              "extensible": true,
+              "frozen": false,
+              "sealed": false,
+              "ownPropertyLength": 0,
+              "preview": {
+                "kind": "DOMNode",
+                "nodeType": 1,
+                "nodeName": "button",
+                "attributes": {
+                  "id": "btn-1",
+                  "class": "btn btn-log",
+                  "type": "button"
+                },
+                "attributesLength": 3
+              }
+            },
+            "creationTimestamp": 1480423091620.3716,
+            "timeToSettle": 0.02842400000372436
+          },
+          "ownPropertyLength": 0,
+          "preview": {
+            "kind": "Object",
+            "ownProperties": {},
+            "ownPropertiesLength": 0,
+            "safeGetterValues": {}
+          }
+        };
     }
     return null;
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_text-node.html
@@ -14,31 +14,41 @@ Test text-node rep
 </head>
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 "use strict";
 
 window.onload = Task.async(function* () {
-  const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
+  const {
+    REPS,
+    MODE,
+    getSelectableInInspectorGrips,
+  } = browserRequire("devtools/client/shared/components/reps/load-reps");
   let { Rep, TextNode } = REPS;
 
   let gripStubs = new Map();
   gripStubs.set("testRendering", {
     "class": "Text",
     "actor": "server1.conn1.child1/obj50",
     "preview": {
+      "kind": "DOMNode",
+      "nodeType": 3,
+      "nodeName": "#text",
       "textContent": "hello world"
     }
   });
   gripStubs.set("testRenderingWithEOL", {
     "class": "Text",
     "actor": "server1.conn1.child1/obj50",
     "preview": {
+      "kind": "DOMNode",
+      "nodeType": 3,
+      "nodeName": "#text",
       "textContent": "hello\nworld"
     }
   });
 
   try {
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, {
       object: gripStubs.get("testRendering")
@@ -47,16 +57,17 @@ window.onload = Task.async(function* () 
     is(renderedRep.type, TextNode.rep,
       `Rep correctly selects ${TextNode.rep.displayName}`);
 
     yield testRendering();
     yield testRenderingWithEOL();
 
     yield testOnMouseOver();
     yield testOnMouseOut();
+    yield testOnInspectIconClick();
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testRendering() {
     const stub = gripStubs.get("testRendering");
@@ -108,38 +119,86 @@ window.onload = Task.async(function* () 
     ];
 
     testRepRenderModes(modeTests, "testRenderingWithEOL", TextNode, stub);
   }
 
   function testOnMouseOver() {
     const stub = gripStubs.get("testRendering");
 
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one text node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
     let mouseOverValue;
     let onDOMNodeMouseOver = (object) => {
       mouseOverValue = object;
     };
-    const renderedComponent = renderComponent(
-      TextNode.rep, {object: stub, onDOMNodeMouseOver});
+    const renderedComponent = renderComponent(TextNode.rep, {
+      object: stub,
+      onDOMNodeMouseOver,
+      attachedActorIds,
+    });
 
     TestUtils.Simulate.mouseOver(renderedComponent);
-    is(mouseOverValue, stub, "onDOMNodeMouseOver is called with the expected argument " +
-      "when mouseover is fired on the Rep");
+    is(mouseOverValue, grips[0], "onDOMNodeMouseOver is called " +
+      "with the expected argument when mouseover is fired on the Rep");
   }
 
   function testOnMouseOut() {
     const stub = gripStubs.get("testRendering");
 
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one text node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
     let called = false;
     let onDOMNodeMouseOut = (object) => {
       called = true;
     };
-    const renderedComponent = renderComponent(
-      TextNode.rep, {object: stub, onDOMNodeMouseOut});
+    const renderedComponent = renderComponent(TextNode.rep, {
+      object: stub,
+      onDOMNodeMouseOut,
+      attachedActorIds,
+    });
 
     TestUtils.Simulate.mouseOut(renderedComponent);
     is(called, true, "onDOMNodeMouseOut is called when mouseout is fired on the Rep");
   }
+
+  function testOnInspectIconClick() {
+    const stub = gripStubs.get("testRendering");
+
+    const grips = getSelectableInInspectorGrips(stub);
+    is(grips.length, 1, "the stub has one text node grip");
+    const attachedActorIds = getStubAttachedActorIds(grips);
+
+    let inspectIconClickedValue = null;
+    let onInspectIconClick = (object) => {
+      inspectIconClickedValue = object;
+    };
+
+    const renderedComponentWithoutInspectIcon = renderComponent(TextNode.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds: ["someOtherId"]
+    });
+    is(renderedComponentWithoutInspectIcon.querySelector(".open-inspector"), null,
+      "There isn't an inspect icon when the actor is not in attachedActorIds");
+
+    const renderedComponent = renderComponent(TextNode.rep, {
+      object: stub,
+      onInspectIconClick,
+      attachedActorIds,
+    });
+
+    const inspectIconNode = renderedComponent.querySelector(".open-inspector");
+    ok(inspectIconNode !== null, "There is an inspect icon as expected");
+    TestUtils.Simulate.click(inspectIconNode);
+
+    is(inspectIconClickedValue, grips[0],
+      "onInspectIconClick is called with expected value when inspect icon is clicked");
+  }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
+++ b/devtools/client/shared/components/reps/test/mochitest/test_reps_window.html
@@ -16,17 +16,17 @@ Test window rep
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
 
-    const { REPS } = browserRequire("devtools/client/shared/components/reps/load-reps");
+    const { REPS, MODE } = browserRequire("devtools/client/shared/components/reps/load-reps");
     let { Rep, Window } = REPS;
 
     let gripStub = {
       "type": "object",
       "class": "Window",
       "actor": "server1.conn3.obj198",
       "extensible": true,
       "frozen": false,
@@ -40,18 +40,48 @@ window.onload = Task.async(function* () 
 
     // Test that correct rep is chosen
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, Window.rep, `Rep correctly selects ${Window.rep.displayName}`);
 
     // Test rendering
     const renderedComponent = renderComponent(Window.rep, { object: gripStub });
     ok(renderedComponent.className.includes("objectBox-Window"), "Window rep has expected class name");
+    is(renderedComponent.textContent, "Window about:newtab", "Window rep has expected text content");
     const innerNode = renderedComponent.querySelector(".objectPropValue");
     is(innerNode.textContent, "about:newtab", "Window rep has expected inner HTML structure and text content");
+
+    const tinyRenderedComponent = renderComponent(Window.rep, {
+      object: gripStub,
+      mode: MODE.TINY,
+    });
+    is(tinyRenderedComponent.textContent, "Window",
+       "Window rep has expected text content in TINY mode");
+
+    const longRenderedComponent = renderComponent(Window.rep, {
+      object: gripStub,
+      mode: MODE.LONG,
+    });
+    is(longRenderedComponent.textContent, "Window about:newtab",
+       "Window rep has expected text content in LONG mode");
+
+    const displayClassRenderedComponent = renderComponent(Window.rep, {
+      object: Object.assign({}, gripStub, {displayClass : "Custom"}),
+      mode: MODE.TINY,
+    });
+    is(displayClassRenderedComponent.textContent, "Custom",
+       "Window rep has expected text content in TINY mode with Custom display class");
+
+    const displayClassLongRenderedComponent = renderComponent(Window.rep, {
+      object: Object.assign({}, gripStub, {displayClass : "Custom"}),
+      mode: MODE.LONG,
+      title: "Custom"
+    });
+    is(displayClassLongRenderedComponent.textContent, "Custom about:newtab",
+       "Window rep has expected text content in LONG mode with Custom display class");
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
--- a/devtools/client/themes/common.css
+++ b/devtools/client/themes/common.css
@@ -330,17 +330,17 @@ checkbox:-moz-focusring {
   padding: 0 5px;
 }
 
 .devtools-toolbarbutton:not([label]):hover,
 .devtools-button:empty:not(:disabled):hover {
   background: var(--toolbarbutton-background);
 }
 
-.devtools-button:not(:empty):not(:disabled):hover,
+.devtools-button:not(:empty):not(:disabled):not(.checked):hover,
 .devtools-toolbarbutton[label]:not(:-moz-any([checked=true],[disabled])):hover,
 .devtools-button:empty:not(:disabled):-moz-any(:hover:active,.checked),
 .devtools-toolbarbutton:not([label]):-moz-any([checked],[open],:hover:active) {
   background: var(--toolbarbutton-hover-background);
   border-color: var(--toolbarbutton-hover-border-color);
 }
 
 .devtools-button:not(:empty):not(.checked):not(:disabled):hover:active,
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -118,23 +118,25 @@
   padding-right: 5px;
 }
 
 .ruleview-propertyvaluecontainer a {
   cursor: pointer;
 }
 
 .ruleview-computedlist,
+.ruleview-overridden-items[hidden],
 .ruleview-overridden-rule-filter[hidden],
 .ruleview-warning[hidden] {
   display: none;
 }
 
 .ruleview-computedlist[user-open],
-.ruleview-computedlist[filter-open] {
+.ruleview-computedlist[filter-open],
+.ruleview-overridden-items {
   display: block;
 }
 
 .ruleview-rule-source {
   text-align: end;
   float: right;
   max-width: 100%;
 
@@ -357,16 +359,52 @@
   list-style: none;
   padding: 0;
 }
 
 .ruleview-computed {
   margin-inline-start: 35px;
 }
 
+.ruleview-overridden-items {
+  margin: 0px 0px 0px 5px;
+  list-style: none;
+  line-height: 1.5em;
+}
+
+.ruleview-overridden-item {
+  position: relative;
+}
+
+.ruleview-overridden-item::before {
+  position: absolute;
+  left: -15px;
+  top: 0px;
+  content: '';
+  display: block;
+  border-left: 1px solid var(--theme-highlight-gray);
+  height: 0.7em;
+  border-bottom: 1px solid var(--theme-highlight-gray);
+  width: 10px;
+}
+
+.ruleview-overridden-item::after {
+  position: absolute;
+  left: -15px;
+  bottom: -7px;
+  content: '';
+  display: block;
+  border-left: 1px solid var(--theme-highlight-gray);
+  height: 100%;
+}
+
+.ruleview-overridden-item:last-child:after {
+  display: none;
+}
+
 .ruleview-grid,
 .ruleview-swatch {
   cursor: pointer;
   border-radius: 50%;
   width: 1em;
   height: 1em;
   vertical-align: middle;
   /* align the swatch with its value */
--- a/devtools/client/webconsole/net/components/net-info-body.js
+++ b/devtools/client/webconsole/net/components/net-info-body.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 React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/load-reps");
+const { createFactories } = require("devtools/client/shared/components/reps/reps");
 const { Tabs, TabPanel } = createFactories(require("devtools/client/shared/components/tabs/tabs"));
 
 // Network
 const HeadersTab = React.createFactory(require("./headers-tab"));
 const ResponseTab = React.createFactory(require("./response-tab"));
 const ParamsTab = React.createFactory(require("./params-tab"));
 const CookiesTab = React.createFactory(require("./cookies-tab"));
 const PostTab = React.createFactory(require("./post-tab"));
--- a/devtools/client/webconsole/net/components/post-tab.js
+++ b/devtools/client/webconsole/net/components/post-tab.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/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
 
-const { REPS, MODE, parseURLEncodedText } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE, parseURLEncodedText } = require("devtools/client/shared/components/reps/reps");
 const Rep = React.createFactory(REPS.Rep);
 
 // Network
 const NetInfoParams = React.createFactory(require("./net-info-params"));
 const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
 const Spinner = React.createFactory(require("./spinner"));
 const SizeLimit = React.createFactory(require("./size-limit"));
 const NetUtils = require("../utils/net");
--- a/devtools/client/webconsole/net/components/response-tab.js
+++ b/devtools/client/webconsole/net/components/response-tab.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/. */
 "use strict";
 
 const React = require("devtools/client/shared/vendor/react");
 
 // Reps
 const TreeView = React.createFactory(require("devtools/client/shared/components/tree/tree-view"));
-const { REPS, MODE } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const Rep = React.createFactory(REPS.Rep);
 
 // Network
 const SizeLimit = React.createFactory(require("./size-limit"));
 const NetInfoGroupList = React.createFactory(require("./net-info-group-list"));
 const Spinner = React.createFactory(require("./spinner"));
 const Json = require("../utils/json");
 const NetUtils = require("../utils/net");
--- a/devtools/client/webconsole/net/net-request.js
+++ b/devtools/client/webconsole/net/net-request.js
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 // React
 const React = require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 
 // Reps
-const { parseURLParams } = require("devtools/client/shared/components/reps/load-reps");
+const { parseURLParams } = require("devtools/client/shared/components/reps/reps");
 
 // Network
 const { cancelEvent, isLeftClick } = require("./utils/events");
 const NetInfoBody = React.createFactory(require("./components/net-info-body"));
 const DataProvider = require("./data-provider");
 
 // Constants
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
--- a/devtools/client/webconsole/new-console-output/components/console-table.js
+++ b/devtools/client/webconsole/new-console-output/components/console-table.js
@@ -7,17 +7,17 @@ const {
   createClass,
   createFactory,
   DOM: dom,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 const { ObjectClient } = require("devtools/shared/client/main");
 const actions = require("devtools/client/webconsole/new-console-output/actions/messages");
 const { l10n } = require("devtools/client/webconsole/new-console-output/utils/messages");
-const { MODE } = require("devtools/client/shared/components/reps/load-reps");
+const { MODE } = require("devtools/client/shared/components/reps/reps");
 const GripMessageBody = createFactory(require("devtools/client/webconsole/new-console-output/components/grip-message-body"));
 
 const TABLE_ROW_MAX_ITEMS = 1000;
 const TABLE_COLUMN_MAX_ITEMS = 10;
 
 const ConsoleTable = createClass({
 
   displayName: "ConsoleTable",
--- a/devtools/client/webconsole/new-console-output/components/grip-message-body.js
+++ b/devtools/client/webconsole/new-console-output/components/grip-message-body.js
@@ -15,17 +15,17 @@ if (typeof define === "undefined") {
 // React
 const {
   createFactory,
   PropTypes
 } = require("devtools/client/shared/vendor/react");
 
 const VariablesViewLink = createFactory(require("devtools/client/webconsole/new-console-output/components/variables-view-link"));
 
-const { REPS, MODE, createFactories } = require("devtools/client/shared/components/reps/load-reps");
+const { REPS, MODE, createFactories } = require("devtools/client/shared/components/reps/reps");
 const Rep = createFactory(REPS.Rep);
 const Grip = REPS.Grip;
 const StringRep = createFactories(REPS.StringRep).rep;
 
 GripMessageBody.displayName = "GripMessageBody";
 
 GripMessageBody.propTypes = {
   grip: PropTypes.oneOfType([
--- a/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/console-api-call.test.js
@@ -19,34 +19,32 @@ const {
   MESSAGE_CLOSE,
 } = require("devtools/client/webconsole/new-console-output/constants");
 const { INDENT_WIDTH } = require("devtools/client/webconsole/new-console-output/components/message-indent");
 
 // Test fakes.
 const { stubPreparedMessages } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index");
 const serviceContainer = require("devtools/client/webconsole/new-console-output/test/fixtures/serviceContainer");
 
-const tempfilePath = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js";
-
 describe("ConsoleAPICall component:", () => {
   describe("console.log", () => {
     it("renders string grips", () => {
       const message = stubPreparedMessages.get("console.log('foobar', 'test')");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       expect(wrapper.find(".message-body").text()).toBe("foobar test");
       expect(wrapper.find(".objectBox-string").length).toBe(2);
       let selector = "div.message.cm-s-mozilla span span.message-flex-body " +
         "span.message-body.devtools-monospace";
       expect(wrapper.find(selector).length).toBe(1);
 
       // There should be the location
       const locationLink = wrapper.find(`.message-location`);
       expect(locationLink.length).toBe(1);
-      expect(locationLink.text()).toBe("test-tempfile.js:1:27");
+      expect(locationLink.text()).toBe("test-console-api.html:1:27");
     });
 
     it("renders string grips with custom style", () => {
       const message = stubPreparedMessages.get("console.log(%cfoobar)");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer }));
 
       const elements = wrapper.find(".objectBox-string");
       expect(elements.text()).toBe("foobar");
@@ -142,22 +140,24 @@ describe("ConsoleAPICall component:", ()
       expect(wrapper.find(".message-body").text()).toMatch(/^bar: \d+(\.\d+)?ms$/);
     });
   });
 
   describe("console.trace", () => {
     it("renders", () => {
       const message = stubPreparedMessages.get("console.trace()");
       const wrapper = render(ConsoleApiCall({ message, serviceContainer, open: true }));
-      const filepath = `${tempfilePath}`;
+      const filepath = "http://example.com/browser/devtools/client/webconsole/" +
+                       "new-console-output/test/fixtures/stub-generators/" +
+                       "test-console-api.html";
 
       expect(wrapper.find(".message-body").text()).toBe("console.trace()");
 
       const frameLinks = wrapper.find(
-        `.stack-trace span.frame-link[data-url='${filepath}']`);
+        `.stack-trace span.frame-link[data-url]`);
       expect(frameLinks.length).toBe(3);
 
       expect(frameLinks.eq(0).find(".frame-link-function-display-name").text())
         .toBe("testStacktraceFiltering");
       expect(frameLinks.eq(0).find(".frame-link-filename").text())
         .toBe(filepath);
 
       expect(frameLinks.eq(1).find(".frame-link-function-display-name").text())
--- a/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
+++ b/devtools/client/webconsole/new-console-output/test/components/page-error.test.js
@@ -39,17 +39,17 @@ describe("PageError component:", () => {
     // The stacktrace should be closed by default.
     const frameLinks = wrapper.find(`.stack-trace`);
     expect(frameLinks.length).toBe(0);
 
     // There should be the location.
     const locationLink = wrapper.find(`.message-location`);
     expect(locationLink.length).toBe(1);
     // @TODO Will likely change. See bug 1307952
-    expect(locationLink.text()).toBe("test-tempfile.js:3:5");
+    expect(locationLink.text()).toBe("test-console-api.html:3:5");
   });
 
   it("displays a [Learn more] link", () => {
     const store = setupStore([]);
 
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     serviceContainer.openLink = sinon.spy();
@@ -71,19 +71,19 @@ describe("PageError component:", () => {
 
   it("has a stacktrace which can be openned", () => {
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
     const wrapper = render(PageError({ message, serviceContainer, open: true }));
 
     // There should be a collapse button.
     expect(wrapper.find(".theme-twisty.open").length).toBe(1);
 
-    // There should be three stacktrace items.
+    // There should be five stacktrace items.
     const frameLinks = wrapper.find(`.stack-trace span.frame-link`);
-    expect(frameLinks.length).toBe(3);
+    expect(frameLinks.length).toBe(5);
   });
 
   it("toggle the stacktrace when the collapse button is clicked", () => {
     const store = setupStore([]);
     store.dispatch = sinon.spy();
     const message = stubPreparedMessages.get("ReferenceError: asdf is not defined");
 
     let wrapper = mount(Provider({store},
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser.ini
@@ -1,20 +1,24 @@
 [DEFAULT]
 tags = devtools
 subsuite = devtools
 support-files =
   head.js
   !/devtools/client/framework/test/shared-head.js
+  ../stubs/*
   test-console-api.html
   test-css-message.html
   test-network-event.html
-  test-tempfile.css
-  test-tempfile.js
 
+[browser_webconsole_check_stubs_console_api.js]
+[browser_webconsole_check_stubs_css_message.js]
+[browser_webconsole_check_stubs_evaluation_result.js]
+[browser_webconsole_check_stubs_network_event.js]
+[browser_webconsole_check_stubs_page_error.js]
 [browser_webconsole_update_stubs_console_api.js]
 skip-if=true # This is only used to update stubs. It is not an actual test.
 [browser_webconsole_update_stubs_css_message.js]
 skip-if=true # This is only used to update stubs. It is not an actual test.
 [browser_webconsole_update_stubs_evaluation_result.js]
 skip-if=true # This is only used to update stubs. It is not an actual test.
 [browser_webconsole_update_stubs_network_event.js]
 skip-if=true # This is only used to update stubs. It is not an actual test.
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_check_stubs_console_api.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm", {});
+
+add_task(function* () {
+  let generatedStubs = yield generateConsoleApiStubs();
+
+  let repoStubFilePath = getTestFilePath("../stubs/consoleApi.js");
+  let repoStubFileContent = yield OS.File.read(repoStubFilePath, { encoding: "utf-8" });
+
+  if (generatedStubs != repoStubFileContent) {
+    ok(false, "The consoleApi stubs file needs to be updated by running " +
+      "`mach test devtools/client/webconsole/new-console-output/test/fixtures/" +
+      "stub-generators/browser_webconsole_update_stubs_console_api.js`");
+  } else {
+    ok(true, "The consoleApi stubs file is up to date");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_check_stubs_css_message.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm", {});
+
+add_task(function* () {
+  let generatedStubs = yield generateCssMessageStubs();
+
+  let repoStubFilePath = getTestFilePath("../stubs/cssMessage.js");
+  let repoStubFileContent = yield OS.File.read(repoStubFilePath, { encoding: "utf-8" });
+
+  if (generatedStubs != repoStubFileContent) {
+    ok(false, "The cssMessage stubs file needs to be updated by running " +
+      "`mach test devtools/client/webconsole/new-console-output/test/fixtures/" +
+      "stub-generators/browser_webconsole_update_stubs_css_message.js`");
+  } else {
+    ok(true, "The cssMessage stubs file is up to date");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_check_stubs_evaluation_result.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm", {});
+
+add_task(function* () {
+  let generatedStubs = yield generateEvaluationResultStubs();
+
+  let repoStubFilePath = getTestFilePath("../stubs/evaluationResult.js");
+  let repoStubFileContent = yield OS.File.read(repoStubFilePath, { encoding: "utf-8" });
+
+  if (generatedStubs != repoStubFileContent) {
+    ok(false, "The evaluationResult stubs file needs to be updated by running " +
+      "`mach test devtools/client/webconsole/new-console-output/test/fixtures/" +
+      "stub-generators/browser_webconsole_update_stubs_evaluation_result.js`");
+  } else {
+    ok(true, "The evaluationResult stubs file is up to date");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_check_stubs_network_event.js
@@ -0,0 +1,23 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm", {});
+
+add_task(function* () {
+  let generatedStubs = yield generateNetworkEventStubs();
+
+  let repoStubFilePath = getTestFilePath("../stubs/networkEvent.js");
+  let repoStubFileContent = yield OS.File.read(repoStubFilePath, { encoding: "utf-8" });
+
+  if (generatedStubs != repoStubFileContent) {
+    ok(false, "The networkEvent stubs file needs to be updated by running " +
+      "`mach test devtools/client/webconsole/new-console-output/test/fixtures/" +
+      "stub-generators/browser_webconsole_update_stubs_network_event.js`");
+  } else {
+    ok(true, "The networkEvent stubs file is up to date");
+  }
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_check_stubs_page_error.js
@@ -0,0 +1,29 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+Cu.import("resource://gre/modules/osfile.jsm", {});
+
+add_task(function* () {
+  // On e10s, the exception is triggered in child process
+  // and is ignored by test harness
+  if (!Services.appinfo.browserTabsRemoteAutostart) {
+    expectUncaughtException();
+  }
+
+  let generatedStubs = yield generatePageErrorStubs();
+
+  let repoStubFilePath = getTestFilePath("../stubs/pageError.js");
+  let repoStubFileContent = yield OS.File.read(repoStubFilePath, { encoding: "utf-8" });
+
+  if (generatedStubs != repoStubFileContent) {
+    ok(false, "The pageError stubs file needs to be updated by running " +
+      "`mach test devtools/client/webconsole/new-console-output/test/fixtures/" +
+      "stub-generators/browser_webconsole_update_stubs_page_error.js`");
+  } else {
+    ok(true, "The pageError stubs file is up to date");
+  }
+});
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_console_api.js
@@ -1,58 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
-requestLongerTimeout(2);
 
 Cu.import("resource://gre/modules/osfile.jsm", {});
-const { consoleApi: snippets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
-
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
-
-let stubs = {
-  preparedMessages: [],
-  packets: [],
-};
 
 add_task(function* () {
-  for (let [key, {keys, code}] of snippets) {
-    yield OS.File.writeAtomic(TEMP_FILE_PATH, `function triggerPacket() {${code}}`);
-
-    let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
-    let {ui} = toolbox.getCurrentPanel().hud;
-
-    ok(ui.jsterm, "jsterm exists");
-    ok(ui.newConsoleOutput, "newConsoleOutput exists");
-
-    let received = new Promise(resolve => {
-      let i = 0;
-      let listener = (type, res) => {
-        stubs.packets.push(formatPacket(keys[i], res));
-        stubs.preparedMessages.push(formatStub(keys[i], res));
-        if (++i === keys.length) {
-          toolbox.target.client.removeListener("consoleAPICall", listener);
-          resolve();
-        }
-      };
-      toolbox.target.client.addListener("consoleAPICall", listener);
-    });
-
-    yield ContentTask.spawn(gBrowser.selectedBrowser, key, function (subKey) {
-      let script = content.document.createElement("script");
-      script.src = "test-tempfile.js?key=" + encodeURIComponent(subKey);
-      script.onload = function () {
-        content.wrappedJSObject.triggerPacket();
-      };
-      content.document.body.appendChild(script);
-    });
-
-    yield received;
-
-    yield closeTabAndToolbox();
-  }
+  let fileContent = yield generateConsoleApiStubs();
   let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "consoleApi.js");
-  OS.File.writeAtomic(filePath, formatFile(stubs, "ConsoleMessage"));
-  OS.File.writeAtomic(TEMP_FILE_PATH, "");
+  yield OS.File.writeAtomic(filePath, fileContent);
+  ok(true, "Make the test not fail");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_css_message.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_css_message.js
@@ -1,48 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/osfile.jsm", {});
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-css-message.html";
-
-const { cssMessage: snippets} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
-
-let stubs = {
-  preparedMessages: [],
-  packets: [],
-};
 
 add_task(function* () {
-  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
-  ok(true, "make the test not fail");
-
-  for (let [key, code] of snippets) {
-    OS.File.writeAtomic(TEMP_CSS_FILE_PATH, code);
-    let received = new Promise(resolve => {
-      /* CSS errors are considered as pageError on the server */
-      toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
-        toolbox.target.client.removeListener("pageError", onPacket);
-        info("Received css message:" + e + " " + JSON.stringify(packet, null, "\t"));
-
-        let message = prepareMessage(packet, {getNextId: () => 1});
-        stubs.packets.push(formatPacket(message.messageText, packet));
-        stubs.preparedMessages.push(formatStub(message.messageText, packet));
-        resolve();
-      });
-    });
-
-    yield ContentTask.spawn(gBrowser.selectedBrowser, key, function (snippetKey) {
-      let stylesheet = content.document.createElement("link");
-      stylesheet.rel = "stylesheet";
-      stylesheet.href = "test-tempfile.css?key=" + encodeURIComponent(snippetKey);
-      content.document.body.appendChild(stylesheet);
-    });
-
-    yield received;
-  }
-
+  let fileContent = yield generateCssMessageStubs();
   let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "cssMessage.js");
-  OS.File.writeAtomic(filePath, formatFile(stubs, "ConsoleMessage"));
-  OS.File.writeAtomic(TEMP_CSS_FILE_PATH, "");
+  yield OS.File.writeAtomic(filePath, fileContent);
+  ok(true, "Make the test not fail");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_evaluation_result.js
@@ -1,32 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/osfile.jsm", {});
-const TEST_URI = "data:text/html;charset=utf-8,stub generation";
-
-const { evaluationResult: snippets} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
-
-let stubs = {
-  preparedMessages: [],
-  packets: [],
-};
 
 add_task(function* () {
-  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
-  ok(true, "make the test not fail");
-
-  for (let [code, key] of snippets) {
-    const packet = yield new Promise(resolve => {
-      toolbox.target.activeConsole.evaluateJS(code, resolve);
-    });
-    stubs.packets.push(formatPacket(key, packet));
-    stubs.preparedMessages.push(formatStub(key, packet));
-  }
-
+  let fileContent = yield generateEvaluationResultStubs();
   let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "evaluationResult.js");
-  OS.File.writeAtomic(filePath, formatFile(stubs, "ConsoleMessage"));
+  yield OS.File.writeAtomic(filePath, fileContent);
+  ok(true, "Make the test not fail");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_network_event.js
@@ -1,59 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/osfile.jsm", {});
-const TARGET = "networkEvent";
-const { [TARGET]: snippets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html";
-
-let stubs = {
-  preparedMessages: [],
-  packets: [],
-};
 
 add_task(function* () {
-  for (let {keys, code} of snippets.values()) {
-    OS.File.writeAtomic(TEMP_FILE_PATH, `function triggerPacket() {${code}}`);
-    let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
-    let {ui} = toolbox.getCurrentPanel().hud;
-
-    ok(ui.jsterm, "jsterm exists");
-    ok(ui.newConsoleOutput, "newConsoleOutput exists");
-
-    let networkEvent = new Promise(resolve => {
-      let i = 0;
-      toolbox.target.activeConsole.on(TARGET, (type, res) => {
-        stubs.packets.push(formatPacket(keys[i], res));
-        stubs.preparedMessages.push(formatNetworkEventStub(keys[i], res));
-        if (++i === keys.length) {
-          resolve();
-        }
-      });
-    });
-
-    let networkEventUpdate = new Promise(resolve => {
-      let i = 0;
-      ui.jsterm.hud.on("network-message-updated", function onNetworkUpdated(event, res) {
-        ui.jsterm.hud.off("network-message-updated", onNetworkUpdated);
-        stubs.preparedMessages.push(
-          formatNetworkEventStub(`${keys[i++]} ${res.packet.updateType}`, res));
-        if (i === keys.length) {
-          resolve();
-        }
-      });
-    });
-
-    yield ContentTask.spawn(gBrowser.selectedBrowser, {}, function () {
-      content.wrappedJSObject.triggerPacket();
-    });
-
-    yield Promise.all([networkEvent, networkEventUpdate]);
-  }
-  let filePath = OS.Path.join(`${BASE_PATH}/stubs/${TARGET}.js`);
-  OS.File.writeAtomic(filePath, formatFile(stubs, "NetworkEventMessage"));
-  OS.File.writeAtomic(TEMP_FILE_PATH, "");
+  let fileContent = yield generateNetworkEventStubs();
+  let filePath = OS.Path.join(`${BASE_PATH}/stubs/networkEvent.js`);
+  yield OS.File.writeAtomic(filePath, fileContent);
+  ok(true, "Make the test not fail");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/browser_webconsole_update_stubs_page_error.js
@@ -1,48 +1,15 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 Cu.import("resource://gre/modules/osfile.jsm", {});
-const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
-
-const { pageError: snippets} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
-
-let stubs = {
-  preparedMessages: [],
-  packets: [],
-};
 
 add_task(function* () {
-  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
-  ok(true, "make the test not fail");
-
-  for (let [key, code] of snippets) {
-    OS.File.writeAtomic(TEMP_FILE_PATH, `${code}`);
-    let received = new Promise(resolve => {
-      toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
-        toolbox.target.client.removeListener("pageError", onPacket);
-        info("Received page error:" + e + " " + JSON.stringify(packet, null, "\t"));
-
-        let message = prepareMessage(packet, {getNextId: () => 1});
-        stubs.packets.push(formatPacket(message.messageText, packet));
-        stubs.preparedMessages.push(formatStub(message.messageText, packet));
-        resolve();
-      });
-    });
-
-    yield ContentTask.spawn(gBrowser.selectedBrowser, key, function (subKey) {
-      let script = content.document.createElement("script");
-      script.src = "test-tempfile.js?key=" + encodeURIComponent(subKey);
-      content.document.body.appendChild(script);
-    });
-
-    yield received;
-  }
-
+  let fileContent = yield generatePageErrorStubs();
   let filePath = OS.Path.join(`${BASE_PATH}/stubs`, "pageError.js");
-  OS.File.writeAtomic(filePath, formatFile(stubs, "ConsoleMessage"));
-  OS.File.writeAtomic(TEMP_FILE_PATH, "");
+  yield OS.File.writeAtomic(filePath, fileContent);
+  ok(true, "Make the test not fail");
 });
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/head.js
@@ -1,36 +1,41 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 /* import-globals-from ../../../../../framework/test/shared-head.js */
-/* exported TEMP_FILE_PATH, TEMP_CSS_FILE_PATH, formatPacket, formatStub,
-            formatNetworkEventStub, formatFile */
+/* exported generateConsoleApiStubs, generateCssMessageStubs,
+            generateEvaluationResultStubs, generateNetworkEventStubs,
+            generatePageErrorStubs, BASE_PATH */
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 Services.prefs.setBoolPref("devtools.webconsole.new-frontend-enabled", true);
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.webconsole.new-frontend-enabled");
 });
 
 const { prepareMessage } = require("devtools/client/webconsole/new-console-output/utils/messages");
 const { stubPackets } = require("devtools/client/webconsole/new-console-output/test/fixtures/stubs/index.js");
+const {
+  consoleApi,
+  cssMessage,
+  evaluationResult,
+  networkEvent,
+  pageError,
+} = require("devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/stub-snippets.js");
 
 const BASE_PATH =
   "../../../../devtools/client/webconsole/new-console-output/test/fixtures";
-const TEMP_FILE_PATH = OS.Path.join(`${BASE_PATH}/stub-generators`, "test-tempfile.js");
-const TEMP_CSS_FILE_PATH = OS.Path.join(`${BASE_PATH}/stub-generators`,
-                                        "test-tempfile.css");
 
 let cachedPackets = {};
 
 function getCleanedPacket(key, packet) {
   if (Object.keys(cachedPackets).includes(key)) {
     return cachedPackets[key];
   }
 
@@ -51,33 +56,53 @@ function getCleanedPacket(key, packet) {
       from: existingPacket.from
     });
 
     // Clean root timestamp.
     if (res.timestamp) {
       res.timestamp = existingPacket.timestamp;
     }
 
+    if (res.timeStamp) {
+      res.timeStamp = existingPacket.timeStamp;
+    }
+
+    if (res.startedDateTime) {
+      res.startedDateTime = existingPacket.startedDateTime;
+    }
+
+    if (res.actor) {
+      res.actor = existingPacket.actor;
+    }
+
     if (res.message) {
       // Clean timeStamp on the message prop.
       res.message.timeStamp = existingPacket.message.timeStamp;
       if (res.message.timer) {
         // Clean timer properties on the message.
         // Those properties are found on console.time and console.timeEnd calls,
         // and those time can vary, which is why we need to clean them.
         if (res.message.timer.duration) {
           res.message.timer.duration = existingPacket.message.timer.duration;
         }
       }
 
       if (Array.isArray(res.message.arguments)) {
-        // Clean actor ids on each message.arguments item.
         res.message.arguments.forEach((argument, i) => {
+          let existingArgument = existingPacket.message.arguments[i];
+
+          // Clean actor ids on each message.arguments item.
           if (argument && argument.actor) {
-            argument.actor = existingPacket.message.arguments[i].actor;
+            argument.actor = existingArgument.actor;
+          }
+
+          // `window`'s properties count can vary from OS to OS, so we
+          // clean the `ownPropertyLength` property from the grip.
+          if (argument && argument.class === "Window") {
+            argument.ownPropertyLength = existingArgument.ownPropertyLength;
           }
         });
       }
     }
 
     if (res.result) {
       // Clean actor ids on evaluation result messages.
       res.result.actor = existingPacket.result.actor;
@@ -106,16 +131,53 @@ function getCleanedPacket(key, packet) {
       res.eventActor.startedDateTime = existingPacket.eventActor.startedDateTime;
       res.eventActor.timeStamp = existingPacket.eventActor.timeStamp;
     }
 
     if (res.pageError) {
       // Clean timeStamp on pageError messages.
       res.pageError.timeStamp = existingPacket.pageError.timeStamp;
     }
+
+    if (res.packet) {
+      if (res.packet.totalTime) {
+        // res.packet.totalTime is read-only so we use assign to override it.
+        res.packet = Object.assign({}, existingPacket.packet, {
+          totalTime: existingPacket.packet.totalTime
+        });
+      }
+    }
+
+    if (res.networkInfo) {
+      if (res.networkInfo.timeStamp) {
+        res.networkInfo.timeStamp = existingPacket.networkInfo.timeStamp;
+      }
+
+      if (res.networkInfo.startedDateTime) {
+        res.networkInfo.startedDateTime = existingPacket.networkInfo.startedDateTime;
+      }
+
+      if (res.networkInfo.totalTime) {
+        res.networkInfo.totalTime = existingPacket.networkInfo.totalTime;
+      }
+
+      if (res.networkInfo.actor) {
+        res.networkInfo.actor = existingPacket.networkInfo.actor;
+      }
+
+      if (res.networkInfo.request && res.networkInfo.request.headersSize) {
+        res.networkInfo.request.headersSize =
+          existingPacket.networkInfo.request.headersSize;
+      }
+
+      if (res.networkInfo.response && res.networkInfo.response.headersSize) {
+        res.networkInfo.response.headersSize =
+          existingPacket.networkInfo.response.headersSize;
+      }
+    }
   } else {
     res = packet;
   }
 
   cachedPackets[key] = res;
   return res;
 }
 
@@ -129,18 +191,23 @@ function formatStub(key, packet) {
     getCleanedPacket(key, packet),
     {getNextId: () => "1"}
   );
   let stringifiedMessage = JSON.stringify(prepared, null, 2);
   return `stubPreparedMessages.set("${key}", new ConsoleMessage(${stringifiedMessage}));`;
 }
 
 function formatNetworkEventStub(key, packet) {
-  let networkInfo = packet.actor ? packet : packet.networkInfo;
-  let prepared = prepareMessage(networkInfo, {getNextId: () => networkInfo.actor});
+  let cleanedPacket = getCleanedPacket(key, packet);
+  let networkInfo = cleanedPacket.networkInfo ? cleanedPacket.networkInfo : cleanedPacket;
+
+  let prepared = prepareMessage(
+    networkInfo,
+    {getNextId: () => "1"}
+  );
   let stringifiedMessage = JSON.stringify(prepared, null, 2);
   return `stubPreparedMessages.set("${key}", ` +
     `new NetworkEventMessage(${stringifiedMessage}));`;
 }
 
 function formatFile(stubs, type) {
   return `/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */
@@ -162,8 +229,213 @@ let stubPackets = new Map();
 ${stubs.packets.join("\n\n")}
 
 module.exports = {
   stubPreparedMessages,
   stubPackets,
 };
 `;
 }
+
+function* generateConsoleApiStubs() {
+  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
+
+  let stubs = {
+    preparedMessages: [],
+    packets: [],
+  };
+
+  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+  let {ui} = toolbox.getCurrentPanel().hud;
+  ok(ui.jsterm, "jsterm exists");
+  ok(ui.newConsoleOutput, "newConsoleOutput exists");
+
+  for (let [key, {keys, code}] of consoleApi) {
+    let received = new Promise(resolve => {
+      let i = 0;
+      let listener = (type, res) => {
+        stubs.packets.push(formatPacket(keys[i], res));
+        stubs.preparedMessages.push(formatStub(keys[i], res));
+        if (++i === keys.length) {
+          toolbox.target.client.removeListener("consoleAPICall", listener);
+          resolve();
+        }
+      };
+      toolbox.target.client.addListener("consoleAPICall", listener);
+    });
+
+    yield ContentTask.spawn(
+      gBrowser.selectedBrowser,
+      [key, code],
+      function ([subKey, subCode]) {
+        let script = content.document.createElement("script");
+        script.innerHTML = `function triggerPacket() {${subCode}}`;
+        content.document.body.appendChild(script);
+        content.wrappedJSObject.triggerPacket();
+        script.remove();
+      }
+    );
+
+    yield received;
+  }
+
+  yield closeTabAndToolbox();
+  return formatFile(stubs, "ConsoleMessage");
+}
+
+function* generateCssMessageStubs() {
+  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-css-message.html";
+
+  let stubs = {
+    preparedMessages: [],
+    packets: [],
+  };
+
+  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+
+  for (let [key, code] of cssMessage) {
+    let received = new Promise(resolve => {
+      /* CSS errors are considered as pageError on the server */
+      toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
+        toolbox.target.client.removeListener("pageError", onPacket);
+        info("Received css message:" + e + " " + JSON.stringify(packet, null, "\t"));
+
+        let message = prepareMessage(packet, {getNextId: () => 1});
+        stubs.packets.push(formatPacket(message.messageText, packet));
+        stubs.preparedMessages.push(formatStub(message.messageText, packet));
+        resolve();
+      });
+    });
+
+    yield ContentTask.spawn(
+      gBrowser.selectedBrowser,
+      [key, code],
+      function ([subKey, subCode]) {
+        let style = content.document.createElement("style");
+        style.innerHTML = subCode;
+        content.document.body.appendChild(style);
+      }
+    );
+
+    yield received;
+  }
+
+  yield closeTabAndToolbox();
+  return formatFile(stubs, "ConsoleMessage");
+}
+
+function* generateEvaluationResultStubs() {
+  const TEST_URI = "data:text/html;charset=utf-8,stub generation";
+
+  let stubs = {
+    preparedMessages: [],
+    packets: [],
+  };
+
+  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+
+  for (let [code, key] of evaluationResult) {
+    const packet = yield new Promise(resolve => {
+      toolbox.target.activeConsole.evaluateJS(code, resolve);
+    });
+    stubs.packets.push(formatPacket(key, packet));
+    stubs.preparedMessages.push(formatStub(key, packet));
+  }
+
+  yield closeTabAndToolbox();
+  return formatFile(stubs, "ConsoleMessage");
+}
+
+function* generateNetworkEventStubs() {
+  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html";
+
+  let stubs = {
+    preparedMessages: [],
+    packets: [],
+  };
+
+  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+  let {ui} = toolbox.getCurrentPanel().hud;
+
+  for (let [key, {keys, code}] of networkEvent) {
+    let onNetwork = new Promise(resolve => {
+      let i = 0;
+      toolbox.target.activeConsole.on("networkEvent", function onNetworkEvent(type, res) {
+        stubs.packets.push(formatPacket(keys[i], res));
+        stubs.preparedMessages.push(formatNetworkEventStub(keys[i], res));
+        if (++i === keys.length) {
+          toolbox.target.activeConsole.off("networkEvent", onNetworkEvent);
+          resolve();
+        }
+      });
+    });
+
+    let onNetworkUpdate = new Promise(resolve => {
+      let i = 0;
+      ui.jsterm.hud.on("network-message-updated", function onNetworkUpdated(event, res) {
+        let updateKey = `${keys[i++]} ${res.packet.updateType}`;
+        stubs.packets.push(formatPacket(updateKey, res));
+        stubs.preparedMessages.push(formatNetworkEventStub(updateKey, res));
+        if (i === keys.length) {
+          ui.jsterm.hud.off("network-message-updated", onNetworkUpdated);
+          resolve();
+        }
+      });
+    });
+
+    yield ContentTask.spawn(
+      gBrowser.selectedBrowser,
+      [key, code],
+      function ([subKey, subCode]) {
+        let script = content.document.createElement("script");
+        script.innerHTML = `function triggerPacket() {${subCode}}`;
+        content.document.body.appendChild(script);
+        content.wrappedJSObject.triggerPacket();
+        script.remove();
+      }
+    );
+
+    yield Promise.all([onNetwork, onNetworkUpdate]);
+  }
+
+  yield closeTabAndToolbox();
+  return formatFile(stubs, "NetworkEventMessage");
+}
+
+function* generatePageErrorStubs() {
+  const TEST_URI = "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html";
+
+  let stubs = {
+    preparedMessages: [],
+    packets: [],
+  };
+
+  let toolbox = yield openNewTabAndToolbox(TEST_URI, "webconsole");
+
+  for (let [key, code] of pageError) {
+    let received = new Promise(resolve => {
+      toolbox.target.client.addListener("pageError", function onPacket(e, packet) {
+        let message = prepareMessage(packet, {getNextId: () => 1});
+        stubs.packets.push(formatPacket(message.messageText, packet));
+        stubs.preparedMessages.push(formatStub(message.messageText, packet));
+        resolve();
+      }, {
+        once: true
+      });
+    });
+
+    yield ContentTask.spawn(
+      gBrowser.selectedBrowser,
+      [key, code],
+      function ([subKey, subCode]) {
+        let script = content.document.createElement("script");
+        script.innerHTML = subCode;
+        content.document.body.appendChild(script);
+        script.remove();
+      }
+    );
+
+    yield received;
+  }
+
+  yield closeTabAndToolbox();
+  return formatFile(stubs, "ConsoleMessage");
+}
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/moz.build
@@ -1,8 +1,8 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DevToolsModules(
-    'stub-snippets.js',
+    'stub-snippets.js'
 )
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html
@@ -1,11 +1,10 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Stub generator</title>
   </head>
   <body>
     <p>Stub generator</p>
-    <script src="test-tempfile.js"></script>
   </body>
 </html>
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-css-message.html
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-css-message.html
@@ -1,11 +1,10 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Stub generator</title>
   </head>
   <body>
     <p>Stub generator</p>
-    <link rel="stylesheet" href="test-tempfile.css"/>
   </body>
 </html>
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-network-event.html
@@ -1,11 +1,10 @@
 <!DOCTYPE html>
 <html lang="en">
   <head>
     <meta charset="utf-8">
     <title>Stub generator for network event</title>
   </head>
   <body>
     <p>Stub generator for network event</p>
-    <script src="test-tempfile.js"></script>
   </body>
 </html>
deleted file mode 100644
deleted file mode 100644
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -21,20 +21,20 @@ stubPreparedMessages.set("console.log('f
   "type": "log",
   "level": "log",
   "messageText": null,
   "parameters": [
     "foobar",
     "test"
   ],
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159894798,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"foobar\",\"test\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27foobar%27%2C%20%27test%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159894798,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[\"foobar\",\"test\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
   "stacktrace": null,
   "frame": {
-    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(%27foobar%27%2C%20%27test%27)",
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 1,
     "column": 27
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null
 }));
@@ -48,20 +48,20 @@ stubPreparedMessages.set("console.log(un
   "level": "log",
   "messageText": null,
   "parameters": [
     {
       "type": "undefined"
     }
   ],
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159896036,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"undefined\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(undefined)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159896036,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"undefined\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
   "stacktrace": null,
   "frame": {
-    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(undefined)",
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 1,
     "column": 27
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null
 }));
@@ -73,20 +73,20 @@ stubPreparedMessages.set("console.warn('
   "timeStamp": 1479159897333,
   "type": "warn",
   "level": "warn",
   "messageText": null,
   "parameters": [
     "danger, will robinson!"
   ],
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159897333,\"type\":\"warn\",\"level\":\"warn\",\"messageText\":null,\"parameters\":[\"danger, will robinson!\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.warn(%27danger%2C%20will%20robinson!%27)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159897333,\"type\":\"warn\",\"level\":\"warn\",\"messageText\":null,\"parameters\":[\"danger, will robinson!\"],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
   "stacktrace": null,
   "frame": {
-    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.warn(%27danger%2C%20will%20robinson!%27)",
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 1,
     "column": 27
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null
 }));
@@ -100,20 +100,20 @@ stubPreparedMessages.set("console.log(Na
   "level": "log",
   "messageText": null,
   "parameters": [
     {
       "type": "NaN"
     }
   ],
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159898667,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"NaN\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(NaN)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159898667,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"NaN\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
   "stacktrace": null,
   "frame": {
-    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(NaN)",
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 1,
     "column": 27
   },
   "groupId": null,
   "exceptionDocURL": null,
   "userProvidedStyles": [],
   "notes": null
 }));
@@ -127,20 +127,20 @@ stubPreparedMessages.set("console.log(nu
   "level": "log",
   "messageText": null,
   "parameters": [
     {
       "type": "null"
     }
   ],
   "repeat": 1,
-  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159900151,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"null\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(null)\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
+  "repeatId": "{\"id\":null,\"allowRepeating\":true,\"source\":\"console-api\",\"timeStamp\":1479159900151,\"type\":\"log\",\"level\":\"log\",\"messageText\":null,\"parameters\":[{\"type\":\"null\"}],\"repeatId\":null,\"stacktrace\":null,\"frame\":{\"source\":\"http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html\",\"line\":1,\"column\":27},\"groupId\":null,\"exceptionDocURL\":null,\"userProvidedStyles\":[],\"notes\":null}",
   "stacktrace": null,
   "frame": {
-    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-tempfile.js?key=console.log(null)",
+    "source": "http://example.com/browser/devtools/client/webconsole/new-console-output/test/fixtures/stub-generators/test-console-api.html",
     "line": 1,
     "column": 27
   },
   "groupId": null,
   "exceptionDocURL":