merge fx-team to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 30 Jun 2016 11:54:32 +0200
changeset 343179 d700dc054751333e0735f975fce3d3adf153c62a
parent 343154 0edb9df3c97ff7d205a99f8af607f99a858f140b (current diff)
parent 343178 32bb090d7e625a763a80728da2c276724a2c6761 (diff)
child 343180 4a860475d96a0f7705106498e7380debc7fb2ab9
push id6389
push userraliiev@mozilla.com
push dateMon, 19 Sep 2016 13:38:22 +0000
treeherdermozilla-beta@01d67bfe6c81 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone50.0a1
first release with
nightly linux32
d700dc054751 / 50.0a1 / 20160630030207 / files
nightly linux64
d700dc054751 / 50.0a1 / 20160630030207 / files
nightly mac
d700dc054751 / 50.0a1 / 20160630030201 / files
nightly win32
d700dc054751 / 50.0a1 / 20160630030207 / files
nightly win64
d700dc054751 / 50.0a1 / 20160630030207 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge fx-team to mozilla-central a=merge
devtools/client/responsive.html/audio/camera-click.mp3
devtools/client/responsive.html/audio/moz.build
devtools/server/tests/mochitest/test_css-logic-inheritance.html
toolkit/components/telemetry/Histograms.json
--- a/.eslintignore
+++ b/.eslintignore
@@ -104,33 +104,33 @@ devtools/client/sourceeditor/**
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/**
 !devtools/client/webconsole/panel.js
 !devtools/client/webconsole/jsterm.js
 !devtools/client/webconsole/console-commands.js
 devtools/client/webide/**
 !devtools/client/webide/components/webideCli.js
 devtools/server/**
+!devtools/server/css-logic.js
 !devtools/server/actors/webbrowser.js
 !devtools/server/actors/styles.js
 !devtools/server/actors/string.js
 devtools/shared/*.js
 !devtools/shared/css-lexer.js
 !devtools/shared/defer.js
 !devtools/shared/event-emitter.js
 !devtools/shared/task.js
 devtools/shared/*.jsm
 !devtools/shared/Loader.jsm
 devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 !devtools/shared/gcli/templater.js
 devtools/shared/heapsnapshot/**
-devtools/shared/inspector/**
 devtools/shared/layout/**
 devtools/shared/locales/**
 devtools/shared/performance/**
 devtools/shared/qrcode/**
 devtools/shared/security/**
 devtools/shared/shims/**
 devtools/shared/tests/**
 !devtools/shared/tests/unit/test_csslexer.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -287,16 +287,20 @@ pref("browser.urlbar.match.url", "@");
 
 // The default behavior for the urlbar can be configured to use any combination
 // of the match filters with each additional filter adding more results (union).
 pref("browser.urlbar.suggest.history",              true);
 pref("browser.urlbar.suggest.bookmark",             true);
 pref("browser.urlbar.suggest.openpage",             true);
 pref("browser.urlbar.suggest.searches",             false);
 pref("browser.urlbar.userMadeSearchSuggestionsChoice", false);
+// 4 here means the suggestion notification will be automatically
+// hidden the 4th day, so it will actually be shown on 3 different days.
+pref("browser.urlbar.daysBeforeHidingSuggestionsPrompt", 4);
+pref("browser.urlbar.lastSuggestionsPromptDate", 20160601);
 
 // Limit the number of characters sent to the current search engine to fetch
 // suggestions.
 pref("browser.urlbar.maxCharsForSearchSuggestions", 20);
 
 // Restrictions to current suggestions can also be applied (intersection).
 // Typed suggestion works only if history is set to true.
 pref("browser.urlbar.suggest.history.onlyTyped",    false);
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -956,17 +956,18 @@ file, You can obtain one at http://mozil
 
       <property name="shouldShowSearchSuggestionsNotification" readonly="true">
         <getter><![CDATA[
           return !this._userMadeSearchSuggestionsChoice &&
                  !this.inPrivateContext &&
                  // When _urlbarFocused is true, tabbrowser would close the
                  // popup if it's opened here, so don't show the notification.
                  !gBrowser.selectedBrowser._urlbarFocused &&
-                 Services.prefs.getBoolPref("browser.search.suggest.enabled");
+                 Services.prefs.getBoolPref("browser.search.suggest.enabled") &&
+                 this._prefs.getIntPref("daysBeforeHidingSuggestionsPrompt");
         ]]></getter>
       </property>
 
     </implementation>
 
     <handlers>
       <handler event="keydown"><![CDATA[
         if ((event.keyCode === KeyEvent.DOM_VK_ALT ||
@@ -1295,18 +1296,36 @@ file, You can obtain one at http://mozil
         <parameter name="aInput"/>
         <parameter name="aElement"/>
         <body>
           <![CDATA[
           // initially the panel is hidden
           // to avoid impacting startup / new window performance
           aInput.popup.hidden = false;
 
-          if (aInput.shouldShowSearchSuggestionsNotification) {
+          let showNotification = aInput.shouldShowSearchSuggestionsNotification;
+          if (showNotification) {
+            let prefs = aInput._prefs;
+            let date = parseInt((new Date()).toLocaleFormat("%Y%m%d"));
+            let previousDate = prefs.getIntPref("lastSuggestionsPromptDate");
+            if (previousDate < date) {
+              let remainingDays =
+                prefs.getIntPref("daysBeforeHidingSuggestionsPrompt") - 1;
+              prefs.setIntPref("daysBeforeHidingSuggestionsPrompt",
+                               remainingDays);
+              prefs.setIntPref("lastSuggestionsPromptDate", date);
+              if (!remainingDays)
+                showNotification = false;
+            }
+          }
+
+          if (showNotification) {
             this._showSearchSuggestionsNotification();
+          } else if (this.classList.contains("showSearchSuggestionsNotification")) {
+            this._hideSearchSuggestionsNotification();
           }
 
           this._openAutocompletePopup(aInput, aElement);
           ]]>
         </body>
       </method>
 
       <method name="_openAutocompletePopup">
@@ -1379,18 +1398,16 @@ file, You can obtain one at http://mozil
         <body>
           <![CDATA[
           this.footer.collapsed = this._matchCount == 0;
           ]]>
         </body>
       </method>
 
       <method name="_showSearchSuggestionsNotification">
-        <parameter name="aInput"/>
-        <parameter name="aElement"/>
         <body>
           <![CDATA[
           // With the notification shown, the listbox's height can sometimes be
           // too small when it's flexed, as it normally is.  Also, it can start
           // out slightly scrolled down.  Both problems appear together, most
           // often when the popup is very narrow and the notification's text
           // must wrap.  Work around them by removing the flex.
           //
--- a/browser/components/extensions/.eslintrc
+++ b/browser/components/extensions/.eslintrc
@@ -1,18 +1,18 @@
 {
   "extends": "../../../toolkit/components/extensions/.eslintrc",
 
   "globals": {
     "AllWindowEvents": true,
     "currentWindow": true,
     "EventEmitter": true,
-    "IconDetails": true,
     "makeWidgetId": true,
     "pageActionFor": true,
+    "IconDetails": true,
     "PanelPopup": true,
     "TabContext": true,
     "ViewPopup": true,
     "WindowEventManager": true,
     "WindowListManager": true,
     "WindowManager": true,
   },
 }
--- a/browser/components/extensions/ext-browserAction.js
+++ b/browser/components/extensions/ext-browserAction.js
@@ -5,16 +5,17 @@
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 
 Cu.import("resource://devtools/shared/event-emitter.js");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
+  IconDetails,
 } = ExtensionUtils;
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 // WeakMap[Extension -> BrowserAction]
 var browserActionMap = new WeakMap();
 
 // Responsible for the browser_action section of the manifest as well
--- a/browser/components/extensions/ext-pageAction.js
+++ b/browser/components/extensions/ext-pageAction.js
@@ -1,16 +1,17 @@
 /* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set sts=2 sw=2 et tw=80: */
 "use strict";
 
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 var {
   EventManager,
+  IconDetails,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 // Handles URL bar icons, including the |page_action| manifest entry
 // and associated API.
 function PageAction(options, extension) {
@@ -218,21 +219,23 @@ extensions.registerSchemaAPI("pageAction
         return () => {
           pageAction.off("click", listener);
         };
       }).api(),
 
       show(tabId) {
         let tab = TabManager.getTab(tabId);
         PageAction.for(extension).setProperty(tab, "show", true);
+        return Promise.resolve();
       },
 
       hide(tabId) {
         let tab = TabManager.getTab(tabId);
         PageAction.for(extension).setProperty(tab, "show", false);
+        return Promise.resolve();
       },
 
       setTitle(details) {
         let tab = TabManager.getTab(details.tabId);
 
         // Clear the tab-specific title when given a null string.
         PageAction.for(extension).setProperty(tab, "title", details.title || null);
       },
--- a/browser/components/extensions/ext-utils.js
+++ b/browser/components/extensions/ext-utils.js
@@ -3,148 +3,31 @@
 "use strict";
 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
                                   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                                   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
-Cu.import("resource://gre/modules/AddonManager.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
-const INTEGER = /^[1-9]\d*$/;
+
 // Minimum time between two resizes.
 const RESIZE_TIMEOUT = 100;
 
 var {
   EventManager,
-  instanceOf,
 } = ExtensionUtils;
 
 // This file provides some useful code for the |tabs| and |windows|
 // modules. All of the code is installed on |global|, which is a scope
 // shared among the different ext-*.js scripts.
 
-
-// Manages icon details for toolbar buttons in the |pageAction| and
-// |browserAction| APIs.
-global.IconDetails = {
-  // Normalizes the various acceptable input formats into an object
-  // with icon size as key and icon URL as value.
-  //
-  // If a context is specified (function is called from an extension):
-  // Throws an error if an invalid icon size was provided or the
-  // extension is not allowed to load the specified resources.
-  //
-  // If no context is specified, instead of throwing an error, this
-  // function simply logs a warning message.
-  normalize(details, extension, context = null) {
-    let result = {};
-
-    try {
-      if (details.imageData) {
-        let imageData = details.imageData;
-
-        // The global might actually be from Schema.jsm, which
-        // normalizes most of our arguments. In that case it won't have
-        // an ImageData property. But Schema.jsm doesn't normalize
-        // actual ImageData objects, so they will come from a global
-        // with the right property.
-        if (instanceOf(imageData, "ImageData")) {
-          imageData = {"19": imageData};
-        }
-
-        for (let size of Object.keys(imageData)) {
-          if (!INTEGER.test(size)) {
-            throw new Error(`Invalid icon size ${size}, must be an integer`);
-          }
-          result[size] = this.convertImageDataToPNG(imageData[size], context);
-        }
-      }
-
-      if (details.path) {
-        let path = details.path;
-        if (typeof path != "object") {
-          path = {"19": path};
-        }
-
-        let baseURI = context ? context.uri : extension.baseURI;
-
-        for (let size of Object.keys(path)) {
-          if (!INTEGER.test(size)) {
-            throw new Error(`Invalid icon size ${size}, must be an integer`);
-          }
-
-          let url = baseURI.resolve(path[size]);
-
-          // The Chrome documentation specifies these parameters as
-          // relative paths. We currently accept absolute URLs as well,
-          // which means we need to check that the extension is allowed
-          // to load them. This will throw an error if it's not allowed.
-          Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
-            extension.principal, url,
-            Services.scriptSecurityManager.DISALLOW_SCRIPT);
-
-          result[size] = url;
-        }
-      }
-    } catch (e) {
-      // Function is called from extension code, delegate error.
-      if (context) {
-        throw e;
-      }
-      // If there's no context, it's because we're handling this
-      // as a manifest directive. Log a warning rather than
-      // raising an error.
-      extension.manifestError(`Invalid icon data: ${e}`);
-    }
-
-    return result;
-  },
-
-  // Returns the appropriate icon URL for the given icons object and the
-  // screen resolution of the given window.
-  getURL(icons, window, extension, size = 16) {
-    const DEFAULT = "chrome://browser/content/extension.svg";
-
-    size *= window.devicePixelRatio;
-
-    let bestSize = null;
-    if (icons[size]) {
-      bestSize = size;
-    } else if (icons[2 * size]) {
-      bestSize = 2 * size;
-    } else {
-      let sizes = Object.keys(icons)
-                        .map(key => parseInt(key, 10))
-                        .sort((a, b) => a - b);
-
-      bestSize = sizes.find(candidate => candidate > size) || sizes.pop();
-    }
-
-    if (bestSize) {
-      return {size: bestSize, icon: icons[bestSize]};
-    }
-
-    return {size, icon: DEFAULT};
-  },
-
-  convertImageDataToPNG(imageData, context) {
-    let document = context.contentWindow.document;
-    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
-    canvas.width = imageData.width;
-    canvas.height = imageData.height;
-    canvas.getContext("2d").putImageData(imageData, 0, 0);
-
-    return canvas.toDataURL("image/png");
-  },
-};
-
 global.makeWidgetId = id => {
   id = id.toLowerCase();
   // FIXME: This allows for collisions.
   return id.replace(/[^a-z0-9_-]/g, "_");
 };
 
 function promisePopupShown(popup) {
   return new Promise(resolve => {
--- a/browser/components/extensions/schemas/page_action.json
+++ b/browser/components/extensions/schemas/page_action.json
@@ -50,24 +50,26 @@
         "additionalProperties": { "type": "any" },
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
       }
     ],
     "functions": [
       {
         "name": "show",
         "type": "function",
+        "async": true,
         "description": "Shows the page action. The page action is shown whenever the tab is selected.",
         "parameters": [
           {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
         ]
       },
       {
         "name": "hide",
         "type": "function",
+        "async": true,
         "description": "Hides the page action.",
         "parameters": [
           {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
         ]
       },
       {
         "name": "setTitle",
         "type": "function",
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_pageAction_icon.js
@@ -196,19 +196,19 @@ add_task(function* testDetailsObjects() 
     }
 
     // Sort by resolution, so we don't needlessly switch back and forth
     // between each test.
     tests.sort(test => test.resolution);
 
     browser.tabs.query({active: true, currentWindow: true}, tabs => {
       tabId = tabs[0].id;
-      browser.pageAction.show(tabId);
-
-      browser.test.sendMessage("ready", tests);
+      browser.pageAction.show(tabId).then(() => {
+        browser.test.sendMessage("ready", tests);
+      });
     });
   }
 
   let extension = ExtensionTestUtils.loadExtension({
     manifest: {
       "browser_action": {},
       "page_action": {},
       "background": {
@@ -332,18 +332,19 @@ add_task(function* testDefaultDetails() 
         "browser_action": {"default_icon": icon},
         "page_action": {"default_icon": icon},
       },
 
       background: function() {
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
           let tabId = tabs[0].id;
 
-          browser.pageAction.show(tabId);
-          browser.test.sendMessage("ready");
+          browser.pageAction.show(tabId).then(() => {
+            browser.test.sendMessage("ready");
+          });
         });
       }
     });
 
     yield Promise.all([extension.startup(), extension.awaitMessage("ready")]);
 
     let browserActionId = makeWidgetId(extension.id) + "-browser-action";
     let pageActionId = makeWidgetId(extension.id) + "-page-action";
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -207,18 +207,19 @@ add_task(function* testTabSwitchContext(
       };
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
         expect => {
           browser.test.log("Show the icon on the first tab, expect default properties.");
-          browser.pageAction.show(tabs[0]);
-          expect(details[0]);
+          browser.pageAction.show(tabs[0]).then(() => {
+            expect(details[0]);
+          });
         },
         expect => {
           browser.test.log("Change the icon. Expect default properties excluding the icon.");
           browser.pageAction.setIcon({tabId: tabs[0], path: "1.png"});
           expect(details[1]);
         },
         expect => {
           browser.test.log("Create a new tab. No icon visible.");
@@ -229,22 +230,23 @@ add_task(function* testTabSwitchContext(
         },
         expect => {
           browser.test.log("Await tab load. No icon visible.");
           expect(null);
         },
         expect => {
           browser.test.log("Change properties. Expect new properties.");
           let tabId = tabs[1];
-          browser.pageAction.show(tabId);
-          browser.pageAction.setIcon({tabId, path: "2.png"});
-          browser.pageAction.setPopup({tabId, popup: "2.html"});
-          browser.pageAction.setTitle({tabId, title: "Title 2"});
+          browser.pageAction.show(tabId).then(() => {
+            browser.pageAction.setIcon({tabId, path: "2.png"});
+            browser.pageAction.setPopup({tabId, popup: "2.html"});
+            browser.pageAction.setTitle({tabId, title: "Title 2"});
 
-          expect(details[2]);
+            expect(details[2]);
+          });
         },
         expect => {
           browser.test.log("Clear the title. Expect default title.");
           browser.pageAction.setTitle({tabId: tabs[1], title: ""});
 
           expect(details[3]);
         },
         expect => {
@@ -255,42 +257,45 @@ add_task(function* testTabSwitchContext(
           promiseTabLoad({id: tabs[1], url: "about:blank?1"}).then(() => {
             expect(null);
           });
 
           browser.tabs.update(tabs[1], {url: "about:blank?1"});
         },
         expect => {
           browser.test.log("Show the icon. Expect default properties again.");
-          browser.pageAction.show(tabs[1]);
-          expect(details[0]);
+          browser.pageAction.show(tabs[1]).then(() => {
+            expect(details[0]);
+          });
         },
         expect => {
           browser.test.log("Switch back to the first tab. Expect previously set properties.");
           browser.tabs.update(tabs[0], {active: true}, () => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Hide the icon on tab 2. Switch back, expect hidden.");
-          browser.pageAction.hide(tabs[1]);
-          browser.tabs.update(tabs[1], {active: true}, () => {
-            expect(null);
+          browser.pageAction.hide(tabs[1]).then(() => {
+            browser.tabs.update(tabs[1], {active: true}, () => {
+              expect(null);
+            });
           });
         },
         expect => {
           browser.test.log("Switch back to tab 1. Expect previous results again.");
           browser.tabs.remove(tabs[1], () => {
             expect(details[1]);
           });
         },
         expect => {
           browser.test.log("Hide the icon. Expect hidden.");
-          browser.pageAction.hide(tabs[0]);
-          expect(null);
+          browser.pageAction.hide(tabs[0]).then(() => {
+            expect(null);
+          });
         },
       ];
     },
   });
 });
 
 add_task(function* testDefaultTitle() {
   yield runTests({
@@ -316,18 +321,19 @@ add_task(function* testDefaultTitle() {
 
       return [
         expect => {
           browser.test.log("Initial state. No icon visible.");
           expect(null);
         },
         expect => {
           browser.test.log("Show the icon on the first tab, expect extension title as default title.");
-          browser.pageAction.show(tabs[0]);
-          expect(details[0]);
+          browser.pageAction.show(tabs[0]).then(() => {
+            expect(details[0]);
+          });
         },
         expect => {
           browser.test.log("Change the title. Expect new title.");
           browser.pageAction.setTitle({tabId: tabs[0], title: "Foo Title"});
           expect(details[1]);
         },
         expect => {
           browser.test.log("Clear the title. Expect extension title.");
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup.js
@@ -123,18 +123,19 @@ add_task(function* testPageActionPopup()
           } else {
             browser.test.notifyPass("pageaction-tests-done");
           }
         });
 
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
           tabId = tabs[0].id;
 
-          browser.pageAction.show(tabId);
-          browser.test.sendMessage("next-test");
+          browser.pageAction.show(tabId).then(() => {
+            browser.test.sendMessage("next-test");
+          });
         });
       },
     },
   });
 
   let pageActionId = makeWidgetId(extension.id) + "-page-action";
   let panelId = makeWidgetId(extension.id) + "-panel";
 
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_popup_resize.js
@@ -9,18 +9,19 @@ add_task(function* testPageActionPopupRe
         "default_popup": "popup.html",
         "browser_style": true,
       },
     },
     background: function() {
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         const tabId = tabs[0].id;
 
-        browser.pageAction.show(tabId);
-        browser.test.sendMessage("action-shown");
+        browser.pageAction.show(tabId).then(() => {
+          browser.test.sendMessage("action-shown");
+        });
       });
     },
 
     files: {
       "popup.html": "<html><head><meta charset=\"utf-8\"></head></html>",
     },
   });
 
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_simple.js
@@ -27,18 +27,19 @@ add_task(function* () {
     background: function() {
       browser.runtime.onMessage.addListener(msg => {
         browser.test.assertEq(msg, "from-popup", "correct message received");
         browser.test.sendMessage("popup");
       });
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         let tabId = tabs[0].id;
 
-        browser.pageAction.show(tabId);
-        browser.test.sendMessage("page-action-shown");
+        browser.pageAction.show(tabId).then(() => {
+          browser.test.sendMessage("page-action-shown");
+        });
       });
     },
   });
 
   SimpleTest.waitForExplicitFinish();
   let waitForConsole = new Promise(resolve => {
     SimpleTest.monitorConsole(resolve, [{
       message: /Reading manifest: Error processing page_action.unrecognized_property: An unexpected property was found/,
--- a/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
+++ b/browser/components/extensions/test/browser/browser_ext_popup_api_injection.js
@@ -24,18 +24,19 @@ add_task(function* testPageActionPopup()
                        <script type="application/javascript" src="popup-b.js"></script></head></html>`,
       "popup-b.js": 'browser.test.sendMessage("from-popup-b");',
     },
 
     background: function() {
       let tabId;
       browser.tabs.query({active: true, currentWindow: true}, tabs => {
         tabId = tabs[0].id;
-        browser.pageAction.show(tabId);
-        browser.test.sendMessage("ready");
+        browser.pageAction.show(tabId).then(() => {
+          browser.test.sendMessage("ready");
+        });
       });
 
       browser.test.onMessage.addListener(() => {
         browser.browserAction.setPopup({popup: "/popup-a.html"});
         browser.pageAction.setPopup({tabId, popup: "popup-b.html"});
 
         browser.test.sendMessage("ok");
       });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -90,18 +90,19 @@ add_task(function* testBadPermissions() 
   yield testHasNoPermission({
     manifest: {
       "permissions": ["http://example.com/", "activeTab"],
       "page_action": {},
     },
     contentSetup() {
       return new Promise(resolve => {
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id);
-          resolve();
+          browser.pageAction.show(tabs[0].id).then(() => {
+            resolve();
+          });
         });
       });
     },
   });
 
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -94,18 +94,19 @@ add_task(function* testGoodPermissions()
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {},
     },
     contentSetup() {
       return new Promise(resolve => {
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id);
-          resolve();
+          browser.pageAction.show(tabs[0].id).then(() => {
+            resolve();
+          });
         });
       });
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a browser action w/popup click");
@@ -122,18 +123,19 @@ add_task(function* testGoodPermissions()
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "page_action": {"default_popup": "_blank.html"},
     },
     contentSetup() {
       return new Promise(resolve => {
         browser.tabs.query({active: true, currentWindow: true}, tabs => {
-          browser.pageAction.show(tabs[0].id);
-          resolve();
+          browser.pageAction.show(tabs[0].id).then(() => {
+            resolve();
+          });
         });
       });
     },
     setup: clickPageAction,
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a context menu click");
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -106,17 +106,17 @@ body.certerror #advancedButton {
 
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
   display: inline-block;
-  -moz-padding-start: 2.3em;
+  padding-inline-start: 2.3em;
   text-indent: -2.3em;
   line-height: 16px
 }
 
 #errorCode:not([href]) {
   color: var(--in-content-page-color);
   cursor: text;
   text-decoration: none;
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -898,17 +898,18 @@ StackFrames.prototype = {
     // Start recording any added variables or properties in any scope and
     // clear existing scopes to create each one dynamically.
     DebuggerView.Variables.empty();
 
     // If watch expressions evaluation results are available, create a scope
     // to contain all the values.
     if (this._syncedWatchExpressions && aDepth == 0) {
       let label = L10N.getStr("watchExpressionsScopeLabel");
-      let scope = DebuggerView.Variables.addScope(label);
+      let scope = DebuggerView.Variables.addScope(label,
+        "variables-view-watch-expressions");
 
       // Customize the scope for holding watch expressions evaluations.
       scope.descriptorTooltip = false;
       scope.contextMenuId = "debuggerWatchExpressionsContextMenu";
       scope.separatorStr = L10N.getStr("watchExpressionsSeparatorLabel2");
       scope.switch = DebuggerView.WatchExpressions.switchExpression;
       scope.delete = DebuggerView.WatchExpressions.deleteExpression;
 
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -6,17 +6,17 @@
 
 /* globals StopIteration */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 
 const ToolDefinitions = require("devtools/client/definitions").Tools;
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const CssLogic = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const Services = require("Services");
 const {OutputParser} = require("devtools/client/shared/output-parser");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
 const {createChild} = require("devtools/client/inspector/shared/utils");
 const {gDevTools} = require("devtools/client/framework/devtools");
--- a/devtools/client/inspector/rules/models/rule.js
+++ b/devtools/client/inspector/rules/models/rule.js
@@ -3,17 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const CssLogic = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const {TextProperty} =
       require("devtools/client/inspector/rules/models/text-property");
 const {promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {parseDeclarations} = require("devtools/shared/css-parsing-utils");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "osString", function () {
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -9,17 +9,17 @@
 
 const {Cc, Ci} = require("chrome");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const {Task} = require("devtools/shared/task");
 const {Tools} = require("devtools/client/definitions");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {l10n} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const {OutputParser} = require("devtools/client/shared/output-parser");
 const {PrefObserver, PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
 const {ElementStyle} = require("devtools/client/inspector/rules/models/element-style");
 const {Rule} = require("devtools/client/inspector/rules/models/rule");
 const {RuleEditor} = require("devtools/client/inspector/rules/views/rule-editor");
 const {createChild, promiseWarn} = require("devtools/client/inspector/shared/utils");
 const {gDevTools} = require("devtools/client/framework/devtools");
@@ -963,17 +963,17 @@ CssRuleView.prototype = {
    */
   _showEmpty: function () {
     if (this.styleDocument.getElementById("noResults") > 0) {
       return;
     }
 
     createChild(this.element, "div", {
       id: "noResults",
-      textContent: CssLogic.l10n("rule.empty")
+      textContent: l10n("rule.empty")
     });
   },
 
   /**
    * Clear the rules.
    */
   _clearRules: function () {
     this.element.innerHTML = "";
@@ -1006,28 +1006,28 @@ CssRuleView.prototype = {
 
   /**
    * Text for header that shows above rules for this element
    */
   get selectedElementLabel() {
     if (this._selectedElementLabel) {
       return this._selectedElementLabel;
     }
-    this._selectedElementLabel = CssLogic.l10n("rule.selectedElement");
+    this._selectedElementLabel = l10n("rule.selectedElement");
     return this._selectedElementLabel;
   },
 
   /**
    * Text for header that shows above rules for pseudo elements
    */
   get pseudoElementLabel() {
     if (this._pseudoElementLabel) {
       return this._pseudoElementLabel;
     }
-    this._pseudoElementLabel = CssLogic.l10n("rule.pseudoElement");
+    this._pseudoElementLabel = l10n("rule.pseudoElement");
     return this._pseudoElementLabel;
   },
 
   get showPseudoElements() {
     if (this._showPseudoElements === undefined) {
       this._showPseudoElements =
         Services.prefs.getBoolPref("devtools.inspector.show_pseudo_elements");
     }
--- a/devtools/client/inspector/rules/test/doc_frame_script.js
+++ b/devtools/client/inspector/rules/test/doc_frame_script.js
@@ -14,17 +14,17 @@
 // let response = yield executeInContent(browser, "Test:msgName", data, true);
 // The response message should have the same name "Test:msgName"
 //
 // Some listeners do not send a response message back.
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var {CssLogic} = require("devtools/shared/inspector/css-logic");
+var {isContentStylesheet} = require("devtools/shared/inspector/css-logic");
 var defer = require("devtools/shared/defer");
 
 /**
  * Get a value for a given property name in a css rule in a stylesheet, given
  * their indexes
  * @param {Object} data Expects a data object with the following properties
  * - {Number} styleSheetIndex
  * - {Number} ruleIndex
@@ -63,17 +63,17 @@ addMessageListener("Test:GetStyleSheetsI
   let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
     .getService(Ci.inIDOMUtils);
   let domRules = domUtils.getCSSStyleRules(target);
 
   for (let i = 0, n = domRules.Count(); i < n; i++) {
     let sheet = domRules.GetElementAt(i).parentStyleSheet;
     sheets.push({
       href: sheet.href,
-      isContentSheet: CssLogic.isContentStylesheet(sheet)
+      isContentSheet: isContentStylesheet(sheet)
     });
   }
 
   sendAsyncMessage("Test:GetStyleSheetsInfoForNode", sheets);
 });
 
 /**
  * Get the property value from the computed style for an element.
--- a/devtools/client/inspector/rules/views/rule-editor.js
+++ b/devtools/client/inspector/rules/views/rule-editor.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 {Ci} = require("chrome");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {l10n} = require("devtools/shared/inspector/css-logic");
 const {ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 const {PREF_ORIG_SOURCES} = require("devtools/client/styleeditor/utils");
 const {Rule} = require("devtools/client/inspector/rules/models/rule");
 const {InplaceEditor, editableField, editableItem} =
       require("devtools/client/shared/inplace-editor");
 const {TextPropertyEditor} =
       require("devtools/client/inspector/rules/views/text-property-editor");
 const {
@@ -154,17 +154,17 @@ RuleEditor.prototype = {
     if (this.rule.domRule.type !== CSSRule.KEYFRAME_RULE &&
         this.rule.domRule.selectors) {
       let selector = this.rule.domRule.selectors.join(", ");
 
       let selectorHighlighter = createChild(header, "span", {
         class: "ruleview-selectorhighlighter" +
                (this.ruleView.highlightedSelector === selector ?
                 " highlighted" : ""),
-        title: CssLogic.l10n("rule.selectorHighlighter.tooltip")
+        title: l10n("rule.selectorHighlighter.tooltip")
       });
       selectorHighlighter.addEventListener("click", () => {
         this.ruleView.toggleSelectorHighlighter(selectorHighlighter, selector);
       });
     }
 
     this.openBrace = createChild(header, "span", {
       class: "ruleview-ruleopen",
--- a/devtools/client/inspector/rules/views/text-property-editor.js
+++ b/devtools/client/inspector/rules/views/text-property-editor.js
@@ -1,16 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Ci} = require("chrome");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {l10n} = require("devtools/shared/inspector/css-logic");
 const {getCssProperties} = require("devtools/shared/fronts/css-properties");
 const {InplaceEditor, editableField} =
       require("devtools/client/shared/inplace-editor");
 const {
   createChild,
   appendText,
   advanceValidate,
   blurOnMultipleProperties,
@@ -171,25 +171,25 @@ TextPropertyEditor.prototype = {
                        value: parsedValue,
                        priority: this.prop.priority };
 
     appendText(this.valueContainer, ";");
 
     this.warning = createChild(this.container, "div", {
       class: "ruleview-warning",
       hidden: "",
-      title: CssLogic.l10n("rule.warning.title"),
+      title: l10n("rule.warning.title"),
     });
 
     // Filter button that filters for the current property name and is
     // displayed when the property is overridden by another rule.
     this.filterProperty = createChild(this.container, "div", {
       class: "ruleview-overridden-rule-filter",
       hidden: "",
-      title: CssLogic.l10n("rule.filterProperty.title"),
+      title: l10n("rule.filterProperty.title"),
     });
 
     this.filterProperty.addEventListener("click", event => {
       this.ruleEditor.ruleView.setFilterStyles("`" + this.prop.name + "`");
       event.stopPropagation();
     }, false);
 
     // Holds the viewers for the computed properties.
@@ -366,17 +366,17 @@ TextPropertyEditor.prototype = {
         // knows about
         this.ruleView.tooltips.colorPicker.addSwatch(span, {
           onShow: this._onStartEditing,
           onPreview: this._onSwatchPreview,
           onCommit: this._onSwatchCommit,
           onRevert: this._onSwatchRevert
         });
         span.on("unit-change", this._onSwatchCommit);
-        let title = CssLogic.l10n("rule.colorSwatch.tooltip");
+        let title = l10n("rule.colorSwatch.tooltip");
         span.setAttribute("title", title);
       }
     }
 
     // Attach the cubic-bezier tooltip to the bezier swatches
     this._bezierSwatchSpans =
       this.valueSpan.querySelectorAll("." + BEZIER_SWATCH_CLASS);
     if (this.ruleEditor.isEditable) {
@@ -384,44 +384,44 @@ TextPropertyEditor.prototype = {
         // Adding this swatch to the list of swatches our colorpicker
         // knows about
         this.ruleView.tooltips.cubicBezier.addSwatch(span, {
           onShow: this._onStartEditing,
           onPreview: this._onSwatchPreview,
           onCommit: this._onSwatchCommit,
           onRevert: this._onSwatchRevert
         });
-        let title = CssLogic.l10n("rule.bezierSwatch.tooltip");
+        let title = l10n("rule.bezierSwatch.tooltip");
         span.setAttribute("title", title);
       }
     }
 
     // Attach the filter editor tooltip to the filter swatch
     let span = this.valueSpan.querySelector("." + FILTER_SWATCH_CLASS);
     if (this.ruleEditor.isEditable) {
       if (span) {
         parserOptions.filterSwatch = true;
 
         this.ruleView.tooltips.filterEditor.addSwatch(span, {
           onShow: this._onStartEditing,
           onPreview: this._onSwatchPreview,
           onCommit: this._onSwatchCommit,
           onRevert: this._onSwatchRevert
         }, outputParser, parserOptions);
-        let title = CssLogic.l10n("rule.filterSwatch.tooltip");
+        let title = l10n("rule.filterSwatch.tooltip");
         span.setAttribute("title", title);
       }
     }
 
     this.angleSwatchSpans =
       this.valueSpan.querySelectorAll("." + ANGLE_SWATCH_CLASS);
     if (this.ruleEditor.isEditable) {
       for (let angleSpan of this.angleSwatchSpans) {
         angleSpan.on("unit-change", this._onSwatchCommit);
-        let title = CssLogic.l10n("rule.angleSwatch.tooltip");
+        let title = l10n("rule.angleSwatch.tooltip");
         angleSpan.setAttribute("title", title);
       }
     }
 
     // Now that we have updated the property's value, we might have a pending
     // click on the value container. If we do, we have to trigger a click event
     // on the right element.
     if (this._hasPendingClick) {
--- a/devtools/client/inspector/shared/test/doc_frame_script.js
+++ b/devtools/client/inspector/shared/test/doc_frame_script.js
@@ -14,17 +14,17 @@
 // let response = yield executeInContent(browser, "Test:MsgName", data, true);
 // The response message should have the same name "Test:MsgName"
 //
 // Some listeners do not send a response message back.
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-var {CssLogic} = require("devtools/shared/inspector/css-logic");
+var {isContentStylesheet} = require("devtools/shared/inspector/css-logic");
 var defer = require("devtools/shared/defer");
 
 /**
  * Get a value for a given property name in a css rule in a stylesheet, given
  * their indexes
  * @param {Object} data Expects a data object with the following properties
  * - {Number} styleSheetIndex
  * - {Number} ruleIndex
@@ -63,17 +63,17 @@ addMessageListener("Test:GetStyleSheetsI
   let domUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
     .getService(Ci.inIDOMUtils);
   let domRules = domUtils.getCSSStyleRules(target);
 
   for (let i = 0, n = domRules.Count(); i < n; i++) {
     let sheet = domRules.GetElementAt(i).parentStyleSheet;
     sheets.push({
       href: sheet.href,
-      isContentSheet: CssLogic.isContentStylesheet(sheet)
+      isContentSheet: isContentStylesheet(sheet)
     });
   }
 
   sendAsyncMessage("Test:GetStyleSheetsInfoForNode", sheets);
 });
 
 /**
  * Get the property value from the computed style for an element.
--- a/devtools/client/inspector/shared/test/head.js
+++ b/devtools/client/inspector/shared/test/head.js
@@ -7,17 +7,16 @@
 "use strict";
 
 // Import the inspector's head.js first (which itself imports shared-head.js).
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/inspector/test/head.js",
   this);
 
 var {CssRuleView} = require("devtools/client/inspector/rules/rules");
-var {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic");
 var {getInplaceEditorForSpan: inplaceEditor} =
   require("devtools/client/shared/inplace-editor");
 
 const TEST_URL_ROOT =
   "http://example.com/browser/devtools/client/inspector/shared/test/";
 const TEST_URL_ROOT_SSL =
   "https://example.com/browser/devtools/client/inspector/shared/test/";
 const ROOT_TEST_DIR = getRootDirectory(gTestPath);
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -133,16 +133,21 @@ const LOAD_CAUSE_STRINGS = {
   [Ci.nsIContentPolicy.TYPE_WEBSOCKET]: "websocket",
   [Ci.nsIContentPolicy.TYPE_CSP_REPORT]: "csp",
   [Ci.nsIContentPolicy.TYPE_XSLT]: "xslt",
   [Ci.nsIContentPolicy.TYPE_BEACON]: "beacon",
   [Ci.nsIContentPolicy.TYPE_FETCH]: "fetch",
   [Ci.nsIContentPolicy.TYPE_IMAGESET]: "imageset",
   [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest"
 };
+
+function loadCauseString(causeType) {
+  return LOAD_CAUSE_STRINGS[causeType] || "unknown";
+}
+
 const DEFAULT_EDITOR_CONFIG = {
   mode: Editor.modes.text,
   readOnly: true,
   lineNumbers: true
 };
 const GENERIC_VARIABLES_VIEW_SETTINGS = {
   lazyEmpty: true,
   // ms
@@ -1223,16 +1228,23 @@ RequestsMenuView.prototype = Heritage.ex
         break;
       case "domain":
         if (direction == "ascending") {
           this.sortContents(this._byDomain);
         } else {
           this.sortContents((a, b) => !this._byDomain(a, b));
         }
         break;
+      case "cause":
+        if (direction == "ascending") {
+          this.sortContents(this._byCause);
+        } else {
+          this.sortContents((a, b) => !this._byCause(a, b));
+        }
+        break;
       case "type":
         if (direction == "ascending") {
           this.sortContents(this._byType);
         } else {
           this.sortContents((a, b) => !this._byType(a, b));
         }
         break;
       case "transferred":
@@ -1434,20 +1446,28 @@ RequestsMenuView.prototype = Heritage.ex
   _byDomain: function ({ attachment: first }, { attachment: second }) {
     let firstDomain = this._getUriHostPort(first.url).toLowerCase();
     let secondDomain = this._getUriHostPort(second.url).toLowerCase();
     return firstDomain == secondDomain
       ? first.startedMillis > second.startedMillis
       : firstDomain > secondDomain;
   },
 
+  _byCause: function ({ attachment: first }, { attachment: second }) {
+    let firstCause = loadCauseString(first.cause.type);
+    let secondCause = loadCauseString(second.cause.type);
+
+    return firstCause == secondCause
+      ? first.startedMillis > second.startedMillis
+      : firstCause > secondCause;
+  },
+
   _byType: function ({ attachment: first }, { attachment: second }) {
     let firstType = this._getAbbreviatedMimeType(first.mimeType).toLowerCase();
-    let secondType = this._getAbbreviatedMimeType(second.mimeType)
-      .toLowerCase();
+    let secondType = this._getAbbreviatedMimeType(second.mimeType).toLowerCase();
 
     return firstType == secondType
       ? first.startedMillis > second.startedMillis
       : firstType > secondType;
   },
 
   _byTransferred: function ({ attachment: first }, { attachment: second }) {
     return first.transferredSize > second.transferredSize;
@@ -1935,18 +1955,17 @@ RequestsMenuView.prototype = Heritage.ex
       }
       case "statusText": {
         let node = $(".requests-menu-status", target);
         node.setAttribute("tooltiptext", value);
         break;
       }
       case "cause": {
         let labelNode = $(".requests-menu-cause-label", target);
-        let text = LOAD_CAUSE_STRINGS[value.type] || "unknown";
-        labelNode.setAttribute("value", text);
+        labelNode.setAttribute("value", loadCauseString(value.type));
         if (value.loadingDocumentUri) {
           labelNode.setAttribute("tooltiptext", value.loadingDocumentUri);
         }
 
         let stackNode = $(".requests-menu-cause-stack", target);
         if (value.stacktrace && value.stacktrace.length > 0) {
           stackNode.removeAttribute("hidden");
         }
--- a/devtools/client/netmonitor/test/browser_net_cause.js
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -80,17 +80,18 @@ const EXPECTED_REQUESTS = [
 var test = Task.async(function* () {
   // the initNetMonitor function clears the network request list after the
   // page is loaded. That's why we first load a bogus page from SIMPLE_URL,
   // and only then load the real thing from CAUSE_URL - we want to catch
   // all the requests the page is making, not only the XHRs.
   // We can't use about:blank here, because initNetMonitor checks that the
   // page has actually made at least one request.
   let [, debuggee, monitor] = yield initNetMonitor(SIMPLE_URL);
-  let { RequestsMenu } = monitor.panelWin.NetMonitorView;
+  let { $, NetMonitorView } = monitor.panelWin;
+  let { RequestsMenu } = NetMonitorView;
   RequestsMenu.lazyUpdate = false;
 
   debuggee.location = CAUSE_URL;
 
   yield waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
 
   is(RequestsMenu.itemCount, EXPECTED_REQUESTS.length,
     "All the page events should be recorded.");
@@ -124,11 +125,21 @@ var test = Task.async(function* () {
             `Request #${i} has the correct async cause on JS stack frame #${j}`);
         });
       }
     } else {
       is(stackLen, 0, `Request #${i} (${causeType}) has an empty stacktrace`);
     }
   });
 
