Merge mozilla-central to autoland
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Mon, 25 Jul 2016 16:22:04 +0200
changeset 348583 8db944cbd91f64621a496b9547bc38d89cdc0797
parent 348582 fcf088f6097c1f657c29d2d5a1255cf0e5495702 (current diff)
parent 348579 66498480fe6516aa48f8e7265d09b03122d17d7a (diff)
child 348584 a81edfa4344094366b65a43e6a514226beabeb44
push id1230
push userjlund@mozilla.com
push dateMon, 31 Oct 2016 18:13:35 +0000
treeherdermozilla-release@5e06e3766db2 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone50.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to autoland
devtools/client/eyedropper/commands.js
devtools/client/eyedropper/crosshairs.css
devtools/client/eyedropper/eyedropper-child.js
devtools/client/eyedropper/eyedropper.js
devtools/client/eyedropper/eyedropper.xul
devtools/client/eyedropper/moz.build
devtools/client/eyedropper/nocursor.css
devtools/client/eyedropper/test/.eslintrc
devtools/client/eyedropper/test/browser.ini
devtools/client/eyedropper/test/browser_eyedropper_basic.js
devtools/client/eyedropper/test/browser_eyedropper_cmd.js
devtools/client/eyedropper/test/color-block.html
devtools/client/eyedropper/test/head.js
devtools/client/shared/components/reps/named-node-map.js
devtools/client/shared/css-color-db.js
devtools/client/shared/css-color.js
media/libcubeb/src/cubeb_audiounit.c
mfbt/NumericLimits.h
mobile/android/base/java/org/mozilla/gecko/distribution/PartnerBookmarksProviderClient.java
mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAwesomebar.java
netwerk/test/unit/test_cache2-29e-non-206-response.js
security/manager/ssl/tests/unit/test_fallback_cipher.js
testing/mozharness/configs/builds/releng_sub_android_configs/64_api_11.py
testing/mozharness/configs/builds/releng_sub_android_configs/64_api_11_debug.py
testing/mozharness/configs/builds/releng_sub_android_configs/64_api_9.py
testing/mozharness/configs/builds/releng_sub_android_configs/64_api_9_debug.py
testing/mozharness/configs/single_locale/ash_android-api-9.py
testing/mozharness/configs/single_locale/mozilla-aurora_android-api-9.py
testing/mozharness/configs/single_locale/mozilla-central_android-api-9.py
testing/mozharness/configs/single_locale/release_mozilla-beta_android_api_9.py
testing/mozharness/configs/single_locale/release_mozilla-release_android_api_9.py
testing/mozharness/configs/single_locale/staging_release_mozilla-beta_android_api_9.py
testing/mozharness/configs/single_locale/staging_release_mozilla-release_android_api_9.py
widget/android/android/StrongPointer.h
--- a/.eslintignore
+++ b/.eslintignore
@@ -108,16 +108,18 @@ devtools/client/webconsole/**
 !devtools/client/webconsole/jsterm.js
 !devtools/client/webconsole/console-commands.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
 devtools/server/**
 !devtools/server/child.js
 !devtools/server/css-logic.js
 !devtools/server/main.js
+!devtools/server/actors/inspector.js
+!devtools/server/actors/highlighters/eye-dropper.js
 !devtools/server/actors/webbrowser.js
 !devtools/server/actors/styles.js
 !devtools/server/actors/string.js
 !devtools/server/actors/csscoverage.js
 devtools/shared/*.js
 !devtools/shared/css-lexer.js
 !devtools/shared/defer.js
 !devtools/shared/event-emitter.js
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1287946 - clobber due to generated SDK headers changing (bug 1182840)
+Bug 1285541 - Clobber needed because this patch renames a file.
--- a/addon-sdk/source/lib/sdk/tab/events.js
+++ b/addon-sdk/source/lib/sdk/tab/events.js
@@ -34,37 +34,41 @@ const TYPES = ["TabOpen","TabClose","Tab
 function tabEventsFor(window) {
   // Map supported event types to a streams of those events on the given
   // `window` and than merge these streams into single form stream off
   // all events.
   let channels = TYPES.map(type => open(window, type));
   return merge(channels);
 }
 
-// Filter DOMContentLoaded events from all the browser events.
-var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
-// Map DOMContentLoaded events to it's target browser windows.
-var futureWindows = map(readyEvents, e => e.target);
-// Expand all browsers that will become interactive to supported tab events
-// on these windows. Result will be a tab events from all tabs of all windows
-// that will become interactive.
-var eventsFromFuture = expand(futureWindows, tabEventsFor);
+// Create our event channels.  We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+  // Filter DOMContentLoaded events from all the browser events.
+  var readyEvents = filter(events, e => e.type === "DOMContentLoaded");
+  // Map DOMContentLoaded events to it's target browser windows.
+  var futureWindows = map(readyEvents, e => e.target);
+  // Expand all browsers that will become interactive to supported tab events
+  // on these windows. Result will be a tab events from all tabs of all windows
+  // that will become interactive.
+  var eventsFromFuture = expand(futureWindows, tabEventsFor);
 
-// Above covers only windows that will become interactive in a future, but some
-// windows may already be interactive so we pick those and expand to supported
-// tab events for them too.
-var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
-                         filter(isInteractive);
-var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
+  // Above covers only windows that will become interactive in a future, but some
+  // windows may already be interactive so we pick those and expand to supported
+  // tab events for them too.
+  var interactiveWindows = windows("navigator:browser", { includePrivate: true }).
+                           filter(isInteractive);
+  var eventsFromInteractive = merge(interactiveWindows.map(tabEventsFor));
 
 
-// Finally merge stream of tab events from future windows and current windows
-// to cover all tab events on all windows that will open.
-var allEvents = merge([eventsFromInteractive, eventsFromFuture]);
+  // Finally merge stream of tab events from future windows and current windows
+  // to cover all tab events on all windows that will open.
+  return merge([eventsFromInteractive, eventsFromFuture]);
+}
 
 // Map events to Fennec format if necessary
-exports.events = map(allEvents, function (event) {
+exports.events = map(makeEvents(), function (event) {
   return !isFennec ? event : {
     type: event.type,
     target: event.target.ownerDocument.defaultView.BrowserApp
             .getTabForBrowser(event.target)
   };
 });
--- a/addon-sdk/source/lib/sdk/window/events.js
+++ b/addon-sdk/source/lib/sdk/window/events.js
@@ -38,27 +38,31 @@ function eventsFor(window) {
   //       completely closed.
   let interactive = open(window, "DOMContentLoaded", { capture: true });
   let complete = open(window, "load", { capture: true });
   let states = merge([interactive, complete]);
   let changes = filter(states, makeStrictDocumentFilter(window));
   return map(changes, toEventWithDefaultViewTarget);
 }
 
-// In addition to observing windows that are open we also observe windows
-// that are already already opened in case they're in process of loading.
-var opened = windows(null, { includePrivate: true });
-var currentEvents = merge(opened.map(eventsFor));
+// Create our event channels.  We do this in a separate function to
+// minimize the chance of leaking intermediate objects on the global.
+function makeEvents() {
+  // In addition to observing windows that are open we also observe windows
+  // that are already already opened in case they're in process of loading.
+  var opened = windows(null, { includePrivate: true });
+  var currentEvents = merge(opened.map(eventsFor));
 
-// Register system event listeners for top level window open / close.
-function rename({type, target, data}) {
-  return { type: rename[type], target: target, data: data }
+  // Register system event listeners for top level window open / close.
+  function rename({type, target, data}) {
+    return { type: rename[type], target: target, data: data }
+  }
+  rename.domwindowopened = "open";
+  rename.domwindowclosed = "close";
+
+  var openEvents = map(observe("domwindowopened"), rename);
+  var closeEvents = map(observe("domwindowclosed"), rename);
+  var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
+
+  return merge([currentEvents, futureEvents, openEvents, closeEvents]);
 }
-rename.domwindowopened = "open";
-rename.domwindowclosed = "close";
 
-var openEvents = map(observe("domwindowopened"), rename);
-var closeEvents = map(observe("domwindowclosed"), rename);
-var futureEvents = expand(openEvents, ({target}) => eventsFor(target));
-
-var channel = merge([currentEvents, futureEvents,
-                     openEvents, closeEvents]);
-exports.events = channel;
+exports.events = makeEvents();
--- a/addon-sdk/source/test/leak/jetpack-package.ini
+++ b/addon-sdk/source/test/leak/jetpack-package.ini
@@ -1,7 +1,8 @@
 [DEFAULT]
 support-files =
   leak-utils.js
 
 [test-leak-window-events.js]
 [test-leak-event-dom-closed-window.js]
+[test-leak-tab-events.js]
 [test-leak-event-chrome.js]
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/test/leak/test-leak-tab-events.js
@@ -0,0 +1,46 @@
+/* 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 { asyncWindowLeakTest } = require("./leak-utils");
+const { Loader } = require('sdk/test/loader');
+const openWindow = require("sdk/window/utils").open;
+
+exports["test sdk/tab/events does not leak new window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise(resolve => {
+      let loader = Loader(module);
+      let { events } = loader.require('sdk/tab/events');
+      let w = openWindow();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+}
+
+exports["test sdk/tab/events does not leak when attached to existing window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise(resolve => {
+      let loader = Loader(module);
+      let w = openWindow();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        let { events } = loader.require('sdk/tab/events');
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+}
+
+require("sdk/test").run(exports);
--- a/addon-sdk/source/test/leak/test-leak-window-events.js
+++ b/addon-sdk/source/test/leak/test-leak-window-events.js
@@ -39,9 +39,27 @@ exports["test window/events for leaks"] 
       });
 
       // Open a window.  This will trigger our data events.
       open();
     });
   });
 };
 
+exports["test window/events for leaks with existing window"] = function*(assert) {
+  yield asyncWindowLeakTest(assert, _ => {
+    return new Promise((resolve, reject) => {
+      let loader = Loader(module);
+      let w = open();
+      w.addEventListener("load", function windowLoaded(evt) {
+        w.removeEventListener("load", windowLoaded);
+        let { events } = loader.require("sdk/window/events");
+        w.addEventListener("DOMWindowClose", function windowClosed(evt) {
+          w.removeEventListener("DOMWindowClose", windowClosed);
+          resolve(loader);
+        });
+        w.close();
+      });
+    });
+  });
+};
+
 require("sdk/test").run(exports);
--- a/browser/app/moz.build
+++ b/browser/app/moz.build
@@ -75,11 +75,14 @@ if CONFIG['OS_ARCH'] == 'WINNT' and not 
 DISABLE_STL_WRAPPING = True
 
 if CONFIG['MOZ_LINKER']:
     OS_LIBS += CONFIG['MOZ_ZLIB_LIBS']
 
 if CONFIG['HAVE_CLOCK_MONOTONIC']:
     OS_LIBS += CONFIG['REALTIME_LIBS']
 
+if CONFIG['MOZ_GPSD']:
+    DEFINES['MOZ_GPSD'] = True
+
 for icon in ('firefox', 'document', 'newwindow', 'newtab', 'pbmode'):
     DEFINES[icon.upper() + '_ICO'] = '"%s/dist/branding/%s.ico"' % (
         TOPOBJDIR, icon)
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1235,16 +1235,26 @@ pref("geo.provider.use_corelocation", fa
 pref("geo.provider.use_corelocation", true);
 #endif
 #endif
 
 #ifdef XP_WIN
 pref("geo.provider.ms-windows-location", false);
 #endif
 
+#ifdef MOZ_WIDGET_GTK
+#ifdef MOZ_GPSD
+#ifdef RELEASE_BUILD
+pref("geo.provider.use_gpsd", false);
+#else
+pref("geo.provider.use_gpsd", true);
+#endif
+#endif
+#endif
+
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // CustomizableUI state of the browser's user interface
@@ -1458,10 +1468,10 @@ pref("dom.mozBrowserFramesEnabled", true
 pref("extensions.pocket.enabled", true);
 
 pref("signon.schemeUpgrades", true);
 
 // Enable the "Simplify Page" feature in Print Preview
 pref("print.use_simplify_page", true);
 
 // Space separated list of URLS that are allowed to send objects (instead of
-// only strings) through webchannels.
+// only strings) through webchannels. This list is duplicated in mobile/android/app/mobile.js
 pref("webchannel.allowObject.urlWhitelist", "https://accounts.firefox.com https://content.cdn.mozilla.net https://hello.firefox.com https://input.mozilla.org https://support.mozilla.org https://install.mozilla.org");
--- a/browser/base/content/browser-fxaccounts.js
+++ b/browser/base/content/browser-fxaccounts.js
@@ -402,20 +402,22 @@ var gFxAccounts = {
     }
 
     const clients = this.remoteClients;
     for (let client of clients) {
       addTargetDevice(client.id, client.name);
     }
 
     // "All devices" menu item
-    const separator = document.createElement("menuseparator");
-    fragment.appendChild(separator);
-    const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
-    addTargetDevice("", allDevicesLabel);
+    if (clients.length > 1) {
+      const separator = document.createElement("menuseparator");
+      fragment.appendChild(separator);
+      const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
+      addTargetDevice("", allDevicesLabel);
+    }
 
     devicesPopup.appendChild(fragment);
   },
 
   updateTabContextMenu: function (aPopupMenu) {
     if (!this.sendTabToDeviceEnabled) {
       return;
     }
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -3,16 +3,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/. */
 
 var Ci = Components.interfaces;
 var Cu = Components.utils;
 var Cc = Components.classes;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/ContextualIdentityService.jsm");
 Cu.import("resource://gre/modules/NotificationDB.jsm");
 Cu.import("resource:///modules/RecentWindow.jsm");
 
 
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Deprecated",
                                   "resource://gre/modules/Deprecated.jsm");
@@ -51,18 +52,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
                                    "mozIAsyncFavicons");
 XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
                                    "@mozilla.org/network/dns-service;1",
                                    "nsIDNSService");
 XPCOMUtils.defineLazyServiceGetter(this, "WindowsUIUtils",
                                    "@mozilla.org/windows-ui-utils;1", "nsIWindowsUIUtils");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
 XPCOMUtils.defineLazyServiceGetter(this, "gAboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 XPCOMUtils.defineLazyGetter(this, "gBrowserBundle", function() {
   return Services.strings.createBundle('chrome://browser/locale/browser.properties');
 });
 XPCOMUtils.defineLazyModuleGetter(this, "AddonWatcher",
                                   "resource://gre/modules/AddonWatcher.jsm");
@@ -7756,31 +7755,16 @@ var ResponsiveUI = {
 };
 
 XPCOMUtils.defineLazyGetter(ResponsiveUI, "ResponsiveUIManager", function() {
   let tmp = {};
   Cu.import("resource://devtools/client/responsivedesign/responsivedesign.jsm", tmp);
   return tmp.ResponsiveUIManager;
 });
 
-function openEyedropper() {
-  var eyedropper = new this.Eyedropper(this, { context: "menu",
-                                               copyOnSelect: true });
-  eyedropper.open();
-}
-
-Object.defineProperty(this, "Eyedropper", {
-  get: function() {
-    let devtools = Cu.import("resource://devtools/shared/Loader.jsm", {}).devtools;
-    return devtools.require("devtools/client/eyedropper/eyedropper").Eyedropper;
-  },
-  configurable: true,
-  enumerable: true
-});
-
 XPCOMUtils.defineLazyGetter(window, "gShowPageResizers", function () {
   // Only show resizers on Windows 2000 and XP
   return AppConstants.isPlatformAndVersionAtMost("win", "5.9");
 });
 
 var MousePosTracker = {
   _listeners: new Set(),
   _x: 0,
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1,23 +1,22 @@
 /* -*- tab-width: 2; indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ts=2 sw=2 sts=2 et tw=80: */
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource://gre/modules/InlineSpellChecker.jsm");
 Components.utils.import("resource://gre/modules/LoginManagerContextMenu.jsm");
 Components.utils.import("resource://gre/modules/BrowserUtils.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LoginHelper",
   "resource://gre/modules/LoginHelper.jsm");
 
 var gContextMenuContentData = null;
 
 function nsContextMenu(aXulMenu, aIsShift) {
   this.shouldDisplay = true;
--- a/browser/base/content/utilityOverlay.js
+++ b/browser/base/content/utilityOverlay.js
@@ -1,26 +1,24 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * 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/. */
 
 // Services = object with smart getters for common XPCOM services
 Components.utils.import("resource://gre/modules/AppConstants.jsm");
+Components.utils.import("resource://gre/modules/ContextualIdentityService.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
 Components.utils.import("resource:///modules/RecentWindow.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "ShellService",
                                   "resource:///modules/ShellService.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "ContextualIdentityService",
-                                  "resource://gre/modules/ContextualIdentityService.jsm");
-
 XPCOMUtils.defineLazyServiceGetter(this, "aboutNewTabService",
                                    "@mozilla.org/browser/aboutnewtab-service;1",
                                    "nsIAboutNewTabService");
 
 this.__defineGetter__("BROWSER_NEW_TAB_URL", () => {
   if (PrivateBrowsingUtils.isWindowPrivate(window) &&
       !PrivateBrowsingUtils.permanentPrivateBrowsing &&
       !aboutNewTabService.overridden) {
@@ -442,18 +440,22 @@ function createUserContextMenu(event, ad
 
   ContextualIdentityService.getIdentities().forEach(identity => {
     if (identity.userContextId == excludeUserContextId) {
       return;
     }
 
     let menuitem = document.createElement("menuitem");
     menuitem.setAttribute("usercontextid", identity.userContextId);
-    menuitem.setAttribute("label", bundle.getString(identity.label));
-    menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    menuitem.setAttribute("label", ContextualIdentityService.getUserContextLabel(identity.userContextId));
+
+    if (identity.accessKey) {
+      menuitem.setAttribute("accesskey", bundle.getString(identity.accessKey));
+    }
+
     menuitem.classList.add("menuitem-iconic");
 
     if (addCommandAttribute) {
       menuitem.setAttribute("command", "Browser:NewUserContextTab");
     }
 
     menuitem.setAttribute("image", identity.icon);
 
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -1143,17 +1143,17 @@ const CustomizableWidgets = [
       while (items.firstChild) {
         items.firstChild.remove();
       }
 
       let fragment = doc.createDocumentFragment();
 
       ContextualIdentityService.getIdentities().forEach(identity => {
         let bundle = doc.getElementById("bundle_browser");
-        let label = bundle.getString(identity.label);
+        let label = ContextualIdentityService.getUserContextLabel(identity.userContextId);
 
         let item = doc.createElementNS(kNSXUL, "toolbarbutton");
         item.setAttribute("label", label);
         item.setAttribute("usercontextid", identity.userContextId);
         item.setAttribute("class", "subviewbutton");
         item.setAttribute("image", identity.icon);
 
         fragment.appendChild(item);
--- a/browser/components/extensions/ext-tabs.js
+++ b/browser/components/extensions/ext-tabs.js
@@ -37,73 +37,75 @@ function getSender(context, target, send
     // The message came from an ExtensionContext. In that case, it should
     // include a tabId property (which is filled in by the page-open
     // listener below).
     sender.tab = TabManager.convert(context.extension, TabManager.getTab(sender.tabId));
     delete sender.tabId;
   }
 }
 
-// WeakMap[ExtensionContext -> {tab, parentWindow}]
-var pageDataMap = new WeakMap();
+function getDocShellOwner(docShell) {
+  let browser = docShell.chromeEventHandler;
+
+  let xulWindow = browser.ownerGlobal;
+
+  let {gBrowser} = xulWindow;
+  if (gBrowser) {
+    let tab = gBrowser.getTabForBrowser(browser);
+
+    return {xulWindow, tab};
+  }
+
+  return {};
+}
 
 /* eslint-disable mozilla/balanced-listeners */
 // This listener fires whenever an extension page opens in a tab
 // (either initiated by the extension or the user). Its job is to fill
 // in some tab-specific details and keep data around about the
 // ExtensionContext.
-extensions.on("page-load", (type, page, params, sender, delegate) => {
+extensions.on("page-load", (type, context, params, sender, delegate) => {
   if (params.type == "tab" || params.type == "popup") {
-    let browser = params.docShell.chromeEventHandler;
+    let {xulWindow, tab} = getDocShellOwner(params.docShell);
 
-    let parentWindow = browser.ownerGlobal;
-    page.windowId = WindowManager.getId(parentWindow);
-
-    let tab = parentWindow.gBrowser.getTabForBrowser(browser);
+    // FIXME: Handle tabs being moved between windows.
+    context.windowId = WindowManager.getId(xulWindow);
     if (tab) {
       sender.tabId = TabManager.getId(tab);
-      page.tabId = TabManager.getId(tab);
+      context.tabId = TabManager.getId(tab);
     }
-
-    pageDataMap.set(page, {tab, parentWindow});
   }
 
   delegate.getSender = getSender;
 });
 
-extensions.on("page-unload", (type, page) => {
-  pageDataMap.delete(page);
-});
-
-extensions.on("page-shutdown", (type, page) => {
-  if (pageDataMap.has(page)) {
-    let {tab, parentWindow} = pageDataMap.get(page);
-    pageDataMap.delete(page);
-
+extensions.on("page-shutdown", (type, context) => {
+  if (context.type == "tab") {
+    let {xulWindow, tab} = getDocShellOwner(context.docShell);
     if (tab) {
-      parentWindow.gBrowser.removeTab(tab);
+      xulWindow.gBrowser.removeTab(tab);
     }
   }
 });
 
 extensions.on("fill-browser-data", (type, browser, data, result) => {
   let tabId = TabManager.getBrowserId(browser);
   if (tabId == -1) {
     result.cancel = true;
     return;
   }
 
   data.tabId = tabId;
 });
 /* eslint-enable mozilla/balanced-listeners */
 
 global.currentWindow = function(context) {
-  let pageData = pageDataMap.get(context);
-  if (pageData) {
-    return pageData.parentWindow;
+  let {xulWindow} = getDocShellOwner(context.docShell);
+  if (xulWindow) {
+    return xulWindow;
   }
   return WindowManager.topWindow;
 };
 
 let tabListener = {
   init() {
     if (this.initialized) {
       return;
--- a/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_connect.js
@@ -38,17 +38,20 @@ add_task(function* () {
 
           port_messages_received++;
           browser.test.assertEq(2, port_messages_received, "2 port messages received");
 
           browser.test.notifyPass("contentscript_connect.pass");
         });
       });
 
-      browser.tabs.executeScript({file: "script.js"});
+      browser.tabs.executeScript({file: "script.js"}).catch(e => {
+        browser.test.fail(`Error: ${e} :: ${e.stack}`);
+        browser.test.notifyFail("contentscript_connect.pass");
+      });
     },
 
     files: {
       "script.js": function() {
         let port = browser.runtime.connect();
         port.postMessage("port message");
       },
     },
--- a/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
+++ b/browser/components/extensions/test/browser/browser_ext_runtime_openOptionsPage.js
@@ -252,8 +252,71 @@ add_task(function* test_options_no_manif
         browser.test.notifyFail("options-no-manifest");
       });
     },
   });
 
   yield extension.awaitFinish("options-no-manifest");
   yield extension.unload();
 });
+
+add_task(function* test_inline_options_uninstall() {
+  let tab = yield BrowserTestUtils.openNewForegroundTab(gBrowser, "http://example.com/");
+
+  let extension = yield loadExtension({
+    manifest: {
+      "options_ui": {
+        "page": "options.html",
+      },
+    },
+
+    background: function() {
+      let _optionsPromise;
+      let awaitOptions = () => {
+        browser.test.assertFalse(_optionsPromise, "Should not be awaiting options already");
+
+        return new Promise(resolve => {
+          _optionsPromise = {resolve};
+        });
+      };
+
+      browser.runtime.onMessage.addListener((msg, sender) => {
+        if (msg == "options.html") {
+          if (_optionsPromise) {
+            _optionsPromise.resolve(sender.tab);
+            _optionsPromise = null;
+          } else {
+            browser.test.fail("Saw unexpected options page load");
+          }
+        }
+      });
+
+      let firstTab;
+      browser.tabs.query({currentWindow: true, active: true}).then(tabs => {
+        firstTab = tabs[0].id;
+
+        browser.test.log("Open options page. Expect fresh load.");
+        return Promise.all([
+          browser.runtime.openOptionsPage(),
+          awaitOptions(),
+        ]);
+      }).then(([, tab]) => {
+        browser.test.assertEq("about:addons", tab.url, "Tab contains AddonManager");
+        browser.test.assertTrue(tab.active, "Tab is active");
+        browser.test.assertTrue(tab.id != firstTab, "Tab is a new tab");
+
+        browser.test.sendMessage("options-ui-open");
+      }).catch(error => {
+        browser.test.fail(`Error: ${error} :: ${error.stack}`);
+      });
+    },
+  });
+
+  yield extension.awaitMessage("options-ui-open");
+  yield extension.unload();
+
+  is(gBrowser.selectedBrowser.currentURI.spec, "about:addons",
+     "Add-on manager tab should still be open");
+
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+
+  yield BrowserTestUtils.removeTab(tab);
+});
--- a/browser/config/tooltool-manifests/linux32/releng.manifest
+++ b/browser/config/tooltool-manifests/linux32/releng.manifest
@@ -19,15 +19,23 @@
 "version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 102276708,
 "digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 3123796,
+"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/linux64/releng.manifest
+++ b/browser/config/tooltool-manifests/linux64/releng.manifest
@@ -19,15 +19,23 @@
 "version": "gecko rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 102276708,
 "digest": "8cc9ea8347fc7e6e6fdb15a8fd1faae977f1235a426b879b3f9128ec91d8f2b6268297ce80bf4eceb47738bd40bfeda13f143dc3fe85f1434b13adfbc095ab90",
 "algorithm": "sha512",
 "filename": "rustc.tar.xz",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 3123796,
+"digest": "4b9d2bcb8488b6649ba6c748e19d33bfceb25c7566e882fc7e00322392e424a5a9c5878c11c61d57cdaecf67bcc110842c6eff95e49736e8f3c83d9ce1677122",
+"algorithm": "sha512",
+"filename": "cargo.tar.xz",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/cross-releng.manifest
@@ -19,16 +19,24 @@
 "size": 35215976, 
 "visibility": "internal", 
 "digest": "8be736545ddab25ebded188458ce974d5c9a7e29f3c50d2ebfbcb878f6aff853dd2ff5a3528bdefc64396a10101a1b50fd2fe52000140df33643cebe1ea759da", 
 "algorithm": "sha512", 
 "unpack": true,
 "filename": "MacOSX10.7.sdk.tar.bz2"
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2571167,
+"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "unpack": true,
 "filename": "sccache.tar.bz2"
 },
 {
 "size": 57060, 
--- a/browser/config/tooltool-manifests/macosx64/releng.manifest
+++ b/browser/config/tooltool-manifests/macosx64/releng.manifest
@@ -10,15 +10,23 @@
 {
 "size": 93295855,
 "digest": "2b8fd0c1ba337a7035090c420305a7892e663ce6781569b100b36fa21cc26146e67f44a34c7715f0004f48bbe46c232bbbf2928c9d0595243d2584530770b504",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2571167,
+"digest": "b2616459fbf15c75b54628a6bfe8cf89c0841ea08431f5096e72be4fac4c685785dfc7a2f18a03a5f7bd377e78d3c108e5029b12616842cbbd0497ff7363fdaf",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 }
 ]
--- a/browser/config/tooltool-manifests/win32/releng.manifest
+++ b/browser/config/tooltool-manifests/win32/releng.manifest
@@ -9,16 +9,24 @@
 "version": "rustc 1.10.0 (cfcb716cf 2016-07-03)",
 "size": 88820579,
 "digest": "3bc772d951bf90b01cdba9dcd0e1d131a98519dff0710bb219784ea43d4d001dbce191071a4b3824933386bb9613f173760c438939eb396b0e0dfdad9a42e4f0",
 "algorithm": "sha512",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2298848,
+"digest": "d3d1f7b6d195248550f98eb8ce87aa314d36a8a667c110ff2058777fe5a97b7007a41dc1c8a4605c4230e9105972768918222352d5e0fdebbc49639671de38ca",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
--- a/browser/config/tooltool-manifests/win64/releng.manifest
+++ b/browser/config/tooltool-manifests/win64/releng.manifest
@@ -10,16 +10,24 @@
 "size": 94067220,
 "digest": "05cabda2a28ce6674f062aab589b4b3758e0cd4a4af364bb9a2e736254baa10d668936b2b7ed0df530c7f5ba8ea1e7f51ff3affc84a6551c46188b2f67f10e05",
 "algorithm": "sha512",
 "visibility": "public",
 "filename": "rustc.tar.bz2",
 "unpack": true
 },
 {
+"version": "cargo 0.13.0-nightly (664125b 2016-07-19)",
+"size": 2561498,
+"digest": "d300fd06b16efe49bdb1a238d516c8797d2de0edca7efadd55249401e1dd1d775fb84649630e273f95d9e8b956d87d1f75726c0a68294d25fafe078c3b2b9ba9",
+"algorithm": "sha512",
+"filename": "cargo.tar.bz2",
+"unpack": true
+},
+{
 "size": 167175,
 "digest": "0b71a936edf5bd70cf274aaa5d7abc8f77fe8e7b5593a208f805cc9436fac646b9c4f0b43c2b10de63ff3da671497d35536077ecbc72dba7f8159a38b580f831",
 "algorithm": "sha512",
 "filename": "sccache.tar.bz2",
 "unpack": true
 },
 {
 "version": "Visual Studio 2015 Update 2 / SDK 10.0.10586.0/212",
--- 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.5.337
+Current extension version is: 1.5.345
--- a/browser/extensions/pdfjs/content/PdfJs.jsm
+++ b/browser/extensions/pdfjs/content/PdfJs.jsm
@@ -272,17 +272,17 @@ var PdfJs = {
       let PdfjsChromeUtils = Components.utils.import(jsm, {}).PdfjsChromeUtils;
       PdfjsChromeUtils.notifyChildOfSettingsChange();
     }
   },
 
   /**
    * pdf.js is only enabled if it is both selected as the pdf viewer and if the
    * global switch enabling it is true.
-   * @return {boolean} Wether or not it's enabled.
+   * @return {boolean} Whether or not it's enabled.
    */
   get enabled() {
     var disabled = getBoolPref(PREF_DISABLED, true);
     if (disabled) {
       return false;
     }
 
     // Check if the 'application/pdf' preview handler is configured properly.
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -329,17 +329,17 @@ ChromeActions.prototype = {
     try {
       // Lazy initialization of localizedStrings
       if (!('localizedStrings' in this)) {
         this.localizedStrings = getLocalizedStrings('viewer.properties');
       }
       var result = this.localizedStrings[data];
       return JSON.stringify(result || null);
     } catch (e) {
-      log('Unable to retrive localized strings: ' + e);
+      log('Unable to retrieve localized strings: ' + e);
       return 'null';
     }
   },
   supportsIntegratedFind: function() {
     // Integrated find is only supported when we're not in a frame
     if (this.domWindow.frameElement !== null) {
       return false;
     }
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.337';
-var pdfjsBuild = '11381cd';
+var pdfjsVersion = '1.5.345';
+var pdfjsBuild = '10f9f11';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -1075,17 +1075,17 @@ function isSpace(ch) {
   return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A);
 }
 
 /**
  * Promise Capability object.
  *
  * @typedef {Object} PromiseCapability
  * @property {Promise} promise - A promise object.
- * @property {function} resolve - Fullfills the promise.
+ * @property {function} resolve - Fulfills the promise.
  * @property {function} reject - Rejects the promise.
  */
 
 /**
  * Creates a promise capability object.
  * @alias createPromiseCapability
  *
  * @return {PromiseCapability} A capability object contains:
@@ -1098,18 +1098,18 @@ function createPromiseCapability() {
     capability.reject = reject;
   });
   return capability;
 }
 
 /**
  * Polyfill for Promises:
  * The following promise implementation tries to generally implement the
- * Promise/A+ spec. Some notable differences from other promise libaries are:
- * - There currently isn't a seperate deferred and promise object.
+ * Promise/A+ spec. Some notable differences from other promise libraries are:
+ * - There currently isn't a separate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
  * Based off of the work in:
  * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
  */
 (function PromiseClosure() {
   if (globalScope.Promise) {
     // Promises existing in the DOM/Worker, checking presence of all/resolve
@@ -3610,17 +3610,17 @@ var createMeshCanvas = (function createM
   function createMeshCanvas(bounds, combinesScale, coords, colors, figures,
                             backgroundColor, cachedCanvases) {
     // we will increase scale on some weird factor to let antialiasing take
     // care of "rough" edges
     var EXPECTED_SCALE = 1.1;
     // MAX_PATTERN_SIZE is used to avoid OOM situation.
     var MAX_PATTERN_SIZE = 3000; // 10in @ 300dpi shall be enough
     // We need to keep transparent border around our pattern for fill():
-    // createPattern with 'no-repeat' will bleed edges accross entire area.
+    // createPattern with 'no-repeat' will bleed edges across entire area.
     var BORDER_SIZE = 2;
 
     var offsetX = Math.floor(bounds[0]);
     var offsetY = Math.floor(bounds[1]);
     var boundsWidth = Math.ceil(bounds[2]) - offsetX;
     var boundsHeight = Math.ceil(bounds[3]) - offsetY;
 
     var width = Math.min(Math.ceil(Math.abs(boundsWidth * combinesScale[0] *
@@ -5683,17 +5683,17 @@ var CanvasGraphics = (function CanvasGra
       this.baseTransform = this.baseTransformStack.pop();
     },
 
     beginGroup: function CanvasGraphics_beginGroup(group) {
       this.save();
       var currentCtx = this.ctx;
       // TODO non-isolated groups - according to Rik at adobe non-isolated
       // group results aren't usually that different and they even have tools
-      // that ignore this setting. Notes from Rik on implmenting:
+      // that ignore this setting. Notes from Rik on implementing:
       // - When you encounter an transparency group, create a new canvas with
       // the dimensions of the bbox
       // - copy the content from the previous canvas to the new canvas
       // - draw as usual
       // - remove the backdrop alpha:
       // alphaNew = 1 - (1 - alpha)/(1 - alphaBackdrop) with 'alpha' the alpha
       // value of your transparency group and 'alphaBackdrop' the alpha of the
       // backdrop
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.5.337';
-var pdfjsBuild = '11381cd';
+var pdfjsVersion = '1.5.345';
+var pdfjsBuild = '10f9f11';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -3096,17 +3096,17 @@ function isSpace(ch) {
   return (ch === 0x20 || ch === 0x09 || ch === 0x0D || ch === 0x0A);
 }
 
 /**
  * Promise Capability object.
  *
  * @typedef {Object} PromiseCapability
  * @property {Promise} promise - A promise object.
- * @property {function} resolve - Fullfills the promise.
+ * @property {function} resolve - Fulfills the promise.
  * @property {function} reject - Rejects the promise.
  */
 
 /**
  * Creates a promise capability object.
  * @alias createPromiseCapability
  *
  * @return {PromiseCapability} A capability object contains:
@@ -3119,18 +3119,18 @@ function createPromiseCapability() {
     capability.reject = reject;
   });
   return capability;
 }
 
 /**
  * Polyfill for Promises:
  * The following promise implementation tries to generally implement the
- * Promise/A+ spec. Some notable differences from other promise libaries are:
- * - There currently isn't a seperate deferred and promise object.
+ * Promise/A+ spec. Some notable differences from other promise libraries are:
+ * - There currently isn't a separate deferred and promise object.
  * - Unhandled rejections eventually show an error if they aren't handled.
  *
  * Based off of the work in:
  * https://bugzilla.mozilla.org/show_bug.cgi?id=810490
  */
 (function PromiseClosure() {
   if (globalScope.Promise) {
     // Promises existing in the DOM/Worker, checking presence of all/resolve
@@ -4293,17 +4293,17 @@ var CFFParser = (function CFFParserClosu
               var left = bytes[pos++];
               for (var j = start; j <= start + left; j++) {
                 encoding[j] = gid++;
               }
             }
             break;
 
           default:
-            error('Unknow encoding format: ' + format + ' in CFF');
+            error('Unknown encoding format: ' + format + ' in CFF');
             break;
         }
         var dataEnd = pos;
         if (format & 0x80) {
           // The font sanitizer does not support CFF encoding with a
           // supplement, since the encoding is not really used to map
           // between gid to glyph, let's overwrite what is declared in
           // the top dictionary to let the sanitizer think the font use
@@ -4766,17 +4766,17 @@ var CFFCompiler = (function CFFCompilerC
       var topDictTracker = compiled.trackers[0];
 
       var stringIndex = this.compileStringIndex(cff.strings.strings);
       output.add(stringIndex);
 
       var globalSubrIndex = this.compileIndex(cff.globalSubrIndex);
       output.add(globalSubrIndex);
 
-      // Now start on the other entries that have no specfic order.
+      // Now start on the other entries that have no specific order.
       if (cff.encoding && cff.topDict.hasName('Encoding')) {
         if (cff.encoding.predefined) {
           topDictTracker.setEntryLocation('Encoding', [cff.encoding.format],
                                           output);
         } else {
           var encoding = this.compileEncoding(cff.encoding);
           topDictTracker.setEntryLocation('Encoding', [output.length], output);
           output.add(encoding);
@@ -12528,23 +12528,23 @@ var JpxImage = (function JpxImageClosure
           mu = spqcds[b].mu;
           epsilon = spqcds[b].epsilon;
           b++;
         }
 
         var subband = resolution.subbands[j];
         var gainLog2 = SubbandsGainLog2[subband.type];
 
-        // calulate quantization coefficient (Section E.1.1.1)
+        // calculate quantization coefficient (Section E.1.1.1)
         var delta = (reversible ? 1 :
           Math.pow(2, precision + gainLog2 - epsilon) * (1 + mu / 2048));
         var mb = (guardBits + epsilon - 1);
 
         // In the first resolution level, copyCoefficients will fill the
-        // whole array with coefficients. In the succeding passes,
+        // whole array with coefficients. In the succeeding passes,
         // copyCoefficients will consecutively fill in the values that belong
         // to the interleaved positions of the HL, LH, and HH coefficients.
         // The LL coefficients will then be interleaved in Transform.iterate().
         copyCoefficients(coefficients, width, height, subband, delta, mb,
                          reversible, segmentationSymbolUsed);
       }
       subbandCoefficients.push({
         width: width,
@@ -16373,17 +16373,17 @@ exports.getMetrics = getMetrics;
   {
     factory((root.pdfjsCoreMurmurHash3 = {}), root.pdfjsSharedUtil);
   }
 }(this, function (exports, sharedUtil) {
 
 var Uint32ArrayView = sharedUtil.Uint32ArrayView;
 
 var MurmurHash3_64 = (function MurmurHash3_64Closure (seed) {
-  // Workaround for missing math precison in JS.
+  // Workaround for missing math precision in JS.
   var MASK_HIGH = 0xffff0000;
   var MASK_LOW = 0xffff;
 
   function MurmurHash3_64 (seed) {
     var SEED = 0xc3d2e1f0;
     this.h1 = seed ? seed & 0xffffffff : SEED;
     this.h2 = seed ? seed & 0xffffffff : SEED;
   }
@@ -24776,17 +24776,17 @@ var Lexer = (function LexerClosure() {
             divideBy = 1;
           } else {
             // A number can have only one '.'
             break;
           }
         } else if (ch === 0x2D) { // '-'
           // ignore minus signs in the middle of numbers to match
           // Adobe's behavior
-          warn('Badly formated number');
+          warn('Badly formatted number');
         } else if (ch === 0x45 || ch === 0x65) { // 'E', 'e'
           // 'E' can be either a scientific notation or the beginning of a new
           // operator
           ch = this.peekChar();
           if (ch === 0x2B || ch === 0x2D) { // '+', '-'
             powerValueSign = (ch === 0x2D) ? -1 : 1;
             this.nextChar(); // Consume the sign character
           } else if (ch < 0x30 || ch > 0x39) { // '0' - '9'
@@ -25857,19 +25857,21 @@ exports.Type1Parser = Type1Parser;
   {
     factory((root.pdfjsCoreCMap = {}), root.pdfjsSharedUtil,
       root.pdfjsCorePrimitives, root.pdfjsCoreStream, root.pdfjsCoreParser);
   }
 }(this, function (exports, sharedUtil, corePrimitives, coreStream, coreParser) {
 
 var Util = sharedUtil.Util;
 var assert = sharedUtil.assert;
+var warn = sharedUtil.warn;
 var error = sharedUtil.error;
 var isInt = sharedUtil.isInt;
 var isString = sharedUtil.isString;
+var MissingDataException = sharedUtil.MissingDataException;
 var isName = corePrimitives.isName;
 var isCmd = corePrimitives.isCmd;
 var isStream = corePrimitives.isStream;
 var StringStream = coreStream.StringStream;
 var Lexer = coreParser.Lexer;
 var isEOF = coreParser.isEOF;
 
 var BUILT_IN_CMAPS = [
@@ -26707,64 +26709,71 @@ var CMapFactory = (function CMapFactoryC
       cMap.name = obj.name;
     }
   }
 
   function parseCMap(cMap, lexer, builtInCMapParams, useCMap) {
     var previous;
     var embededUseCMap;
     objLoop: while (true) {
-      var obj = lexer.getObj();
-      if (isEOF(obj)) {
-        break;
-      } else if (isName(obj)) {
-        if (obj.name === 'WMode') {
-          parseWMode(cMap, lexer);
-        } else if (obj.name === 'CMapName') {
-          parseCMapName(cMap, lexer);
-        }
-        previous = obj;
-      } else if (isCmd(obj)) {
-        switch (obj.cmd) {
-          case 'endcmap':
-            break objLoop;
-          case 'usecmap':
-            if (isName(previous)) {
-              embededUseCMap = previous.name;
-            }
-            break;
-          case 'begincodespacerange':
-            parseCodespaceRange(cMap, lexer);
-            break;
-          case 'beginbfchar':
-            parseBfChar(cMap, lexer);
-            break;
-          case 'begincidchar':
-            parseCidChar(cMap, lexer);
-            break;
-          case 'beginbfrange':
-            parseBfRange(cMap, lexer);
-            break;
-          case 'begincidrange':
-            parseCidRange(cMap, lexer);
-            break;
-        }
+      try {
+        var obj = lexer.getObj();
+        if (isEOF(obj)) {
+          break;
+        } else if (isName(obj)) {
+          if (obj.name === 'WMode') {
+            parseWMode(cMap, lexer);
+          } else if (obj.name === 'CMapName') {
+            parseCMapName(cMap, lexer);
+          }
+          previous = obj;
+        } else if (isCmd(obj)) {
+          switch (obj.cmd) {
+            case 'endcmap':
+              break objLoop;
+            case 'usecmap':
+              if (isName(previous)) {
+                embededUseCMap = previous.name;
+              }
+              break;
+            case 'begincodespacerange':
+              parseCodespaceRange(cMap, lexer);
+              break;
+            case 'beginbfchar':
+              parseBfChar(cMap, lexer);
+              break;
+            case 'begincidchar':
+              parseCidChar(cMap, lexer);
+              break;
+            case 'beginbfrange':
+              parseBfRange(cMap, lexer);
+              break;
+            case 'begincidrange':
+              parseCidRange(cMap, lexer);
+              break;
+          }
+        }
+      } catch (ex) {
+        if (ex instanceof MissingDataException) {
+          throw ex;
+        }
+        warn('Invalid cMap data: ' + ex);
+        continue;
       }
     }
 
     if (!useCMap && embededUseCMap) {
       // Load the usecmap definition from the file only if there wasn't one
       // specified.
       useCMap = embededUseCMap;
     }
     if (useCMap) {
       return extendCMap(cMap, builtInCMapParams, useCMap);
-    } else {
-      return Promise.resolve(cMap);
-    }
+    }
+    return Promise.resolve(cMap);
   }
 
   function extendCMap(cMap, builtInCMapParams, useCMap) {
     return createBuiltInCMap(useCMap, builtInCMapParams).then(
         function(newCMap) {
       cMap.useCMap = newCMap;
       // If there aren't any code space ranges defined clone all the parent ones
       // into this cMap.
@@ -26816,18 +26825,16 @@ var CMapFactory = (function CMapFactoryC
       request.onreadystatechange = function () {
         if (request.readyState === XMLHttpRequest.DONE) {
           if (request.status === 200 || request.status === 0) {
             var cMap = new CMap(true);
             var lexer = new Lexer(new StringStream(request.responseText));
             parseCMap(cMap, lexer, builtInCMapParams, null).then(
                 function (parsedCMap) {
               resolve(parsedCMap);
-            }).catch(function (e) {
-              reject(new Error({ message: 'Invalid CMap data', error: e }));
             });
           } else {
             reject(new Error('Unable to get cMap at: ' + url));
           }
         }
       };
       request.open('GET', url, true);
       request.send(null);
@@ -27285,16 +27292,17 @@ var ProblematicCharRanges = new Int32Arr
   // Control characters.
   0x0000, 0x0020,
   0x007F, 0x00A1,
   0x00AD, 0x00AE,
   // Chars that is used in complex-script shaping.
   0x0600, 0x0780,
   0x08A0, 0x10A0,
   0x1780, 0x1800,
+  0x1C00, 0x1C50,
   // General punctuation chars.
   0x2000, 0x2010,
   0x2011, 0x2012,
   0x2028, 0x2030,
   0x205F, 0x2070,
   0x25CC, 0x25CD,
   0x3000, 0x3001,
   // Chars that is used in complex-script shaping.
@@ -29465,17 +29473,17 @@ var Font = (function FontClosure() {
       // Maximum profile
       builder.addTable('maxp',
             '\x00\x00\x50\x00' + // Version number
             string16(numGlyphs)); // Num of glyphs
 
       // Naming tables
       builder.addTable('name', createNameTable(fontName));
 
-      // PostScript informations
+      // PostScript information
       builder.addTable('post', createPostTable(properties));
 
       return builder.toArray();
     },
 
     get spaceWidth() {
       if ('_shadowWidth' in this) {
         return this._shadowWidth;
@@ -29842,17 +29850,17 @@ var Type1Font = (function Type1FontClosu
     var pfbHeader = file.peekBytes(PFB_HEADER_SIZE);
     var pfbHeaderPresent = pfbHeader[0] === 0x80 && pfbHeader[1] === 0x01;
     if (pfbHeaderPresent) {
       file.skip(PFB_HEADER_SIZE);
       headerBlockLength = (pfbHeader[5] << 24) | (pfbHeader[4] << 16) |
                           (pfbHeader[3] << 8) | pfbHeader[2];
     }
 
-    // Get the data block containing glyphs and subrs informations
+    // Get the data block containing glyphs and subrs information
     var headerBlock = getHeaderBlock(file, headerBlockLength);
     headerBlockLength = headerBlock.length;
     var headerBlockParser = new Type1Parser(headerBlock.stream, false,
                                             SEAC_ANALYSIS_ENABLED);
     headerBlockParser.extractFontHeader(properties);
 
     if (pfbHeaderPresent) {
       pfbHeader = file.getBytes(PFB_HEADER_SIZE);
@@ -30709,17 +30717,17 @@ var PDFFunction = (function PDFFunctionC
           } else if (v < min) {
             v = min;
           }
           return v;
         };
 
         // clip to domain
         var v = clip(src[srcOffset], domain[0], domain[1]);
-        // calulate which bound the value is in
+        // calculate which bound the value is in
         for (var i = 0, ii = bounds.length; i < ii; ++i) {
           if (v < bounds[i]) {
             break;
           }
         }
 
         // encode value into domain of function
         var dmin = domain[0];
@@ -33152,17 +33160,17 @@ var PDFImage = (function PDFImageClosure
             output[i + 3] = (buf >> 4) & 1;
             output[i + 4] = (buf >> 3) & 1;
             output[i + 5] = (buf >> 2) & 1;
             output[i + 6] = (buf >> 1) & 1;
             output[i + 7] = buf & 1;
             i += 8;
           }
 
-          // handle remaing bits
+          // handle remaining bits
           if (i < loop2End) {
             buf = buffer[bufferPos++];
             mask = 128;
             while (i < loop2End) {
               output[i++] = +!!(buf & mask);
               mask >>= 1;
             }
           }
@@ -33220,17 +33228,17 @@ var PDFImage = (function PDFImageClosure
             alphaBuf[i] = 255 - alphaBuf[i];
           }
 
           if (sw !== width || sh !== height) {
             alphaBuf = resizeImageMask(alphaBuf, mask.bpc, sw, sh,
                                        width, height);
           }
         } else if (isArray(mask)) {
-          // Color key mask: if any of the compontents are outside the range
+          // Color key mask: if any of the components are outside the range
           // then they should be painted.
           alphaBuf = new Uint8Array(width * height);
           var numComps = this.numComps;
           for (i = 0, ii = width * height; i < ii; ++i) {
             var opacity = 0;
             var imageOffset = i * numComps;
             for (j = 0; j < numComps; ++j) {
               var color = image[imageOffset + j];
@@ -38053,17 +38061,17 @@ var PartialEvaluator = (function Partial
                                                           properties);
             return new Font(baseFontName, null, properties);
           }.bind(this));
         }
       }
 
       // According to the spec if 'FontDescriptor' is declared, 'FirstChar',
       // 'LastChar' and 'Widths' should exist too, but some PDF encoders seem
-      // to ignore this rule when a variant of a standart font is used.
+      // to ignore this rule when a variant of a standard font is used.
       // TODO Fill the width array depending on which of the base font this is
       // a variant.
       var firstChar = (dict.get('FirstChar') || 0);
       var lastChar = (dict.get('LastChar') || maxCharIndex);
 
       var fontName = descriptor.get('FontName');
       var baseFont = dict.get('BaseFont');
       // Some bad PDFs have a string as the font name.
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -573,17 +573,17 @@ var PDFBug = (function PDFBugClosure() {
         panels.appendChild(panel);
         tool.panel = panel;
         tool.manager = this;
         if (tool.enabled) {
           tool.init(pdfjsLib);
         } else {
           panel.textContent = tool.name + ' is disabled. To enable add ' +
                               ' "' + tool.id + '" to the pdfBug parameter ' +
-                              'and refresh (seperate multiple by commas).';
+                              'and refresh (separate multiple by commas).';
         }
         buttons.push(panelButton);
       }
       this.selectPanel(0);
     },
     cleanup: function cleanup() {
       for (var i = 0, ii = this.tools.length; i < ii; i++) {
         if (this.tools[i].enabled) {
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -5544,17 +5544,17 @@ var PDFPageView = (function PDFPageViewC
         };
 
         pdfPage.render(renderContext).promise.then(function() {
           // Tell the printEngine that rendering this canvas/page has finished.
           obj.done();
         }, function(error) {
           console.error(error);
           // Tell the printEngine that rendering this canvas/page has failed.
-          // This will make the print proces stop.
+          // This will make the print process stop.
           if ('abort' in obj) {
             obj.abort();
           } else {
             obj.done();
           }
         });
       };
     },
@@ -6341,32 +6341,44 @@ var PDFViewer = (function pdfViewer() {
     get pagesCount() {
       return this._pages.length;
     },
 
     getPageView: function (index) {
       return this._pages[index];
     },
 
+    /**
+     * @returns {number}
+     */
     get currentPageNumber() {
       return this._currentPageNumber;
     },
 
+    /**
+     * @param {number} val - The page number.
+     */
     set currentPageNumber(val) {
       if (!this.pdfDocument) {
         this._currentPageNumber = val;
         return;
       }
-      this._setCurrentPageNumber(val);
       // The intent can be to just reset a scroll position and/or scale.
-      this._resetCurrentPageView();
-    },
-
-    _setCurrentPageNumber: function pdfViewer_setCurrentPageNumber(val) {
+      this._setCurrentPageNumber(val, /* resetCurrentPageView = */ true);
+    },
+
+    /**
+     * @private
+     */
+    _setCurrentPageNumber:
+        function pdfViewer_setCurrentPageNumber(val, resetCurrentPageView) {
       if (this._currentPageNumber === val) {
+        if (resetCurrentPageView) {
+          this._resetCurrentPageView();
+        }
         return;
       }
       var arg;
       if (!(0 < val && val <= this.pagesCount)) {
         arg = {
           source: this,
           pageNumber: this._currentPageNumber,
           previousPageNumber: val
@@ -6379,16 +6391,20 @@ var PDFViewer = (function pdfViewer() {
       arg = {
         source: this,
         pageNumber: val,
         previousPageNumber: this._currentPageNumber
       };
       this._currentPageNumber = val;
       this.eventBus.dispatch('pagechanging', arg);
       this.eventBus.dispatch('pagechange', arg);
+
+      if (resetCurrentPageView) {
+        this._resetCurrentPageView();
+      }
     },
 
     /**
      * @returns {number}
      */
     get currentScale() {
       return this._currentScale !== UNKNOWN_SCALE ? this._currentScale :
                                                     DEFAULT_SCALE;
@@ -6686,16 +6702,17 @@ var PDFViewer = (function pdfViewer() {
             return;
         }
         this._setScaleUpdatePages(scale, value, noScroll, true);
       }
     },
 
     /**
      * Refreshes page view: scrolls to the current page and updates the scale.
+     * @private
      */
     _resetCurrentPageView: function () {
       if (this.isInPresentationMode) {
         // Fixes the case when PDF has different page sizes.
         this._setScale(this._currentScaleValue, true);
       }
 
       var pageView = this._pages[this._currentPageNumber - 1];
@@ -6710,18 +6727,17 @@ var PDFViewer = (function pdfViewer() {
      */
     scrollPageIntoView: function PDFViewer_scrollPageIntoView(pageNumber,
                                                               dest) {
       if (!this.pdfDocument) {
         return;
       }
 
       if (this.isInPresentationMode || !dest) {
-        this._setCurrentPageNumber(pageNumber);
-        this._resetCurrentPageView();
+        this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView */ true);
         return;
       }
 
       var pageView = this._pages[pageNumber - 1];
       var x = 0, y = 0;
       var width = 0, height = 0, widthScale, heightScale;
       var changeOrientation = (pageView.rotation % 180 === 0 ? false : true);
       var pageWidth = (changeOrientation ? pageView.height : pageView.width) /
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -41,16 +41,17 @@ PluginContent.prototype = {
     global.addEventListener("PluginBindingAttached", this, true, true);
     global.addEventListener("PluginCrashed",         this, true);
     global.addEventListener("PluginOutdated",        this, true);
     global.addEventListener("PluginInstantiated",    this, true);
     global.addEventListener("PluginRemoved",         this, true);
     global.addEventListener("pagehide",              this, true);
     global.addEventListener("pageshow",              this, true);
     global.addEventListener("unload",                this);
+    global.addEventListener("HiddenPlugin",          this, true);
 
     global.addMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.addMessageListener("BrowserPlugins:NotificationShown", this);
     global.addMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.addMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.addMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.addMessageListener("BrowserPlugins:Test:ClearCrashData", this);
   },
@@ -61,16 +62,17 @@ PluginContent.prototype = {
     global.removeEventListener("PluginBindingAttached", this, true);
     global.removeEventListener("PluginCrashed",         this, true);
     global.removeEventListener("PluginOutdated",        this, true);
     global.removeEventListener("PluginInstantiated",    this, true);
     global.removeEventListener("PluginRemoved",         this, true);
     global.removeEventListener("pagehide",              this, true);
     global.removeEventListener("pageshow",              this, true);
     global.removeEventListener("unload",                this);
+    global.removeEventListener("HiddenPlugin",          this, true);
 
     global.removeMessageListener("BrowserPlugins:ActivatePlugins", this);
     global.removeMessageListener("BrowserPlugins:NotificationShown", this);
     global.removeMessageListener("BrowserPlugins:ContextMenuCommand", this);
     global.removeMessageListener("BrowserPlugins:NPAPIPluginProcessCrashed", this);
     global.removeMessageListener("BrowserPlugins:CrashReportSubmitted", this);
     global.removeMessageListener("BrowserPlugins:Test:ClearCrashData", this);
     delete this.global;
@@ -189,16 +191,55 @@ PluginContent.prototype = {
              pluginName: pluginName,
              pluginTag: pluginTag,
              permissionString: permissionString,
              fallbackType: fallbackType,
              blocklistState: blocklistState,
            };
   },
 
+  _getPluginInfoForTag: function (pluginTag, tagMimetype) {
+    let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+
+    let pluginName = gNavigatorBundle.GetStringFromName("pluginInfo.unknownPlugin");
+    let permissionString = null;
+    let blocklistState = null;
+
+    if (pluginTag) {
+      pluginName = BrowserUtils.makeNicePluginName(pluginTag.name);
+
+      permissionString = pluginHost.getPermissionStringForTag(pluginTag);
+      blocklistState = pluginTag.blocklistState;
+
+      // Convert this from nsIPluginTag so it can be serialized.
+      let properties = ["name", "description", "filename", "version", "enabledState", "niceName"];
+      let pluginTagCopy = {};
+      for (let prop of properties) {
+        pluginTagCopy[prop] = pluginTag[prop];
+      }
+      pluginTag = pluginTagCopy;
+
+      // Make state-softblocked == state-notblocked for our purposes,
+      // they have the same UI. STATE_OUTDATED should not exist for plugin
+      // items, but let's alias it anyway, just in case.
+      if (blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED ||
+          blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
+        blocklistState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+    }
+
+    return { mimetype: tagMimetype,
+             pluginName: pluginName,
+             pluginTag: pluginTag,
+             permissionString: permissionString,
+             fallbackType: null,
+             blocklistState: blocklistState,
+           };
+  },
+
   /**
    * Update the visibility of the plugin overlay.
    */
   setVisibility : function (plugin, overlay, shouldShow) {
     overlay.classList.toggle("visible", shouldShow);
     if (shouldShow) {
       overlay.removeAttribute("dismissed");
     }
@@ -348,16 +389,24 @@ PluginContent.prototype = {
     if (eventType == "PluginCrashed" &&
         !(event.target instanceof Ci.nsIObjectLoadingContent)) {
       // If the event target is not a plugin object (i.e., an <object> or
       // <embed> element), this call is for a window-global plugin.
       this.onPluginCrashed(event.target, event);
       return;
     }
 
+    if (eventType == "HiddenPlugin") {
+      let pluginTag = event.tag.QueryInterface(Ci.nsIPluginTag);
+      if (event.target.defaultView.top.document != this.content.document) {
+	return;
+      }
+      this._showClickToPlayNotification(pluginTag, true);
+    }
+
     let plugin = event.target;
     let doc = plugin.ownerDocument;
 
     if (!(plugin instanceof Ci.nsIObjectLoadingContent))
       return;
 
     if (eventType == "PluginBindingAttached") {
       // The plugin binding fires this event when it is created.
@@ -708,17 +757,23 @@ PluginContent.prototype = {
     }
 
     let pluginData = this.pluginData;
 
     let principal = this.content.document.nodePrincipal;
     let location = this.content.document.location.href;
 
     for (let p of plugins) {
-      let pluginInfo = this._getPluginInfo(p);
+      let pluginInfo;
+      if (p instanceof Ci.nsIPluginTag) {
+        let mimeType = p.getMimeTypes() > 0 ? p.getMimeTypes()[0] : null;
+        pluginInfo = this._getPluginInfoForTag(p, mimeType);
+      } else {
+        pluginInfo = this._getPluginInfo(p);
+      }
       if (pluginInfo.permissionString === null) {
         Cu.reportError("No permission string for active plugin.");
         continue;
       }
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
--- a/build/mozconfig.rust
+++ b/build/mozconfig.rust
@@ -1,9 +1,10 @@
 # Options to enable rust in automation builds.
 
 # Tell configure to use the tooltool rustc.
 # Assume this is compiled with --enable-rpath so we don't
 # have to set LD_LIBRARY_PATH.
 RUSTC="$topsrcdir/rustc/bin/rustc"
+CARGO="$topsrcdir/cargo/bin/cargo"
 
 # Enable rust in the build.
 ac_add_options --enable-rust
--- a/config/system-headers
+++ b/config/system-headers
@@ -493,16 +493,17 @@ gdk-pixbuf/gdk-pixbuf.h
 Gestalt.h
 getopt.h
 glibconfig.h
 glib.h
 glib-object.h
 gmodule.h
 gnome.h
 gnu/libc-version.h
+gps.h
 grp.h
 gssapi_generic.h
 gssapi/gssapi_generic.h
 gssapi/gssapi.h
 gssapi.h
 gtk/gtk.h
 gtk/gtkx.h
 gtk/gtkunixprint.h
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -93,18 +93,17 @@ Tools.inspector = {
   panelLabel: l10n("inspector.panelLabel", inspectorStrings),
   get tooltip() {
     return l10n("inspector.tooltip2", inspectorStrings,
     (osString == "Darwin" ? "Cmd+Opt+" : "Ctrl+Shift+") + this.key);
   },
   inMenu: true,
   commands: [
     "devtools/client/responsivedesign/resize-commands",
-    "devtools/client/inspector/inspector-commands",
-    "devtools/client/eyedropper/commands.js"
+    "devtools/client/inspector/inspector-commands"
   ],
 
   preventClosingOnKey: true,
   onkey: function (panel, toolbox) {
     toolbox.highlighterUtils.togglePicker();
   },
 
   isTargetSupported: function (target) {
deleted file mode 100644
--- a/devtools/client/eyedropper/commands.js
+++ /dev/null
@@ -1,57 +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/. */
-
-const l10n = require("gcli/l10n");
-const EventEmitter = require("devtools/shared/event-emitter");
-const eventEmitter = new EventEmitter();
-
-var { Eyedropper, EyedropperManager } = require("devtools/client/eyedropper/eyedropper");
-
-/**
- * 'eyedropper' command
- */
-exports.items = [{
-  item: "command",
-  runAt: "client",
-  name: "eyedropper",
-  description: l10n.lookup("eyedropperDesc"),
-  manual: l10n.lookup("eyedropperManual"),
-  buttonId: "command-button-eyedropper",
-  buttonClass: "command-button command-button-invertable",
-  tooltipText: l10n.lookup("eyedropperTooltip"),
-  state: {
-    isChecked: function (target) {
-      if (!target.tab) {
-        return false;
-      }
-      let chromeWindow = target.tab.ownerDocument.defaultView;
-      let dropper = EyedropperManager.getInstance(chromeWindow);
-      if (dropper) {
-        return true;
-      }
-      return false;
-    },
-    onChange: function (target, changeHandler) {
-      eventEmitter.on("changed", changeHandler);
-    },
-    offChange: function (target, changeHandler) {
-      eventEmitter.off("changed", changeHandler);
-    },
-  },
-  exec: function (args, context) {
-    let chromeWindow = context.environment.chromeWindow;
-    let target = context.environment.target;
-
-    let dropper = EyedropperManager.createInstance(chromeWindow,
-                                                   { context: "command",
-                                                     copyOnSelect: true });
-    dropper.open();
-
-    eventEmitter.emit("changed", { target: target });
-
-    dropper.once("destroy", () => {
-      eventEmitter.emit("changed", { target: target });
-    });
-  }
-}];
deleted file mode 100644
--- a/devtools/client/eyedropper/crosshairs.css
+++ /dev/null
@@ -1,3 +0,0 @@
-* {
-  cursor: crosshair !important;
-}
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/eyedropper/eyedropper-child.js
+++ /dev/null
@@ -1,24 +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/. */
-
-var { interfaces: Ci } = Components;
-
-addMessageListener("Eyedropper:RequestContentScreenshot", sendContentScreenshot);
-
-function sendContentScreenshot() {
-  let canvas = content.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-  let scale = content.getInterface(Ci.nsIDOMWindowUtils).fullZoom;
-  let width = content.innerWidth;
-  let height = content.innerHeight;
-  canvas.width = width * scale;
-  canvas.height = height * scale;
-  canvas.mozOpaque = true;
-
-  let ctx = canvas.getContext("2d");
-
-  ctx.scale(scale, scale);
-  ctx.drawWindow(content, content.scrollX, content.scrollY, width, height, "#fff");
-
-  sendAsyncMessage("Eyedropper:Screenshot", canvas.toDataURL());
-}
deleted file mode 100644
--- a/devtools/client/eyedropper/eyedropper.js
+++ /dev/null
@@ -1,839 +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";
-
-const {Cc, Ci} = require("chrome");
-const {rgbToHsl, rgbToColorName} =
-      require("devtools/client/shared/css-color").colorUtils;
-const Telemetry = require("devtools/client/shared/telemetry");
-const EventEmitter = require("devtools/shared/event-emitter");
-const promise = require("promise");
-const defer = require("devtools/shared/defer");
-const Services = require("Services");
-
-loader.lazyGetter(this, "clipboardHelper", function () {
-  return Cc["@mozilla.org/widget/clipboardhelper;1"]
-    .getService(Ci.nsIClipboardHelper);
-});
-
-loader.lazyGetter(this, "ssService", function () {
-  return Cc["@mozilla.org/content/style-sheet-service;1"]
-    .getService(Ci.nsIStyleSheetService);
-});
-
-loader.lazyGetter(this, "ioService", function () {
-  return Cc["@mozilla.org/network/io-service;1"]
-    .getService(Ci.nsIIOService);
-});
-
-loader.lazyGetter(this, "DOMUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
-
-loader.lazyGetter(this, "l10n", () => Services.strings
-  .createBundle("chrome://devtools/locale/eyedropper.properties"));
-
-const EYEDROPPER_URL = "chrome://devtools/content/eyedropper/eyedropper.xul";
-const CROSSHAIRS_URL = "chrome://devtools/content/eyedropper/crosshairs.css";
-const NOCURSOR_URL = "chrome://devtools/content/eyedropper/nocursor.css";
-
-const ZOOM_PREF = "devtools.eyedropper.zoom";
-const FORMAT_PREF = "devtools.defaultColorUnit";
-
-const CANVAS_WIDTH = 96;
-const CANVAS_OFFSET = 3; // equals the border width of the canvas.
-const CLOSE_DELAY = 750;
-
-const HEX_BOX_WIDTH = CANVAS_WIDTH + CANVAS_OFFSET * 2;
-const HSL_BOX_WIDTH = 158;
-
-/**
- * Manage instances of eyedroppers for windows. Registering here isn't
- * necessary for creating an eyedropper, but can be used for testing.
- */
-var EyedropperManager = {
-  _instances: new WeakMap(),
-
-  getInstance: function (chromeWindow) {
-    return this._instances.get(chromeWindow);
-  },
-
-  createInstance: function (chromeWindow, options) {
-    let dropper = this.getInstance(chromeWindow);
-    if (dropper) {
-      return dropper;
-    }
-
-    dropper = new Eyedropper(chromeWindow, options);
-    this._instances.set(chromeWindow, dropper);
-
-    dropper.on("destroy", () => {
-      this.deleteInstance(chromeWindow);
-    });
-
-    return dropper;
-  },
-
-  deleteInstance: function (chromeWindow) {
-    this._instances.delete(chromeWindow);
-  }
-};
-
-exports.EyedropperManager = EyedropperManager;
-
-/**
- * Eyedropper widget. Once opened, shows zoomed area above current pixel and
- * displays the color value of the center pixel. Clicking on the window will
- * close the widget and fire a 'select' event. If 'copyOnSelect' is true, the color
- * will also be copied to the clipboard.
- *
- * let eyedropper = new Eyedropper(window);
- * eyedropper.open();
- *
- * eyedropper.once("select", (ev, color) => {
- *   console.log(color);  // "rgb(20, 50, 230)"
- * })
- *
- * @param {DOMWindow} chromeWindow
- *        window to inspect
- * @param {object} opts
- *        optional options object, with 'copyOnSelect', 'context'
- */
-function Eyedropper(chromeWindow, opts = { copyOnSelect: true, context: "other" }) {
-  this.copyOnSelect = opts.copyOnSelect;
-
-  this._onFirstMouseMove = this._onFirstMouseMove.bind(this);
-  this._onMouseMove = this._onMouseMove.bind(this);
-  this._onMouseDown = this._onMouseDown.bind(this);
-  this._onKeyDown = this._onKeyDown.bind(this);
-  this._onFrameLoaded = this._onFrameLoaded.bind(this);
-
-  this._chromeWindow = chromeWindow;
-  this._chromeDocument = chromeWindow.document;
-
-  this._OS = Services.appinfo.OS;
-
-  this._dragging = true;
-  this.loaded = false;
-
-  this._mouseMoveCounter = 0;
-
-  this.format = Services.prefs.getCharPref(FORMAT_PREF); // color value format
-  this.zoom = Services.prefs.getIntPref(ZOOM_PREF);      // zoom level - integer
-
-  this._zoomArea = {
-    x: 0,          // the left coordinate of the center of the inspected region
-    y: 0,          // the top coordinate of the center of the inspected region
-    width: CANVAS_WIDTH,      // width of canvas to draw zoomed area onto
-    height: CANVAS_WIDTH      // height of canvas
-  };
-
-  if (this._contentTab) {
-    let mm = this._contentTab.linkedBrowser.messageManager;
-    mm.loadFrameScript("resource://devtools/client/eyedropper/eyedropper-child.js", true);
-  }
-
-  // record if this was opened via the picker or standalone
-  var telemetry = new Telemetry();
-  if (opts.context == "command") {
-    telemetry.toolOpened("eyedropper");
-  }
-  else if (opts.context == "menu") {
-    telemetry.toolOpened("menueyedropper");
-  }
-  else if (opts.context == "picker") {
-    telemetry.toolOpened("pickereyedropper");
-  }
-
-  EventEmitter.decorate(this);
-}
-
-exports.Eyedropper = Eyedropper;
-
-Eyedropper.prototype = {
-  /**
-   * Get the number of cells (blown-up pixels) per direction in the grid.
-   */
-  get cellsWide() {
-    // Canvas will render whole "pixels" (cells) only, and an even
-    // number at that. Round up to the nearest even number of pixels.
-    let cellsWide = Math.ceil(this._zoomArea.width / this.zoom);
-    cellsWide += cellsWide % 2;
-
-    return cellsWide;
-  },
-
-  /**
-   * Get the size of each cell (blown-up pixel) in the grid.
-   */
-  get cellSize() {
-    return this._zoomArea.width / this.cellsWide;
-  },
-
-  /**
-   * Get index of cell in the center of the grid.
-   */
-  get centerCell() {
-    return Math.floor(this.cellsWide / 2);
-  },
-
-  /**
-   * Get color of center cell in the grid.
-   */
-  get centerColor() {
-    let x, y;
-    x = y = (this.centerCell * this.cellSize) + (this.cellSize / 2);
-    let rgb = this._ctx.getImageData(x, y, 1, 1).data;
-    return rgb;
-  },
-
-  get _contentTab() {
-    return this._chromeWindow.gBrowser && this._chromeWindow.gBrowser.selectedTab;
-  },
-
-  /**
-   * Fetch a screenshot of the content.
-   *
-   * @return {promise}
-   *         Promise that resolves with the screenshot as a dataURL
-   */
-  getContentScreenshot: function () {
-    if (!this._contentTab) {
-        return promise.resolve(null);
-    }
-
-    let deferred = defer();
-
-    let mm = this._contentTab.linkedBrowser.messageManager;
-    function onScreenshot(message) {
-      mm.removeMessageListener("Eyedropper:Screenshot", onScreenshot);
-      deferred.resolve(message.data);
-    }
-    mm.addMessageListener("Eyedropper:Screenshot", onScreenshot);
-    mm.sendAsyncMessage("Eyedropper:RequestContentScreenshot");
-
-    return deferred.promise;
-  },
-
-  /**
-   * Start the eyedropper. Add listeners for a mouse move in the window to
-   * show the eyedropper.
-   */
-  open: function () {
-    if (this.isOpen) {
-      // the eyedropper is aready open, don't create another panel.
-      return promise.resolve();
-    }
-
-    this.isOpen = true;
-
-    this._showCrosshairs();
-
-    // Get screenshot of content so we can inspect colors
-    return this.getContentScreenshot().then((dataURL) => {
-      // The data url may be null, e.g. if there is no content tab
-      if (dataURL) {
-        this._contentImage = new this._chromeWindow.Image();
-        this._contentImage.src = dataURL;
-
-        // Wait for screenshot to load
-        let imageLoaded = promise.defer();
-        this._contentImage.onload = imageLoaded.resolve
-        return imageLoaded.promise;
-      }
-    }).then(() => {
-      // Then start showing the eyedropper UI
-      this._chromeDocument.addEventListener("mousemove", this._onFirstMouseMove);
-      this.isStarted = true;
-      this.emit("started");
-    });
-  },
-
-  /**
-   * Called on the first mouse move over the window. Opens the eyedropper
-   * panel where the mouse is.
-   */
-  _onFirstMouseMove: function (event) {
-    this._chromeDocument.removeEventListener("mousemove", this._onFirstMouseMove);
-
-    this._panel = this._buildPanel();
-
-    let popupSet = this._chromeDocument.querySelector("#mainPopupSet");
-    popupSet.appendChild(this._panel);
-
-    let { panelX, panelY } = this._getPanelCoordinates(event);
-    this._panel.openPopupAtScreen(panelX, panelY);
-
-    this._setCoordinates(event);
-
-    this._addListeners();
-
-    // hide cursor as we'll be showing the panel over the mouse instead.
-    this._hideCrosshairs();
-    this._hideCursor();
-  },
-
-  /**
-   * Whether the coordinates are over the content or chrome.
-   *
-   * @param {number} clientX
-   *        x-coordinate of mouse relative to browser window.
-   * @param {number} clientY
-   *        y-coordinate of mouse relative to browser window.
-   */
-  _isInContent: function (clientX, clientY) {
-    let box = this._contentTab && this._contentTab.linkedBrowser.getBoundingClientRect();
-    if (box &&
-        clientX > box.left &&
-        clientX < box.right &&
-        clientY > box.top &&
-        clientY < box.bottom) {
-      return true;
-    }
-    return false;
-  },
-
-  /**
-   * Set the current coordinates to inspect from where a mousemove originated.
-   *
-   * @param {MouseEvent} event
-   *        Event for the mouse move.
-   */
-  _setCoordinates: function (event) {
-    let inContent = this._isInContent(event.clientX, event.clientY);
-    let win = this._chromeWindow;
-
-    // offset of mouse from browser window
-    let x = event.clientX;
-    let y = event.clientY;
-
-    if (inContent) {
-      // calculate the offset of the mouse from the content window
-      let box = this._contentTab.linkedBrowser.getBoundingClientRect();
-      x = x - box.left;
-      y = y - box.top;
-
-      this._zoomArea.contentWidth = box.width;
-      this._zoomArea.contentHeight = box.height;
-    }
-    this._zoomArea.inContent = inContent;
-
-    // don't let it inspect outside the browser window
-    x = Math.max(0, Math.min(x, win.outerWidth - 1));
-    y = Math.max(0, Math.min(y, win.outerHeight - 1));
-
-    this._zoomArea.x = x;
-    this._zoomArea.y = y;
-  },
-
-  /**
-   * Build and add a new eyedropper panel to the window.
-   *
-   * @return {Panel}
-   *         The XUL panel holding the eyedropper UI.
-   */
-  _buildPanel: function () {
-    let panel = this._chromeDocument.createElement("panel");
-    panel.setAttribute("noautofocus", true);
-    panel.setAttribute("noautohide", true);
-    panel.setAttribute("level", "floating");
-    panel.setAttribute("class", "devtools-eyedropper-panel");
-
-    let iframe = this._iframe = this._chromeDocument.createElement("iframe");
-    iframe.addEventListener("load", this._onFrameLoaded, true);
-    iframe.setAttribute("flex", "1");
-    iframe.setAttribute("transparent", "transparent");
-    iframe.setAttribute("allowTransparency", true);
-    iframe.setAttribute("class", "devtools-eyedropper-iframe");
-    iframe.setAttribute("src", EYEDROPPER_URL);
-    iframe.setAttribute("width", CANVAS_WIDTH);
-    iframe.setAttribute("height", CANVAS_WIDTH);
-
-    panel.appendChild(iframe);
-
-    return panel;
-  },
-
-  /**
-   * Event handler for the panel's iframe's load event. Emits
-   * a "load" event from this eyedropper object.
-   */
-  _onFrameLoaded: function () {
-    this._iframe.removeEventListener("load", this._onFrameLoaded, true);
-
-    this._iframeDocument = this._iframe.contentDocument;
-    this._colorPreview = this._iframeDocument.querySelector("#color-preview");
-    this._colorValue = this._iframeDocument.querySelector("#color-value");
-
-    // value box will be too long for hex values and too short for hsl
-    let valueBox = this._iframeDocument.querySelector("#color-value-box");
-    if (this.format == "hex") {
-      valueBox.style.width = HEX_BOX_WIDTH + "px";
-    }
-    else if (this.format == "hsl") {
-      valueBox.style.width = HSL_BOX_WIDTH + "px";
-    }
-
-    this._canvas = this._iframeDocument.querySelector("#canvas");
-    this._ctx = this._canvas.getContext("2d");
-
-    // so we preserve the clear pixel boundaries
-    this._ctx.mozImageSmoothingEnabled = false;
-
-    this._drawWindow();
-
-    this._addPanelListeners();
-    this._iframe.focus();
-
-    this.loaded = true;
-    this.emit("load");
-  },
-
-  /**
-   * Add key listeners to the panel.
-   */
-  _addPanelListeners: function () {
-    this._iframeDocument.addEventListener("keydown", this._onKeyDown);
-
-    let closeCmd = this._iframeDocument.getElementById("eyedropper-cmd-close");
-    closeCmd.addEventListener("command", this.destroy.bind(this), true);
-
-    let copyCmd = this._iframeDocument.getElementById("eyedropper-cmd-copy");
-    copyCmd.addEventListener("command", this.selectColor.bind(this), true);
-  },
-
-  /**
-   * Remove listeners from the panel.
-   */
-  _removePanelListeners: function () {
-    this._iframeDocument.removeEventListener("keydown", this._onKeyDown);
-  },
-
-  /**
-   * Add mouse event listeners to the document we're inspecting.
-   */
-  _addListeners: function () {
-    this._chromeDocument.addEventListener("mousemove", this._onMouseMove);
-    this._chromeDocument.addEventListener("mousedown", this._onMouseDown);
-  },
-
-  /**
-   * Remove mouse event listeners from the document we're inspecting.
-   */
-  _removeListeners: function () {
-    this._chromeDocument.removeEventListener("mousemove", this._onFirstMouseMove);
-    this._chromeDocument.removeEventListener("mousemove", this._onMouseMove);
-    this._chromeDocument.removeEventListener("mousedown", this._onMouseDown);
-  },
-
-  /**
-   * Hide the cursor.
-   */
-  _hideCursor: function () {
-    registerStyleSheet(NOCURSOR_URL);
-  },
-
-  /**
-   * Reset the cursor back to default.
-   */
-  _resetCursor: function () {
-    unregisterStyleSheet(NOCURSOR_URL);
-  },
-
-  /**
-   * Show a crosshairs as the mouse cursor
-   */
-  _showCrosshairs: function () {
-    registerStyleSheet(CROSSHAIRS_URL);
-  },
-
-  /**
-   * Reset cursor.
-   */
-  _hideCrosshairs: function () {
-    unregisterStyleSheet(CROSSHAIRS_URL);
-  },
-
-  /**
-   * Event handler for a mouse move over the page we're inspecting.
-   * Preview the area under the cursor, and move panel to be under the cursor.
-   *
-   * @param  {DOMEvent} event
-   *         MouseEvent for the mouse moving
-   */
-  _onMouseMove: function (event) {
-    if (!this._dragging || !this._panel || !this._canvas) {
-      return;
-    }
-
-    if (this._OS == "Linux" && ++this._mouseMoveCounter % 2 == 0) {
-      // skip every other mousemove to preserve performance.
-      return;
-    }
-
-    this._setCoordinates(event);
-    this._drawWindow();
-
-    let { panelX, panelY } = this._getPanelCoordinates(event);
-    this._movePanel(panelX, panelY);
-  },
-
-  /**
-   * Get coordinates of where the eyedropper panel should go based on
-   * the current coordinates of the mouse cursor.
-   *
-   * @param {MouseEvent} event
-   *        object with properties 'screenX' and 'screenY'
-   *
-   * @return {object}
-  *          object with properties 'panelX', 'panelY'
-   */
-  _getPanelCoordinates: function ({screenX, screenY}) {
-    let win = this._chromeWindow;
-    let offset = CANVAS_WIDTH / 2 + CANVAS_OFFSET;
-
-    let panelX = screenX - offset;
-    let windowX = win.screenX + (win.outerWidth - win.innerWidth);
-    let maxX = win.screenX + win.outerWidth - offset - 1;
-
-    let panelY = screenY - offset;
-    let windowY = win.screenY + (win.outerHeight - win.innerHeight);
-    let maxY = win.screenY + win.outerHeight - offset - 1;
-
-    // don't let the panel move outside the browser window
-    panelX = Math.max(windowX - offset, Math.min(panelX, maxX));
-    panelY = Math.max(windowY - offset, Math.min(panelY, maxY));
-
-    return { panelX: panelX, panelY: panelY };
-  },
-
-  /**
-   * Move the eyedropper panel to the given coordinates.
-   *
-   * @param  {number} screenX
-   *         left coordinate on the screen
-   * @param  {number} screenY
-   *         top coordinate
-   */
-  _movePanel: function (screenX, screenY) {
-    this._panelX = screenX;
-    this._panelY = screenY;
-
-    this._panel.moveTo(screenX, screenY);
-  },
-
-  /**
-   * Handler for the mouse down event on the inspected page. This means a
-   * click, so we'll select the color that's currently hovered.
-   *
-   * @param  {Event} event
-   *         DOM MouseEvent object
-   */
-  _onMouseDown: function (event) {
-    event.preventDefault();
-    event.stopPropagation();
-
-    this.selectColor();
-  },
-
-  /**
-   * Select the current color that's being previewed. Fire a
-   * "select" event with the color as an rgb string.
-   */
-  selectColor: function () {
-    if (this._isSelecting) {
-      return;
-    }
-    this._isSelecting = true;
-    this._dragging = false;
-
-    this.emit("select", this._colorValue.value);
-
-    if (this.copyOnSelect) {
-      this.copyColor(this.destroy.bind(this));
-    }
-    else {
-      this.destroy();
-    }
-  },
-
-  /**
-   * Copy the currently inspected color to the clipboard.
-   *
-   * @param  {Function} callback
-   *         Callback to be called when the color is in the clipboard.
-   */
-  copyColor: function (callback) {
-    clearTimeout(this._copyTimeout);
-
-    let color = this._colorValue.value;
-    clipboardHelper.copyString(color);
-
-    this._colorValue.classList.add("highlight");
-    this._colorValue.value = "✓ " + l10n.GetStringFromName("colorValue.copied");
-
-    this._copyTimeout = setTimeout(() => {
-      this._colorValue.classList.remove("highlight");
-      this._colorValue.value = color;
-
-      if (callback) {
-        callback();
-      }
-    }, CLOSE_DELAY);
-  },
-
-  /**
-   * Handler for the keydown event on the panel. Either copy the color
-   * or move the panel in a direction depending on the key pressed.
-   *
-   * @param  {Event} event
-   *         DOM KeyboardEvent object
-   */
-  _onKeyDown: function (event) {
-    if (event.metaKey && event.keyCode === event.DOM_VK_C) {
-      this.copyColor();
-      return;
-    }
-
-    let offsetX = 0;
-    let offsetY = 0;
-    let modifier = 1;
-
-    if (event.keyCode === event.DOM_VK_LEFT) {
-      offsetX = -1;
-    }
-    if (event.keyCode === event.DOM_VK_RIGHT) {
-      offsetX = 1;
-    }
-    if (event.keyCode === event.DOM_VK_UP) {
-      offsetY = -1;
-    }
-    if (event.keyCode === event.DOM_VK_DOWN) {
-      offsetY = 1;
-    }
-    if (event.shiftKey) {
-      modifier = 10;
-    }
-
-    offsetY *= modifier;
-    offsetX *= modifier;
-
-    if (offsetX !== 0 || offsetY !== 0) {
-      this._zoomArea.x += offsetX;
-      this._zoomArea.y += offsetY;
-
-      this._drawWindow();
-
-      this._movePanel(this._panelX + offsetX, this._panelY + offsetY);
-
-      event.preventDefault();
-    }
-  },
-
-  /**
-   * Draw the inspected area onto the canvas using the zoom level.
-   */
-  _drawWindow: function () {
-    let { width, height, x, y, inContent,
-          contentWidth, contentHeight } = this._zoomArea;
-
-    let zoomedWidth = width / this.zoom;
-    let zoomedHeight = height / this.zoom;
-
-    let leftX = x - (zoomedWidth / 2);
-    let topY = y - (zoomedHeight / 2);
-
-    // draw the portion of the window we're inspecting
-    if (inContent) {
-      // draw from content source image "s" to destination rect "d"
-      let sx = leftX;
-      let sy = topY;
-      let sw = zoomedWidth;
-      let sh = zoomedHeight;
-      let dx = 0;
-      let dy = 0;
-
-      // we're at the content edge, so we have to crop the drawing
-      if (leftX < 0) {
-        sx = 0;
-        sw = zoomedWidth + leftX;
-        dx = -leftX;
-      }
-      else if (leftX + zoomedWidth > contentWidth) {
-        sw = contentWidth - leftX;
-      }
-      if (topY < 0) {
-        sy = 0;
-        sh = zoomedHeight + topY;
-        dy = -topY;
-      }
-      else if (topY + zoomedHeight > contentHeight) {
-        sh = contentHeight - topY;
-      }
-      let dw = sw;
-      let dh = sh;
-
-      // we don't want artifacts when we're inspecting the edges of content
-      if (leftX < 0 || topY < 0 ||
-          leftX + zoomedWidth > contentWidth ||
-          topY + zoomedHeight > contentHeight) {
-        this._ctx.fillStyle = "white";
-        this._ctx.fillRect(0, 0, width, height);
-      }
-
-      // draw from the screenshot to the eyedropper canvas
-      this._ctx.drawImage(this._contentImage, sx, sy, sw,
-                          sh, dx, dy, dw, dh);
-    }
-    else {
-      // the mouse is over the chrome, so draw that instead of the content
-      this._ctx.drawWindow(this._chromeWindow, leftX, topY, zoomedWidth,
-                           zoomedHeight, "white");
-    }
-
-    // now scale it
-    this._ctx.drawImage(this._canvas, 0, 0, zoomedWidth, zoomedHeight,
-                                      0, 0, width, height);
-
-    let rgb = this.centerColor;
-    this._colorPreview.style.backgroundColor = toColorString(rgb, "rgb");
-    this._colorValue.value = toColorString(rgb, this.format);
-
-    if (this.zoom > 2) {
-      // grid at 2x is too busy
-      this._drawGrid();
-    }
-    this._drawCrosshair();
-  },
-
-  /**
-   * Draw a grid on the canvas representing pixel boundaries.
-   */
-  _drawGrid: function () {
-    let { width, height } = this._zoomArea;
-
-    this._ctx.lineWidth = 1;
-    this._ctx.strokeStyle = "rgba(143, 143, 143, 0.2)";
-
-    for (let i = 0; i < width; i += this.cellSize) {
-      this._ctx.beginPath();
-      this._ctx.moveTo(i - .5, 0);
-      this._ctx.lineTo(i - .5, height);
-      this._ctx.stroke();
-
-      this._ctx.beginPath();
-      this._ctx.moveTo(0, i - .5);
-      this._ctx.lineTo(width, i - .5);
-      this._ctx.stroke();
-    }
-  },
-
-  /**
-   * Draw a box on the canvas to highlight the center cell.
-   */
-  _drawCrosshair: function () {
-    let x, y;
-    x = y = this.centerCell * this.cellSize;
-
-    this._ctx.lineWidth = 1;
-    this._ctx.lineJoin = "miter";
-    this._ctx.strokeStyle = "rgba(0, 0, 0, 1)";
-    this._ctx.strokeRect(x - 1.5, y - 1.5, this.cellSize + 2, this.cellSize + 2);
-
-    this._ctx.strokeStyle = "rgba(255, 255, 255, 1)";
-    this._ctx.strokeRect(x - 0.5, y - 0.5, this.cellSize, this.cellSize);
-  },
-
-  /**
-   * Destroy the eyedropper and clean up. Emits a "destroy" event.
-   */
-  destroy: function () {
-    this._resetCursor();
-    this._hideCrosshairs();
-
-    if (this._panel) {
-      this._panel.hidePopup();
-      this._panel.remove();
-      this._panel = null;
-    }
-    this._removePanelListeners();
-    this._removeListeners();
-
-    this.isStarted = false;
-    this.isOpen = false;
-    this._isSelecting = false;
-
-    this.emit("destroy");
-  }
-};
-
-/**
- * Add a user style sheet that applies to all documents.
- */
-function registerStyleSheet(url) {
-  var uri = ioService.newURI(url, null, null);
-  if (!ssService.sheetRegistered(uri, ssService.AGENT_SHEET)) {
-    ssService.loadAndRegisterSheet(uri, ssService.AGENT_SHEET);
-  }
-}
-
-/**
- * Remove a user style sheet.
- */
-function unregisterStyleSheet(url) {
-  var uri = ioService.newURI(url, null, null);
-  if (ssService.sheetRegistered(uri, ssService.AGENT_SHEET)) {
-    ssService.unregisterSheet(uri, ssService.AGENT_SHEET);
-  }
-}
-
-/**
- * Get a formatted CSS color string from a color value.
- *
- * @param {array} rgb
- *        Rgb values of a color to format
- * @param {string} format
- *        Format of string. One of "hex", "rgb", "hsl", "name"
- *
- * @return {string}
- *        Formatted color value, e.g. "#FFF" or "hsl(20, 10%, 10%)"
- */
-function toColorString(rgb, format) {
-  let [r, g, b] = rgb;
-
-  switch (format) {
-    case "hex":
-      return hexString(rgb);
-    case "rgb":
-      return "rgb(" + r + ", " + g + ", " + b + ")";
-    case "hsl":
-      let [h, s, l] = rgbToHsl(rgb);
-      return "hsl(" + h + ", " + s + "%, " + l + "%)";
-    case "name":
-      let str;
-      try {
-        str = rgbToColorName(r, g, b);
-      } catch (e) {
-        str = hexString(rgb);
-      }
-      return str;
-    default:
-      return hexString(rgb);
-  }
-}
-
-/**
- * Produce a hex-formatted color string from rgb values.
- *
- * @param {array} rgb
- *        Rgb values of color to stringify
- *
- * @return {string}
- *        Hex formatted string for color, e.g. "#FFEE00"
- */
-function hexString([r, g, b]) {
-  let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
-  return "#" + val.toString(16).substr(-6).toUpperCase();
-}
deleted file mode 100644
--- a/devtools/client/eyedropper/eyedropper.xul
+++ /dev/null
@@ -1,44 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<!DOCTYPE window []>
-
-<?xml-stylesheet href="chrome://devtools/skin/common.css" type="text/css"?>
-<?xml-stylesheet href="chrome://devtools/skin/eyedropper.css" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        no-theme="true">
-  <script type="application/javascript;version=1.8"
-          src="chrome://devtools/content/shared/theme-switching.js"/>
-  <commandset id="eyedropper-commandset">
-    <command id="eyedropper-cmd-close"
-             oncommand="void(0);"/>
-    <command id="eyedropper-cmd-copy"
-             oncommand="void(0);"/>
-  </commandset>
-
-  <keyset id="eyedropper-keyset">
-    <key id="eyedropper-key-escape"
-         keycode="VK_ESCAPE"
-         command="eyedropper-cmd-close"/>
-    <key id="eyedropper-key-enter"
-         keycode="VK_RETURN"
-         command="eyedropper-cmd-copy"/>
-  </keyset>
-
-  <box id="canvas-overflow">
-    <canvas id="canvas" xmlns="http://www.w3.org/1999/xhtml" width="96" height="96">
-    </canvas>
-  </box>
-  <hbox id="color-value-container">
-    <hbox id="color-value-box">
-      <box id="color-preview">
-      </box>
-      <label id="color-value" class="devtools-monospace">
-      </label>
-    </hbox>
-  </hbox>
-</window>
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/eyedropper/moz.build
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DevToolsModules(
-    'commands.js',
-    'eyedropper-child.js',
-    'eyedropper.js'
-)
-
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
deleted file mode 100644
--- a/devtools/client/eyedropper/nocursor.css
+++ /dev/null
@@ -1,3 +0,0 @@
-* {
-  cursor: none !important;
-}
\ No newline at end of file
deleted file mode 100644
--- a/devtools/client/eyedropper/test/.eslintrc
+++ /dev/null
@@ -1,4 +0,0 @@
-{
-  // Extend from the shared list of defined globals for mochitests.
-  "extends": "../../../.eslintrc.mochitests"
-}
deleted file mode 100644
--- a/devtools/client/eyedropper/test/browser.ini
+++ /dev/null
@@ -1,13 +0,0 @@
-[DEFAULT]
-tags = devtools
-subsuite = clipboard
-support-files =
-  color-block.html
-  head.js
-  !/devtools/client/commandline/test/helpers.js
-  !/devtools/client/framework/test/shared-head.js
-
-[browser_eyedropper_basic.js]
-skip-if = os == "win" && debug # bug 963492
-[browser_eyedropper_cmd.js]
-skip-if = true # bug 1278400
deleted file mode 100644
--- a/devtools/client/eyedropper/test/browser_eyedropper_basic.js
+++ /dev/null
@@ -1,80 +0,0 @@
-/* vim: set ts=2 et sw=2 tw=80: */
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const TESTCASE_URI = CHROME_URL_ROOT + "color-block.html";
-const DIV_COLOR = "#0000FF";
-
-/**
- * Test basic eyedropper widget functionality:
- *  - Opening eyedropper and pressing ESC closes the eyedropper
- *  - Opening eyedropper and clicking copies the center color
- */
-add_task(function* () {
-  yield addTab(TESTCASE_URI);
-
-  info("added tab");
-
-  yield testEscape();
-
-  info("testing selecting a color");
-
-  yield testSelect();
-});
-
-function* testEscape() {
-  let dropper = new Eyedropper(window);
-
-  yield inspectPage(dropper, false);
-
-  let destroyed = dropper.once("destroy");
-  pressESC();
-  yield destroyed;
-
-  ok(true, "escape closed the eyedropper");
-}
-
-function* testSelect() {
-  let dropper = new Eyedropper(window);
-
-  let selected = dropper.once("select");
-  let copied = waitForClipboard(() => {}, DIV_COLOR);
-
-  yield inspectPage(dropper);
-
-  let color = yield selected;
-  is(color, DIV_COLOR, "correct color selected");
-
-  // wait for DIV_COLOR to be copied to the clipboard
-  yield copied;
-}
-
-/* Helpers */
-
-function* inspectPage(dropper, click = true) {
-  yield dropper.open();
-
-  info("dropper opened");
-
-  let target = document.documentElement;
-  let win = window;
-
-  // get location of the <div> in the content, offset from browser window
-  let box = gBrowser.selectedBrowser.getBoundingClientRect();
-  let x = box.left + 100;
-  let y = box.top + 100;
-
-  EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
-
-  yield dropperLoaded(dropper);
-
-  EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
-
-  if (click) {
-    EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
-  }
-}
-
-function pressESC() {
-  EventUtils.synthesizeKey("VK_ESCAPE", { });
-}
deleted file mode 100644
--- a/devtools/client/eyedropper/test/browser_eyedropper_cmd.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// Tests that the eyedropper command works
-
-const TESTCASE_URI = CHROME_URL_ROOT + "color-block.html";
-const DIV_COLOR = "#0000FF";
-
-function test() {
-  return Task.spawn(spawnTest).then(finish, helpers.handleError);
-}
-
-function* spawnTest() {
-  let options = yield helpers.openTab(TESTCASE_URI);
-  yield helpers.openToolbar(options);
-
-  yield helpers.audit(options, [
-    {
-      setup: "eyedropper",
-      check: {
-        input: "eyedropper"
-      },
-      exec: { output: "" }
-    },
-  ]);
-
-  yield inspectAndWaitForCopy();
-
-  yield helpers.closeToolbar(options);
-  yield helpers.closeTab(options);
-}
-
-function inspectAndWaitForCopy() {
-  let copied = waitForClipboard(() => {}, DIV_COLOR);
-  let ready = inspectPage(); // resolves once eyedropper is destroyed
-
-  return Promise.all([copied, ready]);
-}
-
-function inspectPage() {
-  let target = document.documentElement;
-  let win = window;
-
-  // get location of the <div> in the content, offset from browser window
-  let box = gBrowser.selectedBrowser.getBoundingClientRect();
-  let x = box.left + 100;
-  let y = box.top + 100;
-
-  let dropper = EyedropperManager.getInstance(window);
-
-  return dropperStarted(dropper).then(() => {
-    EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
-
-    return dropperLoaded(dropper).then(() => {
-      EventUtils.synthesizeMouse(target, x + 10, y + 10, { type: "mousemove" }, win);
-
-      EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
-      return dropper.once("destroy");
-    });
-  });
-}
deleted file mode 100644
--- a/devtools/client/eyedropper/test/color-block.html
+++ /dev/null
@@ -1,22 +0,0 @@
-<!doctype html>
-<html>
-<head>
-  <title>basic eyedropper test case</title>
-  <style type="text/css">
-  body {
-    background: #f99;
-  }
-
-  #test {
-    margin: 100px;
-    background-color: blue;
-    width: 20px;
-    height: 20px;
-  }
-  </style>
-</head>
-<body>
-  <div id="test">
-  </div>
-</body>
-</html>
deleted file mode 100644
--- a/devtools/client/eyedropper/test/head.js
+++ /dev/null
@@ -1,28 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-// shared-head.js handles imports, constants, and utility functions
-Services.scriptloader.loadSubScript("chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js", this);
-Services.scriptloader.loadSubScript(TEST_DIR + "../../../commandline/test/helpers.js", this);
-
-const { Eyedropper, EyedropperManager } = require("devtools/client/eyedropper/eyedropper");
-
-function waitForClipboard(setup, expected) {
-  let deferred = defer();
-  SimpleTest.waitForClipboard(expected, setup, deferred.resolve, deferred.reject);
-  return deferred.promise;
-}
-
-function dropperStarted(dropper) {
-  if (dropper.isStarted) {
-    return promise.resolve();
-  }
-  return dropper.once("started");
-}
-
-function dropperLoaded(dropper) {
-  if (dropper.loaded) {
-    return promise.resolve();
-  }
-  return dropper.once("load");
-}
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -88,17 +88,16 @@ const ToolboxButtons = exports.ToolboxBu
       return target.activeTab && target.activeTab.traits.frames;
     }
   },
   { id: "command-button-splitconsole",
     isTargetSupported: target => !target.isAddon },
   { id: "command-button-responsive" },
   { id: "command-button-paintflashing" },
   { id: "command-button-scratchpad" },
-  { id: "command-button-eyedropper" },
   { id: "command-button-screenshot" },
   { id: "command-button-rulers" },
   { id: "command-button-measure" },
   { id: "command-button-noautohide",
     isTargetSupported: target => target.chrome },
 ];
 
 /**
--- a/devtools/client/inspector/inspector-commands.js
+++ b/devtools/client/inspector/inspector-commands.js
@@ -1,17 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const l10n = require("gcli/l10n");
-loader.lazyRequireGetter(this, "gDevTools",
-                         "devtools/client/framework/devtools", true);
+loader.lazyRequireGetter(this, "gDevTools", "devtools/client/framework/devtools", true);
+const {EyeDropper, HighlighterEnvironment} = require("devtools/server/actors/highlighters");
+const Telemetry = require("devtools/client/shared/telemetry");
 
 exports.items = [{
   item: "command",
   runAt: "server",
   name: "inspect",
   description: l10n.lookup("inspectDesc"),
   manual: l10n.lookup("inspectManual"),
   params: [
@@ -23,9 +24,48 @@ exports.items = [{
     }
   ],
   exec: function (args, context) {
     let target = context.environment.target;
     return gDevTools.showToolbox(target, "inspector").then(toolbox => {
       toolbox.getCurrentPanel().selection.setNode(args.selector, "gcli");
     });
   }
+}, {
+  item: "command",
+  runAt: "client",
+  name: "eyedropper",
+  description: l10n.lookup("eyedropperDesc"),
+  manual: l10n.lookup("eyedropperManual"),
+  params: [{
+    // This hidden parameter is only set to true when the eyedropper browser menu item is
+    // used. It is useful to log a different telemetry event whether the tool was used
+    // from the menu, or from the gcli command line.
+    group: "hiddengroup",
+    params: [{
+      name: "frommenu",
+      type: "boolean",
+      hidden: true
+    }]
+  }],
+  exec: function (args, context) {
+    let telemetry = new Telemetry();
+    telemetry.toolOpened(args.frommenu ? "menueyedropper" : "eyedropper");
+    context.updateExec("eyedropper_server").catch(e => console.error(e));
+  }
+}, {
+  item: "command",
+  runAt: "server",
+  name: "eyedropper_server",
+  hidden: true,
+  exec: function (args, {environment}) {
+    let env = new HighlighterEnvironment();
+    env.initFromWindow(environment.window);
+    let eyeDropper = new EyeDropper(env);
+
+    eyeDropper.show(environment.document.documentElement, {copyOnSelect: true});
+
+    eyeDropper.once("hidden", () => {
+      eyeDropper.destroy();
+      env.destroy();
+    });
+  }
 }];
--- a/devtools/client/inspector/inspector-panel.js
+++ b/devtools/client/inspector/inspector-panel.js
@@ -15,16 +15,17 @@ var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var EventEmitter = require("devtools/shared/event-emitter");
 var clipboard = require("sdk/clipboard");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
 const {initCssProperties} = require("devtools/shared/fronts/css-properties");
 const nodeConstants = require("devtools/shared/dom-node-constants");
+const Telemetry = require("devtools/client/shared/telemetry");
 
 const Menu = require("devtools/client/framework/menu");
 const MenuItem = require("devtools/client/framework/menu-item");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
 loader.lazyRequireGetter(this, "ComputedViewTool", "devtools/client/inspector/computed/computed", true);
@@ -84,36 +85,31 @@ loader.lazyGetter(this, "clipboardHelper
  */
 function InspectorPanel(iframeWindow, toolbox) {
   this._toolbox = toolbox;
   this._target = toolbox._target;
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
   this.panelWin.inspector = this;
 
+  this.telemetry = new Telemetry();
+
   this.nodeMenuTriggerInfo = null;
 
   this._handleRejectionIfNotDestroyed = this._handleRejectionIfNotDestroyed.bind(this);
   this._onBeforeNavigate = this._onBeforeNavigate.bind(this);
   this.onNewRoot = this.onNewRoot.bind(this);
   this._onContextMenu = this._onContextMenu.bind(this);
   this._updateSearchResultsLabel = this._updateSearchResultsLabel.bind(this);
   this.onNewSelection = this.onNewSelection.bind(this);
   this.onBeforeNewSelection = this.onBeforeNewSelection.bind(this);
   this.onDetached = this.onDetached.bind(this);
   this.onPaneToggleButtonClicked = this.onPaneToggleButtonClicked.bind(this);
   this._onMarkupFrameLoad = this._onMarkupFrameLoad.bind(this);
 
-  let doc = this.panelDoc;
-
-  // Handle 'Add Node' toolbar button.
-  this.addNode = this.addNode.bind(this);
-  this.addNodeButton = doc.getElementById("inspector-element-add-button");
-  this.addNodeButton.addEventListener("click", this.addNode);
-
   this._target.on("will-navigate", this._onBeforeNavigate);
   this._detectingActorFeatures = this._detectActorFeatures();
 
   EventEmitter.decorate(this);
 }
 
 exports.InspectorPanel = InspectorPanel;
 
@@ -250,16 +246,17 @@ InspectorPanel.prototype = {
       this.markup.expandNode(this.selection.nodeFront);
 
       this.emit("ready");
       deferred.resolve(this);
     });
 
     this.setupSearchBox();
     this.setupSidebar();
+    this.setupToolbar();
 
     return deferred.promise;
   },
 
   _onBeforeNavigate: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
     this._destroyMarkup();
@@ -457,17 +454,16 @@ InspectorPanel.prototype = {
         "fontinspector",
         strings.GetStringFromName("inspector.sidebar.fontInspectorTitle"),
         defaultTab == "fontinspector");
 
       this.fontInspector = new FontInspector(this, this.panelWin);
       this.sidebar.toggleTab(true, "fontinspector");
     }
 
-    this.setupSidebarToggle();
     this.setupSidebarSize();
 
     this.sidebar.show(defaultTab);
   },
 
   /**
    * Sidebar size is currently driven by vbox.inspector-sidebar-container
    * element, which is located at the left/bottom side of the side bar splitter.
@@ -508,32 +504,60 @@ InspectorPanel.prototype = {
     this.sidebar.on("destroy", () => {
       Services.prefs.setIntPref("devtools.toolsidebar-width.inspector",
         sidePaneContainer.width);
       Services.prefs.setIntPref("devtools.toolsidebar-height.inspector",
         sidePaneContainer.height);
     });
   },
 
-  /**
-   * Add the expand/collapse behavior for the sidebar panel.
-   */
-  setupSidebarToggle: function () {
+  setupToolbar: function () {
+    // Setup the sidebar toggle button.
     let SidebarToggle = this.React.createFactory(this.browserRequire(
       "devtools/client/shared/components/sidebar-toggle"));
 
     let sidebarToggle = SidebarToggle({
       onClick: this.onPaneToggleButtonClicked,
       collapsed: false,
       expandPaneTitle: strings.GetStringFromName("inspector.expandPane"),
       collapsePaneTitle: strings.GetStringFromName("inspector.collapsePane"),
     });
 
     let parentBox = this.panelDoc.getElementById("inspector-sidebar-toggle-box");
     this._sidebarToggle = this.ReactDOM.render(sidebarToggle, parentBox);
+
+    // Setup the add-node button.
+    this.addNode = this.addNode.bind(this);
+    this.addNodeButton = this.panelDoc.getElementById("inspector-element-add-button");
+    this.addNodeButton.addEventListener("click", this.addNode);
+
+    // Setup the eye-dropper icon.
+    this.toolbox.target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
+      if (!value) {
+        return;
+      }
+
+      this.onEyeDropperDone = this.onEyeDropperDone.bind(this);
+      this.onEyeDropperButtonClicked = this.onEyeDropperButtonClicked.bind(this);
+      this.eyeDropperButton = this.panelDoc.getElementById("inspector-eyedropper-toggle");
+      this.eyeDropperButton.style.display = "initial";
+      this.eyeDropperButton.addEventListener("click", this.onEyeDropperButtonClicked);
+    }, e => console.error(e));
+  },
+
+  teardownToolbar: function () {
+    if (this.addNodeButton) {
+      this.addNodeButton.removeEventListener("click", this.addNode);
+      this.addNodeButton = null;
+    }
+
+    if (this.eyeDropperButton) {
+      this.eyeDropperButton.removeEventListener("click", this.onEyeDropperButtonClicked);
+      this.eyeDropperButton = null;
+    }
   },
 
   /**
    * Reset the inspector on new root mutation.
    */
   onNewRoot: function () {
     this._defaultNode = null;
     this.selection.setNodeFront(null);
@@ -763,17 +787,17 @@ InspectorPanel.prototype = {
         front.destroy();
       }
     });
 
     this.sidebar.off("select", this._setDefaultSidebar);
     let sidebarDestroyer = this.sidebar.destroy();
     this.sidebar = null;
 
-    this.addNodeButton.removeEventListener("click", this.addNode);
+    this.teardownToolbar();
     this.breadcrumbs.destroy();
     this.selection.off("new-node-front", this.onNewSelection);
     this.selection.off("before-new-node", this.onBeforeNewSelection);
     this.selection.off("before-new-node-front", this.onBeforeNewSelection);
     this.selection.off("detached-front", this.onDetached);
     let markupDestroyer = this._destroyMarkup();
     this.panelWin.inspector = null;
     this.target = null;
@@ -1246,16 +1270,62 @@ InspectorPanel.prototype = {
     ViewHelpers.togglePane({
       visible: !isVisible,
       animated: true,
       delayed: true,
       callback: onAnimationDone
     }, sidePaneContainer);
   },
 
+  onEyeDropperButtonClicked: function () {
+    this.eyeDropperButton.hasAttribute("checked")
+      ? this.hideEyeDropper()
+      : this.showEyeDropper();
+  },
+
+  startEyeDropperListeners: function () {
+    this.inspector.once("color-pick-canceled", this.onEyeDropperDone);
+    this.inspector.once("color-picked", this.onEyeDropperDone);
+    this.walker.once("new-root", this.onEyeDropperDone);
+  },
+
+  stopEyeDropperListeners: function () {
+    this.inspector.off("color-pick-canceled", this.onEyeDropperDone);
+    this.inspector.off("color-picked", this.onEyeDropperDone);
+    this.walker.off("new-root", this.onEyeDropperDone);
+  },
+
+  onEyeDropperDone: function () {
+    this.eyeDropperButton.removeAttribute("checked");
+    this.stopEyeDropperListeners();
+  },
+
+  /**
+   * Show the eyedropper on the page.
+   * @return {Promise} resolves when the eyedropper is visible.
+   */
+  showEyeDropper: function () {
+    this.telemetry.toolOpened("toolbareyedropper");
+    this.eyeDropperButton.setAttribute("checked", "true");
+    this.startEyeDropperListeners();
+    return this.inspector.pickColorFromPage({copyOnSelect: true})
+                         .catch(e => console.error(e));
+  },
+
+  /**
+   * Hide the eyedropper.
+   * @return {Promise} resolves when the eyedropper is hidden.
+   */
+  hideEyeDropper: function () {
+    this.eyeDropperButton.removeAttribute("checked");
+    this.stopEyeDropperListeners();
+    return this.inspector.cancelPickColorFromPage()
+                         .catch(e => console.error(e));
+  },
+
   /**
    * Create a new node as the last child of the current selection, expand the
    * parent and select the new node.
    */
   addNode: Task.async(function* () {
     if (!this.canAddHTMLChild()) {
       return;
     }
--- a/devtools/client/inspector/inspector.xul
+++ b/devtools/client/inspector/inspector.xul
@@ -40,18 +40,21 @@
           class="devtools-button" />
         <html:div class="devtools-toolbar-spacer" />
         <html:span id="inspector-searchlabel" />
         <textbox id="inspector-searchbox"
           type="search"
           timeout="50"
           class="devtools-searchinput"
           placeholder="&inspectorSearchHTML.label3;"/>
+        <html:button id="inspector-eyedropper-toggle"
+          title="&inspectorEyeDropper.label;"
+          class="devtools-button command-button-invertable" />
         <div xmlns="http://www.w3.org/1999/xhtml"
-             id="inspector-sidebar-toggle-box" />
+          id="inspector-sidebar-toggle-box" />
       </html:div>
       <vbox flex="1" id="markup-box">
       </vbox>
       <html:div id="inspector-breadcrumbs-toolbar" class="devtools-toolbar">
         <html:div id="inspector-breadcrumbs" class="breadcrumbs-widget-container"/>
       </html:div>
     </vbox>
     <splitter class="devtools-side-splitter"/>
@@ -83,93 +86,97 @@
           <html:div id="pseudo-class-panel" hidden="true">
             <html:label><html:input id="pseudo-hover-toggle" type="checkbox" value=":hover" tabindex="-1" />:hover</html:label>
             <html:label><html:input id="pseudo-active-toggle" type="checkbox" value=":active" tabindex="-1" />:active</html:label>
             <html:label><html:input id="pseudo-focus-toggle" type="checkbox" value=":focus" tabindex="-1" />:focus</html:label>
         </html:div>
         </html:div>
 
         <html:div id="ruleview-container" class="ruleview">
+          <html:div id="ruleview-container-focusable" tabindex="-1">
+          </html:div>
         </html:div>
       </html:div>
 
       <html:div id="sidebar-panel-computedview" class="devtools-monospace theme-sidebar inspector-tabpanel">
-        <html:div class="devtools-toolbar">
-          <html:div class="devtools-searchbox">
-            <html:input id="computedview-searchbox"
-                        class="devtools-filterinput devtools-rule-searchbox"
-                        type="search"
-                        placeholder="&filterStylesPlaceholder;"/>
-            <html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
-          </html:div>
-          <html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
-            <html:input id="browser-style-checkbox"
-                        type="checkbox"
-                        class="includebrowserstyles"
-                        label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
-        </html:div>
+        <html:div id="computedview-container">
+          <html:div id="computedview-container-focusable" tabindex="-1">
+            <html:div id="layout-wrapper" tabindex="0">
+              <html:div id="layout-header">
+                <html:div id="layout-expander" class="expander theme-twisty expandable" open=""></html:div>
+                <html:span>&layoutViewTitle;</html:span>
+                <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
+              </html:div>
 
-        <html:div id="computedview-container">
-          <html:div id="layout-wrapper" class="theme-separator" tabindex="0">
-            <html:div id="layout-header">
-              <html:div id="layout-expander" class="expander theme-twisty expandable" open=""></html:div>
-              <html:span>&layoutViewTitle;</html:span>
-              <html:button class="devtools-button" id="layout-geometry-editor" title="&geometry.button.tooltip;"></html:button>
-            </html:div>
-
-            <html:div id="layout-container">
-              <html:div id="layout-main">
-                <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
-                <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
-                  <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
-                  <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
-                    <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
-                    <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
-                      <html:div id="layout-content" data-box="content" title="&content.tooltip;">
+              <html:div id="layout-container">
+                <html:div id="layout-main">
+                  <html:span class="layout-legend" data-box="margin" title="&margin.tooltip;">&margin.tooltip;</html:span>
+                  <html:div id="layout-margins" data-box="margin" title="&margin.tooltip;">
+                    <html:span class="layout-legend" data-box="border" title="&border.tooltip;">&border.tooltip;</html:span>
+                    <html:div id="layout-borders" data-box="border" title="&border.tooltip;">
+                      <html:span class="layout-legend" data-box="padding" title="&padding.tooltip;">&padding.tooltip;</html:span>
+                      <html:div id="layout-padding" data-box="padding" title="&padding.tooltip;">
+                        <html:div id="layout-content" data-box="content" title="&content.tooltip;">
+                        </html:div>
                       </html:div>
                     </html:div>
                   </html:div>
+
+                  <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
+                  <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
+                  <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
+                  <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
+
+                  <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
+                  <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
+                  <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
+                  <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
+
+                  <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
+                  <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
+                  <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
+                  <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
+
+                  <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
+                </html:div>
+
+                <html:div id="layout-info">
+                  <html:span id="layout-element-size"></html:span>
+                  <html:section id="layout-position-group">
+                    <html:span id="layout-element-position"></html:span>
+                  </html:section>
                 </html:div>
 
-                <html:p class="layout-margin layout-top"><html:span data-box="margin" class="layout-editable" title="margin-top"></html:span></html:p>
-                <html:p class="layout-margin layout-right"><html:span data-box="margin" class="layout-editable" title="margin-right"></html:span></html:p>
-                <html:p class="layout-margin layout-bottom"><html:span data-box="margin" class="layout-editable" title="margin-bottom"></html:span></html:p>
-                <html:p class="layout-margin layout-left"><html:span data-box="margin" class="layout-editable" title="margin-left"></html:span></html:p>
-
-                <html:p class="layout-border layout-top"><html:span data-box="border" class="layout-editable" title="border-top"></html:span></html:p>
-                <html:p class="layout-border layout-right"><html:span data-box="border" class="layout-editable" title="border-right"></html:span></html:p>
-                <html:p class="layout-border layout-bottom"><html:span data-box="border" class="layout-editable" title="border-bottom"></html:span></html:p>
-                <html:p class="layout-border layout-left"><html:span data-box="border" class="layout-editable" title="border-left"></html:span></html:p>
-
-                <html:p class="layout-padding layout-top"><html:span data-box="padding" class="layout-editable" title="padding-top"></html:span></html:p>
-                <html:p class="layout-padding layout-right"><html:span data-box="padding" class="layout-editable" title="padding-right"></html:span></html:p>
-                <html:p class="layout-padding layout-bottom"><html:span data-box="padding" class="layout-editable" title="padding-bottom"></html:span></html:p>
-                <html:p class="layout-padding layout-left"><html:span data-box="padding" class="layout-editable" title="padding-left"></html:span></html:p>
-
-                <html:p class="layout-size"><html:span data-box="content" title="&content.tooltip;"></html:span></html:p>
-              </html:div>
-
-              <html:div id="layout-info">
-                <html:span id="layout-element-size"></html:span>
-                <html:section id="layout-position-group">
-                  <html:span id="layout-element-position"></html:span>
-                </html:section>
-              </html:div>
-
-              <html:div style="display: none">
-                <html:p id="layout-dummy"></html:p>
+                <html:div style="display: none">
+                  <html:p id="layout-dummy"></html:p>
+                </html:div>
               </html:div>
             </html:div>
-          </html:div>
 
-          <html:div id="propertyContainer" class="theme-separator" tabindex="0">
-          </html:div>
+            <html:div id="computedview-toolbar" class="devtools-toolbar">
+              <html:div class="devtools-searchbox">
+                <html:input id="computedview-searchbox"
+                            class="devtools-filterinput devtools-rule-searchbox"
+                            type="search"
+                            placeholder="&filterStylesPlaceholder;"/>
+                <html:button id="computedview-searchinput-clear" class="devtools-searchinput-clear"></html:button>
+              </html:div>
+              <html:label id="browser-style-checkbox-label" for="browser-style-checkbox">
+                <html:input id="browser-style-checkbox"
+                            type="checkbox"
+                            class="includebrowserstyles"
+                            label="&browserStylesLabel;"/>&browserStylesLabel;</html:label>
+            </html:div>
 
-          <html:div id="computedview-no-results" hidden="">
-            &noPropertiesFound;
+            <html:div id="propertyContainer" class="theme-separator" tabindex="0">
+            </html:div>
+
+            <html:div id="computedview-no-results" hidden="">
+              &noPropertiesFound;
+            </html:div>
           </html:div>
         </html:div>
       </html:div>
 
       <html:div id="sidebar-panel-fontinspector" class="devtools-monospace theme-sidebar inspector-tabpanel">
         <html:div class="devtools-toolbar">
           <html:div class="devtools-searchbox">
             <html:input id="font-preview-text-input"
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -169,17 +169,17 @@ function CssRuleView(inspector, document
   this._onFilterStyles = this._onFilterStyles.bind(this);
   this._onClearSearch = this._onClearSearch.bind(this);
   this._onFilterTextboxContextMenu =
     this._onFilterTextboxContextMenu.bind(this);
   this._onTogglePseudoClassPanel = this._onTogglePseudoClassPanel.bind(this);
   this._onTogglePseudoClass = this._onTogglePseudoClass.bind(this);
 
   let doc = this.styleDocument;
-  this.element = doc.getElementById("ruleview-container");
+  this.element = doc.getElementById("ruleview-container-focusable");
   this.addRuleButton = doc.getElementById("ruleview-add-rule-button");
   this.searchField = doc.getElementById("ruleview-searchbox");
   this.searchClearButton = doc.getElementById("ruleview-searchinput-clear");
   this.pseudoClassPanel = doc.getElementById("pseudo-class-panel");
   this.pseudoClassToggle = doc.getElementById("pseudo-class-panel-toggle");
   this.hoverCheckbox = doc.getElementById("pseudo-hover-toggle");
   this.activeCheckbox = doc.getElementById("pseudo-active-toggle");
   this.focusCheckbox = doc.getElementById("pseudo-focus-toggle");
--- a/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector-click-on-scrollbar.js
@@ -71,17 +71,17 @@ add_task(function* () {
   info("Enter new value and commit.");
   editor.input.value = newValue;
   EventUtils.synthesizeKey("VK_RETURN", {});
   yield onRuleViewChanged;
   ok(getRuleViewRule(view, newValue), "Rule with '" + newValue + " 'exists.");
 });
 
 function* clickOnRuleviewScrollbar(view) {
-  let container = view.element;
+  let container = view.element.parentNode;
   let onScroll = once(container, "scroll");
   let rect = container.getBoundingClientRect();
   // click 5 pixels before the bottom-right corner should hit the scrollbar
   EventUtils.synthesizeMouse(container, rect.width - 5, rect.height - 5,
     {}, view.styleWindow);
   yield onScroll;
 
   ok(true, "The rule view container scrolled after clicking on the scrollbar.");
--- a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
+++ b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
@@ -1,24 +1,15 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
-
 "use strict";
 
-// So we can test collecting telemetry on the eyedropper
-var oldCanRecord = Services.telemetry.canRecordExtended;
-Services.telemetry.canRecordExtended = true;
-registerCleanupFunction(function () {
-  Services.telemetry.canRecordExtended = oldCanRecord;
-});
-const EXPECTED_TELEMETRY = {
-  "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT": 2,
-  "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG": 1
-};
+// Test opening the eyedropper from the color picker. Pressing escape to close it, and
+// clicking the page to select a color.
 
 const TEST_URI = `
   <style type="text/css">
     body {
       background-color: white;
       padding: 0px
     }
 
@@ -38,151 +29,88 @@ const TEST_URI = `
   <body><div id="div1"></div><div id="div2"></div></body>
 `;
 
 // #f09
 const ORIGINAL_COLOR = "rgb(255, 0, 153)";
 // #ff5
 const EXPECTED_COLOR = "rgb(255, 255, 85)";
 
-// Test opening the eyedropper from the color picker. Pressing escape
-// to close it, and clicking the page to select a color.
-
 add_task(function* () {
-  // clear telemetry so we can get accurate counts
-  clearTelemetry();
-
+  info("Add the test tab, open the rule-view and select the test node");
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {testActor, inspector, view} = yield openRuleView();
   yield selectNode("#div2", inspector);
 
+  info("Get the background-color property from the rule-view");
   let property = getRuleViewProperty(view, "#div2", "background-color");
   let swatch = property.valueSpan.querySelector(".ruleview-colorswatch");
   ok(swatch, "Color swatch is displayed for the bg-color property");
 
-  let dropper = yield openEyedropper(view, swatch);
+  info("Open the eyedropper from the colorpicker tooltip");
+  yield openEyedropper(view, swatch);
 
   let tooltip = view.tooltips.colorPicker.tooltip;
-  ok(!tooltip.isVisible(),
-     "color picker tooltip is closed after opening eyedropper");
+  ok(!tooltip.isVisible(), "color picker tooltip is closed after opening eyedropper");
 
-  yield testESC(swatch, dropper);
+  info("Test that pressing escape dismisses the eyedropper");
+  yield testESC(swatch, inspector, testActor);
 
-  dropper = yield openEyedropper(view, swatch);
-
-  ok(dropper, "dropper opened");
+  info("Open the eyedropper again");
+  yield openEyedropper(view, swatch);
 
-  yield testSelect(view, swatch, dropper);
-
-  checkTelemetry();
+  info("Test that a color can be selected with the eyedropper");
+  yield testSelect(view, swatch, inspector, testActor);
 });
 
-function testESC(swatch, dropper) {
-  let deferred = defer();
-
-  dropper.once("destroy", () => {
-    let color = swatch.style.backgroundColor;
-    is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC");
+function* testESC(swatch, inspector, testActor) {
+  info("Press escape");
+  let onCanceled = new Promise(resolve => {
+    inspector.inspector.once("color-pick-canceled", resolve);
+  });
+  yield testActor.synthesizeKey({key: "VK_ESCAPE", options: {}});
+  yield onCanceled;
 
-    deferred.resolve();
-  });
-
-  inspectPage(dropper, false).then(pressESC);
-
-  return deferred.promise;
+  let color = swatch.style.backgroundColor;
+  is(color, ORIGINAL_COLOR, "swatch didn't change after pressing ESC");
 }
 
-function* testSelect(view, swatch, dropper) {
-  let onDestroyed = dropper.once("destroy");
-  // the change to the content is done async after rule view change
+function* testSelect(view, swatch, inspector, testActor) {
+  info("Click at x:10px y:10px");
+  let onPicked = new Promise(resolve => {
+    inspector.inspector.once("color-picked", resolve);
+  });
+  // The change to the content is done async after rule view change
   let onRuleViewChanged = view.once("ruleview-changed");
 
-  inspectPage(dropper);
+  yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+                                   options: {type: "mousemove"}});
+  yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+                                   options: {type: "mousedown"}});
+  yield testActor.synthesizeMouse({selector: "html", x: 10, y: 10,
+                                   options: {type: "mouseup"}});
 
-  yield onDestroyed;
+  yield onPicked;
   yield onRuleViewChanged;
 
   let color = swatch.style.backgroundColor;
   is(color, EXPECTED_COLOR, "swatch changed colors");
 
   is((yield getComputedStyleProperty("div", null, "background-color")),
      EXPECTED_COLOR,
      "div's color set to body color after dropper");
 }
 
-function clearTelemetry() {
-  for (let histogramId in EXPECTED_TELEMETRY) {
-    let histogram = Services.telemetry.getHistogramById(histogramId);
-    histogram.clear();
-  }
-}
-
-function checkTelemetry() {
-  for (let histogramId in EXPECTED_TELEMETRY) {
-    let expected = EXPECTED_TELEMETRY[histogramId];
-    let histogram = Services.telemetry.getHistogramById(histogramId);
-    let snapshot = histogram.snapshot();
-
-    is(snapshot.sum, expected,
-      "eyedropper telemetry value correct for " + histogramId);
-  }
-}
-
-/* Helpers */
-
-function openEyedropper(view, swatch) {
-  let deferred = defer();
-
+function* openEyedropper(view, swatch) {
   let tooltip = view.tooltips.colorPicker.tooltip;
 
-  tooltip.once("shown", () => {
-    let dropperButton = tooltip.doc.querySelector("#eyedropper-button");
-
-    tooltip.once("eyedropper-opened", (event, dropper) => {
-      deferred.resolve(dropper);
-    });
-    dropperButton.click();
-  });
-
+  info("Click on the swatch");
+  let onShown = tooltip.once("shown");
   swatch.click();
-  return deferred.promise;
-}
-
-function inspectPage(dropper, click = true) {
-  let target = document.documentElement;
-  let win = window;
-
-  // get location of the content, offset from browser window
-  let box = gBrowser.selectedBrowser.getBoundingClientRect();
-  let x = box.left + 1;
-  let y = box.top + 1;
-
-  return dropperStarted(dropper).then(() => {
-    EventUtils.synthesizeMouse(target, x, y, { type: "mousemove" }, win);
+  yield onShown;
 
-    return dropperLoaded(dropper).then(() => {
-      EventUtils.synthesizeMouse(target, x + 10, y + 10,
-        { type: "mousemove" }, win);
-
-      if (click) {
-        EventUtils.synthesizeMouse(target, x + 10, y + 10, {}, win);
-      }
-    });
-  });
-}
+  let dropperButton = tooltip.doc.querySelector("#eyedropper-button");
 
-function dropperStarted(dropper) {
-  if (dropper.isStarted) {
-    return promise.resolve();
-  }
-  return dropper.once("started");
+  info("Click on the eyedropper icon");
+  let onOpened = tooltip.once("eyedropper-opened");
+  dropperButton.click();
+  yield onOpened;
 }
-
-function dropperLoaded(dropper) {
-  if (dropper.loaded) {
-    return promise.resolve();
-  }
-  return dropper.once("load");
-}
-
-function pressESC() {
-  EventUtils.synthesizeKey("VK_ESCAPE", { });
-}
--- a/devtools/client/inspector/shared/style-inspector-overlays.js
+++ b/devtools/client/inspector/shared/style-inspector-overlays.js
@@ -288,17 +288,17 @@ TooltipsOverlay.prototype = {
     this.previewTooltip.startTogglingOnHover(this.view.element,
       this._onPreviewTooltipTargetHover.bind(this));
 
     // MDN CSS help tooltip
     this.cssDocs = new CssDocsTooltip(toolbox);
 
     if (this.isRuleView) {
       // Color picker tooltip
-      this.colorPicker = new SwatchColorPickerTooltip(toolbox);
+      this.colorPicker = new SwatchColorPickerTooltip(toolbox, this.view.inspector);
       // Cubic bezier tooltip
       this.cubicBezier = new SwatchCubicBezierTooltip(toolbox);
       // Filter editor tooltip
       this.filterEditor = new SwatchFilterTooltip(toolbox);
     }
 
     this._isStarted = true;
   },
--- a/devtools/client/inspector/test/browser.ini
+++ b/devtools/client/inspector/test/browser.ini
@@ -58,16 +58,20 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_highlighter-02.js]
 [browser_inspector_highlighter-03.js]
 [browser_inspector_highlighter-04.js]
 [browser_inspector_highlighter-by-type.js]
 [browser_inspector_highlighter-comments.js]
 [browser_inspector_highlighter-csstransform_01.js]
 [browser_inspector_highlighter-csstransform_02.js]
 [browser_inspector_highlighter-embed.js]
+[browser_inspector_highlighter-eyedropper-clipboard.js]
+subsuite = clipboard
+[browser_inspector_highlighter-eyedropper-events.js]
+[browser_inspector_highlighter-eyedropper-show-hide.js]
 [browser_inspector_highlighter-geometry_01.js]
 [browser_inspector_highlighter-geometry_02.js]
 [browser_inspector_highlighter-geometry_03.js]
 [browser_inspector_highlighter-geometry_04.js]
 [browser_inspector_highlighter-geometry_05.js]
 [browser_inspector_highlighter-geometry_06.js]
 [browser_inspector_highlighter-hover_01.js]
 [browser_inspector_highlighter-hover_02.js]
@@ -129,13 +133,14 @@ skip-if = os == "mac" # Full keyboard na
 [browser_inspector_search-04.js]
 [browser_inspector_search-05.js]
 [browser_inspector_search-06.js]
 [browser_inspector_search-07.js]
 [browser_inspector_search-08.js]
 [browser_inspector_search_keyboard_trap.js]
 [browser_inspector_search-reserved.js]
 [browser_inspector_search-selection.js]
+[browser_inspector_search-sidebar.js]
 [browser_inspector_select-docshell.js]
 [browser_inspector_select-last-selected.js]
 [browser_inspector_search-navigation.js]
 [browser_inspector_sidebarstate.js]
 [browser_inspector_switch-to-inspector-on-pick.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-clipboard.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+// Test that the eyedropper can copy colors to the clipboard
+
+const HIGHLIGHTER_TYPE = "EyeDropper";
+const ID = "eye-dropper-";
+const TEST_URI = "data:text/html;charset=utf-8,<style>html{background:red}</style>";
+
+add_task(function* () {
+  let helper = yield openInspectorForURL(TEST_URI)
+               .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
+  helper.prefix = ID;
+
+  let {show, synthesizeKey, finalize} = helper;
+
+  info("Show the eyedropper with the copyOnSelect option");
+  yield show("html", {copyOnSelect: true});
+
+  info("Make sure to wait until the eyedropper is done taking a screenshot of the page");
+  yield waitForElementAttributeSet("root", "drawn", helper);
+
+  yield waitForClipboard(() => {
+    info("Activate the eyedropper so the background color is copied");
+    let generateKey = synthesizeKey({key: "VK_RETURN", options: {}});
+    generateKey.next();
+  }, "#FF0000");
+
+  ok(true, "The clipboard contains the right value");
+
+  yield waitForElementAttributeRemoved("root", "drawn", helper);
+  yield waitForElementAttributeSet("root", "hidden", helper);
+  ok(true, "The eyedropper is now hidden");
+
+  finalize();
+});
+
+function* waitForElementAttributeSet(id, name, {getElementAttribute}) {
+  yield poll(function* () {
+    let value = yield getElementAttribute(id, name);
+    return !!value;
+  }, `Waiting for element ${id} to have attribute ${name} set`);
+}
+
+function* waitForElementAttributeRemoved(id, name, {getElementAttribute}) {
+  yield poll(function* () {
+    let value = yield getElementAttribute(id, name);
+    return !value;
+  }, `Waiting for element ${id} to have attribute ${name} removed`);
+}
+
+function* poll(check, desc) {
+  info(desc);
+
+  for (let i = 0; i < 10; i++) {
+    if (yield check()) {
+      return;
+    }
+    yield new Promise(resolve => setTimeout(resolve, 200));
+  }
+
+  throw new Error(`Timeout while: ${desc}`);
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-events.js
@@ -0,0 +1,71 @@
+/* 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";
+
+// Test the eyedropper mouse and keyboard handling.
+
+const HIGHLIGHTER_TYPE = "EyeDropper";
+const ID = "eye-dropper-";
+
+const MOVE_EVENTS_DATA = [
+  {type: "mouse", x: 200, y: 100, expected: {x: 200, y: 100}},
+  {type: "mouse", x: 100, y: 200, expected: {x: 100, y: 200}},
+  {type: "keyboard", key: "VK_LEFT", expected: {x: 99, y: 200}},
+  {type: "keyboard", key: "VK_LEFT", shift: true, expected: {x: 89, y: 200}},
+  {type: "keyboard", key: "VK_RIGHT", expected: {x: 90, y: 200}},
+  {type: "keyboard", key: "VK_RIGHT", shift: true, expected: {x: 100, y: 200}},
+  {type: "keyboard", key: "VK_DOWN", expected: {x: 100, y: 201}},
+  {type: "keyboard", key: "VK_DOWN", shift: true, expected: {x: 100, y: 211}},
+  {type: "keyboard", key: "VK_UP", expected: {x: 100, y: 210}},
+  {type: "keyboard", key: "VK_UP", shift: true, expected: {x: 100, y: 200}},
+];
+
+add_task(function* () {
+  let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
+               .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
+  helper.prefix = ID;
+
+  yield helper.show("html");
+  yield respondsToMoveEvents(helper);
+  yield respondsToReturnAndEscape(helper);
+
+  helper.finalize();
+});
+
+function* respondsToMoveEvents(helper) {
+  info("Checking that the eyedropper responds to events from the mouse and keyboard");
+  let {mouse, synthesizeKey} = helper;
+
+  for (let {type, x, y, key, shift, expected} of MOVE_EVENTS_DATA) {
+    info(`Simulating a ${type} event to move to ${expected.x} ${expected.y}`);
+    if (type === "mouse") {
+      yield mouse.move(x, y);
+    } else if (type === "keyboard") {
+      let options = shift ? {shiftKey: true} : {};
+      yield synthesizeKey({key, options});
+    }
+    yield checkPosition(expected, helper);
+  }
+}
+
+function* checkPosition({x, y}, {getElementAttribute}) {
+  let style = yield getElementAttribute("root", "style");
+  is(style, `top:${y}px;left:${x}px;`,
+     `The eyedropper is at the expected ${x} ${y} position`);
+}
+
+function* respondsToReturnAndEscape({synthesizeKey, isElementHidden, show}) {
+  info("Simulating return to select the color and hide the eyedropper");
+
+  yield synthesizeKey({key: "VK_RETURN", options: {}});
+  let hidden = yield isElementHidden("root");
+  ok(hidden, "The eyedropper has been hidden");
+
+  info("Showing the eyedropper again and simulating escape to hide it");
+
+  yield show("html");
+  yield synthesizeKey({key: "VK_ESCAPE", options: {}});
+  hidden = yield isElementHidden("root");
+  ok(hidden, "The eyedropper has been hidden again");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_highlighter-eyedropper-show-hide.js
@@ -0,0 +1,42 @@
+/* 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";
+
+// Test the basic structure of the eye-dropper highlighter.
+
+const HIGHLIGHTER_TYPE = "EyeDropper";
+const ID = "eye-dropper-";
+
+add_task(function* () {
+  let helper = yield openInspectorForURL("data:text/html;charset=utf-8,eye-dropper test")
+               .then(getHighlighterHelperFor(HIGHLIGHTER_TYPE));
+  helper.prefix = ID;
+
+  yield isInitiallyHidden(helper);
+  yield canBeShownAndHidden(helper);
+
+  helper.finalize();
+});
+
+function* isInitiallyHidden({isElementHidden}) {
+  info("Checking that the eyedropper is hidden by default");
+
+  let hidden = yield isElementHidden("root");
+  ok(hidden, "The eyedropper is hidden by default");
+}
+
+function* canBeShownAndHidden({show, hide, isElementHidden, getElementAttribute}) {
+  info("Asking to show and hide the highlighter actually works");
+
+  yield show("html");
+  let hidden = yield isElementHidden("root");
+  ok(!hidden, "The eyedropper is now shown");
+
+  let style = yield getElementAttribute("root", "style");
+  is(style, "top:100px;left:100px;", "The eyedropper is correctly positioned");
+
+  yield hide();
+  hidden = yield isElementHidden("root");
+  ok(hidden, "The eyedropper is now hidden again");
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/test/browser_inspector_search-sidebar.js
@@ -0,0 +1,74 @@
+/* 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";
+
+// Test that depending where the user last clicked in the inspector, the right search
+// field is focused when ctrl+F is pressed.
+
+add_task(function* () {
+  let {inspector} = yield openInspectorForURL("data:text/html;charset=utf-8,Search!");
+
+  info("Check that by default, the inspector search field gets focused");
+  pressCtrlF();
+  isInInspectorSearchBox(inspector);
+
+  info("Click somewhere in the rule-view");
+  clickInRuleView(inspector);
+
+  info("Check that the rule-view search field gets focused");
+  pressCtrlF();
+  isInRuleViewSearchBox(inspector);
+
+  info("Click in the inspector again");
+  yield clickContainer("head", inspector);
+
+  info("Check that now we're back in the inspector, its search field gets focused");
+  pressCtrlF();
+  isInInspectorSearchBox(inspector);
+
+  info("Switch to the computed view, and click somewhere inside it");
+  selectComputedView(inspector);
+  clickInComputedView(inspector);
+
+  info("Check that the computed-view search field gets focused");
+  pressCtrlF();
+  isInComputedViewSearchBox(inspector);
+
+  info("Click in the inspector yet again");
+  yield clickContainer("body", inspector);
+
+  info("We're back in the inspector again, check the inspector search field focuses");
+  pressCtrlF();
+  isInInspectorSearchBox(inspector);
+});
+
+function pressCtrlF() {
+  EventUtils.synthesizeKey("f", {accelKey: true});
+}
+
+function clickInRuleView(inspector) {
+  let el = inspector.panelDoc.querySelector("#sidebar-panel-ruleview");
+  EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView);
+}
+
+function clickInComputedView(inspector) {
+  let el = inspector.panelDoc.querySelector("#sidebar-panel-computedview");
+  EventUtils.synthesizeMouseAtCenter(el, {}, inspector.panelDoc.defaultView);
+}
+
+function isInInspectorSearchBox(inspector) {
+  // Focus ends up in an anonymous child of the XUL textbox.
+  ok(inspector.panelDoc.activeElement.closest("#inspector-searchbox"),
+     "The inspector search field is focused when ctrl+F is pressed");
+}
+
+function isInRuleViewSearchBox(inspector) {
+  is(inspector.panelDoc.activeElement, inspector.ruleview.view.searchField,
+     "The rule-view search field is focused when ctrl+F is pressed");
+}
+
+function isInComputedViewSearchBox(inspector) {
+  is(inspector.panelDoc.activeElement, inspector.computedview.computedView.searchField,
+     "The computed-view search field is focused when ctrl+F is pressed");
+}
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -417,32 +417,33 @@ const getHighlighterHelperFor = (type) =
 
     // Highlighted node
     let highlightedNode = null;
 
     return {
       set prefix(value) {
         prefix = value;
       },
+
       get highlightedNode() {
         if (!highlightedNode) {
           return null;
         }
 
         return {
           getComputedStyle: function* (options = {}) {
             return yield inspector.pageStyle.getComputed(
               highlightedNode, options);
           }
         };
       },
 
-      show: function* (selector = ":root") {
+      show: function* (selector = ":root", options) {
         highlightedNode = yield getNodeFront(selector, inspector);
-        return yield highlighter.show(highlightedNode);
+        return yield highlighter.show(highlightedNode, options);
       },
 
       hide: function* () {
         yield highlighter.hide();
       },
 
       isElementHidden: function* (id) {
         return (yield testActor.getHighlighterNodeAttribute(
@@ -459,16 +460,20 @@ const getHighlighterHelperFor = (type) =
           prefix + id, name, highlighter);
       },
 
       synthesizeMouse: function* (options) {
         options = Object.assign({selector: ":root"}, options);
         yield testActor.synthesizeMouse(options);
       },
 
+      synthesizeKey: function* (options) {
+        yield testActor.synthesizeKey(options);
+      },
+
       // This object will synthesize any "mouse" prefixed event to the
       // `testActor`, using the name of method called as suffix for the
       // event's name.
       // If no x, y coords are given, the previous ones are used.
       //
       // For example:
       //   mouse.down(10, 20); // synthesize "mousedown" at 10,20
       //   mouse.move(20, 30); // synthesize "mousemove" at 20,30
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -126,19 +126,16 @@ devtools.jar:
     content/framework/connect/connect.xhtml (framework/connect/connect.xhtml)
     content/framework/connect/connect.css (framework/connect/connect.css)
     content/framework/connect/connect.js (framework/connect/connect.js)
     content/shared/widgets/graphs-frame.xhtml (shared/widgets/graphs-frame.xhtml)
     content/shared/widgets/cubic-bezier.css (shared/widgets/cubic-bezier.css)
     content/shared/widgets/mdn-docs.css (shared/widgets/mdn-docs.css)
     content/shared/widgets/filter-widget.css (shared/widgets/filter-widget.css)
     content/shared/widgets/spectrum.css (shared/widgets/spectrum.css)
-    content/eyedropper/eyedropper.xul (eyedropper/eyedropper.xul)
-    content/eyedropper/crosshairs.css (eyedropper/crosshairs.css)
-    content/eyedropper/nocursor.css (eyedropper/nocursor.css)
     content/aboutdebugging/aboutdebugging.xhtml (aboutdebugging/aboutdebugging.xhtml)
     content/aboutdebugging/aboutdebugging.css (aboutdebugging/aboutdebugging.css)
     content/aboutdebugging/initializer.js (aboutdebugging/initializer.js)
     content/responsive.html/index.xhtml (responsive.html/index.xhtml)
     content/responsive.html/index.js (responsive.html/index.js)
     content/dom/dom.html (dom/dom.html)
     content/dom/content/dom-view.css (dom/content/dom-view.css)
     content/dom/main.js (dom/main.js)
--- a/devtools/client/locales/en-US/inspector.dtd
+++ b/devtools/client/locales/en-US/inspector.dtd
@@ -10,8 +10,13 @@
      shown as the placeholder for the markup view search in the inspector. -->
 <!ENTITY inspectorSearchHTML.label3 "Search HTML">
 
 <!-- LOCALIZATION NOTE (inspectorAddNode.label): This is the label shown in
      the inspector toolbar for the button that lets users add elements to the
      DOM (as children of the currently selected element). -->
 <!ENTITY inspectorAddNode.label       "Create New Node">
 <!ENTITY inspectorAddNode.accesskey   "C">
+
+
+<!-- LOCALIZATION NOTE (inspectorEyeDropper.label): A string displayed as the tooltip of
+     a button in the inspector which toggles the Eyedropper tool -->
+<!ENTITY inspectorEyeDropper.label       "Grab a color from the page">
\ No newline at end of file
--- a/devtools/client/menus.js
+++ b/devtools/client/menus.js
@@ -32,17 +32,18 @@
  *   If true, the menuitem is prefixed by a checkbox and runtime code can
  *   toggle it.
  */
 
 const Services = require("Services");
 const isMac = Services.appinfo.OS === "Darwin";
 
 loader.lazyRequireGetter(this, "gDevToolsBrowser", "devtools/client/framework/devtools-browser", true);
-loader.lazyRequireGetter(this, "Eyedropper", "devtools/client/eyedropper/eyedropper", true);
+loader.lazyRequireGetter(this, "CommandUtils", "devtools/client/shared/developer-toolbar", true);
+loader.lazyRequireGetter(this, "TargetFactory", "devtools/client/framework/target", true);
 
 loader.lazyImporter(this, "BrowserToolboxProcess", "resource://devtools/client/framework/ToolboxProcess.jsm");
 loader.lazyImporter(this, "ResponsiveUIManager", "resource://devtools/client/responsivedesign/responsivedesign.jsm");
 loader.lazyImporter(this, "ScratchpadManager", "resource://devtools/client/scratchpad/scratchpad-manager.jsm");
 
 exports.menuitems = [
   { id: "menu_devToolbox",
     l10nKey: "devToolboxMenuItem",
@@ -139,19 +140,23 @@ exports.menuitems = [
       keytext: true
     },
     checkbox: true
   },
   { id: "menu_eyedropper",
     l10nKey: "eyedropper",
     oncommand(event) {
       let window = event.target.ownerDocument.defaultView;
-      let eyedropper = new Eyedropper(window, { context: "menu",
-                                                copyOnSelect: true });
-      eyedropper.open();
+      let target = TargetFactory.forTab(window.gBrowser.selectedTab);
+
+      CommandUtils.createRequisition(target, {
+        environment: CommandUtils.createEnvironment({target})
+      }).then(requisition => {
+        requisition.updateExec("eyedropper --frommenu");
+      }, e => console.error(e));
     },
     checkbox: true
   },
   { id: "menu_scratchpad",
     l10nKey: "scratchpad",
     oncommand() {
       ScratchpadManager.openScratchpad();
     },
--- a/devtools/client/moz.build
+++ b/devtools/client/moz.build
@@ -8,17 +8,16 @@ include('../templates.mozbuild')
 
 DIRS += [
     'aboutdebugging',
     'animationinspector',
     'canvasdebugger',
     'commandline',
     'debugger',
     'dom',
-    'eyedropper',
     'framework',
     'inspector',
     'jsonview',
     'locales',
     'memory',
     'netmonitor',
     'performance',
     'preferences',
--- a/devtools/client/netmonitor/netmonitor.css
+++ b/devtools/client/netmonitor/netmonitor.css
@@ -2,16 +2,24 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #toolbar-labels {
   overflow: hidden;
 }
 
+/**
+ * Collapsed details pane needs to be truly hidden to prevent both accessibility
+ * tools and keyboard from accessing its contents.
+ */
+#details-pane.pane-collapsed {
+  visibility: hidden;
+}
+
 #details-pane-toggle[disabled] {
   display: none;
 }
 
 #custom-pane {
   overflow: auto;
 }
 
@@ -31,14 +39,13 @@
   /* workaround for textbox not supporting the @crop attribute */
   text-overflow: ellipsis;
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   #toolbar-spacer,
   #details-pane-toggle,
-  #details-pane.pane-collapsed,
   .requests-menu-waterfall,
   #requests-menu-network-summary-button > .toolbarbutton-text {
     display: none;
   }
 }
--- a/devtools/client/performance/modules/widgets/graphs.js
+++ b/devtools/client/performance/modules/widgets/graphs.js
@@ -13,17 +13,17 @@ const { Heritage } = require("devtools/c
 const LineGraphWidget = require("devtools/client/shared/widgets/LineGraphWidget");
 const BarGraphWidget = require("devtools/client/shared/widgets/BarGraphWidget");
 const MountainGraphWidget = require("devtools/client/shared/widgets/MountainGraphWidget");
 const { CanvasGraphUtils } = require("devtools/client/shared/widgets/Graphs");
 
 const promise = require("promise");
 const EventEmitter = require("devtools/shared/event-emitter");
 
-const { colorUtils } = require("devtools/client/shared/css-color");
+const { colorUtils } = require("devtools/shared/css-color");
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkersOverview } = require("devtools/client/performance/modules/widgets/markers-overview");
 const { createTierGraphDataFromFrameNode } = require("devtools/client/performance/modules/logic/jit");
 
 /**
  * For line graphs
  */
--- a/devtools/client/performance/modules/widgets/markers-overview.js
+++ b/devtools/client/performance/modules/widgets/markers-overview.js
@@ -8,17 +8,17 @@
  * the timeline data. Regions inside it may be selected, determining which
  * markers are visible in the "waterfall".
  */
 
 const { Cc, Ci, Cu, Cr } = require("chrome");
 const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
 const { AbstractCanvasGraph } = require("devtools/client/shared/widgets/Graphs");
 
-const { colorUtils } = require("devtools/client/shared/css-color");
+const { colorUtils } = require("devtools/shared/css-color");
 const { getColor } = require("devtools/client/shared/theme");
 const ProfilerGlobal = require("devtools/client/performance/modules/global");
 const { MarkerBlueprintUtils } = require("devtools/client/performance/modules/marker-blueprint-utils");
 const { TickUtils } = require("devtools/client/performance/modules/widgets/waterfall-ticks");
 const { TIMELINE_BLUEPRINT } = require("devtools/client/performance/modules/markers");
 
 const OVERVIEW_HEADER_HEIGHT = 14; // px
 const OVERVIEW_ROW_HEIGHT = 11; // px
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -25,30 +25,29 @@ pref("devtools.toolbar.visible", false);
 pref("devtools.webide.enabled", true);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.previousHost", "side");
 pref("devtools.toolbox.selectedTool", "webconsole");
-pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage", "rulers", "measure"]');
+pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","scratchpad","resize toggle","screenshot --fullpage", "rulers", "measure"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 pref("devtools.toolbox.splitconsoleEnabled", false);
 pref("devtools.toolbox.splitconsoleHeight", 100);
 
 // Toolbox Button preferences
 pref("devtools.command-button-pick.enabled", true);
 pref("devtools.command-button-frames.enabled", true);
 pref("devtools.command-button-splitconsole.enabled", true);
 pref("devtools.command-button-paintflashing.enabled", false);
 pref("devtools.command-button-scratchpad.enabled", false);
 pref("devtools.command-button-responsive.enabled", true);
-pref("devtools.command-button-eyedropper.enabled", false);
 pref("devtools.command-button-screenshot.enabled", false);
 pref("devtools.command-button-rulers.enabled", false);
 pref("devtools.command-button-measure.enabled", false);
 pref("devtools.command-button-noautohide.enabled", false);
 
 // Inspector preferences
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
--- a/devtools/client/shared/components/reps/grip-array.js
+++ b/devtools/client/shared/components/reps/grip-array.js
@@ -30,18 +30,19 @@ define(function (require, exports, modul
       provider: React.PropTypes.object,
     },
 
     getLength: function (grip) {
       return grip.preview ? grip.preview.length : 0;
     },
 
     getTitle: function (object, context) {
-      if (this.props.objectLink) {
-        return this.props.objectLink({
+      let objectLink = this.props.objectLink || span;
+      if (this.props.mode != "tiny") {
+        return objectLink({
           object: object
         }, object.class);
       }
       return "";
     },
 
     arrayIterator: function (grip, max) {
       let items = [];
@@ -112,21 +113,22 @@ define(function (require, exports, modul
         let isEmpty = objectLength === 0;
         items = span({className: "length"}, isEmpty ? "" : objectLength);
       } else {
         let max = (mode == "short") ? 3 : 300;
         items = this.arrayIterator(object, max);
       }
 
       let objectLink = this.props.objectLink || span;
+      let title = this.getTitle(object);
 
       return (
         ObjectBox({
           className: "array"},
-          this.getTitle(object),
+          title,
           objectLink({
             className: "arrayLeftBracket",
             role: "presentation",
             object: object
           }, "["),
           items,
           objectLink({
             className: "arrayRightBracket",
--- a/devtools/client/shared/components/reps/grip.js
+++ b/devtools/client/shared/components/reps/grip.js
@@ -50,40 +50,34 @@ define(function (require, exports, modul
     },
 
     propIterator: function (object, max) {
       // Property filter. Show only interesting properties to the user.
       let isInterestingProp = (type, value) => {
         return (
           type == "boolean" ||
           type == "number" ||
-          type == "string" ||
-          type == "object"
+          (type == "string" && value.length != 0)
         );
       };
 
-      // Object members with non-empty values are preferred since it gives the
-      // user a better overview of the object.
-      let props = this.getProps(object, max, isInterestingProp);
-
-      if (props.length <= max) {
-        // There are not enough props yet (or at least, not enough props to
-        // be able to know whether we should print "more…" or not).
-        // Let's display also empty members and functions.
-        props = props.concat(this.getProps(object, max, (t, value) => {
-          return !isInterestingProp(t, value);
-        }));
+      let ownProperties = object.preview ? object.preview.ownProperties : [];
+      let indexes = this.getPropIndexes(ownProperties, max, isInterestingProp);
+      if (indexes.length < max && indexes.length < object.ownPropertyLength) {
+        // There are not enough props yet. Then add uninteresting props to display them.
+        indexes = indexes.concat(
+          this.getPropIndexes(ownProperties, max - indexes.length, (t, value) => {
+            return !isInterestingProp(t, value);
+          })
+        );
       }
 
-      // getProps() can return max+1 properties (it can't return more)
-      // to indicate that there is more props than allowed. Remove the last
-      // one and append 'more…' postfix in such case.
-      if (props.length > max) {
-        props.pop();
-
+      let props = this.getProps(ownProperties, indexes);
+      if (props.length < object.ownPropertyLength) {
+        // There are some undisplayed props. Then display "more...".
         let objectLink = this.props.objectLink || span;
 
         props.push(Caption({
           key: "more",
           object: objectLink({
             object: object
           }, "more…")
         }));
@@ -95,56 +89,83 @@ define(function (require, exports, modul
         props[last] = React.cloneElement(props[last], {
           delim: ""
         });
       }
 
       return props;
     },
 
-    getProps: function (object, max, filter) {
+    /**
+     * Get props ordered by index.
+     *
+     * @param {Object} ownProperties Props object.
+     * @param {Array} indexes Indexes of props.
+     * @return {Array} Props.
+     */
+    getProps: function (ownProperties, indexes) {
       let props = [];
 
-      max = max || 3;
-      if (!object) {
-        return props;
-      }
+      // Make indexes ordered by ascending.
+      indexes.sort(function (a, b) {
+        return a - b;
+      });
+
+      indexes.forEach((i) => {
+        let name = Object.keys(ownProperties)[i];
+        let value = ownProperties[name].value;
+        props.push(PropRep(Object.assign({}, this.props, {
+          key: name,
+          mode: "tiny",
+          name: name,
+          object: value,
+          equal: ": ",
+          delim: ", ",
+        })));
+      });
+
+      return props;
+    },
+
+    /**
+     * Get the indexes of props in the object.
+     *
+     * @param {Object} ownProperties Props object.
+     * @param {Number} max The maximum length of indexes array.
+     * @param {Function} filter Filter the props you want.
+     * @return {Array} Indexes of interesting props in the object.
+     */
+    getPropIndexes: function (ownProperties, max, filter) {
+      let indexes = [];
 
       try {
-        let ownProperties = object.preview ? object.preview.ownProperties : [];
+        let i = 0;
         for (let name in ownProperties) {
-          if (props.length > max) {
-            return props;
+          if (indexes.length >= max) {
+            return indexes;
           }
 
           let prop = ownProperties[name];
-          let value = prop.value || {};
+          let value = prop.value;
 
           // Type is specified in grip's "class" field and for primitive
           // values use typeof.
           let type = (value.class || typeof value);
           type = type.toLowerCase();
 
-          // Show only interesting properties.
           if (filter(type, value)) {
-            props.push(PropRep(Object.assign({}, this.props, {
-              key: name,
-              mode: "tiny",
-              name: name,
-              object: value,
-              equal: ": ",
-              delim: ", ",
-            })));
+            indexes.push(i);
           }
+          i++;
         }
       } catch (err) {
         console.error(err);
       }
 
-      return props;
+      return indexes;
     },
 
     render: function () {
       let object = this.props.object;
       let props = this.safePropIterator(object,
         (this.props.mode == "long") ? 100 : 3);
 
       let objectLink = this.props.objectLink || span;
--- a/devtools/client/shared/components/reps/moz.build
+++ b/devtools/client/shared/components/reps/moz.build
@@ -9,17 +9,16 @@ DevToolsModules(
     'attribute.js',
     'caption.js',
     'date-time.js',
     'document.js',
     'event.js',
     'function.js',
     'grip-array.js',
     'grip.js',
-    'named-node-map.js',
     'null.js',
     'number.js',
     'object-box.js',
     'object-link.js',
     'object-with-text.js',
     'object-with-url.js',
     'object.js',
     'prop-rep.js',
deleted file mode 100644
--- a/devtools/client/shared/components/reps/named-node-map.js
+++ /dev/null
@@ -1,180 +0,0 @@
-/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
-/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-"use strict";
-
-// Make this available to both AMD and CJS environments
-define(function (require, exports, module) {
-  // ReactJS
-  const React = require("devtools/client/shared/vendor/react");
-
-  // Reps
-  const { createFactories, isGrip } = require("./rep-utils");
-  const { ObjectBox } = createFactories(require("./object-box"));
-  const { Caption } = createFactories(require("./caption"));
-
-  // Shortcuts
-  const { span } = React.DOM;
-
-  /**
-   * Used to render a map of values provided as a grip.
-   */
-  let NamedNodeMap = React.createClass({
-    displayName: "NamedNodeMap",
-
-    propTypes: {
-      object: React.PropTypes.object.isRequired,
-      mode: React.PropTypes.string,
-      provider: React.PropTypes.object,
-    },
-
-    getLength: function (object) {
-      return object.preview.length;
-    },
-
-    getTitle: function (object) {
-      if (this.props.objectLink && object.class) {
-        return this.props.objectLink({
-          object: object
-        }, object.class);
-      }
-      return object.class ? object.class : "";
-    },
-
-    getItems: function (array, max) {
-      let items = this.propIterator(array, max);
-
-      items = items.map(item => PropRep(item));
-
-      if (items.length > max + 1) {
-        items.pop();
-        let objectLink = this.props.objectLink || span;
-        items.push(Caption({
-          key: "more",
-          object: objectLink({
-            object: this.props.object
-          }, "more…")
-        }));
-      }
-
-      return items;
-    },
-
-    propIterator: function (grip, max) {
-      max = max || 3;
-
-      let props = [];
-
-      let provider = this.props.provider;
-      if (!provider) {
-        return props;
-      }
-
-      let ownProperties = grip.preview ? grip.preview.ownProperties : [];
-      for (let name in ownProperties) {
-        if (props.length > max) {
-          break;
-        }
-
-        let item = ownProperties[name];
-        let label = provider.getLabel(item);
-        let value = provider.getValue(item);
-
-        props.push(Object.assign({}, this.props, {
-          name: label,
-          object: value,
-          equal: ": ",
-          delim: ", ",
-        }));
-      }
-
-      return props;
-    },
-
-    render: function () {
-      let grip = this.props.object;
-      let mode = this.props.mode;
-
-      let items;
-      if (mode == "tiny") {
-        items = this.getLength(grip);
-      } else {
-        let max = (mode == "short") ? 3 : 100;
-        items = this.getItems(grip, max);
-      }
-
-      let objectLink = this.props.objectLink || span;
-
-      return (
-        ObjectBox({className: "NamedNodeMap"},
-          this.getTitle(grip),
-          objectLink({
-            className: "arrayLeftBracket",
-            role: "presentation",
-            object: grip
-          }, "["),
-          items,
-          objectLink({
-            className: "arrayRightBracket",
-            role: "presentation",
-            object: grip
-          }, "]")
-        )
-      );
-    },
-  });
-
-  /**
-   * Property for a grip object.
-   */
-  let PropRep = React.createFactory(React.createClass({
-    displayName: "PropRep",
-
-    propTypes: {
-      equal: React.PropTypes.string,
-      delim: React.PropTypes.string,
-    },
-
-    render: function () {
-      const { Rep } = createFactories(require("./rep"));
-
-      return (
-        span({},
-          span({
-            className: "nodeName"},
-            "$prop.name"
-          ),
-          span({
-            className: "objectEqual",
-            role: "presentation"},
-            this.props.equal
-          ),
-          Rep(this.props),
-          span({
-            className: "objectComma",
-            role: "presentation"},
-            this.props.delim
-          )
-        )
-      );
-    }
-  }));
-
-  // Registration
-
-  function supportsObject(grip, type) {
-    if (!isGrip(grip)) {
-      return false;
-    }
-
-    return (type == "NamedNodeMap" && grip.preview);
-  }
-
-  // Exports from this module
-  exports.NamedNodeMap = {
-    rep: NamedNodeMap,
-    supportsObject: supportsObject
-  };
-});
--- a/devtools/client/shared/components/reps/rep.js
+++ b/devtools/client/shared/components/reps/rep.js
@@ -22,17 +22,16 @@ define(function (require, exports, modul
   const { Obj } = require("./object");
 
   // DOM types (grips)
   const { Attribute } = require("./attribute");
   const { DateTime } = require("./date-time");
   const { Document } = require("./document");
   const { Event } = require("./event");
   const { Func } = require("./function");
-  const { NamedNodeMap } = require("./named-node-map");
   const { RegExp } = require("./regexp");
   const { StyleSheet } = require("./stylesheet");
   const { TextNode } = require("./text-node");
   const { Window } = require("./window");
   const { ObjectWithText } = require("./object-with-text");
   const { ObjectWithURL } = require("./object-with-url");
   const { GripArray } = require("./grip-array");
   const { Grip } = require("./grip");
@@ -41,17 +40,16 @@ define(function (require, exports, modul
   // XXX there should be a way for extensions to register a new
   // or modify an existing rep.
   let reps = [
     RegExp,
     StyleSheet,
     Event,
     DateTime,
     TextNode,
-    NamedNodeMap,
     Attribute,
     Func,
     ArrayRep,
     Document,
     Window,
     ObjectWithText,
     ObjectWithURL,
     GripArray,
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -27,33 +27,35 @@ window.onload = Task.async(function* () 
   try {
     yield testBasic();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanShortMaxProps();
     yield testMoreThanLongMaxProps();
     yield testRecursiveArray();
+
+    yield testNamedNodeMap();
   } catch(e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 
   function testBasic() {
     // Test array: `[]`
     const testName = "testBasic";
 
     // Test that correct rep is chosen
     const gripStub = getGripStub("testBasic");
     const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
     is(renderedRep.type, GripArray.rep, `Rep correctly selects ${GripArray.rep.displayName}`);
 
     // Test rendering
-    const defaultOutput = `[]`;
+    const defaultOutput = `Array[]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -71,17 +73,17 @@ window.onload = Task.async(function* () 
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMaxProps() {
     // Test array: `[1, "foo", {}]`;
     const testName = "testMaxProps";
 
-    const defaultOutput = `[1, "foo", Object]`;
+    const defaultOutput = `Array[1, "foo", Object]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -99,46 +101,46 @@ window.onload = Task.async(function* () 
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanShortMaxProps() {
     // Test array = `["test string"…] //4 items`
     const testName = "testMoreThanShortMaxProps";
 
-    const defaultOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
+    const defaultOutput = `Array[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
         expectedOutput: `[${maxLength.short + 1}]`,
       },
       {
         mode: "short",
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
-        expectedOutput: `[${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}]`,
+        expectedOutput: `Array[${Array(maxLength.short + 1).fill("\"test string\"").join(", ")}]`,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanLongMaxProps() {
     // Test array = `["test string"…] //301 items`
     const testName = "testMoreThanLongMaxProps";
 
-    const defaultShortOutput = `[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
-    const defaultLongOutput = `[${Array(maxLength.long).fill("\"test string\"").join(", ")}, more…]`;
+    const defaultShortOutput = `Array[${Array(maxLength.short).fill("\"test string\"").join(", ")}, more…]`;
+    const defaultLongOutput = `Array[${Array(maxLength.long).fill("\"test string\"").join(", ")}, more…]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultShortOutput,
       },
       {
         mode: "tiny",
@@ -159,17 +161,17 @@ window.onload = Task.async(function* () 
 
   function testRecursiveArray() {
     // @TODO This is not how this feature should actually work
     // See Bug 1282465 - Reps: fix or remove recursive handling in grip-array
 
     // Test array = `let a = []; a = [a]`
     const testName = "testRecursiveArray";
 
-    const defaultOutput = `[[1]]`;
+    const defaultOutput = `Array[[1]]`;
 
     const modeTests = [
       {
         mode: undefined,
         expectedOutput: defaultOutput,
       },
       {
         mode: "tiny",
@@ -183,16 +185,43 @@ window.onload = Task.async(function* () 
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
     testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
+  function testNamedNodeMap() {
+    const testName = "testNamedNodeMap";
+
+    const defaultOutput = `NamedNodeMap[class="myclass", cellpadding="7", border="3"]`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "tiny",
+        expectedOutput: `[3]`,
+      },
+      {
+        mode: "short",
+        expectedOutput: defaultOutput,
+      },
+      {
+        mode: "long",
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Array",
           "actor": "server1.conn0.obj35",
           "extensible": true,
@@ -306,15 +335,77 @@ window.onload = Task.async(function* () 
                 "preview": {
                   "kind": "ArrayLike",
                   "length": 1
                 }
               }
             ]
           }
         };
+
+        case "testNamedNodeMap":
+          return {
+            "type": "object",
+            "class": "NamedNodeMap",
+            "actor": "server1.conn3.obj42",
+            "extensible": true,
+            "frozen": false,
+            "sealed": false,
+            "ownPropertyLength": 6,
+            "preview": {
+              "kind": "ArrayLike",
+              "length": 3,
+              "items": [
+                {
+                  "type": "object",
+                  "class": "Attr",
+                  "actor": "server1.conn3.obj43",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 2,
+                    "nodeName": "class",
+                    "value": "myclass"
+                  }
+                },
+                {
+                  "type": "object",
+                  "class": "Attr",
+                  "actor": "server1.conn3.obj44",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 2,
+                    "nodeName": "cellpadding",
+                    "value": "7"
+                  }
+                },
+                {
+                  "type": "object",
+                  "class": "Attr",
+                  "actor": "server1.conn3.obj44",
+                  "extensible": true,
+                  "frozen": false,
+                  "sealed": false,
+                  "ownPropertyLength": 0,
+                  "preview": {
+                    "kind": "DOMNode",
+                    "nodeType": 2,
+                    "nodeName": "border",
+                    "value": "3"
+                  }
+                }
+              ]
+            }
+          };
     }
   }
 });
 </script>
 </pre>
 </body>
 </html>
--- a/devtools/client/shared/moz.build
+++ b/devtools/client/shared/moz.build
@@ -15,18 +15,16 @@ DIRS += [
     'widgets',
 ]
 
 DevToolsModules(
     'AppCacheUtils.jsm',
     'autocomplete-popup.js',
     'browser-loader.js',
     'css-angle.js',
-    'css-color-db.js',
-    'css-color.js',
     'css-reload.js',
     'Curl.jsm',
     'demangle.js',
     'developer-toolbar.js',
     'devices.js',
     'devtools-file-watcher.js',
     'DOMHelpers.jsm',
     'doorhanger.js',
--- a/devtools/client/shared/output-parser.js
+++ b/devtools/client/shared/output-parser.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 {Cc, Ci} = require("chrome");
 const {angleUtils} = require("devtools/client/shared/css-angle");
-const {colorUtils} = require("devtools/client/shared/css-color");
+const {colorUtils} = require("devtools/shared/css-color");
 const {getCSSLexer} = require("devtools/shared/css-lexer");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {
   ANGLE_TAKING_FUNCTIONS,
   BEZIER_KEYWORDS,
   COLOR_TAKING_FUNCTIONS,
   CSS_TYPES
 } = require("devtools/shared/css-properties-db");
--- a/devtools/client/shared/telemetry.js
+++ b/devtools/client/shared/telemetry.js
@@ -182,16 +182,20 @@ Telemetry.prototype = {
     menueyedropper: {
       histogram: "DEVTOOLS_MENU_EYEDROPPER_OPENED_COUNT",
       userHistogram: "DEVTOOLS_MENU_EYEDROPPER_OPENED_PER_USER_FLAG",
     },
     pickereyedropper: {
       histogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT",
       userHistogram: "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG",
     },
+    toolbareyedropper: {
+      histogram: "DEVTOOLS_TOOLBAR_EYEDROPPER_OPENED_COUNT",
+      userHistogram: "DEVTOOLS_TOOLBAR_EYEDROPPER_OPENED_PER_USER_FLAG",
+    },
     developertoolbar: {
       histogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_COUNT",
       userHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_OPENED_PER_USER_FLAG",
       timerHistogram: "DEVTOOLS_DEVELOPERTOOLBAR_TIME_ACTIVE_SECONDS"
     },
     aboutdebugging: {
       histogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_COUNT",
       userHistogram: "DEVTOOLS_ABOUTDEBUGGING_OPENED_PER_USER_FLAG",
--- a/devtools/client/shared/test/browser_css_color.js
+++ b/devtools/client/shared/test/browser_css_color.js
@@ -1,13 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const TEST_URI = "data:text/html;charset=utf-8,browser_css_color.js";
-var {colorUtils} = require("devtools/client/shared/css-color");
+var {colorUtils} = require("devtools/shared/css-color");
 var origColorUnit;
 
 add_task(function* () {
   yield addTab("about:blank");
   let [host, win, doc] = yield createHost("bottom", TEST_URI);
 
   info("Creating a test canvas element to test colors");
   let canvas = createTestCanvas(doc);
--- a/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
+++ b/devtools/client/shared/test/browser_tableWidget_mouse_interaction.js
@@ -197,46 +197,50 @@ var testMouseInteraction = Task.async(fu
 
   is(table.menupopup.querySelectorAll("[disabled]").length, 1,
      "Only 1 menuitem is disabled");
   is(table.menupopup.querySelector("[disabled]"),
      table.menupopup.querySelector("[data-id='col1']"),
      "Which is the unique column");
   // popup should be open now
   // clicking on second column label
+  let onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col2']");
   info("selecting to hide the second column");
   ok(!table.tbody.children[2].hasAttribute("hidden"),
      "Column is not hidden before hiding it");
   click(node);
   id = yield event;
+  yield onPopupHidden;
   is(id, "col2", "Correct column was triggered to be hidden");
   is(table.tbody.children[2].getAttribute("hidden"), "true",
      "Column is hidden after hiding it");
 
   // hiding third column
   // event listener for popupshown
   info("right clicking on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
   onPopupShown = once(table.menupopup, "popupshown");
   click(node, 2);
   yield onPopupShown;
 
   is(table.menupopup.querySelectorAll("[disabled]").length, 1,
      "Only 1 menuitem is disabled");
   // popup should be open now
   // clicking on second column label
+  onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col3']");
   info("selecting to hide the second column");
   ok(!table.tbody.children[4].hasAttribute("hidden"),
      "Column is not hidden before hiding it");
   click(node);
   id = yield event;
+  yield onPopupHidden;
   is(id, "col3", "Correct column was triggered to be hidden");
   is(table.tbody.children[4].getAttribute("hidden"), "true",
      "Column is hidden after hiding it");
 
   // opening again to see if 2 items are disabled now
   // event listener for popupshown
   info("right clicking on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
@@ -251,44 +255,48 @@ var testMouseInteraction = Task.async(fu
      "First is the unique column");
   is(table.menupopup.querySelectorAll("[disabled]")[1],
      table.menupopup.querySelector("[data-id='col4']"),
      "Second is the last column");
 
   // showing back 2nd column
   // popup should be open now
   // clicking on second column label
+  onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col2']");
   info("selecting to hide the second column");
   is(table.tbody.children[2].getAttribute("hidden"), "true",
      "Column is hidden before unhiding it");
   click(node);
   id = yield event;
+  yield onPopupHidden;
   is(id, "col2", "Correct column was triggered to be hidden");
   ok(!table.tbody.children[2].hasAttribute("hidden"),
      "Column is not hidden after unhiding it");
 
   // showing back 3rd column
   // event listener for popupshown
   info("right clicking on the first column header");
   node = table.tbody.firstChild.firstChild.firstChild;
   onPopupShown = once(table.menupopup, "popupshown");
   click(node, 2);
   yield onPopupShown;
 
   // popup should be open now
   // clicking on second column label
+  onPopupHidden = once(table.menupopup, "popuphidden");
   event = table.once(TableWidget.EVENTS.HEADER_CONTEXT_MENU);
   node = table.menupopup.querySelector("[data-id='col3']");
   info("selecting to hide the second column");
   is(table.tbody.children[4].getAttribute("hidden"), "true",
      "Column is hidden before unhiding it");
   click(node);
   id = yield event;
+  yield onPopupHidden;
   is(id, "col3", "Correct column was triggered to be hidden");
   ok(!table.tbody.children[4].hasAttribute("hidden"),
      "Column is not hidden after unhiding it");
 
   // reset table state
   table.clearSelection();
   table.sortBy("col1");
 });
--- a/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
+++ b/devtools/client/shared/test/browser_telemetry_button_eyedropper.js
@@ -1,61 +1,55 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-const TEST_URI = "data:text/html;charset=utf-8," +
-  "<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
-
-var {EyedropperManager} = require("devtools/client/eyedropper/eyedropper");
-
-add_task(function* () {
-  yield addTab(TEST_URI);
-  let Telemetry = loadTelemetryAndRecordLogs();
-
-  let target = TargetFactory.forTab(gBrowser.selectedTab);
-  let toolbox = yield gDevTools.showToolbox(target, "inspector");
-  info("inspector opened");
-
-  info("testing the eyedropper button");
-  yield testButton(toolbox, Telemetry);
-
-  stopRecordingTelemetryLogs(Telemetry);
-  yield gDevTools.closeToolbox(target);
-  gBrowser.removeCurrentTab();
-});
-
-function* testButton(toolbox, Telemetry) {
-  let button = toolbox.doc.querySelector("#command-button-eyedropper");
-  ok(button, "Captain, we have the eyedropper button");
-
-  let clicked = toolbox._requisition.commandOutputManager.onOutput.once();
-
-  info("clicking the button to open the eyedropper");
-  button.click();
-
-  yield clicked;
-
-  checkResults("_EYEDROPPER_", Telemetry);
-}
-
-function checkResults(histIdFocus, Telemetry) {
-  let result = Telemetry.prototype.telemetryInfo;
-
-  for (let [histId, value] of Iterator(result)) {
-    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
-        !histId.includes(histIdFocus)) {
-      // Inspector stats are tested in
-      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
-      // because we only open the inspector once for this test.
-      continue;
-    }
-
-    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
-      ok(value.length === 1 && value[0] === true,
-         "Per user value " + histId + " has a single value of true");
-    } else if (histId.endsWith("OPENED_COUNT")) {
-      is(value.length, 1, histId + " has one entry");
-
-      let okay = value.every(element => element === true);
-      ok(okay, "All " + histId + " entries are === true");
-    }
-  }
-}
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
+
+const TEST_URI = "data:text/html;charset=utf-8," +
+  "<p>browser_telemetry_button_eyedropper.js</p><div>test</div>";
+
+add_task(function* () {
+  yield addTab(TEST_URI);
+  let Telemetry = loadTelemetryAndRecordLogs();
+
+  let target = TargetFactory.forTab(gBrowser.selectedTab);
+  let toolbox = yield gDevTools.showToolbox(target, "inspector");
+  info("inspector opened");
+
+  info("testing the eyedropper button");
+  yield testButton(toolbox, Telemetry);
+
+  stopRecordingTelemetryLogs(Telemetry);
+  yield gDevTools.closeToolbox(target);
+  gBrowser.removeCurrentTab();
+});
+
+function* testButton(toolbox, Telemetry) {
+  info("Calling the eyedropper button's callback");
+  // We call the button callback directly because we don't need to test the UI here, we're
+  // only concerned about testing the telemetry probe.
+  yield toolbox.getPanel("inspector").showEyeDropper();
+
+  checkResults("_EYEDROPPER_", Telemetry);
+}
+
+function checkResults(histIdFocus, Telemetry) {
+  let result = Telemetry.prototype.telemetryInfo;
+
+  for (let [histId, value] of Iterator(result)) {
+    if (histId.startsWith("DEVTOOLS_INSPECTOR_") ||
+        !histId.includes(histIdFocus)) {
+      // Inspector stats are tested in
+      // browser_telemetry_toolboxtabs_{toolname}.js so we skip them here
+      // because we only open the inspector once for this test.
+      continue;
+    }
+
+    if (histId.endsWith("OPENED_PER_USER_FLAG")) {
+      ok(value.length === 1 && value[0] === true,
+         "Per user value " + histId + " has a single value of true");
+    } else if (histId.endsWith("OPENED_COUNT")) {
+      is(value.length, 1, histId + " has one entry");
+
+      let okay = value.every(element => element === true);
+      ok(okay, "All " + histId + " entries are === true");
+    }
+  }
+}
--- a/devtools/client/shared/test/unit/test_cssColor.js
+++ b/devtools/client/shared/test/unit/test_cssColor.js
@@ -5,17 +5,17 @@
 
 "use strict";
 
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 
 var {require, loader} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-const {colorUtils} = require("devtools/client/shared/css-color");
+const {colorUtils} = require("devtools/shared/css-color");
 
 loader.lazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 const CLASSIFY_TESTS = [
   { input: "rgb(255,0,192)", output: "rgb" },
   { input: "RGB(255,0,192)", output: "rgb" },
--- a/devtools/client/shared/test/unit/test_cssColorDatabase.js
+++ b/devtools/client/shared/test/unit/test_cssColorDatabase.js
@@ -8,18 +8,18 @@
 var Cu = Components.utils;
 var Ci = Components.interfaces;
 var Cc = Components.classes;
 
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 
 const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 
-const {colorUtils} = require("devtools/client/shared/css-color");
-const {cssColors} = require("devtools/client/shared/css-color-db");
+const {colorUtils} = require("devtools/shared/css-color");
+const {cssColors} = require("devtools/shared/css-color-db");
 
 function isValid(colorName) {
   ok(colorUtils.isValidCSSColor(colorName),
      colorName + " is valid in database");
   ok(DOMUtils.isValidCSSColor(colorName),
      colorName + " is valid in DOMUtils");
 }
 
--- a/devtools/client/shared/widgets/Tooltip.js
+++ b/devtools/client/shared/widgets/Tooltip.js
@@ -7,21 +7,18 @@
 const {Ci} = require("chrome");
 const defer = require("devtools/shared/defer");
 const {Spectrum} = require("devtools/client/shared/widgets/Spectrum");
 const {CubicBezierWidget} =
       require("devtools/client/shared/widgets/CubicBezierWidget");
 const {CSSFilterEditorWidget} = require("devtools/client/shared/widgets/FilterWidget");
 const {TooltipToggle} = require("devtools/client/shared/widgets/tooltip/TooltipToggle");
 const EventEmitter = require("devtools/shared/event-emitter");
-const {colorUtils} = require("devtools/client/shared/css-color");
+const {colorUtils} = require("devtools/shared/css-color");
 const Heritage = require("sdk/core/heritage");
-const {Eyedropper} = require("devtools/client/eyedropper/eyedropper");
-const {gDevTools} = require("devtools/client/framework/devtools");
-const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/HTMLTooltip");
 const {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 
 loader.lazyRequireGetter(this, "beautify", "devtools/shared/jsbeautify/beautify");
 loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
 loader.lazyRequireGetter(this, "clearNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
 loader.lazyRequireGetter(this, "setNamedTimeout", "devtools/client/shared/widgets/view-helpers", true);
@@ -741,21 +738,25 @@ SwatchBasedEditorTooltip.prototype = {
  * The swatch color picker tooltip class is a specific class meant to be used
  * along with output-parser's generated color swatches.
  * It extends the parent SwatchBasedEditorTooltip class.
  * It just wraps a standard Tooltip and sets its content with an instance of a
  * color picker.
  *
  * @param {Toolbox} toolbox
  *        The devtools toolbox, needed to get the devtools main window.
+ * @param {InspectorPanel} inspector
+ *        The inspector panel, needed for the eyedropper.
  */
-function SwatchColorPickerTooltip(toolbox) {
+function SwatchColorPickerTooltip(toolbox, inspector) {
   let stylesheet = "chrome://devtools/content/shared/widgets/spectrum.css";
   SwatchBasedEditorTooltip.call(this, toolbox, stylesheet);
 
+  this.inspector = inspector;
+
   // Creating a spectrum instance. this.spectrum will always be a promise that
   // resolves to the spectrum instance
   this.spectrum = this.setColorPickerContent([0, 0, 0, 1]);
   this._onSpectrumColorChange = this._onSpectrumColorChange.bind(this);
   this._openEyeDropper = this._openEyeDropper.bind(this);
 }
 
 module.exports.SwatchColorPickerTooltip = SwatchColorPickerTooltip;
@@ -774,17 +775,17 @@ Heritage.extend(SwatchBasedEditorTooltip
     let spectrumNode = doc.createElementNS(XHTML_NS, "div");
     spectrumNode.id = "spectrum";
     container.appendChild(spectrumNode);
     let eyedropper = doc.createElementNS(XHTML_NS, "button");
     eyedropper.id = "eyedropper-button";
     eyedropper.className = "devtools-button";
     container.appendChild(eyedropper);
 
-    this.tooltip.setContent(container, { width: 210, height: 216 });
+    this.tooltip.setContent(container, { width: 218, height: 224 });
 
     let spectrum = new Spectrum(spectrumNode, color);
 
     // Wait for the tooltip to be shown before calling spectrum.show
     // as it expect to be visible in order to compute DOM element sizes.
     this.tooltip.once("shown", () => {
       spectrum.show();
     });
@@ -805,18 +806,26 @@ Heritage.extend(SwatchBasedEditorTooltip
       this._originalColor = this.currentSwatchColor.textContent;
       let color = this.activeSwatch.style.backgroundColor;
       this.spectrum.off("changed", this._onSpectrumColorChange);
       this.spectrum.rgb = this._colorToRgba(color);
       this.spectrum.on("changed", this._onSpectrumColorChange);
       this.spectrum.updateUI();
     }
 
-    let eyeButton = this.tooltip.doc.querySelector("#eyedropper-button");
-    eyeButton.addEventListener("click", this._openEyeDropper);
+    let {target} = this.inspector.toolbox;
+    target.actorHasMethod("inspector", "pickColorFromPage").then(value => {
+      let tooltipDoc = this.tooltip.doc;
+      let eyeButton = tooltipDoc.querySelector("#eyedropper-button");
+      if (value) {
+        eyeButton.addEventListener("click", this._openEyeDropper);
+      } else {
+        eyeButton.style.display = "none";
+      }
+    }, e => console.error(e));
   },
 
   _onSpectrumColorChange: function (event, rgba, cssColor) {
     this._selectColor(cssColor);
   },
 
   _selectColor: function (color) {
     if (this.activeSwatch) {
@@ -829,65 +838,53 @@ Heritage.extend(SwatchBasedEditorTooltip
 
       if (this.eyedropperOpen) {
         this.commit();
       }
     }
   },
 
   _openEyeDropper: function () {
-    let chromeWindow = this.tooltip.doc.defaultView.top;
-    let windowType = chromeWindow.document.documentElement
-                     .getAttribute("windowtype");
-    let toolboxWindow;
-    if (windowType != gDevTools.chromeWindowType) {
-      // this means the toolbox is in a seperate window. We need to make
-      // sure we'll be inspecting the browser window instead
-      toolboxWindow = chromeWindow;
-      chromeWindow = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
-      chromeWindow.focus();
-    }
-    let dropper = new Eyedropper(chromeWindow, { copyOnSelect: false,
-                                                 context: "picker" });
+    let {inspector, toolbox, telemetry} = this.inspector;
+    telemetry.toolOpened("pickereyedropper");
+    inspector.pickColorFromPage({copyOnSelect: false}).catch(e => console.error(e));
 
-    dropper.once("select", (event, color) => {
-      if (toolboxWindow) {
-        toolboxWindow.focus();
-      }
+    inspector.once("color-picked", color => {
+      toolbox.win.focus();
       this._selectColor(color);
     });
 
-    dropper.once("destroy", () => {
+    inspector.once("color-pick-canceled", () => {
       this.eyedropperOpen = false;
       this.activeSwatch = null;
     });
 
-    dropper.open();
     this.eyedropperOpen = true;
 
     // close the colorpicker tooltip so that only the eyedropper is open.
     this.hide();
 
-    this.tooltip.emit("eyedropper-opened", dropper);
+    this.tooltip.emit("eyedropper-opened");
   },
 
   _colorToRgba: function (color) {
     color = new colorUtils.CssColor(color);
     let rgba = color._getRGBATuple();
     return [rgba.r, rgba.g, rgba.b, rgba.a];
   },
 
   _toDefaultType: function (color) {
     let colorObj = new colorUtils.CssColor(color);
     colorObj.setAuthoredUnitFromColor(this._originalColor);
     return colorObj.toString();
   },
 
   destroy: function () {
     SwatchBasedEditorTooltip.prototype.destroy.call(this);
+    this.inspector = null;
     this.currentSwatchColor = null;
     this.spectrum.off("changed", this._onSpectrumColorChange);
     this.spectrum.destroy();
   }
 });
 
 /**
  * The swatch cubic-bezier tooltip class is a specific class meant to be used
--- a/devtools/client/shared/widgets/cubic-bezier.css
+++ b/devtools/client/shared/widgets/cubic-bezier.css
@@ -2,21 +2,22 @@
  * 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/. */
 
 /* Based on Lea Verou www.cubic-bezier.com
    See https://github.com/LeaVerou/cubic-bezier */
 
 .cubic-bezier-container {
   display: flex;
-  width: 500px;
+  width: 510px;
   height: 370px;
   flex-direction: row-reverse;
   overflow: hidden;
   padding: 5px;
+  box-sizing: border-box;
 }
 
 .display-wrap {
   width: 50%;
   height: 100%;
   text-align: center;
   overflow: hidden;
 }
@@ -25,61 +26,62 @@
 
 .coordinate-plane {
   width: 150px;
   height: 370px;
   margin: 0 auto;
   position: relative;
 }
 
-.theme-dark .coordinate-plane:before,
-.theme-dark .coordinate-plane:after {
-  border-color: #eee;
-}
-
 .control-point {
   position: absolute;
   z-index: 1;
   height: 10px;
   width: 10px;
   border: 0;
   background: #666;
   display: block;
   margin: -5px 0 0 -5px;
   outline: none;
   border-radius: 5px;
   padding: 0;
   cursor: pointer;
 }
 
 .display-wrap {
-  background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.05) 0, rgba(0, 0, 0, 0.05) 1px, transparent 1px, transparent 15px) no-repeat;
+  background:
+  repeating-linear-gradient(0deg,
+    transparent,
+    var(--bezier-grid-color) 0,
+    var(--bezier-grid-color) 1px,
+    transparent 1px,
+    transparent 15px) no-repeat,
+  repeating-linear-gradient(90deg,
+    transparent,
+    var(--bezier-grid-color) 0,
+    var(--bezier-grid-color) 1px,
+    transparent 1px,
+    transparent 15px) no-repeat;
   background-size: 100% 100%, 100% 100%;
   background-position: -2px 5px, -2px 5px;
 
   -moz-user-select: none;
 }
 
-.theme-dark .display-wrap {
-  background: repeating-linear-gradient(0deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat, repeating-linear-gradient(90deg, transparent, rgba(0, 0, 0, 0.2) 0, rgba(0, 0, 0, 0.2) 1px, transparent 1px, transparent 15px) no-repeat;
-  background-size: 100% 100%, 100% 100%;
-  background-position: -2px 5px, -2px 5px;
-
-  -moz-user-select: none;
-}
 canvas.curve {
-  background: linear-gradient(-45deg, transparent 49.7%, rgba(0,0,0,.2) 49.7%, rgba(0,0,0,.2) 50.3%, transparent 50.3%) center no-repeat;
+  background:
+    linear-gradient(-45deg,
+      transparent 49.7%,
+      var(--bezier-diagonal-color) 49.7%,
+      var(--bezier-diagonal-color) 50.3%,
+      transparent 50.3%) center no-repeat;
   background-size: 100% 100%;
   background-position: 0 0;
 }
 
-.theme-dark canvas.curve {
-  background: linear-gradient(-45deg, transparent 49.7%, #eee 49.7%, #eee 50.3%, transparent 50.3%) center no-repeat;
-}
-
 /* Timing Function Preview Widget */
 
 .timing-function-preview {
   position: absolute;
   bottom: 20px;
   right: 45px;
   width: 150px;
 }
@@ -179,26 +181,22 @@ canvas.curve {
   cursor: pointer;
   width: 33.33%;
   margin: 5px 0px;
   text-align: center;
 }
 
 .preset canvas {
   display: block;
-  border: 1px solid #ccc;
+  border: 1px solid var(--theme-splitter-color);
   border-radius: 3px;
   background-color: var(--theme-body-background);
   margin: 0 auto;
 }
 
-.theme-dark .preset canvas {
-  border-color: #444e58;
-}
-
 .preset p {
   font-size: 80%;
   margin: 2px auto 0px auto;
   color: var(--theme-body-color-alt);
   text-transform: capitalize;
   text-overflow: ellipsis;
   overflow: hidden;
 }
@@ -206,13 +204,13 @@ canvas.curve {
 .active-preset p, .active-preset:hover p {
   color: var(--theme-body-color);
 }
 
 .preset:hover canvas {
   border-color: var(--theme-selection-background);
 }
 
-.active-preset canvas, .active-preset:hover canvas,
-.theme-dark .active-preset canvas, .theme-dark .preset:hover canvas {
+.active-preset canvas,
+.active-preset:hover canvas {
   background-color: var(--theme-selection-background-semitransparent);
   border-color: var(--theme-selection-background);
 }
--- a/devtools/client/shared/widgets/filter-widget.css
+++ b/devtools/client/shared/widgets/filter-widget.css
@@ -1,19 +1,21 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Main container: Displays the filters and presets in 2 columns */
 
 #filter-container {
-  height: 100%;
+  width: 510px;
+  height: 200px;
   display: flex;
   position: relative;
   padding: 5px;
+  box-sizing: border-box;
   /* when opened in a xul:panel, a gray color is applied to text */
   color: var(--theme-body-color);
 }
 
 #filter-container.dragging {
   -moz-user-select: none;
 }
 
@@ -133,22 +135,16 @@
 }
 
 /* Fix the size of inputs */
 /* Especially needed on Linux where input are bigger */
 input {
   width: 8em;
 }
 
-.theme-light .add,
-.theme-light .remove-button,
-.theme-light #toggle-presets {
-  filter: invert(1);
-}
-
 .preset {
   display: flex;
   margin-bottom: 10px;
   cursor: pointer;
   padding: 3px 5px;
 
   flex-direction: row;
   flex-wrap: wrap;
@@ -169,20 +165,16 @@ input {
 .preset:hover {
   background: var(--theme-selection-background);
 }
 
 .preset:hover label, .preset:hover span {
   color: var(--theme-selection-color);
 }
 
-.theme-light .preset:hover .remove-button {
-  filter: invert(0);
-}
-
 .preset .remove-button {
   order: 2;
 }
 
 .preset span {
   flex: 2 100%;
   white-space: nowrap;
   overflow: hidden;
@@ -233,11 +225,17 @@ input {
 .add {
   background: url(chrome://devtools/skin/images/add.svg);
 }
 
 #toggle-presets {
   background: url(chrome://devtools/skin/images/pseudo-class.svg);
 }
 
+.add,
+.remove-button,
+#toggle-presets {
+  filter: var(--icon-filter);
+}
+
 .show-presets #toggle-presets {
   filter: url(chrome://devtools/skin/images/filters.svg#checked-icon-state);
 }
--- a/devtools/client/shared/widgets/spectrum.css
+++ b/devtools/client/shared/widgets/spectrum.css
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #eyedropper-button {
   margin-inline-start: 5px;
   display: block;
 }
 
 #eyedropper-button::before {
-  background-image: url("chrome://devtools/skin/images/command-eyedropper.svg");
+  background-image: url(chrome://devtools/skin/images/command-eyedropper.svg);
 }
 
 /* Mix-in classes */
 
 .spectrum-checker {
   background-color: #eee;
   background-image: linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc),
     linear-gradient(45deg, #ccc 25%, transparent 25%, transparent 75%, #ccc 75%, #ccc);
@@ -32,16 +32,20 @@
 .spectrum-box {
   border: 1px solid rgba(0,0,0,0.2);
   border-radius: 2px;
   background-clip: content-box;
 }
 
 /* Elements */
 
+#spectrum-tooltip {
+  padding: 4px;
+}
+
 .spectrum-container {
   position: relative;
   display: none;
   top: 0;
   left: 0;
   border-radius: 0;
   width: 200px;
   padding: 5px;
--- a/devtools/client/sourceeditor/debugger.js
+++ b/devtools/client/sourceeditor/debugger.js
@@ -185,17 +185,25 @@ function addBreakpoint(ctx, line, cond) 
 function removeBreakpoints(ctx) {
   let { ed, cm } = ctx;
 
   let meta = dbginfo.get(ed);
   if (meta.breakpoints != null) {
     meta.breakpoints = {};
   }
 
-  cm.doc.iter((line) => { removeBreakpoint(ctx, line); });
+  cm.doc.iter((line) => {
+    // The hasBreakpoint is a slow operation: checks the line type, whether cm
+    // is initialized and creates several new objects. Inlining the line's
+    // wrapClass property check directly.
+    if (line.wrapClass == null || !line.wrapClass.includes("breakpoint")) {
+      return;
+    }
+    removeBreakpoint(ctx, line);
+  });
 }
 
 /**
  * Removes a visual breakpoint from a specified line and
  * makes Editor emit a breakpointRemoved event.
  */
 function removeBreakpoint(ctx, line) {
   if (!hasBreakpoint(ctx, line)) {
--- a/devtools/client/themes/computed.css
+++ b/devtools/client/themes/computed.css
@@ -6,17 +6,32 @@
 #sidebar-panel-computedview {
   margin: 0;
   display : flex;
   flex-direction: column;
   width: 100%;
   height: 100%;
 }
 
-#sidebar-panel-computedview > .devtools-toolbar {
+#computedview-container {
+  overflow: auto;
+  height: 100%;
+}
+
+/* This extra wrapper only serves as a way to get the content of the view focusable.
+   So that when the user reaches it either via keyboard or mouse, we know that the view
+   is focused and therefore can handle shortcuts.
+   However, for accessibility reasons, tabindex is set to -1 to avoid having to tab
+   through it, and the outline is hidden. */
+#computedview-container-focusable {
+  height: 100%;
+  outline: none;
+}
+
+#computedview-toolbar {
   display: flex;
 }
 
 #browser-style-checkbox {
   /* Bug 1200073 - extra space before the browser styles checkbox so
      they aren't squished together in a small window. Put also
      an extra space after. */
   margin-inline-start: 5px;
@@ -27,27 +42,21 @@
   margin-right: 5px;
 
   /* Vertically center the 'Browser styles' checkbox in the
      Computed panel with its label. */
   display: flex;
   align-items: center;
 }
 
-#computedview-container {
-  overflow: auto;
-}
-
 #propertyContainer {
   -moz-user-select: text;
   overflow-y: auto;
   overflow-x: hidden;
   flex: auto;
-  border-top-width: 1px;
-  border-top-style: dotted;
 }
 
 .row-striped {
   background: var(--theme-body-background);
 }
 
 .property-view-hidden,
 .property-content-hidden {
--- a/devtools/client/themes/inspector.css
+++ b/devtools/client/themes/inspector.css
@@ -1,13 +1,21 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+:root {
+  --eyedropper-image: url(images/command-eyedropper.svg);
+}
+
+.theme-firebug {
+  --eyedropper-image: url(images/firebug/command-eyedropper.svg);
+}
+
 /* Use flex layout for the Inspector toolbar. For now, it's done
    specifically for the Inspector toolbar since general rule applied
    on .devtools-toolbar breaks breadcrubs and also toolbars in other
    panels (e.g. webconsole, debugger), these are not ready for HTML
    layout yet. */
 #inspector-toolbar.devtools-toolbar {
   display: flex;
 }
@@ -73,16 +81,27 @@
 }
 
 #inspector-breadcrumbs .breadcrumbs-widget-item {
   white-space: nowrap;
   flex-shrink: 0;
   font: message-box;
 }
 
+/* Eyedropper toolbar button */
+
+#inspector-eyedropper-toggle {
+  /* hidden by default, until we can check that the required highlighter exists */
+  display: none;
+}
+
+#inspector-eyedropper-toggle::before {
+  background-image: var(--eyedropper-image);
+}
+
 /* Add element toolbar button */
 #inspector-element-add-button::before {
   background-image: url("chrome://devtools/skin/images/add.svg");
   list-style-image: url("chrome://devtools/skin/images/add.svg");
   -moz-user-focus: normal;
 }
 
 /* "no results" warning message displayed in the ruleview and in the computed view */
--- a/devtools/client/themes/layout.css
+++ b/devtools/client/themes/layout.css
@@ -1,12 +1,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/ */
 
+#layout-wrapper {
+  border-bottom-style: solid;
+  border-bottom-width: 1px;
+  border-color: var(--theme-splitter-color);
+}
+
 #layout-container {
   /* The view will grow bigger as the window gets resized, until 400px */
   max-width: 400px;
   margin: 0px auto;
   padding: 0;
 }
 
 /* Header */
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -218,17 +218,16 @@
 .requests-menu-security-and-domain {
   width: 14vw;
 }
 
 .requests-security-state-icon {
   width: 16px;
   height: 16px;
   margin-inline-end: 4px;
-  cursor: pointer;
 }
 
 .security-state-insecure {
   list-style-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
 }
 
 .security-state-secure {
   list-style-image: url(chrome://devtools/skin/images/security-state-secure.svg);
@@ -264,17 +263,16 @@
   font-size: 8px;
   font-weight: bold;
   line-height: 10px;
   border-radius: 3px;
   padding: 0 2px;
   margin: 0;
   margin-inline-end: 3px;
   -moz-user-select: none;
-  cursor: pointer;
 }
 
 .requests-menu-transferred {
   max-width: 8em;
   text-align: center;
   width: 8vw;
 }
 
--- a/devtools/client/themes/rules.css
+++ b/devtools/client/themes/rules.css
@@ -77,16 +77,26 @@
 
 #ruleview-container {
   -moz-user-select: text;
   overflow: auto;
   flex: auto;
   height: 100%;
 }
 
+/* This extra wrapper only serves as a way to get the content of the view focusable.
+   So that when the user reaches it either via keyboard or mouse, we know that the view
+   is focused and therefore can handle shortcuts.
+   However, for accessibility reasons, tabindex is set to -1 to avoid having to tab
+   through it, and the outline is hidden. */
+#ruleview-container-focusable {
+  height: 100%;
+  outline: none;
+}
+
 #ruleview-container.non-interactive {
   pointer-events: none;
   visibility: collapse;
   transition: visibility 0.25s;
 }
 
 .ruleview-code {
   direction: ltr;
--- a/devtools/client/themes/toolbox.css
+++ b/devtools/client/themes/toolbox.css
@@ -12,17 +12,16 @@
   --command-paintflashing-image: url(images/command-paintflashing.svg);
   --command-screenshot-image: url(images/command-screenshot.svg);
   --command-responsive-image: url(images/command-responsivemode.svg);
   --command-scratchpad-image: url(images/command-scratchpad.svg);
   --command-pick-image: url(images/command-pick.svg);
   --command-frames-image: url(images/command-frames.svg);
   --command-splitconsole-image: url(images/command-console.svg);
   --command-noautohide-image: url(images/command-noautohide.svg);
-  --command-eyedropper-image: url(images/command-eyedropper.svg);
   --command-rulers-image: url(images/command-rulers.svg);
   --command-measure-image: url(images/command-measure.svg);
 }
 
 .theme-firebug {
   --close-button-image: url(chrome://devtools/skin/images/firebug/close.svg);
   --dock-bottom-image: url(chrome://devtools/skin/images/firebug/dock-bottom.svg);
   --dock-side-image: url(chrome://devtools/skin/images/firebug/dock-side.svg);
@@ -31,17 +30,16 @@
   --command-paintflashing-image: url(images/firebug/command-paintflashing.svg);
   --command-screenshot-image: url(images/firebug/command-screenshot.svg);
   --command-responsive-image: url(images/firebug/command-responsivemode.svg);
   --command-scratchpad-image: url(images/firebug/command-scratchpad.svg);
   --command-pick-image: url(images/firebug/command-pick.svg);
   --command-frames-image: url(images/firebug/command-frames.svg);
   --command-splitconsole-image: url(images/firebug/command-console.svg);
   --command-noautohide-image: url(images/firebug/command-noautohide.svg);
-  --command-eyedropper-image: url(images/firebug/command-eyedropper.svg);
   --command-rulers-image: url(images/firebug/command-rulers.svg);
   --command-measure-image: url(images/firebug/command-measure.svg);
 }
 
 /* Toolbox tabbar */
 
 .devtools-tabbar {
   -moz-appearance: none;
@@ -377,9 +375,28 @@
 .toolbox-panel {
   display: -moz-box;
   -moz-box-flex: 1;
   visibility: collapse;
 }
 
 .toolbox-panel[selected] {
   visibility: visible;
-}
\ No newline at end of file
+}
+
+/**
+ * When panels are collapsed or hidden, making sure that they are also
+ * inaccessible by keyboard. This is not the case by default because the are
+ * predominantly hidden using visibility: collapse; style or collapsed
+ * attribute.
+ */
+.toolbox-panel *,
+#toolbox-panel-webconsole[collapsed] * {
+  -moz-user-focus: ignore;
+}
+
+/**
+ * Enrure that selected toolbox panel's contents are keyboard accessible as they
+ * are explicitly made not to be when hidden (default).
+ */
+.toolbox-panel[selected] * {
+  -moz-user-focus: normal;
+}
--- a/devtools/client/themes/tooltips.css
+++ b/devtools/client/themes/tooltips.css
@@ -1,13 +1,25 @@
 /* vim:set ts=2 sw=2 sts=2 et: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+/* Tooltip specific theme variables */
+
+.theme-dark {
+  --bezier-diagonal-color: #eee;
+  --bezier-grid-color: rgba(0, 0, 0, 0.2);
+}
+
+.theme-light {
+  --bezier-diagonal-color: rgba(0, 0, 0, 0.2);
+  --bezier-grid-color: rgba(0, 0, 0, 0.05);
+}
+
 /* Tooltip widget (see devtools/client/shared/widgets/Tooltip.js) */
 
 .devtools-tooltip .panel-arrowcontent {
   padding: 4px;
 }
 
 .devtools-tooltip .panel-arrowcontainer {
   /* Reseting the transition used when panels are shown */
--- a/devtools/server/actors/highlighters.css
+++ b/devtools/server/actors/highlighters.css
@@ -395,8 +395,72 @@
 :-moz-native-anonymous .measuring-tool-highlighter-guide-top,
 :-moz-native-anonymous .measuring-tool-highlighter-guide-right,
 :-moz-native-anonymous .measuring-tool-highlighter-guide-bottom,
 :-moz-native-anonymous .measuring-tool-highlighter-guide-left {
   stroke: var(--highlighter-guide-color);
   stroke-dasharray: 5 3;
   shape-rendering: crispEdges;
 }
+
+/* Eye dropper */
+
+:-moz-native-anonymous .eye-dropper-root {
+  --magnifier-width: 96px;
+  --magnifier-height: 96px;
+  /* Width accounts for all color formats (hsl being the longest) */
+  --label-width: 160px;
+  --color: #e0e0e0;
+
+  position: absolute;
+  /* Tool start position. This should match the X/Y defines in JS */
+  top: 100px;
+  left: 100px;
+
+  /* Prevent interacting with the page when hovering and clicking */
+  pointer-events: auto;
+
+  /* Offset the UI so it is centered around the pointer */
+  transform: translate(
+    calc(var(--magnifier-width) / -2), calc(var(--magnifier-height) / -2));
+
+  filter: drop-shadow(0 0 1px rgba(0,0,0,.4));
+
+  /* We don't need the UI to be reversed in RTL locales, otherwise the # would appear
+     to the right of the hex code. Force LTR */
+  direction: ltr;
+}
+
+:-moz-native-anonymous .eye-dropper-canvas {
+  image-rendering: -moz-crisp-edges;
+  cursor: none;
+  width: var(--magnifier-width);
+  height: var(--magnifier-height);
+  border-radius: 50%;
+  box-shadow: 0 0 0 3px var(--color);
+  display: block;
+}
+
+:-moz-native-anonymous .eye-dropper-color-container {
+  background-color: var(--color);
+  border-radius: 2px;
+  width: var(--label-width);
+  transform: translateX(calc((var(--magnifier-width) - var(--label-width)) / 2));
+  position: relative;
+}
+
+:-moz-native-anonymous .eye-dropper-color-preview {
+  width: 16px;
+  height: 16px;
+  position: absolute;
+  offset-inline-start: 3px;
+  offset-block-start: 3px;
+  box-shadow: 0px 0px 0px black;
+  border: solid 1px #fff;
+}
+
+:-moz-native-anonymous .eye-dropper-color-value {
+  text-shadow: 1px 1px 1px #fff;
+  font: message-box;
+  font-size: 11px;
+  text-align: center;
+  padding: 4px 0;
+}
--- a/devtools/server/actors/highlighters.js
+++ b/devtools/server/actors/highlighters.js
@@ -674,8 +674,12 @@ exports.GeometryEditorHighlighter = Geom
 
 const { RulersHighlighter } = require("./highlighters/rulers");
 register(RulersHighlighter);
 exports.RulersHighlighter = RulersHighlighter;
 
 const { MeasuringToolHighlighter } = require("./highlighters/measuring-tool");
 register(MeasuringToolHighlighter);
 exports.MeasuringToolHighlighter = MeasuringToolHighlighter;
+
+const { EyeDropper } = require("./highlighters/eye-dropper");
+register(EyeDropper);
+exports.EyeDropper = EyeDropper;
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/highlighters/eye-dropper.js
@@ -0,0 +1,499 @@
+/* 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";
+
+// Eye-dropper tool. This is implemented as a highlighter so it can be displayed in the
+// content page.
+// It basically displays a magnifier that tracks mouse moves and shows a magnified version
+// of the page. On click, it samples the color at the pixel being hovered.
+
+const {Ci, Cc} = require("chrome");
+const {CanvasFrameAnonymousContentHelper, createNode} = require("./utils/markup");
+const Services = require("Services");
+const EventEmitter = require("devtools/shared/event-emitter");
+const {rgbToHsl, rgbToColorName} = require("devtools/shared/css-color").colorUtils;
+const {getCurrentZoom, getFrameOffsets} = require("devtools/shared/layout/utils");
+
+loader.lazyGetter(this, "clipboardHelper",
+  () => Cc["@mozilla.org/widget/clipboardhelper;1"].getService(Ci.nsIClipboardHelper));
+loader.lazyGetter(this, "l10n",
+  () => Services.strings.createBundle("chrome://devtools/locale/eyedropper.properties"));
+
+const ZOOM_LEVEL_PREF = "devtools.eyedropper.zoom";
+const FORMAT_PREF = "devtools.defaultColorUnit";
+// Width of the canvas.
+const MAGNIFIER_WIDTH = 96;
+// Height of the canvas.
+const MAGNIFIER_HEIGHT = 96;
+// Start position, when the tool is first shown. This should match the top/left position
+// defined in CSS.
+const DEFAULT_START_POS_X = 100;
+const DEFAULT_START_POS_Y = 100;
+// How long to wait before closing after copy.
+const CLOSE_DELAY = 750;
+
+/**
+ * The EyeDropper is the class that draws the gradient line and
+ * color stops as an overlay on top of a linear-gradient background-image.
+ */
+function EyeDropper(highlighterEnv) {
+  EventEmitter.decorate(this);
+
+  this.highlighterEnv = highlighterEnv;
+  this.markup = new CanvasFrameAnonymousContentHelper(this.highlighterEnv,
+                                                      this._buildMarkup.bind(this));
+
+  // Get a couple of settings from prefs.
+  this.format = Services.prefs.getCharPref(FORMAT_PREF);
+  this.eyeDropperZoomLevel = Services.prefs.getIntPref(ZOOM_LEVEL_PREF);
+}
+
+EyeDropper.prototype = {
+  typeName: "EyeDropper",
+
+  ID_CLASS_PREFIX: "eye-dropper-",
+
+  get win() {
+    return this.highlighterEnv.window;
+  },
+
+  _buildMarkup() {
+    // Highlighter main container.
+    let container = createNode(this.win, {
+      attributes: {"class": "highlighter-container"}
+    });
+
+    // Wrapper element.
+    let wrapper = createNode(this.win, {
+      parent: container,
+      attributes: {
+        "id": "root",
+        "class": "root",
+        "hidden": "true"
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // The magnifier canvas element.
+    createNode(this.win, {
+      parent: wrapper,
+      nodeType: "canvas",
+      attributes: {
+        "id": "canvas",
+        "class": "canvas",
+        "width": MAGNIFIER_WIDTH,
+        "height": MAGNIFIER_HEIGHT
+      },
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    // The color label element.
+    let colorLabelContainer = createNode(this.win, {
+      parent: wrapper,
+      attributes: {"class": "color-container"},
+      prefix: this.ID_CLASS_PREFIX
+    });
+    createNode(this.win, {
+      nodeType: "div",
+      parent: colorLabelContainer,
+      attributes: {"id": "color-preview", "class": "color-preview"},
+      prefix: this.ID_CLASS_PREFIX
+    });
+    createNode(this.win, {
+      nodeType: "div",
+      parent: colorLabelContainer,
+      attributes: {"id": "color-value", "class": "color-value"},
+      prefix: this.ID_CLASS_PREFIX
+    });
+
+    return container;
+  },
+
+  destroy() {
+    this.hide();
+    this.markup.destroy();
+  },
+
+  getElement(id) {
+    return this.markup.getElement(this.ID_CLASS_PREFIX + id);
+  },
+
+  /**
+   * Show the eye-dropper highlighter.
+   * @param {DOMNode} node The node which document the highlighter should be inserted in.
+   * @param {Object} options The options object may contain the following properties:
+   * - {Boolean} copyOnSelect Whether selecting a color should copy it to the clipboard.
+   */
+  show(node, options = {}) {
+    this.options = options;
+
+    // Get the page's current zoom level.
+    this.pageZoom = getCurrentZoom(this.win);
+
+    // Take a screenshot of the viewport. This needs to be done first otherwise the
+    // eyedropper UI will appear in the screenshot itself (since the UI is injected as
+    // native anonymous content in the page).
+    // Once the screenshot is ready, the magnified area will be drawn.
+    this.prepareImageCapture();
+
+    // Start listening for user events.
+    let {pageListenerTarget} = this.highlighterEnv;
+    pageListenerTarget.addEventListener("mousemove", this);
+    pageListenerTarget.addEventListener("click", this);
+    pageListenerTarget.addEventListener("keydown", this);
+    pageListenerTarget.addEventListener("DOMMouseScroll", this);
+    pageListenerTarget.addEventListener("FullZoomChange", this);
+
+    // Show the eye-dropper.
+    this.getElement("root").removeAttribute("hidden");
+
+    // Prepare the canvas context on which we're drawing the magnified page portion.
+    this.ctx = this.getElement("canvas").getCanvasContext();
+    this.ctx.mozImageSmoothingEnabled = false;
+
+    this.magnifiedArea = {width: MAGNIFIER_WIDTH, height: MAGNIFIER_HEIGHT,
+                          x: DEFAULT_START_POS_X, y: DEFAULT_START_POS_Y};
+
+    this.moveTo(DEFAULT_START_POS_X, DEFAULT_START_POS_Y);
+
+    // Focus the content so the keyboard can be used.
+    this.win.document.documentElement.focus();
+
+    return true;
+  },
+
+  /**
+   * Hide the eye-dropper highlighter.
+   */
+  hide() {
+    this.pageImage = null;
+
+    let {pageListenerTarget} = this.highlighterEnv;
+    pageListenerTarget.removeEventListener("mousemove", this);
+    pageListenerTarget.removeEventListener("click", this);
+    pageListenerTarget.removeEventListener("keydown", this);
+    pageListenerTarget.removeEventListener("DOMMouseScroll", this);
+    pageListenerTarget.removeEventListener("FullZoomChange", this);
+
+    this.getElement("root").setAttribute("hidden", "true");
+    this.getElement("root").removeAttribute("drawn");
+  },
+
+  prepareImageCapture() {
+    // Get the page as an image.
+    let imageData = getWindowAsImageData(this.win);
+    let image = new this.win.Image();
+    image.src = imageData;
+
+    // Wait for screenshot to load
+    image.onload = () => {
+      this.pageImage = image;
+      // We likely haven't drawn anything yet (no mousemove events yet), so start now.
+      this.draw();
+
+      // Set an attribute on the root element to be able to run tests after the first draw
+      // was done.
+      this.getElement("root").setAttribute("drawn", "true");
+    };
+  },
+
+  /**
+   * Get the number of cells (blown-up pixels) per direction in the grid.
+   */
+  get cellsWide() {
+    // Canvas will render whole "pixels" (cells) only, and an even number at that. Round
+    // up to the nearest even number of pixels.
+    let cellsWide = Math.ceil(this.magnifiedArea.width / this.eyeDropperZoomLevel);
+    cellsWide += cellsWide % 2;
+
+    return cellsWide;
+  },
+
+  /**
+   * Get the size of each cell (blown-up pixel) in the grid.
+   */
+  get cellSize() {
+    return this.magnifiedArea.width / this.cellsWide;
+  },
+
+  /**
+   * Get index of cell in the center of the grid.
+   */
+  get centerCell() {
+    return Math.floor(this.cellsWide / 2);
+  },
+
+  /**
+   * Get color of center cell in the grid.
+   */
+  get centerColor() {
+    let pos = (this.centerCell * this.cellSize) + (this.cellSize / 2);
+    let rgb = this.ctx.getImageData(pos, pos, 1, 1).data;
+    return rgb;
+  },
+
+  draw() {
+    // If the image of the page isn't ready yet, bail out, we'll draw later on mousemove.
+    if (!this.pageImage) {
+      return;
+    }
+
+    let {width, height, x, y} = this.magnifiedArea;
+
+    let zoomedWidth = width / this.eyeDropperZoomLevel;
+    let zoomedHeight = height / this.eyeDropperZoomLevel;
+
+    let sx = x - (zoomedWidth / 2);
+    let sy = y - (zoomedHeight / 2);
+    let sw = zoomedWidth;
+    let sh = zoomedHeight;
+
+    this.ctx.drawImage(this.pageImage, sx, sy, sw, sh, 0, 0, width, height);
+
+    // Draw the grid on top, but only at 3x or more, otherwise it's too busy.
+    if (this.eyeDropperZoomLevel > 2) {
+      this.drawGrid();
+    }
+
+    this.drawCrosshair();
+
+    // Update the color preview and value.
+    let rgb = this.centerColor;
+    this.getElement("color-preview").setAttribute("style",
+      `background-color:${toColorString(rgb, "rgb")};`);
+    this.getElement("color-value").setTextContent(toColorString(rgb, this.format));
+  },
+
+  /**
+   * Draw a grid on the canvas representing pixel boundaries.
+   */
+  drawGrid() {
+    let {width, height} = this.magnifiedArea;
+
+    this.ctx.lineWidth = 1;
+    this.ctx.strokeStyle = "rgba(143, 143, 143, 0.2)";
+
+    for (let i = 0; i < width; i += this.cellSize) {
+      this.ctx.beginPath();
+      this.ctx.moveTo(i - .5, 0);
+      this.ctx.lineTo(i - .5, height);
+      this.ctx.stroke();
+
+      this.ctx.beginPath();
+      this.ctx.moveTo(0, i - .5);
+      this.ctx.lineTo(width, i - .5);
+      this.ctx.stroke();
+    }
+  },
+
+  /**
+   * Draw a box on the canvas to highlight the center cell.
+   */
+  drawCrosshair() {
+    let pos = this.centerCell * this.cellSize;
+
+    this.ctx.lineWidth = 1;
+    this.ctx.lineJoin = "miter";
+    this.ctx.strokeStyle = "rgba(0, 0, 0, 1)";
+    this.ctx.strokeRect(pos - 1.5, pos - 1.5, this.cellSize + 2, this.cellSize + 2);
+
+    this.ctx.strokeStyle = "rgba(255, 255, 255, 1)";
+    this.ctx.strokeRect(pos - 0.5, pos - 0.5, this.cellSize, this.cellSize);
+  },
+
+  handleEvent(e) {
+    switch (e.type) {
+      case "mousemove":
+        // We might be getting an event from a child frame, so account for the offset.
+        let [xOffset, yOffset] = getFrameOffsets(this.win, e.target);
+        let x = xOffset + e.pageX - this.win.scrollX;
+        let y = yOffset + e.pageY - this.win.scrollY;
+        // Update the zoom area.
+        this.magnifiedArea.x = x * this.pageZoom;
+        this.magnifiedArea.y = y * this.pageZoom;
+        // Redraw the portion of the screenshot that is now under the mouse.
+        this.draw();
+        // And move the eye-dropper's UI so it follows the mouse.
+        this.moveTo(x, y);
+        break;
+      case "click":
+        this.selectColor();
+        break;
+      case "keydown":
+        this.handleKeyDown(e);
+        break;
+      case "DOMMouseScroll":
+        // Prevent scrolling. That's because we only took a screenshot of the viewport, so
+        // scrolling out of the viewport wouldn't draw the expected things. In the future
+        // we can take the screenshot again on scroll, but for now it doesn't seem
+        // important.
+        e.preventDefault();
+        break;
+      case "FullZoomChange":
+        this.hide();
+        this.show();
+        break;
+    }
+  },
+
+  moveTo(x, y) {
+    this.getElement("root").setAttribute("style", `top:${y}px;left:${x}px;`);
+  },
+
+  /**
+   * Select the current color that's being previewed. Depending on the current options,
+   * selecting might mean copying to the clipboard and closing the
+   */
+  selectColor() {
+    let onColorSelected = Promise.resolve();
+    if (this.options.copyOnSelect) {
+      onColorSelected = this.copyColor();
+    }
+
+    this.emit("selected", toColorString(this.centerColor, this.format));
+    onColorSelected.then(() => this.hide(), e => console.error(e));
+  },
+
+  /**
+   * Handler for the keydown event. Either select the color or move the panel in a
+   * direction depending on the key pressed.
+   */
+  handleKeyDown(e) {
+    if (e.keyCode === e.DOM_VK_RETURN) {
+      this.selectColor();
+      return;
+    }
+
+    if (e.keyCode === e.DOM_VK_ESCAPE) {
+      this.emit("canceled");
+      this.hide();
+      return;
+    }
+
+    let offsetX = 0;
+    let offsetY = 0;
+    let modifier = 1;
+
+    if (e.keyCode === e.DOM_VK_LEFT) {
+      offsetX = -1;
+    }
+    if (e.keyCode === e.DOM_VK_RIGHT) {
+      offsetX = 1;
+    }
+    if (e.keyCode === e.DOM_VK_UP) {
+      offsetY = -1;
+    }
+    if (e.keyCode === e.DOM_VK_DOWN) {
+      offsetY = 1;
+    }
+    if (e.shiftKey) {
+      modifier = 10;
+    }
+
+    offsetY *= modifier;
+    offsetX *= modifier;
+
+    if (offsetX !== 0 || offsetY !== 0) {
+      this.magnifiedArea.x += offsetX;
+      this.magnifiedArea.y += offsetY;
+
+      this.draw();
+
+      this.moveTo(this.magnifiedArea.x / this.pageZoom,
+                  this.magnifiedArea.y / this.pageZoom);
+    }
+
+    // Prevent all keyboard interaction with the page, except if a modifier is used to let
+    // keyboard shortcuts through.
+    let hasModifier = e.metaKey || e.ctrlKey || e.altKey || e.shiftKey;
+    if (!hasModifier) {
+      e.preventDefault();
+    }
+  },
+
+  /**
+   * Copy the currently inspected color to the clipboard.
+   * @return {Promise} Resolves when the copy has been done (after a delay that is used to
+   * let users know that something was copied).
+   */
+  copyColor() {
+    // Copy to the clipboard.
+    let color = toColorString(this.centerColor, this.format);
+    clipboardHelper.copyString(color);
+
+    // Provide some feedback.
+    this.getElement("color-value").setTextContent(
+      "✓ " + l10n.GetStringFromName("colorValue.copied"));
+
+    // Hide the tool after a delay.
+    clearTimeout(this._copyTimeout);
+    return new Promise(resolve => {
+      this._copyTimeout = setTimeout(resolve, CLOSE_DELAY);
+    });
+  }
+};
+
+exports.EyeDropper = EyeDropper;
+
+/**
+ * Get a content window as image data-url.
+ * @param {Window} win
+ * @return {String} The data-url
+ */
+function getWindowAsImageData(win) {
+  let canvas = win.document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+  let scale = getCurrentZoom(win);
+  let width = win.innerWidth;
+  let height = win.innerHeight;
+  canvas.width = width * scale;
+  canvas.height = height * scale;
+  canvas.mozOpaque = true;
+
+  let ctx = canvas.getContext("2d");
+
+  ctx.scale(scale, scale);
+  ctx.drawWindow(win, win.scrollX, win.scrollY, width, height, "#fff");
+
+  return canvas.toDataURL();
+}
+
+/**
+ * Get a formatted CSS color string from a color value.
+ * @param {array} rgb Rgb values of a color to format.
+ * @param {string} format Format of string. One of "hex", "rgb", "hsl", "name".
+ * @return {string} Formatted color value, e.g. "#FFF" or "hsl(20, 10%, 10%)".
+ */
+function toColorString(rgb, format) {
+  let [r, g, b] = rgb;
+
+  switch (format) {
+    case "hex":
+      return hexString(rgb);
+    case "rgb":
+      return "rgb(" + r + ", " + g + ", " + b + ")";
+    case "hsl":
+      let [h, s, l] = rgbToHsl(rgb);
+      return "hsl(" + h + ", " + s + "%, " + l + "%)";
+    case "name":
+      let str;
+      try {
+        str = rgbToColorName(r, g, b);
+      } catch (e) {
+        str = hexString(rgb);
+      }
+      return str;
+    default:
+      return hexString(rgb);
+  }
+}
+
+/**
+ * Produce a hex-formatted color string from rgb values.
+ * @param {array} rgb Rgb values of color to stringify.
+ * @return {string} Hex formatted string for color, e.g. "#FFEE00".
+ */
+function hexString([r, g, b]) {
+  let val = (1 << 24) + (r << 16) + (g << 8) + (b << 0);
+  return "#" + val.toString(16).substr(-6).toUpperCase();
+}
--- a/devtools/server/actors/highlighters/moz.build
+++ b/devtools/server/actors/highlighters/moz.build
@@ -7,15 +7,16 @@
 DIRS += [
     'utils',
 ]
 
 DevToolsModules(
     'auto-refresh.js',
     'box-model.js',
     'css-transform.js',
+    'eye-dropper.js',
     'geometry-editor.js',
     'measuring-tool.js',
     'rect.js',
     'rulers.js',
     'selector.js',
     'simple-outline.js'
 )
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -320,16 +320,20 @@ CanvasFrameAnonymousContentHelper.protot
       this.content.removeAttributeForElement(id, name);
     }
   },
 
   hasAttributeForElement: function (id, name) {
     return typeof this.getAttributeForElement(id, name) === "string";
   },
 
+  getCanvasContext: function (id, type = "2d") {
+    return this.content ? this.content.getCanvasContext(id, type) : null;
+  },
+
   /**
    * Add an event listener to one of the elements inserted in the canvasFrame
    * native anonymous container.
    * Like other methods in this helper, this requires the ID of the element to
    * be passed in.
    *
    * Note that if the content page navigates, the event listeners won't be
    * added again.
@@ -455,16 +459,17 @@ CanvasFrameAnonymousContentHelper.protot
 
     return {
       getTextContent: () => this.getTextContentForElement(id),
       setTextContent: text => this.setTextContentForElement(id, text),
       setAttribute: (name, val) => this.setAttributeForElement(id, name, val),
       getAttribute: name => this.getAttributeForElement(id, name),
       removeAttribute: name => this.removeAttributeForElement(id, name),
       hasAttribute: name => this.hasAttributeForElement(id, name),
+      getCanvasContext: type => this.getCanvasContext(id, type),
       addEventListener: (type, handler) => {
         return this.addEventListenerForElement(id, type, handler);
       },
       removeEventListener: (type, handler) => {
         return this.removeEventListenerForElement(id, type, handler);
       },
       classList
     };
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -48,39 +48,37 @@
  * So to be able to answer "all the children of a given node that we have
  * seen on the client side", we guarantee that every time we've seen a node,
  * we connect it up through its parents.
  */
 
 const {Cc, Ci, Cu} = require("chrome");
 const Services = require("Services");
 const protocol = require("devtools/shared/protocol");
-const {Arg, Option, method, RetVal, types} = protocol;
 const {LongStringActor} = require("devtools/server/actors/string");
 const promise = require("promise");
 const {Task} = require("devtools/shared/task");
-const object = require("sdk/util/object");
 const events = require("sdk/event/core");
-const {Class} = require("sdk/core/heritage");
 const {WalkerSearch} = require("devtools/server/actors/utils/walker-search");
 const {PageStyleActor, getFontPreviewData} = require("devtools/server/actors/styles");
 const {
   HighlighterActor,
   CustomHighlighterActor,
   isTypeRegistered,
+  HighlighterEnvironment
 } = require("devtools/server/actors/highlighters");
+const {EyeDropper} = require("devtools/server/actors/highlighters/eye-dropper");
 const {
   isAnonymous,
   isNativeAnonymous,
   isXBLAnonymous,
   isShadowAnonymous,
   getFrameElement
 } = require("devtools/shared/layout/utils");
-const {getLayoutChangesObserver, releaseLayoutChangesObserver} =
-  require("devtools/server/actors/layout");
+const {getLayoutChangesObserver, releaseLayoutChangesObserver} = require("devtools/server/actors/layout");
 const nodeFilterConstants = require("devtools/shared/dom-node-filter-constants");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 const {EventParsers} = require("devtools/shared/event-parsers");
 const {nodeSpec, nodeListSpec, walkerSpec, inspectorSpec} = require("devtools/shared/specs/inspector");
 
 const FONT_FAMILY_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog";
@@ -437,44 +435,44 @@ var NodeActor = exports.NodeActor = prot
    * Gets event listeners and adds their information to the events array.
    *
    * @param  {Node} node
    *         Node for which we are to get listeners.
    */
   getEventListeners: function (node) {
     let parsers = this._eventParsers;
     let dbg = this.parent().tabActor.makeDebugger();
-    let events = [];
+    let listeners = [];
 
     for (let [, {getListeners, normalizeHandler}] of parsers) {
       try {
         let eventInfos = getListeners(node);
 
         if (!eventInfos) {
           continue;
         }
 
         for (let eventInfo of eventInfos) {
           if (normalizeHandler) {
             eventInfo.normalizeHandler = normalizeHandler;
           }
 
-          this.processHandlerForEvent(node, events, dbg, eventInfo);
+          this.processHandlerForEvent(node, listeners, dbg, eventInfo);
         }
       } catch (e) {
         // An object attached to the node looked like a listener but wasn't...
         // do nothing.
       }
     }
 
-    events.sort((a, b) => {
+    listeners.sort((a, b) => {
       return a.type.localeCompare(b.type);
     });
 
-    return events;
+    return listeners;
   },
 
   /**
    * Process a handler
    *
    * @param  {Node} node
    *         The node for which we want information.
    * @param  {Array} events
@@ -496,17 +494,17 @@ var NodeActor = exports.NodeActor = prot
    *             tags: tags,
    *             DOM0: true,
    *             capturing: true,
    *             hide: {
    *               dom0: true
    *             }
    *           }
    */
-  processHandlerForEvent: function (node, events, dbg, eventInfo) {
+  processHandlerForEvent: function (node, listeners, dbg, eventInfo) {
     let type = eventInfo.type || "";
     let handler = eventInfo.handler;
     let tags = eventInfo.tags || "";
     let hide = eventInfo.hide || {};
     let override = eventInfo.override || {};
     let global = Cu.getGlobalForObject(handler);
     let globalDO = dbg.addDebuggee(global);
     let listenerDO = globalDO.makeDebuggeeValue(handler);
@@ -587,17 +585,17 @@ var NodeActor = exports.NodeActor = prot
                            override.searchString : searchString,
       tags: tags,
       DOM0: typeof override.dom0 !== "undefined" ? override.dom0 : dom0,
       capturing: typeof override.capturing !== "undefined" ?
                         override.capturing : eventInfo.capturing,
       hide: hide
     };
 
-    events.push(eventObj);
+    listeners.push(eventObj);
 
     dbg.removeDebuggee(globalDO);
   },
 
   /**
    * Returns a LongStringActor with the node's value.
    */
   getNodeValue: function () {
@@ -1293,18 +1291,18 @@ var WalkerActor = protocol.ActorClassWit
     }
     let maxNodes = options.maxNodes || -1;
     if (maxNodes == -1) {
       maxNodes = Number.MAX_VALUE;
     }
 
     // We're going to create a few document walkers with the same filter,
     // make it easier.
-    let getFilteredWalker = node => {
-      return this.getDocumentWalker(node, options.whatToShow);
+    let getFilteredWalker = documentWalkerNode => {
+      return this.getDocumentWalker(documentWalkerNode, options.whatToShow);
     };
 
     // Need to know the first and last child.
     let rawNode = node.rawNode;
     let firstChild = getFilteredWalker(rawNode).firstChild();
     let lastChild = getFilteredWalker(rawNode).lastChild();
 
     if (!firstChild) {
@@ -2102,32 +2100,32 @@ var WalkerActor = protocol.ActorClassWit
 
   /**
    * Insert a node into the DOM.
    */
   insertBefore: function (node, parent, sibling) {
     if (isNodeDead(node) ||
         isNodeDead(parent) ||
         (sibling && isNodeDead(sibling))) {
-      return null;
+      return;
     }
 
     let rawNode = node.rawNode;
     let rawParent = parent.rawNode;
     let rawSibling = sibling ? sibling.rawNode : null;
 
     // Don't bother inserting a node if the document position isn't going
     // to change. This prevents needless iframes reloading and mutations.
     if (rawNode.parentNode === rawParent) {
       let currentNextSibling = this.nextSibling(node);
       currentNextSibling = currentNextSibling ? currentNextSibling.rawNode :
                                                 null;
 
       if (rawNode === rawSibling || currentNextSibling === rawSibling) {
-        return null;
+        return;
       }
     }
 
     rawParent.insertBefore(rawNode, rawSibling);
   },
 
   /**
    * Editing a node's tagname actually means creating a new node with the same
@@ -2145,32 +2143,32 @@ var WalkerActor = protocol.ActorClassWit
     // Create a new element with the same attributes as the current element and
     // prepare to replace the current node with it.
     let newNode;
     try {
       newNode = nodeDocument(oldNode).createElement(tagName);
     } catch (x) {
       // Failed to create a new element with that tag name, ignore the change,
       // and signal the error to the front.
-      return Promise.reject(new Error("Could not change node's tagName to " +
-        tagName));
+      return Promise.reject(new Error("Could not change node's tagName to " + tagName));
     }
 
     let attrs = oldNode.attributes;
     for (let i = 0; i < attrs.length; i++) {
       newNode.setAttribute(attrs[i].name, attrs[i].value);
     }
 
     // Insert the new node, and transfer the old node's children.
     oldNode.parentNode.insertBefore(newNode, oldNode);
     while (oldNode.firstChild) {
       newNode.appendChild(oldNode.firstChild);
     }
 
     oldNode.remove();
+    return null;
   },
 
   /**
    * Get any pending mutation records.  Must be called by the client after
    * the `new-mutations` notification is received.  Returns an array of
    * mutation records.
    *
    * Mutation records have a basic structure:
@@ -2575,25 +2573,31 @@ var WalkerActor = protocol.ActorClassWit
     return this.attachElement(obj);
   },
 });
 
 /**
  * Server side of the inspector actor, which is used to create
  * inspector-related actors, including the walker.
  */
-var InspectorActor = exports.InspectorActor = protocol.ActorClassWithSpec(inspectorSpec, {
+exports.InspectorActor = protocol.ActorClassWithSpec(inspectorSpec, {
   initialize: function (conn, tabActor) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.tabActor = tabActor;
+
+    this._onColorPicked = this._onColorPicked.bind(this);
+    this._onColorPickCanceled = this._onColorPickCanceled.bind(this);
+    this.destroyEyeDropper = this.destroyEyeDropper.bind(this);
   },
 
   destroy: function () {
     protocol.Actor.prototype.destroy.call(this);
 
+    this.destroyEyeDropper();
+
     this._highlighterPromise = null;
     this._pageStylePromise = null;
     this._walkerPromise = null;
     this.walker = null;
     this.tabActor = null;
   },
 
   // Forces destruction of the actor and all its children
@@ -2731,16 +2735,76 @@ var InspectorActor = exports.InspectorAc
                    : nodeDocument(node.rawNode);
 
     if (!document) {
       return url;
     }
 
     let baseURI = Services.io.newURI(document.location.href, null, null);
     return Services.io.newURI(url, null, baseURI).spec;
+  },
+
+  /**
+   * Create an instance of the eye-dropper highlighter and store it on this._eyeDropper.
+   * Note that for now, a new instance is created every time to deal with page navigation.
+   */
+  createEyeDropper: function () {
+    this.destroyEyeDropper();
+    this._highlighterEnv = new HighlighterEnvironment();
+    this._highlighterEnv.initFromTabActor(this.tabActor);
+    this._eyeDropper = new EyeDropper(this._highlighterEnv);
+  },
+
+  /**
+   * Destroy the current eye-dropper highlighter instance.
+   */
+  destroyEyeDropper: function () {
+    if (this._eyeDropper) {
+      this.cancelPickColorFromPage();
+      this._eyeDropper.destroy();
+      this._eyeDropper = null;
+      this._highlighterEnv.destroy();
+      this._highlighterEnv = null;
+    }
+  },
+
+  /**
+   * Pick a color from the page using the eye-dropper. This method doesn't return anything
+   * but will cause events to be sent to the front when a color is picked or when the user
+   * cancels the picker.
+   * @param {Object} options
+   */
+  pickColorFromPage: function (options) {
+    this.createEyeDropper();
+    this._eyeDropper.show(this.window.document.documentElement, options);
+    this._eyeDropper.once("selected", this._onColorPicked);
+    this._eyeDropper.once("canceled", this._onColorPickCanceled);
+    events.once(this.tabActor, "will-navigate", this.destroyEyeDropper);
+  },
+
+  /**
+   * After the pickColorFromPage method is called, the only way to dismiss the eye-dropper
+   * highlighter is for the user to click in the page and select a color. If you need to
+   * dismiss the eye-dropper programatically instead, use this method.
+   */
+  cancelPickColorFromPage: function () {
+    if (this._eyeDropper) {
+      this._eyeDropper.hide();
+      this._eyeDropper.off("selected", this._onColorPicked);
+      this._eyeDropper.off("canceled", this._onColorPickCanceled);
+      events.off(this.tabActor, "will-navigate", this.destroyEyeDropper);
+    }
+  },
+
+  _onColorPicked: function (e, color) {
+    events.emit(this, "color-picked", color);
+  },
+
+  _onColorPickCanceled: function () {
+    events.emit(this, "color-pick-canceled");
   }
 });
 
 // Exported for test purposes.
 exports._documentWalker = DocumentWalker;
 
 function nodeDocument(node) {
   if (Cu.isDeadWrapper(node)) {
@@ -2752,16 +2816,17 @@ function nodeDocument(node) {
 
 function nodeDocshell(node) {
   let doc = node ? nodeDocument(node) : null;
   let win = doc ? doc.defaultView : null;
   if (win) {
     return win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDocShell);
   }
+  return null;
 }
 
 function isNodeDead(node) {
   return !node || !node.rawNode || Cu.isDeadWrapper(node.rawNode);
 }
 
 /**
  * Wrapper for inDeepTreeWalker.  Adds filtering to the traversal methods.
@@ -2957,17 +3022,17 @@ function ensureImageLoaded(image, timeou
   let onLoad = AsyncUtils.listenOnce(image, "load");
 
   // Reject if loading fails.
   let onError = AsyncUtils.listenOnce(image, "error").then(() => {
     return promise.reject("Image '" + image.src + "' failed to load.");
   });
 
   // Don't timeout when testing. This is never settled.
-  let onAbort = new promise(() => {});
+  let onAbort = new Promise(() => {});
 
   if (!DevToolsUtils.testing) {
     // Tests are not running. Reject the promise after given timeout.
     onAbort = DevToolsUtils.waitForTime(timeout).then(() => {
       return promise.reject("Image '" + image.src + "' took too long to load.");
     });
   }
 
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -6,16 +6,17 @@ support-files =
   Debugger.Source.prototype.element.js
   Debugger.Source.prototype.element-2.js
   Debugger.Source.prototype.element.html
   director-helpers.js
   hello-actor.js
   inspector_css-properties.html
   inspector_getImageData.html
   inspector-delay-image-response.sjs
+  inspector-eyedropper.html
   inspector-helpers.js
   inspector-search-data.html
   inspector-styles-data.css
   inspector-styles-data.html
   inspector-traversal-data.html
   large-image.jpg
   memory-helpers.js
   nonchrome_unsafeDereference.html
@@ -70,16 +71,17 @@ skip-if = buildapp == 'mulet'
 [test_inspector_getNodeFromActor.html]
 [test_inspector-hide.html]
 [test_inspector-insert.html]
 [test_inspector-mutations-attr.html]
 [test_inspector-mutations-events.html]
 [test_inspector-mutations-childlist.html]
 [test_inspector-mutations-frameload.html]
 [test_inspector-mutations-value.html]
+[test_inspector-pick-color.html]
 [test_inspector-pseudoclass-lock.html]
 [test_inspector-release.html]
 [test_inspector-reload.html]
 [test_inspector-remove.html]
 [test_inspector-resize.html]
 [test_inspector-resolve-url.html]
 [test_inspector-retain.html]
 [test_inspector-search.html]
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/inspector-eyedropper.html
@@ -0,0 +1,18 @@
+<html>
+<head>
+  <meta charset="UTF-8">
+  <title>Inspector Eyedropper tests</title>
+  <style>
+    html {
+      background: black;
+    }
+  </style>
+  <script type="text/javascript">
+    window.onload = function() {
+      window.opener.postMessage('ready', '*');
+    };
+  </script>
+</head>
+</body>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/devtools/server/tests/mochitest/test_inspector-pick-color.html
@@ -0,0 +1,101 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Test that the inspector actor has the pickColorFromPage and cancelPickColorFromPage
+methods and that when a color is picked the color-picked event is emitted and that when
+the eyedropper is dimissed, the color-pick-canceled event is emitted.
+https://bugzilla.mozilla.org/show_bug.cgi?id=1262439
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1262439</title>
+  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
+  <script type="application/javascript;version=1.8">
+window.onload = function() {
+  const Cu = Components.utils;
+  Cu.import("resource://devtools/shared/Loader.jsm");
+  const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
+  const {InspectorFront} = devtools.require("devtools/shared/fronts/inspector");
+  const {console} = Cu.import("resource://gre/modules/Console.jsm", {});
+
+  SimpleTest.waitForExplicitFinish();
+
+  let win = null;
+  let inspector = null;
+
+  addAsyncTest(function*() {
+    info("Setting up inspector actor");
+
+    let url = document.getElementById("inspectorContent").href;
+
+    yield new Promise(resolve => {
+      attachURL(url, function(err, client, tab, doc) {
+        win = doc.defaultView;
+        inspector = InspectorFront(client, tab);
+        resolve();
+      });
+    });
+
+    runNextTest();
+  });
+
+  addAsyncTest(function*() {
+    info("Start picking a color from the page");
+    yield inspector.pickColorFromPage();
+
+    info("Click in the page and make sure a color-picked event is received");
+    let onColorPicked = waitForEvent("color-picked");
+    win.document.body.click();
+    let color = yield onColorPicked;
+
+    is(color, "#000000", "The color-picked event was received with the right color");
+
+    runNextTest();
+  });
+
+  addAsyncTest(function*() {
+    info("Start picking a color from the page");
+    yield inspector.pickColorFromPage();
+
+    info("Use the escape key to dismiss the eyedropper");
+    let onPickCanceled = waitForEvent("color-pick-canceled");
+
+    let keyboardEvent = win.document.createEvent("KeyboardEvent");
+    keyboardEvent.initKeyEvent("keydown", true, true, win, false, false,
+                               false, false, 27, 0);
+    win.document.dispatchEvent(keyboardEvent);
+
+    yield onPickCanceled;
+    ok(true, "The color-pick-canceled event was received");
+
+    runNextTest();
+  });
+
+  addAsyncTest(function*() {
+    info("Start picking a color from the page");
+    yield inspector.pickColorFromPage();
+
+    info("And cancel the color picking");
+    yield inspector.cancelPickColorFromPage();
+
+    runNextTest();
+  });
+
+  function waitForEvent(name) {
+    return new Promise(resolve => inspector.once(name, resolve));
+  }
+
+  runNextTest();
+};
+  </script>
+</head>
+<body>
+<a id="inspectorContent" target="_blank" href="inspector-eyedropper.html">Test Document</a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+</pre>
+</body>
+</html>
rename from devtools/client/shared/css-color-db.js
rename to devtools/shared/css-color-db.js
rename from devtools/client/shared/css-color.js
rename to devtools/shared/css-color.js
--- a/devtools/client/shared/css-color.js
+++ b/devtools/shared/css-color.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 Services = require("Services");
 
 const {getCSSLexer} = require("devtools/shared/css-lexer");
-const {cssColors} = require("devtools/client/shared/css-color-db");
+const {cssColors} = require("devtools/shared/css-color-db");
 
 const COLOR_UNIT_PREF = "devtools.defaultColorUnit";
 
 const SPECIALVALUES = new Set([
   "currentcolor",
   "initial",
   "inherit",
   "transparent",
--- a/devtools/shared/heapsnapshot/DeserializedNode.cpp
+++ b/devtools/shared/heapsnapshot/DeserializedNode.cpp
@@ -116,17 +116,17 @@ Concrete<DeserializedNode>::allocationSt
   MOZ_ASSERT(ptr);
   // See above comment in DeserializedNode::getEdgeReferent about why this
   // const_cast is needed and safe.
   return JS::ubi::StackFrame(const_cast<DeserializedStackFrame*>(&*ptr));
 }
 
 
 js::UniquePtr<EdgeRange>
-Concrete<DeserializedNode>::edges(JSRuntime* rt, bool) const
+Concrete<DeserializedNode>::edges(JSContext* cx, bool) const
 {
   js::UniquePtr<DeserializedEdgeRange> range(js_new<DeserializedEdgeRange>(get()));
 
   if (!range)
     return nullptr;
 
   return js::UniquePtr<EdgeRange>(range.release());
 }
--- a/devtools/shared/heapsnapshot/DeserializedNode.h
+++ b/devtools/shared/heapsnapshot/DeserializedNode.h
@@ -265,17 +265,17 @@ public:
   const char* jsObjectClassName() const override { return get().jsObjectClassName; }
   const char* scriptFilename() const final { return get().scriptFilename; }
 
   bool hasAllocationStack() const override { return get().allocationStack.isSome(); }
   StackFrame allocationStack() const override;
 
   // We ignore the `bool wantNames` parameter because we can't control whether
   // the core dump was serialized with edge names or not.
-  js::UniquePtr<EdgeRange> edges(JSRuntime* rt, bool) const override;
+  js::UniquePtr<EdgeRange> edges(JSContext* cx, bool) const override;
 
   static const char16_t concreteTypeName[];
 };
 
 template<>
 class ConcreteStackFrame<DeserializedStackFrame> : public BaseStackFrame
 {
 protected:
--- a/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
+++ b/devtools/shared/heapsnapshot/FileDescriptorOutputStream.cpp
@@ -10,17 +10,18 @@ namespace mozilla {
 namespace devtools {
 
 /* static */ already_AddRefed<FileDescriptorOutputStream>
 FileDescriptorOutputStream::Create(const ipc::FileDescriptor& fileDescriptor)
 {
   if (NS_WARN_IF(!fileDescriptor.IsValid()))
     return nullptr;
 
-  PRFileDesc* prfd = PR_ImportFile(PROsfd(fileDescriptor.PlatformHandle()));
+  auto rawFD = fileDescriptor.ClonePlatformHandle();
+  PRFileDesc* prfd = PR_ImportFile(PROsfd(rawFD.release()));
   if (NS_WARN_IF(!prfd))
     return nullptr;
 
   RefPtr<FileDescriptorOutputStream> stream = new FileDescriptorOutputStream(prfd);
   return stream.forget();
 }
 
 NS_IMPL_ISUPPORTS(FileDescriptorOutputStream, nsIOutputStream);
--- a/devtools/shared/heapsnapshot/HeapSnapshot.cpp
+++ b/devtools/shared/heapsnapshot/HeapSnapshot.cpp
@@ -484,17 +484,17 @@ HeapSnapshot::TakeCensus(JSContext* cx, 
     return;
   }
 
   JS::ubi::CensusHandler handler(census, rootCount, GetCurrentThreadDebuggerMallocSizeOf());
 
   {
     JS::AutoCheckCannotGC nogc;
 
-    JS::ubi::CensusTraversal traversal(JS_GetRuntime(cx), handler, nogc);
+    JS::ubi::CensusTraversal traversal(cx, handler, nogc);
     if (NS_WARN_IF(!traversal.init())) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
     }
 
     if (NS_WARN_IF(!traversal.addStart(getRoot()))) {
       rv.Throw(NS_ERROR_OUT_OF_MEMORY);
       return;
@@ -551,20 +551,20 @@ HeapSnapshot::DescribeNode(JSContext* cx
 
 already_AddRefed<DominatorTree>
 HeapSnapshot::ComputeDominatorTree(ErrorResult& rv)
 {
   Maybe<JS::ubi::DominatorTree> maybeTree;
   {
     auto ccrt = CycleCollectedJSRuntime::Get();
     MOZ_ASSERT(ccrt);
-    auto rt = ccrt->Runtime();
-    MOZ_ASSERT(rt);
-    JS::AutoCheckCannotGC nogc(rt);
-    maybeTree = JS::ubi::DominatorTree::Create(rt, nogc, getRoot());
+    auto cx = ccrt->Context();
+    MOZ_ASSERT(cx);
+    JS::AutoCheckCannotGC nogc(cx);
+    maybeTree = JS::ubi::DominatorTree::Create(cx, nogc, getRoot());
   }
 
   if (NS_WARN_IF(maybeTree.isNothing())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return nullptr;
   }
 
   return MakeAndAddRef<DominatorTree>(Move(*maybeTree), this, mParent);
@@ -616,22 +616,18 @@ HeapSnapshot::ComputeShortestPaths(JSCon
       return;
     }
   }
 
   // Walk the heap graph and find the shortest paths.
 
   Maybe<ShortestPaths> maybeShortestPaths;
   {
-    auto ccrt = CycleCollectedJSRuntime::Get();
-    MOZ_ASSERT(ccrt);
-    auto rt = ccrt->Runtime();
-    MOZ_ASSERT(rt);
-    JS::AutoCheckCannotGC nogc(rt);
-    maybeShortestPaths = ShortestPaths::Create(rt, nogc, maxNumPaths, *startNode,
+    JS::AutoCheckCannotGC nogc(cx);
+    maybeShortestPaths = ShortestPaths::Create(cx, nogc, maxNumPaths, *startNode,
                                                Move(targetsSet));
   }
 
   if (NS_WARN_IF(maybeShortestPaths.isNothing())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
@@ -1233,17 +1229,17 @@ public:
       return false;
     }
 
     mozilla::MallocSizeOf mallocSizeOf = dbg::GetDebuggerMallocSizeOf(cx);
     MOZ_ASSERT(mallocSizeOf);
     protobufNode.set_size(ubiNode.size(mallocSizeOf));
 
     if (includeEdges) {
-      auto edges = ubiNode.edges(JS_GetRuntime(cx), wantNames);
+      auto edges = ubiNode.edges(cx, wantNames);
       if (NS_WARN_IF(!edges))
         return false;
 
       for ( ; !edges->empty(); edges->popFront()) {
         ubi::Edge& ubiEdge = edges->front();
 
         protobuf::Edge* protobufEdge = protobufNode.add_edges();
         if (NS_WARN_IF(!protobufEdge)) {
@@ -1375,17 +1371,17 @@ WriteHeapGraph(JSContext* cx,
   if (NS_WARN_IF(!writer.writeNode(node, CoreDumpWriter::INCLUDE_EDGES))) {
     return false;
   }
 
   // Walk the heap graph starting from the given node and serialize it into the
   // core dump.
 
   HeapSnapshotHandler handler(writer, compartments);
-  HeapSnapshotHandler::Traversal traversal(JS_GetRuntime(cx), handler, noGC);
+  HeapSnapshotHandler::Traversal traversal(cx, handler, noGC);
   if (!traversal.init())
     return false;
   traversal.wantNames = wantNames;
 
   bool ok = traversal.addStartVisited(node) &&
             traversal.traverse();
 
   if (ok) {
@@ -1540,17 +1536,17 @@ ThreadSafeChromeUtils::SaveHeapSnapshot(
   StreamWriter writer(cx, gzipStream, wantNames);
   if (NS_WARN_IF(!writer.init())) {
     rv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   {
     Maybe<AutoCheckCannotGC> maybeNoGC;
-    ubi::RootList rootList(JS_GetRuntime(cx), maybeNoGC, wantNames);
+    ubi::RootList rootList(cx, maybeNoGC, wantNames);
     if (!EstablishBoundaries(cx, rv, boundaries, rootList, compartments))
       return;
 
     MOZ_ASSERT(maybeNoGC.isSome());
     ubi::Node roots(&rootList);
 
     // Serialize the initial heap snapshot metadata to the core dump.
     if (!writer.writeMetadata(PR_Now()) ||
--- a/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DeserializedNodeUbiNodes.cpp
@@ -85,16 +85,16 @@ DEF_TEST(DeserializedNodeUbiNodes, {
                                                                    nullptr,
                                                                    30));
     DeserializedEdge edge3(referent3->id);
     mocked.addEdge(Move(edge3));
     EXPECT_CALL(mocked, getEdgeReferent(EdgeTo(referent3->id)))
       .Times(1)
       .WillOnce(Return(JS::ubi::Node(referent3.get())));
 
-    auto range = ubi.edges(rt);
+    auto range = ubi.edges(cx);
     ASSERT_TRUE(!!range);
 
     for ( ; !range->empty(); range->popFront()) {
       // Nothing to do here. This loop ensures that we get each edge referent
       // that we expect above.
     }
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
+++ b/devtools/shared/heapsnapshot/tests/gtest/DevTools.h
@@ -153,17 +153,17 @@ namespace ubi {
 
 template<>
 class Concrete<FakeNode> : public Base
 {
   const char16_t* typeName() const override {
     return concreteTypeName;
   }
 
-  js::UniquePtr<EdgeRange> edges(JSRuntime*, bool) const override {
+  js::UniquePtr<EdgeRange> edges(JSContext*, bool) const override {
     return js::UniquePtr<EdgeRange>(js_new<PreComputedEdgeRange>(get().edges));
   }
 
   Size size(mozilla::MallocSizeOf) const override {
     return get().size;
   }
 
   JS::Zone* zone() const override {
@@ -204,32 +204,32 @@ void AddEdge(FakeNode& node, FakeNode& r
 
 // Custom GMock Matchers
 
 // Use the testing namespace to avoid static analysis failures in the gmock
 // matcher classes that get generated from MATCHER_P macros.
 namespace testing {
 
 // Ensure that given node has the expected number of edges.
-MATCHER_P2(EdgesLength, rt, expectedLength, "") {
-  auto edges = arg.edges(rt);
+MATCHER_P2(EdgesLength, cx, expectedLength, "") {
+  auto edges = arg.edges(cx);
   if (!edges)
     return false;
 
   int actualLength = 0;
   for ( ; !edges->empty(); edges->popFront())
     actualLength++;
 
   return Matcher<int>(Eq(expectedLength))
     .MatchAndExplain(actualLength, result_listener);
 }
 
 // Get the nth edge and match it with the given matcher.
-MATCHER_P3(Edge, rt, n, matcher, "") {
-  auto edges = arg.edges(rt);
+MATCHER_P3(Edge, cx, n, matcher, "") {
+  auto edges = arg.edges(cx);
   if (!edges)
     return false;
 
   int i = 0;
   for ( ; !edges->empty(); edges->popFront()) {
     if (i == n) {
       return Matcher<const JS::ubi::Edge&>(matcher)
         .MatchAndExplain(edges->front(), result_listener);
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesCrossCompartmentBoundaries.cpp
@@ -56,17 +56,17 @@ DEF_TEST(DoesCrossCompartmentBoundaries,
 
     // Should also serialize nodeC, which is in our target compartments, but a
     // different compartment than A.
     ExpectWriteNode(writer, nodeC);
 
     // However, should not serialize nodeD because nodeB doesn't belong to one
     // of our target compartments and so its edges are excluded from serialization.
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                &targetCompartments,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/DoesntCrossCompartmentBoundaries.cpp
@@ -48,17 +48,17 @@ DEF_TEST(DoesntCrossCompartmentBoundarie
     ExpectWriteNode(writer, nodeA);
 
     // Should serialize nodeB, because it doesn't belong to a compartment and is
     // therefore assumed to be shared.
     ExpectWriteNode(writer, nodeB);
 
     // But we shouldn't ever serialize nodeC.
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                &targetCompartments,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEdgeNames.cpp
@@ -23,31 +23,31 @@ DEF_TEST(SerializesEdgeNames, {
     AddEdge(node, referent, emptyStr);
     AddEdge(node, referent, nullptr);
 
     ::testing::NiceMock<MockWriter> writer;
 
     // Should get the node with edges once.
     EXPECT_CALL(
       writer,
-      writeNode(AllOf(EdgesLength(rt, 3),
-                      Edge(rt, 0, Field(&JS::ubi::Edge::name,
+      writeNode(AllOf(EdgesLength(cx, 3),
+                      Edge(cx, 0, Field(&JS::ubi::Edge::name,
                                         UniqueUTF16StrEq(edgeName))),
-                      Edge(rt, 1, Field(&JS::ubi::Edge::name,
+                      Edge(cx, 1, Field(&JS::ubi::Edge::name,
                                         UniqueUTF16StrEq(emptyStr))),
-                      Edge(rt, 2, Field(&JS::ubi::Edge::name,
+                      Edge(cx, 2, Field(&JS::ubi::Edge::name,
                                         UniqueIsNull()))),
                 _)
     )
       .Times(1)
       .WillOnce(Return(true));
 
     // Should get the referent node that doesn't have any edges once.
     ExpectWriteNode(writer, referent);
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&node),
                                writer,
                                /* wantNames = */ true,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesEverythingInHeapGraphOnce.cpp
@@ -21,17 +21,17 @@ DEF_TEST(SerializesEverythingInHeapGraph
     ::testing::NiceMock<MockWriter> writer;
 
     // Should serialize each node once.
     ExpectWriteNode(writer, nodeA);
     ExpectWriteNode(writer, nodeB);
     ExpectWriteNode(writer, nodeC);
     ExpectWriteNode(writer, nodeD);
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
 
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&nodeA),
                                writer,
                                /* wantNames = */ false,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
+++ b/devtools/shared/heapsnapshot/tests/gtest/SerializesTypeNames.cpp
@@ -15,16 +15,16 @@ DEF_TEST(SerializesTypeNames, {
 
     ::testing::NiceMock<MockWriter> writer;
     EXPECT_CALL(writer, writeNode(Property(&JS::ubi::Node::typeName,
                                            UTF16StrEq(u"FakeNode")),
                                   _))
       .Times(1)
       .WillOnce(Return(true));
 
-    JS::AutoCheckCannotGC noGC(rt);
+    JS::AutoCheckCannotGC noGC(cx);
     ASSERT_TRUE(WriteHeapGraph(cx,
                                JS::ubi::Node(&node),
                                writer,
                                /* wantNames = */ true,
                                /* zones = */ nullptr,
                                noGC));
   });
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -161,16 +161,17 @@ function getFrameOffsets(boundaryWindow,
     xOffset += frameRect.left + offsetLeft;
     yOffset += frameRect.top + offsetTop;
 
     frameWin = getParentWindow(frameWin);
   }
 
   return [xOffset * scale, yOffset * scale];
 }
+exports.getFrameOffsets = getFrameOffsets;
 
 /**
  * Get box quads adjusted for iframes and zoom level.
  *
  * @param {DOMWindow} boundaryWindow
  *        The window where to stop to iterate. If `null` is given, the top
  *        window is used.
  * @param {DOMNode} node
--- a/devtools/shared/locales/en-US/gclicommands.properties
+++ b/devtools/shared/locales/en-US/gclicommands.properties
@@ -305,20 +305,16 @@ inspectNodeManual=A CSS selector for use
 # string is designed to be shown in a menu alongside the command name, which
 # is why it should be as short as possible.
 eyedropperDesc=Grab a color from the page
 
 # LOCALIZATION NOTE (eyedropperManual) A fuller description of the 'eyedropper'
 # command, displayed when the user asks for help on what it does.
 eyedropperManual=Open a panel that magnifies an area of page to inspect pixels and copy color values
 
-# LOCALIZATION NOTE (eyedropperTooltip) A string displayed as the
-# tooltip of button in devtools toolbox which toggles the Eyedropper tool.
-eyedropperTooltip=Grab a color from the page
-
 # LOCALIZATION NOTE (debuggerClosed) Used in the output of several commands
 # to explain that the debugger must be opened first.
 debuggerClosed=The debugger must be opened before using this command
 
 # LOCALIZATION NOTE (debuggerStopped) Used in the output of several commands
 # to explain that the debugger must be opened first before setting breakpoints.
 debuggerStopped=The debugger must be opened before setting breakpoints
 
--- a/devtools/shared/moz.build
+++ b/devtools/shared/moz.build
@@ -37,16 +37,18 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/unit
 
 JAR_MANIFESTS += ['jar.mn']
 
 DevToolsModules(
     'async-storage.js',
     'async-utils.js',
     'builtin-modules.js',
     'content-observer.js',
+    'css-color-db.js',
+    'css-color.js',
     'css-lexer.js',
     'css-parsing-utils.js',
     'css-properties-db.js',
     'defer.js',
     'deprecated-sync-thenables.js',
     'DevToolsUtils.js',
     'dom-node-constants.js',
     'dom-node-filter-constants.js',
--- a/devtools/shared/specs/inspector.js
+++ b/devtools/shared/specs/inspector.js
@@ -366,16 +366,26 @@ const walkerSpec = generateActorSpec({
   }
 });
 
 exports.walkerSpec = walkerSpec;
 
 const inspectorSpec = generateActorSpec({
   typeName: "inspector",
 
+  events: {
+    "color-picked": {
+      type: "colorPicked",
+      color: Arg(0, "string")
+    },
+    "color-pick-canceled": {
+      type: "colorPickCanceled"
+    }
+  },
+
   methods: {
     getWalker: {
       request: {
         options: Arg(0, "nullable:json")
       },
       response: {
         walker: RetVal("domwalker")
       }
@@ -404,13 +414,21 @@ const inspectorSpec = generateActorSpec(
     },
     getImageDataFromURL: {
       request: {url: Arg(0), maxDim: Arg(1, "nullable:number")},
       response: RetVal("imageData")
     },
     resolveRelativeURL: {
       request: {url: Arg(0, "string"), node: Arg(1, "nullable:domnode")},
       response: {value: RetVal("string")}
+    },
+    pickColorFromPage: {
+      request: {options: Arg(0, "nullable:json")},
+      response: {}
+    },
+    cancelPickColorFromPage: {
+      request: {},
+      response: {}
     }
   }
 });
 
 exports.inspectorSpec = inspectorSpec;
--- a/dom/asmjscache/AsmJSCache.cpp
+++ b/dom/asmjscache/AsmJSCache.cpp
@@ -1305,17 +1305,18 @@ private:
   RecvOnOpenCacheFile(const int64_t& aFileSize,
                       const FileDescriptor& aFileDesc) override
   {
     MOZ_ASSERT(NS_IsMainThread());
     MOZ_ASSERT(mState == eOpening);
 
     mFileSize = aFileSize;
 
-    mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
+    auto rawFD = aFileDesc.ClonePlatformHandle();
+    mFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
     if (!mFileDesc) {
       return false;
     }
 
     mState = eOpened;
     Notify(JS::AsmJSCache_Success);
     return true;
   }
--- a/dom/base/BodyUtil.cpp
+++ b/dom/base/BodyUtil.cpp
@@ -319,16 +319,17 @@ private:
         File::CreateMemoryFile(mParentObject,
                                reinterpret_cast<void *>(copy), body.Length(),
                                NS_ConvertUTF8toUTF16(mFilename),
                                NS_ConvertUTF8toUTF16(mContentType), /* aLastModifiedDate */ 0);
       Optional<nsAString> dummy;
       ErrorResult rv;
       mFormData->Append(name, *file, dummy, rv);
       if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
         return false;
       }
     }
 
     return true;
   }
 
 public:
--- a/dom/base/File.cpp
+++ b/dom/base/File.cpp
@@ -919,17 +919,19 @@ BlobImplFile::GetType(nsAString& aType)
         return;
       }
 
       RefPtr<GetTypeRunnable> runnable =
         new GetTypeRunnable(workerPrivate, this);
 
       ErrorResult rv;
       runnable->Dispatch(rv);
-      NS_WARN_IF(rv.Failed());
+      if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
+      }
       return;
     }
 
     nsresult rv;
     nsCOMPtr<nsIMIMEService> mimeService =
       do_GetService(NS_MIMESERVICE_CONTRACTID, &rv);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -2581,67 +2581,96 @@ Navigator::GetUserAgent(nsPIDOMWindowInn
 
   return siteSpecificUA->GetUserAgentForURIAndWindow(aURI, aWindow, aUserAgent);
 }
 
 #ifdef MOZ_EME
 static nsCString
 ToCString(const nsString& aString)
 {
-  return NS_ConvertUTF16toUTF8(aString);
+  nsCString str("'");
+  str.Append(NS_ConvertUTF16toUTF8(aString));
+  str.AppendLiteral("'");
+  return str;
+}
+
+static nsCString
+ToCString(const MediaKeysRequirement aValue)
+{
+  nsCString str("'");
+  str.Append(nsDependentCString(MediaKeysRequirementValues::strings[static_cast<uint32_t>(aValue)].value));
+  str.AppendLiteral("'");
+  return str;
 }
 
 static nsCString
 ToCString(const MediaKeySystemMediaCapability& aValue)
 {
   nsCString str;
-  str.AppendLiteral("{contentType='");
-  if (!aValue.mContentType.IsEmpty()) {
-    str.Append(ToCString(aValue.mContentType));
-  }
-  str.AppendLiteral("'}");
+  str.AppendLiteral("{contentType=");
+  str.Append(ToCString(aValue.mContentType));
+  str.AppendLiteral(", robustness=");
+  str.Append(ToCString(aValue.mRobustness));
+  str.AppendLiteral("}");
   return str;
 }
 
 template<class Type>
 static nsCString
 ToCString(const Sequence<Type>& aSequence)
 {
-  nsCString s;
-  s.AppendLiteral("[");
+  nsCString str;
+  str.AppendLiteral("[");
   for (size_t i = 0; i < aSequence.Length(); i++) {
     if (i != 0) {
-      s.AppendLiteral(",");
+      str.AppendLiteral(",");
     }
-    s.Append(ToCString(aSequence[i]));
+    str.Append(ToCString(aSequence[i]));
   }
-  s.AppendLiteral("]");
-  return s;
+  str.AppendLiteral("]");
+  return str;
+}
+
+template<class Type>
+static nsCString
+ToCString(const Optional<Sequence<Type>>& aOptional)
+{
+  nsCString str;
+  if (aOptional.WasPassed()) {
+    str.Append(ToCString(aOptional.Value()));
+  } else {
+    str.AppendLiteral("[]");
+  }
+  return str;
 }
 
 static nsCString
 ToCString(const MediaKeySystemConfiguration& aConfig)
 {
   nsCString str;
-  str.AppendLiteral("{");
-  str.AppendPrintf("label='%s'", NS_ConvertUTF16toUTF8(aConfig.mLabel).get());
-
-  if (aConfig.mInitDataTypes.WasPassed()) {
-    str.AppendLiteral(", initDataTypes=");
-    str.Append(ToCString(aConfig.mInitDataTypes.Value()));
-  }
-
-  if (aConfig.mAudioCapabilities.WasPassed()) {
-    str.AppendLiteral(", audioCapabilities=");
-    str.Append(ToCString(aConfig.mAudioCapabilities.Value()));
-  }
-  if (aConfig.mVideoCapabilities.WasPassed()) {
-    str.AppendLiteral(", videoCapabilities=");
-    str.Append(ToCString(aConfig.mVideoCapabilities.Value()));
-  }
+  str.AppendLiteral("{label=");
+  str.Append(ToCString(aConfig.mLabel));
+
+  str.AppendLiteral(", initDataTypes=");
+  str.Append(ToCString(aConfig.mInitDataTypes));
+
+  str.AppendLiteral(", audioCapabilities=");
+  str.Append(ToCString(aConfig.mAudioCapabilities));
+
+  str.AppendLiteral(", videoCapabilities=");
+  str.Append(ToCString(aConfig.mVideoCapabilities));
+
+  str.AppendLiteral(", distinctiveIdentifier=");
+  str.Append(ToCString(aConfig.mDistinctiveIdentifier));
+
+  str.AppendLiteral(", persistentState=");
+  str.Append(ToCString(aConfig.mPersistentState));
+
+  str.AppendLiteral(", sessionTypes=");
+  str.Append(ToCString(aConfig.mSessionTypes));
 
   str.AppendLiteral("}");
 
   return str;
 }
 
 static nsCString
 RequestKeySystemAccessLogString(const nsAString& aKeySystem,
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -1798,19 +1798,22 @@ nsContentUtils::IsControlledByServiceWor
   }
 
   RefPtr<workers::ServiceWorkerManager> swm =
     workers::ServiceWorkerManager::GetInstance();
   MOZ_ASSERT(swm);
 
   ErrorResult rv;
   bool controlled = swm->IsControlled(aDocument, rv);
-  NS_WARN_IF(rv.Failed());
-
-  return !rv.Failed() && controlled;
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+    return false;
+  }
+
+  return controlled;
 }
 
 /* static */
 void
 nsContentUtils::GetOfflineAppManifest(nsIDocument *aDocument, nsIURI **aURI)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aDocument);
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1737,20 +1737,16 @@ nsDocument::DeleteCycleCollectable()
 }
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsDocument)
   if (Element::CanSkip(tmp, aRemovingAllowed)) {
     EventListenerManager* elm = tmp->GetExistingListenerManager();
     if (elm) {
       elm->MarkForCC();
     }
-    if (tmp->mExpandoAndGeneration.expando.isObject()) {
-      JS::ExposeObjectToActiveJS(
-        &(tmp->mExpandoAndGeneration.expando.toObject()));
-    }
     return true;
   }
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
 
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsDocument)
   return Element::CanSkipInCC(tmp);
 NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
 
@@ -1926,23 +1922,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
       NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mDOMMediaQueryLists item");
       cb.NoteXPCOMChild(mql);
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsDocument)
 
-NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsDocument)
-  if (tmp->PreservingWrapper()) {
-    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpandoAndGeneration.expando)
-  }
-  NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
-NS_IMPL_CYCLE_COLLECTION_TRACE_END
-
+NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(nsDocument)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsDocument)
   tmp->mInUnlinkOrDeletion = true;
 
   // Clear out our external resources
   tmp->mExternalResourceMap.Shutdown();
 
   nsAutoScriptBlocker scriptBlocker;
@@ -2005,17 +1995,17 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(ns
 
   tmp->mRadioGroups.Clear();
 
   // nsDocument has a pretty complex destructor, so we're going to
   // assume that *most* cycles you actually want to break somewhere
   // else, and not unlink an awful lot here.
 
   tmp->mIdentifierMap.Clear();
-  tmp->mExpandoAndGeneration.Unlink();
+  tmp->mExpandoAndGeneration.OwnerUnlinked();
 
   if (tmp->mAnimationController) {
     tmp->mAnimationController->Unlink();
   }
 
   tmp->mPendingTitleChangeEvent.Revoke();
 
   if (tmp->mCSSLoader) {
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -787,16 +787,17 @@ nsFrameMessageManager::SendMessage(const
   NS_ENSURE_TRUE(dataArray, NS_ERROR_OUT_OF_MEMORY);
 
   for (uint32_t i = 0; i < len; ++i) {
     JS::Rooted<JS::Value> ret(aCx);
     ErrorResult rv;
     retval[i].Read(aCx, &ret, rv);
     if (rv.Failed()) {
       MOZ_ASSERT(false, "Unable to read structured clone in SendMessage");
+      rv.SuppressException();
       return NS_ERROR_UNEXPECTED;
     }
 
     NS_ENSURE_TRUE(JS_DefineElement(aCx, dataArray, i, ret, JSPROP_ENUMERATE),
                    NS_ERROR_OUT_OF_MEMORY);
   }
 
   aRetval.setObject(*dataArray);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -70,16 +70,17 @@ GK_ATOM(activateontab, "activateontab")
 GK_ATOM(actuate, "actuate")
 GK_ATOM(address, "address")
 GK_ATOM(after, "after")
 GK_ATOM(after_end, "after_end")
 GK_ATOM(after_start, "after_start")
 GK_ATOM(align, "align")
 GK_ATOM(alink, "alink")
 GK_ATOM(all, "all")
+GK_ATOM(allowdirs, "allowdirs")
 GK_ATOM(allowevents, "allowevents")
 GK_ATOM(allownegativeassertions, "allownegativeassertions")
 GK_ATOM(allowforms,"allow-forms")
 GK_ATOM(allowfullscreen, "allowfullscreen")
 GK_ATOM(allowmodals, "allow-modals")
 GK_ATOM(alloworientationlock,"allow-orientation-lock")
 GK_ATOM(allowpointerlock,"allow-pointer-lock")
 GK_ATOM(allowpopupstoescapesandbox,"allow-popups-to-escape-sandbox")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -2740,18 +2740,26 @@ nsGlobalWindow::SetNewDocument(nsIDocume
     JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal);
 #ifdef DEBUG
     bool sameOrigin = false;
     nsIPrincipal *existing =
       nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment));
     aDocument->NodePrincipal()->Equals(existing, &sameOrigin);
     MOZ_ASSERT(sameOrigin);
 #endif
-    JS_SetCompartmentPrincipals(compartment,
-                                nsJSPrincipals::get(aDocument->NodePrincipal()));
+    MOZ_ASSERT_IF(aDocument == oldDoc,
+                  xpc::GetCompartmentPrincipal(compartment) ==
+                  aDocument->NodePrincipal());
+    if (aDocument != oldDoc) {
+      JS_SetCompartmentPrincipals(compartment,
+                                  nsJSPrincipals::get(aDocument->NodePrincipal()));
+      // Make sure we clear out the old content XBL scope, so the new one will
+      // get created with a principal that subsumes our new principal.
+      xpc::ClearContentXBLScope(newInnerGlobal);
+    }
   } else {
     if (aState) {
       newInnerWindow = wsh->GetInnerWindow();
       newInnerGlobal = newInnerWindow->GetWrapperPreserveColor();
     } else {
       if (thisChrome) {
         newInnerWindow = nsGlobalChromeWindow::Create(this);
       } else if (mIsModalContentWindow) {
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2071,47 +2071,47 @@ NotifyGCEndRunnable::Run()
   const char16_t oomMsg[3] = { '{', '}', 0 };
   const char16_t *toSend = mMessage.get() ? mMessage.get() : oomMsg;
   observerService->NotifyObservers(nullptr, "garbage-collection-statistics", toSend);
 
   return NS_OK;
 }
 
 static void
-DOMGCSliceCallback(JSRuntime *aRt, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
+DOMGCSliceCallback(JSContext* aCx, JS::GCProgress aProgress, const JS::GCDescription &aDesc)
 {
   NS_ASSERTION(NS_IsMainThread(), "GCs must run on the main thread");
 
   switch (aProgress) {
     case JS::GC_CYCLE_BEGIN: {
       // Prevent cycle collections and shrinking during incremental GC.
       sCCLockedOut = true;
       break;
     }
 
     case JS::GC_CYCLE_END: {
       PRTime delta = GetCollectionTimeDelta();
 
       if (sPostGCEventsToConsole) {
         NS_NAMED_LITERAL_STRING(kFmt, "GC(T+%.1f)[%s] ");
         nsString prefix, gcstats;
-        gcstats.Adopt(aDesc.formatSummaryMessage(aRt));
+        gcstats.Adopt(aDesc.formatSummaryMessage(aCx));
         prefix.Adopt(nsTextFormatter::smprintf(kFmt.get(),
                                                double(delta) / PR_USEC_PER_SEC,
                                                ProcessNameForCollectorLog()));
         nsString msg = prefix + gcstats;
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(msg.get());
         }
       }
 
       if (sPostGCEventsToObserver) {
         nsString json;
-        json.Adopt(aDesc.formatJSON(aRt, PR_Now()));
+        json.Adopt(aDesc.formatJSON(aCx, PR_Now()));
         RefPtr<NotifyGCEndRunnable> notify = new NotifyGCEndRunnable(json);
         NS_DispatchToMainThread(notify);
       }
 
       sCCLockedOut = false;
       sIsCompactingOnUserInactive = false;
 
       // May need to kill the inter-slice GC timer
@@ -2162,31 +2162,31 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
       }
 
       if (ShouldTriggerCC(nsCycleCollector_suspectedCount())) {
         nsCycleCollector_dispatchDeferredDeletion();
       }
 
       if (sPostGCEventsToConsole) {
         nsString gcstats;
-        gcstats.Adopt(aDesc.formatSliceMessage(aRt));
+        gcstats.Adopt(aDesc.formatSliceMessage(aCx));
         nsCOMPtr<nsIConsoleService> cs = do_GetService(NS_CONSOLESERVICE_CONTRACTID);
         if (cs) {
           cs->LogStringMessage(gcstats.get());
         }
       }
 
       break;
 
     default:
       MOZ_CRASH("Unexpected GCProgress value");
   }
 
   if (sPrevGCSliceCallback) {
-    (*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
+    (*sPrevGCSliceCallback)(aCx, aProgress, aDesc);
   }
 
 }
 
 void
 nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy)
 {
   mWindowProxy = aWindowProxy;
--- a/dom/base/nsPluginArray.cpp
+++ b/dom/base/nsPluginArray.cpp
@@ -3,28 +3,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsPluginArray.h"
 
 #include "mozilla/dom/PluginArrayBinding.h"
 #include "mozilla/dom/PluginBinding.h"
+#include "mozilla/dom/HiddenPluginEvent.h"
 
 #include "nsMimeTypeArray.h"
 #include "Navigator.h"
 #include "nsIDocShell.h"
 #include "nsIWebNavigation.h"
 #include "nsPluginHost.h"
 #include "nsPluginTags.h"
 #include "nsIObserverService.h"
 #include "nsIWeakReference.h"
 #include "mozilla/Services.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsContentUtils.h"
+#include "nsIPermissionManager.h"
+#include "nsIDocument.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsPluginArray::nsPluginArray(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 {
 }
@@ -68,17 +71,18 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsPluginArray,
                                       mWindow,
-                                      mPlugins)
+                                      mPlugins,
+                                      mCTPPlugins)
 
 static void
 GetPluginMimeTypes(const nsTArray<RefPtr<nsPluginElement> >& aPlugins,
                    nsTArray<RefPtr<nsMimeType> >& aMimeTypes)
 {
   for (uint32_t i = 0; i < aPlugins.Length(); ++i) {
     nsPluginElement *plugin = aPlugins[i];
     aMimeTypes.AppendElements(plugin->MimeTypes());
@@ -148,16 +152,17 @@ nsPluginArray::Refresh(bool aReloadDocum
     // the both arrays contain the same plugin tags (though as
     // different types).
     if (newPluginTags.Length() == mPlugins.Length()) {
       return;
     }
   }
 
   mPlugins.Clear();
+  mCTPPlugins.Clear();
 
   nsCOMPtr<nsIDOMNavigator> navigator = mWindow->GetNavigator();
 
   if (!navigator) {
     return;
   }
 
   static_cast<mozilla::dom::Navigator*>(navigator.get())->RefreshMIMEArray();
@@ -223,16 +228,31 @@ nsPluginArray::NamedGetter(const nsAStri
   if (!AllowPlugins() || ResistFingerprinting()) {
     return nullptr;
   }
 
   EnsurePlugins();
 
   nsPluginElement* plugin = FindPlugin(mPlugins, aName);
   aFound = (plugin != nullptr);
+  if (!aFound) {
+    nsPluginElement* hiddenPlugin = FindPlugin(mCTPPlugins, aName);
+    if (hiddenPlugin) {
+      HiddenPluginEventInit init;
+      init.mTag = hiddenPlugin->PluginTag();
+      nsCOMPtr<nsIDocument> doc = hiddenPlugin->GetParentObject()->GetDoc();
+      RefPtr<HiddenPluginEvent> event =
+        HiddenPluginEvent::Constructor(doc, NS_LITERAL_STRING("HiddenPlugin"), init);
+      event->SetTarget(doc);
+      event->SetTrusted(true);
+      event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+      bool dummy;
+      doc->DispatchEvent(event, &dummy);
+    }
+  }
   return plugin;
 }
 
 uint32_t
 nsPluginArray::Length()
 {
   if (!AllowPlugins() || ResistFingerprinting()) {
     return 0;
@@ -284,34 +304,58 @@ operator<(const RefPtr<nsPluginElement>&
 {
   // Sort plugins alphabetically by name.
   return lhs->PluginTag()->Name() < rhs->PluginTag()->Name();
 }
 
 void
 nsPluginArray::EnsurePlugins()
 {
-  if (!mPlugins.IsEmpty()) {
+  if (!mPlugins.IsEmpty() || !mCTPPlugins.IsEmpty()) {
     // We already have an array of plugin elements.
     return;
   }
 
   RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
   if (!pluginHost) {
     // We have no plugin host.
     return;
   }
 
   nsTArray<nsCOMPtr<nsIInternalPluginTag> > pluginTags;
   pluginHost->GetPlugins(pluginTags);
 
   // need to wrap each of these with a nsPluginElement, which is
   // scriptable.
   for (uint32_t i = 0; i < pluginTags.Length(); ++i) {
-    mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    nsCOMPtr<nsPluginTag> pluginTag = do_QueryInterface(pluginTags[i]);
+    if (!pluginTag) {
+      mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+    } else if (pluginTag->IsActive()) {
+      uint32_t permission = nsIPermissionManager::ALLOW_ACTION;
+      if (pluginTag->IsClicktoplay()) {
+        nsCString name;
+        pluginTag->GetName(name);
+        if (NS_LITERAL_CSTRING("Shockwave Flash").Equals(name)) {
+          RefPtr<nsPluginHost> pluginHost = nsPluginHost::GetInst();
+          nsCString permString;
+          nsresult rv = pluginHost->GetPermissionStringForTag(pluginTag, 0, permString);
+          if (rv == NS_OK) {
+            nsIPrincipal* principal = mWindow->GetExtantDoc()->NodePrincipal();
+            nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+            permMgr->TestPermissionFromPrincipal(principal, permString.get(), &permission);
+          }
+        }
+      }
+      if (permission == nsIPermissionManager::ALLOW_ACTION) {
+        mPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      } else {
+        mCTPPlugins.AppendElement(new nsPluginElement(mWindow, pluginTags[i]));
+      }
+    }
   }
 
   // Alphabetize the enumeration order of non-hidden plugins to reduce
   // fingerprintable entropy based on plugins' installation file times.
   mPlugins.Sort();
 }
 
 // nsPluginElement implementation.
--- a/dom/base/nsPluginArray.h
+++ b/dom/base/nsPluginArray.h
@@ -55,16 +55,20 @@ public:
 private:
   virtual ~nsPluginArray();
 
   bool AllowPlugins() const;
   void EnsurePlugins();
 
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsTArray<RefPtr<nsPluginElement> > mPlugins;
+  /* A separate list of click-to-play plugins that we don't tell content
+   * about but keep track of so we can still prompt the user to click to play.
+   */
+  nsTArray<RefPtr<nsPluginElement> > mCTPPlugins;
 };
 
 class nsPluginElement final : public nsISupports,
                               public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsPluginElement)
--- a/dom/base/test/browser_use_counters.js
+++ b/dom/base/test/browser_use_counters.js
@@ -82,17 +82,17 @@ add_task(function* () {
       resolve();
     });
   });
 });
 
 
 function waitForDestroyedDocuments() {
   let deferred = promise.defer();
-  SpecialPowers.exactGC(window, deferred.resolve);
+  SpecialPowers.exactGC(deferred.resolve);
   return deferred.promise;
 }
 
 function waitForPageLoad(browser) {
   return ContentTask.spawn(browser, null, function*() {
     Cu.import("resource://gre/modules/PromiseUtils.jsm");
     yield new Promise(resolve => {
       let listener = () => {
--- a/dom/base/test/test_navigator_language.html
+++ b/dom/base/test/test_navigator_language.html
@@ -190,17 +190,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     var frame = document.createElement('iframe');
     frame.src = 'data:text/html,<script>window.onlanguagechange=function(){}<\/script>';
     document.body.appendChild(frame);
 
     frame.contentWindow.onload = function() {
       document.body.removeChild(frame);
       frame = null;
 
-      SpecialPowers.exactGC(window, function() {
+      SpecialPowers.exactGC(function() {
         // This should not crash.
         SpecialPowers.pushPrefEnv({"set": [['intl.accept_languages', 'en-GB']]}, nextTest);
       });
     }
   });
 
   // There is one test using document.body.
   addLoadEvent(function() {
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -840,16 +840,20 @@ DOMInterfaces = {
     'headerFile' : 'nsPluginArray.h',
     'nativeType': 'nsPluginElement',
 },
 
 'PluginArray': {
     'nativeType': 'nsPluginArray',
 },
 
+'PluginTag': {
+    'nativeType': 'nsIPluginTag',
+},
+
 'PopupBoxObject': {
     'resultNotAddRefed': ['triggerNode', 'anchorNode'],
 },
 
 'Position': {
     'headerFile': 'nsGeoPosition.h'
 },
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -11718,18 +11718,23 @@ class CGDOMJSProxyHandler(CGClass):
              descriptor.interface.getExtendedAttribute('OverrideBuiltins'))):
             methods.append(CGDOMJSProxyHandler_setCustom(descriptor))
         if descriptor.hasNonOrdinaryGetPrototypeOf():
             methods.append(CGDOMJSProxyHandler_getPrototypeIfOrdinary())
         if descriptor.operations['LegacyCaller']:
             methods.append(CGDOMJSProxyHandler_call())
             methods.append(CGDOMJSProxyHandler_isCallable())
 
+        if descriptor.interface.getExtendedAttribute('OverrideBuiltins'):
+            parentClass = 'ShadowingDOMProxyHandler'
+        else:
+            parentClass = 'mozilla::dom::DOMProxyHandler'
+
         CGClass.__init__(self, 'DOMProxyHandler',
-                         bases=[ClassBase('mozilla::dom::DOMProxyHandler')],
+                         bases=[ClassBase(parentClass)],
                          constructors=constructors,
                          methods=methods)
 
 
 class CGDOMJSProxyHandlerDeclarer(CGThing):
     """
     A class for declaring a DOMProxyHandler.
     """
--- a/dom/bindings/DOMJSProxyHandler.cpp
+++ b/dom/bindings/DOMJSProxyHandler.cpp
@@ -292,10 +292,31 @@ DOMProxyHandler::GetExpandoObject(JSObje
   }
 
   js::ExpandoAndGeneration* expandoAndGeneration =
     static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
   v = expandoAndGeneration->expando;
   return v.isUndefined() ? nullptr : &v.toObject();
 }
 
+void
+ShadowingDOMProxyHandler::trace(JSTracer* trc, JSObject* proxy) const
+{
+  DOMProxyHandler::trace(trc, proxy);
+
+  MOZ_ASSERT(IsDOMProxy(proxy), "expected a DOM proxy object");
+  JS::Value v = js::GetProxyExtra(proxy, JSPROXYSLOT_EXPANDO);
+  MOZ_ASSERT(!v.isObject(), "Should not have expando object directly!");
+
+  if (v.isUndefined()) {
+    // This can happen if we GC while creating our object, before we get a
+    // chance to set up its JSPROXYSLOT_EXPANDO slot.
+    return;
+  }
+
+  js::ExpandoAndGeneration* expandoAndGeneration =
+    static_cast<js::ExpandoAndGeneration*>(v.toPrivate());
+  JS::TraceEdge(trc, &expandoAndGeneration->expando,
+                "Shadowing DOM proxy expando");
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/bindings/DOMJSProxyHandler.h
+++ b/dom/bindings/DOMJSProxyHandler.h
@@ -27,17 +27,19 @@ enum {
    * The expando object is a plain JSObject whose properties correspond to
    * "expandos" (custom properties set by the script author).
    *
    * The exact value stored in the JSPROXYSLOT_EXPANDO slot depends on whether
    * the interface is annotated with the [OverrideBuiltins] extended attribute.
    *
    * If it is, the proxy is initialized with a PrivateValue, which contains a
    * pointer to a js::ExpandoAndGeneration object; this contains a pointer to
-   * the actual expando object as well as the "generation" of the object.
+   * the actual expando object as well as the "generation" of the object.  The
+   * proxy handler will trace the expando object stored in the
+   * js::ExpandoAndGeneration while the proxy itself is alive.
    *
    * If it is not, the proxy is initialized with an UndefinedValue. In
    * EnsureExpandoObject, it is set to an ObjectValue that points to the
    * expando object directly. (It is set back to an UndefinedValue only when
    * the object is about to die.)
    */
   JSPROXYSLOT_EXPANDO = 0
 };
@@ -135,16 +137,23 @@ public:
   /* GetAndClearExpandoObject does not DROP or clear the preserving wrapper flag. */
   static JSObject* GetAndClearExpandoObject(JSObject* obj);
   static JSObject* EnsureExpandoObject(JSContext* cx,
                                        JS::Handle<JSObject*> obj);
 
   static const char family;
 };
 
+// Class used by shadowing handlers (the ones that have [OverrideBuiltins].
+// This handles tracing the expando in ExpandoAndGeneration.
+class ShadowingDOMProxyHandler : public DOMProxyHandler
+{
+  virtual void trace(JSTracer* trc, JSObject* proxy) const override;
+};
+
 inline bool IsDOMProxy(JSObject *obj)
 {
     const js::Class* clasp = js::GetObjectClass(obj);
     return clasp->isProxy() &&
            js::GetProxyHandler(obj)->family() == &DOMProxyHandler::family;
 }
 
 inline const DOMProxyHandler*
--- a/dom/bindings/test/test_traceProtos.html
+++ b/dom/bindings/test/test_traceProtos.html
@@ -24,14 +24,14 @@ SimpleTest.waitForExplicitFinish();
 
 function callback() {
   new XMLHttpRequest().upload;
   ok(true, "Accessing unreferenced DOM interface objects shouldn't crash");
   SimpleTest.finish();
 }
 
 delete window.XMLHttpRequestUpload;
-SpecialPowers.exactGC(window, callback);
+SpecialPowers.exactGC(callback);
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/broadcastchannel/BroadcastChannelChild.cpp
+++ b/dom/broadcastchannel/BroadcastChannelChild.cpp
@@ -94,31 +94,32 @@ BroadcastChannelChild::RecvNotify(const 
   cloneData.UseExternalData(buffer.data, buffer.dataLength);
 
   JSContext* cx = jsapi.cx();
   JS::Rooted<JS::Value> value(cx, JS::NullValue());
   if (buffer.dataLength) {
     ErrorResult rv;
     cloneData.Read(cx, &value, rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return true;
     }
   }
 
   RootedDictionary<MessageEventInit> init(cx);
   init.mBubbles = false;
   init.mCancelable = false;
   init.mOrigin.Construct(mOrigin);
   init.mData = value;
 
   ErrorResult rv;
   RefPtr<MessageEvent> event =
     MessageEvent::Constructor(mBC, NS_LITERAL_STRING("message"), init, rv);
-  if (rv.Failed()) {
-    NS_WARNING("Failed to create a MessageEvent object.");
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return true;
   }
 
   event->SetTrusted(true);
 
   bool status;
   mBC->DispatchEvent(static_cast<Event*>(event.get()), &status);
 
--- a/dom/cache/test/mochitest/test_cache_orphaned_body.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_body.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_orphaned_cache.html
+++ b/dom/cache/test/mochitest/test_cache_orphaned_cache.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/cache/test/mochitest/test_cache_shrink.html
+++ b/dom/cache/test/mochitest/test_cache_shrink.html
@@ -49,17 +49,17 @@ function resetStorage() {
     var request = qms.reset();
     var cb = SpecialPowers.wrapCallback(resolve);
     request.callback = cb;
   });
 }
 
 function gc() {
   return new Promise(function(resolve, reject) {
-    SpecialPowers.exactGC(window, resolve);
+    SpecialPowers.exactGC(resolve);
   });
 }
 
 SimpleTest.waitForExplicitFinish();
 SpecialPowers.pushPrefEnv({
   "set": [["dom.caches.enabled", true],
           ["dom.caches.testing.enabled", true],
           ["dom.quotaManager.testing", true]],
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -1247,25 +1247,25 @@ nsGonkCameraControl::StartRecordingImpl(
   // close the file descriptor when we leave this function. Also note, that
   // since we're already off the main thread, we don't need to dispatch this.
   // We just let the CloseFileRunnable destructor do the work.
   RefPtr<CloseFileRunnable> closer;
   if (aFileDescriptor->mFileDescriptor.IsValid()) {
     closer = new CloseFileRunnable(aFileDescriptor->mFileDescriptor);
   }
   nsresult rv;
-  int fd = aFileDescriptor->mFileDescriptor.PlatformHandle();
+  auto rawFD = aFileDescriptor->mFileDescriptor.ClonePlatformHandle();
   if (aOptions) {
-    rv = SetupRecording(fd, aOptions->rotation, aOptions->maxFileSizeBytes,
+    rv = SetupRecording(rawFD.get(), aOptions->rotation, aOptions->maxFileSizeBytes,
                         aOptions->maxVideoLengthMs);
     if (NS_SUCCEEDED(rv)) {
       rv = SetupRecordingFlash(aOptions->autoEnableLowLightTorch);
     }
   } else {
-    rv = SetupRecording(fd, 0, 0, 0);
+    rv = SetupRecording(rawFD.get(), 0, 0, 0);
   }
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
 #ifdef MOZ_WIDGET_GONK
   if (mRecorder->start() != OK) {
     DOM_CAMERA_LOGE("mRecorder->start() failed\n");
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -1050,19 +1050,16 @@ NS_INTERFACE_MAP_END
 // Initialize our static variables.
 uint32_t CanvasRenderingContext2D::sNumLivingContexts = 0;
 DrawTarget* CanvasRenderingContext2D::sErrorTarget = nullptr;
 
 
 
 CanvasRenderingContext2D::CanvasRenderingContext2D()
   : mRenderingMode(RenderingMode::OpenGLBackendMode)
-#ifdef USE_SKIA_GPU
-  , mVideoTexture(0)
-#endif
   // these are the default values from the Canvas spec
   , mWidth(0), mHeight(0)
   , mZero(false), mOpaque(false)
   , mResetLayer(true)
   , mIPC(false)
   , mIsSkiaGL(false)
   , mHasPendingStableStateCallback(false)
   , mDrawObserver(nullptr)
@@ -1094,25 +1091,16 @@ CanvasRenderingContext2D::~CanvasRenderi
   // Drop references from all CanvasRenderingContext2DUserData to this context
   for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
   if (!sNumLivingContexts) {
     NS_IF_RELEASE(sErrorTarget);
   }
-#ifdef USE_SKIA_GPU
-  if (mVideoTexture) {
-    SkiaGLGlue* glue = gfxPlatform::GetPlatform()->GetSkiaGLGlue();
-    MOZ_ASSERT(glue);
-    glue->GetGLContext()->MakeCurrent();
-    glue->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
-  }
-#endif
-
   RemoveDemotableContext(this);
 }
 
 JSObject*
 CanvasRenderingContext2D::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return CanvasRenderingContext2DBinding::Wrap(aCx, this, aGivenProto);
 }
@@ -1339,25 +1327,16 @@ bool CanvasRenderingContext2D::SwitchRen
   }
 
 #ifdef USE_SKIA_GPU
   // Do not attempt to switch into GL mode if the platform doesn't allow it.
   if ((aRenderingMode == RenderingMode::OpenGLBackendMode) &&
       !gfxPlatform::GetPlatform()->UseAcceleratedCanvas()) {
       return false;
   }
-
-  if (mRenderingMode == RenderingMode::OpenGLBackendMode) {
-    if (mVideoTexture) {
-      gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->MakeCurrent();
-      gfxPlatform::GetPlatform()->GetSkiaGLGlue()->GetGLContext()->fDeleteTextures(1, &mVideoTexture);
-    }
-    mCurrentVideoSize.width = 0;
-    mCurrentVideoSize.height = 0;
-  }
 #endif
 
   RefPtr<SourceSurface> snapshot;
   Matrix transform;
   RefPtr<PersistentBufferProvider> oldBufferProvider = mBufferProvider;
   AutoReturnSnapshot autoReturn(nullptr);
 
   if (mTarget) {
@@ -4645,56 +4624,54 @@ CanvasRenderingContext2D::DrawImage(cons
     AutoLockImage lockImage(container);
     layers::Image* srcImage = lockImage.GetImage();
     if (!srcImage) {
       aError.Throw(NS_ERROR_NOT_AVAILABLE);
       return;
     }
 
     gl->MakeCurrent();
-    if (!mVideoTexture) {
-      gl->fGenTextures(1, &mVideoTexture);
-    }
+    GLuint videoTexture = 0;
+    gl->fGenTextures(1, &videoTexture);
     // skiaGL expect upload on drawing, and uses texture 0 for texturing,
     // so we must active texture 0 and bind the texture for it.
     gl->fActiveTexture(LOCAL_GL_TEXTURE0);
-    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, mVideoTexture);
-
-    bool dimensionsMatch = mCurrentVideoSize.width == srcImage->GetSize().width &&
-                           mCurrentVideoSize.height == srcImage->GetSize().height;
-    if (!dimensionsMatch) {
-      // we need to allocation
-      mCurrentVideoSize.width = srcImage->GetSize().width;
-      mCurrentVideoSize.height = srcImage->GetSize().height;
-      gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
-      gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
-    }
+    gl->fBindTexture(LOCAL_GL_TEXTURE_2D, videoTexture);
+
+    gl->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGB, srcImage->GetSize().width, srcImage->GetSize().height, 0, LOCAL_GL_RGB, LOCAL_GL_UNSIGNED_SHORT_5_6_5, nullptr);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T, LOCAL_GL_CLAMP_TO_EDGE);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER, LOCAL_GL_LINEAR);
+    gl->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER, LOCAL_GL_LINEAR);
+
     const gl::OriginPos destOrigin = gl::OriginPos::TopLeft;
     bool ok = gl->BlitHelper()->BlitImageToTexture(srcImage, srcImage->GetSize(),
-                                                   mVideoTexture, LOCAL_GL_TEXTURE_2D,
+                                                   videoTexture, LOCAL_GL_TEXTURE_2D,
                                                    destOrigin);
     if (ok) {
       NativeSurface texSurf;
       texSurf.mType = NativeSurfaceType::OPENGL_TEXTURE;
       texSurf.mFormat = SurfaceFormat::R5G6B5_UINT16;
-      texSurf.mSize.width = mCurrentVideoSize.width;
-      texSurf.mSize.height = mCurrentVideoSize.height;
-      texSurf.mSurface = (void*)((uintptr_t)mVideoTexture);
+      texSurf.mSize.width = srcImage->GetSize().width;
+      texSurf.mSize.height = srcImage->GetSize().height;
+      texSurf.mSurface = (void*)((uintptr_t)videoTexture);
 
       srcSurf = mTarget->CreateSourceSurfaceFromNativeSurface(texSurf);
-      imgSize.width = mCurrentVideoSize.width;
-      imgSize.height = mCurrentVideoSize.height;
+      if (!srcSurf) {
+        gl->fDeleteTextures(1, &videoTexture);
+      }
+      imgSize.width = srcImage->GetSize().width;
+      imgSize.height = srcImage->GetSize().height;
 
       int32_t displayWidth = video->VideoWidth();
       int32_t displayHeight = video->VideoHeight();
       aSw *= (double)imgSize.width / (double)displayWidth;
       aSh *= (double)imgSize.height / (double)displayHeight;
+    } else {
+      gl->fDeleteTextures(1, &videoTexture);
     }
     srcImage = nullptr;
 
     if (mCanvasElement) {
       CanvasUtils::DoDrawImageSecurityCheck(mCanvasElement,
                                             principal, false,
                                             video->GetCORSMode() != CORS_NONE);
     }
--- a/dom/canvas/CanvasRenderingContext2D.h
+++ b/dom/canvas/CanvasRenderingContext2D.h
@@ -718,20 +718,16 @@ protected:
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
 
   static void AddDemotableContext(CanvasRenderingContext2D* aContext);
   static void RemoveDemotableContext(CanvasRenderingContext2D* aContext);
 
   RenderingMode mRenderingMode;
 
-  // Texture informations for fast video rendering
-  unsigned int mVideoTexture;
-  nsIntSize mCurrentVideoSize;
-
   // Member vars
   int32_t mWidth, mHeight;
 
   // This is true when the canvas is valid, but of zero size, this requires
   // specific behavior on some operations.
   bool mZero;
 
   bool mOpaque;
--- a/dom/canvas/ImageBitmap.cpp
+++ b/dom/canvas/ImageBitmap.cpp
@@ -86,16 +86,17 @@ static already_AddRefed<DataSourceSurfac
 CropAndCopyDataSourceSurface(DataSourceSurface* aSurface, const IntRect& aCropRect)
 {
   MOZ_ASSERT(aSurface);
 
   // Check the aCropRect
   ErrorResult error;
   const IntRect positiveCropRect = FixUpNegativeDimension(aCropRect, error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   // Calculate the size of the new SourceSurface.
   // We cannot keep using aSurface->GetFormat() to create new DataSourceSurface,
   // since it might be SurfaceFormat::B8G8R8X8 which does not handle opacity,
   // however the specification explicitly define that "If any of the pixels on
   // this rectangle are outside the area where the input bitmap was placed, then
@@ -1101,16 +1102,17 @@ AsyncFulfillImageBitmapPromise(Promise* 
 static already_AddRefed<SourceSurface>
 DecodeBlob(Blob& aBlob)
 {
   // Get the internal stream of the blob.
   nsCOMPtr<nsIInputStream> stream;
   ErrorResult error;
   aBlob.Impl()->GetInternalStream(getter_AddRefs(stream), error);
   if (NS_WARN_IF(error.Failed())) {
+    error.SuppressException();
     return nullptr;
   }
 
   // Get the MIME type string of the blob.
   // The type will be checked in the DecodeImage() method.
   nsAutoString mimeTypeUTF16;
   aBlob.GetType(mimeTypeUTF16);
 
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -7,34 +7,25 @@
 #include "WebGLSampler.h"
 #include "GLContext.h"
 
 namespace mozilla {
 
 already_AddRefed<WebGLSampler>
 WebGL2Context::CreateSampler()
 {
-    const char funcName[] = "createSampler";
-
     if (IsContextLost())
         return nullptr;
 
-    /*
     GLuint sampler;
     MakeContextCurrent();
     gl->fGenSamplers(1, &sampler);
 
     RefPtr<WebGLSampler> globj = new WebGLSampler(this, sampler);
     return globj.forget();
-    */
-
-    ErrorInvalidOperation("%s: Sampler objects are still under development, and are"
-                          " currently disabled.",
-                          funcName);
-    return nullptr;
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
     if (IsContextLost())
         return;
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -771,52 +771,107 @@ WebGLContext::GetFramebufferAttachmentPa
         ErrorInvalidEnum("%s: For the default framebuffer, can only query COLOR, DEPTH,"
                          " or STENCIL.",
                          funcName);
         return JS::NullValue();
     }
 
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE:
+        switch (attachment) {
+        case LOCAL_GL_BACK:
+            break;
+        case LOCAL_GL_DEPTH:
+            if (!mOptions.depth) {
+              return JS::Int32Value(LOCAL_GL_NONE);
+            }
+            break;
+        case LOCAL_GL_STENCIL:
+            if (!mOptions.stencil) {
+              return JS::Int32Value(LOCAL_GL_NONE);
+            }
+            break;
+        default:
+            ErrorInvalidEnum("%s: With the default framebuffer, can only query COLOR, DEPTH,"
+                             " or STENCIL for GL_FRAMEBUFFER_ATTACHMENT_OBJECT_TYPE",
+                             funcName);
+            return JS::NullValue();
+        }
         return JS::Int32Value(LOCAL_GL_FRAMEBUFFER_DEFAULT);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_OBJECT_NAME:
         return JS::NullValue();
 
     ////////////////
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_BLUE_SIZE:
         if (attachment == LOCAL_GL_BACK)
             return JS::NumberValue(8);
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_ALPHA_SIZE:
-        if (attachment == LOCAL_GL_BACK)
-            return JS::NumberValue(mOptions.alpha ? 8 : 0);
+        if (attachment == LOCAL_GL_BACK) {
+            if (mOptions.alpha) {
+                return JS::NumberValue(8);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an alpha buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_DEPTH_SIZE:
-        if (attachment == LOCAL_GL_DEPTH)
-            return JS::NumberValue(mOptions.depth ? 24 : 0);
+        if (attachment == LOCAL_GL_DEPTH) {
+            if (mOptions.depth) {
+                return JS::NumberValue(24);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_STENCIL_SIZE:
-        if (attachment == LOCAL_GL_STENCIL)
-            return JS::NumberValue(mOptions.stencil ? 8 : 0);
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (mOptions.stencil) {
+                return JS::NumberValue(8);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+            return JS::NullValue();
+        }
         return JS::NumberValue(0);
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
-        if (attachment == LOCAL_GL_STENCIL)
-            return JS::NumberValue(LOCAL_GL_UNSIGNED_INT);
-        else
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (mOptions.stencil) {
+                return JS::NumberValue(LOCAL_GL_UNSIGNED_INT);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+        } else if (attachment == LOCAL_GL_DEPTH) {
+            if (mOptions.depth) {
+                return JS::NumberValue(LOCAL_GL_UNSIGNED_NORMALIZED);
+            }
+            ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+        } else { // LOCAL_GL_BACK
             return JS::NumberValue(LOCAL_GL_UNSIGNED_NORMALIZED);
+        }
+        return JS::NullValue();
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
+        if (attachment == LOCAL_GL_STENCIL) {
+            if (!mOptions.stencil) {
+                ErrorInvalidOperation("The default framebuffer doesn't contain an stencil buffer");
+                return JS::NullValue();
+            }
+        } else if (attachment == LOCAL_GL_DEPTH) {
+            if (!mOptions.depth) {
+                ErrorInvalidOperation("The default framebuffer doesn't contain an depth buffer");
+                return JS::NullValue();
+            }
+        }
         return JS::NumberValue(LOCAL_GL_LINEAR);
     }
 
     ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
     return JS::NullValue();
 }
 
 JS::Value
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -523,17 +523,17 @@ WebGLFBAttachPoint::GetParameter(const c
         webgl->ErrorInvalidEnum("%s: Invalid pname: 0x%04x", funcName, pname);
         return JS::NullValue();
     }
 
     const auto usage = Format();
     if (!usage)
         return JS::NullValue();
 
-    const auto format = usage->format;
+    auto format = usage->format;
 
     GLint ret = 0;
     switch (pname) {
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_RED_SIZE:
         ret = format->r;
         break;
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_GREEN_SIZE:
         ret = format->g;
@@ -554,20 +554,48 @@ WebGLFBAttachPoint::GetParameter(const c
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COLOR_ENCODING:
         ret = (format->isSRGB ? LOCAL_GL_SRGB
                               : LOCAL_GL_LINEAR);
         break;
 
     case LOCAL_GL_FRAMEBUFFER_ATTACHMENT_COMPONENT_TYPE:
         MOZ_ASSERT(attachment != LOCAL_GL_DEPTH_STENCIL_ATTACHMENT);
 
+        if (format->componentType == webgl::ComponentType::Special) {
+            // Special format is used for DS mixed format(e.g. D24S8 and D32FS8).
+            MOZ_ASSERT(format->unsizedFormat == webgl::UnsizedFormat::DS);
+            MOZ_ASSERT(attachment == LOCAL_GL_DEPTH_ATTACHMENT ||
+                       attachment == LOCAL_GL_STENCIL_ATTACHMENT);
+
+            if (attachment == LOCAL_GL_DEPTH_ATTACHMENT) {
+                switch (format->effectiveFormat) {
+                case webgl::EffectiveFormat::DEPTH24_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT24);
+                    break;
+                case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::DEPTH_COMPONENT32F);
+                    break;
+                default:
+                    MOZ_ASSERT(false, "no matched DS format");
+                    break;
+                }
+            } else if (attachment == LOCAL_GL_STENCIL_ATTACHMENT) {
+                switch (format->effectiveFormat) {
+                case webgl::EffectiveFormat::DEPTH24_STENCIL8:
+                case webgl::EffectiveFormat::DEPTH32F_STENCIL8:
+                    format = webgl::GetFormat(webgl::EffectiveFormat::STENCIL_INDEX8);
+                    break;
+                default:
+                    MOZ_ASSERT(false, "no matched DS format");
+                    break;
+                }
+            }
+        }
+
         switch (format->componentType) {
-        case webgl::ComponentType::Special:
-            MOZ_ASSERT(false, "Should never happen.");
-            break;
         case webgl::ComponentType::None:
             ret = LOCAL_GL_NONE;
             break;
         case webgl::ComponentType::Int:
             ret = LOCAL_GL_INT;
             break;
         case webgl::ComponentType::UInt:
             ret = LOCAL_GL_UNSIGNED_INT;
@@ -576,16 +604,19 @@ WebGLFBAttachPoint::GetParameter(const c
             ret = LOCAL_GL_SIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::NormUInt:
             ret = LOCAL_GL_UNSIGNED_NORMALIZED;
             break;
         case webgl::ComponentType::Float:
             ret = LOCAL_GL_FLOAT;
             break;
+        default:
+            MOZ_ASSERT(false, "No matched component type");
+            break;
         }
         break;
 
     default:
         MOZ_ASSERT(false, "Missing case.");
         break;
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/canvas/crashtests/1288872-1.html
@@ -0,0 +1,6 @@
+<canvas id='id0'></canvas>
+<script>
+var c=document.getElementById('id0').getContext('2d');
+c.transform(1,0,1,0,0,0);
+c.fillText('A',0,53);
+</script>
\ No newline at end of file
--- a/dom/canvas/crashtests/crashtests.list
+++ b/dom/canvas/crashtests/crashtests.list
@@ -25,8 +25,10 @@ load 1183363.html
 load 1190705.html
 load 1223740-1.html
 load 1225381-1.html
 skip-if(azureCairo) load 1229983-1.html
 load 1229932-1.html
 load 1244850-1.html
 load 1246775-1.html
 skip-if(d2d) load 1287515-1.html
+load 1288872-1.html
+
--- a/dom/canvas/test/webgl-conf/generated-mochitest.ini
+++ b/dom/canvas/test/webgl-conf/generated-mochitest.ini
@@ -4541,20 +4541,18 @@ skip-if = (os == 'android' || os == 'lin
 skip-if = (os == 'win' && os_version == '6.1') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__texture-offset-out-of-range.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__uniform-location-length-limits.html]
 skip-if = (os == 'win' && debug) || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__glsl3__vector-dynamic-indexing.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__expando-loss-2.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__instanceof-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__misc__uninitialized-test-2.html]
 skip-if = (os == 'mac') || (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__query__occlusion-query.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__query__query.html]
 skip-if = (os == 'win') || (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__reading__read-pixels-from-fbo-test.html]
@@ -4581,20 +4579,18 @@ fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__element-index-uint.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__framebuffer-completeness-unaffected.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__rendering__instanced-arrays.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__samplers__sampler-drawing-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__samplers__samplers.html]
-fail-if = (os == 'mac') || (os == 'win')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-enum-tests.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-get-calls.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
 [generated/test_2_conformance2__state__gl-getstring.html]
 skip-if = (os == 'android' || os == 'linux' || (os == 'win' && os_version == '5.1') || (os == 'win' && os_version == '6.2'))
--- a/dom/canvas/test/webgl-conf/mochitest-errata.ini
+++ b/dom/canvas/test/webgl-conf/mochitest-errata.ini
@@ -128,28 +128,22 @@ fail-if = (os == 'win') || (os == 'mac')
 # application crashed [@ mozilla::gl::GLContext::AfterGLCall]
 skip-if = (os == 'android') || (os == 'win')
 
 [generated/test_2_conformance__textures__misc__cube-incomplete-fbo.html]
 fail-if = (os == 'mac')
 skip-if = (os == 'win')
 [generated/test_2_conformance__extensions__webgl-compressed-texture-s3tc.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__misc__instanceof-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-invalid-data.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__samplers__sampler-drawing-test.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-type-restrictions.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__rendering__draw-buffers.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__samplers__samplers.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance__textures__misc__tex-image-with-format-and-type.html]
 fail-if = (os == 'mac')
 [generated/test_2_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-copying-restrictions.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__glsl3__forbidden-operators.html]
 fail-if = (os == 'mac') || (os == 'win')
@@ -164,18 +158,16 @@ fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__buffers__buffer-copying-contents.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_2_conformance2__reading__read-pixels-pack-parameters.html]
 fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__attribs__gl-vertexattribpointer.html]
 fail-if = (os == 'mac') || (os == 'win') || (os == 'android') || (os == 'linux')
 [generated/test_2_conformance__textures__misc__tex-sub-image-2d-bad-args.html]
 fail-if = (os == 'mac') || (os == 'win')
-[generated/test_2_conformance2__misc__expando-loss-2.html]
-fail-if = (os == 'mac') || (os == 'win')
 [generated/test_conformance__ogles__GL__biuDepthRange__biuDepthRange_001_to_002.html]
 fail-if = (os == 'android') || (os == 'linux')
 [generated/test_conformance__ogles__GL__gl_FragCoord__gl_FragCoord_001_to_003.html]
 fail-if = (os == 'android') || (os == 'linux')
 
 [generated/test_conformance__textures__misc__texture-size-limit.html]
 fail-if = (os == 'linux') || (os == 'android')
 skip-if = (os == 'linux' && asan)
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -1953,16 +1953,17 @@ public:
   }
 
   NS_IMETHOD Run() override
   {
     ErrorResult rv;
     nsCOMPtr<nsIInputStream> stream;
     mBlob->GetInternalStream(getter_AddRefs(stream), rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     bool check = false;
     mFile->mFile->Exists(&check);
     if (check) {
       return Reject(POST_ERROR_EVENT_FILE_EXISTS);
     }
@@ -2065,16 +2066,17 @@ public:
   }
 
   NS_IMETHOD Run() override
   {
     ErrorResult rv;
     nsCOMPtr<nsIInputStream> stream;
     mBlob->GetInternalStream(getter_AddRefs(stream), rv);
     if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
       return Reject(POST_ERROR_EVENT_UNKNOWN);
     }
 
     bool check = false;
     mFile->mFile->Exists(&check);
     if (!check) {
       return Reject(POST_ERROR_EVENT_FILE_DOES_NOT_EXIST);
     }
--- a/dom/devicestorage/nsDeviceStorage.h
+++ b/dom/devicestorage/nsDeviceStorage.h
@@ -4,17 +4,16 @@
  * 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/. */
 
 #ifndef nsDeviceStorage_h
 #define nsDeviceStorage_h
 
 #include "mozilla/Atomics.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/Logging.h"
 #include "mozilla/dom/devicestorage/DeviceStorageRequestChild.h"
 
 #include "DOMRequest.h"
 #include "DOMCursor.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMClassInfoID.h"
 #include "nsIClassInfo.h"
 #include "nsIDOMWindow.h"
--- a/dom/events/BeforeAfterKeyboardEvent.cpp
+++ b/dom/events/BeforeAfterKeyboardEvent.cpp
@@ -36,17 +36,19 @@ BeforeAfterKeyboardEvent::Constructor(
                             EventTarget* aOwner,
                             const nsAString& aType,
                             const BeforeAfterKeyboardEventInit& aParam)
 {
   RefPtr<BeforeAfterKeyboardEvent> event =
     new BeforeAfterKeyboardEvent(aOwner, nullptr, nullptr);
   ErrorResult rv;
   event->InitWithKeyboardEventInit(aOwner, aType, aParam, rv);
-  NS_WARN_IF(rv.Failed());
+  if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
+  }
 
   event->mEvent->AsBeforeAfterKeyboardEvent()->mEmbeddedCancelled =
     aParam.mEmbeddedCancelled;
 
   return event.forget();
 }
 
 // static
--- a/dom/events/DataTransfer.cpp
+++ b/dom/events/DataTransfer.cpp
@@ -1281,17 +1281,19 @@ DataTransfer::SetDataWithPrincipalFromOt
   } else {
     nsAutoString format;
     GetRealFormat(aFormat, format);
 
     ErrorResult rv;
     RefPtr<DataTransferItem> item =
       mItems->SetDataWithPrincipal(format, aData, aIndex, aPrincipal,
                                    /* aInsertOnly = */ false, aHidden, rv);
-    NS_WARN_IF(rv.Failed());
+    if (NS_WARN_IF(rv.Failed())) {
+      rv.SuppressException();
+    }
   }
 }
 
 void
 DataTransfer::GetRealFormat(const nsAString& aInFormat,
                             nsAString& aOutFormat) const
 {
   // treat text/unicode as equivalent to text/plain
--- a/dom/filehandle/ActorsParent.cpp
+++ b/dom/filehandle/ActorsParent.cpp
@@ -2531,16 +2531,17 @@ WriteOp::Init(FileHandle* aFileHandle)
 
       auto blobActor = static_cast<BlobParent*>(blobData.blobParent());
 
       RefPtr<BlobImpl> blobImpl = blobActor->GetBlobImpl();
 
       ErrorResult rv;
       blobImpl->GetInternalStream(getter_AddRefs(inputStream), rv);
       if (NS_WARN_IF(rv.Failed())) {
+        rv.SuppressException();
         return false;
       }
 
       break;
     }
 
     default:
       MOZ_CRASH("Should never get here!");
--- a/dom/filesystem/FileSystemBase.cpp
+++ b/dom/filesystem/FileSystemBase.cpp
@@ -79,22 +79,24 @@ FileSystemBase::GetRealPath(BlobImpl* aF
   AssertIsOnOwningThread();
   MOZ_ASSERT(aFile, "aFile Should not be null.");
   MOZ_ASSERT(aPath);
 
   nsAutoString filePath;
   ErrorResult rv;
   aFile->GetMozFullPathInternal(filePath, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   rv = NS_NewNativeLocalFile(NS_ConvertUTF16toUTF8(filePath),
                              true, aPath);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return false;
   }
 
   return true;
 }
 
 bool
 FileSystemBase::IsSafeFile(nsIFile* aFile) const
--- a/dom/filesystem/FileSystemRequestParent.cpp
+++ b/dom/filesystem/FileSystemRequestParent.cpp
@@ -30,16 +30,17 @@ FileSystemRequestParent::~FileSystemRequ
 
 #define FILESYSTEM_REQUEST_PARENT_DISPATCH_ENTRY(name)                         \
     case FileSystemParams::TFileSystem##name##Params: {                        \
       const FileSystem##name##Params& p = aParams;                             \
       mFileSystem = FileSystemBase::DeserializeDOMPath(p.filesystem());        \
       MOZ_ASSERT(mFileSystem);                                                 \
       mTask = name##TaskParent::Create(mFileSystem, p, this, rv);              \
       if (NS_WARN_IF(rv.Failed())) {                                           \
+        rv.SuppressException();                                                \
         return false;                                                          \
       }                                                                        \
       break;                                                                   \
     }
 
 bool
 FileSystemRequestParent::Initialize(const FileSystemParams& aParams)
 {
--- a/dom/filesystem/FileSystemTaskBase.cpp
+++ b/dom/filesystem/FileSystemTaskBase.cpp
@@ -145,16 +145,17 @@ FileSystemTaskChildBase::Start()
   }
 
   nsAutoString serialization;
   mFileSystem->SerializeDOMPath(serialization);
 
   ErrorResult rv;
   FileSystemParams params = GetRequestParams(serialization, rv);
   if (NS_WARN_IF(rv.Failed())) {
+    rv.SuppressException();
     return;
   }
 
   // Retain a reference so the task object isn't deleted without IPDL's
   // knowledge. The reference will be released by
   // mozilla::ipc::BackgroundChildImpl::DeallocPFileSystemRequestChild.
   NS_ADDREF_THIS();
 
--- a/dom/filesystem/GetDirectoryListingTask.cpp
+++ b/dom/filesystem/GetDirectoryListingTask.cpp
@@ -360,20 +360,19 @@ GetDirectoryListingTaskParent::IOWork()
     nsCOMPtr<nsISupports> supp;
     if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
       break;
     }
 
     nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
     MOZ_ASSERT(currFile);
 
-    bool isLink, isSpecial, isFile;
-    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
-                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
+    bool isSpecial, isFile;
+    if (NS_WARN_IF(NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
+        isSpecial) {
       continue;
     }
     if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
                    NS_FAILED(currFile->IsDirectory(&isDir))) ||
         !(isFile || isDir)) {
       continue;
     }
 
--- a/dom/filesystem/GetFilesHelper.cpp
+++ b/dom/filesystem/GetFilesHelper.cpp
@@ -67,18 +67,18 @@ GetFilesHelper::Create(nsIGlobalObject* 
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return helper.forget();
 }
 
 GetFilesHelper::GetFilesHelper(nsIGlobalObject* aGlobal, bool aRecursiveFlag)
-  : mGlobal(aGlobal)
-  , mRecursiveFlag(aRecursiveFlag)
+  : GetFilesHelperBase(aRecursiveFlag)
+  , mGlobal(aGlobal)
   , mListingCompleted(false)
   , mErrorResult(NS_OK)
   , mMutex("GetFilesHelper::mMutex")
   , mCanceled(false)
 {
 }
 
 void
@@ -254,28 +254,33 @@ GetFilesHelper::RunMainThread()
       mErrorResult = NS_ERROR_OUT_OF_MEMORY;
       mFiles.Clear();
       return;
     }
   }
 }
 
 nsresult
-GetFilesHelper::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
+GetFilesHelperBase::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile)
 {
   MOZ_ASSERT(!NS_IsMainThread());
   MOZ_ASSERT(aFile);
 
   // We check if this operation has to be terminated at each recursion.
   if (IsCanceled()) {
     return NS_OK;
   }
 
+  nsresult rv = AddExploredDirectory(aFile);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   nsCOMPtr<nsISimpleEnumerator> entries;
-  nsresult rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
+  rv = aFile->GetDirectoryEntries(getter_AddRefs(entries));
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   for (;;) {
     bool hasMore = false;
     if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
       break;
@@ -287,26 +292,31 @@ GetFilesHelper::ExploreDirectory(const n
     }
 
     nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
     MOZ_ASSERT(currFile);
 
     bool isLink, isSpecial, isFile, isDir;
     if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
                    NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
+        isSpecial) {
       continue;
     }
 
     if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
                    NS_FAILED(currFile->IsDirectory(&isDir))) ||
         !(isFile || isDir)) {
       continue;
     }
 
+    // We don't want to explore loops of links.
+    if (isDir && isLink && !ShouldFollowSymLink(currFile)) {
+      continue;
+    }
+
     // The new domPath
     nsAutoString domPath;
     domPath.Assign(aDOMPath);
     if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
       domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
     }
 
     nsAutoString leafName;
@@ -339,16 +349,79 @@ GetFilesHelper::ExploreDirectory(const n
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+GetFilesHelperBase::AddExploredDirectory(nsIFile* aDir)
+{
+  nsresult rv;
+
+#ifdef DEBUG
+  bool isDir;
+  rv = aDir->IsDirectory(&isDir);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  MOZ_ASSERT(isDir, "Why are we here?");
+#endif
+
+  bool isLink;
+  rv = aDir->IsSymlink(&isLink);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  nsAutoCString path;
+
+  if (!isLink) {
+    nsAutoString path16;
+    rv = aDir->GetPath(path16);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    path = NS_ConvertUTF16toUTF8(path16);
+  } else {
+    rv = aDir->GetNativeTarget(path);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+  }
+
+  mExploredDirectories.PutEntry(path);
+  return NS_OK;
+}
+
+bool
+GetFilesHelperBase::ShouldFollowSymLink(nsIFile* aDir)
+{
+#ifdef DEBUG
+  bool isLink, isDir;
+  if (NS_WARN_IF(NS_FAILED(aDir->IsSymlink(&isLink)) ||
+                 NS_FAILED(aDir->IsDirectory(&isDir)))) {
+    return false;
+  }
+
+  MOZ_ASSERT(isLink && isDir, "Why are we here?");
+#endif
+
+  nsAutoCString targetPath;
+  if (NS_WARN_IF(NS_FAILED(aDir->GetNativeTarget(targetPath)))) {
+    return false;
+  }
+
+  return !mExploredDirectories.Contains(targetPath);
+}
+
 void
 GetFilesHelper::ResolveOrRejectPromise(Promise* aPromise)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mListingCompleted);
   MOZ_ASSERT(aPromise);
 
   // Error propagation.
--- a/dom/filesystem/GetFilesHelper.h
+++ b/dom/filesystem/GetFilesHelper.h
@@ -6,16 +6,17 @@
 
 #ifndef mozilla_dom_GetFilesHelper_h
 #define mozilla_dom_GetFilesHelper_h
 
 #include "mozilla/Mutex.h"
 #include "mozilla/RefPtr.h"
 #include "nsCycleCollectionTraversalCallback.h"
 #include "nsTArray.h"
+#include "nsTHashtable.h"
 
 class nsIGlobalObject;
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 class ContentParent;
@@ -31,19 +32,56 @@ public:
 
   virtual void
   Callback(nsresult aStatus, const Sequence<RefPtr<File>>& aFiles) = 0;
 
 protected:
   virtual ~GetFilesCallback() {}
 };
 
+class GetFilesHelperBase
+{
+protected:
+  explicit GetFilesHelperBase(bool aRecursiveFlag)
+    : mRecursiveFlag(aRecursiveFlag)
+  {}
+
+  virtual ~GetFilesHelperBase() {}
+
+  virtual bool
+  IsCanceled()
+  {
+    return false;
+  }
+
+  nsresult
+  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
+
+  nsresult
+  AddExploredDirectory(nsIFile* aDirectory);
+
+  bool
+  ShouldFollowSymLink(nsIFile* aDirectory);
+
+  bool mRecursiveFlag;
+
+  // We populate this array in the I/O thread with the paths of the Files that
+  // we want to send as result to the promise objects.
+  struct FileData {
+    nsString mDomPath;
+    nsString mRealPath;
+  };
+  FallibleTArray<FileData> mTargetPathArray;
+  nsTHashtable<nsCStringHashKey> mExploredDirectories;
+};
+
 // Retrieving the list of files can be very time/IO consuming. We use this
 // helper class to do it just once.
 class GetFilesHelper : public Runnable
+                     , public GetFilesHelperBase
 {
   friend class GetFilesHelperParent;
 
 public:
   static already_AddRefed<GetFilesHelper>
   Create(nsIGlobalObject* aGlobal,
          const nsTArray<OwningFileOrDirectory>& aFilesOrDirectory,
          bool aRecursiveFlag, ErrorResult& aRv);
@@ -64,18 +102,18 @@ protected:
   virtual ~GetFilesHelper() {}
 
   void
   SetDirectoryPath(const nsAString& aDirectoryPath)
   {
     mDirectoryPath = aDirectoryPath;
   }
 
-  bool
-  IsCanceled()
+  virtual bool
+  IsCanceled() override
   {
     MutexAutoLock lock(mMutex);
     return mCanceled;
   }
 
   virtual void
   Work(ErrorResult& aRv);
 
@@ -89,38 +127,27 @@ protected:
   RunIO();
 
   void
   RunMainThread();
 
   void
   OperationCompleted();
 
-  nsresult
-  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aFile);
   void
   ResolveOrRejectPromise(Promise* aPromise);
 
   void
   RunCallback(GetFilesCallback* aCallback);
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
 
-  bool mRecursiveFlag;
   bool mListingCompleted;
   nsString mDirectoryPath;
 
-  // We populate this array in the I/O thread with the paths of the Files that
-  // we want to send as result to the promise objects.
-  struct FileData {
-    nsString mDomPath;
-    nsString mRealPath;
-  };
-  FallibleTArray<FileData> mTargetPathArray;
-
   // This is the real File sequence that we expose via Promises.
   Sequence<RefPtr<File>> mFiles;
 
   // Error code to propagate.
   nsresult mErrorResult;
 
   nsTArray<RefPtr<Promise>> mPromises;
   nsTArray<RefPtr<GetFilesCallback>> mCallbacks;
--- a/dom/filesystem/GetFilesTask.cpp
+++ b/dom/filesystem/GetFilesTask.cpp
@@ -218,42 +218,42 @@ GetFilesTaskParent::Create(FileSystemBas
 
   return task.forget();
 }
 
 GetFilesTaskParent::GetFilesTaskParent(FileSystemBase* aFileSystem,
                                        const FileSystemGetFilesParams& aParam,
                                        FileSystemRequestParent* aParent)
   : FileSystemTaskParentBase(aFileSystem, aParam, aParent)
+  , GetFilesHelperBase(aParam.recursiveFlag())
   , mDirectoryDOMPath(aParam.domPath())
-  , mRecursiveFlag(aParam.recursiveFlag())
 {
   MOZ_ASSERT(XRE_IsParentProcess(), "Only call from parent process!");
   AssertIsOnBackgroundThread();
   MOZ_ASSERT(aFileSystem);
 }
 
 FileSystemResponseValue
 GetFilesTaskParent::GetSuccessRequestResult(ErrorResult& aRv) const
 {
   AssertIsOnBackgroundThread();
 
   InfallibleTArray<PBlobParent*> blobs;
 
   FallibleTArray<FileSystemFileResponse> inputs;
-  if (!inputs.SetLength(mTargetData.Length(), mozilla::fallible_t())) {
+  if (!inputs.SetLength(mTargetPathArray.Length(), mozilla::fallible_t())) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     FileSystemFilesResponse response;
     return response;
   }
 
-  for (unsigned i = 0; i < mTargetData.Length(); i++) {
+  for (unsigned i = 0; i < mTargetPathArray.Length(); i++) {
     FileSystemFileResponse fileData;
-    fileData.realPath() = mTargetData[i].mRealPath;
-    fileData.domPath() = mTargetData[i].mDOMPath;
+    fileData.realPath() = mTargetPathArray[i].mRealPath;
+    fileData.domPath() = mTargetPathArray[i].mDomPath;
     inputs[i] = fileData;
   }
 
   FileSystemFilesResponse response;
   response.data().SwapElements(inputs);
   return response;
 }
 
@@ -273,117 +273,32 @@ GetFilesTaskParent::IOWork()
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!exists) {
     return NS_OK;
   }
 
-  // Get isDirectory.
-  rv = ExploreDirectory(mDirectoryDOMPath, mTargetPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-
-  return NS_OK;
-}
-
-nsresult
-GetFilesTaskParent::ExploreDirectory(const nsAString& aDOMPath, nsIFile* aPath)
-{
-  MOZ_ASSERT(XRE_IsParentProcess(),
-             "Only call from parent process!");
-  MOZ_ASSERT(!NS_IsMainThread(), "Only call on worker thread!");
-  MOZ_ASSERT(aPath);
-
   bool isDir;
-  nsresult rv = aPath->IsDirectory(&isDir);
+  rv = mTargetPath->IsDirectory(&isDir);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!isDir) {
     return NS_ERROR_DOM_FILESYSTEM_TYPE_MISMATCH_ERR;
   }
 
-  nsCOMPtr<nsISimpleEnumerator> entries;
-  rv = aPath->GetDirectoryEntries(getter_AddRefs(entries));
+  // Get isDirectory.
+  rv = ExploreDirectory(mDirectoryDOMPath, mTargetPath);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
-  for (;;) {
-    bool hasMore = false;
-    if (NS_WARN_IF(NS_FAILED(entries->HasMoreElements(&hasMore))) || !hasMore) {
-      break;
-    }
-    nsCOMPtr<nsISupports> supp;
-    if (NS_WARN_IF(NS_FAILED(entries->GetNext(getter_AddRefs(supp))))) {
-      break;
-    }
-
-    nsCOMPtr<nsIFile> currFile = do_QueryInterface(supp);
-    MOZ_ASSERT(currFile);
-
-    bool isLink, isSpecial, isFile;
-    if (NS_WARN_IF(NS_FAILED(currFile->IsSymlink(&isLink)) ||
-                   NS_FAILED(currFile->IsSpecial(&isSpecial))) ||
-        isLink || isSpecial) {
-      continue;
-    }
-
-    if (NS_WARN_IF(NS_FAILED(currFile->IsFile(&isFile)) ||
-                   NS_FAILED(currFile->IsDirectory(&isDir))) ||
-        !(isFile || isDir)) {
-      continue;
-    }
-
-    nsAutoString domPath;
-    domPath.Assign(aDOMPath);
-
-    // This is specific for unix root filesystem.
-    if (!aDOMPath.EqualsLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL)) {
-      domPath.AppendLiteral(FILESYSTEM_DOM_PATH_SEPARATOR_LITERAL);
-    }
-
-    nsAutoString leafName;
-    if (NS_WARN_IF(NS_FAILED(currFile->GetLeafName(leafName)))) {
-      continue;
-    }
-    domPath.Append(leafName);
-
-    if (isFile) {
-      FileData data;
-      data.mDOMPath.Append(domPath);
-
-      if (NS_WARN_IF(NS_FAILED(currFile->GetPath(data.mRealPath)))) {
-        continue;
-      }
-
-      if (!mTargetData.AppendElement(data, fallible)) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
-
-      continue;
-    }
-
-    MOZ_ASSERT(isDir);
-
-    if (!mRecursiveFlag) {
-      continue;
-    }
-
-    // Recursive.
-    rv = ExploreDirectory(domPath, currFile);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return rv;
-    }
-  }
-
   return NS_OK;
 }
 
 void
 GetFilesTaskParent::GetPermissionAccessType(nsCString& aAccess) const
 {
   aAccess.AssignLiteral(DIRECTORY_READ_PERMISSION);
 }
--- a/dom/filesystem/GetFilesTask.h
+++ b/dom/filesystem/GetFilesTask.h
@@ -4,16 +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/. */
 
 #ifndef mozilla_dom_GetFilesTask_h
 #define mozilla_dom_GetFilesTask_h
 
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/FileSystemTaskBase.h"
+#include "mozilla/dom/GetFilesHelper.h"
 #include "mozilla/ErrorResult.h"
 
 namespace mozilla {
 namespace dom {
 
 class BlobImpl;
 
 class GetFilesTaskChild final : public FileSystemTaskChildBase
@@ -63,16 +64,17 @@ private:
     nsString mRealPath;
     nsString mDOMPath;
   };
 
   FallibleTArray<FileData> mTargetData;
 };
 
 class GetFilesTaskParent final : public FileSystemTaskParentBase
+                               , public GetFilesHelperBase
 {
 public:
   static already_AddRefed<GetFilesTaskParent>
   Create(FileSystemBase* aFileSystem,
          const FileSystemGetFilesParams& aParam,
          FileSystemRequestParent* aParent,
          ErrorResult& aRv);
 
@@ -85,28 +87,16 @@ private:
                      FileSystemRequestParent* aParent);
 
   virtual FileSystemResponseValue
   GetSuccessRequestResult(ErrorResult& aRv) const override;
 
   virtual nsresult
   IOWork() override;
 
-  nsresult
-  ExploreDirectory(const nsAString& aDOMPath, nsIFile* aPath);
-
   nsString mDirectoryDOMPath;
   nsCOMPtr<nsIFile> mTargetPath;
-  bool mRecursiveFlag;
-
-  // We store the fullpath and the dom path of Files.
-  struct FileData {
-    nsString mRealPath;
-    nsString mDOMPath;
-  };
-
-  FallibleTArray<FileData> mTargetData;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_GetFilesTask_h
--- a/dom/filesystem/tests/test_webkitdirectory.html
+++ b/dom/filesystem/tests/test_webkitdirectory.html
@@ -3,18 +3,18 @@
 <head>
   <title>Test for webkitdirectory and webkitRelativePath</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 
 <body>
 <input id="inputFileWebkitDirectory" type="file" webkitdirectory></input>
-<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory directory></input>
-<input id="inputFileDirectory" type="file" directory></input>
+<input id="inputFileWebkitDirectoryAndDirectory" type="file" webkitdirectory allowdirs></input>
+<input id="inputFileDirectory" type="file" allowdirs></input>
 <input id="inputFileDirectoryChange" type="file" webkitdirectory></input>
 
 <script type="application/javascript;version=1.7">
 
 function populateInputFile(aInputFile) {
   var url = SimpleTest.getTestFileURL("script_fileList.js");
   var script = SpecialPowers.loadChromeScript(url);
 
--- a/dom/flyweb/HttpServer.cpp
+++ b/dom/flyweb/HttpServer.cpp
@@ -49,18 +49,17 @@ HttpServer::Init(int32_t aPort, bool aHt
 {
   mPort = aPort;
   mHttps = aHttps;
   mListener = aListener;
 
   if (mHttps) {
     nsCOMPtr<nsILocalCertService> lcs =
       do_CreateInstance("@mozilla.org/security/local-cert-service;1");
-    nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this,
-                                       nsILocalCertService::KEY_TYPE_EC);
+    nsresult rv = lcs->GetOrCreateCert(NS_LITERAL_CSTRING("flyweb"), this);
     if (NS_FAILED(rv)) {
       NotifyStarted(rv);
     }
   } else {
     // Make sure to always have an async step before notifying callbacks
     HandleCert(nullptr, NS_OK);
   }
 }
--- a/dom/geolocation/moz.build
+++ b/dom/geolocation/moz.build
@@ -37,8 +37,14 @@ elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'go
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     LOCAL_INCLUDES += [
         '/dom/system/mac',
     ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     LOCAL_INCLUDES += [
         '/dom/system/windows',
     ]
+elif CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk2', 'gtk3'):
+    if CONFIG['MOZ_GPSD']:
+        LOCAL_INCLUDES += [
+            '/dom/system/linux',
+        ]
+        DEFINES['MOZ_GPSD'] = True
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -40,16 +40,20 @@ class nsIPrincipal;
 #ifdef MOZ_WIDGET_ANDROID
 #include "AndroidLocationProvider.h"
 #endif
 
 #ifdef MOZ_WIDGET_GONK