+  // Sort the requests by cause and check the order
+  EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button"));
+  let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort();
+  expectedOrder.forEach((expectedCause, i) => {
+    let { target } = RequestsMenu.getItemAtIndex(i);
+    let causeLabel = target.querySelector(".requests-menu-cause-label");
+    let cause = causeLabel.getAttribute("value");
+    is(cause, expectedCause, `The request #${i} has the expected cause after sorting`);
+  });
+
   yield teardown(monitor);
   finish();
 });
--- a/devtools/client/responsive.html/actions/screenshot.js
+++ b/devtools/client/responsive.html/actions/screenshot.js
@@ -11,18 +11,17 @@ const {
   TAKE_SCREENSHOT_END,
 } = require("./index");
 
 const { getFormatStr } = require("../utils/l10n");
 const { getToplevelWindow } = require("sdk/window/utils");
 const { Task: { spawn } } = require("devtools/shared/task");
 const e10s = require("../utils/e10s");
 
-const BASE_URL = "resource://devtools/client/responsive.html";
-const audioCamera = new window.Audio(`${BASE_URL}/audio/camera-click.mp3`);
+const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
 
 const animationFrame = () => new Promise(resolve => {
   window.requestAnimationFrame(resolve);
 });
 
 function getFileName() {
   let date = new Date();
   let month = ("0" + (date.getMonth() + 1)).substr(-2);
deleted file mode 100644
index 6d9af013315d873e910ecf6e15bc298093dc1e99..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/devtools/client/responsive.html/audio/moz.build
+++ /dev/null
@@ -1,9 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-DevToolsModules(
-    'camera-click.mp3',
-)
--- a/devtools/client/responsive.html/components/device-selector.js
+++ b/devtools/client/responsive.html/components/device-selector.js
@@ -74,45 +74,51 @@ module.exports = createClass({
     }
 
     let state = devices.listState;
     let listContent;
 
     if (state == Types.deviceListState.LOADED) {
       listContent = [dom.option({
         value: "",
+        title: "",
         disabled: true,
         hidden: true,
       }, getStr("responsive.noDeviceSelected")),
         options.map(device => {
           return dom.option({
             key: device.name,
             value: device.name,
+            title: "",
           }, device.name);
         }),
         dom.option({
           value: OPEN_DEVICE_MODAL_VALUE,
+          title: "",
         }, getStr("responsive.editDeviceList"))];
     } else if (state == Types.deviceListState.LOADING
       || state == Types.deviceListState.INITIALIZED) {
       listContent = [dom.option({
         value: "",
+        title: "",
         disabled: true,
       }, getStr("responsive.deviceListLoading"))];
     } else if (state == Types.deviceListState.ERROR) {
       listContent = [dom.option({
         value: "",
+        title: "",
         disabled: true,
       }, getStr("responsive.deviceListError"))];
     }
 
     return dom.select(
       {
         className: selectClass,
         value: selectedDevice,
+        title: selectedDevice,
         onChange: this.onSelectChange,
         disabled: (state !== Types.deviceListState.LOADED),
       },
       ...listContent
     );
   },
 
 });
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -172,27 +172,28 @@ html, body {
   justify-content: center;
   height: 18px;
 }
 
 .viewport-device-selector {
   -moz-appearance: none;
   background-color: var(--theme-toolbar-background);
   background-image: var(--viewport-selection-arrow);
-  background-position: 136px;
+  background-position: 100% 52%;
   background-repeat: no-repeat;
   background-size: 7px;
   border: none;
   color: var(--viewport-color);
   height: 100%;
-  padding: 0 16px 0 0;
+  padding: 0 8px 0 8px;
   text-align: center;
   text-overflow: ellipsis;
   width: 150px;
   font-size: 11px;
+  width: -moz-fit-content;
 }
 
 .viewport-device-selector.selected {
   background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
 .viewport-device-selector:hover {
--- a/devtools/client/responsive.html/moz.build
+++ b/devtools/client/responsive.html/moz.build
@@ -1,17 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'actions',
-    'audio',
     'browser',
     'components',
     'images',
     'reducers',
     'utils',
 ]
 
 DevToolsModules(
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -3,16 +3,17 @@ support-files =
   head.js
 
 [test_HSplitBox_01.html]
 [test_notification_box_01.html]
 [test_notification_box_02.html]
 [test_notification_box_03.html]
 [test_reps_attribute.html]
 [test_reps_date-time.html]
+[test_reps_function.html]
 [test_reps_grip.html]
 [test_reps_object-with-url.html]
 [test_reps_stylesheet.html]
 [test_reps_undefined.html]
 [test_reps_window.html]
 [test_frame_01.html]
 [test_tree_01.html]
 [test_tree_02.html]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -183,8 +183,23 @@ function renderComponent(component, prop
 }
 
 function shallowRenderComponent(component, props) {
   const el = React.createElement(component, props);
   const renderer = TestUtils.createRenderer();
   renderer.render(el, {});
   return renderer.getRenderOutput();
 }
+
+/**
+ * Test that a rep renders correctly across different modes.
+ */
+function testRepRenderModes(modeTests, testName, componentUnderTest, gripStub) {
+  modeTests.forEach(({mode, expectedOutput, message}) => {
+    const modeString = typeof mode === "undefined" ? "no mode" : mode;
+    if (!message) {
+      message = `${testName}: ${modeString} renders correctly.`;
+    }
+
+    const rendered = renderComponent(componentUnderTest.rep, { object: gripStub, mode });
+    is(rendered.textContent, expectedOutput, message);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/shared/components/test/mochitest/test_reps_function.html
@@ -0,0 +1,171 @@
+
+<!DOCTYPE HTML>
+<html>
+<!--
+Test Func rep
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Rep test - Func</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">
+</head>
+<body>
+<pre id="test">
+<script src="head.js" type="application/javascript;version=1.8"></script>
+<script type="application/javascript;version=1.8">
+window.onload = Task.async(function* () {
+  let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
+  let { Func } = browserRequire("devtools/client/shared/components/reps/function");
+
+  const componentUnderTest = Func;
+
+  try {
+    // Test that correct rep is chosen
+    const gripStub = getGripStub("testNamed");
+    const renderedRep = shallowRenderComponent(Rep, { object: gripStub });
+    is(renderedRep.type, Func.rep, `Rep correctly selects ${Func.rep.displayName}`);
+
+    yield testNamed();
+    yield testVarNamed();
+    yield testAnon();
+    yield testLongName();
+  } catch(e) {
+    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
+  } finally {
+    SimpleTest.finish();
+  }
+
+  function testNamed() {
+    // Test declaration: `function testName{ let innerVar = "foo" }`
+    const testName = "testNamed";
+
+    const defaultOutput = `testName()`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testVarNamed() {
+    // Test declaration: `let testVarName = function() { }`
+    const testName = "testVarNamed";
+
+    const defaultOutput = `testVarName()`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testAnon() {
+    // Test declaration: `() => {}`
+    const testName = "testAnon";
+
+    const defaultOutput = `function()`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function testLongName() {
+    // Test declaration: `let f = function loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong() { }`
+    const testName = "testLongName";
+
+    const defaultOutput = `looooooooooooooooooooooooooooooooooooooooooooooooo\u2026ooooooooooooooooooooooooooooooooooooooooooooong()`;
+
+    const modeTests = [
+      {
+        mode: undefined,
+        expectedOutput: defaultOutput,
+      }
+    ];
+
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
+  }
+
+  function getGripStub(functionName) {
+    switch (functionName) {
+      case "testNamed":
+        return {
+          "type": "object",
+          "class": "Function",
+          "actor": "server1.conn6.obj35",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "name": "testName",
+          "displayName": "testName",
+          "location": {
+            "url": "debugger eval code",
+            "line": 1
+          }
+        };
+
+      case "testVarNamed":
+        return {
+          "type": "object",
+          "class": "Function",
+          "actor": "server1.conn7.obj41",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "displayName": "testVarName",
+          "location": {
+            "url": "debugger eval code",
+            "line": 1
+          }
+        };
+
+      case "testAnon":
+        return {
+          "type": "object",
+          "class": "Function",
+          "actor": "server1.conn7.obj45",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "location": {
+            "url": "debugger eval code",
+            "line": 1
+          }
+        };
+
+      case "testLongName":
+        return {
+          "type": "object",
+          "class": "Function",
+          "actor": "server1.conn7.obj67",
+          "extensible": true,
+          "frozen": false,
+          "sealed": false,
+          "name": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
+          "displayName": "loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooong",
+          "location": {
+            "url": "debugger eval code",
+            "line": 1
+          }
+        };
+    }
+  }
+});
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -13,16 +13,18 @@ Test grip rep
 <body>
 <pre id="test">
 <script src="head.js" type="application/javascript;version=1.8"></script>
 <script type="application/javascript;version=1.8">
 window.onload = Task.async(function* () {
   let { Rep } = browserRequire("devtools/client/shared/components/reps/rep");
   let { Grip } = browserRequire("devtools/client/shared/components/reps/grip");
 
+  const componentUnderTest = Grip;
+
   try {
     yield testBasic();
 
     // Test property iterator
     yield testMaxProps();
     yield testMoreThanMaxProps();
     yield testUninterestingProps();
 
@@ -61,17 +63,17 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
-    testRenderingInMode(modeTests, testName);
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMaxProps() {
     // Test object: `{a: "a", b: "b", c: "c"}`;
     const testName = "testMaxProps";
 
     const defaultOutput = `Object {a: "a", b: "b", c: "c"}`;
 
@@ -89,17 +91,17 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
-    testRenderingInMode(modeTests, testName);
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testMoreThanMaxProps() {
     // Test object = `{p0: "0", p1: "1", p2: "2", ..., p101: "101"}`
     const testName = "testMoreThanMaxProps";
 
     const defaultOutput = `Object {p0: "0", p1: "1", p2: "2", more...}`;
 
@@ -126,17 +128,17 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
         expectedOutput: longOutput,
       }
     ];
 
-    testRenderingInMode(modeTests, testName);
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testUninterestingProps() {
     // Test object: `{a: undefined, b: undefined, c: "c", d: 1}`
     // @TODO This is not how we actually want the preview to be output.
     // See https://bugzilla.mozilla.org/show_bug.cgi?id=1276376
     const expectedOutput = `Object {a: undefined, b: undefined, c: "c", more...}`;
   }
@@ -161,17 +163,17 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
-    testRenderingInMode(modeTests, testName);
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function testNestedArray() {
     // Test object: `{arrProp: ["foo", "bar", "baz"]}`
     const testName = "testNestedArray";
 
     const defaultOutput = `Object {arrProp: [3]}`;
 
@@ -189,30 +191,17 @@ window.onload = Task.async(function* () 
         expectedOutput: defaultOutput,
       },
       {
         mode: "long",
         expectedOutput: defaultOutput,
       }
     ];
 
-    testRenderingInMode(modeTests, testName);
-  }
-
-  function testRenderingInMode(modeTests, testName) {
-    modeTests.forEach(({mode, expectedOutput, message}) => {
-      const modeString = typeof mode === "undefined" ? "no mode" : mode;
-      if (!message) {
-        message = `${testName}: ${modeString} renders correctly.`
-      }
-      const gripStub = getGripStub(testName);
-
-      const rendered = renderComponent(Grip.rep, { object: gripStub, mode });
-      is(rendered.textContent, expectedOutput, message);
-    });
+    testRepRenderModes(modeTests, testName, componentUnderTest, getGripStub(testName));
   }
 
   function getGripStub(functionName) {
     switch (functionName) {
       case "testBasic":
         return {
           "type": "object",
           "class": "Object",
--- a/devtools/client/shared/widgets/VariablesView.jsm
+++ b/devtools/client/shared/widgets/VariablesView.jsm
@@ -123,24 +123,26 @@ VariablesView.prototype = {
   /**
    * Adds a scope to contain any inspected variables.
    *
    * This new scope will be considered the parent of any other scope
    * added afterwards.
    *
    * @param string aName
    *        The scope's name (e.g. "Local", "Global" etc.).
+   * @param string aCustomClass
+   *        An additional class name for the containing element.
    * @return Scope
    *         The newly created Scope instance.
    */
-  addScope: function (aName = "") {
+  addScope: function (aName = "", aCustomClass = "") {
     this._removeEmptyNotice();
     this._toggleSearchVisibility(true);
 
-    let scope = new Scope(this, aName);
+    let scope = new Scope(this, aName, { customClass: aCustomClass });
     this._store.push(scope);
     this._itemsByElement.set(scope._target, scope);
     this._currHierarchy.set(aName, scope);
     scope.header = !!aName;
 
     return scope;
   },
 
@@ -1787,17 +1789,18 @@ Scope.prototype = {
    *
    * @param string aName
    *        The scope's name.
    * @param object aFlags [optional]
    *        Additional options or flags for this scope.
    */
   _init: function (aName, aFlags) {
     this._idString = generateId(this._nameString = aName);
-    this._displayScope(aName, this.targetClassName, "devtools-toolbar");
+    this._displayScope(aName, `${this.targetClassName} ${aFlags.customClass}`,
+                       "devtools-toolbar");
     this._addEventListeners();
     this.parentNode.appendChild(this._target);
   },
 
   /**
    * Creates the necessary nodes for this scope.
    *
    * @param string aName
@@ -1815,16 +1818,17 @@ Scope.prototype = {
     element.className = aTargetClassName;
 
     let arrow = this._arrow = document.createElement("hbox");
     arrow.className = "arrow theme-twisty";
 
     let name = this._name = document.createElement("label");
     name.className = "plain name";
     name.setAttribute("value", aName);
+    name.setAttribute("crop", "end");
 
     let title = this._title = document.createElement("hbox");
     title.className = "title " + aTitleClassName;
     title.setAttribute("align", "center");
 
     let enumerable = this._enum = document.createElement("vbox");
     let nonenum = this._nonenum = document.createElement("vbox");
     enumerable.className = "variables-view-element-details enum";
--- a/devtools/client/sourceeditor/codemirror/README
+++ b/devtools/client/sourceeditor/codemirror/README
@@ -1,16 +1,16 @@
 This is the CodeMirror editor packaged for the Mozilla Project. CodeMirror
 is a JavaScript component that provides a code editor in the browser. When
 a mode is available for the language you are coding in, it will color your
 code, and optionally help with indentation.
 
 # Upgrade
 
-Currently used version is 5.15.2. To upgrade, download a new version of
+Currently used version is 5.16.0. To upgrade, download a new version of
 CodeMirror from the project's page [1] and replace all JavaScript and
 CSS files inside the codemirror directory [2].
 
 To confirm the functionality run mochitests for the following components:
 
  * sourceeditor
  * scratchpad
  * debugger
--- a/devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
+++ b/devtools/client/sourceeditor/codemirror/addon/fold/comment-fold.js
@@ -24,17 +24,17 @@ CodeMirror.registerGlobalHelper("fold", 
     if (found == -1) {
       if (pass == 1) return;
       pass = 1;
       at = lineText.length;
       continue;
     }
     if (pass == 1 && found < start.ch) return;
     if (/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found + 1))) &&
-        (lineText.slice(found - endToken.length, found) == endToken ||
+        (found == 0 || lineText.slice(found - endToken.length, found) == endToken ||
          !/comment/.test(cm.getTokenTypeAt(CodeMirror.Pos(line, found))))) {
       startCh = found + startToken.length;
       break;
     }
     at = found - 1;
   }
 
   var depth = 1, lastLine = cm.lastLine(), end, endCh;
--- a/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
+++ b/devtools/client/sourceeditor/codemirror/addon/fold/foldcode.js
@@ -44,17 +44,17 @@
 
     var myWidget = makeWidget(cm, options);
     CodeMirror.on(myWidget, "mousedown", function(e) {
       myRange.clear();
       CodeMirror.e_preventDefault(e);
     });
     var myRange = cm.markText(range.from, range.to, {
       replacedWith: myWidget,
-      clearOnEnter: true,
+      clearOnEnter: getOption(cm, options, "clearOnEnter"),
       __isFold: true
     });
     myRange.on("clear", function(from, to) {
       CodeMirror.signal(cm, "unfold", cm, from, to);
     });
     CodeMirror.signal(cm, "fold", cm, range.from, range.to);
   }
 
@@ -124,17 +124,18 @@
       if (cur) return cur;
     }
   });
 
   var defaultOptions = {
     rangeFinder: CodeMirror.fold.auto,
     widget: "\u2194",
     minFoldSize: 0,
-    scanUp: false
+    scanUp: false,
+    clearOnEnter: true
   };
 
   CodeMirror.defineOption("foldOptions", null);
 
   function getOption(cm, options, name) {
     if (options && options[name] !== undefined)
       return options[name];
     var editorOptions = cm.options.foldOptions;
--- a/devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js
+++ b/devtools/client/sourceeditor/codemirror/addon/fold/foldgutter.js
@@ -45,17 +45,17 @@
     if (opts === true) opts = {};
     if (opts.gutter == null) opts.gutter = "CodeMirror-foldgutter";
     if (opts.indicatorOpen == null) opts.indicatorOpen = "CodeMirror-foldgutter-open";
     if (opts.indicatorFolded == null) opts.indicatorFolded = "CodeMirror-foldgutter-folded";
     return opts;
   }
 
   function isFolded(cm, line) {
-    var marks = cm.findMarksAt(Pos(line));
+    var marks = cm.findMarks(Pos(line, 0), Pos(line + 1, 0));
     for (var i = 0; i < marks.length; ++i)
       if (marks[i].__isFold && marks[i].find().from.line == line) return marks[i];
   }
 
   function marker(spec) {
     if (typeof spec == "string") {
       var elt = document.createElement("div");
       elt.className = spec + " CodeMirror-guttermarker-subtle";
--- a/devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js
+++ b/devtools/client/sourceeditor/codemirror/addon/hint/show-hint.js
@@ -224,16 +224,17 @@
     var left = pos.left, top = pos.bottom, below = true;
     hints.style.left = left + "px";
     hints.style.top = top + "px";
     // If we're at the edge of the screen, then we want the menu to appear on the left of the cursor.
     var winW = window.innerWidth || Math.max(document.body.offsetWidth, document.documentElement.offsetWidth);
     var winH = window.innerHeight || Math.max(document.body.offsetHeight, document.documentElement.offsetHeight);
     (completion.options.container || document.body).appendChild(hints);
     var box = hints.getBoundingClientRect(), overlapY = box.bottom - winH;
+    var scrolls = hints.scrollHeight > hints.clientHeight + 1
     if (overlapY > 0) {
       var height = box.bottom - box.top, curTop = pos.top - (pos.bottom - box.top);
       if (curTop - height > 0) { // Fits above cursor
         hints.style.top = (top = pos.top - height) + "px";
         below = false;
       } else if (height > winH) {
         hints.style.height = (winH - 5) + "px";
         hints.style.top = (top = pos.bottom - box.top) + "px";
@@ -248,16 +249,18 @@
     var overlapX = box.right - winW;
     if (overlapX > 0) {
       if (box.right - box.left > winW) {
         hints.style.width = (winW - 5) + "px";
         overlapX -= (box.right - box.left) - winW;
       }
       hints.style.left = (left = pos.left - overlapX) + "px";
     }
+    if (scrolls) for (var node = hints.firstChild; node; node = node.nextSibling)
+      node.style.paddingRight = cm.display.nativeBarWidth + "px"
 
     cm.addKeyMap(this.keyMap = buildKeyMap(completion, {
       moveFocus: function(n, avoidWrap) { widget.changeActive(widget.selectedHint + n, avoidWrap); },
       setFocus: function(n) { widget.changeActive(n); },
       menuSize: function() { return widget.screenAmount(); },
       length: completions.length,
       close: function() { completion.close(); },
       pick: function() { widget.pick(); },
--- a/devtools/client/sourceeditor/codemirror/keymap/sublime.js
+++ b/devtools/client/sourceeditor/codemirror/keymap/sublime.js
@@ -415,16 +415,44 @@
         at = word.from;
         cm.replaceRange(mod(word.word), word.from, word.to);
       }
     });
   }
 
   map[cK + ctrl + "Backspace"] = "delLineLeft";
 
+  cmds[map["Backspace"] = "smartBackspace"] = function(cm) {
+    if (cm.somethingSelected()) return CodeMirror.Pass;
+
+    cm.operation(function() {
+      var cursors = cm.listSelections();
+      var indentUnit = cm.getOption("indentUnit");
+
+      for (var i = cursors.length - 1; i >= 0; i--) {
+        var cursor = cursors[i].head;
+        var toStartOfLine = cm.getRange({line: cursor.line, ch: 0}, cursor);
+        var column = CodeMirror.countColumn(toStartOfLine, null, cm.getOption("tabSize"));
+
+        // Delete by one character by default
+        var deletePos = cm.findPosH(cursor, -1, "char", false);
+
+        if (toStartOfLine && !/\S/.test(toStartOfLine) && column % indentUnit == 0) {
+          var prevIndent = new Pos(cursor.line,
+            CodeMirror.findColumn(toStartOfLine, column - indentUnit, indentUnit));
+
+          // Smart delete only if we found a valid prevIndent location
+          if (prevIndent.ch != cursor.ch) deletePos = prevIndent;
+        }
+
+        cm.replaceRange("", deletePos, cursor, "+delete");
+      }
+    });
+  };
+
   cmds[map[cK + ctrl + "K"] = "delLineRight"] = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = ranges.length - 1; i >= 0; i--)
         cm.replaceRange("", ranges[i].anchor, Pos(ranges[i].to().line), "+delete");
       cm.scrollIntoView();
     });
   };
@@ -467,27 +495,28 @@
   };
 
   map[cK + ctrl + "G"] = "clearBookmarks";
   cmds[map[cK + ctrl + "C"] = "showInCenter"] = function(cm) {
     var pos = cm.cursorCoords(null, "local");
     cm.scrollTo(null, (pos.top + pos.bottom) / 2 - cm.getScrollInfo().clientHeight / 2);
   };
 
-  cmds[map["Shift-Alt-Up"] = "selectLinesUpward"] = function(cm) {
+  var selectLinesCombo = mac ? "Ctrl-Shift-" : "Ctrl-Alt-";
+  cmds[map[selectLinesCombo + "Up"] = "selectLinesUpward"] = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = 0; i < ranges.length; i++) {
         var range = ranges[i];
         if (range.head.line > cm.firstLine())
           cm.addSelection(Pos(range.head.line - 1, range.head.ch));
       }
     });
   };
-  cmds[map["Shift-Alt-Down"] = "selectLinesDownward"] = function(cm) {
+  cmds[map[selectLinesCombo + "Down"] = "selectLinesDownward"] = function(cm) {
     cm.operation(function() {
       var ranges = cm.listSelections();
       for (var i = 0; i < ranges.length; i++) {
         var range = ranges[i];
         if (range.head.line < cm.lastLine())
           cm.addSelection(Pos(range.head.line + 1, range.head.ch));
       }
     });
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.css
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.css
@@ -83,18 +83,24 @@
   100% {}
 }
 
 /* Can style cursor different in overwrite (non-insert) mode */
 .CodeMirror-overwrite .CodeMirror-cursor {}
 
 .cm-tab { display: inline-block; text-decoration: inherit; }
 
+.CodeMirror-rulers {
+  position: absolute;
+  left: 0; right: 0; top: -50px; bottom: -20px;
+  overflow: hidden;
+}
 .CodeMirror-ruler {
   border-left: 1px solid #ccc;
+  top: 0; bottom: 0;
   position: absolute;
 }
 
 /* DEFAULT THEME */
 
 .cm-s-default .cm-header {color: blue;}
 .cm-s-default .cm-quote {color: #090;}
 .cm-negative {color: #d44;}
@@ -286,17 +292,20 @@ div.CodeMirror span.CodeMirror-nonmatchi
 .CodeMirror-measure {
   position: absolute;
   width: 100%;
   height: 0;
   overflow: hidden;
   visibility: hidden;
 }
 
-.CodeMirror-cursor { position: absolute; }
+.CodeMirror-cursor {
+  position: absolute;
+  pointer-events: none;
+}
 .CodeMirror-measure pre { position: static; }
 
 div.CodeMirror-cursors {
   visibility: hidden;
   position: relative;
   z-index: 3;
 }
 div.CodeMirror-dragcursors {
--- a/devtools/client/sourceeditor/codemirror/lib/codemirror.js
+++ b/devtools/client/sourceeditor/codemirror/lib/codemirror.js
@@ -2928,20 +2928,33 @@
     var from = lineLeft(lineObj), to = lineRight(lineObj);
     var fromX = getX(from), fromOutside = wrongLine, toX = getX(to), toOutside = wrongLine;
 
     if (x > toX) return PosWithInfo(lineNo, to, toOutside, 1);
     // Do a binary search between these bounds.
     for (;;) {
       if (bidi ? to == from || to == moveVisually(lineObj, from, 1) : to - from <= 1) {
         var ch = x < fromX || x - fromX <= toX - x ? from : to;
+        var outside = ch == from ? fromOutside : toOutside
         var xDiff = x - (ch == from ? fromX : toX);
+        // This is a kludge to handle the case where the coordinates
+        // are after a line-wrapped line. We should replace it with a
+        // more general handling of cursor positions around line
+        // breaks. (Issue #4078)
+        if (toOutside && !bidi && !/\s/.test(lineObj.text.charAt(ch)) && xDiff > 0 &&
+            ch < lineObj.text.length && preparedMeasure.view.measure.heights.length > 1) {
+          var charSize = measureCharPrepared(cm, preparedMeasure, ch, "right");
+          if (innerOff <= charSize.bottom && innerOff >= charSize.top && Math.abs(x - charSize.right) < xDiff) {
+            outside = false
+            ch++
+            xDiff = x - charSize.right
+          }
+        }
         while (isExtendingChar(lineObj.text.charAt(ch))) ++ch;
-        var pos = PosWithInfo(lineNo, ch, ch == from ? fromOutside : toOutside,
-                              xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
+        var pos = PosWithInfo(lineNo, ch, outside, xDiff < -1 ? -1 : xDiff > 1 ? 1 : 0);
         return pos;
       }
       var step = Math.ceil(dist / 2), middle = from + step;
       if (bidi) {
         middle = from;
         for (var i = 0; i < step; ++i) middle = moveVisually(lineObj, middle, 1);
       }
       var middleX = getX(middle);
@@ -3655,16 +3668,17 @@
           setTimeout(function() {document.body.focus(); display.input.focus();}, 20);
         else
           display.input.focus();
       }
     });
     // Let the drag handler handle this.
     if (webkit) display.scroller.draggable = true;
     cm.state.draggingText = dragEnd;
+    dragEnd.copy = mac ? e.altKey : e.ctrlKey
     // IE's approach to draggable
     if (display.scroller.dragDrop) display.scroller.dragDrop();
     on(document, "mouseup", dragEnd);
     on(display.scroller, "drop", dragEnd);
   }
 
   // Normal selection, as opposed to text dragging.
   function leftButtonSelect(cm, e, start, type, addNew) {
@@ -3885,17 +3899,17 @@
         cm.state.draggingText(e);
         // Ensure the editor is re-focused
         setTimeout(function() {cm.display.input.focus();}, 20);
         return;
       }
       try {
         var text = e.dataTransfer.getData("Text");
         if (text) {
-          if (cm.state.draggingText && !(mac ? e.altKey : e.ctrlKey))
+          if (cm.state.draggingText && !cm.state.draggingText.copy)
             var selected = cm.listSelections();
           setSelectionNoUndo(cm.doc, simpleSelection(pos, pos));
           if (selected) for (var i = 0; i < selected.length; ++i)
             replaceRange(cm.doc, "", selected[i].anchor, selected[i].head, "drag");
           cm.replaceSelection(text, "around", "paste");
           cm.display.input.focus();
         }
       }
@@ -8897,12 +8911,12 @@
         order.push(new BidiSpan(order[0].level, len, len));
 
       return order;
     };
   })();
 
   // THE END
 
-  CodeMirror.version = "5.15.2";
+  CodeMirror.version = "5.16.0";
 
   return CodeMirror;
 });
--- a/devtools/client/sourceeditor/test/codemirror/sublime_test.js
+++ b/devtools/client/sourceeditor/test/codemirror/sublime_test.js
@@ -269,16 +269,20 @@
          "toggleBookmark",
          "prevBookmark", hasSel(2, 1, 2, 2),
          "prevBookmark", hasSel(0, 1, 0, 1),
          "selectBookmarks", hasSel(0, 1, 0, 1,
                                    2, 1, 2, 2),
          "clearBookmarks",
          Pos(0, 0), "selectBookmarks", at(0, 0));
 
+  stTest("smartBackspace", "  foo\n    bar",
+         setSel(0, 2, 0, 2, 1, 4, 1, 4, 1, 6, 1, 6), "smartBackspace",
+         val("foo\n  br"))
+
   stTest("upAndDowncaseAtCursor", "abc\ndef  x\nghI",
          setSel(0, 1, 0, 3,
                 1, 1, 1, 1,
                 1, 4, 1, 4), "upcaseAtCursor",
          val("aBC\nDEF  x\nghI"), hasSel(0, 1, 0, 3,
                                          1, 3, 1, 3,
                                          1, 4, 1, 4),
          "downcaseAtCursor",
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -10,17 +10,17 @@ this.EXPORTED_SYMBOLS = ["StyleSheetEdit
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const Editor = require("devtools/client/sourceeditor/editor");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {shortSource, prettifyCSS} = require("devtools/shared/inspector/css-logic");
 const {console} = require("resource://gre/modules/Console.jsm");
 const Services = require("Services");
 const EventEmitter = require("devtools/shared/event-emitter");
 const {Task} = require("devtools/shared/task");
 const {FileUtils} = require("resource://gre/modules/FileUtils.jsm");
 const {NetUtil} = require("resource://gre/modules/NetUtil.jsm");
 const {TextDecoder, OS} = Cu.import("resource://gre/modules/osfile.jsm", {});
 const {
@@ -191,17 +191,17 @@ StyleSheetEditor.prototype = {
 
     if (!this.styleSheet.href) {
       let index = this.styleSheet.styleSheetIndex + 1;
       return getString("inlineStyleSheet", index);
     }
 
     if (!this._friendlyName) {
       let sheetURI = this.styleSheet.href;
-      this._friendlyName = CssLogic.shortSource({ href: sheetURI });
+      this._friendlyName = shortSource({ href: sheetURI });
       try {
         this._friendlyName = decodeURI(this._friendlyName);
       } catch (ex) {
         // Ignore.
       }
     }
     return this._friendlyName;
   },
@@ -257,29 +257,28 @@ StyleSheetEditor.prototype = {
       this._fileModDate = info.lastModificationDate.getTime();
     }, this.markLinkedFileBroken);
 
     this.emit("linked-css-file");
   },
 
   /**
    * A helper function that fetches the source text from the style
-   * sheet.  The text is possibly prettified using
-   * CssLogic.prettifyCSS.  This also sets |this._state.text| to the
-   * new text.
+   * sheet.  The text is possibly prettified using prettifyCSS.  This
+   * also sets |this._state.text| to the new text.
    *
    * @return {Promise} a promise that resolves to the new text
    */
   _getSourceTextAndPrettify: function () {
     return this.styleSheet.getText().then((longStr) => {
       return longStr.string();
     }).then((source) => {
       let ruleCount = this.styleSheet.ruleCount;
       if (!this.styleSheet.isOriginalSource) {
-        source = CssLogic.prettifyCSS(source, ruleCount);
+        source = prettifyCSS(source, ruleCount);
       }
       this._state.text = source;
       return source;
     });
   },
 
   /**
    * Start fetching the full text source for this editor's sheet.
new file mode 100644
--- /dev/null
+++ b/devtools/client/themes/audio/moz.build
@@ -0,0 +1,9 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+    'shutter.wav',
+)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e9d742913eb4f38f2b66aacf115f13277130f18e
GIT binary patch
literal 25744
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
--- a/devtools/client/themes/moz.build
+++ b/devtools/client/themes/moz.build
@@ -1,10 +1,14 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+DIRS += [
+    'audio',
+]
+
 DevToolsModules(
     'common.css',
     'variables.css',
 )
--- a/devtools/client/themes/widgets.css
+++ b/devtools/client/themes/widgets.css
@@ -622,16 +622,22 @@ widgets.css is overwritten. */
 }
 
 .variables-view-scope > .title {
   border-top-width: 1px;
   border-top-style: solid;
   margin-top: -1px;
 }
 
+/* Custom scope stylings */
+
+.variables-view-watch-expressions .title > .name  {
+  max-width: 14em;
+}
+
 .theme-firebug .variables-view-scope > .title {
   height: auto;
   border: none;
 }
 
 .theme-firebug .variables-view-scope > .title > .theme-twisty {
   display: none;
 }
--- a/devtools/server/actors/csscoverage.js
+++ b/devtools/server/actors/csscoverage.js
@@ -13,17 +13,17 @@ const events = require("sdk/event/core")
 const protocol = require("devtools/shared/protocol");
 const { custom } = protocol;
 const { cssUsageSpec } = require("devtools/shared/specs/csscoverage");
 
 loader.lazyGetter(this, "DOMUtils", () => {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 loader.lazyRequireGetter(this, "stylesheets", "devtools/server/actors/stylesheets");
-loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "prettifyCSS", "devtools/shared/inspector/css-logic", true);
 
 const CSSRule = Ci.nsIDOMCSSRule;
 
 const MAX_UNUSED_RULES = 10000;
 
 /**
  * Allow: let foo = l10n.lookup("csscoverageFoo");
  */
@@ -392,17 +392,17 @@ var CSSUsageActor = protocol.ActorClassW
 
     // Helper function to create a JSONable data structure representing a rule
     const ruleToRuleReport = function (rule, ruleData) {
       return {
         url: rule.url,
         shortUrl: rule.url.split("/").slice(-1)[0],
         start: { line: rule.line, column: rule.column },
         selectorText: ruleData.selectorText,
-        formattedCssText: CssLogic.prettifyCSS(ruleData.cssText)
+        formattedCssText: prettifyCSS(ruleData.cssText)
       };
     };
 
     // A count of each type of rule for the bar chart
     let summary = { used: 0, unused: 0, preload: 0 };
 
     // Create the set of the unused rules
     let unusedMap = new Map();
--- a/devtools/server/actors/highlighters/utils/markup.js
+++ b/devtools/server/actors/highlighters/utils/markup.js
@@ -7,17 +7,17 @@
 const { Cc, Ci, Cu } = require("chrome");
 const { getCurrentZoom,
   getRootBindingParent } = require("devtools/shared/layout/utils");
 const { on, emit } = require("sdk/event/core");
 
 const lazyContainer = {};
 
 loader.lazyRequireGetter(lazyContainer, "CssLogic",
-  "devtools/shared/inspector/css-logic", true);
+  "devtools/server/css-logic", true);
 exports.getComputedStyle = (node) =>
   lazyContainer.CssLogic.getComputedStyle(node);
 
 exports.getBindingElementAndPseudo = (node) =>
   lazyContainer.CssLogic.getBindingElementAndPseudo(node);
 
 loader.lazyGetter(lazyContainer, "DOMUtils", () =>
   Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
--- a/devtools/server/actors/inspector.js
+++ b/devtools/server/actors/inspector.js
@@ -145,17 +145,17 @@ loader.lazyGetter(this, "DOMParser", fun
            .createInstance(Ci.nsIDOMParser);
 });
 
 loader.lazyGetter(this, "eventListenerService", function () {
   return Cc["@mozilla.org/eventlistenerservice;1"]
            .getService(Ci.nsIEventListenerService);
 });
 
-loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
+loader.lazyGetter(this, "CssLogic", () => require("devtools/server/css-logic").CssLogic);
 
 /**
  * We only send nodeValue up to a certain size by default.  This stuff
  * controls that size.
  */
 exports.DEFAULT_VALUE_SUMMARY_LENGTH = 50;
 var gValueSummaryLength = exports.DEFAULT_VALUE_SUMMARY_LENGTH;
 
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -28,17 +28,17 @@ const { threadSpec } = require("devtools
 
 const { defer, resolve, reject, all } = promise;
 
 loader.lazyGetter(this, "Debugger", () => {
   let Debugger = require("Debugger");
   hackDebugger(Debugger);
   return Debugger;
 });
-loader.lazyRequireGetter(this, "CssLogic", "devtools/shared/inspector/css-logic", true);
+loader.lazyRequireGetter(this, "CssLogic", "devtools/server/css-logic", true);
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "mapURIToAddonID", "devtools/server/actors/utils/map-uri-to-addon-id");
 
 /**
  * A BreakpointActorMap is a map from locations to instances of BreakpointActor.
  */
 function BreakpointActorMap() {
   this._size = 0;
--- a/devtools/server/actors/styleeditor.js
+++ b/devtools/server/actors/styleeditor.js
@@ -9,17 +9,17 @@ const Services = require("Services");
 const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
 const promise = require("promise");
 const events = require("sdk/event/core");
 const protocol = require("devtools/shared/protocol");
 const {Arg, method, RetVal} = protocol;
 const {fetch} = require("devtools/shared/DevToolsUtils");
 const {oldStyleSheetSpec, styleEditorSpec} = require("devtools/shared/specs/styleeditor");
 
-loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
+loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic"));
 
 var TRANSITION_CLASS = "moz-styleeditor-transitioning";
 var TRANSITION_DURATION_MS = 500;
 var TRANSITION_RULE = "\
 :root.moz-styleeditor-transitioning, :root.moz-styleeditor-transitioning * {\
 transition-duration: " + TRANSITION_DURATION_MS + "ms !important; \
 transition-delay: 0ms !important;\
 transition-timing-function: ease-out !important;\
--- a/devtools/server/actors/styles.js
+++ b/devtools/server/actors/styles.js
@@ -14,17 +14,18 @@ const {isCssPropertyKnown} = require("de
 const {Task} = require("devtools/shared/task");
 const events = require("sdk/event/core");
 
 // This will also add the "stylesheet" actor type for protocol.js to recognize
 const {UPDATE_PRESERVING_RULES, UPDATE_GENERAL} = require("devtools/server/actors/stylesheets");
 const {pageStyleSpec, styleRuleSpec, ELEMENT_STYLE} = require("devtools/shared/specs/styles");
 
 loader.lazyRequireGetter(this, "CSS", "CSS");
-loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
+loader.lazyGetter(this, "CssLogic", () => require("devtools/server/css-logic").CssLogic);
+loader.lazyGetter(this, "SharedCssLogic", () => require("devtools/shared/inspector/css-logic"));
 loader.lazyGetter(this, "DOMUtils", () => Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils));
 
 // When gathering rules to read for pseudo elements, we will skip
 // :before and :after, which are handled as a special case.
 loader.lazyGetter(this, "PSEUDO_ELEMENTS_TO_READ", () => {
   return DOMUtils.getCSSPseudoElementNames().filter(pseudo => {
     return pseudo !== ":before" && pseudo !== ":after";
   });
@@ -197,17 +198,17 @@ var PageStyleActor = protocol.ActorClass
    *       matched: <true if there are matched selectors for this value>
    *     },
    *     ...
    *   }
    */
   getComputed: function (node, options) {
     let ret = Object.create(null);
 
-    this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
+    this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA;
     this.cssLogic.highlight(node.rawNode);
     let computed = this.cssLogic.computedStyle || [];
 
     Array.prototype.forEach.call(computed, name => {
       ret[name] = {
         value: computed.getPropertyValue(name),
         priority: computed.getPropertyPriority(name) || undefined
       };
@@ -375,17 +376,17 @@ var PageStyleActor = protocol.ActorClass
    *     // The full form of any domrule referenced.
    *     rules: [ <domrule>, ... ], // The full form of any domrule referenced
    *
    *     // The full form of any sheets referenced.
    *     sheets: [ <domsheet>, ... ]
    *  }
    */
   getMatchedSelectors: function (node, property, options) {
-    this.cssLogic.sourceFilter = options.filter || CssLogic.FILTER.UA;
+    this.cssLogic.sourceFilter = options.filter || SharedCssLogic.FILTER.UA;
     this.cssLogic.highlight(node.rawNode);
 
     let rules = new Set();
     let sheets = new Set();
 
     let matched = [];
     let propInfo = this.cssLogic.getPropertyInfo(property);
     for (let selectorInfo of propInfo.matchedSelectors) {
@@ -572,19 +573,19 @@ var PageStyleActor = protocol.ActorClass
 
     let rules = [];
 
     // getCSSStyleRules returns ordered from least-specific to
     // most-specific.
     for (let i = domRules.Count() - 1; i >= 0; i--) {
       let domRule = domRules.GetElementAt(i);
 
-      let isSystem = !CssLogic.isContentStylesheet(domRule.parentStyleSheet);
+      let isSystem = !SharedCssLogic.isContentStylesheet(domRule.parentStyleSheet);
 
-      if (isSystem && options.filter != CssLogic.FILTER.UA) {
+      if (isSystem && options.filter != SharedCssLogic.FILTER.UA) {
         continue;
       }
 
       if (inherited) {
         // Don't include inherited rules if none of its properties
         // are inheritable.
         let hasInherited = [...domRule.style].some(
           prop => DOMUtils.isInheritedProperty(prop)
--- a/devtools/server/actors/stylesheets.js
+++ b/devtools/server/actors/stylesheets.js
@@ -13,17 +13,17 @@ const events = require("sdk/event/core")
 const protocol = require("devtools/shared/protocol");
 const {LongStringActor} = require("devtools/server/actors/string");
 const {fetch} = require("devtools/shared/DevToolsUtils");
 const {listenOnce} = require("devtools/shared/async-utils");
 const {originalSourceSpec, mediaRuleSpec, styleSheetSpec,
        styleSheetsSpec} = require("devtools/shared/specs/stylesheets");
 const {SourceMapConsumer} = require("source-map");
 
-loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic").CssLogic);
+loader.lazyGetter(this, "CssLogic", () => require("devtools/shared/inspector/css-logic"));
 
 XPCOMUtils.defineLazyGetter(this, "DOMUtils", function () {
   return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
 });
 
 var TRANSITION_CLASS = "moz-styleeditor-transitioning";
 var TRANSITION_DURATION_MS = 500;
 var TRANSITION_BUFFER_MS = 1000;
new file mode 100644
--- /dev/null
+++ b/devtools/server/css-logic.js
@@ -0,0 +1,1544 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set 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/. */
+
+/*
+ * About the objects defined in this file:
+ * - CssLogic contains style information about a view context. It provides
+ *   access to 2 sets of objects: Css[Sheet|Rule|Selector] provide access to
+ *   information that does not change when the selected element changes while
+ *   Css[Property|Selector]Info provide information that is dependent on the
+ *   selected element.
+ *   Its key methods are highlight(), getPropertyInfo() and forEachSheet(), etc
+ *
+ * - CssSheet provides a more useful API to a DOM CSSSheet for our purposes,
+ *   including shortSource and href.
+ * - CssRule a more useful API to a nsIDOMCSSRule including access to the group
+ *   of CssSelectors that the rule provides properties for
+ * - CssSelector A single selector - i.e. not a selector group. In other words
+ *   a CssSelector does not contain ','. This terminology is different from the
+ *   standard DOM API, but more inline with the definition in the spec.
+ *
+ * - CssPropertyInfo contains style information for a single property for the
+ *   highlighted element.
+ * - CssSelectorInfo is a wrapper around CssSelector, which adds sorting with
+ *   reference to the selected element.
+ */
+
+"use strict";
+
+const { Cc, Ci, Cu } = require("chrome");
+const DevToolsUtils = require("devtools/shared/DevToolsUtils");
+const { getRootBindingParent } = require("devtools/shared/layout/utils");
+const nodeConstants = require("devtools/shared/dom-node-constants");
+const {l10n, isContentStylesheet, shortSource, FILTER, STATUS} = require("devtools/shared/inspector/css-logic");
+
+// This should be ok because none of the functions that use this should be used
+// on the worker thread, where Cu is not available.
+loader.lazyRequireGetter(this, "CSS", "CSS");
+
+loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer");
+
+/**
+ * @param {function} isInherited A function that determines if the CSS property
+ *                   is inherited.
+ */
+function CssLogic(isInherited) {
+  // The cache of examined CSS properties.
+  this._isInherited = isInherited;
+  this._propertyInfos = {};
+}
+
+exports.CssLogic = CssLogic;
+
+CssLogic.prototype = {
+  // Both setup by highlight().
+  viewedElement: null,
+  viewedDocument: null,
+
+  // The cache of the known sheets.
+  _sheets: null,
+
+  // Have the sheets been cached?
+  _sheetsCached: false,
+
+  // The total number of rules, in all stylesheets, after filtering.
+  _ruleCount: 0,
+
+  // The computed styles for the viewedElement.
+  _computedStyle: null,
+
+  // Source filter. Only display properties coming from the given source
+  _sourceFilter: FILTER.USER,
+
+  // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
+  // processMatchedSelectors().
+  _passId: 0,
+
+  // Used for tracking matched CssSelector objects.
+  _matchId: 0,
+
+  _matchedRules: null,
+  _matchedSelectors: null,
+
+  // Cached keyframes rules in all stylesheets
+  _keyframesRules: null,
+
+  /**
+   * Reset various properties
+   */
+  reset: function () {
+    this._propertyInfos = {};
+    this._ruleCount = 0;
+    this._sheetIndex = 0;
+    this._sheets = {};
+    this._sheetsCached = false;
+    this._matchedRules = null;
+    this._matchedSelectors = null;
+    this._keyframesRules = [];
+  },
+
+  /**
+   * Focus on a new element - remove the style caches.
+   *
+   * @param {nsIDOMElement} aViewedElement the element the user has highlighted
+   * in the Inspector.
+   */
+  highlight: function (viewedElement) {
+    if (!viewedElement) {
+      this.viewedElement = null;
+      this.viewedDocument = null;
+      this._computedStyle = null;
+      this.reset();
+      return;
+    }
+
+    if (viewedElement === this.viewedElement) {
+      return;
+    }
+
+    this.viewedElement = viewedElement;
+
+    let doc = this.viewedElement.ownerDocument;
+    if (doc != this.viewedDocument) {
+      // New document: clear/rebuild the cache.
+      this.viewedDocument = doc;
+
+      // Hunt down top level stylesheets, and cache them.
+      this._cacheSheets();
+    } else {
+      // Clear cached data in the CssPropertyInfo objects.
+      this._propertyInfos = {};
+    }
+
+    this._matchedRules = null;
+    this._matchedSelectors = null;
+    this._computedStyle = CssLogic.getComputedStyle(this.viewedElement);
+  },
+
+  /**
+   * Get the values of all the computed CSS properties for the highlighted
+   * element.
+   * @returns {object} The computed CSS properties for a selected element
+   */
+  get computedStyle() {
+    return this._computedStyle;
+  },
+
+  /**
+   * Get the source filter.
+   * @returns {string} The source filter being used.
+   */
+  get sourceFilter() {
+    return this._sourceFilter;
+  },
+
+  /**
+   * Source filter. Only display properties coming from the given source (web
+   * address). Note that in order to avoid information overload we DO NOT show
+   * unmatched system rules.
+   * @see FILTER.*
+   */
+  set sourceFilter(value) {
+    let oldValue = this._sourceFilter;
+    this._sourceFilter = value;
+
+    let ruleCount = 0;
+
+    // Update the CssSheet objects.
+    this.forEachSheet(function (sheet) {
+      sheet._sheetAllowed = -1;
+      if (sheet.contentSheet && sheet.sheetAllowed) {
+        ruleCount += sheet.ruleCount;
+      }
+    }, this);
+
+    this._ruleCount = ruleCount;
+
+    // Full update is needed because the this.processMatchedSelectors() method
+    // skips UA stylesheets if the filter does not allow such sheets.
+    let needFullUpdate = (oldValue == FILTER.UA || value == FILTER.UA);
+
+    if (needFullUpdate) {
+      this._matchedRules = null;
+      this._matchedSelectors = null;
+      this._propertyInfos = {};
+    } else {
+      // Update the CssPropertyInfo objects.
+      for (let property in this._propertyInfos) {
+        this._propertyInfos[property].needRefilter = true;
+      }
+    }
+  },
+
+  /**
+   * Return a CssPropertyInfo data structure for the currently viewed element
+   * and the specified CSS property. If there is no currently viewed element we
+   * return an empty object.
+   *
+   * @param {string} property The CSS property to look for.
+   * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
+   * property.
+   */
+  getPropertyInfo: function (property) {
+    if (!this.viewedElement) {
+      return {};
+    }
+
+    let info = this._propertyInfos[property];
+    if (!info) {
+      info = new CssPropertyInfo(this, property, this._isInherited);
+      this._propertyInfos[property] = info;
+    }
+
+    return info;
+  },
+
+  /**
+   * Cache all the stylesheets in the inspected document
+   * @private
+   */
+  _cacheSheets: function () {
+    this._passId++;
+    this.reset();
+
+    // styleSheets isn't an array, but forEach can work on it anyway
+    Array.prototype.forEach.call(this.viewedDocument.styleSheets,
+        this._cacheSheet, this);
+
+    this._sheetsCached = true;
+  },
+
+  /**
+   * Cache a stylesheet if it falls within the requirements: if it's enabled,
+   * and if the @media is allowed. This method also walks through the stylesheet
+   * cssRules to find @imported rules, to cache the stylesheets of those rules
+   * as well. In addition, the @keyframes rules in the stylesheet are cached.
+   *
+   * @private
+   * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache.
+   */
+  _cacheSheet: function (domSheet) {
+    if (domSheet.disabled) {
+      return;
+    }
+
+    // Only work with stylesheets that have their media allowed.
+    if (!this.mediaMatches(domSheet)) {
+      return;
+    }
+
+    // Cache the sheet.
+    let cssSheet = this.getSheet(domSheet, this._sheetIndex++);
+    if (cssSheet._passId != this._passId) {
+      cssSheet._passId = this._passId;
+
+      // Find import and keyframes rules.
+      for (let aDomRule of domSheet.cssRules) {
+        if (aDomRule.type == CSSRule.IMPORT_RULE &&
+            aDomRule.styleSheet &&
+            this.mediaMatches(aDomRule)) {
+          this._cacheSheet(aDomRule.styleSheet);
+        } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) {
+          this._keyframesRules.push(aDomRule);
+        }
+      }
+    }
+  },
+
+  /**
+   * Retrieve the list of stylesheets in the document.
+   *
+   * @return {array} the list of stylesheets in the document.
+   */
+  get sheets() {
+    if (!this._sheetsCached) {
+      this._cacheSheets();
+    }
+
+    let sheets = [];
+    this.forEachSheet(function (sheet) {
+      if (sheet.contentSheet) {
+        sheets.push(sheet);
+      }
+    }, this);
+
+    return sheets;
+  },
+
+  /**
+   * Retrieve the list of keyframes rules in the document.
+   *
+   * @ return {array} the list of keyframes rules in the document.
+   */
+  get keyframesRules() {
+    if (!this._sheetsCached) {
+      this._cacheSheets();
+    }
+    return this._keyframesRules;
+  },
+
+  /**
+   * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
+   * stylesheet is already cached, you get the existing CssSheet object,
+   * otherwise the new CSSStyleSheet object is cached.
+   *
+   * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want.
+   * @param {number} index the index, within the document, of the stylesheet.
+   *
+   * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
+   */
+  getSheet: function (domSheet, index) {
+    let cacheId = "";
+
+    if (domSheet.href) {
+      cacheId = domSheet.href;
+    } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) {
+      cacheId = domSheet.ownerNode.ownerDocument.location;
+    }
+
+    let sheet = null;
+    let sheetFound = false;
+
+    if (cacheId in this._sheets) {
+      for (let i = 0, numSheets = this._sheets[cacheId].length;
+           i < numSheets;
+           i++) {
+        sheet = this._sheets[cacheId][i];
+        if (sheet.domSheet === domSheet) {
+          if (index != -1) {
+            sheet.index = index;
+          }
+          sheetFound = true;
+          break;
+        }
+      }
+    }
+
+    if (!sheetFound) {
+      if (!(cacheId in this._sheets)) {
+        this._sheets[cacheId] = [];
+      }
+
+      sheet = new CssSheet(this, domSheet, index);
+      if (sheet.sheetAllowed && sheet.contentSheet) {
+        this._ruleCount += sheet.ruleCount;
+      }
+
+      this._sheets[cacheId].push(sheet);
+    }
+
+    return sheet;
+  },
+
+  /**
+   * Process each cached stylesheet in the document using your callback.
+   *
+   * @param {function} callback the function you want executed for each of the
+   * CssSheet objects cached.
+   * @param {object} scope the scope you want for the callback function. scope
+   * will be the this object when callback executes.
+   */
+  forEachSheet: function (callback, scope) {
+    for (let cacheId in this._sheets) {
+      let sheets = this._sheets[cacheId];
+      for (let i = 0; i < sheets.length; i++) {
+        // We take this as an opportunity to clean dead sheets
+        try {
+          let sheet = sheets[i];
+          // If accessing domSheet raises an exception, then the style
+          // sheet is a dead object.
+          sheet.domSheet;
+          callback.call(scope, sheet, i, sheets);
+        } catch (e) {
+          sheets.splice(i, 1);
+          i--;
+        }
+      }
+    }
+  },
+
+  /**
+
+  /**
+   * Get the number nsIDOMCSSRule objects in the document, counted from all of
+   * the stylesheets. System sheets are excluded. If a filter is active, this
+   * tells only the number of nsIDOMCSSRule objects inside the selected
+   * CSSStyleSheet.
+   *
+   * WARNING: This only provides an estimate of the rule count, and the results
+   * could change at a later date. Todo remove this
+   *
+   * @return {number} the number of nsIDOMCSSRule (all rules).
+   */
+  get ruleCount() {
+    if (!this._sheetsCached) {
+      this._cacheSheets();
+    }
+
+    return this._ruleCount;
+  },
+
+  /**
+   * Process the CssSelector objects that match the highlighted element and its
+   * parent elements. scope.callback() is executed for each CssSelector
+   * object, being passed the CssSelector object and the match status.
+   *
+   * This method also includes all of the element.style properties, for each
+   * highlighted element parent and for the highlighted element itself.
+   *
+   * Note that the matched selectors are cached, such that next time your
+   * callback is invoked for the cached list of CssSelector objects.
+   *
+   * @param {function} callback the function you want to execute for each of
+   * the matched selectors.
+   * @param {object} scope the scope you want for the callback function. scope
+   * will be the this object when callback executes.
+   */
+  processMatchedSelectors: function (callback, scope) {
+    if (this._matchedSelectors) {
+      if (callback) {
+        this._passId++;
+        this._matchedSelectors.forEach(function (value) {
+          callback.call(scope, value[0], value[1]);
+          value[0].cssRule._passId = this._passId;
+        }, this);
+      }
+      return;
+    }
+
+    if (!this._matchedRules) {
+      this._buildMatchedRules();
+    }
+
+    this._matchedSelectors = [];
+    this._passId++;
+
+    for (let i = 0; i < this._matchedRules.length; i++) {
+      let rule = this._matchedRules[i][0];
+      let status = this._matchedRules[i][1];
+
+      rule.selectors.forEach(function (selector) {
+        if (selector._matchId !== this._matchId &&
+           (selector.elementStyle ||
+            this.selectorMatchesElement(rule.domRule,
+                                        selector.selectorIndex))) {
+          selector._matchId = this._matchId;
+          this._matchedSelectors.push([ selector, status ]);
+          if (callback) {
+            callback.call(scope, selector, status);
+          }
+        }
+      }, this);
+
+      rule._passId = this._passId;
+    }
+  },
+
+  /**
+   * Check if the given selector matches the highlighted element or any of its
+   * parents.
+   *
+   * @private
+   * @param {DOMRule} domRule
+   *        The DOM Rule containing the selector.
+   * @param {Number} idx
+   *        The index of the selector within the DOMRule.
+   * @return {boolean}
+   *         true if the given selector matches the highlighted element or any
+   *         of its parents, otherwise false is returned.
+   */
+  selectorMatchesElement: function (domRule, idx) {
+    let element = this.viewedElement;
+    do {
+      if (domUtils.selectorMatchesElement(element, domRule, idx)) {
+        return true;
+      }
+    } while ((element = element.parentNode) &&
+             element.nodeType === nodeConstants.ELEMENT_NODE);
+
+    return false;
+  },
+
+  /**
+   * Check if the highlighted element or it's parents have matched selectors.
+   *
+   * @param {array} aProperties The list of properties you want to check if they
+   * have matched selectors or not.
+   * @return {object} An object that tells for each property if it has matched
+   * selectors or not. Object keys are property names and values are booleans.
+   */
+  hasMatchedSelectors: function (properties) {
+    if (!this._matchedRules) {
+      this._buildMatchedRules();
+    }
+
+    let result = {};
+
+    this._matchedRules.some(function (value) {
+      let rule = value[0];
+      let status = value[1];
+      properties = properties.filter((property) => {
+        // We just need to find if a rule has this property while it matches
+        // the viewedElement (or its parents).
+        if (rule.getPropertyValue(property) &&
+            (status == STATUS.MATCHED ||
+             (status == STATUS.PARENT_MATCH &&
+              this._isInherited(property)))) {
+          result[property] = true;
+          return false;
+        }
+        // Keep the property for the next rule.
+        return true;
+      });
+      return properties.length == 0;
+    }, this);
+
+    return result;
+  },
+
+  /**
+   * Build the array of matched rules for the currently highlighted element.
+   * The array will hold rules that match the viewedElement and its parents.
+   *
+   * @private
+   */
+  _buildMatchedRules: function () {
+    let domRules;
+    let element = this.viewedElement;
+    let filter = this.sourceFilter;
+    let sheetIndex = 0;
+
+    this._matchId++;
+    this._passId++;
+    this._matchedRules = [];
+
+    if (!element) {
+      return;
+    }
+
+    do {
+      let status = this.viewedElement === element ?
+                   STATUS.MATCHED : STATUS.PARENT_MATCH;
+
+      try {
+        // Handle finding rules on pseudo by reading style rules
+        // on the parent node with proper pseudo arg to getCSSStyleRules.
+        let {bindingElement, pseudo} =
+            CssLogic.getBindingElementAndPseudo(element);
+        domRules = domUtils.getCSSStyleRules(bindingElement, pseudo);
+      } catch (ex) {
+        console.log("CL__buildMatchedRules error: " + ex);
+        continue;
+      }
+
+      // getCSSStyleRules can return null with a shadow DOM element.
+      let numDomRules = domRules ? domRules.Count() : 0;
+      for (let i = 0; i < numDomRules; i++) {
+        let domRule = domRules.GetElementAt(i);
+        if (domRule.type !== CSSRule.STYLE_RULE) {
+          continue;
+        }
+
+        let sheet = this.getSheet(domRule.parentStyleSheet, -1);
+        if (sheet._passId !== this._passId) {
+          sheet.index = sheetIndex++;
+          sheet._passId = this._passId;
+        }
+
+        if (filter === FILTER.USER && !sheet.contentSheet) {
+          continue;
+        }
+
+        let rule = sheet.getRule(domRule);
+        if (rule._passId === this._passId) {
+          continue;
+        }
+
+        rule._matchId = this._matchId;
+        rule._passId = this._passId;
+        this._matchedRules.push([rule, status]);
+      }
+
+      // Add element.style information.
+      if (element.style && element.style.length > 0) {
+        let rule = new CssRule(null, { style: element.style }, element);
+        rule._matchId = this._matchId;
+        rule._passId = this._passId;
+        this._matchedRules.push([rule, status]);
+      }
+    } while ((element = element.parentNode) &&
+              element.nodeType === nodeConstants.ELEMENT_NODE);
+  },
+
+  /**
+   * Tells if the given DOM CSS object matches the current view media.
+   *
+   * @param {object} domObject The DOM CSS object to check.
+   * @return {boolean} True if the DOM CSS object matches the current view
+   * media, or false otherwise.
+   */
+  mediaMatches: function (domObject) {
+    let mediaText = domObject.media.mediaText;
+    return !mediaText ||
+      this.viewedDocument.defaultView.matchMedia(mediaText).matches;
+  },
+};
+
+/**
+ * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
+ * n is the index of this element in its siblings.
+ * <p>A technically more 'correct' output from the no-id case might be:
+ * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
+ * and it is longer.
+ *
+ * @param {nsIDOMElement} element the element for which you want the short name.
+ * @return {string} the string to be displayed for element.
+ */
+CssLogic.getShortName = function (element) {
+  if (!element) {
+    return "null";
+  }
+  if (element.id) {
+    return "#" + element.id;
+  }
+  let priorSiblings = 0;
+  let temp = element;
+  while ((temp = temp.previousElementSibling)) {
+    priorSiblings++;
+  }
+  return element.tagName + "[" + priorSiblings + "]";
+};
+
+/**
+ * Get a string list of selectors for a given DOMRule.
+ *
+ * @param {DOMRule} domRule
+ *        The DOMRule to parse.
+ * @return {Array}
+ *         An array of string selectors.
+ */
+CssLogic.getSelectors = function (domRule) {
+  let selectors = [];
+
+  let len = domUtils.getSelectorCount(domRule);
+  for (let i = 0; i < len; i++) {
+    let text = domUtils.getSelectorText(domRule, i);
+    selectors.push(text);
+  }
+  return selectors;
+};
+
+/**
+ * Given a node, check to see if it is a ::before or ::after element.
+ * If so, return the node that is accessible from within the document
+ * (the parent of the anonymous node), along with which pseudo element
+ * it was.  Otherwise, return the node itself.
+ *
+ * @returns {Object}
+ *            - {DOMNode} node The non-anonymous node
+ *            - {string} pseudo One of ':before', ':after', or null.
+ */
+CssLogic.getBindingElementAndPseudo = function (node) {
+  let bindingElement = node;
+  let pseudo = null;
+  if (node.nodeName == "_moz_generated_content_before") {
+    bindingElement = node.parentNode;
+    pseudo = ":before";
+  } else if (node.nodeName == "_moz_generated_content_after") {
+    bindingElement = node.parentNode;
+    pseudo = ":after";
+  }
+  return {
+    bindingElement: bindingElement,
+    pseudo: pseudo
+  };
+};
+
+/**
+ * Get the computed style on a node.  Automatically handles reading
+ * computed styles on a ::before/::after element by reading on the
+ * parent node with the proper pseudo argument.
+ *
+ * @param {Node}
+ * @returns {CSSStyleDeclaration}
+ */
+CssLogic.getComputedStyle = function (node) {
+  if (!node ||
+      Cu.isDeadWrapper(node) ||
+      node.nodeType !== nodeConstants.ELEMENT_NODE ||
+      !node.ownerDocument ||
+      !node.ownerDocument.defaultView) {
+    return null;
+  }
+
+  let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
+  return node.ownerDocument.defaultView.getComputedStyle(bindingElement,
+                                                         pseudo);
+};
+
+/**
+ * Get a source for a stylesheet, taking into account embedded stylesheets
+ * for which we need to use document.defaultView.location.href rather than
+ * sheet.href
+ *
+ * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
+ * @return {string} the address of the stylesheet.
+ */
+CssLogic.href = function (sheet) {
+  let href = sheet.href;
+  if (!href) {
+    href = sheet.ownerNode.ownerDocument.location;
+  }
+
+  return href;
+};
+
+/**
+ * Find the position of [element] in [nodeList].
+ * @returns an index of the match, or -1 if there is no match
+ */
+function positionInNodeList(element, nodeList) {
+  for (let i = 0; i < nodeList.length; i++) {
+    if (element === nodeList[i]) {
+      return i;
+    }
+  }
+  return -1;
+}
+
+/**
+ * Find a unique CSS selector for a given element
+ * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
+ * and ele.ownerDocument.querySelectorAll(reply).length === 1
+ */
+CssLogic.findCssSelector = function (ele) {
+  ele = getRootBindingParent(ele);
+  let document = ele.ownerDocument;
+  if (!document || !document.contains(ele)) {
+    throw new Error("findCssSelector received element not inside document");
+  }
+
+  // document.querySelectorAll("#id") returns multiple if elements share an ID
+  if (ele.id &&
+      document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) {
+    return "#" + CSS.escape(ele.id);
+  }
+
+  // Inherently unique by tag name
+  let tagName = ele.localName;
+  if (tagName === "html") {
+    return "html";
+  }
+  if (tagName === "head") {
+    return "head";
+  }
+  if (tagName === "body") {
+    return "body";
+  }
+
+  // We might be able to find a unique class name
+  let selector, index, matches;
+  if (ele.classList.length > 0) {
+    for (let i = 0; i < ele.classList.length; i++) {
+      // Is this className unique by itself?
+      selector = "." + CSS.escape(ele.classList.item(i));
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+      // Maybe it's unique with a tag name?
+      selector = tagName + selector;
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+      // Maybe it's unique using a tag name and nth-child
+      index = positionInNodeList(ele, ele.parentNode.children) + 1;
+      selector = selector + ":nth-child(" + index + ")";
+      matches = document.querySelectorAll(selector);
+      if (matches.length === 1) {
+        return selector;
+      }
+    }
+  }
+
+  // Not unique enough yet.  As long as it's not a child of the document,
+  // continue recursing up until it is unique enough.
+  if (ele.parentNode !== document) {
+    index = positionInNodeList(ele, ele.parentNode.children) + 1;
+    selector = CssLogic.findCssSelector(ele.parentNode) + " > " +
+      tagName + ":nth-child(" + index + ")";
+  }
+
+  return selector;
+};
+
+/**
+ * A safe way to access cached bits of information about a stylesheet.
+ *
+ * @constructor
+ * @param {CssLogic} cssLogic pointer to the CssLogic instance working with
+ * this CssSheet object.
+ * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object.
+ * @param {number} index tells the index/position of the stylesheet within the
+ * main document.
+ */
+function CssSheet(cssLogic, domSheet, index) {
+  this._cssLogic = cssLogic;
+  this.domSheet = domSheet;
+  this.index = this.contentSheet ? index : -100 * index;
+
+  // Cache of the sheets href. Cached by the getter.
+  this._href = null;
+  // Short version of href for use in select boxes etc. Cached by getter.
+  this._shortSource = null;
+
+  // null for uncached.
+  this._sheetAllowed = null;
+
+  // Cached CssRules from the given stylesheet.
+  this._rules = {};
+
+  this._ruleCount = -1;
+}
+
+CssSheet.prototype = {
+  _passId: null,
+  _contentSheet: null,
+
+  /**
+   * Tells if the stylesheet is provided by the browser or not.
+   *
+   * @return {boolean} false if this is a browser-provided stylesheet, or true
+   * otherwise.
+   */
+  get contentSheet() {
+    if (this._contentSheet === null) {
+      this._contentSheet = isContentStylesheet(this.domSheet);
+    }
+    return this._contentSheet;
+  },
+
+  /**
+   * Tells if the stylesheet is disabled or not.
+   * @return {boolean} true if this stylesheet is disabled, or false otherwise.
+   */
+  get disabled() {
+    return this.domSheet.disabled;
+  },
+
+  /**
+   * Get a source for a stylesheet, using CssLogic.href
+   *
+   * @return {string} the address of the stylesheet.
+   */
+  get href() {
+    if (this._href) {
+      return this._href;
+    }
+
+    this._href = CssLogic.href(this.domSheet);
+    return this._href;
+  },
+
+  /**
+   * Create a shorthand version of the href of a stylesheet.
+   *
+   * @return {string} the shorthand source of the stylesheet.
+   */
+  get shortSource() {
+    if (this._shortSource) {
+      return this._shortSource;
+    }
+
+    this._shortSource = shortSource(this.domSheet);
+    return this._shortSource;
+  },
+
+  /**
+   * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
+   * false otherwise.
+   */
+  get sheetAllowed() {
+    if (this._sheetAllowed !== null) {
+      return this._sheetAllowed;
+    }
+
+    this._sheetAllowed = true;
+
+    let filter = this._cssLogic.sourceFilter;
+    if (filter === FILTER.USER && !this.contentSheet) {
+      this._sheetAllowed = false;
+    }
+    if (filter !== FILTER.USER && filter !== FILTER.UA) {
+      this._sheetAllowed = (filter === this.href);
+    }
+
+    return this._sheetAllowed;
+  },
+
+  /**
+   * Retrieve the number of rules in this stylesheet.
+   *
+   * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
+   */
+  get ruleCount() {
+    return this._ruleCount > -1 ?
+      this._ruleCount :
+      this.domSheet.cssRules.length;
+  },
+
+  /**
+   * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
+   * cached, such that subsequent retrievals return the same CssRule object for
+   * the same CSSStyleRule object.
+   *
+   * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
+   * CssRule object.
+   * @return {CssRule} the cached CssRule object for the given CSSStyleRule
+   * object.
+   */
+  getRule: function (domRule) {
+    let cacheId = domRule.type + domRule.selectorText;
+
+    let rule = null;
+    let ruleFound = false;
+
+    if (cacheId in this._rules) {
+      for (let i = 0, rulesLen = this._rules[cacheId].length;
+           i < rulesLen;
+           i++) {
+        rule = this._rules[cacheId][i];
+        if (rule.domRule === domRule) {
+          ruleFound = true;
+          break;
+        }
+      }
+    }
+
+    if (!ruleFound) {
+      if (!(cacheId in this._rules)) {
+        this._rules[cacheId] = [];
+      }
+
+      rule = new CssRule(this, domRule);
+      this._rules[cacheId].push(rule);
+    }
+
+    return rule;
+  },
+
+  toString: function () {
+    return "CssSheet[" + this.shortSource + "]";
+  }
+};
+
+/**
+ * Information about a single CSSStyleRule.
+ *
+ * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that
+ * holds the CSSStyleRule. If the rule comes from element.style, set this
+ * argument to null.
+ * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want
+ * to cache data. If the rule comes from element.style, then provide
+ * an object of the form: {style: element.style}.
+ * @param {Element} [element] If the rule comes from element.style, then this
+ * argument must point to the element.
+ * @constructor
+ */
+function CssRule(cssSheet, domRule, element) {
+  this._cssSheet = cssSheet;
+  this.domRule = domRule;
+
+  let parentRule = domRule.parentRule;
+  if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) {
+    this.mediaText = parentRule.media.mediaText;
+  }
+
+  if (this._cssSheet) {
+    // parse domRule.selectorText on call to this.selectors
+    this._selectors = null;
+    this.line = domUtils.getRuleLine(this.domRule);
+    this.source = this._cssSheet.shortSource + ":" + this.line;
+    if (this.mediaText) {
+      this.source += " @media " + this.mediaText;
+    }
+    this.href = this._cssSheet.href;
+    this.contentRule = this._cssSheet.contentSheet;
+  } else if (element) {
+    this._selectors = [ new CssSelector(this, "@element.style", 0) ];
+    this.line = -1;
+    this.source = l10n("rule.sourceElement");
+    this.href = "#";
+    this.contentRule = true;
+    this.sourceElement = element;
+  }
+}
+
+CssRule.prototype = {
+  _passId: null,
+
+  mediaText: "",
+
+  get isMediaRule() {
+    return !!this.mediaText;
+  },
+
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed() {
+    return this._cssSheet ? this._cssSheet.sheetAllowed : true;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex() {
+    return this._cssSheet ? this._cssSheet.index : 0;
+  },
+
+  /**
+   * Retrieve the style property value from the current CSSStyleRule.
+   *
+   * @param {string} property the CSS property name for which you want the
+   * value.
+   * @return {string} the property value.
+   */
+  getPropertyValue: function (property) {
+    return this.domRule.style.getPropertyValue(property);
+  },
+
+  /**
+   * Retrieve the style property priority from the current CSSStyleRule.
+   *
+   * @param {string} property the CSS property name for which you want the
+   * priority.
+   * @return {string} the property priority.
+   */
+  getPropertyPriority: function (property) {
+    return this.domRule.style.getPropertyPriority(property);
+  },
+
+  /**
+   * Retrieve the list of CssSelector objects for each of the parsed selectors
+   * of the current CSSStyleRule.
+   *
+   * @return {array} the array hold the CssSelector objects.
+   */
+  get selectors() {
+    if (this._selectors) {
+      return this._selectors;
+    }
+
+    // Parse the CSSStyleRule.selectorText string.
+    this._selectors = [];
+
+    if (!this.domRule.selectorText) {
+      return this._selectors;
+    }
+
+    let selectors = CssLogic.getSelectors(this.domRule);
+
+    for (let i = 0, len = selectors.length; i < len; i++) {
+      this._selectors.push(new CssSelector(this, selectors[i], i));
+    }
+
+    return this._selectors;
+  },
+
+  toString: function () {
+    return "[CssRule " + this.domRule.selectorText + "]";
+  },
+};
+
+/**
+ * The CSS selector class allows us to document the ranking of various CSS
+ * selectors.
+ *
+ * @constructor
+ * @param {CssRule} cssRule the CssRule instance from where the selector comes.
+ * @param {string} selector The selector that we wish to investigate.
+ * @param {Number} index The index of the selector within it's rule.
+ */
+function CssSelector(cssRule, selector, index) {
+  this.cssRule = cssRule;
+  this.text = selector;
+  this.elementStyle = this.text == "@element.style";
+  this._specificity = null;
+  this.selectorIndex = index;
+}
+
+exports.CssSelector = CssSelector;
+
+CssSelector.prototype = {
+  _matchId: null,
+
+  /**
+   * Retrieve the CssSelector source, which is the source of the CssSheet owning
+   * the selector.
+   *
+   * @return {string} the selector source.
+   */
+  get source() {
+    return this.cssRule.source;
+  },
+
+  /**
+   * Retrieve the CssSelector source element, which is the source of the CssRule
+   * owning the selector. This is only available when the CssSelector comes from
+   * an element.style.
+   *
+   * @return {string} the source element selector.
+   */
+  get sourceElement() {
+    return this.cssRule.sourceElement;
+  },
+
+  /**
+   * Retrieve the address of the CssSelector. This points to the address of the
+   * CssSheet owning this selector.
+   *
+   * @return {string} the address of the CssSelector.
+   */
+  get href() {
+    return this.cssRule.href;
+  },
+
+  /**
+   * Check if the selector comes from a browser-provided stylesheet.
+   *
+   * @return {boolean} true if the selector comes from a content-provided
+   * stylesheet, or false otherwise.
+   */
+  get contentRule() {
+    return this.cssRule.contentRule;
+  },
+
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed() {
+    return this.cssRule.sheetAllowed;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex() {
+    return this.cssRule.sheetIndex;
+  },
+
+  /**
+   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
+   *
+   * @return {number} the line of the parent CSSStyleRule in the parent
+   * stylesheet.
+   */
+  get ruleLine() {
+    return this.cssRule.line;
+  },
+
+  /**
+   * Retrieve specificity information for the current selector.
+   *
+   * @see http://www.w3.org/TR/css3-selectors/#specificity
+   * @see http://www.w3.org/TR/CSS2/selector.html
+   *
+   * @return {Number} The selector's specificity.
+   */
+  get specificity() {
+    if (this.elementStyle) {
+      // We can't ask specificity from DOMUtils as element styles don't provide
+      // CSSStyleRule interface DOMUtils expect. However, specificity of element
+      // style is constant, 1,0,0,0 or 0x01000000, just return the constant
+      // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity
+      return 0x01000000;
+    }
+
+    if (this._specificity) {
+      return this._specificity;
+    }
+
+    this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
+                                                this.selectorIndex);
+
+    return this._specificity;
+  },
+
+  toString: function () {
+    return this.text;
+  },
+};
+
+/**
+ * A cache of information about the matched rules, selectors and values attached
+ * to a CSS property, for the highlighted element.
+ *
+ * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
+ * method. This are invoked when the PropertyView tries to access the
+ * .matchedSelectors array.
+ * Results are cached, for later reuse.
+ *
+ * @param {CssLogic} cssLogic Reference to the parent CssLogic instance
+ * @param {string} property The CSS property we are gathering information for
+ * @param {function} isInherited A function that determines if the CSS property
+ *                   is inherited.
+ * @constructor
+ */
+function CssPropertyInfo(cssLogic, property, isInherited) {
+  this._cssLogic = cssLogic;
+  this.property = property;
+  this._value = "";
+  this._isInherited = isInherited;
+
+  // An array holding CssSelectorInfo objects for each of the matched selectors
+  // that are inside a CSS rule. Only rules that hold the this.property are
+  // counted. This includes rules that come from filtered stylesheets (those
+  // that have sheetAllowed = false).
+  this._matchedSelectors = null;
+}
+
+CssPropertyInfo.prototype = {
+  /**
+   * Retrieve the computed style value for the current property, for the
+   * highlighted element.
+   *
+   * @return {string} the computed style value for the current property, for the
+   * highlighted element.
+   */
+  get value() {
+    if (!this._value && this._cssLogic.computedStyle) {
+      try {
+        this._value =
+          this._cssLogic.computedStyle.getPropertyValue(this.property);
+      } catch (ex) {
+        console.log("Error reading computed style for " + this.property);
+        console.log(ex);
+      }
+    }
+    return this._value;
+  },
+
+  /**
+   * Retrieve the array holding CssSelectorInfo objects for each of the matched
+   * selectors, from each of the matched rules. Only selectors coming from
+   * allowed stylesheets are included in the array.
+   *
+   * @return {array} the list of CssSelectorInfo objects of selectors that match
+   * the highlighted element and its parents.
+   */
+  get matchedSelectors() {
+    if (!this._matchedSelectors) {
+      this._findMatchedSelectors();
+    } else if (this.needRefilter) {
+      this._refilterSelectors();
+    }
+
+    return this._matchedSelectors;
+  },
+
+  /**
+   * Find the selectors that match the highlighted element and its parents.
+   * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
+   * passing in a reference to CssPropertyInfo._processMatchedSelector() to
+   * create CssSelectorInfo objects, which we then sort
+   * @private
+   */
+  _findMatchedSelectors: function () {
+    this._matchedSelectors = [];
+    this.needRefilter = false;
+
+    this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
+
+    // Sort the selectors by how well they match the given element.
+    this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) {
+      if (selectorInfo1.status > selectorInfo2.status) {
+        return -1;
+      } else if (selectorInfo2.status > selectorInfo1.status) {
+        return 1;
+      }
+      return selectorInfo1.compareTo(selectorInfo2);
+    });
+
+    // Now we know which of the matches is best, we can mark it BEST_MATCH.
+    if (this._matchedSelectors.length > 0 &&
+        this._matchedSelectors[0].status > STATUS.UNMATCHED) {
+      this._matchedSelectors[0].status = STATUS.BEST;
+    }
+  },
+
+  /**
+   * Process a matched CssSelector object.
+   *
+   * @private
+   * @param {CssSelector} selector the matched CssSelector object.
+   * @param {STATUS} status the CssSelector match status.
+   */
+  _processMatchedSelector: function (selector, status) {
+    let cssRule = selector.cssRule;
+    let value = cssRule.getPropertyValue(this.property);
+    if (value &&
+        (status == STATUS.MATCHED ||
+         (status == STATUS.PARENT_MATCH &&
+          this._isInherited(this.property)))) {
+      let selectorInfo = new CssSelectorInfo(selector, this.property, value,
+          status);
+      this._matchedSelectors.push(selectorInfo);
+    }
+  },
+
+  /**
+   * Refilter the matched selectors array when the CssLogic.sourceFilter
+   * changes. This allows for quick filter changes.
+   * @private
+   */
+  _refilterSelectors: function () {
+    let passId = ++this._cssLogic._passId;
+    let ruleCount = 0;
+
+    let iterator = function (selectorInfo) {
+      let cssRule = selectorInfo.selector.cssRule;
+      if (cssRule._passId != passId) {
+        if (cssRule.sheetAllowed) {
+          ruleCount++;
+        }
+        cssRule._passId = passId;
+      }
+    };
+
+    if (this._matchedSelectors) {
+      this._matchedSelectors.forEach(iterator);
+    }
+
+    this.needRefilter = false;
+  },
+
+  toString: function () {
+    return "CssPropertyInfo[" + this.property + "]";
+  },
+};
+
+/**
+ * A class that holds information about a given CssSelector object.
+ *
+ * Instances of this class are given to CssHtmlTree in the array of matched
+ * selectors. Each such object represents a displayable row in the PropertyView
+ * objects. The information given by this object blends data coming from the
+ * CssSheet, CssRule and from the CssSelector that own this object.
+ *
+ * @param {CssSelector} selector The CssSelector object for which to
+ *        present information.
+ * @param {string} property The property for which information should
+ *        be retrieved.
+ * @param {string} value The property value from the CssRule that owns
+ *        the selector.
+ * @param {STATUS} status The selector match status.
+ * @constructor
+ */
+function CssSelectorInfo(selector, property, value, status) {
+  this.selector = selector;
+  this.property = property;
+  this.status = status;
+  this.value = value;
+  let priority = this.selector.cssRule.getPropertyPriority(this.property);
+  this.important = (priority === "important");
+}
+
+CssSelectorInfo.prototype = {
+  /**
+   * Retrieve the CssSelector source, which is the source of the CssSheet owning
+   * the selector.
+   *
+   * @return {string} the selector source.
+   */
+  get source() {
+    return this.selector.source;
+  },
+
+  /**
+   * Retrieve the CssSelector source element, which is the source of the CssRule
+   * owning the selector. This is only available when the CssSelector comes from
+   * an element.style.
+   *
+   * @return {string} the source element selector.
+   */
+  get sourceElement() {
+    return this.selector.sourceElement;
+  },
+
+  /**
+   * Retrieve the address of the CssSelector. This points to the address of the
+   * CssSheet owning this selector.
+   *
+   * @return {string} the address of the CssSelector.
+   */
+  get href() {
+    return this.selector.href;
+  },
+
+  /**
+   * Check if the CssSelector comes from element.style or not.
+   *
+   * @return {boolean} true if the CssSelector comes from element.style, or
+   * false otherwise.
+   */
+  get elementStyle() {
+    return this.selector.elementStyle;
+  },
+
+  /**
+   * Retrieve specificity information for the current selector.
+   *
+   * @return {object} an object holding specificity information for the current
+   * selector.
+   */
+  get specificity() {
+    return this.selector.specificity;
+  },
+
+  /**
+   * Retrieve the parent stylesheet index/position in the viewed document.
+   *
+   * @return {number} the parent stylesheet index/position in the viewed
+   * document.
+   */
+  get sheetIndex() {
+    return this.selector.sheetIndex;
+  },
+
+  /**
+   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
+   *
+   * @return {boolean} true if the parent stylesheet is allowed by the current
+   * sourceFilter, or false otherwise.
+   */
+  get sheetAllowed() {
+    return this.selector.sheetAllowed;
+  },
+
+  /**
+   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
+   *
+   * @return {number} the line of the parent CSSStyleRule in the parent
+   * stylesheet.
+   */
+  get ruleLine() {
+    return this.selector.ruleLine;
+  },
+
+  /**
+   * Check if the selector comes from a browser-provided stylesheet.
+   *
+   * @return {boolean} true if the selector comes from a browser-provided
+   * stylesheet, or false otherwise.
+   */
+  get contentRule() {
+    return this.selector.contentRule;
+  },
+
+  /**
+   * Compare the current CssSelectorInfo instance to another instance, based on
+   * specificity information.
+   *
+   * @param {CssSelectorInfo} that The instance to compare ourselves against.
+   * @return number -1, 0, 1 depending on how that compares with this.
+   */
+  compareTo: function (that) {
+    if (!this.contentRule && that.contentRule) {
+      return 1;
+    }
+    if (this.contentRule && !that.contentRule) {
+      return -1;
+    }
+
+    if (this.elementStyle && !that.elementStyle) {
+      if (!this.important && that.important) {
+        return 1;
+      }
+      return -1;
+    }
+
+    if (!this.elementStyle && that.elementStyle) {
+      if (this.important && !that.important) {
+        return -1;
+      }
+      return 1;
+    }
+
+    if (this.important && !that.important) {
+      return -1;
+    }
+    if (that.important && !this.important) {
+      return 1;
+    }
+
+    if (this.specificity > that.specificity) {
+      return -1;
+    }
+    if (that.specificity > this.specificity) {
+      return 1;
+    }
+
+    if (this.sheetIndex > that.sheetIndex) {
+      return -1;
+    }
+    if (that.sheetIndex > this.sheetIndex) {
+      return 1;
+    }
+
+    if (this.ruleLine > that.ruleLine) {
+      return -1;
+    }
+    if (that.ruleLine > this.ruleLine) {
+      return 1;
+    }
+
+    return 0;
+  },
+
+  toString: function () {
+    return this.selector + " -> " + this.value;
+  },
+};
+
+DevToolsUtils.defineLazyGetter(this, "domUtils", function () {
+  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
+});
--- a/devtools/server/docs/protocol.js.md
+++ b/devtools/server/docs/protocol.js.md
@@ -644,29 +644,8 @@ This will require some matching work on 
         if (this._temporaryParent) {
             this._temporaryParent.destroy();
             delete this._temporaryParent;
         }
         return this._clearTemporaryChildren();
     }, {
         impl: "_clearTemporaryChildren"
     })
-
-Telemetry
----------
-
-You can specify a telemetry probe id in your method spec:
-
-    // spec:
-    methods: {
-      echo: {
-        request: { str: Arg(0) },
-        response: { str: RetVal() },
-        telemetry: "ECHO"
-      }
-    }
-
-    // implementation:
-    echo: function (str) {
-      return str;
-    }
-
-... and the time to execute that request will be included as a telemetry probe.
--- a/devtools/server/moz.build
+++ b/devtools/server/moz.build
@@ -27,13 +27,14 @@ SOURCES += [
 ]
 
 FINAL_LIBRARY = 'xul'
 
 DevToolsModules(
     'child.js',
     'content-globals.js',
     'content-server.jsm',
+    'css-logic.js',
     'main.js',
     'primitive.js',
     'service-worker-child.js',
     'worker.js'
 )
--- a/devtools/server/tests/mochitest/chrome.ini
+++ b/devtools/server/tests/mochitest/chrome.ini
@@ -24,17 +24,16 @@ support-files =
   setup-in-parent.js
 
 [test_animation_actor-lifetime.html]
 [test_connection-manager.html]
 skip-if = buildapp == 'mulet'
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
 [test_css-logic.html]
-[test_css-logic-inheritance.html]
 [test_css-logic-media-queries.html]
 [test_css-logic-specificity.html]
 [test_css-properties.html]
 [test_Debugger.Source.prototype.introductionScript.html]
 [test_Debugger.Source.prototype.introductionType.html]
 [test_Debugger.Source.prototype.element.html]
 [test_Debugger.Script.prototype.global.html]
 [test_device.html]
deleted file mode 100644
--- a/devtools/server/tests/mochitest/test_css-logic-inheritance.html
+++ /dev/null
@@ -1,46 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-Test that css-logic handles inherited properties correctly
--->
-<head>
-  <meta charset="utf-8">
-  <title>Test css-logic inheritance</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">
-</head>
-<body>
-  <div style="margin-left:10px; font-size: 5px">
-    <div id="innerdiv">Inner div</div>
-  </div>
-  <script type="application/javascript;version=1.8">
-
-  window.onload = function() {
-    var { classes: Cc, utils: Cu, interfaces: Ci } = Components;
-    const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
-      .getService(Ci.inIDOMUtils);
-
-    const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-    var Services = require("Services");
-    const {CssLogic} = require("devtools/shared/inspector/css-logic");
-
-
-    SimpleTest.waitForExplicitFinish();
-
-    let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
-    cssLogic.highlight(document.getElementById("innerdiv"));
-
-    let marginProp = cssLogic.getPropertyInfo("margin-left");
-    is(marginProp.matchedRuleCount, 0,
-      "margin-left should not be included in matched selectors.");
-
-    let fontSizeProp = cssLogic.getPropertyInfo("font-size");
-    is(fontSizeProp.matchedRuleCount, 1,
-      "font-size should be included in matched selectors.");
-
-    SimpleTest.finish();
-  }
-
-  </script>
-</body>
-</html>
--- a/devtools/server/tests/mochitest/test_css-logic-media-queries.html
+++ b/devtools/server/tests/mochitest/test_css-logic-media-queries.html
@@ -28,17 +28,17 @@ Test that css-logic handles media-querie
 
   window.onload = function() {
     var { classes: Cc, utils: Cu, interfaces: Ci } = Components;
     const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
       .getService(Ci.inIDOMUtils);
 
     var {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
     var Services = require("Services");
-    const {CssLogic} = require("devtools/shared/inspector/css-logic");
+    const {CssLogic} = require("devtools/server/css-logic");
 
     SimpleTest.waitForExplicitFinish();
 
     let div = document.querySelector("div");
     let cssLogic = new CssLogic(DOMUtils.isInheritedProperty);
     cssLogic.highlight(div);
     cssLogic.processMatchedSelectors();
 
--- a/devtools/server/tests/mochitest/test_css-logic-specificity.html
+++ b/devtools/server/tests/mochitest/test_css-logic-specificity.html
@@ -10,17 +10,17 @@ Test that css-logic calculates CSS speci
 </head>
 <body style="background:blue;">
   <script type="application/javascript;version=1.8">
 
   window.onload = function() {
     var {utils: Cu, classes: Cc, interfaces: Ci} = Components;
 
     const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
-    const {CssLogic, CssSelector} = require("devtools/shared/inspector/css-logic");
+    const {CssLogic, CssSelector} = require("devtools/server/css-logic");
     const DOMUtils = Cc["@mozilla.org/inspector/dom-utils;1"]
                        .getService(Ci.inIDOMUtils);
 
     const TEST_DATA = [
       {text: "*", expected: 0},
       {text: "LI", expected: 1},
       {text: "UL LI", expected: 2},
       {text: "UL OL + LI", expected: 3},
--- a/devtools/server/tests/mochitest/test_css-logic.html
+++ b/devtools/server/tests/mochitest/test_css-logic.html
@@ -6,17 +6,17 @@ https://bugzilla.mozilla.org/show_bug.cg
 <head>
   <meta charset="utf-8">
   <title>Test for Bug </title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {CssLogic} = require("devtools/server/css-logic");
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
   runNextTest();
 }
 
 addTest(function findAllCssSelectors() {
   var nodes = document.querySelectorAll('*');
--- a/devtools/server/tests/mochitest/test_styles-matched.html
+++ b/devtools/server/tests/mochitest/test_styles-matched.html
@@ -7,17 +7,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <meta charset="utf-8">
   <title>Test for Bug </title>
 
   <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
   <script type="application/javascript;version=1.8" src="inspector-helpers.js"></script>
   <script type="application/javascript;version=1.8">
 const inspector = require("devtools/server/actors/inspector");
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const CssLogic = require("devtools/shared/inspector/css-logic");
 
 window.onload = function() {
   SimpleTest.waitForExplicitFinish();
   runNextTest();
 }
 
 var gWalker = null;
 var gStyles = null;
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -52,17 +52,16 @@ const childSpec = protocol.generateActor
       detail: Arg(0, "array:childActor#detail2"),
     }
   },
 
   methods: {
     echo: {
       request: { str: Arg(0) },
       response: { str: RetVal("string") },
-      telemetry: "ECHO"
     },
     getDetail1: {
       // This also exercises return-value-as-packet.
       response: RetVal("childActor#detail1"),
     },
     getDetail2: {
       // This also exercises return-value-as-packet.
       response: RetVal("childActor#detail2"),
--- a/devtools/shared/client/main.js
+++ b/devtools/shared/client/main.js
@@ -229,44 +229,32 @@ const DebuggerClient = exports.DebuggerC
 };
 
 /**
  * A declarative helper for defining methods that send requests to the server.
  *
  * @param aPacketSkeleton
  *        The form of the packet to send. Can specify fields to be filled from
  *        the parameters by using the |args| function.
- * @param telemetry
- *        The unique suffix of the telemetry histogram id.
  * @param before
  *        The function to call before sending the packet. Is passed the packet,
  *        and the return value is used as the new packet. The |this| context is
  *        the instance of the client object we are defining a method for.
  * @param after
  *        The function to call after the response is received. It is passed the
  *        response, and the return value is considered the new response that
  *        will be passed to the callback. The |this| context is the instance of
  *        the client object we are defining a method for.
  * @return Request
  *         The `Request` object that is a Promise object and resolves once
  *         we receive the response. (See request method for more details)
  */
 DebuggerClient.requester = function (aPacketSkeleton, config = {}) {
-  let { telemetry, before, after } = config;
+  let { before, after } = config;
   return DevToolsUtils.makeInfallible(function (...args) {
-    let histogram, startTime;
-    if (telemetry) {
-      let transportType = this._transport.onOutputStreamReady === undefined
-        ? "LOCAL_"
-        : "REMOTE_";
-      let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
-        + transportType + telemetry + "_MS";
-      histogram = Services.telemetry.getHistogramById(histogramId);
-      startTime = +new Date;
-    }
     let outgoingPacket = {
       to: aPacketSkeleton.to || this.actor
     };
 
     let maxPosition = -1;
     for (let k of Object.keys(aPacketSkeleton)) {
       if (aPacketSkeleton[k] instanceof DebuggerClient.Argument) {
         let { position } = aPacketSkeleton[k];
@@ -290,20 +278,16 @@ DebuggerClient.requester = function (aPa
         }
       }
 
       // The callback is always the last parameter.
       let thisCallback = args[maxPosition + 1];
       if (thisCallback) {
         thisCallback(aResponse);
       }
-
-      if (histogram) {
-        histogram.add(+new Date - startTime);
-      }
     }, "DebuggerClient.requester request callback"));
   }, "DebuggerClient.requester");
 };
 
 function args(aPos) {
   return new DebuggerClient.Argument(aPos);
 }
 
@@ -644,18 +628,16 @@ DebuggerClient.prototype = {
    *        The actor ID to send the request to.
    * @param aOnResponse function
    *        If specified, will be called with the response packet when
    *        debugging server responds.
    */
   release: DebuggerClient.requester({
     to: args(0),
     type: "release"
-  }, {
-    telemetry: "RELEASE"
   }),
 
   /**
    * Send a request to the debugging server.
    *
    * @param aRequest object
    *        A JSON packet to send to the debugging server.
    * @param aOnResponse function
@@ -1309,17 +1291,16 @@ TabClient.prototype = {
         this.thread.detach();
       }
       return aPacket;
     },
     after: function (aResponse) {
       this.client.unregisterClient(this);
       return aResponse;
     },
-    telemetry: "TABDETACH"
   }),
 
   /**
    * Bring the window to the front.
    */
   focus: DebuggerClient.requester({
     type: "focus"
   }, {}),
@@ -1332,52 +1313,44 @@ TabClient.prototype = {
    *        this reload should skip the cache
    */
   reload: function (options = { force: false }) {
     return this._reload(options);
   },
   _reload: DebuggerClient.requester({
     type: "reload",
     options: args(0)
-  }, {
-    telemetry: "RELOAD"
   }),
 
   /**
    * Navigate to another URL.
    *
    * @param string url
    *        The URL to navigate to.
    */
   navigateTo: DebuggerClient.requester({
     type: "navigateTo",
     url: args(0)
-  }, {
-    telemetry: "NAVIGATETO"
   }),
 
   /**
    * Reconfigure the tab actor.
    *
    * @param object aOptions
    *        A dictionary object of the new options to use in the tab actor.
    * @param function aOnResponse
    *        Called with the response packet.
    */
   reconfigure: DebuggerClient.requester({
     type: "reconfigure",
     options: args(0)
-  }, {
-    telemetry: "RECONFIGURETAB"
   }),
 
   listWorkers: DebuggerClient.requester({
     type: "listWorkers"
-  }, {
-    telemetry: "LISTWORKERS"
   }),
 
   attachWorker: function (aWorkerActor, aOnResponse) {
     this.client.attachWorker(aWorkerActor, aOnResponse);
   },
 
   /**
    * Resolve a location ({ url, line, column }) to its current
@@ -1432,18 +1405,16 @@ WorkerClient.prototype = {
   detach: DebuggerClient.requester({ type: "detach" }, {
     after: function (aResponse) {
       if (this.thread) {
         this.client.unregisterClient(this.thread);
       }
       this.client.unregisterClient(this);
       return aResponse;
     },
-
-    telemetry: "WORKERDETACH"
   }),
 
   attachThread: function (aOptions = {}, aOnResponse = noop) {
     if (this.thread) {
       let response = [{
         type: "connected",
         threadActor: this.thread._actor,
         consoleActor: this.consoleActor,
@@ -1525,17 +1496,16 @@ AddonClient.prototype = {
   }, {
     after: function (aResponse) {
       if (this._client.activeAddon === this) {
         this._client.activeAddon = null;
       }
       this._client.unregisterClient(this);
       return aResponse;
     },
-    telemetry: "ADDONDETACH"
   })
 };
 
 /**
  * A RootClient object represents a root actor on the server. Each
  * DebuggerClient keeps a RootClient instance representing the root actor
  * for the initial connection; DebuggerClient's 'listTabs' and
  * 'listChildProcesses' methods forward to that root actor.
@@ -1566,54 +1536,51 @@ RootClient.prototype = {
   constructor: RootClient,
 
   /**
    * List the open tabs.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listTabs: DebuggerClient.requester({ type: "listTabs" },
-                                     { telemetry: "LISTTABS" }),
+  listTabs: DebuggerClient.requester({ type: "listTabs" }),
 
   /**
    * List the installed addons.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listAddons: DebuggerClient.requester({ type: "listAddons" },
-                                       { telemetry: "LISTADDONS" }),
+  listAddons: DebuggerClient.requester({ type: "listAddons" }),
 
   /**
    * List the registered workers.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listWorkers: DebuggerClient.requester({ type: "listWorkers" },
-                                        { telemetry: "LISTWORKERS" }),
+  listWorkers: DebuggerClient.requester({ type: "listWorkers" }),
 
   /**
    * List the registered service workers.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listServiceWorkerRegistrations: DebuggerClient.requester({ type: "listServiceWorkerRegistrations" },
-                                                           { telemetry: "LISTSERVICEWORKERREGISTRATIONS" }),
+  listServiceWorkerRegistrations: DebuggerClient.requester({
+    type: "listServiceWorkerRegistrations"
+  }),
 
   /**
    * List the running processes.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  listProcesses: DebuggerClient.requester({ type: "listProcesses" },
-                                          { telemetry: "LISTPROCESSES" }),
+  listProcesses: DebuggerClient.requester({ type: "listProcesses" }),
 
   /**
    * Fetch the TabActor for the currently selected tab, or for a specific
    * tab given as first parameter.
    *
    * @param [optional] object aFilter
    *        A dictionary object with following optional attributes:
    *         - outerWindowID: used to match tabs in parent process
@@ -1659,18 +1626,17 @@ RootClient.prototype = {
   },
 
   /**
    * Description of protocol's actors and methods.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
-  protocolDescription: DebuggerClient.requester({ type: "protocolDescription" },
-                                                 { telemetry: "PROTOCOLDESCRIPTION" }),
+  protocolDescription: DebuggerClient.requester({ type: "protocolDescription" }),
 
   /*
    * Methods constructed by DebuggerClient.requester require these forwards
    * on their 'this'.
    */
   get _transport() { return this._client._transport; },
   get request() { return this._client.request; }
 };
@@ -1752,32 +1718,29 @@ ThreadClient.prototype = {
     },
     after: function (aResponse) {
       if (aResponse.error) {
         // There was an error resuming, back to paused state.
         this._state = "paused";
       }
       return aResponse;
     },
-    telemetry: "RESUME"
   }),
 
   /**
    * Reconfigure the thread actor.
    *
    * @param object aOptions
    *        A dictionary object of the new options to use in the thread actor.
    * @param function aOnResponse
    *        Called with the response packet.
    */
   reconfigure: DebuggerClient.requester({
     type: "reconfigure",
     options: args(0)
-  }, {
-    telemetry: "RECONFIGURETHREAD"
   }),
 
   /**
    * Resume a paused thread.
    */
   resume: function (aOnResponse) {
     return this._doResume(null, aOnResponse);
   },
@@ -1846,18 +1809,16 @@ ThreadClient.prototype = {
    * Interrupt a running thread.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   _doInterrupt: DebuggerClient.requester({
     type: "interrupt",
     when: args(0)
-  }, {
-    telemetry: "INTERRUPT"
   }),
 
   /**
    * Enable or disable pausing when an exception is thrown.
    *
    * @param boolean aFlag
    *        Enables pausing if true, disables otherwise.
    * @param function aOnResponse
@@ -1942,86 +1903,76 @@ ThreadClient.prototype = {
     },
     after: function (aResponse) {
       if (aResponse.error) {
         // There was an error resuming, back to paused state.
         this._state = "paused";
       }
       return aResponse;
     },
-    telemetry: "CLIENTEVALUATE"
   }),
 
   /**
    * Detach from the thread actor.
    *
    * @param function aOnResponse
    *        Called with the response packet.
    */
   detach: DebuggerClient.requester({
     type: "detach"
   }, {
     after: function (aResponse) {
       this.client.unregisterClient(this);
       this._parent.thread = null;
       return aResponse;
     },
-    telemetry: "THREADDETACH"
   }),
 
   /**
    * Release multiple thread-lifetime object actors. If any pause-lifetime
    * actors are included in the request, a |notReleasable| error will return,
    * but all the thread-lifetime ones will have been released.
    *
    * @param array actors
    *        An array with actor IDs to release.
    */
   releaseMany: DebuggerClient.requester({
     type: "releaseMany",
     actors: args(0),
-  }, {
-    telemetry: "RELEASEMANY"
   }),
 
   /**
    * Promote multiple pause-lifetime object actors to thread-lifetime ones.
    *
    * @param array actors
    *        An array with actor IDs to promote.
    */
   threadGrips: DebuggerClient.requester({
     type: "threadGrips",
     actors: args(0)
-  }, {
-    telemetry: "THREADGRIPS"
   }),
 
   /**
    * Return the event listeners defined on the page.
    *
    * @param aOnResponse Function
    *        Called with the thread's response.
    */
   eventListeners: DebuggerClient.requester({
     type: "eventListeners"
-  }, {
-    telemetry: "EVENTLISTENERS"
   }),
 
   /**
    * Request the loaded sources for the current thread.
    *
    * @param aOnResponse Function
    *        Called with the thread's response.
    */
   getSources: DebuggerClient.requester({
     type: "sources"
-  }, {
-    telemetry: "SOURCES"
   }),
 
   /**
    * Clear the thread's source script cache. A scriptscleared event
    * will be sent.
    */
   _clearScripts: function () {
     if (Object.keys(this._scriptCache).length > 0) {
@@ -2041,18 +1992,16 @@ ThreadClient.prototype = {
    *        frames.
    * @param aOnResponse function
    *        Called with the thread's response.
    */
   getFrames: DebuggerClient.requester({
     type: "frames",
     start: args(0),
     count: args(1)
-  }, {
-    telemetry: "FRAMES"
   }),
 
   /**
    * An array of cached frames. Clients can observe the framesadded and
    * framescleared event to keep up to date on changes to this cache,
    * and can fill it using the fillFrames method.
    */
   get cachedFrames() { return this._frameCache; },
@@ -2259,18 +2208,16 @@ ThreadClient.prototype = {
    * @param aOnResponse function
    *        Called with the request's response.
    * @param actors [string]
    *        List of actor ID of the queried objects.
    */
   getPrototypesAndProperties: DebuggerClient.requester({
     type: "prototypesAndProperties",
     actors: args(0)
-  }, {
-    telemetry: "PROTOTYPESANDPROPERTIES"
   }),
 
   events: ["newSource"]
 };
 
 eventSource(ThreadClient.prototype);
 
 /**
@@ -2305,17 +2252,16 @@ TraceClient.prototype = {
    */
   detach: DebuggerClient.requester({
     type: "detach"
   }, {
     after: function (aResponse) {
       this._client.unregisterClient(this);
       return aResponse;
     },
-    telemetry: "TRACERDETACH"
   }),
 
   /**
    * Start a new trace.
    *
    * @param aTrace [string]
    *        An array of trace types to be recorded by the new trace.
    *
@@ -2338,17 +2284,16 @@ TraceClient.prototype = {
       if (!this.tracing) {
         this._waitingPackets.clear();
         this._expectedPacket = 0;
       }
       this._activeTraces.add(aResponse.name);
 
       return aResponse;
     },
-    telemetry: "STARTTRACE"
   }),
 
   /**
    * End a trace. If a name is provided, stop the named
    * trace. Otherwise, stop the most recently started trace.
    *
    * @param aName string
    *        The name of the trace to stop.
@@ -2364,17 +2309,16 @@ TraceClient.prototype = {
       if (aResponse.error) {
         return aResponse;
       }
 
       this._activeTraces.delete(aResponse.name);
 
       return aResponse;
     },
-    telemetry: "STOPTRACE"
   })
 };
 
 /**
  * Grip clients are used to retrieve information about the relevant object.
  *
  * @param aClient DebuggerClient
  *        The debugger client parent.
@@ -2428,40 +2372,35 @@ ObjectClient.prototype = {
     type: "parameterNames"
   }, {
     before: function (aPacket) {
       if (this._grip["class"] !== "Function") {
         throw new Error("getParameterNames is only valid for function grips.");
       }
       return aPacket;
     },
-    telemetry: "PARAMETERNAMES"
   }),
 
   /**
    * Request the names of the properties defined on the object and not its
    * prototype.
    *
    * @param aOnResponse function Called with the request's response.
    */
   getOwnPropertyNames: DebuggerClient.requester({
     type: "ownPropertyNames"
-  }, {
-    telemetry: "OWNPROPERTYNAMES"
   }),
 
   /**
    * Request the prototype and own properties of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
   getPrototypeAndProperties: DebuggerClient.requester({
     type: "prototypeAndProperties"
-  }, {
-    telemetry: "PROTOTYPEANDPROPERTIES"
   }),
 
   /**
    * Request a PropertyIteratorClient instance to ease listing
    * properties for this object.
    *
    * @param options Object
    *        A dictionary object with various boolean attributes:
@@ -2482,17 +2421,16 @@ ObjectClient.prototype = {
     options: args(0)
   }, {
     after: function (aResponse) {
       if (aResponse.iterator) {
         return { iterator: new PropertyIteratorClient(this._client, aResponse.iterator) };
       }
       return aResponse;
     },
-    telemetry: "ENUMPROPERTIES"
   }),
 
   /**
    * Request a PropertyIteratorClient instance to enumerate entries in a
    * Map/Set-like object.
    *
    * @param aOnResponse function Called with the request's response.
    */
@@ -2519,57 +2457,50 @@ ObjectClient.prototype = {
    * Request the property descriptor of the object's specified property.
    *
    * @param aName string The name of the requested property.
    * @param aOnResponse function Called with the request's response.
    */
   getProperty: DebuggerClient.requester({
     type: "property",
     name: args(0)
-  }, {
-    telemetry: "PROPERTY"
   }),
 
   /**
    * Request the prototype of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
   getPrototype: DebuggerClient.requester({
     type: "prototype"
-  }, {
-    telemetry: "PROTOTYPE"
   }),
 
   /**
    * Request the display string of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
   getDisplayString: DebuggerClient.requester({
     type: "displayString"
-  }, {
-    telemetry: "DISPLAYSTRING"
   }),
 
   /**
    * Request the scope of the object.
    *
    * @param aOnResponse function Called with the request's response.
    */
   getScope: DebuggerClient.requester({
     type: "scope"
   }, {
     before: function (aPacket) {
       if (this._grip.class !== "Function") {
         throw new Error("scope is only valid for function grips.");
       }
       return aPacket;
     },
-    telemetry: "SCOPE"
   }),
 
   /**
    * Request the promises directly depending on the current promise.
    */
   getDependentPromises: DebuggerClient.requester({
     type: "dependentPromises"
   }, {
@@ -2728,18 +2659,16 @@ LongStringClient.prototype = {
    *        The ending index.
    * @param aCallback Function
    *        The function called when we receive the substring.
    */
   substring: DebuggerClient.requester({
     type: "substring",
     start: args(0),
     end: args(1)
-  }, {
-    telemetry: "SUBSTRING"
   }),
 };
 
 /**
  * A SourceClient provides a way to access the source text of a script.
  *
  * @param aClient ThreadClient
  *        The thread client parent.
@@ -2778,17 +2707,16 @@ SourceClient.prototype = {
    * Black box this SourceClient's source.
    *
    * @param aCallback Function
    *        The callback function called when we receive the response from the server.
    */
   blackBox: DebuggerClient.requester({
     type: "blackbox"
   }, {
-    telemetry: "BLACKBOX",
     after: function (aResponse) {
       if (!aResponse.error) {
         this._isBlackBoxed = true;
         if (this._activeThread) {
           this._activeThread.emit("blackboxchange", this);
         }
       }
       return aResponse;
@@ -2799,17 +2727,16 @@ SourceClient.prototype = {
    * Un-black box this SourceClient's source.
    *
    * @param aCallback Function
    *        The callback function called when we receive the response from the server.
    */
   unblackBox: DebuggerClient.requester({
     type: "unblackbox"
   }, {
-    telemetry: "UNBLACKBOX",
     after: function (aResponse) {
       if (!aResponse.error) {
         this._isBlackBoxed = false;
         if (this._activeThread) {
           this._activeThread.emit("blackboxchange", this);
         }
       }
       return aResponse;
@@ -3023,18 +2950,16 @@ BreakpointClient.prototype = {
   get actor() { return this._actor; },
   get _transport() { return this._client._transport; },
 
   /**
    * Remove the breakpoint from the server.
    */
   remove: DebuggerClient.requester({
     type: "delete"
-  }, {
-    telemetry: "DELETE"
   }),
 
   /**
    * Determines if this breakpoint has a condition
    */
   hasCondition: function () {
     let root = this._client.mainRoot;
     // XXX bug 990137: We will remove support for client-side handling of
@@ -3131,26 +3056,22 @@ EnvironmentClient.prototype = {
   },
   get _transport() { return this._client._transport; },
 
   /**
    * Fetches the bindings introduced by this lexical environment.
    */
   getBindings: DebuggerClient.requester({
     type: "bindings"
-  }, {
-    telemetry: "BINDINGS"
   }),
 
   /**
    * Changes the value of the identifier whose name is name (a string) to that
    * represented by value (a grip).
    */
   assign: DebuggerClient.requester({
     type: "assign",
     name: args(0),
     value: args(1)
-  }, {
-    telemetry: "ASSIGN"
   })
 };
 
 eventSource(EnvironmentClient.prototype);
--- a/devtools/shared/gcli/commands/screenshot.js
+++ b/devtools/shared/gcli/commands/screenshot.js
@@ -1,15 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const { Cc, Ci, Cr } = require("chrome");
+const { Cc, Ci, Cr, Cu } = require("chrome");
 const l10n = require("gcli/l10n");
 const Services = require("Services");
 const { NetUtil } = require("resource://gre/modules/NetUtil.jsm");
 const { getRect } = require("devtools/shared/layout/utils");
 const promise = require("promise");
 const defer = require("devtools/shared/defer");
 const { Task } = require("devtools/shared/task");
 
@@ -186,17 +186,17 @@ exports.items = [
             name: "chrome",
             type: "boolean",
             description: l10n.lookupFormat("screenshotChromeDesc2", [BRAND_SHORT_NAME]),
             manual: l10n.lookupFormat("screenshotChromeManual2", [BRAND_SHORT_NAME])
           },
         ]
       },
     ],
-    exec: function(args, context) {
+    exec: function (args, context) {
       if (args.chrome && args.selector) {
         // Node screenshot with chrome option does not work as intended
         // Refer https://bugzilla.mozilla.org/show_bug.cgi?id=659268#c7
         // throwing for now.
         throw new Error(l10n.lookup("screenshotSelectorChromeConflict"));
       }
 
       let capture;
@@ -204,34 +204,49 @@ exports.items = [
         // Re-execute the command on the server
         const command = context.typed.replace(/^screenshot/, "screenshot_server");
         capture = context.updateExec(command).then(output => {
           return output.error ? Promise.reject(output.data) : output.data;
         });
       } else {
         capture = captureScreenshot(args, context.environment.chromeDocument);
       }
-
+      simulateCameraEffect(context.environment.chromeDocument, "shutter");
       return capture.then(saveScreenshot.bind(null, args, context));
     },
   },
   {
     item: "command",
     runAt: "server",
     name: "screenshot_server",
     hidden: true,
     returnType: "imageSummary",
     params: [ filenameParam, standardParams ],
-    exec: function(args, context) {
+    exec: function (args, context) {
       return captureScreenshot(args, context.environment.document);
     },
   }
 ];
 
 /**
+ * This function is called to simulate camera effects
+ */
+function simulateCameraEffect(document, effect) {
+  let window = document.defaultView;
+  if (effect === "shutter") {
+    const audioCamera = new window.Audio("resource://devtools/client/themes/audio/shutter.wav");
+    audioCamera.play();
+  }
+  if (effect == "flash") {
+    const frames = Cu.cloneInto({ opacity: [ 0, 1 ] }, window);
+    document.documentElement.animate(frames, 500);
+  }
+}
+
+/**
  * This function simply handles the --delay argument before calling
  * createScreenshotData
  */
 function captureScreenshot(args, document) {
   if (args.delay > 0) {
     return new Promise((resolve, reject) => {
       document.defaultView.setTimeout(() => {
         createScreenshotData(document, args).then(resolve, reject);
@@ -316,16 +331,18 @@ function createScreenshotData(document, 
   ctx.drawWindow(window, left, top, width, height, "#fff");
   const data = canvas.toDataURL("image/png", "");
 
   // See comment above on bug 961832
   if (args.fullpage) {
     window.scrollTo(currentX, currentY);
   }
 
+  simulateCameraEffect(document, "flash");
+
   return Promise.resolve({
     destinations: [],
     data: data,
     height: height,
     width: width,
     filename: filename,
   });
 }
--- a/devtools/shared/inspector/css-logic.js
+++ b/devtools/shared/inspector/css-logic.js
@@ -35,828 +35,83 @@
  * CssLogic uses the standard DOM API, and the Gecko inIDOMUtils API to access
  * styling information in the page, and present this to the user in a way that
  * helps them understand:
  * - why their expectations may not have been fulfilled
  * - how browsers process CSS
  * @constructor
  */
 
-const { Cc, Ci, Cu } = require("chrome");
+const { Cc, Ci } = require("chrome");
 const Services = require("Services");
-const DevToolsUtils = require("devtools/shared/DevToolsUtils");
-const { getRootBindingParent } = require("devtools/shared/layout/utils");
-const nodeConstants = require("devtools/shared/dom-node-constants");
 
 // This should be ok because none of the functions that use this should be used
 // on the worker thread, where Cu is not available.
 loader.lazyRequireGetter(this, "CSS", "CSS");
 
 loader.lazyRequireGetter(this, "CSSLexer", "devtools/shared/css-lexer");
 
 /**
- * @param {function} isInherited A function that determines if the CSS property
- *                   is inherited.
- */
-function CssLogic(isInherited) {
-  // The cache of examined CSS properties.
-  this._isInherited = isInherited;
-  this._propertyInfos = {};
-}
-
-exports.CssLogic = CssLogic;
-
-/**
  * Special values for filter, in addition to an href these values can be used
  */
-CssLogic.FILTER = {
+exports.FILTER = {
   // show properties for all user style sheets.
   USER: "user",
   // USER, plus user-agent (i.e. browser) style sheets
   UA: "ua",
 };
 
 /**
- * Known media values. To distinguish "all" stylesheets (above) from "all" media
- * The full list includes braille, embossed, handheld, print, projection,
- * speech, tty, and tv, but this is only a hack because these are not defined
- * in the DOM at all.
- * @see http://www.w3.org/TR/CSS21/media.html#media-types
- */
-CssLogic.MEDIA = {
-  ALL: "all",
-  SCREEN: "screen",
-};
-
-/**
  * Each rule has a status, the bigger the number, the better placed it is to
  * provide styling information.
  *
  * These statuses are localized inside the styleinspector.properties
  * string bundle.
  * @see csshtmltree.js RuleView._cacheStatusNames()
  */
-CssLogic.STATUS = {
+exports.STATUS = {
   BEST: 3,
   MATCHED: 2,
   PARENT_MATCH: 1,
   UNMATCHED: 0,
   UNKNOWN: -1,
 };
 
-CssLogic.prototype = {
-  // Both setup by highlight().
-  viewedElement: null,
-  viewedDocument: null,
-
-  // The cache of the known sheets.
-  _sheets: null,
-
-  // Have the sheets been cached?
-  _sheetsCached: false,
-
-  // The total number of rules, in all stylesheets, after filtering.
-  _ruleCount: 0,
-
-  // The computed styles for the viewedElement.
-  _computedStyle: null,
-
-  // Source filter. Only display properties coming from the given source
-  _sourceFilter: CssLogic.FILTER.USER,
-
-  // Used for tracking unique CssSheet/CssRule/CssSelector objects, in a run of
-  // processMatchedSelectors().
-  _passId: 0,
-
-  // Used for tracking matched CssSelector objects.
-  _matchId: 0,
-
-  _matchedRules: null,
-  _matchedSelectors: null,
-
-  // Cached keyframes rules in all stylesheets
-  _keyframesRules: null,
-
-  /**
-   * Reset various properties
-   */
-  reset: function () {
-    this._propertyInfos = {};
-    this._ruleCount = 0;
-    this._sheetIndex = 0;
-    this._sheets = {};
-    this._sheetsCached = false;
-    this._matchedRules = null;
-    this._matchedSelectors = null;
-    this._keyframesRules = [];
-  },
-
-  /**
-   * Focus on a new element - remove the style caches.
-   *
-   * @param {nsIDOMElement} aViewedElement the element the user has highlighted
-   * in the Inspector.
-   */
-  highlight: function (viewedElement) {
-    if (!viewedElement) {
-      this.viewedElement = null;
-      this.viewedDocument = null;
-      this._computedStyle = null;
-      this.reset();
-      return;
-    }
-
-    if (viewedElement === this.viewedElement) {
-      return;
-    }
-
-    this.viewedElement = viewedElement;
-
-    let doc = this.viewedElement.ownerDocument;
-    if (doc != this.viewedDocument) {
-      // New document: clear/rebuild the cache.
-      this.viewedDocument = doc;
-
-      // Hunt down top level stylesheets, and cache them.
-      this._cacheSheets();
-    } else {
-      // Clear cached data in the CssPropertyInfo objects.
-      this._propertyInfos = {};
-    }
-
-    this._matchedRules = null;
-    this._matchedSelectors = null;
-    this._computedStyle = CssLogic.getComputedStyle(this.viewedElement);
-  },
-
-  /**
-   * Get the values of all the computed CSS properties for the highlighted
-   * element.
-   * @returns {object} The computed CSS properties for a selected element
-   */
-  get computedStyle() {
-    return this._computedStyle;
-  },
-
-  /**
-   * Get the source filter.
-   * @returns {string} The source filter being used.
-   */
-  get sourceFilter() {
-    return this._sourceFilter;
-  },
-
-  /**
-   * Source filter. Only display properties coming from the given source (web
-   * address). Note that in order to avoid information overload we DO NOT show
-   * unmatched system rules.
-   * @see CssLogic.FILTER.*
-   */
-  set sourceFilter(value) {
-    let oldValue = this._sourceFilter;
-    this._sourceFilter = value;
-
-    let ruleCount = 0;
-
-    // Update the CssSheet objects.
-    this.forEachSheet(function (sheet) {
-      sheet._sheetAllowed = -1;
-      if (sheet.contentSheet && sheet.sheetAllowed) {
-        ruleCount += sheet.ruleCount;
-      }
-    }, this);
-
-    this._ruleCount = ruleCount;
-
-    // Full update is needed because the this.processMatchedSelectors() method
-    // skips UA stylesheets if the filter does not allow such sheets.
-    let needFullUpdate = (oldValue == CssLogic.FILTER.UA ||
-        value == CssLogic.FILTER.UA);
-
-    if (needFullUpdate) {
-      this._matchedRules = null;
-      this._matchedSelectors = null;
-      this._propertyInfos = {};
-    } else {
-      // Update the CssPropertyInfo objects.
-      for (let property in this._propertyInfos) {
-        this._propertyInfos[property].needRefilter = true;
-      }
-    }
-  },
-
-  /**
-   * Return a CssPropertyInfo data structure for the currently viewed element
-   * and the specified CSS property. If there is no currently viewed element we
-   * return an empty object.
-   *
-   * @param {string} property The CSS property to look for.
-   * @return {CssPropertyInfo} a CssPropertyInfo structure for the given
-   * property.
-   */
-  getPropertyInfo: function (property) {
-    if (!this.viewedElement) {
-      return {};
-    }
-
-    let info = this._propertyInfos[property];
-    if (!info) {
-      info = new CssPropertyInfo(this, property, this._isInherited);
-      this._propertyInfos[property] = info;
-    }
-
-    return info;
-  },
-
-  /**
-   * Cache all the stylesheets in the inspected document
-   * @private
-   */
-  _cacheSheets: function () {
-    this._passId++;
-    this.reset();
-
-    // styleSheets isn't an array, but forEach can work on it anyway
-    Array.prototype.forEach.call(this.viewedDocument.styleSheets,
-        this._cacheSheet, this);
-
-    this._sheetsCached = true;
-  },
-
-  /**
-   * Cache a stylesheet if it falls within the requirements: if it's enabled,
-   * and if the @media is allowed. This method also walks through the stylesheet
-   * cssRules to find @imported rules, to cache the stylesheets of those rules
-   * as well. In addition, the @keyframes rules in the stylesheet are cached.
-   *
-   * @private
-   * @param {CSSStyleSheet} domSheet the CSSStyleSheet object to cache.
-   */
-  _cacheSheet: function (domSheet) {
-    if (domSheet.disabled) {
-      return;
-    }
-
-    // Only work with stylesheets that have their media allowed.
-    if (!this.mediaMatches(domSheet)) {
-      return;
-    }
-
-    // Cache the sheet.
-    let cssSheet = this.getSheet(domSheet, this._sheetIndex++);
-    if (cssSheet._passId != this._passId) {
-      cssSheet._passId = this._passId;
-
-      // Find import and keyframes rules.
-      for (let aDomRule of domSheet.cssRules) {
-        if (aDomRule.type == CSSRule.IMPORT_RULE &&
-            aDomRule.styleSheet &&
-            this.mediaMatches(aDomRule)) {
-          this._cacheSheet(aDomRule.styleSheet);
-        } else if (aDomRule.type == CSSRule.KEYFRAMES_RULE) {
-          this._keyframesRules.push(aDomRule);
-        }
-      }
-    }
-  },
-
-  /**
-   * Retrieve the list of stylesheets in the document.
-   *
-   * @return {array} the list of stylesheets in the document.
-   */
-  get sheets() {
-    if (!this._sheetsCached) {
-      this._cacheSheets();
-    }
-
-    let sheets = [];
-    this.forEachSheet(function (sheet) {
-      if (sheet.contentSheet) {
-        sheets.push(sheet);
-      }
-    }, this);
-
-    return sheets;
-  },
-
-  /**
-   * Retrieve the list of keyframes rules in the document.
-   *
-   * @ return {array} the list of keyframes rules in the document.
-   */
-  get keyframesRules() {
-    if (!this._sheetsCached) {
-      this._cacheSheets();
-    }
-    return this._keyframesRules;
-  },
-
-  /**
-   * Retrieve a CssSheet object for a given a CSSStyleSheet object. If the
-   * stylesheet is already cached, you get the existing CssSheet object,
-   * otherwise the new CSSStyleSheet object is cached.
-   *
-   * @param {CSSStyleSheet} domSheet the CSSStyleSheet object you want.
-   * @param {number} index the index, within the document, of the stylesheet.
-   *
-   * @return {CssSheet} the CssSheet object for the given CSSStyleSheet object.
-   */
-  getSheet: function (domSheet, index) {
-    let cacheId = "";
-
-    if (domSheet.href) {
-      cacheId = domSheet.href;
-    } else if (domSheet.ownerNode && domSheet.ownerNode.ownerDocument) {
-      cacheId = domSheet.ownerNode.ownerDocument.location;
-    }
-
-    let sheet = null;
-    let sheetFound = false;
-
-    if (cacheId in this._sheets) {
-      for (let i = 0, numSheets = this._sheets[cacheId].length;
-           i < numSheets;
-           i++) {
-        sheet = this._sheets[cacheId][i];
-        if (sheet.domSheet === domSheet) {
-          if (index != -1) {
-            sheet.index = index;
-          }
-          sheetFound = true;
-          break;
-        }
-      }
-    }
-
-    if (!sheetFound) {
-      if (!(cacheId in this._sheets)) {
-        this._sheets[cacheId] = [];
-      }
-
-      sheet = new CssSheet(this, domSheet, index);
-      if (sheet.sheetAllowed && sheet.contentSheet) {
-        this._ruleCount += sheet.ruleCount;
-      }
-
-      this._sheets[cacheId].push(sheet);
-    }
-
-    return sheet;
-  },
-
-  /**
-   * Process each cached stylesheet in the document using your callback.
-   *
-   * @param {function} callback the function you want executed for each of the
-   * CssSheet objects cached.
-   * @param {object} scope the scope you want for the callback function. scope
-   * will be the this object when callback executes.
-   */
-  forEachSheet: function (callback, scope) {
-    for (let cacheId in this._sheets) {
-      let sheets = this._sheets[cacheId];
-      for (let i = 0; i < sheets.length; i++) {
-        // We take this as an opportunity to clean dead sheets
-        try {
-          let sheet = sheets[i];
-          // If accessing domSheet raises an exception, then the style
-          // sheet is a dead object.
-          sheet.domSheet;
-          callback.call(scope, sheet, i, sheets);
-        } catch (e) {
-          sheets.splice(i, 1);
-          i--;
-        }
-      }
-    }
-  },
-
-  /**
-   * Process *some* cached stylesheets in the document using your callback. The
-   * callback function should return true in order to halt processing.
-   *
-   * @param {function} callback the function you want executed for some of the
-   * CssSheet objects cached.
-   * @param {object} scope the scope you want for the callback function. scope
-   * will be the this object when callback executes.
-   * @return {Boolean} true if callback returns true during any iteration,
-   * otherwise false is returned.
-   */
-  forSomeSheets: function (callback, scope) {
-    for (let cacheId in this._sheets) {
-      if (this._sheets[cacheId].some(callback, scope)) {
-        return true;
-      }
-    }
-    return false;
-  },
-
-  /**
-   * Get the number nsIDOMCSSRule objects in the document, counted from all of
-   * the stylesheets. System sheets are excluded. If a filter is active, this
-   * tells only the number of nsIDOMCSSRule objects inside the selected
-   * CSSStyleSheet.
-   *
-   * WARNING: This only provides an estimate of the rule count, and the results
-   * could change at a later date. Todo remove this
-   *
-   * @return {number} the number of nsIDOMCSSRule (all rules).
-   */
-  get ruleCount() {
-    if (!this._sheetsCached) {
-      this._cacheSheets();
-    }
-
-    return this._ruleCount;
-  },
-
-  /**
-   * Process the CssSelector objects that match the highlighted element and its
-   * parent elements. scope.callback() is executed for each CssSelector
-   * object, being passed the CssSelector object and the match status.
-   *
-   * This method also includes all of the element.style properties, for each
-   * highlighted element parent and for the highlighted element itself.
-   *
-   * Note that the matched selectors are cached, such that next time your
-   * callback is invoked for the cached list of CssSelector objects.
-   *
-   * @param {function} callback the function you want to execute for each of
-   * the matched selectors.
-   * @param {object} scope the scope you want for the callback function. scope
-   * will be the this object when callback executes.
-   */
-  processMatchedSelectors: function (callback, scope) {
-    if (this._matchedSelectors) {
-      if (callback) {
-        this._passId++;
-        this._matchedSelectors.forEach(function (value) {
-          callback.call(scope, value[0], value[1]);
-          value[0].cssRule._passId = this._passId;
-        }, this);
-      }
-      return;
-    }
-
-    if (!this._matchedRules) {
-      this._buildMatchedRules();
-    }
-
-    this._matchedSelectors = [];
-    this._passId++;
-
-    for (let i = 0; i < this._matchedRules.length; i++) {
-      let rule = this._matchedRules[i][0];
-      let status = this._matchedRules[i][1];
-
-      rule.selectors.forEach(function (selector) {
-        if (selector._matchId !== this._matchId &&
-           (selector.elementStyle ||
-            this.selectorMatchesElement(rule.domRule,
-                                        selector.selectorIndex))) {
-          selector._matchId = this._matchId;
-          this._matchedSelectors.push([ selector, status ]);
-          if (callback) {
-            callback.call(scope, selector, status);
-          }
-        }
-      }, this);
-
-      rule._passId = this._passId;
-    }
-  },
-
-  /**
-   * Check if the given selector matches the highlighted element or any of its
-   * parents.
-   *
-   * @private
-   * @param {DOMRule} domRule
-   *        The DOM Rule containing the selector.
-   * @param {Number} idx
-   *        The index of the selector within the DOMRule.
-   * @return {boolean}
-   *         true if the given selector matches the highlighted element or any
-   *         of its parents, otherwise false is returned.
-   */
-  selectorMatchesElement: function (domRule, idx) {
-    let element = this.viewedElement;
-    do {
-      if (domUtils.selectorMatchesElement(element, domRule, idx)) {
-        return true;
-      }
-    } while ((element = element.parentNode) &&
-             element.nodeType === nodeConstants.ELEMENT_NODE);
-
-    return false;
-  },
-
-  /**
-   * Check if the highlighted element or it's parents have matched selectors.
-   *
-   * @param {array} aProperties The list of properties you want to check if they
-   * have matched selectors or not.
-   * @return {object} An object that tells for each property if it has matched
-   * selectors or not. Object keys are property names and values are booleans.
-   */
-  hasMatchedSelectors: function (properties) {
-    if (!this._matchedRules) {
-      this._buildMatchedRules();
-    }
-
-    let result = {};
-
-    this._matchedRules.some(function (value) {
-      let rule = value[0];
-      let status = value[1];
-      properties = properties.filter((property) => {
-        // We just need to find if a rule has this property while it matches
-        // the viewedElement (or its parents).
-        if (rule.getPropertyValue(property) &&
-            (status == CssLogic.STATUS.MATCHED ||
-             (status == CssLogic.STATUS.PARENT_MATCH &&
-              this._isInherited(property)))) {
-          result[property] = true;
-          return false;
-        }
-        // Keep the property for the next rule.
-        return true;
-      });
-      return properties.length == 0;
-    }, this);
-
-    return result;
-  },
-
-  /**
-   * Build the array of matched rules for the currently highlighted element.
-   * The array will hold rules that match the viewedElement and its parents.
-   *
-   * @private
-   */
-  _buildMatchedRules: function () {
-    let domRules;
-    let element = this.viewedElement;
-    let filter = this.sourceFilter;
-    let sheetIndex = 0;
-
-    this._matchId++;
-    this._passId++;
-    this._matchedRules = [];
-
-    if (!element) {
-      return;
-    }
-
-    do {
-      let status = this.viewedElement === element ?
-                   CssLogic.STATUS.MATCHED : CssLogic.STATUS.PARENT_MATCH;
-
-      try {
-        // Handle finding rules on pseudo by reading style rules
-        // on the parent node with proper pseudo arg to getCSSStyleRules.
-        let {bindingElement, pseudo} =
-            CssLogic.getBindingElementAndPseudo(element);
-        domRules = domUtils.getCSSStyleRules(bindingElement, pseudo);
-      } catch (ex) {
-        console.log("CL__buildMatchedRules error: " + ex);
-        continue;
-      }
-
-      // getCSSStyleRules can return null with a shadow DOM element.
-      let numDomRules = domRules ? domRules.Count() : 0;
-      for (let i = 0; i < numDomRules; i++) {
-        let domRule = domRules.GetElementAt(i);
-        if (domRule.type !== CSSRule.STYLE_RULE) {
-          continue;
-        }
-
-        let sheet = this.getSheet(domRule.parentStyleSheet, -1);
-        if (sheet._passId !== this._passId) {
-          sheet.index = sheetIndex++;
-          sheet._passId = this._passId;
-        }
-
-        if (filter === CssLogic.FILTER.USER && !sheet.contentSheet) {
-          continue;
-        }
-
-        let rule = sheet.getRule(domRule);
-        if (rule._passId === this._passId) {
-          continue;
-        }
-
-        rule._matchId = this._matchId;
-        rule._passId = this._passId;
-        this._matchedRules.push([rule, status]);
-      }
-
-      // Add element.style information.
-      if (element.style && element.style.length > 0) {
-        let rule = new CssRule(null, { style: element.style }, element);
-        rule._matchId = this._matchId;
-        rule._passId = this._passId;
-        this._matchedRules.push([rule, status]);
-      }
-    } while ((element = element.parentNode) &&
-              element.nodeType === nodeConstants.ELEMENT_NODE);
-  },
-
-  /**
-   * Tells if the given DOM CSS object matches the current view media.
-   *
-   * @param {object} domObject The DOM CSS object to check.
-   * @return {boolean} True if the DOM CSS object matches the current view
-   * media, or false otherwise.
-   */
-  mediaMatches: function (domObject) {
-    let mediaText = domObject.media.mediaText;
-    return !mediaText ||
-      this.viewedDocument.defaultView.matchMedia(mediaText).matches;
-  },
-};
-
 /**
- * If the element has an id, return '#id'. Otherwise return 'tagname[n]' where
- * n is the index of this element in its siblings.
- * <p>A technically more 'correct' output from the no-id case might be:
- * 'tagname:nth-of-type(n)' however this is unlikely to be more understood
- * and it is longer.
- *
- * @param {nsIDOMElement} element the element for which you want the short name.
- * @return {string} the string to be displayed for element.
- */
-CssLogic.getShortName = function (element) {
-  if (!element) {
-    return "null";
-  }
-  if (element.id) {
-    return "#" + element.id;
-  }
-  let priorSiblings = 0;
-  let temp = element;
-  while ((temp = temp.previousElementSibling)) {
-    priorSiblings++;
-  }
-  return element.tagName + "[" + priorSiblings + "]";
-};
-
-/**
- * Get an array of short names from the given element to document.body.
- *
- * @param {nsIDOMElement} element the element for which you want the array of
- * short names.
- * @return {array} The array of elements.
- * <p>Each element is an object of the form:
- * <ul>
- * <li>{ display: "what to display for the given (parent) element",
- * <li>  element: referenceToTheElement }
- * </ul>
- */
-CssLogic.getShortNamePath = function (element) {
-  let doc = element.ownerDocument;
-  let reply = [];
-
-  if (!element) {
-    return reply;
-  }
-
-  // We want to exclude nodes high up the tree (body/html) unless the user
-  // has selected that node, in which case we need to report something.
-  do {
-    reply.unshift({
-      display: CssLogic.getShortName(element),
-      element: element
-    });
-    element = element.parentNode;
-  } while (element && element != doc.body && element != doc.head &&
-           element != doc);
-
-  return reply;
-};
-
-/**
- * Get a string list of selectors for a given DOMRule.
- *
- * @param {DOMRule} domRule
- *        The DOMRule to parse.
- * @return {Array}
- *         An array of string selectors.
- */
-CssLogic.getSelectors = function (domRule) {
-  let selectors = [];
-
-  let len = domUtils.getSelectorCount(domRule);
-  for (let i = 0; i < len; i++) {
-    let text = domUtils.getSelectorText(domRule, i);
-    selectors.push(text);
-  }
-  return selectors;
-};
-
-/**
- * Given a node, check to see if it is a ::before or ::after element.
- * If so, return the node that is accessible from within the document
- * (the parent of the anonymous node), along with which pseudo element
- * it was.  Otherwise, return the node itself.
- *
- * @returns {Object}
- *            - {DOMNode} node The non-anonymous node
- *            - {string} pseudo One of ':before', ':after', or null.
- */
-CssLogic.getBindingElementAndPseudo = function (node) {
-  let bindingElement = node;
-  let pseudo = null;
-  if (node.nodeName == "_moz_generated_content_before") {
-    bindingElement = node.parentNode;
-    pseudo = ":before";
-  } else if (node.nodeName == "_moz_generated_content_after") {
-    bindingElement = node.parentNode;
-    pseudo = ":after";
-  }
-  return {
-    bindingElement: bindingElement,
-    pseudo: pseudo
-  };
-};
-
-/**
- * Get the computed style on a node.  Automatically handles reading
- * computed styles on a ::before/::after element by reading on the
- * parent node with the proper pseudo argument.
- *
- * @param {Node}
- * @returns {CSSStyleDeclaration}
- */
-CssLogic.getComputedStyle = function (node) {
-  if (!node ||
-      Cu.isDeadWrapper(node) ||
-      node.nodeType !== nodeConstants.ELEMENT_NODE ||
-      !node.ownerDocument ||
-      !node.ownerDocument.defaultView) {
-    return null;
-  }
-
-  let {bindingElement, pseudo} = CssLogic.getBindingElementAndPseudo(node);
-  return node.ownerDocument.defaultView.getComputedStyle(bindingElement,
-                                                         pseudo);
-};
-
-/**
- * Memonized lookup of a l10n string from a string bundle.
+ * Memoized lookup of a l10n string from a string bundle.
  * @param {string} name The key to lookup.
  * @returns A localized version of the given key.
  */
-CssLogic.l10n = function (name) {
-  return CssLogic._strings.GetStringFromName(name);
+exports.l10n = function (name) {
+  return exports._strings.GetStringFromName(name);
 };
 
-DevToolsUtils.defineLazyGetter(CssLogic, "_strings", function () {
-  return Services.strings
-    .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
-});
+exports._strings = Services.strings
+  .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
 
 /**
  * Is the given property sheet a content stylesheet?
  *
  * @param {CSSStyleSheet} sheet a stylesheet
  * @return {boolean} true if the given stylesheet is a content stylesheet,
  * false otherwise.
  */
-CssLogic.isContentStylesheet = function (sheet) {
+exports.isContentStylesheet = function (sheet) {
   return sheet.parsingMode !== "agent";
 };
 
 /**
- * Get a source for a stylesheet, taking into account embedded stylesheets
- * for which we need to use document.defaultView.location.href rather than
- * sheet.href
- *
- * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
- * @return {string} the address of the stylesheet.
- */
-CssLogic.href = function (sheet) {
-  let href = sheet.href;
-  if (!href) {
-    href = sheet.ownerNode.ownerDocument.location;
-  }
-
-  return href;
-};
-
-/**
  * Return a shortened version of a style sheet's source.
  *
  * @param {CSSStyleSheet} sheet the DOM object for the style sheet.
  */
-CssLogic.shortSource = function (sheet) {
+exports.shortSource = function (sheet) {
   // Use a string like "inline" if there is no source href
   if (!sheet || !sheet.href) {
-    return CssLogic.l10n("rule.sourceInline");
+    return exports.l10n("rule.sourceInline");
   }
 
   // We try, in turn, the filename, filePath, query string, whole thing
   let url = {};
   try {
     url = Services.io.newURI(sheet.href, null, null);
     url = url.QueryInterface(Ci.nsIURL);
   } catch (ex) {
@@ -874,109 +129,29 @@ CssLogic.shortSource = function (sheet) 
   if (url.query) {
     return url.query;
   }
 
   let dataUrl = sheet.href.match(/^(data:[^,]*),/);
   return dataUrl ? dataUrl[1] : sheet.href;
 };
 
-/**
- * Find the position of [element] in [nodeList].
- * @returns an index of the match, or -1 if there is no match
- */
-function positionInNodeList(element, nodeList) {
-  for (let i = 0; i < nodeList.length; i++) {
-    if (element === nodeList[i]) {
-      return i;
-    }
-  }
-  return -1;
-}
-
-/**
- * Find a unique CSS selector for a given element
- * @returns a string such that ele.ownerDocument.querySelector(reply) === ele
- * and ele.ownerDocument.querySelectorAll(reply).length === 1
- */
-CssLogic.findCssSelector = function (ele) {
-  ele = getRootBindingParent(ele);
-  let document = ele.ownerDocument;
-  if (!document || !document.contains(ele)) {
-    throw new Error("findCssSelector received element not inside document");
-  }
-
-  // document.querySelectorAll("#id") returns multiple if elements share an ID
-  if (ele.id &&
-      document.querySelectorAll("#" + CSS.escape(ele.id)).length === 1) {
-    return "#" + CSS.escape(ele.id);
-  }
-
-  // Inherently unique by tag name
-  let tagName = ele.localName;
-  if (tagName === "html") {
-    return "html";
-  }
-  if (tagName === "head") {
-    return "head";
-  }
-  if (tagName === "body") {
-    return "body";
-  }
-
-  // We might be able to find a unique class name
-  let selector, index, matches;
-  if (ele.classList.length > 0) {
-    for (let i = 0; i < ele.classList.length; i++) {
-      // Is this className unique by itself?
-      selector = "." + CSS.escape(ele.classList.item(i));
-      matches = document.querySelectorAll(selector);
-      if (matches.length === 1) {
-        return selector;
-      }
-      // Maybe it's unique with a tag name?
-      selector = tagName + selector;
-      matches = document.querySelectorAll(selector);
-      if (matches.length === 1) {
-        return selector;
-      }
-      // Maybe it's unique using a tag name and nth-child
-      index = positionInNodeList(ele, ele.parentNode.children) + 1;
-      selector = selector + ":nth-child(" + index + ")";
-      matches = document.querySelectorAll(selector);
-      if (matches.length === 1) {
-        return selector;
-      }
-    }
-  }
-
-  // Not unique enough yet.  As long as it's not a child of the document,
-  // continue recursing up until it is unique enough.
-  if (ele.parentNode !== document) {
-    index = positionInNodeList(ele, ele.parentNode.children) + 1;
-    selector = CssLogic.findCssSelector(ele.parentNode) + " > " +
-      tagName + ":nth-child(" + index + ")";
-  }
-
-  return selector;
-};
-
 const TAB_CHARS = "\t";
 
 /**
  * Prettify minified CSS text.
  * This prettifies CSS code where there is no indentation in usual places while
  * keeping original indentation as-is elsewhere.
  * @param string text The CSS source to prettify.
  * @return string Prettified CSS source
  */
-CssLogic.prettifyCSS = function (text, ruleCount) {
-  if (CssLogic.LINE_SEPARATOR == null) {
+function prettifyCSS(text, ruleCount) {
+  if (prettifyCSS.LINE_SEPARATOR == null) {
     let os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
-    CssLogic.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n");
+    prettifyCSS.LINE_SEPARATOR = (os === "WINNT" ? "\r\n" : "\n");
   }
 
   // remove initial and terminating HTML comments and surrounding whitespace
   text = text.replace(/(?:^\s*<!--[\r\n]*)|(?:\s*-->\s*$)/g, "");
   let originalText = text;
   text = text.trim();
 
   // don't attempt to prettify if there's more than one line per rule.
@@ -1103,17 +278,17 @@ CssLogic.prettifyCSS = function (text, r
     // Append any saved up text to the result, applying indentation.
     if (startIndex !== undefined) {
       if (isCloseBrace && !anyNonWS) {
         // If we saw only whitespace followed by a "}", then we don't
         // need anything here.
       } else {
         result = result + indent + text.substring(startIndex, endIndex);
         if (isCloseBrace) {
-          result += CssLogic.LINE_SEPARATOR;
+          result += prettifyCSS.LINE_SEPARATOR;
         }
       }
     }
 
     if (isCloseBrace) {
       indent = TAB_CHARS.repeat(--indentLevel);
       result = result + indent + "}";
     }
@@ -1138,865 +313,20 @@ CssLogic.prettifyCSS = function (text, r
     // Here we ignore the case where whitespace appears at the end of
     // the text.
     if (pushbackToken && token && token.tokenType === "whitespace" &&
         /\n/g.test(text.substring(token.startOffset, token.endOffset))) {
       return originalText;
     }
 
     // Finally time for that newline.
-    result = result + CssLogic.LINE_SEPARATOR;
+    result = result + prettifyCSS.LINE_SEPARATOR;
 
     // Maybe we hit EOF.
     if (!pushbackToken) {
       break;
     }
   }
 
   return result;
-};
-
-/**
- * A safe way to access cached bits of information about a stylesheet.
- *
- * @constructor
- * @param {CssLogic} cssLogic pointer to the CssLogic instance working with
- * this CssSheet object.
- * @param {CSSStyleSheet} domSheet reference to a DOM CSSStyleSheet object.
- * @param {number} index tells the index/position of the stylesheet within the
- * main document.
- */
-function CssSheet(cssLogic, domSheet, index) {
-  this._cssLogic = cssLogic;
-  this.domSheet = domSheet;
-  this.index = this.contentSheet ? index : -100 * index;
-
-  // Cache of the sheets href. Cached by the getter.
-  this._href = null;
-  // Short version of href for use in select boxes etc. Cached by getter.
-  this._shortSource = null;
-
-  // null for uncached.
-  this._sheetAllowed = null;
-
-  // Cached CssRules from the given stylesheet.
-  this._rules = {};
-
-  this._ruleCount = -1;
-}
-
-CssSheet.prototype = {
-  _passId: null,
-  _contentSheet: null,
-  _mediaMatches: null,
-
-  /**
-   * Tells if the stylesheet is provided by the browser or not.
-   *
-   * @return {boolean} false if this is a browser-provided stylesheet, or true
-   * otherwise.
-   */
-  get contentSheet() {
-    if (this._contentSheet === null) {
-      this._contentSheet = CssLogic.isContentStylesheet(this.domSheet);
-    }
-    return this._contentSheet;
-  },
-
-  /**
-   * Tells if the stylesheet is disabled or not.
-   * @return {boolean} true if this stylesheet is disabled, or false otherwise.
-   */
-  get disabled() {
-    return this.domSheet.disabled;
-  },
-
-  /**
-   * Tells if the stylesheet matches the current browser view media.
-   * @return {boolean} true if this stylesheet matches the current browser view
-   * media, or false otherwise.
-   */
-  get mediaMatches() {
-    if (this._mediaMatches === null) {
-      this._mediaMatches = this._cssLogic.mediaMatches(this.domSheet);
-    }
-    return this._mediaMatches;
-  },
-
-  /**
-   * Get a source for a stylesheet, using CssLogic.href
-   *
-   * @return {string} the address of the stylesheet.
-   */
-  get href() {
-    if (this._href) {
-      return this._href;
-    }
-
-    this._href = CssLogic.href(this.domSheet);
-    return this._href;
-  },
-
-  /**
-   * Create a shorthand version of the href of a stylesheet.
-   *
-   * @return {string} the shorthand source of the stylesheet.
-   */
-  get shortSource() {
-    if (this._shortSource) {
-      return this._shortSource;
-    }
-
-    this._shortSource = CssLogic.shortSource(this.domSheet);
-    return this._shortSource;
-  },
-
-  /**
-   * Tells if the sheet is allowed or not by the current CssLogic.sourceFilter.
-   *
-   * @return {boolean} true if the stylesheet is allowed by the sourceFilter, or
-   * false otherwise.
-   */
-  get sheetAllowed() {
-    if (this._sheetAllowed !== null) {
-      return this._sheetAllowed;
-    }
-
-    this._sheetAllowed = true;
-
-    let filter = this._cssLogic.sourceFilter;
-    if (filter === CssLogic.FILTER.USER && !this.contentSheet) {
-      this._sheetAllowed = false;
-    }
-    if (filter !== CssLogic.FILTER.USER && filter !== CssLogic.FILTER.UA) {
-      this._sheetAllowed = (filter === this.href);
-    }
-
-    return this._sheetAllowed;
-  },
-
-  /**
-   * Retrieve the number of rules in this stylesheet.
-   *
-   * @return {number} the number of nsIDOMCSSRule objects in this stylesheet.
-   */
-  get ruleCount() {
-    return this._ruleCount > -1 ?
-      this._ruleCount :
-      this.domSheet.cssRules.length;
-  },
-
-  /**
-   * Retrieve a CssRule object for the given CSSStyleRule. The CssRule object is
-   * cached, such that subsequent retrievals return the same CssRule object for
-   * the same CSSStyleRule object.
-   *
-   * @param {CSSStyleRule} aDomRule the CSSStyleRule object for which you want a
-   * CssRule object.
-   * @return {CssRule} the cached CssRule object for the given CSSStyleRule
-   * object.
-   */
-  getRule: function (domRule) {
-    let cacheId = domRule.type + domRule.selectorText;
-
-    let rule = null;
-    let ruleFound = false;
-
-    if (cacheId in this._rules) {
-      for (let i = 0, rulesLen = this._rules[cacheId].length;
-           i < rulesLen;
-           i++) {
-        rule = this._rules[cacheId][i];
-        if (rule.domRule === domRule) {
-          ruleFound = true;
-          break;
-        }
-      }
-    }
-
-    if (!ruleFound) {
-      if (!(cacheId in this._rules)) {
-        this._rules[cacheId] = [];
-      }
-
-      rule = new CssRule(this, domRule);
-      this._rules[cacheId].push(rule);
-    }
-
-    return rule;
-  },
-
-  /**
-   * Process each rule in this stylesheet using your callback function. Your
-   * function receives one argument: the CssRule object for each CSSStyleRule
-   * inside the stylesheet.
-   *
-   * Note that this method also iterates through @media rules inside the
-   * stylesheet.
-   *
-   * @param {function} callback the function you want to execute for each of
-   * the style rules.
-   * @param {object} scope the scope you want for the callback function. scope
-   * will be the this object when callback executes.
-   */
-  forEachRule: function (callback, scope) {
-    let ruleCount = 0;
-    let domRules = this.domSheet.cssRules;
-
-    function _iterator(domRule) {
-      if (domRule.type == CSSRule.STYLE_RULE) {
-        callback.call(scope, this.getRule(domRule));
-        ruleCount++;
-      } else if (domRule.type == CSSRule.MEDIA_RULE &&
-          domRule.cssRules && this._cssLogic.mediaMatches(domRule)) {
-        Array.prototype.forEach.call(domRule.cssRules, _iterator, this);
-      }
-    }
-
-    Array.prototype.forEach.call(domRules, _iterator, this);
-
-    this._ruleCount = ruleCount;
-  },
-
-  /**
-   * Process *some* rules in this stylesheet using your callback function. Your
-   * function receives one argument: the CssRule object for each CSSStyleRule
-   * inside the stylesheet. In order to stop processing the callback function
-   * needs to return a value.
-   *
-   * Note that this method also iterates through @media rules inside the
-   * stylesheet.
-   *
-   * @param {function} callback the function you want to execute for each of
-   * the style rules.
-   * @param {object} scope the scope you want for the callback function. scope
-   * will be the this object when callback executes.
-   * @return {Boolean} true if callback returns true during any iteration,
-   * otherwise false is returned.
-   */
-  forSomeRules: function (callback, scope) {
-    let domRules = this.domSheet.cssRules;
-    function _iterator(domRule) {
-      if (domRule.type == CSSRule.STYLE_RULE) {
-        return callback.call(scope, this.getRule(domRule));
-      } else if (domRule.type == CSSRule.MEDIA_RULE &&
-          domRule.cssRules && this._cssLogic.mediaMatches(domRule)) {
-        return Array.prototype.some.call(domRule.cssRules, _iterator, this);
-      }
-      return false;
-    }
-    return Array.prototype.some.call(domRules, _iterator, this);
-  },
-
-  toString: function () {
-    return "CssSheet[" + this.shortSource + "]";
-  }
-};
-
-/**
- * Information about a single CSSStyleRule.
- *
- * @param {CSSSheet|null} cssSheet the CssSheet object of the stylesheet that
- * holds the CSSStyleRule. If the rule comes from element.style, set this
- * argument to null.
- * @param {CSSStyleRule|object} domRule the DOM CSSStyleRule for which you want
- * to cache data. If the rule comes from element.style, then provide
- * an object of the form: {style: element.style}.
- * @param {Element} [element] If the rule comes from element.style, then this
- * argument must point to the element.
- * @constructor
- */
-function CssRule(cssSheet, domRule, element) {
-  this._cssSheet = cssSheet;
-  this.domRule = domRule;
-
-  let parentRule = domRule.parentRule;
-  if (parentRule && parentRule.type == CSSRule.MEDIA_RULE) {
-    this.mediaText = parentRule.media.mediaText;
-  }
-
-  if (this._cssSheet) {
-    // parse domRule.selectorText on call to this.selectors
-    this._selectors = null;
-    this.line = domUtils.getRuleLine(this.domRule);
-    this.source = this._cssSheet.shortSource + ":" + this.line;
-    if (this.mediaText) {
-      this.source += " @media " + this.mediaText;
-    }
-    this.href = this._cssSheet.href;
-    this.contentRule = this._cssSheet.contentSheet;
-  } else if (element) {
-    this._selectors = [ new CssSelector(this, "@element.style", 0) ];
-    this.line = -1;
-    this.source = CssLogic.l10n("rule.sourceElement");
-    this.href = "#";
-    this.contentRule = true;
-    this.sourceElement = element;
-  }
-}
-
-CssRule.prototype = {
-  _passId: null,
-
-  mediaText: "",
-
-  get isMediaRule() {
-    return !!this.mediaText;
-  },
-
-  /**
-   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
-   *
-   * @return {boolean} true if the parent stylesheet is allowed by the current
-   * sourceFilter, or false otherwise.
-   */
-  get sheetAllowed() {
-    return this._cssSheet ? this._cssSheet.sheetAllowed : true;
-  },
-
-  /**
-   * Retrieve the parent stylesheet index/position in the viewed document.
-   *
-   * @return {number} the parent stylesheet index/position in the viewed
-   * document.
-   */
-  get sheetIndex() {
-    return this._cssSheet ? this._cssSheet.index : 0;
-  },
-
-  /**
-   * Retrieve the style property value from the current CSSStyleRule.
-   *
-   * @param {string} property the CSS property name for which you want the
-   * value.
-   * @return {string} the property value.
-   */
-  getPropertyValue: function (property) {
-    return this.domRule.style.getPropertyValue(property);
-  },
-
-  /**
-   * Retrieve the style property priority from the current CSSStyleRule.
-   *
-   * @param {string} property the CSS property name for which you want the
-   * priority.
-   * @return {string} the property priority.
-   */
-  getPropertyPriority: function (property) {
-    return this.domRule.style.getPropertyPriority(property);
-  },
-
-  /**
-   * Retrieve the list of CssSelector objects for each of the parsed selectors
-   * of the current CSSStyleRule.
-   *
-   * @return {array} the array hold the CssSelector objects.
-   */
-  get selectors() {
-    if (this._selectors) {
-      return this._selectors;
-    }
-
-    // Parse the CSSStyleRule.selectorText string.
-    this._selectors = [];
-
-    if (!this.domRule.selectorText) {
-      return this._selectors;
-    }
-
-    let selectors = CssLogic.getSelectors(this.domRule);
-
-    for (let i = 0, len = selectors.length; i < len; i++) {
-      this._selectors.push(new CssSelector(this, selectors[i], i));
-    }
-
-    return this._selectors;
-  },
-
-  toString: function () {
-    return "[CssRule " + this.domRule.selectorText + "]";
-  },
-};
-
-/**
- * The CSS selector class allows us to document the ranking of various CSS
- * selectors.
- *
- * @constructor
- * @param {CssRule} cssRule the CssRule instance from where the selector comes.
- * @param {string} selector The selector that we wish to investigate.
- * @param {Number} index The index of the selector within it's rule.
- */
-function CssSelector(cssRule, selector, index) {
-  this.cssRule = cssRule;
-  this.text = selector;
-  this.elementStyle = this.text == "@element.style";
-  this._specificity = null;
-  this.selectorIndex = index;
 }
 
-exports.CssSelector = CssSelector;
-
-CssSelector.prototype = {
-  _matchId: null,
-
-  /**
-   * Retrieve the CssSelector source, which is the source of the CssSheet owning
-   * the selector.
-   *
-   * @return {string} the selector source.
-   */
-  get source() {
-    return this.cssRule.source;
-  },
-
-  /**
-   * Retrieve the CssSelector source element, which is the source of the CssRule
-   * owning the selector. This is only available when the CssSelector comes from
-   * an element.style.
-   *
-   * @return {string} the source element selector.
-   */
-  get sourceElement() {
-    return this.cssRule.sourceElement;
-  },
-
-  /**
-   * Retrieve the address of the CssSelector. This points to the address of the
-   * CssSheet owning this selector.
-   *
-   * @return {string} the address of the CssSelector.
-   */
-  get href() {
-    return this.cssRule.href;
-  },
-
-  /**
-   * Check if the selector comes from a browser-provided stylesheet.
-   *
-   * @return {boolean} true if the selector comes from a content-provided
-   * stylesheet, or false otherwise.
-   */
-  get contentRule() {
-    return this.cssRule.contentRule;
-  },
-
-  /**
-   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
-   *
-   * @return {boolean} true if the parent stylesheet is allowed by the current
-   * sourceFilter, or false otherwise.
-   */
-  get sheetAllowed() {
-    return this.cssRule.sheetAllowed;
-  },
-
-  /**
-   * Retrieve the parent stylesheet index/position in the viewed document.
-   *
-   * @return {number} the parent stylesheet index/position in the viewed
-   * document.
-   */
-  get sheetIndex() {
-    return this.cssRule.sheetIndex;
-  },
-
-  /**
-   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
-   *
-   * @return {number} the line of the parent CSSStyleRule in the parent
-   * stylesheet.
-   */
-  get ruleLine() {
-    return this.cssRule.line;
-  },
-
-  /**
-   * Retrieve specificity information for the current selector.
-   *
-   * @see http://www.w3.org/TR/css3-selectors/#specificity
-   * @see http://www.w3.org/TR/CSS2/selector.html
-   *
-   * @return {Number} The selector's specificity.
-   */
-  get specificity() {
-    if (this.elementStyle) {
-      // We can't ask specificity from DOMUtils as element styles don't provide
-      // CSSStyleRule interface DOMUtils expect. However, specificity of element
-      // style is constant, 1,0,0,0 or 0x01000000, just return the constant
-      // directly. @see http://www.w3.org/TR/CSS2/cascade.html#specificity
-      return 0x01000000;
-    }
-
-    if (this._specificity) {
-      return this._specificity;
-    }
-
-    this._specificity = domUtils.getSpecificity(this.cssRule.domRule,
-                                                this.selectorIndex);
-
-    return this._specificity;
-  },
-
-  toString: function () {
-    return this.text;
-  },
-};
-
-/**
- * A cache of information about the matched rules, selectors and values attached
- * to a CSS property, for the highlighted element.
- *
- * The heart of the CssPropertyInfo object is the _findMatchedSelectors()
- * method. This are invoked when the PropertyView tries to access the
- * .matchedSelectors array.
- * Results are cached, for later reuse.
- *
- * @param {CssLogic} cssLogic Reference to the parent CssLogic instance
- * @param {string} property The CSS property we are gathering information for
- * @param {function} isInherited A function that determines if the CSS property
- *                   is inherited.
- * @constructor
- */
-function CssPropertyInfo(cssLogic, property, isInherited) {
-  this._cssLogic = cssLogic;
-  this.property = property;
-  this._value = "";
-  this._isInherited = isInherited;
-
-  // The number of matched rules holding the this.property style property.
-  // Additionally, only rules that come from allowed stylesheets are counted.
-  this._matchedRuleCount = 0;
-
-  // An array holding CssSelectorInfo objects for each of the matched selectors
-  // that are inside a CSS rule. Only rules that hold the this.property are
-  // counted. This includes rules that come from filtered stylesheets (those
-  // that have sheetAllowed = false).
-  this._matchedSelectors = null;
-}
-
-CssPropertyInfo.prototype = {
-  /**
-   * Retrieve the computed style value for the current property, for the
-   * highlighted element.
-   *
-   * @return {string} the computed style value for the current property, for the
-   * highlighted element.
-   */
-  get value() {
-    if (!this._value && this._cssLogic.computedStyle) {
-      try {
-        this._value =
-          this._cssLogic.computedStyle.getPropertyValue(this.property);
-      } catch (ex) {
-        console.log("Error reading computed style for " + this.property);
-        console.log(ex);
-      }
-    }
-    return this._value;
-  },
-
-  /**
-   * Retrieve the number of matched rules holding the this.property style
-   * property. Only rules that come from allowed stylesheets are counted.
-   *
-   * @return {number} the number of matched rules.
-   */
-  get matchedRuleCount() {
-    if (!this._matchedSelectors) {
-      this._findMatchedSelectors();
-    } else if (this.needRefilter) {
-      this._refilterSelectors();
-    }
-
-    return this._matchedRuleCount;
-  },
-
-  /**
-   * Retrieve the array holding CssSelectorInfo objects for each of the matched
-   * selectors, from each of the matched rules. Only selectors coming from
-   * allowed stylesheets are included in the array.
-   *
-   * @return {array} the list of CssSelectorInfo objects of selectors that match
-   * the highlighted element and its parents.
-   */
-  get matchedSelectors() {
-    if (!this._matchedSelectors) {
-      this._findMatchedSelectors();
-    } else if (this.needRefilter) {
-      this._refilterSelectors();
-    }
-
-    return this._matchedSelectors;
-  },
-
-  /**
-   * Find the selectors that match the highlighted element and its parents.
-   * Uses CssLogic.processMatchedSelectors() to find the matched selectors,
-   * passing in a reference to CssPropertyInfo._processMatchedSelector() to
-   * create CssSelectorInfo objects, which we then sort
-   * @private
-   */
-  _findMatchedSelectors: function () {
-    this._matchedSelectors = [];
-    this._matchedRuleCount = 0;
-    this.needRefilter = false;
-
-    this._cssLogic.processMatchedSelectors(this._processMatchedSelector, this);
-
-    // Sort the selectors by how well they match the given element.
-    this._matchedSelectors.sort(function (selectorInfo1, selectorInfo2) {
-      if (selectorInfo1.status > selectorInfo2.status) {
-        return -1;
-      } else if (selectorInfo2.status > selectorInfo1.status) {
-        return 1;
-      }
-      return selectorInfo1.compareTo(selectorInfo2);
-    });
-
-    // Now we know which of the matches is best, we can mark it BEST_MATCH.
-    if (this._matchedSelectors.length > 0 &&
-        this._matchedSelectors[0].status > CssLogic.STATUS.UNMATCHED) {
-      this._matchedSelectors[0].status = CssLogic.STATUS.BEST;
-    }
-  },
-
-  /**
-   * Process a matched CssSelector object.
-   *
-   * @private
-   * @param {CssSelector} selector the matched CssSelector object.
-   * @param {CssLogic.STATUS} status the CssSelector match status.
-   */
-  _processMatchedSelector: function (selector, status) {
-    let cssRule = selector.cssRule;
-    let value = cssRule.getPropertyValue(this.property);
-    if (value &&
-        (status == CssLogic.STATUS.MATCHED ||
-         (status == CssLogic.STATUS.PARENT_MATCH &&
-          this._isInherited(this.property)))) {
-      let selectorInfo = new CssSelectorInfo(selector, this.property, value,
-          status);
-      this._matchedSelectors.push(selectorInfo);
-      if (this._cssLogic._passId !== cssRule._passId && cssRule.sheetAllowed) {
-        this._matchedRuleCount++;
-      }
-    }
-  },
-
-  /**
-   * Refilter the matched selectors array when the CssLogic.sourceFilter
-   * changes. This allows for quick filter changes.
-   * @private
-   */
-  _refilterSelectors: function () {
-    let passId = ++this._cssLogic._passId;
-    let ruleCount = 0;
-
-    let iterator = function (selectorInfo) {
-      let cssRule = selectorInfo.selector.cssRule;
-      if (cssRule._passId != passId) {
-        if (cssRule.sheetAllowed) {
-          ruleCount++;
-        }
-        cssRule._passId = passId;
-      }
-    };
-
-    if (this._matchedSelectors) {
-      this._matchedSelectors.forEach(iterator);
-      this._matchedRuleCount = ruleCount;
-    }
-
-    this.needRefilter = false;
-  },
-
-  toString: function () {
-    return "CssPropertyInfo[" + this.property + "]";
-  },
-};
-
-/**
- * A class that holds information about a given CssSelector object.
- *
- * Instances of this class are given to CssHtmlTree in the array of matched
- * selectors. Each such object represents a displayable row in the PropertyView
- * objects. The information given by this object blends data coming from the
- * CssSheet, CssRule and from the CssSelector that own this object.
- *
- * @param {CssSelector} selector The CssSelector object for which to
- *        present information.
- * @param {string} property The property for which information should
- *        be retrieved.
- * @param {string} value The property value from the CssRule that owns
- *        the selector.
- * @param {CssLogic.STATUS} status The selector match status.
- * @constructor
- */
-function CssSelectorInfo(selector, property, value, status) {
-  this.selector = selector;
-  this.property = property;
-  this.status = status;
-  this.value = value;
-  let priority = this.selector.cssRule.getPropertyPriority(this.property);
-  this.important = (priority === "important");
-}
-
-CssSelectorInfo.prototype = {
-  /**
-   * Retrieve the CssSelector source, which is the source of the CssSheet owning
-   * the selector.
-   *
-   * @return {string} the selector source.
-   */
-  get source() {
-    return this.selector.source;
-  },
-
-  /**
-   * Retrieve the CssSelector source element, which is the source of the CssRule
-   * owning the selector. This is only available when the CssSelector comes from
-   * an element.style.
-   *
-   * @return {string} the source element selector.
-   */
-  get sourceElement() {
-    return this.selector.sourceElement;
-  },
-
-  /**
-   * Retrieve the address of the CssSelector. This points to the address of the
-   * CssSheet owning this selector.
-   *
-   * @return {string} the address of the CssSelector.
-   */
-  get href() {
-    return this.selector.href;
-  },
-
-  /**
-   * Check if the CssSelector comes from element.style or not.
-   *
-   * @return {boolean} true if the CssSelector comes from element.style, or
-   * false otherwise.
-   */
-  get elementStyle() {
-    return this.selector.elementStyle;
-  },
-
-  /**
-   * Retrieve specificity information for the current selector.
-   *
-   * @return {object} an object holding specificity information for the current
-   * selector.
-   */
-  get specificity() {
-    return this.selector.specificity;
-  },
-
-  /**
-   * Retrieve the parent stylesheet index/position in the viewed document.
-   *
-   * @return {number} the parent stylesheet index/position in the viewed
-   * document.
-   */
-  get sheetIndex() {
-    return this.selector.sheetIndex;
-  },
-
-  /**
-   * Check if the parent stylesheet is allowed by the CssLogic.sourceFilter.
-   *
-   * @return {boolean} true if the parent stylesheet is allowed by the current
-   * sourceFilter, or false otherwise.
-   */
-  get sheetAllowed() {
-    return this.selector.sheetAllowed;
-  },
-
-  /**
-   * Retrieve the line of the parent CSSStyleRule in the parent CSSStyleSheet.
-   *
-   * @return {number} the line of the parent CSSStyleRule in the parent
-   * stylesheet.
-   */
-  get ruleLine() {
-    return this.selector.ruleLine;
-  },
-
-  /**
-   * Check if the selector comes from a browser-provided stylesheet.
-   *
-   * @return {boolean} true if the selector comes from a browser-provided
-   * stylesheet, or false otherwise.
-   */
-  get contentRule() {
-    return this.selector.contentRule;
-  },
-
-  /**
-   * Compare the current CssSelectorInfo instance to another instance, based on
-   * specificity information.
-   *
-   * @param {CssSelectorInfo} that The instance to compare ourselves against.
-   * @return number -1, 0, 1 depending on how that compares with this.
-   */
-  compareTo: function (that) {
-    if (!this.contentRule && that.contentRule) {
-      return 1;
-    }
-    if (this.contentRule && !that.contentRule) {
-      return -1;
-    }
-
-    if (this.elementStyle && !that.elementStyle) {
-      if (!this.important && that.important) {
-        return 1;
-      }
-      return -1;
-    }
-
-    if (!this.elementStyle && that.elementStyle) {
-      if (this.important && !that.important) {
-        return -1;
-      }
-      return 1;
-    }
-
-    if (this.important && !that.important) {
-      return -1;
-    }
-    if (that.important && !this.important) {
-      return 1;
-    }
-
-    if (this.specificity > that.specificity) {
-      return -1;
-    }
-    if (that.specificity > this.specificity) {
-      return 1;
-    }
-
-    if (this.sheetIndex > that.sheetIndex) {
-      return -1;
-    }
-    if (that.sheetIndex > this.sheetIndex) {
-      return 1;
-    }
-
-    if (this.ruleLine > that.ruleLine) {
-      return -1;
-    }
-    if (that.ruleLine > this.ruleLine) {
-      return 1;
-    }
-
-    return 0;
-  },
-
-  toString: function () {
-    return this.selector + " -> " + this.value;
-  },
-};
-
-DevToolsUtils.defineLazyGetter(this, "domUtils", function () {
-  return Cc["@mozilla.org/inspector/dom-utils;1"].getService(Ci.inIDOMUtils);
-});
+exports.prettifyCSS = prettifyCSS;
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -929,17 +929,16 @@ exports.Actor = Actor;
  *
  * @param function fn
  *    The implementation function, will be returned.
  * @param spec
  *    The method specification, with the following (optional) properties:
  *      request (object): a request template.
  *      response (object): a response template.
  *      oneway (bool): 'true' if no response should be sent.
- *      telemetry (string): Telemetry probe ID for measuring completion time.
  */
 exports.method = function (fn, spec = {}) {
   fn._methodSpec = Object.freeze(spec);
   if (spec.request) Object.freeze(spec.request);
   if (spec.response) Object.freeze(spec.response);
   return fn;
 };
 
@@ -971,34 +970,32 @@ var generateActorSpec = function (actorD
     }
 
     if (desc.value._methodSpec) {
       let methodSpec = desc.value._methodSpec;
       let spec = {};
       spec.name = methodSpec.name || name;
       spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
       spec.response = Response(methodSpec.response || undefined);
-      spec.telemetry = methodSpec.telemetry;
       spec.release = methodSpec.release;
       spec.oneway = methodSpec.oneway;
 
       actorSpec.methods.push(spec);
     }
   }
 
   // Find additional method specifications
   if (actorDesc.methods) {
     for (let name in actorDesc.methods) {
       let methodSpec = actorDesc.methods[name];
       let spec = {};
 
       spec.name = methodSpec.name || name;
       spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
       spec.response = Response(methodSpec.response || undefined);
-      spec.telemetry = methodSpec.telemetry;
       spec.release = methodSpec.release;
       spec.oneway = methodSpec.oneway;
 
       actorSpec.methods.push(spec);
     }
   }
 
   // Find event specifications
@@ -1341,37 +1338,16 @@ var generateRequestMethods = function (a
       // If the user doesn't need the impl don't generate it.
       if (!custom.impl) {
         return;
       }
       name = custom.impl;
     }
 
     frontProto[name] = function (...args) {
-      let histogram, startTime;
-      if (spec.telemetry) {
-        if (spec.oneway) {
-          // That just doesn't make sense.
-          throw Error("Telemetry specified for a oneway request");
-        }
-        let transportType = this.conn.localTransport
-          ? "LOCAL_"
-          : "REMOTE_";
-        let histogramId = "DEVTOOLS_DEBUGGER_RDP_"
-          + transportType + spec.telemetry + "_MS";
-        try {
-          histogram = Services.telemetry.getHistogramById(histogramId);
-          startTime = new Date();
-        } catch (ex) {
-          // XXX: Is this expected in xpcshell tests?
-          console.error(ex);
-          spec.telemetry = false;
-        }
-      }
-
       let packet;
       try {
         packet = spec.request.write(args, this);
       } catch (ex) {
         console.error("Error writing request: " + name);
         throw ex;
       }
       if (spec.oneway) {
@@ -1383,21 +1359,16 @@ var generateRequestMethods = function (a
       return this.request(packet).then(response => {
         let ret;
         try {
           ret = spec.response.read(response, this);
         } catch (ex) {
           console.error("Error reading response to: " + name);
           throw ex;
         }
-
-        if (histogram) {
-          histogram.add(+new Date - startTime);
-        }
-
         return ret;
       });
     };
 
     // Release methods should call the destroy function on return.
     if (spec.release) {
       let fn = frontProto[name];
       frontProto[name] = function (...args) {
--- a/devtools/shared/specs/addons.js
+++ b/devtools/shared/specs/addons.js
@@ -7,14 +7,13 @@ const {Arg, RetVal, generateActorSpec} =
 
 const addonsSpec = generateActorSpec({
   typeName: "addons",
 
   methods: {
     installTemporaryAddon: {
       request: { addonPath: Arg(0, "string") },
       response: { addon: RetVal("json") },
-      telemetry: "INSTALL_TEMPORARY_ADDON"
     },
   },
 });
 
 exports.addonsSpec = addonsSpec;
--- a/devtools/shared/tests/unit/test_prettifyCSS.js
+++ b/devtools/shared/tests/unit/test_prettifyCSS.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 // Test prettifyCSS.
 
 "use strict";
 
-const {CssLogic} = require("devtools/shared/inspector/css-logic");
+const {prettifyCSS} = require("devtools/shared/inspector/css-logic");
 
 const TESTS = [
   { name: "simple test",
     input: "div { font-family:'Arial Black', Arial, sans-serif; }",
     expected: [
       "div {",
       "\tfont-family:'Arial Black', Arial, sans-serif;",
       "}"
@@ -47,22 +47,22 @@ const TESTS = [
       "div {",
       "\tcolor: red;",
       "}"
     ]
   },
 ];
 
 function run_test() {
-  // Note that CssLogic.LINE_SEPARATOR is computed lazily, so we
+  // Note that prettifyCSS.LINE_SEPARATOR is computed lazily, so we
   // ensure it is set.
-  CssLogic.prettifyCSS("");
+  prettifyCSS("");
 
   for (let test of TESTS) {
     do_print(test.name);
 
-    let input = test.input.split("\n").join(CssLogic.LINE_SEPARATOR);
-    let output = CssLogic.prettifyCSS(input);
-    let expected = test.expected.join(CssLogic.LINE_SEPARATOR) +
-        CssLogic.LINE_SEPARATOR;
+    let input = test.input.split("\n").join(prettifyCSS.LINE_SEPARATOR);
+    let output = prettifyCSS(input);
+    let expected = test.expected.join(prettifyCSS.LINE_SEPARATOR) +
+        prettifyCSS.LINE_SEPARATOR;
     equal(output, expected, test.name);
   }
 }
--- a/intl/hyphenation/glue/nsHyphenationManager.cpp
+++ b/intl/hyphenation/glue/nsHyphenationManager.cpp
@@ -14,16 +14,18 @@
 #include "nsDirectoryServiceDefs.h"
 #include "nsNetUtil.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsZipArchive.h"
 #include "mozilla/Services.h"
 #include "nsIObserverService.h"
 #include "nsCRT.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
 
 using namespace mozilla;
 
 static const char kIntlHyphenationAliasPrefix[] = "intl.hyphenation-alias.";
 static const char kMemoryPressureNotification[] = "memory-pressure";
 
 nsHyphenationManager *nsHyphenationManager::sInstance = nullptr;
 
@@ -163,16 +165,24 @@ nsHyphenationManager::LoadPatternList()
                    NS_GET_IID(nsIFile), getter_AddRefs(appDir));
   if (NS_SUCCEEDED(rv)) {
     appDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
     bool equals;
     if (NS_SUCCEEDED(appDir->Equals(greDir, &equals)) && !equals) {
       LoadPatternListFromDir(appDir);
     }
   }
+
+  nsCOMPtr<nsIFile> profileDir;
+  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_LOCAL_50_DIR,
+                                       getter_AddRefs(profileDir));
+  if (NS_SUCCEEDED(rv)) {
+      profileDir->AppendNative(NS_LITERAL_CSTRING("hyphenation"));
+      LoadPatternListFromDir(profileDir);
+  }
 }
 
 void
 nsHyphenationManager::LoadPatternListFromOmnijar(Omnijar::Type aType)
 {
   nsCString base;
   nsresult rv = Omnijar::GetURIString(aType, base);
   if (NS_FAILED(rv)) {
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -756,16 +756,19 @@ public final class BrowserDatabaseHelper
         }
     }
 
     /**
      * We used to have a separate history extensions database which was used by Sync to store arrays
      * of visits for individual History GUIDs. It was only used by Sync.
      * This function migrates contents of that database over to the Visits table.
      *
+     * Warning to callers: this method might throw IllegalStateException if we fail to allocate a
+     * cursor to read HistoryExtensionsDB data for whatever reason. See Bug 1280409.
+     *
      * @param historyExtensionDb Source History Extensions database
      * @param db Destination database
      */
     private void copyHistoryExtensionDataToVisitsTable(final SQLiteDatabase historyExtensionDb, final SQLiteDatabase db) {
         final String historyExtensionTable = "HistoryExtension";
         final String columnGuid = "guid";
         final String columnVisits = "visits";
 
@@ -1767,24 +1770,30 @@ public final class BrowserDatabaseHelper
         // Otherwise, we risk overwhelming their Top Sites with remote history, just as we did before this migration.
         try {
             // If FxAccount exists (Sync is enabled) then port data over to the Visits table.
             if (FirefoxAccounts.firefoxAccountsExist(mContext)) {
                 try {
                     historyExtensionDb = SQLiteDatabase.openDatabase(historyExtensionsDatabase.getPath(), null,
                             SQLiteDatabase.OPEN_READONLY);
 
+                    if (historyExtensionDb != null) {
+                        copyHistoryExtensionDataToVisitsTable(historyExtensionDb, db);
+                    }
+
                 // If we fail to open HistoryExtensionDatabase, then synthesize visits marking them as remote
                 } catch (SQLiteException e) {
                     Log.w(LOGTAG, "Couldn't open history extension database; synthesizing visits instead", e);
                     synthesizeAndInsertVisits(db, false);
-                }
 
-                if (historyExtensionDb != null) {
-                    copyHistoryExtensionDataToVisitsTable(historyExtensionDb, db);
+                // It's possible that we might fail to copy over visit data from the HistoryExtensionsDB,
+                // so let's synthesize visits marking them as remote. See Bug 1280409.
+                } catch (IllegalStateException e) {
+                    Log.w(LOGTAG, "Couldn't copy over history extension data; synthesizing visits instead", e);
+                    synthesizeAndInsertVisits(db, false);
                 }
 
             // FxAccount doesn't exist, but there's evidence Sync was enabled at some point.
             // Synthesize visits from History table marking them all as remote.
             } else if (historyExtensionsDatabase.exists()) {
                 synthesizeAndInsertVisits(db, false);
 
             // FxAccount doesn't exist and there's no evidence sync was ever enabled.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -795,26 +795,28 @@ public class BrowserProvider extends Sha
                 " LEFT OUTER JOIN " +
                 " (SELECT position FROM numbers WHERE position NOT IN (SELECT " + Bookmarks.POSITION + " " + pinnedSitesFromClause + ")) AS free_ids" +
                 " ON numbers.position > free_ids.position" +
                 " GROUP BY numbers.position" +
                 " ORDER BY numbers.position ASC" +
                 " LIMIT " + suggestedGridLimit;
 
         // Filter out: unvisited pages (history_id == -1) pinned (and other special) sites, deleted sites,
-        // and about: pages.
+        // pages which weren't visited locally, and about: pages.
         final String ignoreForTopSitesWhereClause =
                 "(" + Combined.HISTORY_ID + " IS NOT -1)" +
                 " AND " +
                 Combined.URL + " NOT IN (SELECT " +
                 Bookmarks.URL + " FROM bookmarks WHERE " +
                 DBUtils.qualifyColumn("bookmarks", Bookmarks.PARENT) + " < " + Bookmarks.FIXED_ROOT_ID + " AND " +
                 DBUtils.qualifyColumn("bookmarks", Bookmarks.IS_DELETED) + " == 0)" +
                 " AND " +
-                "(" + Combined.URL + " NOT LIKE ?)";
+                "(" + Combined.URL + " NOT LIKE ?)" +
+                " AND " +
+                "(" + Combined.LOCAL_VISITS_COUNT + " > 0)";
 
         final String[] ignoreForTopSitesArgs = new String[] {
                 AboutPages.URL_FILTER
         };
 
         // Stuff the suggested sites into SQL: this allows us to filter pinned and topsites out of the suggested
         // sites list as part of the final query (as opposed to walking cursors in java)
         final SuggestedSites suggestedSites = GeckoProfile.get(getContext(), uri.getQueryParameter(BrowserContract.PARAM_PROFILE)).getDB().getSuggestedSites();
--- a/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
+++ b/mobile/android/base/java/org/mozilla/gecko/gfx/DynamicToolbarAnimator.java
@@ -8,18 +8,18 @@ package org.mozilla.gecko.gfx;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.FloatUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.graphics.PointF;
 import android.support.v4.view.ViewCompat;
 import android.util.Log;
-import android.view.animation.DecelerateInterpolator;
 import android.view.MotionEvent;
+import android.view.animation.LinearInterpolator;
 
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.EnumSet;
 import java.util.List;
 import java.util.Set;
 
 public class DynamicToolbarAnimator {
@@ -31,17 +31,17 @@ public class DynamicToolbarAnimator {
         ACTION_MODE,
         FULL_SCREEN,
         CARET_DRAG
     }
 
     private final Set<PinReason> pinFlags = Collections.synchronizedSet(EnumSet.noneOf(PinReason.class));
 
     // The duration of the animation in ns
-    private static final long ANIMATION_DURATION = 250000000;
+    private static final long ANIMATION_DURATION = 150000000;
 
     private final GeckoLayerClient mTarget;
     private final List<LayerView.DynamicToolbarListener> mListeners;
 
     /* The translation to be applied to the toolbar UI view. This is the
      * distance from the default/initial location (at the top of the screen,
      * visible to the user) to where we want it to be. This variable should
      * always be between 0 (toolbar fully visible) and the height of the toolbar
@@ -58,17 +58,17 @@ public class DynamicToolbarAnimator {
     private float mLayerViewTranslation;
 
     /* This stores the maximum translation that can be applied to the toolbar
      * and layerview when scrolling. This is populated with the height of the
      * toolbar. */
     private float mMaxTranslation;
 
     /* This interpolator is used for the above mentioned animation */
-    private DecelerateInterpolator mInterpolator;
+    private LinearInterpolator mInterpolator;
 
     /* This is the proportion of the viewport rect that needs to be travelled
      * while scrolling before the translation will start taking effect.
      */
     private float SCROLL_TOOLBAR_THRESHOLD = 0.20f;
     /* The ID of the prefs listener for the scroll-toolbar threshold */
     private final PrefsHelper.PrefHandler mPrefObserver;
 
@@ -97,17 +97,17 @@ public class DynamicToolbarAnimator {
 
     /* Set to true when root content is being scrolled */
     private boolean mScrollingRootContent;
 
     public DynamicToolbarAnimator(GeckoLayerClient aTarget) {
         mTarget = aTarget;
         mListeners = new ArrayList<LayerView.DynamicToolbarListener>();
 
-        mInterpolator = new DecelerateInterpolator();
+        mInterpolator = new LinearInterpolator();
 
         // Listen to the dynamic toolbar pref
         mPrefObserver = new PrefsHelper.PrefHandlerBase() {
             @Override
             public void prefValue(String pref, int value) {
                 SCROLL_TOOLBAR_THRESHOLD = value / 100.0f;
             }
         };
--- a/mobile/android/components/NSSDialogService.js
+++ b/mobile/android/components/NSSDialogService.js
@@ -11,38 +11,54 @@ Cu.import("resource://gre/modules/Servic
 
 XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
                                   "resource://gre/modules/Prompt.jsm");
 
 // -----------------------------------------------------------------------
 // NSS Dialog Service
 // -----------------------------------------------------------------------
 
-function dump(a) {
-  Components.classes["@mozilla.org/consoleservice;1"].getService(Ci.nsIConsoleService).logStringMessage(a);
-}
-
 function NSSDialogs() { }
 
 NSSDialogs.prototype = {
   classID: Components.ID("{cbc08081-49b6-4561-9c18-a7707a50bda1}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsICertificateDialogs, Ci.nsIClientAuthDialogs]),
 
+  /**
+   * Escapes the given input via HTML entity encoding. Used to prevent HTML
+   * injection when the input is to be placed inside an HTML body, but not in
+   * any other context.
+   *
+   * @param {String} input The input to interpret as a plain string.
+   * @returns {String} The escaped input.
+   */
+  escapeHTML: function(input) {
+    return input.replace(/&/g, "&amp;")
+                .replace(/</g, "&lt;")
+                .replace(/>/g, "&gt;")
+                .replace(/"/g, "&quot;")
+                .replace(/'/g, "&#x27;")
+                .replace(/\//g, "&#x2F;");
+  },
+
   getString: function(aName) {
     if (!this.bundle) {
         this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties");
     }
     return this.bundle.GetStringFromName(aName);
   },
 
   formatString: function(aName, argList) {
     if (!this.bundle) {
-      this.bundle = Services.strings.createBundle("chrome://browser/locale/pippki.properties");
+      this.bundle =
+        Services.strings.createBundle("chrome://browser/locale/pippki.properties");
     }
-    return this.bundle.formatStringFromName(aName, argList, argList.length);
+    let escapedArgList = Array.from(argList, x => this.escapeHTML(x));
+    return this.bundle.formatStringFromName(aName, escapedArgList,
+                                            escapedArgList.length);
   },
 
   getPrompt: function(aTitle, aText, aButtons) {
     return new Prompt({
       title: aTitle,
       text: aText,
       buttons: aButtons,
     });
@@ -109,42 +125,53 @@ NSSDialogs.prototype = {
       return false;
     }
 
     aPassword.value = response.pw;
     return true;
   },
 
   certInfoSection: function(aHeading, aDataPairs, aTrailingNewline = true) {
-    var str = "<big>" + this.getString(aHeading) + "</big><br/>";
-    for (var i = 0; i < aDataPairs.length; i += 2) {
-      str += this.getString(aDataPairs[i]) + ": " + aDataPairs[i+1] + "<br/>";
+    let certInfoStrings = [
+      "<big>" + this.getString(aHeading) + "</big>",
+    ];
+
+    for (let i = 0; i < aDataPairs.length; i += 2) {
+      let key = aDataPairs[i];
+      let value = aDataPairs[i + 1];
+      certInfoStrings.push(this.formatString(key, [value]));
     }
-    return str + (aTrailingNewline ? "<br/>" : "");
+
+    if (aTrailingNewline) {
+      certInfoStrings.push("<br/>");
+    }
+
+    return certInfoStrings.join("<br/>");
   },
 
   viewCert: function(aCtx, aCert) {
-    let p = this.getPrompt(this.getString("certmgr.title"),
-                    "",
-                    [ this.getString("nssdialogs.ok.label") ])
+    let p = this.getPrompt(this.getString("certmgr.title"), "", [
+                             this.getString("nssdialogs.ok.label"),
+                           ]);
     p.addLabel({ label: this.certInfoSection("certmgr.subjectinfo.label",
-                          ["certmgr.certdetail.cn", aCert.commonName,
-                           "certmgr.certdetail.o", aCert.organization,
-                           "certmgr.certdetail.ou", aCert.organizationalUnit,
-                           "certmgr.certdetail.serialnumber", aCert.serialNumber])})
+                          ["certdetail.cn", aCert.commonName,
+                           "certdetail.o", aCert.organization,
+                           "certdetail.ou", aCert.organizationalUnit,
+                           "certdetail.serialnumber", aCert.serialNumber])})
      .addLabel({ label: this.certInfoSection("certmgr.issuerinfo.label",
-                          ["certmgr.certdetail.cn", aCert.issuerCommonName,
-                           "certmgr.certdetail.o", aCert.issuerOrganization,
-                           "certmgr.certdetail.ou", aCert.issuerOrganizationUnit])})
+                          ["certdetail.cn", aCert.issuerCommonName,
+                           "certdetail.o", aCert.issuerOrganization,
+                           "certdetail.ou", aCert.issuerOrganizationUnit])})
      .addLabel({ label: this.certInfoSection("certmgr.periodofvalidity.label",
-                          ["certmgr.begins", aCert.validity.notBeforeLocalDay,
-                           "certmgr.expires", aCert.validity.notAfterLocalDay])})
+                          ["certdetail.notBefore", aCert.validity.notBeforeLocalDay,
+                           "certdetail.notAfter", aCert.validity.notAfterLocalDay])})
      .addLabel({ label: this.certInfoSection("certmgr.fingerprints.label",
-                          ["certmgr.certdetail.sha256fingerprint", aCert.sha256Fingerprint,
-                           "certmgr.certdetail.sha1fingerprint", aCert.sha1Fingerprint], false) });
+                          ["certdetail.sha256fingerprint", aCert.sha256Fingerprint,
+                           "certdetail.sha1fingerprint", aCert.sha1Fingerprint],
+                          false) });
     this.showPrompt(p);
   },
 
   /**
    * Returns a list of details of the given cert relevant for TLS client
    * authentication.
    *
    * @param {nsIX509Cert} cert Cert to get the details of.
@@ -186,17 +213,17 @@ NSSDialogs.prototype = {
   },
 
   chooseCertificate: function(ctx, cnAndPort, organization, issuerOrg, certList,
                               selectedIndex) {
     let rememberSetting =
       Services.prefs.getBoolPref("security.remember_cert_checkbox_default_setting");
 
     let serverRequestedDetails = [
-      cnAndPort,
+      this.escapeHTML(cnAndPort),
       this.formatString("clientAuthAsk.organization", [organization]),
       this.formatString("clientAuthAsk.issuer", [issuerOrg]),
     ].join("<br/>");
 
     let certNickList = [];
     let certDetailsList = [];
     for (let i = 0; i < certList.length; i++) {
       let cert = certList.queryElementAt(i, Ci.nsIX509Cert);
--- a/mobile/android/components/extensions/ext-pageAction.js
+++ b/mobile/android/components/extensions/ext-pageAction.js
@@ -10,58 +10,82 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 // Import the android PageActions module.
 XPCOMUtils.defineLazyModuleGetter(this, "PageActions",
                                   "resource://gre/modules/PageActions.jsm");
 
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 
 var {
+  IconDetails,
   SingletonEventManager,
 } = ExtensionUtils;
 
 // WeakMap[Extension -> PageAction]
 var pageActionMap = new WeakMap();
 
 function PageAction(options, extension) {
   this.id = null;
 
-  let DEFAULT_ICON = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+  this.extension = extension;
+  this.icons = IconDetails.normalize({path: options.default_icon}, extension);
 
   this.popupUrl = options.default_popup;
 
   this.options = {
     title: options.default_title || extension.name,
-    icon: DEFAULT_ICON,
     id: extension.id,
     clickCallback: () => {
       if (this.popupUrl) {
         let win = Services.wm.getMostRecentWindow("navigator:browser");
         win.BrowserApp.addTab(this.popupUrl, {
           selected: true,
           parentId: win.BrowserApp.selectedTab.id,
         });
       } else {
         this.emit("click");
       }
     },
   };
 
+  this.shouldShow = false;
+
   EventEmitter.decorate(this);
 }
 
 PageAction.prototype = {
-  show(tabId) {
-    // TODO: Only show the PageAction for the tab with the provided tabId.
-    if (!this.id) {
+  show(tabId, context) {
+    if (this.id) {
+      return Promise.resolve();
+    }
+
+    if (this.options.icon) {
       this.id = PageActions.add(this.options);
+      return Promise.resolve();
     }
+
+    this.shouldShow = true;
+
+    let {icon} = IconDetails.getURL(this.icons, context.contentWindow, this.extension, 18);
+
+    let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
+    return IconDetails.convertImageURLToDataURL(icon, context, browserWindow).then(dataURI => {
+      if (this.shouldShow) {
+        this.options.icon = dataURI;
+        this.id = PageActions.add(this.options);
+      }
+    }).catch(() => {
+      return Promise.reject({
+        message: "Failed to load PageAction icon",
+      });
+    });
   },
 
   hide(tabId) {
+    this.shouldShow = false;
     if (this.id) {
       PageActions.remove(this.id);
       this.id = null;
     }
   },
 
   setPopup(tab, url) {
     // TODO: Only set the popup for the specified tab once we have Tabs API support.
@@ -101,21 +125,24 @@ extensions.registerSchemaAPI("pageAction
         };
         pageActionMap.get(extension).on("click", listener);
         return () => {
           pageActionMap.get(extension).off("click", listener);
         };
       }).api(),
 
       show(tabId) {
-        pageActionMap.get(extension).show(tabId);
+        return pageActionMap.get(extension)
+                            .show(tabId, context)
+                            .then(() => {});
       },
 
       hide(tabId) {
         pageActionMap.get(extension).hide(tabId);
+        return Promise.resolve();
       },
 
       setPopup(details) {
         // TODO: Use the Tabs API to get the tab from details.tabId.
         let tab = null;
         let url = details.popup && context.uri.resolve(details.popup);
         pageActionMap.get(extension).setPopup(tab, url);
       },
--- a/mobile/android/components/extensions/schemas/page_action.json
+++ b/mobile/android/components/extensions/schemas/page_action.json
@@ -14,17 +14,16 @@
             "additionalProperties": { "$ref": "UnrecognizedProperty" },
             "properties": {
               "default_title": {
                 "type": "string",
                 "optional": true,
                 "preprocess": "localize"
               },
               "default_icon": {
-                "unsupported": true,
                 "$ref": "IconPath",
                 "optional": true
               },
               "default_popup": {
                 "type": "string",
                 "format": "relativeUrl",
                 "optional": true,
                 "preprocess": "localize"
@@ -52,24 +51,26 @@
         "description": "Pixel data for an image. Must be an ImageData object (for example, from a <code>canvas</code> element)."
       }
     ],
     "functions": [
       {
         "name": "show",
         "type": "function",
         "description": "Shows the page action. The page action is shown whenever the tab is selected.",
+        "async": true,
         "parameters": [
           {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
         ]
       },
       {
         "name": "hide",
         "type": "function",
         "description": "Hides the page action.",
+        "async": true,
         "parameters": [
           {"type": "integer", "name": "tabId", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."}
         ]
       },
       {
         "name": "setTitle",
         "unsupported": true,
         "type": "function",
@@ -156,16 +157,17 @@
             "optional": true,
             "parameters": []
           }
         ]
       },
       {
         "name": "setPopup",
         "type": "function",
+        "async": true,
         "description": "Sets the html document to be opened as a popup when the user clicks on the page action's icon.",
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {"type": "integer", "minimum": 0, "description": "The id of the tab for which you want to modify the page action."},
               "popup": {
@@ -175,37 +177,27 @@
             }
           }
         ]
       },
       {
         "name": "getPopup",
         "type": "function",
         "description": "Gets the html document set as the popup for this page action.",
-        "async": "callback",
+        "async": true,
         "parameters": [
           {
             "name": "details",
             "type": "object",
             "properties": {
               "tabId": {
                 "type": "integer",
                 "description": "Specify the tab to get the popup from."
               }
             }
-          },
-          {
-            "type": "function",
-            "name": "callback",
-            "parameters": [
-              {
-                "name": "result",
-                "type": "string"
-              }
-            ]
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onClicked",
         "type": "function",
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction.html
@@ -8,29 +8,36 @@
   <script type="text/javascript" src="head.js"></script>
   <link rel="stylesheet" href="chrome://mochikit/contents/tests/SimpleTest/test.css"/>
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
+let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+
+let image = atob(dataURI);
+const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
+
 function backgroundScript() {
   browser.test.assertTrue("pageAction" in browser, "Namespace 'pageAction' exists in browser");
   browser.test.assertTrue("show" in browser.pageAction, "API method 'show' exists in browser.pageAction");
 
   // TODO: Use the Tabs API to obtain the tab ids for showing pageActions.
   let tabId = 1;
   browser.test.onMessage.addListener(msg => {
     if (msg === "pageAction-show") {
-      browser.pageAction.show(tabId);
-      browser.test.sendMessage("page-action-shown");
+      browser.pageAction.show(tabId).then(() => {
+        browser.test.sendMessage("page-action-shown");
+      });
     } else if (msg === "pageAction-hide") {
-      browser.pageAction.hide(tabId);
-      browser.test.sendMessage("page-action-hidden");
+      browser.pageAction.hide(tabId).then(() => {
+        browser.test.sendMessage("page-action-hidden");
+      });
     }
   });
 
   browser.pageAction.onClicked.addListener(tab => {
     // TODO: Make sure we get the correct tab once basic tabs support is added.
     browser.test.sendMessage("page-action-clicked");
   });
 
@@ -39,18 +46,24 @@ function backgroundScript() {
 
 add_task(function* test_contentscript() {
   let extension = ExtensionTestUtils.loadExtension({
     background: "(" + backgroundScript.toString() + ")()",
     manifest: {
       "name": "PageAction Extension",
       "page_action": {
         "default_title": "Page Action",
+        "default_icon": {
+          "18": "extension.png",
+        },
       },
     },
+    files: {
+      "extension.png": IMAGE_ARRAYBUFFER,
+    },
   });
 
   yield extension.startup();
   yield extension.awaitMessage("ready");
 
   extension.sendMessage("pageAction-show");
   yield extension.awaitMessage("page-action-shown");
   ok(isPageActionShown(extension.id), "The PageAction should be shown");
--- a/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html
+++ b/mobile/android/components/extensions/test/mochitest/test_ext_pageAction_popup.html
@@ -10,30 +10,37 @@
 </head>
 <body>
 
 <script type="text/javascript">
 "use strict";
 
 Cu.import("resource://gre/modules/Services.jsm");
 
+let dataURI = "iVBORw0KGgoAAAANSUhEUgAAACQAAAAkCAYAAADhAJiYAAAC4klEQVRYhdWXLWzbQBSADQtDAwsHC1tUhUxqfL67lk2tdn+OJg0ODU0rLByqgqINBY6tmlbn7LMTJ5FaFVVBk1G0oUGjG2jT2Y7jxmmcbU/6iJ+f36fz+e5sGP9riCGm9hB37RG+scd4Yo/wsDXCZyIE2xuXsce4bY+wXkAsQtzYmExrfFgvkJkRbkzo1ehoxx5iXcgI/9iYUGt8WH9MqDXEcmNChmEYrRCf2SHWeYgQx3x0tLNRIeKQLTtEFyJEep4NTuhk8BC+yMrwEE3+iozo42d8gK7FAOkMsRiiN8QhW2ttSK5QTfRRV4QoymVeJMvPvDp7gCZigD613MN6yRFA3SWarow9QB9LCfG+NeF9qCtjAKOSQjCqVKhfVsiHEQ+grgx/lRGqUihAc1uL8EFD+KCRO+GrF4J61phcoRoPoEzkYhZYpykh5sMb7kOdIeY+jHKur4QI4Feh4AFX1nVeLxrAvQchGsBz5ls6wa2QdwcvIcE2863bTH79KOvsz/uUYJsp+J0pSzNlDckVqqVGUAF+n6uS7txcOl6wot4JVy70ufDLy4pWLUQVPE81pRI0mGe9oxLMHSeohHvMs/STUNaUK6vDPCvOyxMFDx4achehRDJmHnydnkPww5OFfLxrGIZBFDyYl4LpMzlTQFIP6AQx86w2UeYBccFpJrcKv5L9eGDtUAU6RIELqsB74uynjy/UBRF1gS5BTFxwQT1wTiXoUg9MH7m/3NZRRoi5IJytUbMgzv4Wc832+oQkiKgEehmyMkkpKsFkQV11QsRJL5rJYBLItQgRaUZEmnoZXsomz3vGiWw+I9KMF9SVFOqZEemZekli1jN3U/UOqhHHvC6oWWGElhfSpGdOk6+O9prdwvtLj5BjRsQxdRnot+Zeifpy/2/0stktKTRNLmbk0mwXyl8253fyojj+8rxOHNAhjjm5n0/5OOCGOKBzkrMO0Z75lvSAzKlrF32Z/3z8BqLAn+yMV7VhAAAAAElFTkSuQmCC";
+
+let image = atob(dataURI);
+const IMAGE_ARRAYBUFFER = Uint8Array.from(image, byte => byte.charCodeAt(0)).buffer;
+
 add_task(function* test_contentscript() {
   function backgroundScript() {
     // TODO: Use the Tabs API to obtain the tab ids for showing pageActions.
     let tabId = 1;
     let onClickedListenerEnabled = false;
 
     browser.test.onMessage.addListener((msg, details) => {
       if (msg === "page-action-show") {
         // TODO: switch to using .show(tabId).then(...) once bug 1270742 lands.
-        browser.pageAction.show(tabId);
-        browser.test.sendMessage("page-action-shown");
+        browser.pageAction.show(tabId).then(() => {
+          browser.test.sendMessage("page-action-shown");
+        });
       } else if (msg == "page-action-set-popup") {
-        browser.pageAction.setPopup({popup: details.name, tabId: tabId});
-        browser.test.sendMessage("page-action-popup-set");
+        browser.pageAction.setPopup({popup: details.name, tabId: tabId}).then(() => {
+          browser.test.sendMessage("page-action-popup-set");
+        });
       } else if (msg == "page-action-get-popup") {
         browser.pageAction.getPopup({tabId: tabId}).then(url => {
           browser.test.sendMessage("page-action-got-popup", url);
         });
       } else if (msg == "page-action-enable-onClicked-listener") {
         onClickedListenerEnabled = true;
         browser.test.sendMessage("page-action-onClicked-listener-enabled");
       } else if (msg == "page-action-disable-onClicked-listener") {
@@ -65,20 +72,24 @@ add_task(function* test_contentscript() 
 
   let extension = ExtensionTestUtils.loadExtension({
     background: `(${backgroundScript}())`,
     manifest: {
       "name": "PageAction Extension",
       "page_action": {
         "default_title": "Page Action",
         "default_popup": "default.html",
+        "default_icon": {
+          "18": "extension.png",
+        },
       },
     },
     files: {
       "default.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
+      "extension.png": IMAGE_ARRAYBUFFER,
       "a.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
       "b.html": `<html><head><meta charset="utf-8"><script src="popup.js"></${"script"}></head></html>`,
       "popup.js": `(${popupScript})()`,
     },
   });
 
   let tabClosedPromise = () => {
     return new Promise(resolve => {
--- a/mobile/android/locales/en-US/chrome/pippki.properties
+++ b/mobile/android/locales/en-US/chrome/pippki.properties
@@ -56,16 +56,26 @@ clientAuthAsk.storedOn=Stored on: %1$S
 clientAuthAsk.viewCert.label=View
 
 certmgr.title=Certificate Details
 # These strings are stolen from security/manager/locales/en-US/chrome/pippki/certManager.dtd
 certmgr.subjectinfo.label=Issued To
 certmgr.issuerinfo.label=Issued By
 certmgr.periodofvalidity.label=Period of Validity
 certmgr.fingerprints.label=Fingerprints
-certmgr.certdetail.cn=Common Name (CN)
-certmgr.certdetail.o=Organization (O)
-certmgr.certdetail.ou=Organizational Unit (OU)
-certmgr.certdetail.serialnumber=Serial Number
-certmgr.certdetail.sha256fingerprint=SHA-256 Fingerprint
-certmgr.certdetail.sha1fingerprint=SHA1 Fingerprint
-certmgr.begins=Begins On
-certmgr.expires=Expires On
+certdetail.cn=Common Name (CN): %1$S
+certdetail.o=Organization (O): %1$S
+certdetail.ou=Organizational Unit (OU): %1$S
+# LOCALIZATION NOTE(certdetail.serialnumber): %1$S is the serial number of the
+# cert being viewed in AA:BB:CC hex format.
+certdetail.serialnumber=Serial Number: %1$S
+# LOCALIZATION NOTE(certdetail.sha256fingerprint): %1$S is the SHA-256
+# Fingerprint of the cert being viewed in AA:BB:CC hex format.
+certdetail.sha256fingerprint=SHA-256 Fingerprint: %1$S
+# LOCALIZATION NOTE(certdetail.sha1fingerprint): %1$S is the SHA-1 Fingerprint
+# of the cert being viewed in AA:BB:CC hex format.
+certdetail.sha1fingerprint=SHA1 Fingerprint: %1$S
+# LOCALIZATION NOTE(certdetail.notBefore): %1$S is the already localized
+# notBefore date of the cert being viewed.
+certdetail.notBefore=Begins On: %1$S
+# LOCALIZATION NOTE(certdetail.notAfter): %1$S is the already localized notAfter
+# date of the cert being viewed.
+certdetail.notAfter=Expires On: %1$S
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -6,19 +6,23 @@
 
 this.EXPORTED_SYMBOLS = ["ExtensionUtils"];
 
 const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
+const INTEGER = /^[1-9]\d*$/;
+
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
                                   "resource://gre/modules/AppConstants.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LanguageDetector",
                                   "resource:///modules/translation/LanguageDetector.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
@@ -386,16 +390,162 @@ class BaseContext {
     });
 
     for (let obj of this.onClose) {
       obj.close();
     }
   }
 }
 
+// Manages icon details for toolbar buttons in the |pageAction| and
+// |browserAction| APIs.
+let IconDetails = {
+  // Normalizes the various acceptable input formats into an object
+  // with icon size as key and icon URL as value.
+  //
+  // If a context is specified (function is called from an extension):
+  // Throws an error if an invalid icon size was provided or the
+  // extension is not allowed to load the specified resources.
+  //
+  // If no context is specified, instead of throwing an error, this
+  // function simply logs a warning message.
+  normalize(details, extension, context = null) {
+    let result = {};
+
+    try {
+      if (details.imageData) {
+        let imageData = details.imageData;
+
+        // The global might actually be from Schema.jsm, which
+        // normalizes most of our arguments. In that case it won't have
+        // an ImageData property. But Schema.jsm doesn't normalize
+        // actual ImageData objects, so they will come from a global
+        // with the right property.
+        if (instanceOf(imageData, "ImageData")) {
+          imageData = {"19": imageData};
+        }
+
+        for (let size of Object.keys(imageData)) {
+          if (!INTEGER.test(size)) {
+            throw new Error(`Invalid icon size ${size}, must be an integer`);
+          }
+          result[size] = this.convertImageDataToDataURL(imageData[size], context);
+        }
+      }
+
+      if (details.path) {
+        let path = details.path;
+        if (typeof path != "object") {
+          path = {"19": path};
+        }
+
+        let baseURI = context ? context.uri : extension.baseURI;
+
+        for (let size of Object.keys(path)) {
+          if (!INTEGER.test(size)) {
+            throw new Error(`Invalid icon size ${size}, must be an integer`);
+          }
+
+          let url = baseURI.resolve(path[size]);
+
+          // The Chrome documentation specifies these parameters as
+          // relative paths. We currently accept absolute URLs as well,
+          // which means we need to check that the extension is allowed
+          // to load them. This will throw an error if it's not allowed.
+          Services.scriptSecurityManager.checkLoadURIStrWithPrincipal(
+            extension.principal, url,
+            Services.scriptSecurityManager.DISALLOW_SCRIPT);
+
+          result[size] = url;
+        }
+      }
+    } catch (e) {
+      // Function is called from extension code, delegate error.
+      if (context) {
+        throw e;
+      }
+      // If there's no context, it's because we're handling this
+      // as a manifest directive. Log a warning rather than
+      // raising an error.
+      extension.manifestError(`Invalid icon data: ${e}`);
+    }
+
+    return result;
+  },
+
+  // Returns the appropriate icon URL for the given icons object and the
+  // screen resolution of the given window.
+  getURL(icons, window, extension, size = 16) {
+    const DEFAULT = "chrome://browser/content/extension.svg";
+
+    size *= window.devicePixelRatio;
+
+    let bestSize = null;
+    if (icons[size]) {
+      bestSize = size;
+    } else if (icons[2 * size]) {
+      bestSize = 2 * size;
+    } else {
+      let sizes = Object.keys(icons)
+                        .map(key => parseInt(key, 10))
+                        .sort((a, b) => a - b);
+
+      bestSize = sizes.find(candidate => candidate > size) || sizes.pop();
+    }
+
+    if (bestSize) {
+      return {size: bestSize, icon: icons[bestSize]};
+    }
+
+    return {size, icon: DEFAULT};
+  },
+
+  convertImageURLToDataURL(imageURL, context, browserWindow, size = 18) {
+    return new Promise((resolve, reject) => {
+      let image = new context.contentWindow.Image();
+      image.onload = function() {
+        let canvas = context.contentWindow.document.createElement("canvas");
+        let ctx = canvas.getContext("2d");
+        let dSize = size * browserWindow.devicePixelRatio;
+
+        // Scales the image while maintaing width to height ratio.
+        // If the width and height differ, the image is centered using the
+        // smaller of the two dimensions.
+        let dWidth, dHeight, dx, dy;
+        if (this.width > this.height) {
+          dWidth = dSize;
+          dHeight = image.height * (dSize / image.width);
+          dx = 0;
+          dy = (dSize - dHeight) / 2;
+        } else {
+          dWidth = image.width * (dSize / image.height);
+          dHeight = dSize;
+          dx = (dSize - dWidth) / 2;
+          dy = 0;
+        }
+
+        ctx.drawImage(this, 0, 0, this.width, this.height, dx, dy, dWidth, dHeight);
+        resolve(canvas.toDataURL("image/png"));
+      };
+      image.onerror = reject;
+      image.src = imageURL;
+    });
+  },
+
+  convertImageDataToDataURL(imageData, context) {
+    let document = context.contentWindow.document;
+    let canvas = document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
+    canvas.width = imageData.width;
+    canvas.height = imageData.height;
+    canvas.getContext("2d").putImageData(imageData, 0, 0);
+
+    return canvas.toDataURL("image/png");
+  },
+};
+
 function LocaleData(data) {
   this.defaultLocale = data.defaultLocale;
   this.selectedLocale = data.selectedLocale;
   this.locales = data.locales || new Map();
   this.warnedMissingKeys = new Set();
 
   // Map(locale-name -> Map(message-key -> localized-string))
   //
@@ -403,16 +553,17 @@ function LocaleData(data) {
   // Map of message keys to their localized strings.
   this.messages = data.messages || new Map();
 
   if (data.builtinMessages) {
     this.messages.set(this.BUILTIN, data.builtinMessages);
   }
 }
 
+
 LocaleData.prototype = {
   // Representation of the object to send to content processes. This
   // should include anything the content process might need.
   serialize() {
     return {
       defaultLocale: this.defaultLocale,
       selectedLocale: this.selectedLocale,
       messages: this.messages,
@@ -1251,15 +1402,16 @@ this.ExtensionUtils = {
   promiseDocumentReady,
   runSafe,
   runSafeSync,
   runSafeSyncWithoutClone,
   runSafeWithoutClone,
   BaseContext,
   DefaultWeakMap,
   EventManager,
+  IconDetails,
   LocaleData,
   Messenger,
   PlatformInfo,
   SingletonEventManager,
   SpreadArgs,
   ChildAPIManager,
 };
--- a/toolkit/components/extensions/Schemas.jsm
+++ b/toolkit/components/extensions/Schemas.jsm
@@ -941,20 +941,21 @@ class ArrayType extends Type {
   }
 
   checkBaseType(baseType) {
     return baseType == "array";
   }
 }
 
 class FunctionType extends Type {
-  constructor(schema, parameters, isAsync) {
+  constructor(schema, parameters, isAsync, hasAsyncCallback) {
     super(schema);
     this.parameters = parameters;
     this.isAsync = isAsync;
+    this.hasAsyncCallback = hasAsyncCallback;
   }
 
   normalize(value, context) {
     return this.normalizeBase("function", value, context);
   }
 
   checkBaseType(baseType) {
     return baseType == "function";
@@ -1151,33 +1152,37 @@ class CallEntry extends Entry {
 class FunctionEntry extends CallEntry {
   constructor(schema, path, name, type, unsupported, allowAmbiguousOptionalArguments, returns, permissions) {
     super(schema, path, name, type.parameters, allowAmbiguousOptionalArguments);
     this.unsupported = unsupported;
     this.returns = returns;
     this.permissions = permissions;
 
     this.isAsync = type.isAsync;
+    this.hasAsyncCallback = type.hasAsyncCallback;
   }
 
   inject(path, name, dest, context) {
     if (this.unsupported) {
       return;
     }
 
     if (this.permissions && !this.permissions.some(perm => context.hasPermission(perm))) {
       return;
     }
 
     let stub;
     if (this.isAsync) {
       stub = (...args) => {
         this.checkDeprecated(context);
         let actuals = this.checkParameters(args, context);
-        let callback = actuals.pop();
+        let callback = null;
+        if (this.hasAsyncCallback) {
+          callback = actuals.pop();
+        }
         return context.callAsyncFunction(path, name, actuals, callback);
       };
     } else if (!this.returns) {
       stub = (...args) => {
         this.checkDeprecated(context);
         let actuals = this.checkParameters(args, context);
         return context.callFunctionNoReturn(path, name, actuals);
       };
@@ -1398,17 +1403,17 @@ this.Schemas = {
       return new NumberType(type);
     } else if (type.type == "integer") {
       checkTypeProperties("minimum", "maximum");
       return new IntegerType(type, type.minimum || -Infinity, type.maximum || Infinity);
     } else if (type.type == "boolean") {
       checkTypeProperties();
       return new BooleanType(type);
     } else if (type.type == "function") {
-      let isAsync = typeof(type.async) == "string";
+      let isAsync = Boolean(type.async);
 
       let parameters = null;
       if ("parameters" in type) {
         parameters = [];
         for (let param of type.parameters) {
           // Callbacks default to optional for now, because of promise
           // handling.
           let isCallback = isAsync && param.name == type.async;
@@ -1416,27 +1421,28 @@ this.Schemas = {
           parameters.push({
             type: this.parseType(path, param, ["name", "optional"]),
             name: param.name,
             optional: param.optional == null ? isCallback : param.optional,
           });
         }
       }
 
+      let hasAsyncCallback = false;
       if (isAsync) {
-        if (!parameters || !parameters.length || parameters[parameters.length - 1].name != type.async) {
-          throw new Error(`Internal error: "async" property must name the last parameter of the function.`);
+        if (parameters && parameters.length && parameters[parameters.length - 1].name == type.async) {
+          hasAsyncCallback = true;
         }
         if (type.returns || type.allowAmbiguousOptionalArguments) {
           throw new Error(`Internal error: Async functions must not have return values or ambiguous arguments.`);
         }
       }
 
       checkTypeProperties("parameters", "async", "returns");
-      return new FunctionType(type, parameters, isAsync);
+      return new FunctionType(type, parameters, isAsync, hasAsyncCallback);
     } else if (type.type == "any") {
       // Need to see what minimum and maximum are supposed to do here.
       checkTypeProperties("minimum", "maximum");
       return new AnyType(type);
     } else {
       throw new Error(`Unexpected type ${type.type}`);
     }
   },
--- a/toolkit/components/prompts/test/chromeScript.js
+++ b/toolkit/components/prompts/test/chromeScript.js
@@ -217,18 +217,25 @@ function getDialogDoc() {
         var childDocShell = containedDocShells.getNext();
         // We don't want it if it's not done loading.
         if (childDocShell.busyFlags != Ci.nsIDocShell.BUSY_FLAGS_NONE)
           continue;
         var childDoc = childDocShell.QueryInterface(Ci.nsIDocShell)
                                     .contentViewer
                                     .DOMDocument;
 
-        //ok(true, "Got window: " + childDoc.location.href);
-        if (childDoc.location.href == "chrome://global/content/commonDialog.xul")
-          return childDoc;
-        if (childDoc.location.href == "chrome://global/content/selectDialog.xul")
-          return childDoc;
+        if (childDoc.location.href != "chrome://global/content/commonDialog.xul" &&
+            childDoc.location.href != "chrome://global/content/selectDialog.xul")
+          continue;
+
+        // We're expecting the dialog to be focused. If it's not yet, try later.
+        // (In particular, this is needed on Linux to reliably check focused elements.)
+        let fm = Cc["@mozilla.org/focus-manager;1"].
+                 getService(Ci.nsIFocusManager);
+        if (fm.focusedWindow != childDoc.defaultView)
+          continue;
+
+        return childDoc;
     }
   }
 
   return null;
 }
--- a/toolkit/components/prompts/test/prompt_common.js
+++ b/toolkit/components/prompts/test/prompt_common.js
@@ -7,17 +7,16 @@ function hasTabModalPrompts() {
   var prefName = "prompts.tab_modal.enabled";
   var Services = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").Services;
   return Services.prefs.getPrefType(prefName) == Services.prefs.PREF_BOOL &&
          Services.prefs.getBoolPref(prefName);
 }
 var isTabModal = hasTabModalPrompts();
 var isSelectDialog = false;
 var isOSX = ("nsILocalFileMac" in SpecialPowers.Ci);
-var isLinux = ("@mozilla.org/gnome-gconf-service;1" in SpecialPowers.Cc);
 var isE10S = SpecialPowers.Services.appinfo.processType == 2;
 
 
 var gChromeScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL("chromeScript.js"));
 SimpleTest.registerCleanupFunction(() => gChromeScript.destroy());
 
 function onloadPromiseFor(id) {
   var iframe = document.getElementById(id);
@@ -68,27 +67,21 @@ function checkPromptState(promptState, e
     }
 
     // For prompts with a time-delay button.
     if (expectedState.butt0Disabled) {
         is(promptState.butt0Disabled, true,  "Checking accept-button is disabled");
         is(promptState.butt1Disabled, false, "Checking cancel-button isn't disabled");
     }
 
-    if (isLinux && !promptState.focused) {
-      todo(false, "Checking button default fails if focus is not correct."); // bug 1278418
-    } else {
-      is(promptState.defButton0, expectedState.defButton == "button0", "checking button0 default");
-      is(promptState.defButton1, expectedState.defButton == "button1", "checking button1 default");
-      is(promptState.defButton2, expectedState.defButton == "button2", "checking button2 default");
-    }
+    is(promptState.defButton0, expectedState.defButton == "button0", "checking button0 default");
+    is(promptState.defButton1, expectedState.defButton == "button1", "checking button1 default");
+    is(promptState.defButton2, expectedState.defButton == "button2", "checking button2 default");
 
-    if (isLinux && (!promptState.focused || isE10S)) {
-        todo(false, "Focus seems missing or wrong on Linux"); // bug 1265077
-    } else if (isOSX && expectedState.focused && expectedState.focused.startsWith("button")) {
+    if (isOSX && expectedState.focused && expectedState.focused.startsWith("button")) {
         is(promptState.focused, "infoBody", "buttons don't focus on OS X, but infoBody does instead");
     } else {
         is(promptState.focused, expectedState.focused, "Checking focused element");
     }
 }
 
 function checkEchoedAuthInfo(expectedState, doc) {
     // The server echos back the HTTP auth info it received.
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -5742,524 +5742,30 @@
   "PRINT_PREVIEW_SIMPLIFY_PAGE_OPENED_COUNT": {
     "alert_emails": ["carnold@mozilla.org"],
     "bug_numbers": [1275570],
     "expires_in_version": "56",
     "kind": "count",
     "releaseChannelCollection": "opt-out",
     "description": "A counter incremented every time the browser enters simplified mode on print preview."
   },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELOAD_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reload' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELOAD_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reload' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_NAVIGATETO_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'navigateTo' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_NAVIGATETO_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'navigateTo' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_EVENTLISTENERS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'eventListeners' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_EVENTLISTENERS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'eventListeners' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RESUME_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'resume' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RESUME_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'resume' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_INTERRUPT_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_INTERRUPT_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'interrupt' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_CLIENTEVALUATE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_CLIENTEVALUATE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'clientEvaluate' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASEMANY_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASEMANY_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'releaseMany' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADGRIPS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADGRIPS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'threadGrips' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_SOURCES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'sources' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_SOURCES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'sources' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_FRAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'frames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_FRAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'frames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PARAMETERNAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PARAMETERNAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'parameterNames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_OWNPROPERTYNAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_OWNPROPERTYNAMES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'ownPropertyNames' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPEANDPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPEANDPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototypeAndProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_ENUMPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'enumProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_ENUMPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'enumProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPESANDPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPESANDPROPERTIES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototypesAndProperties' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROPERTY_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'property' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROPERTY_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'property' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOTYPE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOTYPE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'prototype' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DISPLAYSTRING_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DISPLAYSTRING_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'displayString' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_SUBSTRING_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'substring' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_SUBSTRING_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'substring' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RELEASE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'release' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RELEASE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'release' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTTABS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTTABS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listTabs' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTSERVICEWORKERREGISTRATIONS_MS": {
-    "alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 100,
-    "description": "The time (in milliseconds) that it took a 'listServiceWorkerRegistrations' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTSERVICEWORKERREGISTRATIONS_MS": {
-    "alert_emails": ["dev-developer-tools@lists.mozilla.org", "ejpbruel@mozilla.com"],
-    "expires_in_version": "50",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 100,
-    "description": "The time (in milliseconds) that it took a 'listServiceWorkerRegistrations' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_PROTOCOLDESCRIPTION_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_PROTOCOLDESCRIPTION_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'protocolDescription' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTADDONS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listAddons' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTADDONS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listAddons' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTWORKERS_MS": {
-    "alert_emails": ["dev-developer-tools@lists.mozilla.org", "jan@mozilla.com"],
-    "expires_in_version": "55",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 50,
-    "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTWORKERS_MS": {
-    "alert_emails": ["dev-developer-tools@lists.mozilla.org", "jan@mozilla.com"],
-    "expires_in_version": "55",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 50,
-    "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTPROCESSES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTPROCESSES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listProcesses' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_DELETE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'delete' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_DELETE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'delete' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_THREADDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_THREADDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_ADDONDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_ADDONDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_TABDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_TABDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_WORKERDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_WORKERDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
   "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE_LOCAL_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 1000,
     "description": "The time (in milliseconds) that it took to display a selected source to the user."
   },
   "DEVTOOLS_DEBUGGER_DISPLAY_SOURCE_REMOTE_MS": {
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 10000,
     "n_buckets": 1000,
     "description": "The time (in milliseconds) that it took to display a selected source to the user."
   },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETAB_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETAB_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reconfigure tab' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_LISTWORKERS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_LISTWORKERS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'listWorkers' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_RECONFIGURETHREAD_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_RECONFIGURETHREAD_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'reconfigure thread' request to go round trip."
-  },
   "MEDIA_RUST_MP4PARSE_SUCCESS": {
     "alert_emails": ["giles@mozilla.com", "kinetik@flim.org"],
     "expires_in_version": "55",
     "kind": "boolean",
     "bug_numbers": [1220885],
     "description": "(Bug 1220885) Whether the rust mp4 demuxer successfully parsed a stream segment.",
     "cpp_guard": "MOZ_RUST_MP4PARSE"
   },
@@ -6845,142 +6351,16 @@
   },
   "WEBRTC_CALL_TYPE": {
     "alert_emails": ["webrtc-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 8,
     "description": "Type of call: (Bitmask) Audio = 1, Video = 2, DataChannels = 4"
   },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_TRACERDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_TRACERDETACH_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'detach' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_STARTTRACE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'startTrace' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_STARTTRACE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'startTrace' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_STOPTRACE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'stopTrace' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_STOPTRACE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'stopTrace' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_GET_EXECUTABLE_LINES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_GET_EXECUTABLE_LINES_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'getExecutableLines' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_BLACKBOX_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'blackbox' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_BLACKBOX_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'blackbox' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_UNBLACKBOX_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'ublackbox' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_UNBLACKBOX_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'unblackbox' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_SCOPE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'scope' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_SCOPE_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'scope' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_BINDINGS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'bindings' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_BINDINGS_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took a 'bindings' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_LOCAL_ASSIGN_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'assign' request to go round trip."
-  },
-  "DEVTOOLS_DEBUGGER_RDP_REMOTE_ASSIGN_MS": {
-    "expires_in_version": "never",
-    "kind": "exponential",
-    "high": 10000,
-    "n_buckets": 1000,
-    "description": "The time (in milliseconds) that it took an 'assign' request to go round trip."
-  },
   "DEVTOOLS_TOOLBOX_OPENED_COUNT": {
     "alert_emails": ["dev-developer-tools@lists.mozilla.org"],
     "expires_in_version": "never",
     "kind": "count",
     "bug_numbers": [1247985],
     "description": "Number of times the DevTools toolbox has been opened.",
     "releaseChannelCollection": "opt-out"
   },
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.cc
@@ -759,28 +759,30 @@ bool ExceptionHandler::WriteMinidumpForE
   bool success = WriteMinidumpOnHandlerThread(exinfo, NULL);
   UpdateNextID();
   return success;
 }
 
 // static
 bool ExceptionHandler::WriteMinidump(const wstring &dump_path,
                                      MinidumpCallback callback,
-                                     void* callback_context) {
+                                     void* callback_context,
+                                     MINIDUMP_TYPE dump_type) {
   ExceptionHandler handler(dump_path, NULL, callback, callback_context,
-                           HANDLER_NONE);
+                           HANDLER_NONE, dump_type, (HANDLE)NULL, NULL);
   return handler.WriteMinidump();
 }
 
 // static
 bool ExceptionHandler::WriteMinidumpForChild(HANDLE child,
                                              DWORD child_blamed_thread,
                                              const wstring& dump_path,
                                              MinidumpCallback callback,
-                                             void* callback_context) {
+                                             void* callback_context,
+                                             MINIDUMP_TYPE dump_type) {
   EXCEPTION_RECORD ex;
   CONTEXT ctx;
   EXCEPTION_POINTERS exinfo = { NULL, NULL };
   DWORD last_suspend_count = kFailedToSuspendThread;
   HANDLE child_thread_handle = OpenThread(THREAD_GET_CONTEXT |
                                           THREAD_QUERY_INFORMATION |
                                           THREAD_SUSPEND_RESUME,
                                           FALSE,
@@ -801,17 +803,17 @@ bool ExceptionHandler::WriteMinidumpForC
 #endif
         exinfo.ExceptionRecord = &ex;
         exinfo.ContextRecord = &ctx;
       }
     }
   }
 
   ExceptionHandler handler(dump_path, NULL, callback, callback_context,
-                           HANDLER_NONE);
+                           HANDLER_NONE, dump_type, (HANDLE)NULL, NULL);
   bool success = handler.WriteMinidumpWithExceptionForProcess(
       child_blamed_thread,
       exinfo.ExceptionRecord ? &exinfo : NULL,
       NULL, child, false);
 
   if (last_suspend_count != kFailedToSuspendThread) {
     ResumeThread(child_thread_handle);
   }
--- a/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h
+++ b/toolkit/crashreporter/google-breakpad/src/client/windows/handler/exception_handler.h
@@ -233,28 +233,30 @@ class ExceptionHandler {
 
   // Writes a minidump immediately, with the user-supplied exception
   // information.
   bool WriteMinidumpForException(EXCEPTION_POINTERS* exinfo);
 
   // Convenience form of WriteMinidump which does not require an
   // ExceptionHandler instance.
   static bool WriteMinidump(const wstring &dump_path,
-                            MinidumpCallback callback, void* callback_context);
+                            MinidumpCallback callback, void* callback_context,
+                            MINIDUMP_TYPE dump_type = MiniDumpNormal);
 
   // Write a minidump of |child| immediately.  This can be used to
   // capture the execution state of |child| independently of a crash.
   // Pass a meaningful |child_blamed_thread| to make that thread in
   // the child process the one from which a crash signature is
   // extracted.
   static bool WriteMinidumpForChild(HANDLE child,
                                     DWORD child_blamed_thread,
                                     const wstring& dump_path,
                                     MinidumpCallback callback,
-                                    void* callback_context);
+                                    void* callback_context,
+                                    MINIDUMP_TYPE dump_type = MiniDumpNormal);
 
   // Get the thread ID of the thread requesting the dump (either the exception
   // thread or any other thread that called WriteMinidump directly).  This
   // may be useful if you want to include additional thread state in your
   // dumps.
   DWORD get_requesting_thread_id() const { return requesting_thread_id_; }
 
   // Controls behavior of EXCEPTION_BREAKPOINT and EXCEPTION_SINGLE_STEP.
--- a/toolkit/crashreporter/nsExceptionHandler.cpp
+++ b/toolkit/crashreporter/nsExceptionHandler.cpp
@@ -3860,17 +3860,21 @@ bool TakeMinidump(nsIFile** aResult, boo
 
   // capture the dump
   if (!google_breakpad::ExceptionHandler::WriteMinidump(
          dump_path,
 #ifdef XP_MACOSX
          true,
 #endif
          PairedDumpCallback,
-         static_cast<void*>(aResult))) {
+         static_cast<void*>(aResult)
+#ifdef XP_WIN32
+         , GetMinidumpType()
+#endif
+      )) {
     return false;
   }
 
   if (aMoveToPending) {
     MoveToPending(*aResult, nullptr, nullptr);
   }
   return true;
 }
@@ -3901,33 +3905,40 @@ CreateMinidumpsAndPair(ProcessHandle aTa
 
   // dump the target
   nsCOMPtr<nsIFile> targetMinidump;
   if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild(
          aTargetPid,
          targetThread,
          dump_path,
          PairedDumpCallbackExtra,
-         static_cast<void*>(&targetMinidump)))
+         static_cast<void*>(&targetMinidump)
+#ifdef XP_WIN32
+         , GetMinidumpType()
+#endif
+      ))
     return false;
 
   nsCOMPtr<nsIFile> targetExtra;
   GetExtraFileForMinidump(targetMinidump, getter_AddRefs(targetExtra));
 
   // If aIncomingDumpToPair isn't valid, create a dump of this process.
   nsCOMPtr<nsIFile> incomingDump;
   if (aIncomingDumpToPair == nullptr) {
     if (!google_breakpad::ExceptionHandler::WriteMinidump(
         dump_path,
 #ifdef XP_MACOSX
         true,
 #endif
         PairedDumpCallback,
-        static_cast<void*>(&incomingDump))) {
-
+        static_cast<void*>(&incomingDump)
+#ifdef XP_WIN32
+        , GetMinidumpType()
+#endif
+        )) {
       targetMinidump->Remove(false);
       targetExtra->Remove(false);
       return false;
     }
   } else {
     incomingDump = aIncomingDumpToPair;
   }
 
@@ -3967,17 +3978,21 @@ CreateAdditionalChildMinidump(ProcessHan
 
   // dump the child
   nsCOMPtr<nsIFile> childMinidump;
   if (!google_breakpad::ExceptionHandler::WriteMinidumpForChild(
          childPid,
          childThread,
          dump_path,
          PairedDumpCallback,
-         static_cast<void*>(&childMinidump))) {
+         static_cast<void*>(&childMinidump)
+#ifdef XP_WIN32
+         , GetMinidumpType()
+#endif
+      )) {
     return false;
   }
 
   RenameAdditionalHangMinidump(childMinidump, parentMinidump, name);
 
   return true;
 }
 
--- a/toolkit/mozapps/extensions/internal/AddonRepository.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonRepository.jsm
@@ -395,17 +395,17 @@ AddonSearchResult.prototype = {
    * particular application and platform version.
    *
    * @param  appVersion
    *         An application version to test against
    * @param  platformVersion
    *         A platform version to test against
    * @return Boolean representing if the add-on is compatible
    */
-  isCompatibleWith: function(aAppVerison, aPlatformVersion) {
+  isCompatibleWith: function(aAppVersion, aPlatformVersion) {
     return true;
   },
 
   /**
    * Starts an update check for this add-on. This will perform
    * asynchronously and deliver results to the given listener.
    *
    * @param  aListener
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -573,17 +573,17 @@ PluginWrapper.prototype = {
   get providesUpdatesSecurely() {
     return true;
   },
 
   get foreignInstall() {
     return true;
   },
 
-  isCompatibleWith: function(aAppVerison, aPlatformVersion) {
+  isCompatibleWith: function(aAppVersion, aPlatformVersion) {
     return true;
   },
 
   findUpdates: function(aListener, aReason, aAppVersion, aPlatformVersion) {
     if ("onNoCompatibilityUpdateAvailable" in aListener)
       aListener.onNoCompatibilityUpdateAvailable(this);
     if ("onNoUpdateAvailable" in aListener)
       aListener.onNoUpdateAvailable(this);