Merge fx-team to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 26 Feb 2016 12:19:25 -0800
changeset 285814 5e0140b6d11821e0c2a2de25bc5431783f03380a
parent 285813 07438c9bfc83970721893d77a6daf1bc0c5fd72d (current diff)
parent 285812 daf17f7cf7f61da7e44ebd2814b01fb5eb4cf036 (diff)
child 285815 a0f39313b05eeb3b890c3508e9530283febe654e
child 285985 139446b09ca7f9dd05ad11ca8db9103b90aa69b2
push id72528
push userkwierso@gmail.com
push dateFri, 26 Feb 2016 20:38:36 +0000
treeherdermozilla-inbound@a0f39313b05e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone47.0a1
first release with
nightly linux32
5e0140b6d118 / 47.0a1 / 20160227030205 / files
nightly linux64
5e0140b6d118 / 47.0a1 / 20160227030205 / files
nightly mac
5e0140b6d118 / 47.0a1 / 20160227030205 / files
nightly win32
5e0140b6d118 / 47.0a1 / 20160227030205 / files
nightly win64
5e0140b6d118 / 47.0a1 / 20160227030205 / 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 central, a=merge MozReview-Commit-ID: 9LaKI6lIClP
browser/components/test/.eslintrc
browser/components/test/browser.ini
browser/components/test/browser_bug538331.js
mobile/android/chrome/content/browser.js
mobile/android/themes/core/images/reader-minus-mdpi.png
mobile/android/themes/core/images/reader-plus-mdpi.png
mobile/android/themes/core/images/reader-share-icon-hdpi.png
mobile/android/themes/core/images/reader-share-icon-mdpi.png
mobile/android/themes/core/images/reader-share-icon-xhdpi.png
mobile/android/themes/core/images/reader-share-icon-xxhdpi.png
mobile/android/themes/core/images/reader-style-icon-active-hdpi.png
mobile/android/themes/core/images/reader-style-icon-active-mdpi.png
mobile/android/themes/core/images/reader-style-icon-active-xhdpi.png
mobile/android/themes/core/images/reader-style-icon-active-xxhdpi.png
mobile/android/themes/core/images/reader-style-icon-mdpi.png
mobile/android/themes/core/images/reader-toggle-off-icon-hdpi.png
mobile/android/themes/core/images/reader-toggle-off-icon-mdpi.png
mobile/android/themes/core/images/reader-toggle-off-icon-xhdpi.png
mobile/android/themes/core/images/reader-toggle-off-icon-xxhdpi.png
mobile/android/themes/core/images/reader-toggle-on-icon-hdpi.png
mobile/android/themes/core/images/reader-toggle-on-icon-mdpi.png
mobile/android/themes/core/images/reader-toggle-on-icon-xhdpi.png
mobile/android/themes/core/images/reader-toggle-on-icon-xxhdpi.png
toolkit/mozapps/extensions/test/xpinstall/browser_cookies2.js
toolkit/mozapps/extensions/test/xpinstall/browser_cookies3.js
toolkit/mozapps/extensions/test/xpinstall/browser_cookies4.js
toolkit/themes/shared/reader/RM-Add-24x24.svg
toolkit/themes/shared/reader/RM-Delete-24x24.svg
toolkit/themes/shared/reader/RM-Reading-List-24x24.svg
--- a/.eslintignore
+++ b/.eslintignore
@@ -76,28 +76,26 @@ browser/extensions/pocket/content/panels
 browser/extensions/shumway/**
 browser/locales/**
 
 # Ignore all of loop since it is imported from github and checked at source.
 browser/extensions/loop/**
 
 # devtools/ exclusions
 devtools/*.js
-devtools/client/animationinspector/**
 devtools/client/canvasdebugger/**
 devtools/client/commandline/**
 devtools/client/debugger/**
 devtools/client/eyedropper/**
 devtools/client/framework/**
 # devtools/client/inspector/shared/*.js files are eslint-clean, so they aren't
 # included in the ignore list.
 devtools/client/inspector/computed/**
 devtools/client/inspector/fonts/**
 devtools/client/inspector/markup/test/**
-devtools/client/inspector/rules/**
 devtools/client/inspector/shared/test/**
 devtools/client/inspector/test/**
 devtools/client/inspector/*.js
 devtools/client/jsonview/**
 devtools/client/memory/**
 devtools/client/netmonitor/**
 devtools/client/performance/**
 devtools/client/projecteditor/**
--- a/.gitignore
+++ b/.gitignore
@@ -18,16 +18,17 @@ ID
 
 # Emacs directory variable files.
 **/.dir-locals.el
 
 # User files that may appear at the root
 /.mozconfig*
 /mozconfig
 /configure
+/old-configure
 /config.cache
 /config.log
 /.clang_complete
 /mach.ini
 
 # Empty marker file that's generated when we check out NSS
 security/manager/.nss.checkout
 
@@ -35,16 +36,17 @@ security/manager/.nss.checkout
 /obj*/
 
 # Build directories for js shell
 _DBG.OBJ/
 _OPT.OBJ/
 
 # SpiderMonkey configury
 js/src/configure
+js/src/old-configure
 js/src/autom4te.cache
 # SpiderMonkey test result logs
 js/src/tests/results-*.html
 js/src/tests/results-*.txt
 
 # Java HTML5 parser classes
 parser/html/java/htmlparser/
 parser/html/java/javaparser/
--- a/.hgignore
+++ b/.hgignore
@@ -14,16 +14,17 @@
 
 # Emacs directory variable files.
 \.dir-locals\.el
 
 # User files that may appear at the root
 ^\.mozconfig
 ^mozconfig*
 ^configure$
+^old-configure$
 ^config\.cache$
 ^config\.log$
 ^\.clang_complete
 ^mach.ini$
 
 # Empty marker file that's generated when we check out NSS
 ^security/manager/\.nss\.checkout$
 
@@ -32,16 +33,17 @@
 
 # Build directories for js shell
 _DBG\.OBJ/
 _OPT\.OBJ/
 ^js/src/.*-obj/
 
 # SpiderMonkey configury
 ^js/src/configure$
+^js/src/old-configure$
 ^js/src/autom4te.cache$
 # SpiderMonkey test result logs
 ^js/src/tests/results-.*\.(html|txt)$
 
 # Java HTML5 parser classes
 ^parser/html/java/(html|java)parser/
 
 # SVN directories
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -22,22 +22,23 @@ docShell.isAppTab = true;
 var gDOMContentLoaded = false;
 addEventListener("DOMContentLoaded", function() {
   gDOMContentLoaded = true;
   sendAsyncMessage("DOMContentLoaded");
 });
 var gDOMTitleChangedByUs = false;
 addEventListener("DOMTitleChanged", function(e) {
   if (!gDOMTitleChangedByUs) {
-    sendAsyncMessage("DOMTitleChanged", {
+    sendAsyncMessage("Social:DOMTitleChanged", {
       title: e.target.title
     });
   }
   gDOMTitleChangedByUs = false;
 });
+var gHookedWindowCloseForPanelClose = false;
 
 // Error handling class used to listen for network errors in the social frames
 // and replace them with a social-specific error page
 SocialErrorListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsISupports]),
@@ -112,35 +113,40 @@ SocialErrorListener = {
         sendAsyncMessage("Social:FocusEnsured");
         break;
       case "Social:EnsureFocusElement":
         let fm = Services.focus;
         fm.moveFocus(document.defaultView, null, fm.MOVEFOCUS_FIRST, fm.FLAG_NOSCROLL);
         sendAsyncMessage("Social:FocusEnsured");
         break;
       case "Social:HookWindowCloseForPanelClose":
+        if (gHookedWindowCloseForPanelClose) {
+          break;
+        }
+        gHookedWindowCloseForPanelClose = true;
         // We allow window.close() to close the panel, so add an event handler for
         // this, then cancel the event (so the window itself doesn't die) and
         // close the panel instead.
         // However, this is typically affected by the dom.allow_scripts_to_close_windows
         // preference, but we can avoid that check by setting a flag on the window.
         let dwu = content.QueryInterface(Ci.nsIInterfaceRequestor)
            .getInterface(Ci.nsIDOMWindowUtils);
         dwu.allowScriptsToClose();
 
         content.addEventListener("DOMWindowClose", function _mozSocialDOMWindowClose(evt) {
-          sendAsyncMessage("DOMWindowClose");
           // preventDefault stops the default window.close() function being called,
           // which doesn't actually close anything but causes things to get into
           // a bad state (an internal 'closed' flag is set and debug builds start
           // asserting as the window is used.).
           // None of the windows we inject this API into are suitable for this
           // default close behaviour, so even if we took no action above, we avoid
           // the default close from doing anything.
           evt.preventDefault();
+
+          sendAsyncMessage("Social:DOMWindowClose");
         }, true);
         break;
       case "Social:ListenForEvents":
         for (let eventName of message.data.eventNames) {
           content.addEventListener(eventName, this);
         }
         break;
       case "Social:SetDocumentTitle":
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -76,17 +76,17 @@
         // knows to make new callbacks immediately.
         if (this._callbacks) {
           for (let callback of this._callbacks) {
             callback(this);
           }
           this._callbacks = null;
         }
 
-        mm.addMessageListener("DOMTitleChanged", this);
+        mm.addMessageListener("Social:DOMTitleChanged", this);
 
         mm.sendAsyncMessage("WaitForDOMContentLoaded");
         mm.addMessageListener("DOMContentLoaded", function DOMContentLoaded(event) {
           mm.removeMessageListener("DOMContentLoaded", DOMContentLoaded);
           this.isActive = !this.minimized;
           this._chat.loadButtonSet(this, this.getAttribute("buttonSet"));
           this._deferredChatLoaded.resolve(this);
         }.bind(this));
@@ -211,17 +211,17 @@
           // When a chat window is attached or detached, the docShell hosting
           // the chat document is swapped to the newly created chat window.
           // (Be it inside a popup or back inside a chatbox element attached to
           // the chatbar.)
           // Since a swapDocShells call does not swap the messageManager instances
           // attached to a browser, we'll need to add the message listeners to
           // the new messageManager. This is not a bug in swapDocShells, merely
           // a design decision.
-          content.messageManager.addMessageListener("DOMTitleChanged", content);
+          content.messageManager.addMessageListener("Social:DOMTitleChanged", content);
         ]]></body>
       </method>
 
       <method name="setDecorationAttributes">
         <parameter name="aTarget"/>
         <body><![CDATA[
           if (this.hasAttribute("customSize"))
             aTarget.setAttribute("customSize", this.getAttribute("customSize"));
@@ -321,17 +321,17 @@
             this.chatbar.updateTitlebar(this);
         ]]></body>
       </method>
 
       <method name="receiveMessage">
         <parameter name="aMessage" />
         <body><![CDATA[
           switch (aMessage.name) {
-            case "DOMTitleChanged":
+            case "Social:DOMTitleChanged":
               this.setTitle();
               break;
           }
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
--- a/browser/components/feeds/WebContentConverter.js
+++ b/browser/components/feeds/WebContentConverter.js
@@ -896,38 +896,16 @@ WebContentConverterRegistrarContent.prot
     for (num of nums) {
       let branch = ps.getBranch(PREF_CONTENTHANDLERS_BRANCH + num + ".");
       try {
         this._registerContentHandlerHavingBranch(branch);
       } catch(ex) {
         // do nothing, the next branch might have values
       }
     }
-
-    // We need to do this _after_ registering all of the available handlers,
-    // so that getWebContentHandlerByURI can return successfully.
-    let autoBranch;
-    try {
-      autoBranch = ps.getBranch(PREF_CONTENTHANDLERS_AUTO);
-    } catch (e) {
-      // No auto branch yet, that's fine
-      //LOG("WCCR.init: There is no auto branch, benign");
-    }
-
-    if (autoBranch) {
-      for (let type of autoBranch.getChildList("")) {
-        let uri = autoBranch.getCharPref(type);
-        if (uri) {
-          let handler = this.getWebContentHandlerByURI(type, uri);
-          if (handler) {
-            this._setAutoHandler(type, handler);
-          }
-        }
-      }
-    }
   },
 
   _typeIsRegistered(contentType, uri) {
     return this._contentTypes[contentType]
                .some(e => e.uri == uri);
   },
 
   /**
--- a/browser/components/moz.build
+++ b/browser/components/moz.build
@@ -44,17 +44,21 @@ EXTRA_COMPONENTS += [
     'nsBrowserGlue.js',
 ]
 
 EXTRA_JS_MODULES += [
     'distribution.js',
 ]
 
 BROWSER_CHROME_MANIFESTS += [
-    'test/browser.ini'
+    'tests/browser/browser.ini'
+]
+
+XPCSHELL_TESTS_MANIFESTS += [
+    'tests/unit/xpcshell.ini'
 ]
 
 if CONFIG['MOZ_SAFE_BROWSING']:
     BROWSER_CHROME_MANIFESTS += ['safebrowsing/content/test/browser.ini']
 
 with Files('safebrowsing/*'):
     BUG_COMPONENT = ('Toolkit', 'Phishing Protection')
 
--- a/browser/components/search/content/search.xml
+++ b/browser/components/search/content/search.xml
@@ -399,17 +399,17 @@
       <method name="doSearch">
         <parameter name="aData"/>
         <parameter name="aWhere"/>
         <parameter name="aEngine"/>
         <body><![CDATA[
           var textBox = this._textbox;
 
           // Save the current value in the form history
-          if (aData && !PrivateBrowsingUtils.isWindowPrivate(window)) {
+          if (aData && !PrivateBrowsingUtils.isWindowPrivate(window) && this.FormHistory.enabled) {
             this.FormHistory.update(
               { op : "bump",
                 fieldname : textBox.getAttribute("autocompletesearchparam"),
                 value : aData },
               { handleError : function(aError) {
                   Components.utils.reportError("Saving search to form history failed: " + aError.message);
               }});
           }
rename from browser/components/test/.eslintrc
rename to browser/components/tests/browser/.eslintrc
--- a/browser/components/test/.eslintrc
+++ b/browser/components/tests/browser/.eslintrc
@@ -1,5 +1,5 @@
 {
   "extends": [
-    "../../../testing/mochitest/browser.eslintrc"
+    "../../../../testing/mochitest/browser.eslintrc"
   ]
 }
rename from browser/components/test/browser.ini
rename to browser/components/tests/browser/browser.ini
rename from browser/components/test/browser_bug538331.js
rename to browser/components/tests/browser/browser_bug538331.js
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/unit/distribution.ini
@@ -0,0 +1,32 @@
+# Distribution Configuration File
+# Test of distribution preferences
+
+[Global]
+id=disttest
+version=1.0
+about=Test distribution file
+
+[Preferences]
+distribution.test.string="Test String"
+distribution.test.string.noquotes=Test String
+distribution.test.int=777
+distribution.test.bool.true=true
+distribution.test.bool.false=false
+
+[LocalizablePreferences]
+distribution.test.locale="%LOCALE%"
+distribution.test.reset="Set"
+distribution.test.locale.set="First Set"
+distribution.test.language.set="First Set"
+
+[LocalizablePreferences-en]
+distribution.test.language.en="en"
+distribution.test.language.set="Second Set"
+
+[LocalizablePreferences-en-US]
+distribution.test.locale.en-US="en-US"
+distribution.test.reset=
+distribution.test.locale.set="Second Set"
+
+[LocalizablePreferences-de]
+distribution.test.locale.de="de"
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/unit/test_distribution.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Tests that preferences are properly set by distribution.ini
+ */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/LoadContextInfo.jsm");
+
+// Import common head.
+var commonFile = do_get_file("../../../../toolkit/components/places/tests/head_common.js", false);
+if (commonFile) {
+  let uri = Services.io.newFileURI(commonFile);
+  Services.scriptloader.loadSubScript(uri.spec, this);
+}
+
+const TOPICDATA_DISTRIBUTION_CUSTOMIZATION = "force-distribution-customization";
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+
+function run_test() {
+  // Set special pref to load distribution.ini from the profile folder.
+  Services.prefs.setBoolPref("distribution.testing.loadFromProfile", true);
+
+  // Copy distribution.ini file to the profile dir.
+  let distroDir = gProfD.clone();
+  distroDir.leafName = "distribution";
+  let iniFile = distroDir.clone();
+  iniFile.append("distribution.ini");
+  if (iniFile.exists()) {
+    iniFile.remove(false);
+    print("distribution.ini already exists, did some test forget to cleanup?");
+  }
+
+  let testDistributionFile = gTestDir.clone();
+  testDistributionFile.append("distribution.ini");
+  testDistributionFile.copyTo(distroDir, "distribution.ini");
+  Assert.ok(testDistributionFile.exists());
+
+  run_next_test();
+}
+
+do_register_cleanup(function () {
+  // Remove the distribution file, even if the test failed, otherwise all
+  // next tests will import it.
+  let iniFile = gProfD.clone();
+  iniFile.leafName = "distribution";
+  iniFile.append("distribution.ini");
+  if (iniFile.exists()) {
+    iniFile.remove(false);
+  }
+  Assert.ok(!iniFile.exists());
+});
+
+add_task(function* () {
+  // Force distribution.
+  let glue = Cc["@mozilla.org/browser/browserglue;1"].getService(Ci.nsIObserver)
+  glue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_DISTRIBUTION_CUSTOMIZATION);
+
+  Assert.equal(Services.prefs.getCharPref("distribution.test.string"), "Test String");
+  Assert.throws(() => Services.prefs.getCharPref("distribution.test.string.noquotes"));
+  Assert.equal(Services.prefs.getIntPref("distribution.test.int"), 777);
+  Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.true"), true);
+  Assert.equal(Services.prefs.getBoolPref("distribution.test.bool.false"), false);
+  Assert.equal(Services.prefs.getComplexValue("distribution.test.locale", Ci.nsIPrefLocalizedString).data, "en-US");
+  Assert.equal(Services.prefs.getComplexValue("distribution.test.language.en", Ci.nsIPrefLocalizedString).data, "en");
+  Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.en-US", Ci.nsIPrefLocalizedString).data, "en-US");
+  Assert.throws(() => Services.prefs.getComplexValue("distribution.test.locale.de", Ci.nsIPrefLocalizedString));
+  // This value was never set because of the empty locale specific pref
+  // This testcase currently fails - the value is set to "undefined" - it should not be set at all (throw)
+  // Assert.throws(() => Services.prefs.getComplexValue("distribution.test.reset", Ci.nsIPrefLocalizedString));
+  // This value was overriden by a locale specific setting
+  Assert.equal(Services.prefs.getComplexValue("distribution.test.locale.set", Ci.nsIPrefLocalizedString).data, "Second Set");
+  // This value was overriden by a language specific setting
+  Assert.equal(Services.prefs.getComplexValue("distribution.test.language.set", Ci.nsIPrefLocalizedString).data, "Second Set");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/unit/xpcshell.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+firefox-appdir = browser
+skip-if = toolkit == 'android' || toolkit == 'gonk'
+support-files =
+  distribution.ini
+
+[test_distribution.js]
--- a/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
+++ b/browser/extensions/loop/chrome/content/modules/MozLoopService.jsm
@@ -608,17 +608,19 @@ var MozLoopServiceInternal = {
     }
 
     if (sessionToken) {
       // true = use a hex key, as required by the server (see bug 1032738).
       credentials = deriveHawkCredentials(sessionToken, "sessionToken",
                                           2 * 32, true);
     }
 
-    if (payloadObj) {
+    // Later versions of Firefox will do utf-8 encoding of the request, but
+    // we need to do it ourselves for older versions.
+    if (!gHawkClient.willUTF8EncodeRequests && payloadObj) {
       // Note: we must copy the object rather than mutate it, to avoid
       // mutating the values of the object passed in.
       let newPayloadObj = {};
       for (let property of Object.getOwnPropertyNames(payloadObj)) {
         if (typeof payloadObj[property] == "string") {
           newPayloadObj[property] = CommonUtils.encodeUTF8(payloadObj[property]);
         } else {
           newPayloadObj[property] = payloadObj[property];
@@ -994,17 +996,17 @@ var MozLoopServiceInternal = {
                 chatbox.setAttribute("customSize", customSize);
                 chatbox.parentNode.setAttribute("customSize", customSize);
               }
             }
           });
 
           // Handle window.close correctly on the chatbox.
           mm.sendAsyncMessage("Social:HookWindowCloseForPanelClose");
-          messageName = "DOMWindowClose";
+          messageName = "Social:DOMWindowClose";
           mm.addMessageListener(messageName, listeners[messageName] = () => {
             // Remove message listeners.
             for (let name of Object.getOwnPropertyNames(listeners)) {
               mm.removeMessageListener(name, listeners[name]);
             }
             listeners = {};
 
             windowCloseCallback();
--- a/browser/extensions/loop/chrome/test/xpcshell/test_loopservice_hawk_request.js
+++ b/browser/extensions/loop/chrome/test/xpcshell/test_loopservice_hawk_request.js
@@ -7,22 +7,23 @@
 
 "use strict";
 
 /* exported run_test */
 
 Cu.import("resource://services-common/utils.js");
 
 add_task(function* request_with_unicode() {
-  const unicodeName = "yøü";
+  // Note unicodeName must be unicode, not utf-8
+  const unicodeName = "y\xf8\xfc"; // "yøü"
 
   loopServer.registerPathHandler("/fake", (request, response) => {
     let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
-    let jsonBody = JSON.parse(body);
-    Assert.equal(jsonBody.name, CommonUtils.encodeUTF8(unicodeName));
+    let jsonBody = JSON.parse(CommonUtils.decodeUTF8(body));
+    Assert.equal(jsonBody.name, unicodeName);
 
     response.setStatusLine(null, 200, "OK");
     response.processAsync();
     response.finish();
   });
 
   yield MozLoopServiceInternal.hawkRequestInternal(LOOP_SESSION_TYPE.GUEST, "/fake", "POST", { name: unicodeName }).then(
     () => Assert.ok(true, "Should have accepted"),
--- a/browser/extensions/loop/test/functional/test_1_browser_call.py
+++ b/browser/extensions/loop/test/functional/test_1_browser_call.py
@@ -1,16 +1,17 @@
 from marionette_driver.by import By
 from marionette_driver.errors import NoSuchElementException, StaleElementException
 # noinspection PyUnresolvedReferences
 from marionette_driver import Wait
 from marionette import MarionetteTestCase
 
 import os
 import sys
+import time
 import urlparse
 sys.path.insert(1, os.path.dirname(os.path.abspath(__file__)))
 
 import pyperclip
 
 from serversetup import LoopTestServers
 from config import FIREFOX_PREFERENCES
 
@@ -72,16 +73,18 @@ class Test1BrowserCall(MarionetteTestCas
         # switch to the frame
         frame = self.marionette.find_element(By.ID, "loop-panel-iframe")
         self.marionette.switch_to_frame(frame)
 
     def switch_to_chatbox(self):
         self.marionette.set_context("chrome")
         self.marionette.switch_to_frame()
 
+        # Added time lapse to allow for DOM to catch up
+        time.sleep(2)
         # XXX should be using wait_for_element_displayed, but need to wait
         # for Marionette bug 1094246 to be fixed.
         chatbox = self.wait_for_element_exists(By.TAG_NAME, 'chatbox')
         script = ("return document.getAnonymousElementByAttribute("
                   "arguments[0], 'anonid', 'content');")
         frame = self.marionette.execute_script(script, [chatbox])
         self.marionette.switch_to_frame(frame)
 
@@ -125,17 +128,17 @@ class Test1BrowserCall(MarionetteTestCas
         # Join the room
         join_button = self.wait_for_element_displayed(By.CLASS_NAME,
                                                       "btn-join")
         join_button.click()
 
     # Assumes the standalone or the conversation window is selected first.
     def check_video(self, selector):
         video = self.wait_for_element_displayed(By.CSS_SELECTOR,
-                                                selector, 20)
+                                                selector, 30)
         self.wait_for_element_attribute_to_be_false(video, "paused")
         self.assertEqual(video.get_attribute("ended"), "false")
 
     def standalone_check_remote_video(self):
         self.switch_to_standalone()
         self.check_video(".remote-video")
 
     def local_check_remote_video(self):
--- a/browser/modules/Chat.jsm
+++ b/browser/modules/Chat.jsm
@@ -193,17 +193,16 @@ var Chat = {
     // no good - we just use the "most recent" browser window which can host
     // chats (we used to try and "group" all chats in the same browser window,
     // but that didn't work out so well - see bug 835111
 
     // Try first the most recent window as getMostRecentWindow works
     // even on platforms where getZOrderDOMWindowEnumerator is broken
     // (ie. Linux).  This will handle most cases, but won't work if the
     // foreground window is a popup.
-
     let mostRecent = Services.wm.getMostRecentWindow("navigator:browser");
     if (isWindowGoodForChats(mostRecent))
       return mostRecent;
 
     let topMost, enumerator;
     // *sigh* - getZOrderDOMWindowEnumerator is broken except on Mac and
     // Windows.  We use BROKEN_WM_Z_ORDER as that is what some other code uses
     // and a few bugs recommend searching mxr for this symbol to identify the
--- a/browser/modules/ContentSearch.jsm
+++ b/browser/modules/ContentSearch.jsm
@@ -370,26 +370,28 @@ this.ContentSearch = {
       // properties, which won't be true if the browser has been destroyed.
       // That may be the case here due to the asynchronous nature of messaging.
       isPrivate = PrivateBrowsingUtils.isBrowserPrivate(msg.target);
     } catch (err) {}
     if (isPrivate || entry === "") {
       return Promise.resolve();
     }
     let browserData = this._suggestionDataForBrowser(msg.target, true);
-    FormHistory.update({
-      op: "bump",
-      fieldname: browserData.controller.formHistoryParam,
-      value: entry,
-    }, {
-      handleCompletion: () => {},
-      handleError: err => {
-        Cu.reportError("Error adding form history entry: " + err);
-      },
-    });
+    if (FormHistory.enabled) {
+      FormHistory.update({
+        op: "bump",
+        fieldname: browserData.controller.formHistoryParam,
+        value: entry,
+      }, {
+        handleCompletion: () => {},
+        handleError: err => {
+          Cu.reportError("Error adding form history entry: " + err);
+        },
+      });
+    }
     return Promise.resolve();
   },
 
   _onMessageRemoveFormHistoryEntry: function (msg, entry) {
     let browserData = this._suggestionDataForBrowser(msg.target);
     if (browserData && browserData.previousFormHistoryResult) {
       let { previousFormHistoryResult } = browserData;
       for (let i = 0; i < previousFormHistoryResult.matchCount; i++) {
--- a/browser/modules/Feeds.jsm
+++ b/browser/modules/Feeds.jsm
@@ -41,17 +41,17 @@ this.Feeds = {
                           getService(Ci.nsIWebContentHandlerRegistrar);
         registrar.registerContentHandler(data.contentType, data.uri, data.title,
                                          aMessage.target);
         break;
       }
 
       case "WCCR:setAutoHandler": {
         let registrar = Cc["@mozilla.org/embeddor.implemented/web-content-handler-registrar;1"].
-                          getService(Ci.nsIWebContentHandlerRegistrar);
+                          getService(Ci.nsIWebContentConverterService);
         registrar.setAutoHandler(data.contentType, data.handler);
         break;
       }
 
       case "FeedConverter:addLiveBookmark": {
         let topWindow = RecentWindow.getMostRecentBrowserWindow();
         topWindow.PlacesCommandHook.addLiveBookmark(data.spec, data.title, data.subtitle)
                                    .catch(Components.utils.reportError);
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -18,23 +18,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm");
 
 const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
 
 var ReaderParent = {
   _readerModeInfoPanelOpen: false,
 
   MESSAGES: [
-    "Reader:AddToList",
     "Reader:ArticleGet",
     "Reader:FaviconRequest",
-    "Reader:ListStatusRequest",
-    "Reader:RemoveFromList",
-    "Reader:Share",
-    "Reader:SystemUIVisibility",
     "Reader:UpdateReaderButton",
     "Reader:SetIntPref",
     "Reader:SetCharPref",
   ],
 
   init: function() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
     for (let msg of this.MESSAGES) {
@@ -67,23 +62,16 @@ var ReaderParent = {
             })
           },
           function onRejection(reason) {
             Cu.reportError("Error requesting favicon URL for about:reader content: " + reason);
           }).catch(Cu.reportError);
         }
         break;
       }
-      case "Reader:Share":
-        // XXX: To implement.
-        break;
-
-      case "Reader:SystemUIVisibility":
-        // XXX: To implement.
-        break;
 
       case "Reader:UpdateReaderButton": {
         let browser = message.target;
         if (message.data && message.data.isArticle !== undefined) {
           browser.isArticle = message.data.isArticle;
         }
         this.updateReaderButton(browser);
         break;
--- a/devtools/.eslintrc
+++ b/devtools/.eslintrc
@@ -30,40 +30,40 @@
     "mozilla/reject-importGlobalProperties": 1,
     "mozilla/var-only-at-top-level": 1,
 
     // Disallow using variables outside the blocks they are defined (especially
     // since only let and const are used, see "no-var").
     "block-scoped-var": 2,
     // Enforce one true brace style (opening brace on the same line) and avoid
     // start and end braces on the same line.
-    "brace-style": [1, "1tbs", {"allowSingleLine": false}],
+    "brace-style": [2, "1tbs", {"allowSingleLine": false}],
     // Require camel case names
-    "camelcase": 1,
+    "camelcase": 2,
     // Allow trailing commas for easy list extension.  Having them does not
     // impair readability, but also not required either.
     "comma-dangle": 0,
     // Enforce spacing before and after comma
-    "comma-spacing": [1, {"before": false, "after": true}],
+    "comma-spacing": [2, {"before": false, "after": true}],
     // Enforce one true comma style.
-    "comma-style": [1, "last"],
+    "comma-style": [2, "last"],
     // Warn about cyclomatic complexity in functions.
-    "complexity": 1,
+    "complexity": 2,
     // Require return statements to either always or never specify values.
     "consistent-return": 2,
     // Don't warn for inconsistent naming when capturing this (not so important
     // with auto-binding fat arrow functions).
     "consistent-this": 0,
     // Enforce curly brace conventions for all control statements.
     "curly": 2,
     // Don't require a default case in switch statements. Avoid being forced to
     // add a bogus default when you know all possible cases are handled.
     "default-case": 0,
     // Enforce dots on the next line with property name.
-    "dot-location": [1, "property"],
+    "dot-location": [2, "property"],
     // Encourage the use of dot notation whenever possible.
     "dot-notation": 2,
     // Enforce newline at the end of file, with no multiple empty lines.
     "eol-last": 2,
     // Allow using == instead of ===, in the interest of landing something since
     // the devtools codebase is split on convention here.
     "eqeqeq": 0,
     // Don't require function expressions to have a name.
@@ -72,32 +72,32 @@
     // the enclosing function name, and worst case you have a line number that
     // you can just look up.
     "func-names": 0,
     // Allow use of function declarations and expressions.
     "func-style": 0,
     // Deprecated, will be removed in 1.0.
     "generator-star": 0,
     // Enforce the spacing around the * in generator functions.
-    "generator-star-spacing": [1, "after"],
+    "generator-star-spacing": [2, "after"],
     // Deprecated, will be removed in 1.0.
     "global-strict": 0,
     // Only useful in a node environment.
     "handle-callback-err": 0,
     // Tab width.
-    "indent": [1, 2, {"SwitchCase": 1}],
+    "indent": [2, 2, {"SwitchCase": 1}],
     // Enforces spacing between keys and values in object literal properties.
-    "key-spacing": [1, {"beforeColon": false, "afterColon": true}],
+    "key-spacing": [2, {"beforeColon": false, "afterColon": true}],
     // Allow mixed 'LF' and 'CRLF' as linebreaks.
     "linebreak-style": 0,
     // Don't enforce the maximum depth that blocks can be nested. The complexity
     // rule is a better rule to check this.
     "max-depth": 0,
     // Maximum length of a line.
-    "max-len": [1, 80, 2, {"ignoreUrls": true, "ignorePattern": "\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}],
+    "max-len": [2, 80, 2, {"ignoreUrls": true, "ignorePattern": "\\s*require\\s*\\(|^\\s*loader\\.lazy|-\\*-"}],
     // Maximum depth callbacks can be nested.
     "max-nested-callbacks": [2, 3],
     // Don't limit the number of parameters that can be used in a function.
     "max-params": 0,
     // Don't limit the maximum number of statement allowed in a function. We
     // already have the complexity rule that's a better measurement.
     "max-statements": 0,
     // Require a capital letter for constructors, only check if all new
@@ -110,17 +110,17 @@
     // Disallow use of the Array constructor.
     "no-array-constructor": 2,
     // Allow use of bitwise operators.
     "no-bitwise": 0,
     // Disallow use of arguments.caller or arguments.callee.
     "no-caller": 2,
     // Disallow the catch clause parameter name being the same as a variable in
     // the outer scope, to avoid confusion.
-    "no-catch-shadow": 1,
+    "no-catch-shadow": 2,
     // Deprecated, will be removed in 1.0.
     "no-comma-dangle": 0,
     // Disallow assignment in conditional expressions.
     "no-cond-assign": 2,
     // Allow using the console API.
     "no-console": 0,
     // Allow using constant expressions in conditions like while (true)
     "no-constant-condition": 0,
@@ -162,47 +162,47 @@
     "no-extend-native": 2,
     // Disallow unnecessary function binding.
     "no-extra-bind": 2,
     // Disallow double-negation boolean casts in a boolean context.
     "no-extra-boolean-cast": 2,
     // Allow unnecessary parentheses, as they may make the code more readable.
     "no-extra-parens": 0,
     // Disallow unnecessary semicolons.
-    "no-extra-semi": 1,
+    "no-extra-semi": 2,
     // Deprecated, will be removed in 1.0.
     "no-extra-strict": 0,
     // Disallow fallthrough of case statements, except if there is a comment.
     "no-fallthrough": 2,
     // Allow the use of leading or trailing decimal points in numeric literals.
     "no-floating-decimal": 0,
     // Disallow comments inline after code.
-    "no-inline-comments": 1,
+    "no-inline-comments": 2,
     // Disallow if as the only statement in an else block.
     "no-lonely-if": 2,
     // Allow mixing regular variable and require declarations (not a node env).
     "no-mixed-requires": 0,
     // Disallow mixed spaces and tabs for indentation.
     "no-mixed-spaces-and-tabs": 2,
     // Disallow use of multiple spaces (sometimes used to align const values,
     // array or object items, etc.). It's hard to maintain and doesn't add that
     // much benefit.
-    "no-multi-spaces": 1,
+    "no-multi-spaces": 2,
     // Disallow use of multiline strings (use template strings instead).
-    "no-multi-str": 1,
+    "no-multi-str": 2,
     // Disallow multiple empty lines.
-    "no-multiple-empty-lines": [1, {"max": 1}],
+    "no-multiple-empty-lines": [2, {"max": 1}],
     // Disallow reassignments of native objects.
     "no-native-reassign": 2,
     // Disallow nested ternary expressions, they make the code hard to read.
     "no-nested-ternary": 2,
     // Allow use of new operator with the require function.
     "no-new-require": 0,
     // Disallow use of octal literals.
-    "no-octal": 1,
+    "no-octal": 2,
     // Allow reassignment of function parameters.
     "no-param-reassign": 0,
     // Allow string concatenation with __dirname and __filename (not a node env).
     "no-path-concat": 0,
     // Allow use of unary operators, ++ and --.
     "no-plusplus": 0,
     // Allow using process.env (not a node environment).
     "no-process-env": 0,
@@ -227,23 +227,23 @@
     "no-self-compare": 2,
     // Disallow use of comma operator.
     "no-sequences": 2,
     // Warn about declaration of variables already declared in the outer scope.
     // This isn't an error because it sometimes is useful to use the same name
     // in a small helper function rather than having to come up with another
     // random name.
     // Still, making this a warning can help people avoid being confused.
-    "no-shadow": 1,
+    "no-shadow": 2,
     // Disallow shadowing of names such as arguments.
     "no-shadow-restricted-names": 2,
     // Deprecated, will be removed in 1.0.
     "no-space-before-semi": 0,
     // Disallow space between function identifier and application.
-    "no-spaced-func": 1,
+    "no-spaced-func": 2,
     // Disallow sparse arrays, eg. let arr = [,,2].
     // Array destructuring is fine though:
     // for (let [, breakpointPromise] of aPromises)
     "no-sparse-arrays": 2,
     // Allow use of synchronous methods (not a node environment).
     "no-sync": 0,
     // Allow the use of ternary operators.
     "no-ternary": 0,
@@ -278,57 +278,57 @@
     "no-with": 2,
     // Don't require method and property shorthand syntax for object literals.
     // We use this in the code a lot, but not consistently, and this seems more
     // like something to check at code review time.
     "object-shorthand": 0,
     // Allow more than one variable declaration per function.
     "one-var": 0,
     // Disallow padding within blocks.
-    "padded-blocks": [1, "never"],
+    "padded-blocks": [2, "never"],
     // Don't require quotes around object literal property names.
     "quote-props": 0,
     // Double quotes should be used.
-    "quotes": [1, "double", "avoid-escape"],
+    "quotes": [2, "double", "avoid-escape"],
     // Require use of the second argument for parseInt().
     "radix": 2,
     // Always require use of semicolons wherever they are valid.
-    "semi": [1, "always"],
+    "semi": [2, "always"],
     // Enforce spacing after semicolons.
-    "semi-spacing": [1, {"before": false, "after": true}],
+    "semi-spacing": [2, {"before": false, "after": true}],
     // Don't require to sort variables within the same declaration block.
     // Anyway, one-var is disabled.
     "sort-vars": 0,
     // Deprecated, will be removed in 1.0.
     "space-after-function-name": 0,
     // Require a space after keywords.
-    "space-after-keywords": [1, "always"],
+    "space-after-keywords": [2, "always"],
     // Require a space before the start brace of a block.
-    "space-before-blocks": [1, "always"],
+    "space-before-blocks": [2, "always"],
     // Deprecated, will be removed in 1.0.
     "space-before-function-parentheses": 0,
     // Disallow space before function opening parenthesis.
-    "space-before-function-paren": [1, "never"],
+    "space-before-function-paren": [2, "never"],
     // Disable the rule that checks if spaces inside {} and [] are there or not.
     // Our code is split on conventions, and it'd be nice to have 2 rules
     // instead, one for [] and one for {}. So, disabling until we write them.
     "space-in-brackets": 0,
     // Disallow spaces inside parentheses.
-    "space-in-parens": [1, "never"],
+    "space-in-parens": [2, "never"],
     // Require spaces around operators, except for a|0.
-    "space-infix-ops": [1, {"int32Hint": true}],
+    "space-infix-ops": [2, {"int32Hint": true}],
     // Require a space after return, throw, and case.
-    "space-return-throw-case": 1,
+    "space-return-throw-case": 2,
     // Require spaces before/after unary operators (words on by default,
     // nonwords off by default).
-    "space-unary-ops": [1, { "words": true, "nonwords": false }],
+    "space-unary-ops": [2, { "words": true, "nonwords": false }],
     // Deprecated, will be removed in 1.0.
     "space-unary-word-ops": 0,
     // Require a space immediately following the // in a line comment.
-    "spaced-comment": [1, "always"],
+    "spaced-comment": [2, "always"],
     // Require "use strict" to be defined globally in the script.
     "strict": [2, "global"],
     // Disallow comparisons with the value NaN.
     "use-isnan": 2,
     // Warn about invalid JSDoc comments.
     // Disabled for now because of https://github.com/eslint/eslint/issues/2270
     // The rule fails on some jsdoc comments like in:
     // devtools/client/webconsole/console-output.js
--- a/devtools/client/animationinspector/animation-controller.js
+++ b/devtools/client/animationinspector/animation-controller.js
@@ -1,29 +1,32 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* animation-panel.js is loaded in the same scope but we don't use
+   import-globals-from to avoid infinite loops since animation-panel.js already
+   imports globals from animation-controller.js */
 /* globals AnimationsPanel */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
 
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/Task.jsm");
 var { loader, require } = Cu.import("resource://devtools/shared/Loader.jsm");
 Cu.import("resource://gre/modules/Console.jsm");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 
 loader.lazyRequireGetter(this, "promise");
-loader.lazyRequireGetter(this, "EventEmitter",
-                               "devtools/shared/event-emitter");
-loader.lazyRequireGetter(this, "AnimationsFront",
-                               "devtools/server/actors/animation", true);
+loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
+loader.lazyRequireGetter(this, "AnimationsFront", "devtools/server/actors/animation", true);
 
 const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 
 // Global toolbox/inspector, set when startup is called.
 var gToolbox, gInspector;
 
 /**
--- a/devtools/client/animationinspector/animation-panel.js
+++ b/devtools/client/animationinspector/animation-panel.js
@@ -1,13 +1,14 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 /* import-globals-from animation-controller.js */
 /* globals document */
 
 "use strict";
 
 const {AnimationsTimeline} = require("devtools/client/animationinspector/components/animation-timeline");
 const {RateSelector} = require("devtools/client/animationinspector/components/rate-selector");
 const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
@@ -135,21 +136,22 @@ var AnimationsPanel = {
   stopListeners: function() {
     AnimationsController.off(AnimationsController.PLAYERS_UPDATED_EVENT,
       this.refreshAnimationsUI);
 
     this.pickerButtonEl.removeEventListener("click", this.togglePicker);
     gToolbox.off("picker-started", this.onPickerStarted);
     gToolbox.off("picker-stopped", this.onPickerStopped);
 
-    this.toggleAllButtonEl.removeEventListener("click", this.onToggleAllClicked);
-    this.playTimelineButtonEl.removeEventListener(
-      "click", this.onTimelinePlayClicked);
-    this.rewindTimelineButtonEl.removeEventListener(
-      "click", this.onTimelineRewindClicked);
+    this.toggleAllButtonEl.removeEventListener("click",
+      this.onToggleAllClicked);
+    this.playTimelineButtonEl.removeEventListener("click",
+      this.onTimelinePlayClicked);
+    this.rewindTimelineButtonEl.removeEventListener("click",
+      this.onTimelineRewindClicked);
 
     document.removeEventListener("keydown", this.onKeyDown, false);
 
     gToolbox.target.off("navigate", this.onTabNavigated);
 
     this.animationsTimelineComponent.off("timeline-data-changed",
       this.onTimelineDataChanged);
 
--- a/devtools/client/animationinspector/components/animation-details.js
+++ b/devtools/client/animationinspector/components/animation-details.js
@@ -1,17 +1,21 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Cu} = require("chrome");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
-const {
-  createNode,
-  TimeScale
-} = require("devtools/client/animationinspector/utils");
+const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
 const {Keyframes} = require("devtools/client/animationinspector/components/keyframes");
+
 /**
  * UI component responsible for displaying detailed information for a given
  * animation.
  * This includes information about timing, easing, keyframes, animated
  * properties.
  */
 function AnimationDetails() {
   EventEmitter.decorate(this);
--- a/devtools/client/animationinspector/components/animation-target-node.js
+++ b/devtools/client/animationinspector/components/animation-target-node.js
@@ -1,15 +1,20 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Cu} = require("chrome");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
-const {DomNodePreview} = require(
-  "devtools/client/inspector/shared/dom-node-preview");
+const {DomNodePreview} = require("devtools/client/inspector/shared/dom-node-preview");
 
 // Map dom node fronts by animation fronts so we don't have to get them from the
 // walker every time the timeline is refreshed.
 var nodeFronts = new WeakMap();
 
 /**
  * UI component responsible for displaying a preview of the target dom node of
  * a given animation.
--- a/devtools/client/animationinspector/components/animation-time-block.js
+++ b/devtools/client/animationinspector/components/animation-time-block.js
@@ -1,16 +1,19 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Cu} = require("chrome");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
-const {
-  createNode,
-  TimeScale
-} = require("devtools/client/animationinspector/utils");
+const {createNode, TimeScale} = require("devtools/client/animationinspector/utils");
 
 const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 
 /**
  * UI component responsible for displaying a single animation timeline, which
  * basically looks like a rectangle that shows the delay and iterations.
  */
@@ -47,17 +50,17 @@ AnimationTimeBlock.prototype = {
     let {state} = this.animation;
 
     // Create a container element to hold the delay and iterations.
     // It is positioned according to its delay (divided by the playbackrate),
     // and its width is according to its duration (divided by the playbackrate).
     let {x, iterationW, delayX, delayW, negativeDelayW} =
       TimeScale.getAnimationDimensions(animation);
 
-    let iterations = createNode({
+    createNode({
       parent: this.containerEl,
       attributes: {
         "class": "iterations" + (state.iterationCount ? "" : " infinite"),
         // Individual iterations are represented by setting the size of the
         // repeating linear-gradient.
         "style": `left:${x}%;
                   width:${iterationW}%;
                   background-size:${100 / (state.iterationCount || 1)}% 100%;`
--- a/devtools/client/animationinspector/components/animation-timeline.js
+++ b/devtools/client/animationinspector/components/animation-timeline.js
@@ -1,8 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {
   createNode,
   drawGraphElementBackground,
   findOptimalTimeInterval,
   TimeScale
 } = require("devtools/client/animationinspector/utils");
@@ -76,42 +82,46 @@ AnimationsTimeline.prototype = {
     });
 
     this.scrubberHandleEl = createNode({
       parent: this.scrubberEl,
       attributes: {
         "class": "scrubber-handle"
       }
     });
-    this.scrubberHandleEl.addEventListener("mousedown", this.onScrubberMouseDown);
+    this.scrubberHandleEl.addEventListener("mousedown",
+      this.onScrubberMouseDown);
 
     this.timeHeaderEl = createNode({
       parent: this.rootWrapperEl,
       attributes: {
         "class": "time-header track-container"
       }
     });
-    this.timeHeaderEl.addEventListener("mousedown", this.onScrubberMouseDown);
+    this.timeHeaderEl.addEventListener("mousedown",
+      this.onScrubberMouseDown);
 
     this.animationsEl = createNode({
       parent: this.rootWrapperEl,
       nodeType: "ul",
       attributes: {
         "class": "animations"
       }
     });
 
-    this.win.addEventListener("resize", this.onWindowResize);
+    this.win.addEventListener("resize",
+      this.onWindowResize);
   },
 
   destroy: function() {
     this.stopAnimatingScrubber();
     this.unrender();
 
-    this.win.removeEventListener("resize", this.onWindowResize);
+    this.win.removeEventListener("resize",
+      this.onWindowResize);
     this.timeHeaderEl.removeEventListener("mousedown",
       this.onScrubberMouseDown);
     this.scrubberHandleEl.removeEventListener("mousedown",
       this.onScrubberMouseDown);
 
     this.rootWrapperEl.remove();
     this.animations = [];
 
@@ -409,17 +419,18 @@ AnimationsTimeline.prototype = {
     // For now, simply re-render the component. The animation front's state has
     // already been updated.
     this.render(this.animations);
   },
 
   drawHeaderAndBackground: function() {
     let width = this.timeHeaderEl.offsetWidth;
     let animationDuration = TimeScale.maxEndTime - TimeScale.minStartTime;
-    let minTimeInterval = TIME_GRADUATION_MIN_SPACING * animationDuration / width;
+    let minTimeInterval = TIME_GRADUATION_MIN_SPACING *
+                          animationDuration / width;
     let intervalLength = findOptimalTimeInterval(minTimeInterval);
     let intervalWidth = intervalLength * width / animationDuration;
 
     drawGraphElementBackground(this.win.document, "time-graduations",
                                width, intervalWidth);
 
     // And the time graduation header.
     this.timeHeaderEl.innerHTML = "";
--- a/devtools/client/animationinspector/components/keyframes.js
+++ b/devtools/client/animationinspector/components/keyframes.js
@@ -1,8 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Cu} = require("chrome");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 const {createNode} = require("devtools/client/animationinspector/utils");
 
 /**
  * UI component responsible for displaying a list of keyframes.
--- a/devtools/client/animationinspector/components/rate-selector.js
+++ b/devtools/client/animationinspector/components/rate-selector.js
@@ -1,8 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 "use strict";
 
 const {Cu} = require("chrome");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
 const {createNode} = require("devtools/client/animationinspector/utils");
 const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 // List of playback rate presets displayed in the timeline toolbar.
--- a/devtools/client/animationinspector/test/browser_animation_panel_exists.js
+++ b/devtools/client/animationinspector/test/browser_animation_panel_exists.js
@@ -5,15 +5,19 @@
 "use strict";
 
 // Test that the animation panel sidebar exists
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8,welcome to the animation panel");
   let {panel, controller} = yield openAnimationInspector();
 
-  ok(controller, "The animation controller exists");
-  ok(controller.animationsFront, "The animation controller has been initialized");
-
-  ok(panel, "The animation panel exists");
-  ok(panel.playersEl, "The animation panel has been initialized");
-  ok(panel.animationsTimelineComponent, "The animation panel has been initialized");
+  ok(controller,
+     "The animation controller exists");
+  ok(controller.animationsFront,
+     "The animation controller has been initialized");
+  ok(panel,
+     "The animation panel exists");
+  ok(panel.playersEl,
+     "The animation panel has been initialized");
+  ok(panel.animationsTimelineComponent,
+     "The animation panel has been initialized");
 });
--- a/devtools/client/animationinspector/test/browser_animation_participate_in_inspector_update.js
+++ b/devtools/client/animationinspector/test/browser_animation_participate_in_inspector_update.js
@@ -9,17 +9,18 @@ requestLongerTimeout(2);
 // Test that the update of the animation panel participate in the
 // inspector-updated event. This means that the test verifies that the
 // inspector-updated event is emitted *after* the animation panel is ready.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel, controller} = yield openAnimationInspector();
 
-  info("Listen for the players-updated, ui-updated and inspector-updated events");
+  info("Listen for the players-updated, ui-updated and " +
+       "inspector-updated events");
   let receivedEvents = [];
   controller.once(controller.PLAYERS_UPDATED_EVENT, () => {
     receivedEvents.push(controller.PLAYERS_UPDATED_EVENT);
   });
   panel.once(panel.UI_UPDATED_EVENT, () => {
     receivedEvents.push(panel.UI_UPDATED_EVENT);
   });
   inspector.once("inspector-updated", () => {
--- a/devtools/client/animationinspector/test/browser_animation_timeline_header.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_header.js
@@ -3,21 +3,20 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 requestLongerTimeout(2);
 
 // Check that the timeline shows correct time graduations in the header.
 
-const {
-  findOptimalTimeInterval,
-  TimeScale
-} = require("devtools/client/animationinspector/utils");
-// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in animation-timeline.js
+const {findOptimalTimeInterval, TimeScale} = require("devtools/client/animationinspector/utils");
+
+// Should be kept in sync with TIME_GRADUATION_MIN_SPACING in
+// animation-timeline.js
 const TIME_GRADUATION_MIN_SPACING = 40;
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
   let {panel} = yield openAnimationInspector();
 
   let timeline = panel.animationsTimelineComponent;
   let headerEl = timeline.timeHeaderEl;
--- a/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_pause_button.js
@@ -94,16 +94,17 @@ function waitForOutOfBoundScrubber({win,
       }
     }
     check();
   });
 }
 
 function waitForScrubberStopped(timeline) {
   return new Promise(resolve => {
-    timeline.on("timeline-data-changed", function onTimelineData(e, {isMoving}) {
-      if (!isMoving) {
-        timeline.off("timeline-data-changed", onTimelineData);
-        resolve();
-      }
-    });
+    timeline.on("timeline-data-changed",
+      function onTimelineData(e, {isMoving}) {
+        if (!isMoving) {
+          timeline.off("timeline-data-changed", onTimelineData);
+          resolve();
+        }
+      });
   });
 }
--- a/devtools/client/animationinspector/test/browser_animation_timeline_rewind_button.js
+++ b/devtools/client/animationinspector/test/browser_animation_timeline_rewind_button.js
@@ -11,40 +11,41 @@ requestLongerTimeout(2);
 // timeline get their playstates changed to paused, and their currentTimes
 // reset to 0, and that the scrubber stops moving and is positioned to the
 // start.
 
 add_task(function*() {
   yield addTab(TEST_URL_ROOT + "doc_simple_animation.html");
 
   let {panel, controller} = yield openAnimationInspector();
+  let players = controller.animationPlayers;
   let btn = panel.rewindTimelineButtonEl;
 
   ok(btn, "The rewind button exists");
 
   info("Click on the button to rewind all timeline animations");
   yield clickTimelineRewindButton(panel);
 
   info("Check that the scrubber has stopped moving");
   yield assertScrubberMoving(panel, false);
 
-  ok(controller.animationPlayers.every(({state}) => state.currentTime === 0),
+  ok(players.every(({state}) => state.currentTime === 0),
      "All animations' currentTimes have been set to 0");
-  ok(controller.animationPlayers.every(({state}) => state.playState === "paused"),
+  ok(players.every(({state}) => state.playState === "paused"),
      "All animations have been paused");
 
   info("Play the animations again");
   yield clickTimelinePlayPauseButton(panel);
 
   info("And pause them after a short while");
   yield new Promise(r => setTimeout(r, 200));
 
   info("Check that rewinding when animations are paused works too");
   yield clickTimelineRewindButton(panel);
 
   info("Check that the scrubber has stopped moving");
   yield assertScrubberMoving(panel, false);
 
-  ok(controller.animationPlayers.every(({state}) => state.currentTime === 0),
+  ok(players.every(({state}) => state.currentTime === 0),
      "All animations' currentTimes have been set to 0");
-  ok(controller.animationPlayers.every(({state}) => state.playState === "paused"),
+  ok(players.every(({state}) => state.playState === "paused"),
      "All animations have been paused");
 });
--- a/devtools/client/animationinspector/test/doc_frame_script.js
+++ b/devtools/client/animationinspector/test/doc_frame_script.js
@@ -1,22 +1,24 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals addMessageListener, sendAsyncMessage */
 
 "use strict";
 
 // A helper frame-script for brower/devtools/animationinspector tests.
 
 /**
  * Toggle (play or pause) one of the animation players of a given node.
  * @param {Object} data
  * - {String} selector The CSS selector to get the node (can be a "super"
  *   selector).
- * - {Number} animationIndex The index of the node's animationPlayers to play or pause
+ * - {Number} animationIndex The index of the node's animationPlayers to play
+ *   or pause
  * - {Boolean} pause True to pause the animation, false to play.
  */
 addMessageListener("Test:ToggleAnimationPlayer", function(msg) {
   let {selector, animationIndex, pause} = msg.data;
   let node = superQuerySelector(selector);
   if (!node) {
     return;
   }
@@ -98,23 +100,23 @@ addMessageListener("Test:GetAnimationPla
  * ".container iframe || .sub-container div" will first try to find the node
  * matched by ".container iframe" in the root document, then try to get the
  * content document inside it, and then try to match ".sub-container div" inside
  * this document.
  * Any selector coming before the || separator *MUST* match a frame node.
  * @param {String} superSelector.
  * @return {DOMNode} The node, or null if not found.
  */
-function superQuerySelector(superSelector, root=content.document) {
+function superQuerySelector(superSelector, root = content.document) {
   let frameIndex = superSelector.indexOf("||");
   if (frameIndex === -1) {
     return root.querySelector(superSelector);
-  } else {
-    let rootSelector = superSelector.substring(0, frameIndex).trim();
-    let childSelector = superSelector.substring(frameIndex+2).trim();
-    root = root.querySelector(rootSelector);
-    if (!root || !root.contentWindow) {
-      return null;
-    }
+  }
 
-    return superQuerySelector(childSelector, root.contentWindow.document);
+  let rootSelector = superSelector.substring(0, frameIndex).trim();
+  let childSelector = superSelector.substring(frameIndex + 2).trim();
+  root = root.querySelector(rootSelector);
+  if (!root || !root.contentWindow) {
+    return null;
   }
+
+  return superQuerySelector(childSelector, root.contentWindow.document);
 }
--- a/devtools/client/animationinspector/test/doc_modify_playbackRate.html
+++ b/devtools/client/animationinspector/test/doc_modify_playbackRate.html
@@ -17,14 +17,16 @@
     }
   }
   </style>
 </head>
 <body>
   <div></div>
   <div class="rate"></div>
   <script>
+    "use strict";
+
     var el = document.querySelector(".rate");
     var ani = el.getAnimations()[0];
     ani.playbackRate = 2;
   </script>
 </body>
 </html>
--- a/devtools/client/animationinspector/test/doc_multiple_animation_types.html
+++ b/devtools/client/animationinspector/test/doc_multiple_animation_types.html
@@ -37,22 +37,24 @@
   </style>
 </head>
 <body>
   <div class="ball script-animation"></div>
   <div class="ball css-animation"></div>
   <div class="ball css-transition"></div>
 
   <script>
-    setTimeout(function(){
+    "use strict";
+
+    setTimeout(function() {
       document.querySelector(".css-transition").style.backgroundColor = "yellow";
     }, 0);
 
     document.querySelector(".script-animation").animate([
-      {  opacity: 1, offset: 0 },
-      {  opacity: .1, offset: 1 }
+      {opacity: 1, offset: 0},
+      {opacity: .1, offset: 1}
     ], {
       duration: 10000,
       fill: "forwards"
     });
   </script>
 </body>
 </html>
--- a/devtools/client/animationinspector/test/doc_negative_animation.html
+++ b/devtools/client/animationinspector/test/doc_negative_animation.html
@@ -40,16 +40,18 @@
   }
   </style>
 </head>
 <body>
   <div class="negative"></div>
   <div class="zero"></div>
   <div class="positive"></div>
   <script>
+    "use strict";
+
     var negative = document.querySelector(".negative");
     var zero = document.querySelector(".zero");
     var positive = document.querySelector(".positive");
 
     // The non-delayed animation starts now.
     zero.classList.add("move");
     // The negative-delayed animation starts in 1 second.
     setTimeout(function() {
--- a/devtools/client/animationinspector/test/head.js
+++ b/devtools/client/animationinspector/test/head.js
@@ -1,11 +1,12 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {gDevTools} = require("devtools/client/framework/devtools");
 const promise = require("promise");
 const {TargetFactory} = require("devtools/client/framework/target");
@@ -109,17 +110,17 @@ function getNodeFront(selector, {walker}
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently
  * loaded in the toolbox
  * @param {String} reason
  *        Defaults to "test" which instructs the inspector not
  *        to highlight the node upon selection
  * @return {Promise} Resolves when the inspector is updated with the new node
  */
-var selectNode = Task.async(function*(data, inspector, reason="test") {
+var selectNode = Task.async(function*(data, inspector, reason = "test") {
   info("Selecting the node for '" + data + "'");
   let nodeFront = data;
   if (!data._form) {
     nodeFront = yield getNodeFront(data, inspector);
   }
   let updated = inspector.once("inspector-updated");
   inspector.selection.setNodeFront(nodeFront, reason);
   yield updated;
@@ -253,17 +254,17 @@ function hasSideBarTab(inspector, id) {
 /**
  * Wait for eventName on target.
  * @param {Object} target An observable object that either supports on/off or
  * addEventListener/removeEventListener
  * @param {String} eventName
  * @param {Boolean} useCapture Optional, for add/removeEventListener
  * @return A promise that resolves when the event has been handled
  */
-function once(target, eventName, useCapture=false) {
+function once(target, eventName, useCapture = false) {
   info("Waiting for event: '" + eventName + "' on " + target + ".");
 
   let deferred = promise.defer();
 
   for (let [add, remove] of [
     ["addEventListener", "removeEventListener"],
     ["addListener", "removeListener"],
     ["on", "off"]
@@ -307,32 +308,34 @@ function waitForContentMessage(name) {
  * in doc_frame_script.js
  * @param {Object} data Optional data to send along
  * @param {Object} objects Optional CPOW objects to send along
  * @param {Boolean} expectResponse If set to false, don't wait for a response
  * with the same name from the content script. Defaults to true.
  * @return {Promise} Resolves to the response data if a response is expected,
  * immediately resolves otherwise
  */
-function executeInContent(name, data={}, objects={}, expectResponse=true) {
+function executeInContent(name, data = {}, objects = {},
+                          expectResponse = true) {
   info("Sending message " + name + " to content");
   let mm = gBrowser.selectedBrowser.messageManager;
 
   mm.sendAsyncMessage(name, data, objects);
   if (expectResponse) {
     return waitForContentMessage(name);
   }
 
   return promise.resolve();
 }
 
 /**
  * Get the current playState of an animation player on a given node.
  */
-var getAnimationPlayerState = Task.async(function*(selector, animationIndex=0) {
+var getAnimationPlayerState = Task.async(function*(selector,
+                                                   animationIndex = 0) {
   let playState = yield executeInContent("Test:GetAnimationPlayerState",
                                          {selector, animationIndex});
   return playState;
 });
 
 /**
  * Is the given node visible in the page (rendered in the frame tree).
  * @param {DOMNode}
@@ -361,17 +364,16 @@ var waitForAllAnimationTargets = Task.as
 
 /**
  * Check the scrubber element in the timeline is moving.
  * @param {AnimationPanel} panel
  * @param {Boolean} isMoving
  */
 function* assertScrubberMoving(panel, isMoving) {
   let timeline = panel.animationsTimelineComponent;
-  let scrubberEl = timeline.scrubberEl;
 
   if (isMoving) {
     // If we expect the scrubber to move, just wait for a couple of
     // timeline-data-changed events and compare times.
     let {time: time1} = yield timeline.once("timeline-data-changed");
     let {time: time2} = yield timeline.once("timeline-data-changed");
     ok(time2 > time1, "The scrubber is moving");
   } else {
@@ -441,18 +443,18 @@ function* changeTimelinePlaybackRate(pan
   EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"}, win);
   EventUtils.synthesizeMouseAtCenter(option, {type: "mouseup"}, win);
 
   yield onUiUpdated;
   yield waitForAllAnimationTargets(panel);
 
   // Simulate a mousemove outside of the rate selector area to avoid subsequent
   // tests from failing because of unwanted mouseover events.
-  EventUtils.synthesizeMouseAtCenter(win.document.querySelector("#timeline-toolbar"),
-                                     {type: "mousemove"}, win);
+  EventUtils.synthesizeMouseAtCenter(
+    win.document.querySelector("#timeline-toolbar"), {type: "mousemove"}, win);
 }
 
 /**
  * Prevent the toolbox common highlighter from making backend requests.
  * @param {Toolbox} toolbox
  */
 function disableHighlighter(toolbox) {
   toolbox._highlighter = {
@@ -514,11 +516,13 @@ function getKeyframeComponent(panel, ani
  * Get a keyframe element from the timeline.
  * @param {AnimationsPanel} panel The panel instance.
  * @param {Number} animationIndex The index of the animation in the timeline.
  * @param {String} propertyName The name of the animated property.
  * @param {Index} keyframeIndex The index of the keyframe.
  * @return {DOMNode} The keyframe element.
  */
 function getKeyframeEl(panel, animationIndex, propertyName, keyframeIndex) {
-  let keyframeComponent = getKeyframeComponent(panel, animationIndex, propertyName);
-  return keyframeComponent.keyframesEl.querySelectorAll(".frame")[keyframeIndex];
+  let keyframeComponent = getKeyframeComponent(panel, animationIndex,
+                                               propertyName);
+  return keyframeComponent.keyframesEl
+                          .querySelectorAll(".frame")[keyframeIndex];
 }
--- a/devtools/client/animationinspector/test/unit/test_formatStopwatchTime.js
+++ b/devtools/client/animationinspector/test/unit/test_formatStopwatchTime.js
@@ -4,17 +4,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 var Cu = Components.utils;
 const {require} = Cu.import("resource://devtools/shared/Loader.jsm", {});
 const {formatStopwatchTime} = require("devtools/client/animationinspector/utils");
 
-
 const TEST_DATA = [{
   desc: "Formatting 0",
   time: 0,
   expected: "00:00.000"
 }, {
   desc: "Formatting null",
   time: null,
   expected: "00:00.000"
--- a/devtools/client/animationinspector/utils.js
+++ b/devtools/client/animationinspector/utils.js
@@ -3,20 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const {Cu} = require("chrome");
 Cu.import("resource://devtools/client/shared/widgets/ViewHelpers.jsm");
-const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 var {loader} = Cu.import("resource://devtools/shared/Loader.jsm");
-loader.lazyRequireGetter(this, "EventEmitter",
-                               "devtools/shared/event-emitter");
+loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 
 const STRINGS_URI = "chrome://devtools/locale/animationinspector.properties";
 const L10N = new ViewHelpers.L10N(STRINGS_URI);
 // How many times, maximum, can we loop before we find the optimal time
 // interval in the timeline graph.
 const OPTIMAL_TIME_INTERVAL_MAX_ITERS = 100;
 // Time graduations should be multiple of one of these number.
 const OPTIMAL_TIME_INTERVAL_MULTIPLES = [1, 2.5, 5];
@@ -62,17 +60,18 @@ function createNode(options) {
 }
 
 exports.createNode = createNode;
 
 /**
  * Given a data-scale, draw the background for a graph (vertical lines) into a
  * canvas and set that canvas as an image-element with an ID that can be used
  * from CSS.
- * @param {Document} document The document where the image-element should be set.
+ * @param {Document} document The document where the image-element should be
+ * set.
  * @param {String} id The ID for the image-element.
  * @param {Number} graphWidth The width of the graph.
  * @param {Number} intervalWidth The width of one interval
  */
 function drawGraphElementBackground(document, id, graphWidth, intervalWidth) {
   let canvas = document.createElement("canvas");
   let ctx = canvas.getContext("2d");
 
@@ -157,20 +156,20 @@ function formatStopwatchTime(time) {
   }
 
   let milliseconds = parseInt(time % 1000, 10);
   let seconds = parseInt((time / 1000) % 60, 10);
   let minutes = parseInt((time / (1000 * 60)), 10);
 
   let pad = (nb, max) => {
     if (nb < max) {
-      return new Array((max+"").length - (nb+"").length + 1).join("0") + nb;
+      return new Array((max + "").length - (nb + "").length + 1).join("0") + nb;
     }
     return nb;
-  }
+  };
 
   minutes = pad(minutes, 10);
   seconds = pad(seconds, 10);
   milliseconds = pad(milliseconds, 100);
 
   return `${minutes}:${seconds}.${milliseconds}`;
 }
 
--- a/devtools/client/debugger/debugger-controller.js
+++ b/devtools/client/debugger/debugger-controller.js
@@ -292,17 +292,19 @@ var DebuggerController = {
       newSource(packet.source);
 
       // Make sure the events listeners are up to date.
       if (DebuggerView.instrumentsPaneTab == "events-tab") {
         fetchEventListeners();
       }
     });
 
-    this.Workers.connect();
+    if (this._target.isTabActor) {
+      this.Workers.connect();
+    }
     this.ThreadState.connect();
     this.StackFrames.connect();
 
     // Load all of the sources. Note that the server will actually
     // emit individual `newSource` notifications, which trigger
     // separate actions, so this won't do anything other than force
     // the server to traverse sources.
     this.dispatch(actions.loadSources()).then(() => {
--- a/devtools/client/debugger/test/mochitest/browser.ini
+++ b/devtools/client/debugger/test/mochitest/browser.ini
@@ -120,16 +120,18 @@ support-files =
   testactors.js
 
 [browser_dbg_aaa_run_first_leaktest.js]
 skip-if = e10s && debug
 [browser_dbg_addonactor.js]
 tags = addons
 [browser_dbg_addon-sources.js]
 tags = addons
+[browser_dbg_addon-workers-dbg-enabled.js]
+tags = addons
 [browser_dbg_addon-modules.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-modules-unpacked.js]
 skip-if = e10s # TODO
 tags = addons
 [browser_dbg_addon-panels.js]
 tags = addons
new file mode 100644
--- /dev/null
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addon-workers-dbg-enabled.js
@@ -0,0 +1,39 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that the Addon Debugger works when devtools.debugger.workers is enabled.
+// Workers controller cannot be used when debugging an Addon actor.
+
+const ADDON_URL = EXAMPLE_URL + "addon3.xpi";
+
+function test() {
+  Task.spawn(function*() {
+    info("Enable worker debugging.");
+    yield new Promise(resolve => {
+      SpecialPowers.pushPrefEnv({
+        "set": [["devtools.debugger.workers", true]]
+      }, resolve);
+    });
+
+    let addon = yield addAddon(ADDON_URL);
+    let addonDebugger = yield initAddonDebugger(ADDON_URL);
+
+    is(addonDebugger.title, "Debugger - browser_dbg_addon3",
+                            "Saw the right toolbox title.");
+
+    info("Check that groups and sources are displayed.");
+    let groups = yield addonDebugger.getSourceGroups();
+    is(groups.length, 2, "Should be only two groups.");
+    let sources = groups[0].sources;
+    is(sources.length, 2, "Should be two sources");
+
+    yield addonDebugger.destroy();
+    yield removeAddon(addon);
+    finish();
+  });
+}
+
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -952,17 +952,17 @@ CssRuleView.prototype = {
       }
     }
   },
 
   _populate: function() {
     let elementStyle = this._elementStyle;
     return this._elementStyle.populate().then(() => {
       if (this._elementStyle !== elementStyle || this.isDestroyed) {
-        return;
+        return null;
       }
 
       this._clearRules();
       let onEditorsReady = this._createEditors();
       this.refreshPseudoClassPanel();
 
       // Notify anyone that cares that we refreshed.
       return onEditorsReady.then(() => {
@@ -1688,18 +1688,18 @@ RuleViewTool.prototype = {
     let location = promise.resolve(rule.location);
     if (Services.prefs.getBoolPref(PREF_ORIG_SOURCES)) {
       location = rule.getOriginalLocation();
     }
     location.then(({ source, href, line, column }) => {
       let target = this.inspector.target;
       if (Tools.styleEditor.isTargetSupported(target)) {
         gDevTools.showToolbox(target, "styleeditor").then(function(toolbox) {
-          let sheet = source || href;
-          toolbox.getCurrentPanel().selectStyleSheet(sheet, line, column);
+          let url = source || href;
+          toolbox.getCurrentPanel().selectStyleSheet(url, line, column);
         });
       }
       return;
     });
   },
 
   onPropertyChanged: function() {
     this.inspector.markDirty();
--- a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_01.js
@@ -17,32 +17,27 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testCancelNew(view);
-});
 
-function* testCancelNew(view) {
   let elementRuleEditor = getRuleViewRuleEditor(view, 0);
-  let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
+  let editor = yield focusNewRuleViewProperty(elementRuleEditor);
   is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
     "The new property editor got focused");
 
   info("Escape the new property editor");
   let onBlur = once(editor.input, "blur");
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
   yield onBlur;
 
   info("Checking the state of cancelling a new property name editor");
-  ok(!elementRuleEditor.rule._applyingModifications,
-    "Shouldn't have an outstanding request after a cancel.");
   is(elementRuleEditor.rule.textProps.length, 0,
     "Should have cancelled creating a new text property.");
   ok(!elementRuleEditor.propertyList.hasChildNodes(),
     "Should not have any properties.");
   is(view.styleDocument.activeElement, view.styleDocument.documentElement,
     "Correct element has focus");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_02.js
@@ -16,66 +16,19 @@ const TEST_URI = `
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
   info("Test creating a new property and escaping");
-
-  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
-
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
-
-  is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
-    "The new property editor got focused.");
-
-  info("Entering a value in the property name editor");
-  editor.input.value = "color";
-
-  info("Pressing return to commit and focus the new value field");
-  let onValueFocus = once(elementRuleEditor.element, "focus", true);
-  let onRuleViewChanged = view.once("ruleview-changed");
-  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onValueFocus;
-  yield onRuleViewChanged;
-
-  // Getting the new value editor after focus
-  editor = inplaceEditor(view.styleDocument.activeElement);
-  let textProp = elementRuleEditor.rule.textProps[1];
-
-  is(elementRuleEditor.rule.textProps.length, 2,
-    "Created a new text property.");
-  is(elementRuleEditor.propertyList.children.length, 2,
-    "Created a property editor.");
-  is(editor, inplaceEditor(textProp.editor.valueSpan),
-    "Editing the value span now.");
-
-  info("Entering a property value");
-  editor.input.value = "red";
-
-  // Setting the input value above schedules a preview to be shown in 10ms
-  // which triggers a ruleview-changed event. If the event arrives at just the
-  // right moment after pressing escape but before the new property is removed
-  // from the rule view (it's done after a tick), the test continues too soon
-  // and the assertions below fail as the new property is still there (bug
-  // 1209295).
-  //
-  // Thus, wait for the ruleview-changed event triggered by the preview before
-  // continuing to discard the new property.
-  info("Waiting for preview to be applied.");
-  yield view.once("ruleview-changed");
-
-  info("Escaping out of the field");
-  onRuleViewChanged = view.once("ruleview-changed");
-  EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
-  yield onRuleViewChanged;
+  yield addProperty(view, 1, "color", "red", "VK_ESCAPE", false);
 
   is(view.styleDocument.documentElement, view.styleDocument.activeElement,
     "Correct element has focus");
 
+  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
   is(elementRuleEditor.rule.textProps.length, 1,
     "Removed the new text property.");
   is(elementRuleEditor.propertyList.children.length, 1,
     "Removed the property editor.");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-cancel_03.js
@@ -4,50 +4,40 @@
 
 "use strict";
 
 // Tests adding a new property and escapes the property name editor with a
 // value.
 
 const TEST_URI = `
   <style type='text/css'>
-    #testid {
+    div {
       background-color: blue;
     }
-    .testclass {
-      background-color: green;
-    }
   </style>
-  <div id='testid' class='testclass'>Styled Node</div>
+  <div>Test node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
-  yield selectNode("#testid", inspector);
-  yield testCancelNewOnEscape(inspector, view);
-});
+  yield selectNode("div", inspector);
 
-function* testCancelNewOnEscape(inspector, view) {
-  // Start at the beginning: start to add a rule to the element's style
-  // declaration, add some text, then press escape.
+  // Add a property to the element's style declaration, add some text,
+  // then press escape.
 
-  let elementRuleEditor = getRuleViewRuleEditor(view, 0);
-  let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
+  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
+  let editor = yield focusNewRuleViewProperty(elementRuleEditor);
 
   is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
     "Next focused editor should be the new property editor.");
 
   EventUtils.sendString("background", view.styleWindow);
 
   let onBlur = once(editor.input, "blur");
   EventUtils.synthesizeKey("VK_ESCAPE", {});
   yield onBlur;
 
-  ok(!elementRuleEditor.rule._applyingModifications,
-    "Shouldn't have an outstanding modification request after a cancel.");
-  is(elementRuleEditor.rule.textProps.length, 0,
+  is(elementRuleEditor.rule.textProps.length, 1,
     "Should have canceled creating a new text property.");
-  ok(!elementRuleEditor.propertyList.hasChildNodes(),
-    "Should not have any properties.");
   is(view.styleDocument.documentElement, view.styleDocument.activeElement,
     "Correct element has focus");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property-svg.js
@@ -8,54 +8,15 @@
 
 var TEST_URL = "chrome://global/skin/icons/warning.svg";
 var TEST_SELECTOR = "path";
 
 add_task(function*() {
   yield addTab(TEST_URL);
   let {inspector, view} = yield openRuleView();
   yield selectNode(TEST_SELECTOR, inspector);
-  yield testCreateNew(view);
-});
 
-function* testCreateNew(ruleView) {
   info("Test creating a new property");
-
-  let elementRuleEditor = getRuleViewRuleEditor(ruleView, 0);
-
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(ruleView, elementRuleEditor.closeBrace);
-
-  is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
-    "Next focused editor should be the new property editor.");
-
-  let input = editor.input;
-
-  info("Entering the property name");
-  input.value = "fill";
-
-  info("Pressing RETURN and waiting for the value field focus");
-  let onFocus = once(elementRuleEditor.element, "focus", true);
-  EventUtils.sendKey("return", ruleView.styleWindow);
-  yield onFocus;
-  yield elementRuleEditor.rule._applyingModifications;
-
-  editor = inplaceEditor(ruleView.styleDocument.activeElement);
-
-  is(elementRuleEditor.rule.textProps.length, 1,
-    "Should have created a new text property.");
-  is(elementRuleEditor.propertyList.children.length, 1,
-    "Should have created a property editor.");
-  let textProp = elementRuleEditor.rule.textProps[0];
-  is(editor, inplaceEditor(textProp.editor.valueSpan),
-    "Should be editing the value span now.");
-
-  editor.input.value = "red";
-  let onBlur = once(editor.input, "blur");
-  EventUtils.sendKey("return", ruleView.styleWindow);
-  yield onBlur;
-  yield elementRuleEditor.rule._applyingModifications;
-
-  is(textProp.value, "red", "Text prop should have been changed.");
+  yield addProperty(view, 0, "fill", "red");
 
   is((yield getComputedStyleProperty(TEST_SELECTOR, null, "fill")),
-    "rgb(255, 0, 0)", "The fill was changed to red");
-}
+     "rgb(255, 0, 0)", "The fill was changed to red");
+});
--- a/devtools/client/inspector/rules/test/browser_rules_add-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property_01.js
@@ -1,17 +1,15 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Testing various inplace-editor behaviors in the rule-view
-// FIXME: To be split in several test files, and some of the inplace-editor
-// focus/blur/commit/revert stuff should be factored out in head.js
+// Test adding an invalid property.
 
 const TEST_URI = `
   <style type='text/css'>
     #testid {
       background-color: blue;
     }
     .testclass {
       background-color: green;
@@ -19,56 +17,16 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testCreateNew(view);
-});
 
-function* testCreateNew(view) {
   info("Test creating a new property");
-
-  let elementRuleEditor = getRuleViewRuleEditor(view, 0);
-
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
-
-  is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
-    "The new property editor got focused");
-  let input = editor.input;
-
-  info("Entering background-color in the property name editor");
-  input.value = "background-color";
-
-  info("Pressing return to commit and focus the new value field");
-  let onModifications = view.once("ruleview-changed");
-  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onModifications;
-
-  // Getting the new value editor after focus
-  editor = inplaceEditor(view.styleDocument.activeElement);
-  let textProp = elementRuleEditor.rule.textProps[0];
-
-  is(elementRuleEditor.rule.textProps.length, 1,
-    "Created a new text property.");
-  is(elementRuleEditor.propertyList.children.length, 1,
-    "Created a property editor.");
-  is(editor, inplaceEditor(textProp.editor.valueSpan),
-    "Editing the value span now.");
-
-  ok(!textProp.editor.element.classList.contains("ruleview-overridden"),
-    "property should not be overridden.");
-
-  info("Entering a value and bluring the field to expect a rule change");
-  editor.input.value = "#XYZ";
-
-  onModifications = view.once("ruleview-changed");
-  editor.input.blur();
-  yield onModifications;
+  let textProp = yield addProperty(view, 0, "background-color", "#XYZ");
 
   is(textProp.value, "#XYZ", "Text prop should have been changed.");
   is(textProp.overridden, true, "Property should be overridden");
   is(textProp.editor.isValid(), false, "#XYZ should not be a valid entry");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-property_02.js
@@ -1,16 +1,15 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Tests all sorts of additions and updates of properties in the rule-view.
-// FIXME: TO BE SPLIT IN *MANY* SMALLER TESTS
+//
 
 const TEST_URI = `
   <style type="text/css">
     #testid {
       background-color: blue;
     }
     .testclass, .unmatched {
       background-color: green;
@@ -18,57 +17,53 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
   <div id='testid2'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
-  yield testCreateNew(inspector, view);
-});
 
-function* testCreateNew(inspector, ruleView) {
-  // Create a new property.
-  let elementRuleEditor = getRuleViewRuleEditor(ruleView, 0);
-  let editor = yield focusEditableField(ruleView, elementRuleEditor.closeBrace);
+  info("Focus the new property name field");
+  let elementRuleEditor = getRuleViewRuleEditor(view, 0);
+  let editor = yield focusNewRuleViewProperty(elementRuleEditor);
+  let input = editor.input;
 
   is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
     "Next focused editor should be the new property editor.");
-
-  let input = editor.input;
-
   ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
     "Editor contents are selected.");
 
   // Try clicking on the editor's input again, shouldn't cause trouble
   // (see bug 761665).
-  EventUtils.synthesizeMouse(input, 1, 1, {}, ruleView.styleWindow);
+  EventUtils.synthesizeMouse(input, 1, 1, {}, view.styleWindow);
   input.select();
 
   info("Entering the property name");
-  input.value = "background-color";
+  editor.input.value = "background-color";
 
   info("Pressing RETURN and waiting for the value field focus");
-  let onFocus = once(elementRuleEditor.element, "focus", true);
-  EventUtils.sendKey("return", ruleView.styleWindow);
-  yield onFocus;
-  yield elementRuleEditor.rule._applyingModifications;
+  let onNameAdded = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onNameAdded;
 
-  editor = inplaceEditor(ruleView.styleDocument.activeElement);
+  editor = inplaceEditor(view.styleDocument.activeElement);
 
   is(elementRuleEditor.rule.textProps.length, 1,
     "Should have created a new text property.");
   is(elementRuleEditor.propertyList.children.length, 1,
     "Should have created a property editor.");
   let textProp = elementRuleEditor.rule.textProps[0];
   is(editor, inplaceEditor(textProp.editor.valueSpan),
     "Should be editing the value span now.");
 
+  info("Entering the property value");
+  // We're editing inline style, so expect a style attribute mutation.
   let onMutated = inspector.once("markupmutation");
+  let onValueAdded = view.once("ruleview-changed");
   editor.input.value = "purple";
-  let onBlur = once(editor.input, "blur");
-  EventUtils.sendKey("return", ruleView.styleWindow);
-  yield onBlur;
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onValueAdded;
   yield onMutated;
 
   is(textProp.value, "purple", "Text prop should have been changed.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule_01.js
@@ -29,26 +29,27 @@ const TEST_DATA = [
   { node: ".class3.class4", expected: ".class3.class4" },
   { node: "p", expected: "p" },
   { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" },
   { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" }
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {inspector, view, testActor} = yield openRuleView();
 
   for (let data of TEST_DATA) {
     let {node, expected} = data;
     yield selectNode(node, inspector);
     yield addNewRule(inspector, view);
     yield testNewRule(view, expected, 1);
 
     info("Resetting page content");
-    content.document.body.innerHTML = TEST_URI;
+    yield testActor.eval(
+      "content.document.body.innerHTML = `" + TEST_URI + "`;");
   }
 });
 
 function* testNewRule(view, expected, index) {
   let idRuleEditor = getRuleViewRuleEditor(view, index);
   let editor = idRuleEditor.selectorText.ownerDocument.activeElement;
   is(editor.value, expected,
       "Selector editor value is as expected: " + expected);
--- a/devtools/client/inspector/rules/test/browser_rules_add-rule_05.js
+++ b/devtools/client/inspector/rules/test/browser_rules_add-rule_05.js
@@ -29,26 +29,27 @@ const TEST_DATA = [
   { node: ".class3.class4", expected: ".class3.class4" },
   { node: "p", expected: "p" },
   { node: "h1", expected: ".asd\\@\\@\\@\\@a\\!\\!\\!\\!\\:\\:\\:\\@asd" },
   { node: "h2", expected: "#asd\\@\\@\\@a\\!\\!2a" }
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {inspector, view, testActor} = yield openRuleView();
 
   for (let data of TEST_DATA) {
     let {node, expected} = data;
     yield selectNode(node, inspector);
     yield addNewRuleFromContextMenu(inspector, view);
     yield testNewRule(view, expected, 1);
 
     info("Resetting page content");
-    content.document.body.innerHTML = TEST_URI;
+    yield testActor.eval(
+      "content.document.body.innerHTML = `" + TEST_URI + "`;");
   }
 });
 
 function* addNewRuleFromContextMenu(inspector, view) {
   info("Waiting for context menu to be shown");
   let onPopup = once(view._contextmenu._menupopup, "popupshown");
   let win = view.styleWindow;
 
--- a/devtools/client/inspector/rules/test/browser_rules_authored.js
+++ b/devtools/client/inspector/rules/test/browser_rules_authored.js
@@ -2,21 +2,21 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for as-authored styles.
 
 function* createTestContent(style) {
-  let content = `<style type="text/css">
+  let html = `<style type="text/css">
       ${style}
       </style>
       <div id="testid" class="testclass">Styled Node</div>`;
-  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(content));
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
 
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
   return view;
 }
 
 add_task(function* () {
   let view = yield createTestContent("#testid {" +
--- a/devtools/client/inspector/rules/test/browser_rules_authored_color.js
+++ b/devtools/client/inspector/rules/test/browser_rules_authored_color.js
@@ -2,22 +2,22 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for as-authored styles.
 
 function* createTestContent(style) {
-  let content = `<style type="text/css">
+  let html = `<style type="text/css">
       ${style}
       </style>
       <div id="testid" class="testclass">Styled Node</div>`;
   let tab = yield addTab("data:text/html;charset=utf-8," +
-                         encodeURIComponent(content));
+                         encodeURIComponent(html));
 
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
   return {view, tab};
 }
 
 add_task(function* () {
   let colors = [
@@ -36,20 +36,18 @@ add_task(function* () {
 
     let cPicker = view.tooltips.colorPicker;
     let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
         .querySelector(".ruleview-colorswatch");
     let onShown = cPicker.tooltip.once("shown");
     swatch.click();
     yield onShown;
 
-    let testNode = getNode("#testid");
-
     yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
-      element: testNode,
+      selector: "#testid",
       name: "color",
       value: "rgb(0, 255, 0)"
     });
 
     let spectrum = yield cPicker.spectrum;
     let onHidden = cPicker.tooltip.once("hidden");
     // Validating the color change ends up updating the rule view twice
     let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
--- a/devtools/client/inspector/rules/test/browser_rules_authored_override.js
+++ b/devtools/client/inspector/rules/test/browser_rules_authored_override.js
@@ -2,21 +2,21 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test for as-authored styles.
 
 function* createTestContent(style) {
-  let content = `<style type="text/css">
+  let html = `<style type="text/css">
       ${style}
       </style>
       <div id="testid" class="testclass">Styled Node</div>`;
-  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(content));
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(html));
 
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
   return view;
 }
 
 add_task(function* () {
   let gradientText1 = "(orange, blue);";
@@ -37,18 +37,17 @@ add_task(function* () {
 
   // Initially the last property should be active.
   for (let i = 0; i < 3; ++i) {
     let prop = rule.textProps[i];
     is(prop.name, "background-image", "check the property name");
     is(prop.overridden, i !== 2, "check overridden for " + i);
   }
 
-  rule.textProps[2].setEnabled(false);
-  yield rule._applyingModifications;
+  yield togglePropStatus(view, rule.textProps[2]);
 
   // Now the first property should be active.
   for (let i = 0; i < 3; ++i) {
     let prop = rule.textProps[i];
     is(prop.overridden || !prop.enabled, i !== 0,
        "post-change check overridden for " + i);
   }
 });
--- a/devtools/client/inspector/rules/test/browser_rules_colorUnit.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorUnit.js
@@ -41,20 +41,18 @@ add_task(function*() {
 function* basicTest(view, name, result) {
   let cPicker = view.tooltips.colorPicker;
   let swatch = getRuleViewProperty(view, "#testid", "color").valueSpan
       .querySelector(".ruleview-colorswatch");
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
-  let testNode = getNode("#testid");
-
   yield simulateColorPickerChange(view, cPicker, [0, 255, 0, 1], {
-    element: testNode,
+    selector: "#testid",
     name: "color",
     value: "rgb(0, 255, 0)"
   });
 
   let spectrum = yield cPicker.spectrum;
   let onHidden = cPicker.tooltip.once("hidden");
   // Validating the color change ends up updating the rule view twice
   let onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_01.js
@@ -34,18 +34,18 @@ function* testImageTooltipAfterColorChan
   is(anchor, url, "The anchor returned by the showOnHover callback is correct");
 
   info("Open the color picker tooltip and change the color");
   let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
   yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], {
-    element: content.document.body,
-    name: "backgroundImage",
+    selector: "body",
+    name: "background-image",
     value: 'url("chrome://global/skin/icons/warning-64.png"), linear-gradient(rgb(0, 0, 0), rgb(255, 0, 102) 400px)'
   });
 
   let spectrum = yield picker.spectrum;
   let onHidden = picker.tooltip.once("hidden");
   let onModifications = ruleView.once("ruleview-changed");
   EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-and-image-tooltip_02.js
@@ -15,20 +15,16 @@ const TEST_URI = `
     body {
       background: red url("chrome://global/skin/icons/warning-64.png")
         no-repeat center center;
     }
   </style>
   Testing the color picker tooltip!
 `;
 
-const PAGE_CONTENT = [
-
-].join("\n");
-
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {view} = yield openRuleView();
   yield testColorChangeIsntRevertedWhenOtherTooltipIsShown(view);
 });
 
 function* testColorChangeIsntRevertedWhenOtherTooltipIsShown(ruleView) {
   let swatch = getRuleViewProperty(ruleView, "body", "background").valueSpan
@@ -36,18 +32,18 @@ function* testColorChangeIsntRevertedWhe
 
   info("Open the color picker tooltip and change the color");
   let picker = ruleView.tooltips.colorPicker;
   let onShown = picker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   yield simulateColorPickerChange(ruleView, picker, [0, 0, 0, 1], {
-    element: content.document.body,
-    name: "backgroundColor",
+    selector: "body",
+    name: "background-color",
     value: "rgb(0, 0, 0)"
   });
 
   let spectrum = yield picker.spectrum;
   let onModifications = ruleView.once("ruleview-changed");
   let onHidden = picker.tooltip.once("hidden");
   EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-commit-on-ENTER.js
@@ -28,34 +28,34 @@ add_task(function*() {
 function* testPressingEnterCommitsChanges(swatch, ruleView) {
   let cPicker = ruleView.tooltips.colorPicker;
 
   let onShown = cPicker.tooltip.once("shown");
   swatch.click();
   yield onShown;
 
   yield simulateColorPickerChange(ruleView, cPicker, [0, 255, 0, .5], {
-    element: content.document.body,
-    name: "borderLeftColor",
+    selector: "body",
+    name: "border-left-color",
     value: "rgba(0, 255, 0, 0.5)"
   });
 
   is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
     "The color swatch's background was updated");
   is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent,
     "2em solid rgba(0, 255, 0, 0.5)",
     "The text of the border css property was updated");
 
   let onModified = ruleView.once("ruleview-changed");
   let spectrum = yield cPicker.spectrum;
   let onHidden = cPicker.tooltip.once("hidden");
   EventUtils.sendKey("RETURN", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
   yield onModified;
 
-  is(content.getComputedStyle(content.document.body).borderLeftColor,
+  is((yield getComputedStyleProperty("body", null, "border-left-color")),
     "rgba(0, 255, 0, 0.5)", "The element's border was kept after RETURN");
   is(swatch.style.backgroundColor, "rgba(0, 255, 0, 0.5)",
     "The color swatch's background was kept after RETURN");
   is(getRuleViewProperty(ruleView, "body", "border").valueSpan.textContent,
     "2em solid rgba(0, 255, 0, 0.5)",
     "The text of the border css property was kept after RETURN");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-edit-gradient.js
@@ -46,31 +46,32 @@ function testColorParsing(view) {
 }
 
 function* testPickingNewColor(view) {
   // Grab the first color swatch and color in the gradient
   let ruleEl = getRuleViewProperty(view, "body", "background-image");
   let swatchEl = ruleEl.valueSpan.querySelector(".ruleview-colorswatch");
   let colorEl = ruleEl.valueSpan.querySelector(".ruleview-color");
 
-  info("Getting the color picker tooltip and clicking on the swatch to show it");
+  info("Get the color picker tooltip and clicking on the swatch to show it");
   let cPicker = view.tooltips.colorPicker;
   let onShown = cPicker.tooltip.once("shown");
   swatchEl.click();
   yield onShown;
 
   let change = {
-    element: content.document.body,
-    name: "backgroundImage",
-    value: "linear-gradient(to left, rgb(1, 1, 1) 25%, rgb(51, 51, 51) 95%, rgb(0, 0, 0) 100%)"
+    selector: "body",
+    name: "background-image",
+    value: "linear-gradient(to left, rgb(1, 1, 1) 25%, " +
+           "rgb(51, 51, 51) 95%, rgb(0, 0, 0) 100%)"
   };
   yield simulateColorPickerChange(view, cPicker, [1, 1, 1, 1], change);
 
   is(swatchEl.style.backgroundColor, "rgb(1, 1, 1)",
     "The color swatch's background was updated");
   is(colorEl.textContent, "#010101", "The color text was updated");
-  is(content.getComputedStyle(content.document.body).backgroundImage,
+  is((yield getComputedStyleProperty("body", null, "background-image")),
     "linear-gradient(to left, rgb(1, 1, 1) 25%, rgb(51, 51, 51) 95%, " +
       "rgb(0, 0, 0) 100%)",
     "The gradient has been updated correctly");
 
   yield hideTooltipAndWaitForRuleViewChanged(cPicker, view);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-multiple-changes.js
@@ -50,17 +50,17 @@ function* testSimpleMultipleColorChanges
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
     {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
   ];
   for (let {rgba, computed} of colors) {
     yield simulateColorPickerChange(ruleView, picker, rgba, {
-      element: content.document.querySelector("p"),
+      selector: "p",
       name: "color",
       value: computed
     });
   }
 }
 
 function* testComplexMultipleColorChanges(inspector, ruleView) {
   yield selectNode("body", inspector);
@@ -78,18 +78,18 @@ function* testComplexMultipleColorChange
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
     {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
   ];
   for (let {rgba, computed} of colors) {
     yield simulateColorPickerChange(ruleView, picker, rgba, {
-      element: content.document.body,
-      name: "backgroundColor",
+      selector: "body",
+      name: "background-color",
       value: computed
     });
   }
 
   info("Closing the color picker");
   yield hideTooltipAndWaitForRuleViewChanged(picker.tooltip, ruleView);
 }
 
@@ -109,16 +109,16 @@ function* testOverriddenMultipleColorCha
   info("Changing the color several times");
   let colors = [
     {rgba: [0, 0, 0, 1], computed: "rgb(0, 0, 0)"},
     {rgba: [100, 100, 100, 1], computed: "rgb(100, 100, 100)"},
     {rgba: [200, 200, 200, 1], computed: "rgb(200, 200, 200)"}
   ];
   for (let {rgba, computed} of colors) {
     yield simulateColorPickerChange(ruleView, picker, rgba, {
-      element: content.document.body,
+      selector: "body",
       name: "color",
       value: computed
     });
-    is(content.getComputedStyle(content.document.querySelector("p")).color,
+    is((yield getComputedStyleProperty("p", null, "color")),
       "rgb(200, 200, 200)", "The color of the P tag is still correct");
   }
 }
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-release-outside-frame.js
@@ -39,17 +39,18 @@ add_task(function*() {
 
   // Releasing the button pressed by mousedown above on top of a different frame
   // does not make sense in this test as EventUtils doesn't preserve the context
   // i.e. the buttons that were pressed down between events.
 
   info("Moving mouse over color picker without any buttons pressed.");
 
   EventUtils.synthesizeMouse(spectrum.dragger, 10, 10, {
-    button: -1, // -1 = no buttons are pressed down
+    // -1 = no buttons are pressed down
+    button: -1,
     type: "mousemove",
   }, spectrum.dragger.ownerDocument.defaultView);
 });
 
 function* openColorPickerForSwatch(swatch, view) {
   let cPicker = view.tooltips.colorPicker;
   ok(cPicker, "The rule-view has the expected colorPicker property");
 
--- a/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js
+++ b/devtools/client/inspector/rules/test/browser_rules_colorpicker-revert-on-ESC.js
@@ -18,30 +18,22 @@ const TEST_URI = `
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {view} = yield openRuleView();
   yield testPressingEscapeRevertsChanges(view);
   yield testPressingEscapeRevertsChangesAndDisables(view);
 });
 
 function* testPressingEscapeRevertsChanges(view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
-  let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
-  let cPicker = view.tooltips.colorPicker;
-
-  let onShown = cPicker.tooltip.once("shown");
-  swatch.click();
-  yield onShown;
-
-  yield simulateColorPickerChange(view, cPicker, [0, 0, 0, 1], {
-    element: content.document.body,
-    name: "backgroundColor",
-    value: "rgb(0, 0, 0)"
-  });
+  let {swatch, propEditor, cPicker} = yield openColorPickerAndSelectColor(view,
+    1, 0, [0, 0, 0, 1], {
+      selector: "body",
+      name: "background-color",
+      value: "rgb(0, 0, 0)"
+    });
 
   is(swatch.style.backgroundColor, "rgb(0, 0, 0)",
     "The color swatch's background was updated");
   is(propEditor.valueSpan.textContent, "#000",
     "The text of the background-color css property was updated");
 
   let spectrum = yield cPicker.spectrum;
 
@@ -55,66 +47,60 @@ function* testPressingEscapeRevertsChang
   yield waitForComputedStyleProperty("body", null, "background-color",
     "rgb(237, 237, 237)");
   is(propEditor.valueSpan.textContent, "#EDEDED",
     "Got expected property value.");
 }
 
 function* testPressingEscapeRevertsChangesAndDisables(view) {
   let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
-  let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
-  let cPicker = view.tooltips.colorPicker;
 
   info("Disabling background-color property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  let textProp = ruleEditor.rule.textProps[0];
+  yield togglePropStatus(view, textProp);
 
-  ok(propEditor.element.classList.contains("ruleview-overridden"),
+  ok(textProp.editor.element.classList.contains("ruleview-overridden"),
     "property is overridden.");
-  is(propEditor.enable.style.visibility, "visible",
+  is(textProp.editor.enable.style.visibility, "visible",
     "property enable checkbox is visible.");
-  ok(!propEditor.enable.getAttribute("checked"),
+  ok(!textProp.editor.enable.getAttribute("checked"),
     "property enable checkbox is not checked.");
-  ok(!propEditor.prop.enabled,
+  ok(!textProp.editor.prop.enabled,
     "background-color property is disabled.");
   let newValue = yield getRulePropertyValue("background-color");
   is(newValue, "", "background-color should have been unset.");
 
-  let onShown = cPicker.tooltip.once("shown");
-  swatch.click();
-  yield onShown;
+  let {cPicker} = yield openColorPickerAndSelectColor(view,
+    1, 0, [0, 0, 0, 1]);
 
-  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+  ok(!textProp.editor.element.classList.contains("ruleview-overridden"),
     "property overridden is not displayed.");
-  is(propEditor.enable.style.visibility, "hidden",
+  is(textProp.editor.enable.style.visibility, "hidden",
     "property enable checkbox is hidden.");
 
   let spectrum = yield cPicker.spectrum;
-  info("Simulating a color picker change in the widget");
-  spectrum.rgb = [0, 0, 0, 1];
-  yield ruleEditor.rule._applyingModifications;
 
   info("Pressing ESCAPE to close the tooltip");
   let onHidden = cPicker.tooltip.once("hidden");
+  let onModifications = view.once("ruleview-changed");
   EventUtils.sendKey("ESCAPE", spectrum.element.ownerDocument.defaultView);
   yield onHidden;
-  yield ruleEditor.rule._applyingModifications;
+  yield onModifications;
 
-  ok(propEditor.element.classList.contains("ruleview-overridden"),
+  ok(textProp.editor.element.classList.contains("ruleview-overridden"),
     "property is overridden.");
-  is(propEditor.enable.style.visibility, "visible",
+  is(textProp.editor.enable.style.visibility, "visible",
     "property enable checkbox is visible.");
-  ok(!propEditor.enable.getAttribute("checked"),
+  ok(!textProp.editor.enable.getAttribute("checked"),
     "property enable checkbox is not checked.");
-  ok(!propEditor.prop.enabled,
+  ok(!textProp.editor.prop.enabled,
     "background-color property is disabled.");
   newValue = yield getRulePropertyValue("background-color");
   is(newValue, "", "background-color should have been unset.");
-  is(propEditor.valueSpan.textContent, "#EDEDED",
+  is(textProp.editor.valueSpan.textContent, "#EDEDED",
     "Got expected property value.");
 }
 
 function* getRulePropertyValue(name) {
   let propValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: name
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_01.js
@@ -53,61 +53,71 @@ var testData = [
   ["i", "fiill", -1, 0],
   ["VK_ESCAPE", null, -1, 0],
 ];
 
 const TEST_URI = "<h1 style='font: 24px serif'>Header</h1>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {toolbox, inspector, view, testActor} = yield openRuleView();
 
   info("Test autocompletion after 1st page load");
   yield runAutocompletionTest(toolbox, inspector, view);
 
   info("Test autocompletion after page navigation");
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing the css property editable field");
   let propertyName = view.styleDocument
     .querySelectorAll(".ruleview-propertyname")[0];
   let editor = yield focusEditableField(view, propertyName);
 
   info("Starting to test for css property completion");
+  let previousPopupSize = 0;
   for (let i = 0; i < testData.length; i++) {
-    yield testCompletion(testData[i], editor, view);
+    let expectPopupHiddenEvent = previousPopupSize > 0 && testData[3] === 0;
+    yield testCompletion(testData[i], expectPopupHiddenEvent, editor, view);
+    previousPopupSize = testData[3];
   }
 }
 
-function* testCompletion([key, completion, index, total], editor, view) {
+function* testCompletion([key, completion, index, total],
+                         expectPopupHiddenEvent, editor, view) {
   info("Pressing key " + key);
   info("Expecting " + completion + ", " + index + ", " + total);
 
+  // Listening for the right event that will tell us when the key has been
+  // entered and processed.
   let onSuggest;
-
   if (/(left|right|back_space|escape|home|end|page_up|page_down)/ig.test(key)) {
     info("Adding event listener for " +
       "left|right|back_space|escape|home|end|page_up|page_down keys");
     onSuggest = once(editor.input, "keypress");
   } else {
     info("Waiting for after-suggest event on the editor");
     onSuggest = editor.once("after-suggest");
   }
 
+  // Also listening for popup hiding if needed.
+  let onMaybePopupHidden = expectPopupHiddenEvent
+                           ? once(editor.popup._panel, "hidden")
+                           : null;
+
   info("Synthesizing key " + key);
   EventUtils.synthesizeKey(key, {}, view.styleWindow);
 
   yield onSuggest;
-  yield wait(1); // Equivalent of executeSoon
+  yield onMaybePopupHidden;
 
   info("Checking the state");
   if (completion != null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
   if (total == 0) {
     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
   } else {
--- a/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-existing-property_02.js
@@ -35,23 +35,23 @@ var testData = [
   ["n", {}, "none", -1, 0],
   ["VK_RETURN", {}, null, -1, 0]
 ];
 
 const TEST_URI = "<h1 style='color: red'>Header</h1>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {toolbox, inspector, view, testActor} = yield openRuleView();
 
   info("Test autocompletion after 1st page load");
   yield runAutocompletionTest(toolbox, inspector, view);
 
   info("Test autocompletion after page navigation");
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing the css property editable value");
@@ -85,17 +85,17 @@ function* testCompletion([key, modifiers
     info("Waiting for after-suggest event on the editor");
     onKeyPress = editor.once("after-suggest");
   }
 
   info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
   EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
 
   yield onKeyPress;
-  yield wait(1); // Equivalent of executeSoon
+  yield waitForTick();
 
   // The key might have been a TAB or shift-TAB, in which case the editor will
   // be a new one
   editor = inplaceEditor(view.styleDocument.activeElement);
 
   info("Checking the state");
   if (completion != null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_01.js
@@ -36,23 +36,23 @@ var testData = [
   ["i", "fill", 0, 4],
   ["VK_ESCAPE", null, -1, 0],
 ];
 
 const TEST_URI = "<h1 style='border: 1px solid red'>Header</h1>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {toolbox, inspector, view, testActor} = yield openRuleView();
 
   info("Test autocompletion after 1st page load");
   yield runAutocompletionTest(toolbox, inspector, view);
 
   info("Test autocompletion after page navigation");
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing the css property editable field");
@@ -78,17 +78,17 @@ function* testCompletion([key, completio
     info("Waiting for after-suggest event on the editor");
     onSuggest = editor.once("after-suggest");
   }
 
   info("Synthesizing key " + key);
   EventUtils.synthesizeKey(key, {}, view.styleWindow);
 
   yield onSuggest;
-  yield wait(1); // Equivalent of executeSoon
+  yield waitForTick();
 
   info("Checking the state");
   if (completion != null) {
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
   if (total == 0) {
     ok(!(editor.popup && editor.popup.isOpen), "Popup is closed");
   } else {
--- a/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-new-property_02.js
@@ -45,23 +45,23 @@ const TEST_URI = `
       border: 1px solid red;
     }
   </style>
   <h1>Test element</h1>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {toolbox, inspector, view, testActor} = yield openRuleView();
 
   info("Test autocompletion after 1st page load");
   yield runAutocompletionTest(toolbox, inspector, view);
 
   info("Test autocompletion after page navigation");
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
   yield runAutocompletionTest(toolbox, inspector, view);
 });
 
 function* runAutocompletionTest(toolbox, inspector, view) {
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing a new css property editable property");
@@ -96,17 +96,17 @@ function* testCompletion([key, modifiers
     info("Waiting for after-suggest event on the editor");
     onKeyPress = editor.once("after-suggest");
   }
 
   info("Synthesizing key " + key + ", modifiers: " + Object.keys(modifiers));
   EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
 
   yield onKeyPress;
-  yield wait(1); // Equivalent of executeSoon
+  yield waitForTick();
 
   info("Checking the state");
   if (completion != null) {
     // The key might have been a TAB or shift-TAB, in which case the editor will
     // be a new one
     editor = inplaceEditor(view.styleDocument.activeElement);
     is(editor.input.value, completion, "Correct value is autocompleted");
   }
--- a/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js
+++ b/devtools/client/inspector/rules/test/browser_rules_completion-popup-hidden-after-navigation.js
@@ -5,17 +5,17 @@
 "use strict";
 
 // Tests that the ruleview autocomplete popup is hidden after page navigation.
 
 const TEST_URI = "<h1 style='font: 24px serif'></h1>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {inspector, view, testActor} = yield openRuleView();
 
   info("Test autocompletion popup is hidden after page navigation");
 
   info("Selecting the test node");
   yield selectNode("h1", inspector);
 
   info("Focusing the css property editable field");
   let propertyName = view.styleDocument
@@ -23,17 +23,17 @@ add_task(function*() {
   let editor = yield focusEditableField(view, propertyName);
 
   info("Pressing key VK_DOWN");
   let onSuggest = once(editor.input, "keypress");
   EventUtils.synthesizeKey("VK_DOWN", {}, view.styleWindow);
 
   info("Waiting for autocomplete popup to be displayed");
   yield onSuggest;
-  yield wait(1);
+  yield waitForTick();
 
   ok(view.popup && view.popup.isOpen, "Popup should be opened");
 
   info("Reloading the page");
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
 
   ok(!(view.popup && view.popup.isOpen), "Popup should be closed");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_computed-lists_02.js
@@ -26,43 +26,49 @@ add_task(function*() {
 
 function* testComputedList(inspector, view) {
   let rule = getRuleViewRuleEditor(view, 1).rule;
   let propEditor = rule.textProps[0].editor;
   let expander = propEditor.expander;
 
   ok(!expander.hasAttribute("open"), "margin computed list is closed");
 
-  info("Opening the computed list of margin property")
+  info("Opening the computed list of margin property");
   expander.click();
   ok(expander.hasAttribute("open"), "margin computed list is open");
 
   let computed = propEditor.prop.computed;
   let computedDom = propEditor.computed;
   let propNames = [
     "margin-top",
     "margin-right",
     "margin-bottom",
     "margin-left"
   ];
 
   is(computed.length, propNames.length, "There should be 4 computed values");
-  is(computedDom.children.length, propNames.length, "There should be 4 nodes in the DOM");
+  is(computedDom.children.length, propNames.length,
+     "There should be 4 nodes in the DOM");
+
   propNames.forEach((propName, i) => {
     let propValue = i + "px";
-    is(computed[i].name, propName, "Computed property #" + i + " has name " + propName);
-    is(computed[i].value, propValue, "Computed property #" + i + " has value " + propValue);
-    is(computedDom.getElementsByClassName("ruleview-propertyname")[i].textContent, propName,
-        "Computed property #" + i + " in DOM has correct name");
-    is(computedDom.getElementsByClassName("ruleview-propertyvalue")[i].textContent, propValue,
-        "Computed property #" + i + " in DOM has correct value");
+    is(computed[i].name, propName,
+       "Computed property #" + i + " has name " + propName);
+    is(computed[i].value, propValue,
+       "Computed property #" + i + " has value " + propValue);
+    is(computedDom.querySelectorAll(".ruleview-propertyname")[i].textContent,
+       propName,
+       "Computed property #" + i + " in DOM has correct name");
+    is(computedDom.querySelectorAll(".ruleview-propertyvalue")[i].textContent,
+       propValue,
+       "Computed property #" + i + " in DOM has correct value");
   });
 
-  info("Closing the computed list of margin property")
+  info("Closing the computed list of margin property");
   expander.click();
   ok(!expander.hasAttribute("open"), "margin computed list is closed");
 
-  info("Opening the computed list of margin property")
+  info("Opening the computed list of margin property");
   expander.click();
   ok(expander.hasAttribute("open"), "margin computed list is open");
   is(computed.length, propNames.length, "Still 4 computed values");
   is(computedDom.children.length, propNames.length, "Still 4 nodes in the DOM");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_content_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_content_01.js
@@ -1,54 +1,51 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the rule-view content is correct
 
-add_task(function*() {
-  yield addTab("data:text/html;charset=utf-8,browser_ruleview_content.js");
-  let {toolbox, inspector, view} = yield openRuleView();
+const TEST_URI = `
+  <style type="text/css">
+    @media screen and (min-width: 10px) {
+      #testid {
+        background-color: blue;
+      }
+    }
+    .testclass, .unmatched {
+      background-color: green;
+    }
+  </style>
+  <div id="testid" class="testclass">Styled Node</div>
+  <div id="testid2">Styled Node</div>
+`;
 
-  info("Creating the test document");
-  let style = "" +
-    "@media screen and (min-width: 10px) {" +
-    "  #testid {" +
-    "    background-color: blue;" +
-    "  }" +
-    "}" +
-    ".testclass, .unmatched {" +
-    "  background-color: green;" +
-    "}";
-  let styleNode = addStyle(content.document, style);
-  content.document.body.innerHTML = "<div id='testid' class='testclass'>Styled Node</div>" +
-                                    "<div id='testid2'>Styled Node</div>";
+add_task(function*() {
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
+  let {inspector, view} = yield openRuleView();
 
-  yield testContentAfterNodeSelection(inspector, view);
-});
-
-function* testContentAfterNodeSelection(inspector, ruleView) {
   yield selectNode("#testid", inspector);
-  is(ruleView.element.querySelectorAll("#noResults").length, 0,
+  is(view.element.querySelectorAll("#noResults").length, 0,
     "After a highlight, no longer has a no-results element.");
 
-  yield clearCurrentNodeSelection(inspector)
-  is(ruleView.element.querySelectorAll("#noResults").length, 1,
+  yield clearCurrentNodeSelection(inspector);
+  is(view.element.querySelectorAll("#noResults").length, 1,
     "After highlighting null, has a no-results element again.");
 
   yield selectNode("#testid", inspector);
 
-  let linkText = getRuleViewLinkTextByIndex(ruleView, 1);
-  is(linkText, "inline:1 @screen and (min-width: 10px)",
+  let linkText = getRuleViewLinkTextByIndex(view, 1);
+  is(linkText, "inline:3 @screen and (min-width: 10px)",
     "link text at index 1 contains media query text.");
 
-  linkText = getRuleViewLinkTextByIndex(ruleView, 2);
-  is(linkText, "inline:1",
+  linkText = getRuleViewLinkTextByIndex(view, 2);
+  is(linkText, "inline:7",
     "link text at index 2 contains no media query text.");
 
-  let classEditor = getRuleViewRuleEditor(ruleView, 2);
-  is(classEditor.selectorText.querySelector(".ruleview-selector-matched").textContent,
+  let selector = getRuleViewRuleEditor(view, 2).selectorText;
+  is(selector.querySelector(".ruleview-selector-matched").textContent,
     ".testclass", ".textclass should be matched.");
-  is(classEditor.selectorText.querySelector(".ruleview-selector-unmatched").textContent,
+  is(selector.querySelector(".ruleview-selector-unmatched").textContent,
     ".unmatched", ".unmatched should not be matched.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_content_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_content_02.js
@@ -1,24 +1,26 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
-
+/* globals getTestActorWithoutToolbox */
 "use strict";
 
 // Test the rule-view content when the inspector gets opened via the page
 // ctx-menu "inspect element"
 
-const CONTENT = '<body style="color:red;">\
-                   <div style="color:blue;">\
-                     <p style="color:green;">\
-                       <span style="color:yellow;">test element</span>\
-                     </p>\
-                   </div>\
-                 </body>';
+const CONTENT = `
+  <body style="color:red;">
+    <div style="color:blue;">
+      <p style="color:green;">
+        <span style="color:yellow;">test element</span>
+      </p>
+    </div>
+  </body>
+`;
 
 const STRINGS = Services.strings
   .createBundle("chrome://devtools-shared/locale/styleinspector.properties");
 
 add_task(function*() {
   let tab = yield addTab("data:text/html;charset=utf-8," + CONTENT);
 
   let testActor = yield getTestActorWithoutToolbox(tab);
@@ -48,15 +50,17 @@ function checkRuleViewContent({styleDocu
 
   for (let rule of rules) {
     let selector = rule.querySelector(".ruleview-selectorcontainer");
     is(selector.textContent,
       STRINGS.GetStringFromName("rule.sourceElement"),
       "The rule's selector is correct");
 
     let propertyNames = [...rule.querySelectorAll(".ruleview-propertyname")];
-    is(propertyNames.length, 1, "There's only one property name, as expected");
+    is(propertyNames.length, 1,
+       "There's only one property name, as expected");
 
     let propertyValues = [...rule.querySelectorAll(".ruleview-propertyvalue")];
-    is(propertyValues.length, 1, "There's only one property value, as expected");
+    is(propertyValues.length, 1,
+       "There's only one property value, as expected");
   }
 }
 
--- a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-01.js
@@ -11,18 +11,16 @@
  * docs tooltip should be shown, containing docs from MDN for that property.
  *
  * This file tests that the context menu item is shown when it should be
  * shown and hidden when it should be hidden.
  */
 
 "use strict";
 
-const {setBaseCssDocsUrl} = require("devtools/client/shared/widgets/MdnDocsWidget");
-
 /**
  * The test document tries to confuse the context menu
  * code by having a tag called "padding" and a property
  * value called "margin".
  */
 const TEST_URI = `
   <html>
     <head>
--- a/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_context-menu-show-mdn-docs-02.js
@@ -12,17 +12,18 @@
  *
  * This file tests that:
  * - clicking the context menu item shows the tooltip
  * - pressing "Escape" while the tooltip is showing hides the tooltip
  */
 
 "use strict";
 
-const {setBaseCssDocsUrl} = require("devtools/client/shared/widgets/MdnDocsWidget");
+const {setBaseCssDocsUrl} =
+  require("devtools/client/shared/widgets/MdnDocsWidget");
 
 const PROPERTYNAME = "color";
 
 const TEST_DOC = `
   <html>
     <body>
       <div style="color: red">
         Test "Show MDN Docs" context menu option
@@ -65,23 +66,20 @@ function* testShowMdnTooltip(view) {
  *  - the tooltip is hidden when we press Escape
  */
 function* testShowAndHideMdnTooltip(view) {
   yield testShowMdnTooltip(view);
 
   info("Quick check that the tooltip contents are set");
   let cssDocs = view.tooltips.cssDocs;
 
+  // FIXME: Remove the comment below when bug 1246896 is fixed.
+  /* eslint-disable mozilla/no-cpows-in-tests */
   let tooltipDocument = cssDocs.tooltip.content.contentDocument;
   let h1 = tooltipDocument.getElementById("property-name");
   is(h1.textContent, PROPERTYNAME, "The MDN docs tooltip h1 is correct");
 
   info("Simulate pressing the 'Escape' key");
   let onHidden = cssDocs.tooltip.once("hidden");
   EventUtils.sendKey("escape");
   yield onHidden;
   ok(true, "The MDN docs tooltip was hidden on pressing 'escape'");
 }
-
-/**
- * Returns the root element for the rule view.
- */
-var rootElement = view => (view.element) ? view.element : view.styleDocument;
--- a/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_copy_styles.js
@@ -254,30 +254,27 @@ function* checkCopyStyle(view, node, men
   is(view._contextmenu.menuitemCopyRule.hidden,
      hidden.copyRule,
      "Copy Rule hidden attribute is as expected: " +
      hidden.copyRule);
 
   try {
     yield waitForClipboard(() => menuItem.click(),
       () => checkClipboardData(expectedPattern));
-  } catch(e) {
+  } catch (e) {
     failedClipboard(expectedPattern);
   }
 
   view._contextmenu._menupopup.hidePopup();
 }
 
 function* disableProperty(view, index) {
   let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[index].editor;
-
-  info("Disabling a property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  let textProp = ruleEditor.rule.textProps[index];
+  yield togglePropStatus(view, textProp);
 }
 
 function checkClipboardData(expectedPattern) {
   let actual = SpecialPowers.getClipboardData("text/unicode");
   let expectedRegExp = new RegExp(expectedPattern, "g");
   return expectedRegExp.test(actual);
 }
 
--- a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-commit-on-ENTER.js
@@ -34,30 +34,33 @@ function* testPressingEnterCommitsChange
   swatch.click();
   yield onShown;
 
   let widget = yield bezierTooltip.widget;
   info("Simulating a change of curve in the widget");
   widget.coordinates = [0.1, 2, 0.9, -1];
   let expected = "cubic-bezier(0.1, 2, 0.9, -1)";
 
-  yield waitForSuccess(() => {
-    return content.getComputedStyle(content.document.body)
-      .transitionTimingFunction === expected;
+  yield waitForSuccess(function*() {
+    let func = yield getComputedStyleProperty("body", null,
+                                              "transition-timing-function");
+    return func === expected;
   }, "Waiting for the change to be previewed on the element");
 
   ok(getRuleViewProperty(ruleView, "body", "transition").valueSpan.textContent
     .indexOf("cubic-bezier(") !== -1,
     "The text of the timing-function was updated");
 
   info("Sending RETURN key within the tooltip document");
   // Pressing RETURN ends up doing 2 rule-view updates, one for the preview and
   // one for the commit when the tooltip closes.
   let onRuleViewChanged = waitForNEvents(ruleView, "ruleview-changed", 2);
   EventUtils.sendKey("RETURN", widget.parent.ownerDocument.defaultView);
   yield onRuleViewChanged;
 
-  is(content.getComputedStyle(content.document.body).transitionTimingFunction,
-    expected, "The element's timing-function was kept after RETURN");
-  ok(getRuleViewProperty(ruleView, "body", "transition").valueSpan.textContent
-    .indexOf("cubic-bezier(") !== -1,
-    "The text of the timing-function was kept after RETURN");
+  let style = yield getComputedStyleProperty("body", null,
+                                             "transition-timing-function");
+  is(style, expected, "The element's timing-function was kept after RETURN");
+
+  let ruleViewStyle = getRuleViewProperty(ruleView, "body", "transition")
+                      .valueSpan.textContent.indexOf("cubic-bezier(") !== -1;
+  ok(ruleViewStyle, "The text of the timing-function was kept after RETURN");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js
+++ b/devtools/client/inspector/rules/test/browser_rules_cubicbezier-revert-on-ESC.js
@@ -18,87 +18,56 @@ const TEST_URI = `
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {view} = yield openRuleView();
   yield testPressingEscapeRevertsChanges(view);
   yield testPressingEscapeRevertsChangesAndDisables(view);
 });
 
 function* testPressingEscapeRevertsChanges(view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
-  let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
-  let bezierTooltip = view.tooltips.cubicBezier;
+  let {propEditor} = yield openCubicBezierAndChangeCoords(view, 1, 0,
+    [0.1, 2, 0.9, -1], {
+      selector: "body",
+      name: "animation-timing-function",
+      value: "cubic-bezier(0.1, 2, 0.9, -1)"
+    });
 
-  let onShown = bezierTooltip.tooltip.once("shown");
-  swatch.click();
-  yield onShown;
-
-  let widget = yield bezierTooltip.widget;
-  info("Simulating a change of curve in the widget");
-  widget.coordinates = [0.1, 2, 0.9, -1];
-  yield ruleEditor.rule._applyingModifications;
-
-  yield waitForComputedStyleProperty("body", null, "animation-timing-function",
-    "cubic-bezier(0.1, 2, 0.9, -1)");
   is(propEditor.valueSpan.textContent, "cubic-bezier(.1,2,.9,-1)",
     "Got expected property value.");
 
-  info("Pressing ESCAPE to close the tooltip");
-  let onHidden = bezierTooltip.tooltip.once("hidden");
-  EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
-  yield onHidden;
-  yield ruleEditor.rule._applyingModifications;
+  yield escapeTooltip(view);
 
   yield waitForComputedStyleProperty("body", null, "animation-timing-function",
     "linear");
   is(propEditor.valueSpan.textContent, "linear",
     "Got expected property value.");
 }
 
 function* testPressingEscapeRevertsChangesAndDisables(view) {
   let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
-  let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
-  let bezierTooltip = view.tooltips.cubicBezier;
+  let textProp = ruleEditor.rule.textProps[0];
+  let propEditor = textProp.editor;
 
   info("Disabling animation-timing-function property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  yield togglePropStatus(view, textProp);
 
   ok(propEditor.element.classList.contains("ruleview-overridden"),
     "property is overridden.");
   is(propEditor.enable.style.visibility, "visible",
     "property enable checkbox is visible.");
   ok(!propEditor.enable.getAttribute("checked"),
     "property enable checkbox is not checked.");
   ok(!propEditor.prop.enabled,
     "animation-timing-function property is disabled.");
   let newValue = yield getRulePropertyValue("animation-timing-function");
   is(newValue, "", "animation-timing-function should have been unset.");
 
-  let onShown = bezierTooltip.tooltip.once("shown");
-  swatch.click();
-  yield onShown;
-
-  ok(!propEditor.element.classList.contains("ruleview-overridden"),
-    "property overridden is not displayed.");
-  is(propEditor.enable.style.visibility, "hidden",
-    "property enable checkbox is hidden.");
+  yield openCubicBezierAndChangeCoords(view, 1, 0, [0.1, 2, 0.9, -1]);
 
-  let widget = yield bezierTooltip.widget;
-  info("Simulating a change of curve in the widget");
-  widget.coordinates = [0.1, 2, 0.9, -1];
-  yield ruleEditor.rule._applyingModifications;
-
-  info("Pressing ESCAPE to close the tooltip");
-  let onHidden = bezierTooltip.tooltip.once("hidden");
-  EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
-  yield onHidden;
-  yield ruleEditor.rule._applyingModifications;
+  yield escapeTooltip(view);
 
   ok(propEditor.element.classList.contains("ruleview-overridden"),
     "property is overridden.");
   is(propEditor.enable.style.visibility, "visible",
     "property enable checkbox is visible.");
   ok(!propEditor.enable.getAttribute("checked"),
     "property enable checkbox is not checked.");
   ok(!propEditor.prop.enabled,
@@ -112,8 +81,20 @@ function* testPressingEscapeRevertsChang
 function* getRulePropertyValue(name) {
   let propValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: name
   });
   return propValue;
 }
+
+function* escapeTooltip(view) {
+  info("Pressing ESCAPE to close the tooltip");
+
+  let bezierTooltip = view.tooltips.cubicBezier;
+  let widget = yield bezierTooltip.widget;
+  let onHidden = bezierTooltip.tooltip.once("hidden");
+  let onModifications = view.once("ruleview-changed");
+  EventUtils.sendKey("ESCAPE", widget.parent.ownerDocument.defaultView);
+  yield onHidden;
+  yield onModifications;
+}
--- a/devtools/client/inspector/rules/test/browser_rules_custom.js
+++ b/devtools/client/inspector/rules/test/browser_rules_custom.js
@@ -15,66 +15,58 @@ add_task(function*() {
   yield simpleCustomOverride(inspector, view);
   yield importantCustomOverride(inspector, view);
   yield disableCustomOverride(inspector, view);
 });
 
 function* simpleCustomOverride(inspector, view) {
   yield selectNode("#testidSimple", inspector);
 
-  let elementStyle = view._elementStyle;
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
+  let idRuleProp = idRule.textProps[0];
 
-  let idRule = elementStyle.rules[1];
-  let idProp = idRule.textProps[0];
-  is(idProp.name, "--background-color",
+  is(idRuleProp.name, "--background-color",
      "First ID prop should be --background-color");
-  ok(!idProp.overridden, "ID prop should not be overridden.");
+  ok(!idRuleProp.overridden, "ID prop should not be overridden.");
 
-  let classRule = elementStyle.rules[2];
-  let classProp = classRule.textProps[0];
-  is(classProp.name, "--background-color",
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
+  let classRuleProp = classRule.textProps[0];
+
+  is(classRuleProp.name, "--background-color",
      "First class prop should be --background-color");
-  ok(classProp.overridden, "Class property should be overridden.");
+  ok(classRuleProp.overridden, "Class property should be overridden.");
 
   // Override --background-color by changing the element style.
-  let elementRule = elementStyle.rules[0];
-  elementRule.createProperty("--background-color", "purple", "");
-  yield elementRule._applyingModifications;
+  let elementProp = yield addProperty(view, 0, "--background-color", "purple");
 
-  let elementProp = elementRule.textProps[0];
-  is(classProp.name, "--background-color",
+  is(classRuleProp.name, "--background-color",
      "First element prop should now be --background-color");
   ok(!elementProp.overridden,
      "Element style property should not be overridden");
-  ok(idProp.overridden, "ID property should be overridden");
-  ok(classProp.overridden, "Class property should be overridden");
+  ok(idRuleProp.overridden, "ID property should be overridden");
+  ok(classRuleProp.overridden, "Class property should be overridden");
 }
 
 function* importantCustomOverride(inspector, view) {
   yield selectNode("#testidImportant", inspector);
 
-  let elementStyle = view._elementStyle;
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
+  let idRuleProp = idRule.textProps[0];
+  ok(idRuleProp.overridden, "Not-important rule should be overridden.");
 
-  let idRule = elementStyle.rules[1];
-  let idProp = idRule.textProps[0];
-  ok(idProp.overridden, "Not-important rule should be overridden.");
-
-  let classRule = elementStyle.rules[2];
-  let classProp = classRule.textProps[0];
-  ok(!classProp.overridden, "Important rule should not be overridden.");
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
+  let classRuleProp = classRule.textProps[0];
+  ok(!classRuleProp.overridden, "Important rule should not be overridden.");
 }
 
 function* disableCustomOverride(inspector, view) {
   yield selectNode("#testidDisable", inspector);
 
-  let elementStyle = view._elementStyle;
-
-  let idRule = elementStyle.rules[1];
-  let idProp = idRule.textProps[0];
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
+  let idRuleProp = idRule.textProps[0];
 
-  idProp.setEnabled(false);
-  yield idRule._applyingModifications;
+  yield togglePropStatus(view, idRuleProp);
 
-  let classRule = elementStyle.rules[2];
-  let classProp = classRule.textProps[0];
-  ok(!classProp.overridden,
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
+  let classRuleProp = classRule.textProps[0];
+  ok(!classRuleProp.overridden,
      "Class prop should not be overridden after id prop was disabled.");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-cancel.js
@@ -15,43 +15,40 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditPropertyAndCancel(inspector, view);
-});
 
-function* testEditPropertyAndCancel(inspector, view) {
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let propEditor = ruleEditor.rule.textProps[0].editor;
 
   yield focusEditableField(view, propEditor.nameSpan);
   yield sendCharsAndWaitForFocus(view, ruleEditor.element,
     ["VK_DELETE", "VK_ESCAPE"]);
-  yield ruleEditor.rule._applyingModifications;
 
   is(propEditor.nameSpan.textContent, "background-color",
     "'background-color' property name is correctly set.");
   is((yield getComputedStyleProperty("#testid", null, "background-color")),
     "rgb(0, 0, 255)", "#00F background color is set.");
 
   yield focusEditableField(view, propEditor.valueSpan);
+  let onValueDeleted = view.once("ruleview-changed");
   yield sendCharsAndWaitForFocus(view, ruleEditor.element,
     ["VK_DELETE", "VK_ESCAPE"]);
-  yield ruleEditor.rule._applyingModifications;
+  yield onValueDeleted;
 
   is(propEditor.valueSpan.textContent, "#00F",
     "'#00F' property value is correctly set.");
   is((yield getComputedStyleProperty("#testid", null, "background-color")),
     "rgb(0, 0, 255)", "#00F background color is set.");
-}
+});
 
 function* sendCharsAndWaitForFocus(view, element, chars) {
   let onFocus = once(element, "focus", true);
   for (let ch of chars) {
     EventUtils.sendChar(ch, view.styleWindow);
   }
   yield onFocus;
 }
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-increments.js
@@ -2,63 +2,62 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that increasing/decreasing values in rule view using
 // arrow keys works correctly.
 
+const TEST_URI = `
+  <style>
+    #test {
+      margin-top: 0px;
+      padding-top: 0px;
+      color: #000000;
+      background-color: #000000;
+      background: none;
+      transition: initial;
+      z-index: 0;
+    }
+  </style>
+  <div id="test"></div>
+`;
+
 add_task(function*() {
-  yield addTab("data:text/html;charset=utf-8,sample document for bug 722691");
-  createDocument();
+  yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
 
   let {inspector, view} = yield openRuleView();
   yield selectNode("#test", inspector);
 
   yield testMarginIncrements(view);
   yield testVariousUnitIncrements(view);
   yield testHexIncrements(view);
   yield testRgbIncrements(view);
   yield testShorthandIncrements(view);
   yield testOddCases(view);
   yield testZeroValueIncrements(view);
 });
 
-function createDocument() {
-  content.document.body.innerHTML = "" +
-    "<style>" +
-    "  #test {" +
-    "    margin-top:0px;" +
-    "    padding-top: 0px;" +
-    "    color:#000000;" +
-    "    background-color: #000000;" +
-    "    background: none;" +
-    "    transition: initial;" +
-    "    z-index: 0;" +
-    "  }" +
-    "</style>" +
-    "<div id=\"test\"></div>";
-}
-
 function* testMarginIncrements(view) {
   info("Testing keyboard increments on the margin property");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
 
   yield runIncrementTest(marginPropEditor, view, {
     1: {alt: true, start: "0px", end: "0.1px", selectAll: true},
     2: {start: "0px", end: "1px", selectAll: true},
     3: {shift: true, start: "0px", end: "10px", selectAll: true},
     4: {down: true, alt: true, start: "0.1px", end: "0px", selectAll: true},
     5: {down: true, start: "0px", end: "-1px", selectAll: true},
     6: {down: true, shift: true, start: "0px", end: "-10px", selectAll: true},
     7: {pageUp: true, shift: true, start: "0px", end: "100px", selectAll: true},
-    8: {pageDown: true, shift: true, start: "0px", end: "-100px", selectAll: true},
+    8: {pageDown: true, shift: true, start: "0px", end: "-100px",
+        selectAll: true},
     9: {start: "0", end: "1px", selectAll: true},
     10: {down: true, start: "0", end: "-1px", selectAll: true},
   });
 }
 
 function* testVariousUnitIncrements(view) {
   info("Testing keyboard increments on values with various units");
 
@@ -87,52 +86,63 @@ function* testHexIncrements(view) {
   let hexColorPropEditor = idRuleEditor.rule.textProps[2].editor;
 
   yield runIncrementTest(hexColorPropEditor, view, {
     1: {start: "#CCCCCC", end: "#CDCDCD", selectAll: true},
     2: {shift: true, start: "#CCCCCC", end: "#DCDCDC", selectAll: true},
     3: {start: "#CCCCCC", end: "#CDCCCC", selection: [1, 3]},
     4: {shift: true, start: "#CCCCCC", end: "#DCCCCC", selection: [1, 3]},
     5: {start: "#FFFFFF", end: "#FFFFFF", selectAll: true},
-    6: {down: true, shift: true, start: "#000000", end: "#000000", selectAll: true}
+    6: {down: true, shift: true, start: "#000000", end: "#000000",
+        selectAll: true}
   });
 }
 
 function* testRgbIncrements(view) {
   info("Testing keyboard increments with rgb colors");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let rgbColorPropEditor = idRuleEditor.rule.textProps[3].editor;
 
   yield runIncrementTest(rgbColorPropEditor, view, {
     1: {start: "rgb(0,0,0)", end: "rgb(0,1,0)", selection: [6, 7]},
-    2: {shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)", selection: [6, 7]},
+    2: {shift: true, start: "rgb(0,0,0)", end: "rgb(0,10,0)",
+        selection: [6, 7]},
     3: {start: "rgb(0,255,0)", end: "rgb(0,255,0)", selection: [6, 9]},
-    4: {shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)", selection: [6, 9]},
+    4: {shift: true, start: "rgb(0,250,0)", end: "rgb(0,255,0)",
+        selection: [6, 9]},
     5: {down: true, start: "rgb(0,0,0)", end: "rgb(0,0,0)", selection: [6, 7]},
-    6: {down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)", selection: [6, 7]}
+    6: {down: true, shift: true, start: "rgb(0,5,0)", end: "rgb(0,0,0)",
+        selection: [6, 7]}
   });
 }
 
 function* testShorthandIncrements(view) {
   info("Testing keyboard increments within shorthand values");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let paddingPropEditor = idRuleEditor.rule.textProps[1].editor;
 
   yield runIncrementTest(paddingPropEditor, view, {
     1: {start: "0px 0px 0px 0px", end: "0px 1px 0px 0px", selection: [4, 7]},
-    2: {shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px", selection: [4, 7]},
+    2: {shift: true, start: "0px 0px 0px 0px", end: "0px 10px 0px 0px",
+        selection: [4, 7]},
     3: {start: "0px 0px 0px 0px", end: "1px 0px 0px 0px", selectAll: true},
-    4: {shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px", selectAll: true},
-    5: {down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px", selection: [8, 11]},
-    6: {down: true, shift: true, start: "0px 0px 0px 0px", end: "-10px 0px 0px 0px", selectAll: true},
-    7: {up: true, start: "0.1em .1em 0em 0em", end: "0.1em 1.1em 0em 0em", selection: [6, 9]},
-    8: {up: true, alt: true, start: "0.1em .9em 0em 0em", end: "0.1em 1em 0em 0em", selection: [6, 9]},
-    9: {up: true, shift: true, start: "0.2em .2em 0em 0em", end: "0.2em 10.2em 0em 0em", selection: [6, 9]}
+    4: {shift: true, start: "0px 0px 0px 0px", end: "10px 0px 0px 0px",
+        selectAll: true},
+    5: {down: true, start: "0px 0px 0px 0px", end: "0px 0px -1px 0px",
+        selection: [8, 11]},
+    6: {down: true, shift: true, start: "0px 0px 0px 0px",
+        end: "-10px 0px 0px 0px", selectAll: true},
+    7: {up: true, start: "0.1em .1em 0em 0em", end: "0.1em 1.1em 0em 0em",
+        selection: [6, 9]},
+    8: {up: true, alt: true, start: "0.1em .9em 0em 0em",
+        end: "0.1em 1em 0em 0em", selection: [6, 9]},
+    9: {up: true, shift: true, start: "0.2em .2em 0em 0em",
+        end: "0.2em 10.2em 0em 0em", selection: [6, 9]}
   });
 }
 
 function* testOddCases(view) {
   info("Testing some more odd cases");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let marginPropEditor = idRuleEditor.rule.textProps[0].editor;
@@ -140,23 +150,29 @@ function* testOddCases(view) {
   yield runIncrementTest(marginPropEditor, view, {
     1: {start: "98.7%", end: "99.7%", selection: [3, 3]},
     2: {alt: true, start: "98.7%", end: "98.8%", selection: [3, 3]},
     3: {start: "0", end: "1px"},
     4: {down: true, start: "0", end: "-1px"},
     5: {start: "'a=-1'", end: "'a=0'", selection: [4, 4]},
     6: {start: "0 -1px", end: "0 0px", selection: [2, 2]},
     7: {start: "url(-1)", end: "url(-1)", selection: [4, 4]},
-    8: {start: "url('test1.1.png')", end: "url('test1.2.png')", selection: [11, 11]},
+    8: {start: "url('test1.1.png')", end: "url('test1.2.png')",
+        selection: [11, 11]},
     9: {start: "url('test1.png')", end: "url('test2.png')", selection: [9, 9]},
-    10: {shift: true, start: "url('test1.1.png')", end: "url('test11.1.png')", selection: [9, 9]},
-    11: {down: true, start: "url('test-1.png')", end: "url('test-2.png')", selection: [9, 11]},
-    12: {start: "url('test1.1.png')", end: "url('test1.2.png')", selection: [11, 12]},
-    13: {down: true, alt: true, start: "url('test-0.png')", end: "url('test--0.1.png')", selection: [10, 11]},
-    14: {alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')", selection: [10, 14]},
+    10: {shift: true, start: "url('test1.1.png')", end: "url('test11.1.png')",
+         selection: [9, 9]},
+    11: {down: true, start: "url('test-1.png')", end: "url('test-2.png')",
+         selection: [9, 11]},
+    12: {start: "url('test1.1.png')", end: "url('test1.2.png')",
+         selection: [11, 12]},
+    13: {down: true, alt: true, start: "url('test-0.png')",
+         end: "url('test--0.1.png')", selection: [10, 11]},
+    14: {alt: true, start: "url('test--0.1.png')", end: "url('test-0.png')",
+         selection: [10, 14]}
   });
 }
 
 function* testZeroValueIncrements(view) {
   info("Testing a valid unit is added when incrementing from 0");
 
   let idRuleEditor = getRuleViewRuleEditor(view, 1);
   let backgroundPropEditor = idRuleEditor.rule.textProps[4].editor;
@@ -213,19 +229,25 @@ function* testIncrement(editor, options,
   } else if (options.selection) {
     input.setSelectionRange(options.selection[0], options.selection[1]);
   }
 
   is(input.value, options.start, "Value initialized at " + options.start);
 
   let onRuleViewChanged = view.once("ruleview-changed");
   let onKeyUp = once(input, "keyup");
+
   let key;
   key = options.down ? "VK_DOWN" : "VK_UP";
-  key = options.pageDown ? "VK_PAGE_DOWN" : options.pageUp ? "VK_PAGE_UP" : key;
+  if (options.pageDown) {
+    key = "VK_PAGE_DOWN";
+  } else if (options.pageUp) {
+    key = "VK_PAGE_UP";
+  }
+
   EventUtils.synthesizeKey(key, {altKey: options.alt, shiftKey: options.shift},
     view.styleWindow);
   yield onKeyUp;
   // Only expect a change if the value actually changed!
   if (options.start !== options.end) {
     yield onRuleViewChanged;
   }
 
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-order.js
@@ -1,82 +1,89 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Checking properties orders and overrides in the rule-view.
 
-const TEST_URI = "<div id='testid'>Styled Node</div>";
+const TEST_URI = "<style>#testid {}</style><div id='testid'>Styled Node</div>";
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
-  let element = getNode("#testid");
   let elementStyle = view._elementStyle;
-  let elementRule = elementStyle.rules[0];
+  let elementRule = elementStyle.rules[1];
 
   info("Checking rules insertion order and checking the applied style");
-  let firstProp = elementRule.createProperty("background-color", "green", "");
-  let secondProp = elementRule.createProperty("background-color", "blue", "");
-  is(elementRule.textProps[0], firstProp, "Rules should be in addition order.");
+  let firstProp = yield addProperty(view, 1, "background-color", "green");
+  let secondProp = yield addProperty(view, 1, "background-color", "blue");
+
+  is(elementRule.textProps[0], firstProp,
+     "Rules should be in addition order.");
   is(elementRule.textProps[1], secondProp,
-    "Rules should be in addition order.");
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "blue",
-    "Second property should have been used.");
+     "Rules should be in addition order.");
+
+  // rgb(0, 0, 255) = blue
+  is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+     "Second property should have been used.");
 
   info("Removing the second property and checking the applied style again");
-  secondProp.remove();
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "green",
-    "After deleting second property, first should be used.");
+  yield removeProperty(view, secondProp);
+  // rgb(0, 128, 0) = green
+  is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)",
+     "After deleting second property, first should be used.");
 
   info("Creating a new second property and checking that the insertion order " +
-    "is still the same");
-  secondProp = elementRule.createProperty("background-color", "blue", "");
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "blue",
-    "New property should be used.");
+     "is still the same");
+
+  secondProp = yield addProperty(view, 1, "background-color", "blue");
+
+  is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+     "New property should be used.");
   is(elementRule.textProps[0], firstProp,
-    "Rules shouldn't have switched places.");
+     "Rules shouldn't have switched places.");
   is(elementRule.textProps[1], secondProp,
-    "Rules shouldn't have switched places.");
+     "Rules shouldn't have switched places.");
 
   info("Disabling the second property and checking the applied style");
-  secondProp.setEnabled(false);
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "green",
-    "After disabling second property, first value should be used");
+  yield togglePropStatus(view, secondProp);
+
+  is((yield getValue("#testid", "background-color")), "rgb(0, 128, 0)",
+     "After disabling second property, first value should be used");
 
   info("Disabling the first property too and checking the applied style");
-  firstProp.setEnabled(false);
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "",
-    "After disabling both properties, value should be empty.");
+  yield togglePropStatus(view, firstProp);
+
+  is((yield getValue("#testid", "background-color")), "transparent",
+     "After disabling both properties, value should be empty.");
 
   info("Re-enabling the second propertyt and checking the applied style");
-  secondProp.setEnabled(true);
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "blue",
-    "Value should be set correctly after re-enabling");
+  yield togglePropStatus(view, secondProp);
+
+  is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+     "Value should be set correctly after re-enabling");
 
   info("Re-enabling the first property and checking the insertion order " +
-    "is still respected");
-  firstProp.setEnabled(true);
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "blue",
-    "Re-enabling an earlier property shouldn't make it override " +
-    "a later property.");
+       "is still respected");
+  yield togglePropStatus(view, firstProp);
+
+  is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+     "Re-enabling an earlier property shouldn't make it override " +
+     "a later property.");
   is(elementRule.textProps[0], firstProp,
-    "Rules shouldn't have switched places.");
+     "Rules shouldn't have switched places.");
   is(elementRule.textProps[1], secondProp,
-    "Rules shouldn't have switched places.");
-
+     "Rules shouldn't have switched places.");
   info("Modifying the first property and checking the applied style");
-  firstProp.setValue("purple", "");
-  yield elementRule._applyingModifications;
-  is(element.style.getPropertyValue("background-color"), "blue",
-    "Modifying an earlier property shouldn't override a later property.");
+  yield setProperty(view, firstProp, "purple");
+
+  is((yield getValue("#testid", "background-color")), "rgb(0, 0, 255)",
+     "Modifying an earlier property shouldn't override a later property.");
 });
+
+function* getValue(selector, propName) {
+  let value = yield getComputedStyleProperty(selector, null, propName);
+  return value;
+}
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_01.js
@@ -17,60 +17,51 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditPropertyAndRemove(inspector, view);
-});
 
-function* testEditPropertyAndRemove(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  info("Getting the first property in the #testid rule");
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
-  yield focusEditableField(view, propEditor.nameSpan);
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element,
-    ["VK_DELETE", "VK_RETURN"]);
-  yield ruleEditor.rule._applyingModifications;
+  info("Deleting the name of that property to remove the property");
+  yield removeProperty(view, prop, false);
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should have been unset.");
 
-  propEditor = ruleEditor.rule.textProps[0].editor;
+  info("Getting the new first property in the rule");
+  prop = rule.textProps[0];
 
   let editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(propEditor.nameSpan), editor,
+  is(inplaceEditor(prop.editor.nameSpan), editor,
     "Focus should have moved to the next property name");
 
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element,
-    ["VK_DELETE", "VK_RETURN"]);
-  yield ruleEditor.rule._applyingModifications;
+  info("Deleting the name of that property to remove the property");
+  view.styleDocument.activeElement.blur();
+  yield removeProperty(view, prop, false);
 
   newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "color"
   });
   is(newValue, "", "color should have been unset.");
 
   editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(ruleEditor.newPropSpan), editor,
+  is(inplaceEditor(rule.editor.newPropSpan), editor,
     "Focus should have moved to the new property span");
-  is(ruleEditor.rule.textProps.length, 0,
+  is(rule.textProps.length, 0,
     "All properties should have been removed.");
-  is(ruleEditor.propertyList.children.length, 1,
+  is(rule.editor.propertyList.children.length, 1,
     "Should have the new property span.");
-}
 
-function* sendKeysAndWaitForFocus(view, element, keys) {
-  let onFocus = once(element, "focus", true);
-  for (let key of keys) {
-    EventUtils.synthesizeKey(key, {}, view.styleWindow);
-  }
-  yield onFocus;
-}
+  view.styleDocument.activeElement.blur();
+});
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_02.js
@@ -17,62 +17,51 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditPropertyAndRemove(inspector, view);
-});
 
-function* testEditPropertyAndRemove(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  info("Getting the first property in the rule");
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
-  yield focusEditableField(view, propEditor.valueSpan);
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element,
-    ["VK_DELETE", "VK_RETURN"]);
-  yield ruleEditor.rule._applyingModifications;
+  info("Clearing the property value");
+  yield setProperty(view, prop, null, false);
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should have been unset.");
 
-  propEditor = ruleEditor.rule.textProps[0].editor;
+  info("Getting the new first property in the rule");
+  prop = rule.textProps[0];
 
   let editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(propEditor.nameSpan), editor,
+  is(inplaceEditor(prop.editor.nameSpan), editor,
     "Focus should have moved to the next property name");
+  view.styleDocument.activeElement.blur();
 
-  info("Focus the property value and remove the property");
-  let onChanged = view.once("ruleview-changed");
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element,
-    ["VK_TAB", "VK_DELETE", "VK_RETURN"]);
-  yield onChanged;
+  info("Clearing the property value");
+  yield setProperty(view, prop, null, false);
 
   newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "color"
   });
   is(newValue, "", "color should have been unset.");
 
   editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(ruleEditor.newPropSpan), editor,
+  is(inplaceEditor(rule.editor.newPropSpan), editor,
     "Focus should have moved to the new property span");
-  is(ruleEditor.rule.textProps.length, 0,
+  is(rule.textProps.length, 0,
     "All properties should have been removed.");
-  is(ruleEditor.propertyList.children.length, 1,
+  is(rule.editor.propertyList.children.length, 1,
     "Should have the new property span.");
-}
 
-function* sendKeysAndWaitForFocus(view, element, keys) {
-  let onFocus = once(element, "focus", true);
-  for (let key of keys) {
-    EventUtils.synthesizeKey(key, {}, view.styleWindow);
-  }
-  yield onFocus;
-}
+  view.styleDocument.activeElement.blur();
+});
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property-remove_03.js
@@ -17,74 +17,67 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditPropertyAndRemove(inspector, view);
-});
 
-function* testEditPropertyAndRemove(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[1].editor;
+  info("Getting the second property in the rule");
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[1];
 
-  yield focusEditableField(view, propEditor.valueSpan);
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element, [
-    { key: "VK_DELETE", modifiers: {} },
-    { key: "VK_TAB", modifiers: { shiftKey: true } }
-  ]);
-  yield ruleEditor.rule._applyingModifications;
+  info("Clearing the property value and pressing shift-tab");
+  let editor = yield focusEditableField(view, prop.editor.valueSpan);
+  let onValueDone = view.once("ruleview-changed");
+  editor.input.value = "";
+  EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+  yield onValueDone;
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "color"
   });
   is(newValue, "", "color should have been unset.");
-  is(propEditor.valueSpan.textContent, "",
+  is(prop.editor.valueSpan.textContent, "",
     "'' property value is correctly set.");
 
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element, [
-    { key: "VK_TAB", modifiers: { shiftKey: true } }
-  ]);
-  yield ruleEditor.rule._applyingModifications;
+  info("Pressing shift-tab again to focus the previous property value");
+  let onValueFocused = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+  yield onValueFocused;
 
-  propEditor = ruleEditor.rule.textProps[0].editor;
+  info("Getting the first property in the rule");
+  prop = rule.textProps[0];
 
-  let editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(propEditor.valueSpan), editor,
+  editor = inplaceEditor(view.styleDocument.activeElement);
+  is(inplaceEditor(prop.editor.valueSpan), editor,
     "Focus should have moved to the previous property value");
 
-  info("Focus the property name and remove the property");
-  yield sendKeysAndWaitForFocus(view, ruleEditor.element, [
-    { key: "VK_TAB", modifiers: { shiftKey: true } },
-    { key: "VK_DELETE", modifiers: {} },
-    { key: "VK_TAB", modifiers: { shiftKey: true } }
-  ]);
+  info("Pressing shift-tab again to focus the property name");
+  let onNameFocused = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+  yield onNameFocused;
 
-  yield ruleEditor.rule._applyingModifications;
+  info("Removing the name and pressing shift-tab to focus the selector");
+  let onNameDeleted = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+  EventUtils.synthesizeKey("VK_TAB", {shiftKey: true}, view.styleWindow);
+  yield onNameDeleted;
 
   newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should have been unset.");
 
   editor = inplaceEditor(view.styleDocument.activeElement);
-  is(inplaceEditor(ruleEditor.selectorText), editor,
+  is(inplaceEditor(rule.editor.selectorText), editor,
     "Focus should have moved to the selector text.");
-  is(ruleEditor.rule.textProps.length, 0,
+  is(rule.textProps.length, 0,
     "All properties should have been removed.");
-  ok(!ruleEditor.propertyList.hasChildNodes(),
+  ok(!rule.editor.propertyList.hasChildNodes(),
     "Should not have any properties.");
-}
-
-function* sendKeysAndWaitForFocus(view, element, keys) {
-  let onFocus = once(element, "focus", true);
-  for (let {key, modifiers} of keys) {
-    EventUtils.synthesizeKey(key, modifiers, view.styleWindow);
-  }
-  yield onFocus;
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_01.js
@@ -31,69 +31,63 @@ var TEST_DATA = [
   { name: "border", value: "solid 1px foo", isValid: false },
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let rule = getRuleViewRuleEditor(view, 1).rule;
   for (let {name, value, isValid} of TEST_DATA) {
-    yield testEditProperty(ruleEditor, name, value, isValid);
+    yield testEditProperty(view, rule, name, value, isValid);
   }
 });
 
-function* testEditProperty(ruleEditor, name, value, isValid) {
+function* testEditProperty(view, rule, name, value, isValid) {
   info("Test editing existing property name/value fields");
 
-  let doc = ruleEditor.doc;
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let doc = rule.editor.doc;
+  let prop = rule.textProps[0];
 
   info("Focusing an existing property name in the rule-view");
-  let editor = yield focusEditableField(ruleEditor.ruleView,
-    propEditor.nameSpan, 32, 1);
+  let editor = yield focusEditableField(view, prop.editor.nameSpan, 32, 1);
 
-  is(inplaceEditor(propEditor.nameSpan), editor,
+  is(inplaceEditor(prop.editor.nameSpan), editor,
     "The property name editor got focused");
   let input = editor.input;
 
   info("Entering a new property name, including : to commit and " +
     "focus the value");
-  let onValueFocus = once(ruleEditor.element, "focus", true);
-  let onModifications = ruleEditor.ruleView.once("ruleview-changed");
+  let onValueFocus = once(rule.editor.element, "focus", true);
+  let onNameDone = view.once("ruleview-changed");
   EventUtils.sendString(name + ":", doc.defaultView);
   yield onValueFocus;
-  yield onModifications;
+  yield onNameDone;
 
   // Getting the value editor after focus
   editor = inplaceEditor(doc.activeElement);
   input = editor.input;
-  is(inplaceEditor(propEditor.valueSpan), editor, "Focus moved to the value.");
+  is(inplaceEditor(prop.editor.valueSpan), editor, "Focus moved to the value.");
 
   info("Entering a new value, including ; to commit and blur the value");
+  let onValueDone = view.once("ruleview-changed");
   let onBlur = once(input, "blur");
   EventUtils.sendString(value + ";", doc.defaultView);
   yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  yield onValueDone;
 
-  is(propEditor.isValid(), isValid,
+  is(prop.editor.isValid(), isValid,
     value + " is " + isValid ? "valid" : "invalid");
 
   info("Checking that the style property was changed on the content page");
   let propValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name
   });
 
   if (isValid) {
     is(propValue, value, name + " should have been set.");
   } else {
     isnot(propValue, value, name + " shouldn't have been set.");
   }
-
-  info("Wait for remaining modifications to be applied");
-  yield ruleEditor.rule._applyingModifications;
-
-  is(ruleEditor.rule._applyingModifications, null,
-    "Reference to rule modification promise was removed after completion");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_02.js
@@ -25,106 +25,94 @@ add_task(function*() {
   yield selectNode("#testid", inspector);
 
   yield testEditProperty(inspector, view);
   yield testDisableProperty(inspector, view);
   yield testPropertyStillMarkedDirty(inspector, view);
 });
 
 function* testEditProperty(inspector, ruleView) {
-  let idRuleEditor = getRuleViewRuleEditor(ruleView, 1);
-  let propEditor = idRuleEditor.rule.textProps[0].editor;
+  let idRule = getRuleViewRuleEditor(ruleView, 1).rule;
+  let prop = idRule.textProps[0];
 
-  let editor = yield focusEditableField(ruleView, propEditor.nameSpan);
+  let editor = yield focusEditableField(ruleView, prop.editor.nameSpan);
   let input = editor.input;
-  is(inplaceEditor(propEditor.nameSpan), editor,
+  is(inplaceEditor(prop.editor.nameSpan), editor,
     "Next focused editor should be the name editor.");
 
   ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
     "Editor contents are selected.");
 
   // Try clicking on the editor's input again, shouldn't cause trouble
   // (see bug 761665).
   EventUtils.synthesizeMouse(input, 1, 1, {}, ruleView.styleWindow);
   input.select();
 
   info("Entering property name \"border-color\" followed by a colon to " +
     "focus the value");
-  let onFocus = once(idRuleEditor.element, "focus", true);
+  let onNameDone = ruleView.once("ruleview-changed");
+  let onFocus = once(idRule.editor.element, "focus", true);
   EventUtils.sendString("border-color:", ruleView.styleWindow);
   yield onFocus;
-  yield idRuleEditor.rule._applyingModifications;
+  yield onNameDone;
 
   info("Verifying that the focused field is the valueSpan");
   editor = inplaceEditor(ruleView.styleDocument.activeElement);
   input = editor.input;
-  is(inplaceEditor(propEditor.valueSpan), editor,
+  is(inplaceEditor(prop.editor.valueSpan), editor,
     "Focus should have moved to the value.");
   ok(input.selectionStart === 0 && input.selectionEnd === input.value.length,
     "Editor contents are selected.");
 
   info("Entering a value following by a semi-colon to commit it");
   let onBlur = once(editor.input, "blur");
   // Use sendChar() to pass each character as a string so that we can test
-  // propEditor.warning.hidden after each character.
+  // prop.editor.warning.hidden after each character.
   for (let ch of "red;") {
+    let onPreviewDone = ruleView.once("ruleview-changed");
     EventUtils.sendChar(ch, ruleView.styleWindow);
-    is(propEditor.warning.hidden, true,
+    yield onPreviewDone;
+    is(prop.editor.warning.hidden, true,
       "warning triangle is hidden or shown as appropriate");
   }
   yield onBlur;
-  yield idRuleEditor.rule._applyingModifications;
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "border-color"
   });
   is(newValue, "red", "border-color should have been set.");
 
-  info("Entering property name \"color\" followed by a colon to " +
-    "focus the value");
-  onFocus = once(idRuleEditor.element, "focus", true);
-  EventUtils.sendString("color:", ruleView.styleWindow);
-  yield onFocus;
-
-  info("Verifying that the focused field is the valueSpan");
-  editor = inplaceEditor(ruleView.styleDocument.activeElement);
-
-  info("Entering a value following by a semi-colon to commit it");
-  onBlur = once(editor.input, "blur");
-  EventUtils.sendString("red;", ruleView.styleWindow);
-  yield onBlur;
-  yield idRuleEditor.rule._applyingModifications;
+  ruleView.styleDocument.activeElement.blur();
+  yield addProperty(ruleView, 1, "color", "red", ";");
 
   let props = ruleView.element.querySelectorAll(".ruleview-property");
   for (let i = 0; i < props.length; i++) {
     is(props[i].hasAttribute("dirty"), i <= 1,
       "props[" + i + "] marked dirty as appropriate");
   }
 }
 
 function* testDisableProperty(inspector, ruleView) {
-  let idRuleEditor = getRuleViewRuleEditor(ruleView, 1);
-  let propEditor = idRuleEditor.rule.textProps[0].editor;
+  let idRule = getRuleViewRuleEditor(ruleView, 1).rule;
+  let prop = idRule.textProps[0];
 
   info("Disabling a property");
-  propEditor.enable.click();
-  yield idRuleEditor.rule._applyingModifications;
+  yield togglePropStatus(ruleView, prop);
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "border-color"
   });
   is(newValue, "", "Border-color should have been unset.");
 
   info("Enabling the property again");
-  propEditor.enable.click();
-  yield idRuleEditor.rule._applyingModifications;
+  yield togglePropStatus(ruleView, prop);
 
   newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "border-color"
   });
   is(newValue, "red", "Border-color should have been reset.");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_04.js
@@ -15,71 +15,70 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testDisableProperty(inspector, view);
-});
 
-function* testDisableProperty(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
   info("Disabling a property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  yield togglePropStatus(view, prop);
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should have been unset.");
 
-  yield testEditDisableProperty(view, ruleEditor, propEditor,
-    propEditor.nameSpan, "VK_ESCAPE");
-  yield testEditDisableProperty(view, ruleEditor, propEditor,
-    propEditor.valueSpan, "VK_ESCAPE");
-  yield testEditDisableProperty(view, ruleEditor, propEditor,
-    propEditor.valueSpan, "VK_TAB");
-  yield testEditDisableProperty(view, ruleEditor, propEditor,
-    propEditor.valueSpan, "VK_RETURN");
-}
+  yield testEditDisableProperty(view, rule, prop, "name", "VK_ESCAPE");
+  yield testEditDisableProperty(view, rule, prop, "value", "VK_ESCAPE");
+  yield testEditDisableProperty(view, rule, prop, "value", "VK_TAB");
+  yield testEditDisableProperty(view, rule, prop, "value", "VK_RETURN");
+});
 
-function* testEditDisableProperty(view, ruleEditor, propEditor,
-    editableField, commitKey) {
-  let editor = yield focusEditableField(view, editableField);
+function* testEditDisableProperty(view, rule, prop, fieldType, commitKey) {
+  let field = fieldType === "name" ? prop.editor.nameSpan
+                                   : prop.editor.valueSpan;
 
-  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+  let editor = yield focusEditableField(view, field);
+
+  ok(!prop.editor.element.classList.contains("ruleview-overridden"),
     "property is not overridden.");
-  is(propEditor.enable.style.visibility, "hidden",
+  is(prop.editor.enable.style.visibility, "hidden",
     "property enable checkbox is hidden.");
 
   let newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should remain unset.");
 
+  let onChangeDone;
+  if (fieldType === "value") {
+    onChangeDone = view.once("ruleview-changed");
+  }
+
   let onBlur = once(editor.input, "blur");
   EventUtils.synthesizeKey(commitKey, {}, view.styleWindow);
   yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  yield onChangeDone;
 
-  ok(!propEditor.prop.enabled, "property is disabled.");
-  ok(propEditor.element.classList.contains("ruleview-overridden"),
+  ok(!prop.enabled, "property is disabled.");
+  ok(prop.editor.element.classList.contains("ruleview-overridden"),
     "property is overridden.");
-  is(propEditor.enable.style.visibility, "visible",
+  is(prop.editor.enable.style.visibility, "visible",
     "property enable checkbox is visible.");
-  ok(!propEditor.enable.getAttribute("checked"),
+  ok(!prop.editor.enable.getAttribute("checked"),
     "property enable checkbox is not checked.");
 
   newValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: "background-color"
   });
   is(newValue, "", "background-color should remain unset.");
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_05.js
@@ -15,71 +15,62 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditingDisableProperty(inspector, view);
-});
 
-function* testEditingDisableProperty(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
   info("Disabling background-color property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  yield togglePropStatus(view, prop);
 
   let newValue = yield getRulePropertyValue("background-color");
   is(newValue, "", "background-color should have been unset.");
 
-  yield focusEditableField(view, propEditor.nameSpan);
-
   info("Entering a new property name, including : to commit and " +
        "focus the value");
-  let onValueFocus = once(ruleEditor.element, "focus", true);
+
+  yield focusEditableField(view, prop.editor.nameSpan);
+  let onNameDone = view.once("ruleview-changed");
   EventUtils.sendString("border-color:", view.styleWindow);
-  yield onValueFocus;
-  yield ruleEditor.rule._applyingModifications;
+  yield onNameDone;
 
   info("Escape editing the property value");
+  let onValueDone = view.once("ruleview-changed");
   EventUtils.synthesizeKey("VK_ESCAPE", {}, view.styleWindow);
-  yield ruleEditor.rule._applyingModifications;
+  yield onValueDone;
 
   newValue = yield getRulePropertyValue("border-color");
   is(newValue, "blue", "border-color should have been set.");
 
-  ok(propEditor.prop.enabled, "border-color property is enabled.");
-  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+  ok(prop.enabled, "border-color property is enabled.");
+  ok(!prop.editor.element.classList.contains("ruleview-overridden"),
     "border-color is not overridden");
 
   info("Disabling border-color property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  yield togglePropStatus(view, prop);
 
   newValue = yield getRulePropertyValue("border-color");
   is(newValue, "", "border-color should have been unset.");
 
   info("Enter a new property value for the border-color property");
-  let editor = yield focusEditableField(view, propEditor.valueSpan);
-  let onBlur = once(editor.input, "blur");
-  EventUtils.sendString("red;", view.styleWindow);
-  yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  yield setProperty(view, prop, "red");
 
   newValue = yield getRulePropertyValue("border-color");
   is(newValue, "red", "new border-color should have been set.");
 
-  ok(propEditor.prop.enabled, "border-color property is enabled.");
-  ok(!propEditor.element.classList.contains("ruleview-overridden"),
+  ok(prop.enabled, "border-color property is enabled.");
+  ok(!prop.editor.element.classList.contains("ruleview-overridden"),
     "border-color is not overridden");
-}
+});
 
 function* getRulePropertyValue(name) {
   let propValue = yield executeInContent("Test:GetRulePropertyValue", {
     styleSheetIndex: 0,
     ruleIndex: 0,
     name: name
   });
   return propValue;
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_06.js
@@ -17,48 +17,36 @@ const TEST_URI = `
   }
   </style>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("body", inspector);
-  yield testEditPropertyPriorityAndDisable(inspector, view);
-});
 
-function* testEditPropertyPriorityAndDisable(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
   is((yield getComputedStyleProperty("body", null, "background-color")),
     "rgb(0, 128, 0)", "green background color is set.");
 
-  let editor = yield focusEditableField(view, propEditor.valueSpan);
-  let onBlur = once(editor.input, "blur");
-  EventUtils.sendString("red !important;", view.styleWindow);
-  yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  yield setProperty(view, prop, "red !important");
 
-  is(propEditor.valueSpan.textContent, "red !important",
+  is(prop.editor.valueSpan.textContent, "red !important",
     "'red !important' property value is correctly set.");
   is((yield getComputedStyleProperty("body", null, "background-color")),
     "rgb(255, 0, 0)", "red background color is set.");
 
   info("Disabling red background color property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
+  yield togglePropStatus(view, prop);
 
   is((yield getComputedStyleProperty("body", null, "background-color")),
     "rgb(0, 128, 0)", "green background color is set.");
 
-  editor = yield focusEditableField(view, propEditor.valueSpan);
-  onBlur = once(editor.input, "blur");
-  EventUtils.sendString("red;", view.styleWindow);
-  yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  yield setProperty(view, prop, "red");
 
-  is(propEditor.valueSpan.textContent, "red",
+  is(prop.editor.valueSpan.textContent, "red",
     "'red' property value is correctly set.");
-  ok(propEditor.prop.enabled, "red background-color property is enabled.");
+  ok(prop.enabled, "red background-color property is enabled.");
   is((yield getComputedStyleProperty("body", null, "background-color")),
     "rgb(0, 128, 0)", "green background color is set.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-property_07.js
@@ -15,41 +15,36 @@ const TEST_URI = `
   </style>
   <div id='testid'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testEditDisableProperty(inspector, view);
-});
 
-function* testEditDisableProperty(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let rule = getRuleViewRuleEditor(view, 1).rule;
+  let prop = rule.textProps[0];
 
   info("Disabling red background color property");
-  propEditor.enable.click();
-  yield ruleEditor.rule._applyingModifications;
-
-  ok(!propEditor.prop.enabled, "red background-color property is disabled.");
+  yield togglePropStatus(view, prop);
+  ok(!prop.enabled, "red background-color property is disabled.");
 
-  let editor = yield focusEditableField(view, propEditor.valueSpan);
-  let onBlur = once(editor.input, "blur");
-  EventUtils.sendString("red; color: red;", view.styleWindow);
-  yield onBlur;
-  yield ruleEditor.rule._applyingModifications;
+  let editor = yield focusEditableField(view, prop.editor.valueSpan);
+  let onDone = view.once("ruleview-changed");
+  editor.input.value = "red; color: red;";
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onDone;
 
-  is(propEditor.valueSpan.textContent, "red",
+  is(prop.editor.valueSpan.textContent, "red",
     "'red' property value is correctly set.");
-  ok(propEditor.prop.enabled, "red background-color property is enabled.");
+  ok(prop.enabled, "red background-color property is enabled.");
   is((yield getComputedStyleProperty("#testid", null, "background-color")),
     "rgb(255, 0, 0)", "red background color is set.");
 
-  propEditor = ruleEditor.rule.textProps[1].editor;
+  let propEditor = rule.textProps[1].editor;
   is(propEditor.nameSpan.textContent, "color",
     "new 'color' property name is correctly set.");
   is(propEditor.valueSpan.textContent, "red",
     "new 'red' property value is correctly set.");
   is((yield getComputedStyleProperty("#testid", null, "color")),
     "rgb(255, 0, 0)", "red color is set.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js
+++ b/devtools/client/inspector/rules/test/browser_rules_edit-selector_05.js
@@ -66,48 +66,13 @@ function* testEditSelector(view, name) {
 
 function* checkModifiedElement(view, name) {
   is(view._elementStyle.rules.length, 3, "Should have 3 rules.");
   ok(getRuleViewRule(view, name), "Rule with " + name + " selector exists.");
 }
 
 function* testAddProperty(view) {
   info("Test creating a new property");
-
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
-
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(view, ruleEditor.closeBrace);
-
-  is(inplaceEditor(ruleEditor.newPropSpan), editor,
-    "The new property editor got focused");
-  let input = editor.input;
-
-  info("Entering text-align in the property name editor");
-  input.value = "text-align";
-
-  info("Pressing return to commit and focus the new value field");
-  let onValueFocus = once(ruleEditor.element, "focus", true);
-  let onRuleViewChanged = view.once("ruleview-changed");
-  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onValueFocus;
-  yield onRuleViewChanged;
-
-  // Getting the new value editor after focus
-  editor = inplaceEditor(view.styleDocument.activeElement);
-  let textProp = ruleEditor.rule.textProps[0];
-
-  is(ruleEditor.rule.textProps.length, 1, "Created a new text property.");
-  is(ruleEditor.propertyList.children.length, 1, "Created a property editor.");
-  is(editor, inplaceEditor(textProp.editor.valueSpan),
-    "Editing the value span now.");
-
-  info("Entering a value and bluring the field to expect a rule change");
-  editor.input.value = "center";
-  let onBlur = once(editor.input, "blur");
-  onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
-  editor.input.blur();
-  yield onBlur;
-  yield onRuleViewChanged;
+  let textProp = yield addProperty(view, 1, "text-align", "center");
 
   is(textProp.value, "center", "Text prop should have been changed.");
   is(textProp.overridden, false, "Property should not be overridden");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_editable-field-focus_02.js
@@ -24,17 +24,17 @@ const TEST_URI = `
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
   yield testEditableFieldFocus(inspector, view, "VK_TAB", { shiftKey: true });
 });
 
-function* testEditableFieldFocus(inspector, view, commitKey, options={}) {
+function* testEditableFieldFocus(inspector, view, commitKey, options = {}) {
   let ruleEditor = getRuleViewRuleEditor(view, 2);
   let editor = yield focusEditableField(view, ruleEditor.selectorText);
   is(inplaceEditor(ruleEditor.selectorText), editor,
     "Focus should be in the 'div' rule selector");
 
   ruleEditor = getRuleViewRuleEditor(view, 1);
 
   yield focusNextField(view, ruleEditor, commitKey, options);
--- a/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
+++ b/devtools/client/inspector/rules/test/browser_rules_eyedropper.js
@@ -5,18 +5,16 @@
 "use strict";
 
 // So we can test collecting telemetry on the eyedropper
 var oldCanRecord = Services.telemetry.canRecordExtended;
 Services.telemetry.canRecordExtended = true;
 registerCleanupFunction(function() {
   Services.telemetry.canRecordExtended = oldCanRecord;
 });
-const HISTOGRAM_ID = "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT";
-const FLAG_HISTOGRAM_ID = "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG";
 const EXPECTED_TELEMETRY = {
   "DEVTOOLS_PICKER_EYEDROPPER_OPENED_COUNT": 2,
   "DEVTOOLS_PICKER_EYEDROPPER_OPENED_PER_USER_FLAG": 1
 };
 
 const TEST_URI = `
   <style type="text/css">
     body {
@@ -35,18 +33,20 @@ const TEST_URI = `
       width: 20px;
       height: 20px;
       background-color: #f09;
     }
   </style>
   <body><div id="div1"></div><div id="div2"></div></body>
 `;
 
-const ORIGINAL_COLOR = "rgb(255, 0, 153)";  // #f09
-const EXPECTED_COLOR = "rgb(255, 255, 85)"; // #ff5
+// #f09
+const ORIGINAL_COLOR = "rgb(255, 0, 153)";
+// #ff5
+const EXPECTED_COLOR = "rgb(255, 255, 85)";
 
 // Test opening the eyedropper from the color picker. Pressing escape
 // to close it, and clicking the page to select a color.
 
 add_task(function*() {
   // clear telemetry so we can get accurate counts
   clearTelemetry();
 
@@ -98,18 +98,17 @@ function* testSelect(view, swatch, dropp
   inspectPage(dropper);
 
   yield onDestroyed;
   yield onRuleViewChanged;
 
   let color = swatch.style.backgroundColor;
   is(color, EXPECTED_COLOR, "swatch changed colors");
 
-  let element = content.document.querySelector("div");
-  is(content.window.getComputedStyle(element).backgroundColor,
+  is((yield getComputedStyleProperty("div", null, "background-color")),
      EXPECTED_COLOR,
      "div's color set to body color after dropper");
 }
 
 function clearTelemetry() {
   for (let histogramId in EXPECTED_TELEMETRY) {
     let histogram = Services.telemetry.getHistogramById(histogramId);
     histogram.clear();
@@ -143,17 +142,17 @@ function openEyedropper(view, swatch) {
     });
     dropperButton.click();
   });
 
   swatch.click();
   return deferred.promise;
 }
 
-function inspectPage(dropper, click=true) {
+function inspectPage(dropper, click = true) {
   let target = document.documentElement;
   let win = window;
 
   // get location of the content, offset from browser window
   let box = gBrowser.selectedBrowser.getBoundingClientRect();
   let x = box.left + 1;
   let y = box.top + 1;
 
--- a/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js
+++ b/devtools/client/inspector/rules/test/browser_rules_filtereditor-commit-on-ENTER.js
@@ -35,12 +35,11 @@ add_task(function*() {
   ok(true, "Changes previewed on the element");
 
   info("Press RETURN to commit changes");
   // Pressing return in the cssfilter tooltip triggeres 2 ruleview-changed
   onRuleViewChanged = waitForNEvents(view, "ruleview-changed", 2);
   EventUtils.sendKey("RETURN", widget.styleWindow);
   yield onRuleViewChanged;
 
-  const computed = content.getComputedStyle(content.document.body);
-  is(computed.filter, "blur(2px)",
+  is((yield getComputedStyleProperty("body", null, "filter")), "blur(2px)",
      "The elemenet's filter was kept after RETURN");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js
+++ b/devtools/client/inspector/rules/test/browser_rules_guessIndentation.js
@@ -29,45 +29,19 @@ div {
        color: chartreuse;
 }
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {toolbox, inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testIndentation(toolbox, inspector, view);
-});
-
-function* testIndentation(toolbox, inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 2);
-
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(view, ruleEditor.closeBrace);
-
-  let input = editor.input;
-
-  info("Entering color in the property name editor");
-  input.value = "color";
 
-  info("Pressing return to commit and focus the new value field");
-  let onValueFocus = once(ruleEditor.element, "focus", true);
-  let onModifications = ruleEditor.rule._applyingModifications;
-  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onValueFocus;
-  yield onModifications;
+  info("Add a new property in the rule-view");
+  yield addProperty(view, 2, "color", "chartreuse");
 
-  // Getting the new value editor after focus
-  editor = inplaceEditor(view.styleDocument.activeElement);
-  info("Entering a value and bluring the field to expect a rule change");
-  editor.input.value = "chartreuse";
-  let onBlur = once(editor.input, "blur");
-  onModifications = ruleEditor.rule._applyingModifications;
-  editor.input.blur();
-  yield onBlur;
-  yield onModifications;
-
+  info("Switch to the style-editor");
   let { UI } = yield toolbox.selectTool("styleeditor");
 
   let styleEditor = yield UI.editors[0].getSourceEditor();
   let text = styleEditor.sourceEditor.getText();
   is(text, expectedText, "style inspector changes are synced");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_01.js
@@ -2,18 +2,16 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Check that inherited properties appear for a nested element in the
 // rule view.
 
-var {ELEMENT_STYLE} = require("devtools/server/actors/styles");
-
 const TEST_URI = `
   <style type="text/css">
     #test2 {
       background-color: green;
       color: purple;
     }
   </style>
   <div id="test2"><div id="test1">Styled Node</div></div>
--- a/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_inherited-properties_02.js
@@ -2,18 +2,16 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Check that no inherited properties appear when the property does not apply
 // to the nested element.
 
-var {ELEMENT_STYLE} = require("devtools/server/actors/styles");
-
 const TEST_URI = `
   <style type="text/css">
     #test2 {
       background-color: green;
     }
   </style>
   <div id="test2"><div id="test1">Styled Node</div></div>
 `;
--- a/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframeLineNumbers.js
@@ -9,20 +9,17 @@
 
 const TESTCASE_URI = URL_ROOT + "doc_keyframeLineNumbers.html";
 
 add_task(function*() {
   yield addTab(TESTCASE_URI);
   let { inspector, view } = yield openRuleView();
   yield selectNode("#outer", inspector);
 
-  // Insert a new property, which will affect the line numbers.
-  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
-  let onRuleViewChanged = view.once("ruleview-changed");
-  yield createNewRuleViewProperty(elementRuleEditor, "font-size: 72px");
-  yield onRuleViewChanged;
+  info("Insert a new property, which will affect the line numbers");
+  yield addProperty(view, 1, "font-size", "72px");
 
   yield selectNode("#inner", inspector);
 
   let value = getRuleViewLinkTextByIndex(view, 3);
   // Note that this is relative to the <style>.
   is(value.slice(-3), ":27", "rule line number is 27");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_01.js
@@ -60,25 +60,19 @@ function* testMoxy(inspector, view) {
   });
 
   assertGutters(view, {
     guttersNbs: 2,
     gutterHeading: ["Keyframes boxy", "Keyframes moxy"]
   });
 }
 
-function* testNode(selector, inspector, view) {
-  let element = getNode(selector);
+function* assertKeyframeRules(selector, inspector, view, expected) {
   yield selectNode(selector, inspector);
   let elementStyle = view._elementStyle;
-  return {element, elementStyle};
-}
-
-function* assertKeyframeRules(selector, inspector, view, expected) {
-  let {element, elementStyle} = yield testNode(selector, inspector, view);
 
   let rules = {
     elementRules: elementStyle.rules.filter(rule => !rule.keyframes),
     keyframeRules: elementStyle.rules.filter(rule => rule.keyframes)
   };
 
   is(rules.elementRules.length, expected.elementRulesNb, selector +
     " has the correct number of non keyframe element rules");
@@ -88,18 +82,16 @@ function* assertKeyframeRules(selector, 
   let i = 0;
   for (let keyframeRule of rules.keyframeRules) {
     ok(keyframeRule.keyframes.name == expected.keyframesRules[i],
       keyframeRule.keyframes.name + " has the correct keyframes name");
     ok(keyframeRule.domRule.keyText == expected.keyframeRules[i],
       keyframeRule.domRule.keyText + " selector heading is correct");
     i++;
   }
-
-  return {rules, element, elementStyle};
 }
 
 function assertGutters(view, expected) {
   let gutters = view.element.querySelectorAll(".theme-gutter");
 
   is(gutters.length, expected.guttersNbs,
     "There are " + gutters.length + " gutter headings");
 
--- a/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_keyframes-rule_02.js
@@ -14,26 +14,28 @@ add_task(function*() {
   let {inspector, view} = yield openRuleView();
   yield testPacman(inspector, view);
   yield testBoxy(inspector, view);
 });
 
 function* testPacman(inspector, view) {
   info("Test content in the keyframes rule of #pacman");
 
-  let {rules} = yield getKeyframeRules("#pacman", inspector, view);
+  let rules = yield getKeyframeRules("#pacman", inspector, view);
 
   info("Test text properties for Keyframes #pacman");
 
   is(convertTextPropsToString(rules.keyframeRules[0].textProps),
     "left: 750px",
     "Keyframe pacman (100%) property is correct"
   );
 
   // Dynamic changes test disabled because of Bug 1050940
+  // If this part of the test is ever enabled again, it should be changed to
+  // use addProperty (in head.js) and stop using _applyingModifications
 
   // info("Test dynamic changes to keyframe rule for #pacman");
 
   // let defaultView = element.ownerDocument.defaultView;
   // let ruleEditor = view.element.children[5].childNodes[0]._ruleEditor;
   // ruleEditor.addProperty("opacity", "0");
 
   // yield ruleEditor._applyingModifications;
@@ -48,17 +50,17 @@ function* testPacman(inspector, view) {
 
   // is(defaultView.getComputedStyle(element).getPropertyValue("opacity"), "0",
   //   "Added opacity property should have been used.");
 }
 
 function* testBoxy(inspector, view) {
   info("Test content in the keyframes rule of #boxy");
 
-  let {rules} = yield getKeyframeRules("#boxy", inspector, view);
+  let rules = yield getKeyframeRules("#boxy", inspector, view);
 
   info("Test text properties for Keyframes #boxy");
 
   is(convertTextPropsToString(rules.keyframeRules[0].textProps),
     "background-color: blue",
     "Keyframe boxy (10%) property is correct"
   );
 
@@ -73,20 +75,18 @@ function* testBoxy(inspector, view) {
   );
 }
 
 function convertTextPropsToString(textProps) {
   return textProps.map(t => t.name + ": " + t.value).join("; ");
 }
 
 function* getKeyframeRules(selector, inspector, view) {
-  let element = getNode(selector);
-
   yield selectNode(selector, inspector);
   let elementStyle = view._elementStyle;
 
   let rules = {
     elementRules: elementStyle.rules.filter(rule => !rule.keyframes),
     keyframeRules: elementStyle.rules.filter(rule => rule.keyframes)
   };
 
-  return {rules, element, elementStyle};
+  return rules;
 }
--- a/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js
+++ b/devtools/client/inspector/rules/test/browser_rules_lineNumbers.js
@@ -8,45 +8,22 @@
 // rules in the rule view.
 
 const TESTCASE_URI = URL_ROOT + "doc_ruleLineNumbers.html";
 
 add_task(function*() {
   yield addTab(TESTCASE_URI);
   let { inspector, view } = yield openRuleView();
   yield selectNode("#testid", inspector);
-  let elementRuleEditor = getRuleViewRuleEditor(view, 1);
 
   let bodyRuleEditor = getRuleViewRuleEditor(view, 3);
   let value = getRuleViewLinkTextByIndex(view, 2);
   // Note that this is relative to the <style>.
   is(value.slice(-2), ":6", "initial rule line number is 6");
 
-  info("Focusing a new property name in the rule-view");
-  let editor = yield focusEditableField(view, elementRuleEditor.closeBrace);
-
-  is(inplaceEditor(elementRuleEditor.newPropSpan), editor,
-    "The new property editor got focused");
-  let input = editor.input;
-
-  info("Entering font-size in the property name editor");
-  input.value = "font-size";
-
-  info("Pressing return to commit and focus the new value field");
   let onLocationChanged = once(bodyRuleEditor.rule.domRule, "location-changed");
-  let onValueFocus = once(elementRuleEditor.element, "focus", true);
-  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
-  yield onValueFocus;
-  yield elementRuleEditor.rule._applyingModifications;
-
-  // Getting the new value editor after focus
-  editor = inplaceEditor(view.styleDocument.activeElement);
-  info("Entering a value and bluring the field to expect a rule change");
-  editor.input.value = "23px";
-  editor.input.blur();
-  yield elementRuleEditor.rule._applyingModifications;
-
+  yield addProperty(view, 1, "font-size", "23px");
   yield onLocationChanged;
 
   let newBodyTitle = getRuleViewLinkTextByIndex(view, 2);
   // Note that this is relative to the <style>.
   is(newBodyTitle.slice(-2), ":7", "updated rule line number is 7");
 });
--- a/devtools/client/inspector/rules/test/browser_rules_livepreview.js
+++ b/devtools/client/inspector/rules/test/browser_rules_livepreview.js
@@ -37,38 +37,35 @@ add_task(function*() {
   yield selectNode("#testid", inspector);
 
   for (let data of TEST_DATA) {
     yield testLivePreviewData(data, view, "#testid");
   }
 });
 
 function* testLivePreviewData(data, ruleView, selector) {
-  let ruleEditor = getRuleViewRuleEditor(ruleView, 1);
-  let propEditor = ruleEditor.rule.textProps[0].editor;
+  let rule = getRuleViewRuleEditor(ruleView, 1).rule;
+  let propEditor = rule.textProps[0].editor;
 
   info("Focusing the property value inplace-editor");
   let editor = yield focusEditableField(ruleView, propEditor.valueSpan);
   is(inplaceEditor(propEditor.valueSpan), editor,
     "The focused editor is the value");
 
-  info("Enter a value in the editor");
+  info("Entering value in the editor: " + data.value);
+  let onPreviewDone = ruleView.once("ruleview-changed");
   EventUtils.sendString(data.value, ruleView.styleWindow);
+  yield onPreviewDone;
+
+  let onValueDone = ruleView.once("ruleview-changed");
   if (data.escape) {
     EventUtils.synthesizeKey("VK_ESCAPE", {});
   } else {
     EventUtils.synthesizeKey("VK_RETURN", {});
   }
-
-  // Wait for the modifyproperties request to complete before
-  // checking the computed style.
-  for (let rule of ruleView._elementStyle.rules) {
-    if (rule._applyingModifications) {
-      yield rule._applyingModifications;
-    }
-  }
+  yield onValueDone;
 
   // While the editor is still focused in, the display should have
   // changed already
   is((yield getComputedStyleProperty(selector, null, "display")),
     data.expected,
     "Element should be previewed as " + data.expected);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_01.js
@@ -18,47 +18,39 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testMarkOverridden(inspector, view);
-});
 
-function* testMarkOverridden(inspector, view) {
-  let elementStyle = view._elementStyle;
-
-  let idRule = elementStyle.rules[1];
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
   let idProp = idRule.textProps[0];
   is(idProp.name, "background-color",
     "First ID property should be background-color");
   is(idProp.value, "blue", "First ID property value should be blue");
   ok(!idProp.overridden, "ID prop should not be overridden.");
   ok(!idProp.editor.element.classList.contains("ruleview-overridden"),
     "ID property editor should not have ruleview-overridden class");
 
-  let classRule = elementStyle.rules[2];
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
   let classProp = classRule.textProps[0];
   is(classProp.name, "background-color",
     "First class prop should be background-color");
   is(classProp.value, "green", "First class property value should be green");
   ok(classProp.overridden, "Class property should be overridden.");
   ok(classProp.editor.element.classList.contains("ruleview-overridden"),
     "Class property editor should have ruleview-overridden class");
 
   // Override background-color by changing the element style.
-  let elementRule = elementStyle.rules[0];
-  elementRule.createProperty("background-color", "purple", "");
-  yield elementRule._applyingModifications;
+  let elementProp = yield addProperty(view, 0, "background-color", "purple");
 
-  let elementProp = elementRule.textProps[0];
   ok(!elementProp.overridden,
     "Element style property should not be overridden");
   ok(idProp.overridden, "ID property should be overridden");
   ok(idProp.editor.element.classList.contains("ruleview-overridden"),
     "ID property editor should have ruleview-overridden class");
   ok(classProp.overridden, "Class property should be overridden");
   ok(classProp.editor.element.classList.contains("ruleview-overridden"),
     "Class property editor should have ruleview-overridden class");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_03.js
@@ -18,31 +18,24 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testMarkOverridden(inspector, view);
-});
 
-function* testMarkOverridden(inspector, view) {
-  let elementStyle = view._elementStyle;
-
-  let idRule = elementStyle.rules[1];
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
   let idProp = idRule.textProps[0];
   ok(idProp.overridden, "Not-important rule should be overridden.");
 
-  let classRule = elementStyle.rules[2];
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
   let classProp = classRule.textProps[0];
   ok(!classProp.overridden, "Important rule should not be overridden.");
 
-  let elementRule = elementStyle.rules[0];
-  let elementProp = elementRule.createProperty("background-color", "purple",
-    "important");
-  yield elementRule._applyingModifications;
+  ok(idProp.overridden, "ID property should be overridden.");
 
-  ok(!elementProp.overridden, "New important prop should not be overriden.");
-  ok(idProp.overridden, "ID property should be overridden.");
-  ok(classProp.overridden, "Class property should be overridden.");
-}
+  // FIXME: re-enable these 2 assertions when bug 1247737 is fixed.
+  // let elementProp = yield addProperty(view, 0, "background-color", "purple");
+  // ok(!elementProp.overridden, "New important prop should not be overriden.");
+  // ok(classProp.overridden, "Class property should be overridden.");
+});
--- a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_04.js
@@ -18,25 +18,19 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testMarkOverridden(inspector, view);
-});
 
-function* testMarkOverridden(inspector, view) {
-  let elementStyle = view._elementStyle;
-
-  let idRule = elementStyle.rules[1];
+  let idRule = getRuleViewRuleEditor(view, 1).rule;
   let idProp = idRule.textProps[0];
 
-  idProp.setEnabled(false);
-  yield idRule._applyingModifications;
+  yield togglePropStatus(view, idProp);
 
-  let classRule = elementStyle.rules[2];
+  let classRule = getRuleViewRuleEditor(view, 2).rule;
   let classProp = classRule.textProps[0];
   ok(!classProp.overridden,
     "Class prop should not be overridden after id prop was disabled.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js
+++ b/devtools/client/inspector/rules/test/browser_rules_mark_overridden_05.js
@@ -15,23 +15,19 @@ const TEST_URI = `
   </style>
   <div id='testid' class='testclass'>Styled Node</div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
-  yield testMarkOverridden(inspector, view);
-});
 
-function* testMarkOverridden(inspector, view) {
-  let ruleEditor = getRuleViewRuleEditor(view, 1);
+  let rule = getRuleViewRuleEditor(view, 1).rule;
 
-  yield createNewRuleViewProperty(ruleEditor, "background-color: red;");
-  yield ruleEditor.rule._applyingModifications;
+  yield addProperty(view, 1, "background-color", "red");
 
-  let firstProp = ruleEditor.rule.textProps[0];
-  let secondProp = ruleEditor.rule.textProps[1];
+  let firstProp = rule.textProps[0];
+  let secondProp = rule.textProps[1];
 
   ok(firstProp.overridden, "First property should be overridden.");
   ok(!secondProp.overridden, "Second property should not be overridden.");
-}
+});
--- a/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
+++ b/devtools/client/inspector/rules/test/browser_rules_original-source-link.js
@@ -73,13 +73,12 @@ function editorSelected(editor) {
 
   let {line} = editor.sourceEditor.getCursor();
   is(line, 3, "cursor is at correct line number in original source");
 }
 
 function verifyLinkText(text, view) {
   info("Verifying that the rule-view stylesheet link is " + text);
   let label = getRuleViewLinkByIndex(view, 1).querySelector("label");
-  return waitForSuccess(
-    () => label.getAttribute("value") == text,
-    "Link text changed to display correct location: " + text
-  );
+  return waitForSuccess(function*() {
+    return label.getAttribute("value") == text;
+  }, "Link text changed to display correct location: " + text);
 }
--- a/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_pseudo-element_01.js
@@ -21,18 +21,18 @@ add_task(function*() {
   yield testBottomLeft(inspector, view);
   yield testParagraph(inspector, view);
   yield testBody(inspector, view);
 
   Services.prefs.clearUserPref(PSEUDO_PREF);
 });
 
 function* testTopLeft(inspector, view) {
-  let selector = "#topleft";
-  let {rules} = yield assertPseudoElementRulesNumbers(selector,
+  let id = "#topleft";
+  let rules = yield assertPseudoElementRulesNumbers(id,
     inspector, view, {
       elementRulesNb: 4,
       firstLineRulesNb: 2,
       firstLetterRulesNb: 1,
       selectionRulesNb: 0
     }
   );
 
@@ -53,71 +53,74 @@ function* testTopLeft(inspector, view) {
 
   info("Make sure that dblclicking on the header container also toggles " +
        "the pseudo elements");
   EventUtils.synthesizeMouseAtCenter(gutters[0], {clickCount: 2},
                                      view.styleWindow);
   ok(!view.element.firstChild.classList.contains("show-expandable-container"),
      "Pseudo Elements are collapsed by dblclicking");
 
-  let elementRule = rules.elementRules[0];
   let elementRuleView = getRuleViewRuleEditor(view, 3);
 
   let elementFirstLineRule = rules.firstLineRules[0];
-  let elementFirstLineRuleView = [...view.element.children[1].children].filter(e => {
-    return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
-  })[0]._ruleEditor;
+  let elementFirstLineRuleView =
+    [...view.element.children[1].children].filter(e => {
+      return e._ruleEditor && e._ruleEditor.rule === elementFirstLineRule;
+    })[0]._ruleEditor;
 
   is(convertTextPropsToString(elementFirstLineRule.textProps),
      "color: orange",
      "TopLeft firstLine properties are correct");
 
+  let onAdded = view.once("ruleview-changed");
   let firstProp = elementFirstLineRuleView.addProperty("background-color",
     "rgb(0, 255, 0)", "");
+  yield onAdded;
+
+  onAdded = view.once("ruleview-changed");
   let secondProp = elementFirstLineRuleView.addProperty("font-style",
     "italic", "");
+  yield onAdded;
 
   is(firstProp,
      elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 2],
      "First added property is on back of array");
   is(secondProp,
      elementFirstLineRule.textProps[elementFirstLineRule.textProps.length - 1],
      "Second added property is on back of array");
 
-  yield elementFirstLineRule._applyingModifications;
-
-  is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
+  is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(0, 255, 0)", "Added property should have been used.");
-  is((yield getComputedStyleProperty(selector, ":first-line", "font-style")),
+  is((yield getComputedStyleProperty(id, ":first-line", "font-style")),
      "italic", "Added property should have been used.");
-  is((yield getComputedStyleProperty(selector, null, "text-decoration")),
+  is((yield getComputedStyleProperty(id, null, "text-decoration")),
      "none", "Added property should not apply to element");
 
-  firstProp.setEnabled(false);
-  yield elementFirstLineRule._applyingModifications;
+  yield togglePropStatus(view, firstProp);
 
-  is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
+  is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(255, 0, 0)", "Disabled property should now have been used.");
-  is((yield getComputedStyleProperty(selector, null, "background-color")),
+  is((yield getComputedStyleProperty(id, null, "background-color")),
      "rgb(221, 221, 221)", "Added property should not apply to element");
 
-  firstProp.setEnabled(true);
-  yield elementFirstLineRule._applyingModifications;
+  yield togglePropStatus(view, firstProp);
 
-  is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
+  is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(0, 255, 0)", "Added property should have been used.");
-  is((yield getComputedStyleProperty(selector, null, "text-decoration")),
+  is((yield getComputedStyleProperty(id, null, "text-decoration")),
      "none", "Added property should not apply to element");
 
-  firstProp = elementRuleView.addProperty("background-color", "rgb(0, 0, 255)", "");
-  yield elementRule._applyingModifications;
+  onAdded = view.once("ruleview-changed");
+  firstProp = elementRuleView.addProperty("background-color",
+                                          "rgb(0, 0, 255)", "");
+  yield onAdded;
 
-  is((yield getComputedStyleProperty(selector, null, "background-color")),
+  is((yield getComputedStyleProperty(id, null, "background-color")),
      "rgb(0, 0, 255)", "Added property should have been used.");
-  is((yield getComputedStyleProperty(selector, ":first-line", "background-color")),
+  is((yield getComputedStyleProperty(id, ":first-line", "background-color")),
      "rgb(0, 255, 0)", "Added prop does not apply to pseudo");
 }
 
 function* testTopRight(inspector, view) {
   yield assertPseudoElementRulesNumbers("#topright", inspector, view, {
     elementRulesNb: 4,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
@@ -150,22 +153,23 @@ function* testBottomLeft(inspector, view
     elementRulesNb: 4,
     firstLineRulesNb: 1,
     firstLetterRulesNb: 1,
     selectionRulesNb: 0
   });
 }
 
 function* testParagraph(inspector, view) {
-  let {rules} = yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, {
-    elementRulesNb: 3,
-    firstLineRulesNb: 1,
-    firstLetterRulesNb: 1,
-    selectionRulesNb: 1
-  });
+  let rules =
+    yield assertPseudoElementRulesNumbers("#bottomleft p", inspector, view, {
+      elementRulesNb: 3,
+      firstLineRulesNb: 1,
+      firstLetterRulesNb: 1,
+      selectionRulesNb: 1
+    });
 
   assertGutters(view);
 
   let elementFirstLineRule = rules.firstLineRules[0];
   is(convertTextPropsToString(elementFirstLineRule.textProps),
      "background: blue",
      "Paragraph first-line properties are correct");
 
@@ -187,24 +191,23 @@ function* testBody(inspector, view) {
   is(gutters.length, 0, "There are no gutter headings");
 }
 
 function convertTextPropsToString(textProps) {
   return textProps.map(t => t.name + ": " + t.value).join("; ");
 }
 
 function* testNode(selector, inspector, view) {
-  let element = getNode(selector);
   yield selectNode(selector, inspector);
   let elementStyle = view._elementStyle;
-  return {element: element, elementStyle: elementStyle};
+  return elementStyle;
 }
 
 function* assertPseudoElementRulesNumbers(selector, inspector, view, ruleNbs) {
-  let {element, elementStyle} = yield testNode(selector, inspector, view);
+  let elementStyle = yield testNode(selector, inspector, view);
 
   let rules = {
     elementRules: elementStyle.rules.filter(rule => !rule.pseudoElement),
     firstLineRules: elementStyle.rules.filter(rule =>
       rule.pseudoElement === ":first-line"),
     firstLetterRules: elementStyle.rules.filter(rule =>
       rule.pseudoElement === ":first-letter"),
     selectionRules: elementStyle.rules.filter(rule =>
@@ -215,17 +218,17 @@ function* assertPseudoElementRulesNumber
      selector + " has the correct number of non pseudo element rules");
   is(rules.firstLineRules.length, ruleNbs.firstLineRulesNb,
      selector + " has the correct number of :first-line rules");
   is(rules.firstLetterRules.length, ruleNbs.firstLetterRulesNb,
      selector + " has the correct number of :first-letter rules");
   is(rules.selectionRules.length, ruleNbs.selectionRulesNb,
      selector + " has the correct number of :selection rules");
 
-  return {rules, element, elementStyle};
+  return rules;
 }
 
 function getGutters(view) {
   return view.element.querySelectorAll(".theme-gutter");
 }
 
 function assertGutters(view) {
   let gutters = getGutters(view);
--- a/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-no-flicker.js
@@ -1,24 +1,25 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test that the rule view does not go blank while selecting a new node.
 
-const TESTCASE_URI = 'data:text/html;charset=utf-8,' +
-                     '<div id="testdiv" style="font-size:10px;">Test div!</div>';
+const TESTCASE_URI = "data:text/html;charset=utf-8," +
+                     "<div id=\"testdiv\" style=\"font-size:10px;\">" +
+                     "Test div!</div>";
 
 add_task(function*() {
   yield addTab(TESTCASE_URI);
 
   info("Opening the rule view and selecting the test node");
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {inspector, view} = yield openRuleView();
   let testdiv = yield getNodeFront("#testdiv", inspector);
   yield selectNode(testdiv, inspector);
 
   let htmlBefore = view.element.innerHTML;
   ok(htmlBefore.indexOf("font-size") > -1,
      "The rule view should contain a font-size property.");
 
   // Do the selectNode call manually, because otherwise it's hard to guarantee
--- a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_01.js
@@ -17,36 +17,35 @@ const TEST_URI = `
   </style>
   <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;">
     Styled Node
   </div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
-  let testElement = getNode("#testid");
+  let {inspector, view, testActor} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
   info("Checking that the rule-view has the element, #testid and " +
     ".testclass selectors");
   checkRuleViewContent(view, ["element", "#testid", ".testclass"]);
 
   info("Changing the node's ID attribute and waiting for the " +
     "rule-view refresh");
   let ruleViewRefreshed = inspector.once("rule-view-refreshed");
-  testElement.setAttribute("id", "differentid");
+  yield testActor.setAttribute("#testid", "id", "differentid");
   yield ruleViewRefreshed;
 
   info("Checking that the rule-view doesn't have the #testid selector anymore");
   checkRuleViewContent(view, ["element", ".testclass"]);
 
   info("Reverting the ID attribute change");
   ruleViewRefreshed = inspector.once("rule-view-refreshed");
-  testElement.setAttribute("id", "testid");
+  yield testActor.setAttribute("#differentid", "id", "testid");
   yield ruleViewRefreshed;
 
   info("Checking that the rule-view has all the selectors again");
   checkRuleViewContent(view, ["element", "#testid", ".testclass"]);
 });
 
 function checkRuleViewContent(view, expectedSelectors) {
   let selectors = view.styleDocument
--- a/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-attribute-change_02.js
@@ -10,144 +10,144 @@
 const TEST_URI = `
   <div id="testid" class="testclass" style="margin-top: 1px; padding-top: 5px;">
     Styled Node
   </div>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
-  let testElement = getNode("#testid");
+  let {inspector, view, testActor} = yield openRuleView();
   yield selectNode("#testid", inspector);
 
   yield testPropertyChanges(inspector, view);
-  yield testPropertyChange0(inspector, view, testElement);
-  yield testPropertyChange1(inspector, view, testElement);
-  yield testPropertyChange2(inspector, view, testElement);
-  yield testPropertyChange3(inspector, view, testElement);
-  yield testPropertyChange4(inspector, view, testElement);
-  yield testPropertyChange5(inspector, view, testElement);
-  yield testPropertyChange6(inspector, view, testElement);
+  yield testPropertyChange0(inspector, view, "#testid", testActor);
+  yield testPropertyChange1(inspector, view, "#testid", testActor);
+  yield testPropertyChange2(inspector, view, "#testid", testActor);
+  yield testPropertyChange3(inspector, view, "#testid", testActor);
+  yield testPropertyChange4(inspector, view, "#testid", testActor);
+  yield testPropertyChange5(inspector, view, "#testid", testActor);
+  yield testPropertyChange6(inspector, view, "#testid", testActor);
 });
 
 function* testPropertyChanges(inspector, ruleView) {
   info("Adding a second margin-top value in the element selector");
   let ruleEditor = ruleView._elementStyle.rules[0].editor;
   let onRefreshed = inspector.once("rule-view-refreshed");
   ruleEditor.addProperty("margin-top", "5px", "");
   yield onRefreshed;
 
   let rule = ruleView._elementStyle.rules[0];
   validateTextProp(rule.textProps[0], false, "margin-top", "1px",
     "Original margin property active");
 }
 
-function* testPropertyChange0(inspector, ruleView, testElement) {
-  yield changeElementStyle(testElement, "margin-top: 1px; padding-top: 5px",
-    inspector);
+function* testPropertyChange0(inspector, ruleView, selector, testActor) {
+  yield changeElementStyle(selector, "margin-top: 1px; padding-top: 5px",
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
     "Correct number of properties");
   validateTextProp(rule.textProps[0], true, "margin-top", "1px",
     "First margin property re-enabled");
   validateTextProp(rule.textProps[2], false, "margin-top", "5px",
     "Second margin property disabled");
 }
 
-function* testPropertyChange1(inspector, ruleView, testElement) {
+function* testPropertyChange1(inspector, ruleView, selector, testActor) {
   info("Now set it back to 5px, the 5px value should be re-enabled.");
-  yield changeElementStyle(testElement, "margin-top: 5px; padding-top: 5px;",
-    inspector);
+  yield changeElementStyle(selector, "margin-top: 5px; padding-top: 5px;",
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
     "Correct number of properties");
   validateTextProp(rule.textProps[0], false, "margin-top", "1px",
     "First margin property re-enabled");
   validateTextProp(rule.textProps[2], true, "margin-top", "5px",
     "Second margin property disabled");
 }
 
-function* testPropertyChange2(inspector, ruleView, testElement) {
+function* testPropertyChange2(inspector, ruleView, selector, testActor) {
   info("Set the margin property to a value that doesn't exist in the editor.");
   info("Should reuse the currently-enabled element (the second one.)");
-  yield changeElementStyle(testElement, "margin-top: 15px; padding-top: 5px;",
-    inspector);
+  yield changeElementStyle(selector, "margin-top: 15px; padding-top: 5px;",
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
     "Correct number of properties");
   validateTextProp(rule.textProps[0], false, "margin-top", "1px",
     "First margin property re-enabled");
   validateTextProp(rule.textProps[2], true, "margin-top", "15px",
     "Second margin property disabled");
 }
 
-function* testPropertyChange3(inspector, ruleView, testElement) {
+function* testPropertyChange3(inspector, ruleView, selector, testActor) {
   info("Remove the padding-top attribute. Should disable the padding " +
     "property but not remove it.");
-  yield changeElementStyle(testElement, "margin-top: 5px;", inspector);
+  yield changeElementStyle(selector, "margin-top: 5px;", inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
     "Correct number of properties");
   validateTextProp(rule.textProps[1], false, "padding-top", "5px",
     "Padding property disabled");
 }
 
-function* testPropertyChange4(inspector, ruleView, testElement) {
+function* testPropertyChange4(inspector, ruleView, selector, testActor) {
   info("Put the padding-top attribute back in, should re-enable the " +
     "padding property.");
-  yield changeElementStyle(testElement, "margin-top: 5px; padding-top: 25px",
-    inspector);
+  yield changeElementStyle(selector, "margin-top: 5px; padding-top: 25px",
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 3,
     "Correct number of properties");
   validateTextProp(rule.textProps[1], true, "padding-top", "25px",
     "Padding property enabled");
 }
 
-function* testPropertyChange5(inspector, ruleView, testElement) {
+function* testPropertyChange5(inspector, ruleView, selector, testActor) {
   info("Add an entirely new property");
-  yield changeElementStyle(testElement,
-    "margin-top: 5px; padding-top: 25px; padding-left: 20px;", inspector);
+  yield changeElementStyle(selector,
+    "margin-top: 5px; padding-top: 25px; padding-left: 20px;",
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 4,
     "Added a property");
   validateTextProp(rule.textProps[3], true, "padding-left", "20px",
     "Padding property enabled");
 }
 
-function* testPropertyChange6(inspector, ruleView, testElement) {
+function* testPropertyChange6(inspector, ruleView, selector, testActor) {
   info("Add an entirely new property again");
-  yield changeElementStyle(testElement, "background: red " +
+  yield changeElementStyle(selector, "background: red " +
     "url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
-    inspector);
+    inspector, testActor);
 
   let rule = ruleView._elementStyle.rules[0];
   is(rule.editor.element.querySelectorAll(".ruleview-property").length, 5,
     "Added a property");
   validateTextProp(rule.textProps[4], true, "background",
                    "red url(\"chrome://branding/content/about-logo.png\") repeat scroll 0% 0%",
                    "shortcut property correctly set");
 }
 
-function* changeElementStyle(testElement, style, inspector) {
+function* changeElementStyle(selector, style, inspector, testActor) {
   let onRefreshed = inspector.once("rule-view-refreshed");
-  testElement.setAttribute("style", style);
+  yield testActor.setAttribute(selector, "style", style);
   yield onRefreshed;
 }
 
-function validateTextProp(aProp, aEnabled, aName, aValue, aDesc) {
-  is(aProp.enabled, aEnabled, aDesc + ": enabled.");
-  is(aProp.name, aName, aDesc + ": name.");
-  is(aProp.value, aValue, aDesc + ": value.");
+function validateTextProp(prop, enabled, name, value, desc) {
+  is(prop.enabled, enabled, desc + ": enabled.");
+  is(prop.name, name, desc + ": name.");
+  is(prop.value, value, desc + ": value.");
 
-  is(aProp.editor.enable.hasAttribute("checked"), aEnabled,
-    aDesc + ": enabled checkbox.");
-  is(aProp.editor.nameSpan.textContent, aName, aDesc + ": name span.");
-  is(aProp.editor.valueSpan.textContent,
-    aValue, aDesc + ": value span.");
+  is(prop.editor.enable.hasAttribute("checked"), enabled,
+    desc + ": enabled checkbox.");
+  is(prop.editor.nameSpan.textContent, name, desc + ": name span.");
+  is(prop.editor.valueSpan.textContent,
+    value, desc + ": value span.");
 }
--- a/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js
+++ b/devtools/client/inspector/rules/test/browser_rules_refresh-on-style-change.js
@@ -8,27 +8,27 @@
 // changed
 
 const TEST_URI = "<div id='testdiv' style='font-size: 10px;''>Test div!</div>";
 
 add_task(function*() {
   Services.prefs.setCharPref("devtools.defaultColorUnit", "name");
 
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {inspector, view, testActor} = yield openRuleView();
   yield selectNode("#testdiv", inspector);
 
   let fontSize = getRuleViewPropertyValue(view, "element", "font-size");
   is(fontSize, "10px", "The rule view shows the right font-size");
 
   info("Changing the node's style and waiting for the update");
   let onUpdated = inspector.once("rule-view-refreshed");
-  let div = getNode("#testdiv");
-  div.style.cssText = "font-size: 3em; color: lightgoldenrodyellow; " +
-    "text-align: right; text-transform: uppercase";
+  yield testActor.setAttribute("#testdiv", "style",
+    "font-size: 3em; color: lightgoldenrodyellow; " +
+    "text-align: right; text-transform: uppercase");
   yield onUpdated;
 
   let textAlign = getRuleViewPropertyValue(view, "element", "text-align");
   is(textAlign, "right", "The rule view shows the new text align.");
   let color = getRuleViewPropertyValue(view, "element", "color");
   is(color, "lightgoldenrodyellow", "The rule view shows the new color.");
   fontSize = getRuleViewPropertyValue(view, "element", "font-size");
   is(fontSize, "3em", "The rule view shows the new font size.");
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter-computed-list_01.js
@@ -16,61 +16,66 @@ const TEST_URI = `
       background-color: red;
     }
   </style>
   <h1 id="testid" class="testclass">Styled Node</h1>
 `;
 
 const TEST_DATA = [
   {
-    desc: "Tests that the search filter works properly in the computed list for property names",
+    desc: "Tests that the search filter works properly in the computed list " +
+          "for property names",
     search: "margin",
     isExpanderOpen: false,
     isFilterOpen: false,
     isMarginHighlighted: true,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: true,
     isMarginBottomHighlighted: true,
     isMarginLeftHighlighted: true
   },
   {
-    desc: "Tests that the search filter works properly in the computed list for property values",
+    desc: "Tests that the search filter works properly in the computed list " +
+          "for property values",
     search: "0px",
     isExpanderOpen: false,
     isFilterOpen: false,
     isMarginHighlighted: true,
     isMarginTopHighlighted: false,
     isMarginRightHighlighted: true,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: true
   },
   {
-    desc: "Tests that the search filter works properly in the computed list for property line input",
+    desc: "Tests that the search filter works properly in the computed list " +
+          "for property line input",
     search: "margin-top:4px",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
-    desc: "Tests that the search filter works properly in the computed list for parsed name",
+    desc: "Tests that the search filter works properly in the computed list " +
+          "for parsed name",
     search: "margin-top:",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
-    desc: "Tests that the search filter works properly in the computed list for parsed property value",
+    desc: "Tests that the search filter works properly in the computed list " +
+          "for parsed property value",
     search: ":4px",
     isExpanderOpen: false,
     isFilterOpen: false,
     isMarginHighlighted: true,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: true,
     isMarginLeftHighlighted: false
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_01.js
@@ -27,21 +27,23 @@ const TEST_DATA = [
     desc: "Tests that the search filter works properly for property values",
     search: "00F"
   },
   {
     desc: "Tests that the search filter works properly for property line input",
     search: "background-color:#00F"
   },
   {
-    desc: "Tests that the search filter works properly for parsed property names",
+    desc: "Tests that the search filter works properly for parsed property " +
+          "names",
     search: "background:"
   },
   {
-    desc: "Tests that the search filter works properly for parsed property values",
+    desc: "Tests that the search filter works properly for parsed property " +
+          "values",
     search: ":00F"
   },
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
   let {inspector, view} = yield openRuleView();
   yield selectNode("#testid", inspector);
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_08.js
@@ -26,17 +26,17 @@ add_task(function*() {
 
   info("Enter the test value in the search filter");
   yield setSearchFilter(view, SEARCH);
 
   info("Focus the height property value");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let rule = ruleEditor.rule;
   let propEditor = rule.textProps[1].editor;
-  let editor = yield focusEditableField(view, propEditor.valueSpan);
+  yield focusEditableField(view, propEditor.valueSpan);
 
   info("Check that the correct rules are visible");
   is(view.element.children.length, 2, "Should have 2 rules.");
   is(rule.selectorText, "#testid", "Second rule is #testid.");
   ok(rule.textProps[0].editor.container.classList
     .contains("ruleview-highlight"),
     "width text property is correctly highlighted.");
   ok(!propEditor.container.classList.contains("ruleview-highlight"),
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_09.js
@@ -25,17 +25,17 @@ add_task(function*() {
   yield selectNode("#testid", inspector);
 
   info("Enter the test value in the search filter");
   yield setSearchFilter(view, SEARCH);
 
   info("Start entering a new property in the rule");
   let ruleEditor = getRuleViewRuleEditor(view, 1);
   let rule = ruleEditor.rule;
-  let editor = yield focusEditableField(view, ruleEditor.closeBrace);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
 
   info("Check that the correct rules are visible");
   is(view.element.children.length, 2, "Should have 2 rules.");
   is(rule.selectorText, "#testid", "Second rule is #testid.");
   ok(rule.textProps[0].editor.container.classList
     .contains("ruleview-highlight"),
     "width text property is correctly highlighted.");
   ok(!rule.textProps[1].editor.container.classList
--- a/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js
+++ b/devtools/client/inspector/rules/test/browser_rules_search-filter_10.js
@@ -15,23 +15,25 @@ const TEST_URI = `
       width: 100%;
     }
   </style>
   <div id="testid" class="testclass">Styled Node</div>
 `;
 
 const TEST_DATA = [
   {
-    desc: "Tests that the search filter works properly for a single rule selector",
+    desc: "Tests that the search filter works properly for a single rule " +
+          "selector",
     search: "#test",
     selectorText: "#testid",
     index: 0
   },
   {
-    desc: "Tests that the search filter works properly for multiple rule selectors",
+    desc: "Tests that the search filter works properly for multiple rule " +
+          "selectors",
     search: "body",
     selectorText: "html, body, div",
     index: 2
   }
 ];
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
--- a/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_select-and-copy-styles.js
@@ -75,17 +75,17 @@ function* checkCopySelection(view) {
   yield onPopup;
 
   ok(!view._contextmenu.menuitemCopy.hidden,
     "Copy menu item is not hidden as expected");
 
   try {
     yield waitForClipboard(() => view._contextmenu.menuitemCopy.click(),
       () => checkClipboardData(expectedPattern));
-  } catch(e) {
+  } catch (e) {
     failedClipboard(expectedPattern);
   }
 
   view._contextmenu._menupopup.hidePopup();
 }
 
 function* checkSelectAll(view) {
   info("Testing select-all copy");
@@ -113,17 +113,17 @@ function* checkSelectAll(view) {
   yield onPopup;
 
   ok(!view._contextmenu.menuitemCopy.hidden,
     "Copy menu item is not hidden as expected");
 
   try {
     yield waitForClipboard(() => view._contextmenu.menuitemCopy.click(),
       () => checkClipboardData(expectedPattern));
-  } catch(e) {
+  } catch (e) {
     failedClipboard(expectedPattern);
   }
 
   view._contextmenu._menupopup.hidePopup();
 }
 
 function checkClipboardData(expectedPattern) {
   let actual = SpecialPowers.getClipboardData("text/unicode");
--- a/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js
+++ b/devtools/client/inspector/rules/test/browser_rules_selector_highlight.js
@@ -2,18 +2,16 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that the rule view selector text is highlighted correctly according
 // to the components of the selector.
 
-const SEARCH = "00F";
-
 const TEST_URI = [
   "<style type='text/css'>",
   "  h1 {}",
   "  h1#testid {}",
   "  h1 + p {}",
   "  div[hidden=\"true\"] {}",
   "  div[title=\"test\"][checked=true] {}",
   "  p:empty {}",
--- a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter-computed-list_01.js
@@ -16,85 +16,92 @@ const TEST_URI = `
       background-color: red;
     }
   </style>
   <h1 id="testid" class="testclass">Styled Node</h1>
 `;
 
 const TEST_DATA = [
   {
-    desc: "Tests that the strict search filter works properly in the computed list for property names",
+    desc: "Tests that the strict search filter works properly in the " +
+          "computed list for property names",
     search: "`margin-left`",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: false,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: true
   },
   {
-    desc: "Tests that the strict search filter works properly in the computed list for property values",
+    desc: "Tests that the strict search filter works properly in the " +
+          "computed list for property values",
     search: "`0px`",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: false,
     isMarginRightHighlighted: true,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
-    desc: "Tests that the strict search filter works properly in the computed list for parsed property names",
+    desc: "Tests that the strict search filter works properly in the " +
+          "computed list for parsed property names",
     search: "`margin-left`:",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: false,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: true
   },
   {
-    desc: "Tests that the strict search filter works properly in the computed list for parsed property values",
+    desc: "Tests that the strict search filter works properly in the " +
+          "computed list for parsed property values",
     search: ":`4px`",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
-    desc: "Tests that the strict search filter works properly in the computed list for property line input",
+    desc: "Tests that the strict search filter works properly in the " +
+          "computed list for property line input",
     search: "`margin-top`:`4px`",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
     desc: "Tests that the strict search filter works properly in the " +
-          "computed list for a parsed strict property name and non-strict property value",
+          "computed list for a parsed strict property name and non-strict " +
+          "property value",
     search: "`margin-top`:4px",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
   },
   {
     desc: "Tests that the strict search filter works properly in the " +
-          "computed list for a parsed strict property value and non-strict property name",
+          "computed list for a parsed strict property value and non-strict " +
+          "property name",
     search: "i:`4px`",
     isExpanderOpen: true,
     isFilterOpen: true,
     isMarginHighlighted: false,
     isMarginTopHighlighted: true,
     isMarginRightHighlighted: false,
     isMarginBottomHighlighted: false,
     isMarginLeftHighlighted: false
--- a/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js
+++ b/devtools/client/inspector/rules/test/browser_rules_strict-search-filter_01.js
@@ -18,41 +18,46 @@ const TEST_URI = `
       background-color: #00F;
     }
   </style>
   <h1 id="testid" class="testclass">Styled Node</h1>
 `;
 
 const TEST_DATA = [
   {
-    desc: "Tests that the strict search filter works properly for property names",
+    desc: "Tests that the strict search filter works properly for property " +
+          "names",
     search: "`color`",
     ruleCount: 2,
     propertyIndex: 1
   },
   {
-    desc: "Tests that the strict search filter works properly for property values",
+    desc: "Tests that the strict search filter works properly for property " +
+          "values",
     search: "`2%`",
     ruleCount: 2,
     propertyIndex: 0
   },
   {
-    desc: "Tests that the strict search filter works properly for parsed property names",
+    desc: "Tests that the strict search filter works properly for parsed " +
+          "property names",
     search: "`color`:",
     ruleCount: 2,
     propertyIndex: 1
   },
   {
-    desc: "Tests that the strict search filter works properly for parsed property values",
+    desc: "Tests that the strict search filter works properly for parsed " +
+          "property values",
     search: ":`2%`",
     ruleCount: 2,
     propertyIndex: 0
   },
   {
-    desc: "Tests that the strict search filter works properly for property line input",
+    desc: "Tests that the strict search filter works properly for property " +
+          "line input",
     search: "`width`:`2%`",
     ruleCount: 2,
     propertyIndex: 0
   },
   {
     desc: "Tests that the search filter works properly for a parsed strict " +
           "property name and non-strict property value.",
     search: "`width`:2%",
--- a/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
+++ b/devtools/client/inspector/rules/test/browser_rules_style-editor-link.js
@@ -1,73 +1,71 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-///////////////////
-//
-// Whitelisting this test.
+// FIXME: Whitelisting this test.
 // As part of bug 1077403, the leaking uncaught rejection should be fixed.
-//
 thisTestLeaksUncaughtRejectionsAndShouldBeFixed("Error: Unknown sheet source");
 
 // Test the links from the rule-view to the styleeditor
 
-const STYLESHEET_URL = "data:text/css,"+encodeURIComponent(
+const STYLESHEET_URL = "data:text/css," + encodeURIComponent(
   ["#first {",
    "color: blue",
    "}"].join("\n"));
 
 const EXTERNAL_STYLESHEET_FILE_NAME = "doc_style_editor_link.css";
 const EXTERNAL_STYLESHEET_URL = URL_ROOT + EXTERNAL_STYLESHEET_FILE_NAME;
 
-const DOCUMENT_URL = "data:text/html;charset=utf-8,"+encodeURIComponent(
-  ['<html>' +
-   '<head>' +
-   '<title>Rule view style editor link test</title>',
-   '<style type="text/css"> ',
-   'html { color: #000000; } ',
-   'div { font-variant: small-caps; color: #000000; } ',
-   '.nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em; ',
-   'font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">',
-   '</style>',
-   '<style>',
-   'div { font-weight: bold; }',
-   '</style>',
-   '<link rel="stylesheet" type="text/css" href="'+STYLESHEET_URL+'">',
-   '<link rel="stylesheet" type="text/css" href="'+EXTERNAL_STYLESHEET_URL+'">',
-   '</head>',
-   '<body>',
-   '<h1>Some header text</h1>',
-   '<p id="salutation" style="font-size: 12pt">hi.</p>',
-   '<p id="body" style="font-size: 12pt">I am a test-case. This text exists ',
-   'solely to provide some things to ',
-   '<span style="color: yellow" class="highlight">',
-   'highlight</span> and <span style="font-weight: bold">count</span> ',
-   'style list-items in the box at right. If you are reading this, ',
-   'you should go do something else instead. Maybe read a book. Or better ',
-   'yet, write some test-cases for another bit of code. ',
-   '<span style="font-style: italic">some text</span></p>',
-   '<p id="closing">more text</p>',
-   '<p>even more text</p>',
-   '</div>',
-   '</body>',
-   '</html>'].join("\n"));
+const DOCUMENT_URL = "data:text/html;charset=utf-8," + encodeURIComponent(`
+  <html>
+  <head>
+  <title>Rule view style editor link test</title>
+  <style type="text/css">
+  html { color: #000000; }
+  div { font-variant: small-caps; color: #000000; }
+  .nomatches {color: #ff0000;}</style> <div id="first" style="margin: 10em;
+  font-size: 14pt; font-family: helvetica, sans-serif; color: #AAA">
+  </style>
+  <style>
+  div { font-weight: bold; }
+  </style>
+  <link rel="stylesheet" type="text/css" href="${STYLESHEET_URL}">
+  <link rel="stylesheet" type="text/css" href="${EXTERNAL_STYLESHEET_URL}">
+  </head>
+  <body>
+  <h1>Some header text</h1>
+  <p id="salutation" style="font-size: 12pt">hi.</p>
+  <p id="body" style="font-size: 12pt">I am a test-case. This text exists
+  solely to provide some things to
+  <span style="color: yellow" class="highlight">
+  highlight</span> and <span style="font-weight: bold">count</span>
+  style list-items in the box at right. If you are reading this,
+  you should go do something else instead. Maybe read a book. Or better
+  yet, write some test-cases for another bit of code.
+  <span style="font-style: italic">some text</span></p>
+  <p id="closing">more text</p>
+  <p>even more text</p>
+  </div>
+  </body>
+  </html>
+`);
 
 add_task(function*() {
   yield addTab(DOCUMENT_URL);
-  let {toolbox, inspector, view} = yield openRuleView();
+  let {toolbox, inspector, view, testActor} = yield openRuleView();
   yield selectNode("div", inspector);
 
   yield testInlineStyle(view);
-  yield testFirstInlineStyleSheet(view, toolbox);
-  yield testSecondInlineStyleSheet(view, toolbox);
-  yield testExternalStyleSheet(view, toolbox);
+  yield testFirstInlineStyleSheet(view, toolbox, testActor);
+  yield testSecondInlineStyleSheet(view, toolbox, testActor);
+  yield testExternalStyleSheet(view, toolbox, testActor);
 });
 
 function* testInlineStyle(view) {
   info("Testing inline style");
 
   let onTab = waitForTab();
   info("Clicking on the first link in the rule-view");
   clickLinkByIndex(view, 0);
@@ -75,79 +73,83 @@ function* testInlineStyle(view) {
   let tab = yield onTab;
 
   let tabURI = tab.linkedBrowser.documentURI.spec;
   ok(tabURI.startsWith("view-source:"), "View source tab is open");
   info("Closing tab");
   gBrowser.removeTab(tab);
 }
 
-function* testFirstInlineStyleSheet(view, toolbox) {
+function* testFirstInlineStyleSheet(view, toolbox, testActor) {
   info("Testing inline stylesheet");
 
   info("Listening for toolbox switch to the styleeditor");
   let onSwitch = waitForStyleEditor(toolbox);
 
   info("Clicking an inline stylesheet");
   clickLinkByIndex(view, 4);
   let editor = yield onSwitch;
 
   ok(true, "Switched to the style-editor panel in the toolbox");
 
-  validateStyleEditorSheet(editor, 0);
+  yield validateStyleEditorSheet(editor, 0, testActor);
 }
 
-function* testSecondInlineStyleSheet(view, toolbox) {
+function* testSecondInlineStyleSheet(view, toolbox, testActor) {
   info("Testing second inline stylesheet");
 
   info("Waiting for the stylesheet editor to be selected");
   let panel = toolbox.getCurrentPanel();
   let onSelected = panel.UI.once("editor-selected");
 
   info("Switching back to the inspector panel in the toolbox");
   yield toolbox.selectTool("inspector");
 
   info("Clicking on second inline stylesheet link");
   testRuleViewLinkLabel(view);
   clickLinkByIndex(view, 3);
   let editor = yield onSelected;
 
   is(toolbox.currentToolId, "styleeditor",
     "The style editor is selected again");
-  validateStyleEditorSheet(editor, 1);
+  yield validateStyleEditorSheet(editor, 1, testActor);
 }
 
-function* testExternalStyleSheet(view, toolbox) {
+function* testExternalStyleSheet(view, toolbox, testActor) {
   info("Testing external stylesheet");
 
   info("Waiting for the stylesheet editor to be selected");
   let panel = toolbox.getCurrentPanel();
   let onSelected = panel.UI.once("editor-selected");
 
   info("Switching back to the inspector panel in the toolbox");
   yield toolbox.selectTool("inspector");
 
   info("Clicking on an external stylesheet link");
   testRuleViewLinkLabel(view);
   clickLinkByIndex(view, 1);
   let editor = yield onSelected;
 
   is(toolbox.currentToolId, "styleeditor",
     "The style editor is selected again");
-  validateStyleEditorSheet(editor, 2);
+  yield validateStyleEditorSheet(editor, 2, testActor);
 }
 
-function validateStyleEditorSheet(editor, expectedSheetIndex) {
+function* validateStyleEditorSheet(editor, expectedSheetIndex, testActor) {
   info("validating style editor stylesheet");
   is(editor.styleSheet.styleSheetIndex, expectedSheetIndex,
      "loaded stylesheet index matches document stylesheet");
 
-  let sheet = content.document.styleSheets[expectedSheetIndex];
-  is(editor.styleSheet.href, sheet.href,
-    "loaded stylesheet href matches document stylesheet");
+  let href = editor.styleSheet.href || editor.styleSheet.nodeHref;
+
+  let expectedHref = yield testActor.eval(
+    `content.document.styleSheets[${expectedSheetIndex}].href ||
+     content.document.location.href`);
+
+  is(href, expectedHref, "loaded stylesheet href matches document stylesheet");
 }
 
 function testRuleViewLinkLabel(view) {
   let link = getRuleViewLinkByIndex(view, 2);
   let labelElem = link.querySelector(".ruleview-rule-source-label");
   let value = labelElem.getAttribute("value");
   let tooltipText = labelElem.getAttribute("tooltiptext");
 
--- a/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js
+++ b/devtools/client/inspector/rules/test/browser_rules_user-agent-styles.js
@@ -105,27 +105,30 @@ function* userAgentStylesVisible(inspect
     yield compareAppliedStylesWithUI(inspector, view, "ua");
 
     userRules = view._elementStyle.rules.filter(rule=>rule.editor.isEditable);
     uaRules = view._elementStyle.rules.filter(rule=>!rule.editor.isEditable);
     is(userRules.length, data.numUserRules, "Correct number of user rules");
     ok(uaRules.length > data.numUARules, "Has UA rules");
   }
 
-  ok(userRules.some(rule=> rule.matchedSelectors.length === 1),
+  ok(userRules.some(rule => rule.matchedSelectors.length === 1),
     "There is an inline style for element in user styles");
 
   // These tests rely on the "a" selector being the last test in
   // TEST_DATA.
-  ok(uaRules.some(rule=> rule.matchedSelectors.indexOf(":-moz-any-link") !== -1),
-    "There is a rule for :-moz-any-link");
-  ok(uaRules.some(rule=> rule.matchedSelectors.indexOf("*|*:link") !== -1),
-    "There is a rule for *|*:link");
-  ok(uaRules.some(rule=> rule.matchedSelectors.length === 1),
-    "Inline styles for ua styles");
+  ok(uaRules.some(rule => {
+    return rule.matchedSelectors.indexOf(":-moz-any-link") !== -1;
+  }), "There is a rule for :-moz-any-link");
+  ok(uaRules.some(rule => {
+    return rule.matchedSelectors.indexOf("*|*:link") !== -1;
+  }), "There is a rule for *|*:link");
+  ok(uaRules.some(rule => {
+    return rule.matchedSelectors.length === 1;
+  }), "Inline styles for ua styles");
 }
 
 function* userAgentStylesNotVisible(inspector, view) {
   info("Making sure that user agent styles are not currently visible");
 
   let userRules;
   let uaRules;
 
@@ -139,21 +142,23 @@ function* userAgentStylesNotVisible(insp
     is(uaRules.length, data.numUARules, "No UA rules");
   }
 }
 
 function* compareAppliedStylesWithUI(inspector, view, filter) {
   info("Making sure that UI is consistent with pageStyle.getApplied");
 
   let entries = yield inspector.pageStyle.getApplied(
-    inspector.selection.nodeFront, {
-    inherited: true,
-    matchedSelectors: true,
-    filter: filter
-  });
+    inspector.selection.nodeFront,
+    {
+      inherited: true,
+      matchedSelectors: true,
+      filter: filter
+    }
+  );
 
   // We may see multiple entries that map to a given rule; filter the
   // duplicates here to match what the UI does.
   let entryMap = new Map();
   for (let entry of entries) {
     entryMap.set(entry.rule, entry);
   }
   entries = [...entryMap.values()];
--- a/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
+++ b/devtools/client/inspector/rules/test/browser_rules_user-property-reset.js
@@ -9,28 +9,28 @@
 
 const TEST_URI = `
   <p id='id1' style='width:200px;'>element 1</p>
   <p id='id2' style='width:100px;'>element 2</p>
 `;
 
 add_task(function*() {
   yield addTab("data:text/html;charset=utf-8," + encodeURIComponent(TEST_URI));
-  let {inspector, view} = yield openRuleView();
+  let {inspector, view, testActor} = yield openRuleView();
 
   yield selectNode("#id1", inspector);
   yield modifyRuleViewWidth("300px", view, inspector);
   yield assertRuleAndMarkupViewWidth("id1", "300px", view, inspector);
 
   yield selectNode("#id2", inspector);
   yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector);
   yield modifyRuleViewWidth("50px", view, inspector);
   yield assertRuleAndMarkupViewWidth("id2", "50px", view, inspector);
 
-  yield reloadPage(inspector);
+  yield reloadPage(inspector, testActor);
 
   yield selectNode("#id1", inspector);
   yield assertRuleAndMarkupViewWidth("id1", "200px", view, inspector);
   yield selectNode("#id2", inspector);
   yield assertRuleAndMarkupViewWidth("id2", "100px", view, inspector);
 });
 
 function getStyleRule(ruleView) {
--- a/devtools/client/inspector/rules/test/doc_author-sheet.html
+++ b/devtools/client/inspector/rules/test/doc_author-sheet.html
@@ -6,26 +6,26 @@
 
   <style>
   pre a {
     color: orange;
   }
   </style>
 
   <script>
-var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
-  .getService(SpecialPowers.Ci.nsIIOService)
+    "use strict";
+    var gIOService = SpecialPowers.Cc["@mozilla.org/network/io-service;1"]
+                                  .getService(SpecialPowers.Ci.nsIIOService);
 
-var style = "data:text/css,a { background-color: seagreen; }";
-var uri = gIOService.newURI(style, null, null);
-var windowUtils = SpecialPowers.wrap(window)
-    .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
-    .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
-windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
-
+    var style = "data:text/css,a { background-color: seagreen; }";
+    var uri = gIOService.newURI(style, null, null);
+    var windowUtils = SpecialPowers.wrap(window)
+        .QueryInterface(SpecialPowers.Ci.nsIInterfaceRequestor)
+        .getInterface(SpecialPowers.Ci.nsIDOMWindowUtils);
+    windowUtils.loadSheet(uri, windowUtils.AUTHOR_SHEET);
   </script>
 
 </head>
 <body>
   <input type=text placeholder=test></input>
   <input type=color></input>
   <input type=range></input>
   <input type=number></input>
--- a/devtools/client/inspector/rules/test/doc_content_stylesheet.html
+++ b/devtools/client/inspector/rules/test/doc_content_stylesheet.html
@@ -1,22 +1,24 @@
 <html>
 <head>
   <title>test</title>
 
   <link href="./doc_content_stylesheet_linked.css" rel="stylesheet" type="text/css">
 
   <script>
+    /* eslint no-unused-vars: [2, {"vars": "local"}] */
+    "use strict";
     // Load script.css
     function loadCSS() {
-      var link = document.createElement('link');
-      link.rel = 'stylesheet';
-      link.type = 'text/css';
+      let link = document.createElement("link");
+      link.rel = "stylesheet";
+      link.type = "text/css";
       link.href = "./doc_content_stylesheet_script.css";
-      document.getElementsByTagName('head')[0].appendChild(link);
+      document.getElementsByTagName("head")[0].appendChild(link);
     }
   </script>
 
   <style>
     table {
       border: 1px solid #000;
     }
   </style>
--- a/devtools/client/inspector/rules/test/doc_cssom.html
+++ b/devtools/client/inspector/rules/test/doc_cssom.html
@@ -1,15 +1,16 @@
 <!-- Any copyright is dedicated to the Public Domain.
      http://creativecommons.org/publicdomain/zero/1.0/ -->
 <html>
 <head>
   <title>CSSOM test</title>
 
   <script>
+    "use strict";
     window.onload = function() {
       let x = document.styleSheets[0];
       x.insertRule("div { color: seagreen; }", 1);
     };
   </script>
 
   <style>
     span { }
--- a/devtools/client/inspector/rules/test/doc_frame_script.js
+++ b/devtools/client/inspector/rules/test/doc_frame_script.js
@@ -1,26 +1,27 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+/* globals addMessageListener, sendAsyncMessage */
 
 "use strict";
 
 // A helper frame-script for brower/devtools/styleinspector tests.
 //
 // Most listeners in the script expect "Test:"-namespaced messages from chrome,
 // then execute code upon receiving, and immediately send back a message.
 // This is so that chrome test code can execute code in content and wait for a
 // response this way:
-// let response = yield executeInContent(browser, "Test:MessageName", data, true);
-// The response message should have the same name "Test:MessageName"
+// 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, results: Cr} = Components;
+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 promise = require("promise");
 
 /**
  * Get a value for a given property name in a css rule in a stylesheet, given
  * their indexes
@@ -80,17 +81,18 @@ addMessageListener("Test:GetStyleSheetsI
  * - {String} selector: The selector used to obtain the element.
  * - {String} pseudo: pseudo id to query, or null.
  * - {String} name: name of the property
  * @return {String} The value, if found, null otherwise
  */
 addMessageListener("Test:GetComputedStylePropertyValue", function(msg) {
   let {selector, pseudo, name} = msg.data;
   let element = content.document.querySelector(selector);
-  let value = content.document.defaultView.getComputedStyle(element, pseudo).getPropertyValue(name);
+  let value = content.document.defaultView.getComputedStyle(element, pseudo)
+                                          .getPropertyValue(name);
   sendAsyncMessage("Test:GetComputedStylePropertyValue", value);
 });
 
 /**
  * Wait the property value from the computed style for an element and
  * compare it with the expected value
  * @param {Object} data Expects a data object with the following properties
  * - {String} selector: The selector used to obtain the element.
@@ -103,39 +105,36 @@ addMessageListener("Test:WaitForComputed
   let element = content.document.querySelector(selector);
   waitForSuccess(() => {
     let value = content.document.defaultView.getComputedStyle(element, pseudo)
                                             .getPropertyValue(name);
 
     return value === expected;
   }).then(() => {
     sendAsyncMessage("Test:WaitForComputedStylePropertyValue");
-  })
+  });
 });
 
-
 var dumpn = msg => dump(msg + "\n");
 
 /**
  * Polls a given function waiting for it to return true.
  *
  * @param {Function} validatorFn A validator function that returns a boolean.
  * This is called every few milliseconds to check if the result is true. When
  * it is true, the promise resolves.
- * @param {String} name Optional name of the test. This is used to generate
- * the success and failure messages.
  * @return a promise that resolves when the function returned true or rejects
  * if the timeout is reached
  */
-function waitForSuccess(validatorFn, name="untitled") {
+function waitForSuccess(validatorFn) {
   let def = promise.defer();
 
-  function wait(validatorFn) {
-    if (validatorFn()) {
+  function wait(fn) {
+    if (fn()) {
       def.resolve();
     } else {
-      setTimeout(() => wait(validatorFn), 200);
+      setTimeout(() => wait(fn), 200);
     }
   }
   wait(validatorFn);
 
   return def.promise;
 }
--- a/devtools/client/inspector/rules/test/head.js
+++ b/devtools/client/inspector/rules/test/head.js
@@ -1,23 +1,24 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../test/head.js */
 "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);
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.defaultColorUnit");
 });
 
-var {CssLogic} = require("devtools/shared/inspector/css-logic");
 var {getInplaceEditorForSpan: inplaceEditor} =
   require("devtools/client/shared/inplace-editor");
 
 const ROOT_TEST_DIR = getRootDirectory(gTestPath);
 const FRAME_SCRIPT_URL = ROOT_TEST_DIR + "doc_frame_script.js";
 
 registerCleanupFunction(() => {
   Services.prefs.clearUserPref("devtools.defaultColorUnit");
@@ -33,51 +34,37 @@ registerCleanupFunction(() => {
 var _addTab = addTab;
 addTab = function(url) {
   return _addTab(url).then(tab => {
     info("Loading the helper frame script " + FRAME_SCRIPT_URL);
     let browser = tab.linkedBrowser;
     browser.messageManager.loadFrameScript(FRAME_SCRIPT_URL, false);
     return tab;
   });
-}
+};
 
 /**
  * Open the toolbox, with the inspector tool visible, and the rule-view
  * sidebar tab selected.
  *
  * @return a promise that resolves when the inspector is ready and the rule
  * view is visible and ready
  */
 function openRuleView() {
-  return openInspectorSidebarTab("ruleview").then(({toolbox, inspector}) => {
+  return openInspectorSidebarTab("ruleview").then(data => {
     return {
-      toolbox,
-      inspector,
-      view: inspector.ruleview.view
+      toolbox: data.toolbox,
+      inspector: data.inspector,
+      testActor: data.testActor,
+      view: data.inspector.ruleview.view
     };
   });
 }
 
 /**
- * Simple DOM node accesor function that takes either a node or a string css
- * selector as argument and returns the corresponding node
- *
- * @param {String|DOMNode} nodeOrSelector
- * @return {DOMNode|CPOW} Note that in e10s mode a CPOW object is returned which
- * doesn't implement *all* of the DOMNode's properties
- */
-function getNode(nodeOrSelector) {
-  info("Getting the node for '" + nodeOrSelector + "'");
-  return typeof nodeOrSelector === "string" ?
-    content.document.querySelector(nodeOrSelector) :
-    nodeOrSelector;
-}
-
-/**
  * Set the inspector's current selection to null so that no node is selected
  *
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently loaded in the toolbox
  * @return a promise that resolves when the inspector is updated
  */
 function clearCurrentNodeSelection(inspector) {
   info("Clearing the current selection");
@@ -120,30 +107,16 @@ function waitForNEvents(target, eventNam
       break;
     }
   }
 
   return deferred.promise;
 }
 
 /**
- * This shouldn't be used in the tests, but is useful when writing new tests or
- * debugging existing tests in order to introduce delays in the test steps
- *
- * @param {Number} ms
- *        The time to wait
- * @return A promise that resolves when the time is passed
- */
-function wait(ms) {
-  let def = promise.defer();
-  content.setTimeout(def.resolve, ms);
-  return def.promise;
-}
-
-/**
  * Wait for a content -> chrome message on the message manager (the window
  * messagemanager is used).
  *
  * @param {String} name
  *        The message name
  * @return {Promise} A promise that resolves to the response data when the
  * message has been received
  */
@@ -172,17 +145,18 @@ function waitForContentMessage(name) {
  * @param {Object} objects
  *        Optional CPOW objects to send along
  * @param {Boolean} expectResponse
  *        If set to false, don't wait for a response with the same name
  *        from the content script. Defaults to true.
  * @return {Promise} Resolves to the response data if a response is expected,
  * immediately resolves otherwise
  */
-function executeInContent(name, data={}, objects={}, expectResponse=true) {
+function executeInContent(name, data = {}, objects = {},
+                          expectResponse = true) {
   info("Sending message " + name + " to content");
   let mm = gBrowser.selectedBrowser.messageManager;
 
   mm.sendAsyncMessage(name, data, objects);
   if (expectResponse) {
     return waitForContentMessage(name);
   }
 
@@ -203,16 +177,31 @@ function executeInContent(name, data={},
 function* getComputedStyleProperty(selector, pseudo, propName) {
   return yield executeInContent("Test:GetComputedStylePropertyValue",
                                 {selector,
                                 pseudo,
                                 name: propName});
 }
 
 /**
+ * Get an element's inline style property value.
+ * @param {TestActor} testActor
+ * @param {String} selector
+ *        The selector used to obtain the element.
+ * @param {String} name
+ *        name of the property.
+ */
+function getStyle(testActor, selector, propName) {
+  return testActor.eval(`
+    content.document.querySelector("${selector}")
+                    .style.getPropertyValue("${propName}");
+  `);
+}
+
+/**
  * Send an async message to the frame script and wait until the requested
  * computed style property has the expected value.
  *
  * @param {String} selector
  *        The selector used to obtain the element.
  * @param {String} pseudo
  *        pseudo id to query, or null.
  * @param {String} prop
@@ -231,18 +220,18 @@ function* waitForComputedStyleProperty(s
 }
 
 /**
  * Given an inplace editable element, click to switch it to edit mode, wait for
  * focus
  *
  * @return a promise that resolves to the inplace-editor element when ready
  */
-var focusEditableField = Task.async(function*(ruleView, editable, xOffset=1,
-    yOffset=1, options={}) {
+var focusEditableField = Task.async(function*(ruleView, editable, xOffset = 1,
+    yOffset = 1, options = {}) {
   let onFocus = once(editable.parentNode, "focus", true);
   info("Clicking on editable field to turn to edit mode");
   EventUtils.synthesizeMouse(editable, xOffset, yOffset, options,
     editable.ownerDocument.defaultView);
   yield onFocus;
 
   info("Editable field gained focus, returning the input field now");
   let onEdit = inplaceEditor(editable.ownerDocument.activeElement);
@@ -307,61 +296,44 @@ var waitForTab = Task.async(function*() 
   let tab = gBrowser.selectedTab;
   let browser = tab.linkedBrowser;
   yield once(browser, "load", true);
   info("The tab load completed");
   return tab;
 });
 
 /**
- * Polls a given function waiting for it to return true.
+ * Polls a given generator function waiting for it to return true.
  *
  * @param {Function} validatorFn
- *        A validator function that returns a boolean.
+ *        A validator generator function that returns a boolean.
  *        This is called every few milliseconds to check if the result is true.
  *        When it is true, the promise resolves.
  * @param {String} name
  *        Optional name of the test. This is used to generate
  *        the success and failure messages.
  * @return a promise that resolves when the function returned true or rejects
  * if the timeout is reached
  */
-function waitForSuccess(validatorFn, name="untitled") {
-  let def = promise.defer();
-
-  function wait(validator) {
-    if (validator()) {
-      ok(true, "Validator function " + name + " returned true");
-      def.resolve();
-    } else {
-      setTimeout(() => wait(validator), 200);
+var waitForSuccess = Task.async(function*(validatorFn, desc = "untitled") {
+  let i = 0;
+  while (true) {
+    info("Checking: " + desc);
+    if (yield validatorFn()) {
+      ok(true, "Success: " + desc);
+      break;
     }
+    i++;
+    if (i > 10) {
+      ok(false, "Failure: " + desc);
+      break;
+    }
+    yield new Promise(r => setTimeout(r, 200));
   }
-  wait(validatorFn);
-
-  return def.promise;
-}
-
-/**
- * Create a new style tag containing the given style text and append it to the
- * document's head node
- *
- * @param {Document} doc
- * @param {String} style
- * @return {DOMNode} The newly created style node
- */
-function addStyle(doc, style) {
-  info("Adding a new style tag to the document with style content: " +
-    style.substring(0, 50));
-  let node = doc.createElement("style");
-  node.setAttribute("type", "text/css");
-  node.textContent = style;
-  doc.getElementsByTagName("head")[0].appendChild(node);
-  return node;
-}
+});
 
 /**
  * Get the dataURL for the font family tooltip.
  *
  * @param {String} font
  *        The font family value.
  * @param {object} nodeFront
  *        The NodeActor that will used to retrieve the dataURL for the
@@ -490,27 +462,29 @@ function getRuleViewSelector(view, selec
  */
 function getRuleViewSelectorHighlighterIcon(view, selectorText) {
   let rule = getRuleViewRule(view, selectorText);
   return rule.querySelector(".ruleview-selectorhighlighter");
 }
 
 /**
  * Simulate a color change in a given color picker tooltip, and optionally wait
- * for a given element in the page to have its style changed as a result
+ * for a given element in the page to have its style changed as a result.
+ * Note that this function assumes that the colorpicker popup is already open
+ * and it won't close it after having selected the new color.
  *
  * @param {RuleView} ruleView
  *        The related rule view instance
  * @param {SwatchColorPickerTooltip} colorPicker
  * @param {Array} newRgba
  *        The new color to be set [r, g, b, a]
  * @param {Object} expectedChange
  *        Optional object that needs the following props:
- *          - {DOMNode} element The element in the page that will have its
- *            style changed.
+ *          - {String} selector The selector to the element in the page that
+ *            will have its style changed.
  *          - {String} name The style name that will be changed
  *          - {String} value The expected style value
  * The style will be checked like so: getComputedStyle(element)[name] === value
  */
 var simulateColorPickerChange = Task.async(function*(ruleView, colorPicker,
     newRgba, expectedChange) {
   let onRuleViewChanged = ruleView.once("ruleview-changed");
   info("Getting the spectrum colorpicker object");
@@ -520,24 +494,109 @@ var simulateColorPickerChange = Task.asy
   info("Applying the change");
   spectrum.updateUI();
   spectrum.onChange();
   info("Waiting for rule-view to update");
   yield onRuleViewChanged;
 
   if (expectedChange) {
     info("Waiting for the style to be applied on the page");
-    yield waitForSuccess(() => {
-      let {element, name, value} = expectedChange;
-      return content.getComputedStyle(element)[name] === value;
-    }, "Color picker change applied on the page");
+    let {selector, name, value} = expectedChange;
+    yield waitForComputedStyleProperty(selector, null, name, value);
   }
 });
 
 /**
+ * Open the color picker popup for a given property in a given rule and
+ * simulate a color change. Optionally wait for a given element in the page to
+ * have its style changed as a result.
+ *
+ * @param {RuleView} view
+ *        The related rule view instance
+ * @param {Number} ruleIndex
+ *        Which rule to target in the rule view
+ * @param {Number} propIndex
+ *        Which property to target in the rule
+ * @param {Array} newRgba
+ *        The new color to be set [r, g, b, a]
+ * @param {Object} expectedChange
+ *        Optional object that needs the following props:
+ *          - {String} selector The selector to the element in the page that
+ *            will have its style changed.
+ *          - {String} name The style name that will be changed
+ *          - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var openColorPickerAndSelectColor = Task.async(function*(view, ruleIndex,
+    propIndex, newRgba, expectedChange) {
+  let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+  let propEditor = ruleEditor.rule.textProps[propIndex].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-colorswatch");
+  let cPicker = view.tooltips.colorPicker;
+
+  info("Opening the colorpicker by clicking the color swatch");
+  let onShown = cPicker.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
+
+  yield simulateColorPickerChange(view, cPicker, newRgba, expectedChange);
+
+  return {propEditor, swatch, cPicker};
+});
+
+/**
+ * Open the cubicbezier popup for a given property in a given rule and
+ * simulate a curve change. Optionally wait for a given element in the page to
+ * have its style changed as a result.
+ *
+ * @param {RuleView} view
+ *        The related rule view instance
+ * @param {Number} ruleIndex
+ *        Which rule to target in the rule view
+ * @param {Number} propIndex
+ *        Which property to target in the rule
+ * @param {Array} coords
+ *        The new coordinates to be used, e.g. [0.1, 2, 0.9, -1]
+ * @param {Object} expectedChange
+ *        Optional object that needs the following props:
+ *          - {String} selector The selector to the element in the page that
+ *            will have its style changed.
+ *          - {String} name The style name that will be changed
+ *          - {String} value The expected style value
+ * The style will be checked like so: getComputedStyle(element)[name] === value
+ */
+var openCubicBezierAndChangeCoords = Task.async(function*(view, ruleIndex,
+    propIndex, coords, expectedChange) {
+  let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+  let propEditor = ruleEditor.rule.textProps[propIndex].editor;
+  let swatch = propEditor.valueSpan.querySelector(".ruleview-bezierswatch");
+  let bezierTooltip = view.tooltips.cubicBezier;
+
+  info("Opening the cubicBezier by clicking the swatch");
+  let onShown = bezierTooltip.tooltip.once("shown");
+  swatch.click();
+  yield onShown;
+
+  let widget = yield bezierTooltip.widget;
+
+  info("Simulating a change of curve in the widget");
+  let onRuleViewChanged = view.once("ruleview-changed");
+  widget.coordinates = coords;
+  yield onRuleViewChanged;
+
+  if (expectedChange) {
+    info("Waiting for the style to be applied on the page");
+    let {selector, name, value} = expectedChange;
+    yield waitForComputedStyleProperty(selector, null, name, value);
+  }
+
+  return {propEditor, swatch, bezierTooltip};
+});
+
+/**
  * Get a rule-link from the rule-view given its index
  *
  * @param {CssRuleView} view
  *        The instance of the rule-view panel
  * @param {Number} index
  *        The index of the link to get
  * @return {DOMNode} The link if any at this index
  */
@@ -573,16 +632,159 @@ function getRuleViewLinkTextByIndex(view
  */
 function getRuleViewRuleEditor(view, childrenIndex, nodeIndex) {
   return nodeIndex !== undefined ?
     view.element.children[childrenIndex].childNodes[nodeIndex]._ruleEditor :
     view.element.children[childrenIndex]._ruleEditor;
 }
 
 /**
+ * Simulate adding a new property in an existing rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @param {Number} ruleIndex
+ *        The index of the rule to use. Note that if ruleIndex is 0, you might
+ *        want to also listen to markupmutation events in your test since
+ *        that's going to change the style attribute of the selected node.
+ * @param {String} name
+ *        The name for the new property
+ * @param {String} value
+ *        The value for the new property
+ * @param {String} commitValueWith
+ *        Which key should be used to commit the new value. VK_RETURN is used by
+ *        default, but tests might want to use another key to test cancelling
+ *        for exemple.
+ * @param {Boolean} blurNewProperty
+ *        After the new value has been added, a new property would have been
+ *        focused. This parameter is true by default, and that causes the new
+ *        property to be blurred. Set to false if you don't want this.
+ * @return {TextProperty} The instance of the TextProperty that was added
+ */
+var addProperty = Task.async(function*(view, ruleIndex, name, value,
+                                       commitValueWith = "VK_RETURN",
+                                       blurNewProperty = true) {
+  info("Adding new property " + name + ":" + value + " to rule " + ruleIndex);
+
+  let ruleEditor = getRuleViewRuleEditor(view, ruleIndex);
+  let editor = yield focusNewRuleViewProperty(ruleEditor);
+  let numOfProps = ruleEditor.rule.textProps.length;
+
+  info("Adding name " + name);
+  editor.input.value = name;
+  let onNameAdded = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onNameAdded;
+
+  // Focus has moved to the value inplace-editor automatically.
+  editor = inplaceEditor(view.styleDocument.activeElement);
+  let textProps = ruleEditor.rule.textProps;
+  let textProp = textProps[textProps.length - 1];
+
+  is(ruleEditor.rule.textProps.length, numOfProps + 1,
+     "A new test property was added");
+  is(editor, inplaceEditor(textProp.editor.valueSpan),
+     "The inplace editor appeared for the value");
+
+  info("Adding value " + value);
+  // Setting the input value schedules a preview to be shown in 10ms which
+  // triggers a ruleview-changed event (see bug 1209295).
+  let onPreview = view.once("ruleview-changed");
+  editor.input.value = value;
+  yield onPreview;
+
+  let onValueAdded = view.once("ruleview-changed");
+  EventUtils.synthesizeKey(commitValueWith, {}, view.styleWindow);
+  yield onValueAdded;
+
+  if (blurNewProperty) {
+    view.styleDocument.activeElement.blur();
+  }
+
+  return textProp;
+});
+
+/**
+ * Simulate changing the value of a property in a rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ *        The instance of the TextProperty to be changed
+ * @param {String} value
+ *        The new value to be used. If null is passed, then the value will be
+ *        deleted
+ * @param {Boolean} blurNewProperty
+ *        After the value has been changed, a new property would have been
+ *        focused. This parameter is true by default, and that causes the new
+ *        property to be blurred. Set to false if you don't want this.
+ */
+var setProperty = Task.async(function*(view, textProp, value,
+                                       blurNewProperty = true) {
+  yield focusEditableField(view, textProp.editor.valueSpan);
+
+  let onPreview = view.once("ruleview-changed");
+  if (value === null) {
+    EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+  } else {
+    EventUtils.sendString(value, view.styleWindow);
+  }
+  yield onPreview;
+
+  let onValueDone = view.once("ruleview-changed");
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onValueDone;
+
+  if (blurNewProperty) {
+    view.styleDocument.activeElement.blur();
+  }
+});
+
+/**
+ * Simulate removing a property from an existing rule in the rule-view.
+ *
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ *        The instance of the TextProperty to be removed
+ * @param {Boolean} blurNewProperty
+ *        After the property has been removed, a new property would have been
+ *        focused. This parameter is true by default, and that causes the new
+ *        property to be blurred. Set to false if you don't want this.
+ */
+var removeProperty = Task.async(function*(view, textProp,
+                                          blurNewProperty = true) {
+  yield focusEditableField(view, textProp.editor.nameSpan);
+
+  let onModifications = view.once("ruleview-changed");
+  info("Deleting the property name now");
+  EventUtils.synthesizeKey("VK_DELETE", {}, view.styleWindow);
+  EventUtils.synthesizeKey("VK_RETURN", {}, view.styleWindow);
+  yield onModifications;
+
+  if (blurNewProperty) {
+    view.styleDocument.activeElement.blur();
+  }
+});
+
+/**
+ * Simulate clicking the enable/disable checkbox next to a property in a rule.
+ *
+ * @param {CssRuleView} view
+ *        The instance of the rule-view panel
+ * @param {TextProperty} textProp
+ *        The instance of the TextProperty to be enabled/disabled
+ */
+var togglePropStatus = Task.async(function*(view, textProp) {
+  let onRuleViewRefreshed = view.once("ruleview-changed");
+  textProp.editor.enable.click();
+  yield onRuleViewRefreshed;
+});
+
+/**
  * Click on a rule-view's close brace to focus a new property name editor
  *
  * @param {RuleEditor} ruleEditor
  *        An instance of RuleEditor that will receive the new property
  * @return a promise that resolves to the newly created editor when ready and
  * focused
  */
 var focusNewRuleViewProperty = Task.async(function*(ruleEditor) {
@@ -644,25 +846,24 @@ var setSearchFilter = Task.async(functio
 });
 
 /**
  * Reload the current page and wait for the inspector to be initialized after
  * the navigation
  *
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently loaded in the toolbox
- * @return a promise that resolves after page reload and inspector
- * initialization
+ * @param {TestActor} testActor
+ *        The current instance of the TestActor
  */
-function reloadPage(inspector) {
+function* reloadPage(inspector, testActor) {
   let onNewRoot = inspector.once("new-root");
-  content.location.reload();
-  return onNewRoot.then(() => {
-    inspector.markup._waitForChildren();
-  });
+  yield testActor.eval("content.location.reload();");
+  yield onNewRoot;
+  yield inspector.markup._waitForChildren();
 }
 
 /**
  * Create a new rule by clicking on the "add rule" button.
  *
  * @param {InspectorPanel} inspector
  *        The instance of InspectorPanel currently loaded in the toolbox
  * @param {CssRuleView} view
--- a/devtools/client/inspector/test/head.js
+++ b/devtools/client/inspector/test/head.js
@@ -1,12 +1,14 @@
 /* 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/. */
+/* eslint no-unused-vars: [2, {"vars": "local"}] */
+/* import-globals-from ../../framework/test/shared-head.js */
 "use strict";
 
 // Load the shared-head file first.
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/shared-head.js",
   this);
 
 // Services.prefs.setBoolPref("devtools.debugger.log", true);
--- a/devtools/client/sourceeditor/editor.js
+++ b/devtools/client/sourceeditor/editor.js
@@ -297,16 +297,40 @@ Editor.prototype = {
 
       win.CodeMirror.commands.save = () => this.emit("saveRequested");
 
       // Create a CodeMirror instance add support for context menus,
       // overwrite the default controller (otherwise items in the top and
       // context menus won't work).
 
       cm = win.CodeMirror(win.document.body, this.config);
+
+      // Disable APZ for source editors. It currently causes the line numbers to
+      // "tear off" and swim around on top of the content. Bug 1160601 tracks
+      // finding a solution that allows APZ to work with CodeMirror.
+      cm.getScrollerElement().addEventListener("wheel", ev => {
+        // By handling the wheel events ourselves, we force the platform to
+        // scroll synchronously, like it did before APZ. However, we lose smooth
+        // scrolling for users with mouse wheels. This seems acceptible vs.
+        // doing nothing and letting the gutter slide around.
+        ev.preventDefault();
+
+        let { deltaX, deltaY } = ev;
+
+        if (ev.deltaMode == ev.DOM_DELTA_LINE) {
+          deltaX *= cm.defaultCharWidth();
+          deltaY *= cm.defaultTextHeight();
+        } else if (ev.deltaMode == ev.DOM_DELTA_PAGE) {
+          deltaX *= cm.getWrapperElement().clientWidth;
+          deltaY *= cm.getWrapperElement().clientHeight;
+        }
+
+        cm.getScrollerElement().scrollBy(deltaX, deltaY);
+      });
+
       cm.getWrapperElement().addEventListener("contextmenu", ev => {
         ev.preventDefault();
 
         if (!this.config.contextMenu) {
           return;
         }
 
         let popup = this.config.contextMenu;
new file mode 100644
--- /dev/null
+++ b/devtools/server/actors/environment.js
@@ -0,0 +1,214 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2; js-indent-level: 2 -*- */
+/* vim: set ft=javascript ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { createValueGrip } = require("devtools/server/actors/object");
+
+/**
+ * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
+ * the bindings introduced by a lexical environment and assigning new values to
+ * those identifier bindings.
+ *
+ * @param Debugger.Environment aEnvironment
+ *        The lexical environment that will be used to create the actor.
+ * @param ThreadActor aThreadActor
+ *        The parent thread actor that contains this environment.
+ */
+function EnvironmentActor(aEnvironment, aThreadActor)
+{
+  this.obj = aEnvironment;
+  this.threadActor = aThreadActor;
+}
+
+EnvironmentActor.prototype = {
+  actorPrefix: "environment",
+
+  /**
+   * Return an environment form for use in a protocol message.
+   */
+  form: function () {
+    let form = { actor: this.actorID };
+
+    // What is this environment's type?
+    if (this.obj.type == "declarative") {
+      form.type = this.obj.callee ? "function" : "block";
+    } else {
+      form.type = this.obj.type;
+    }
+
+    // Does this environment have a parent?
+    if (this.obj.parent) {
+      form.parent = (this.threadActor
+                     .createEnvironmentActor(this.obj.parent,
+                                             this.registeredPool)
+                     .form());
+    }
+
+    // Does this environment reflect the properties of an object as variables?
+    if (this.obj.type == "object" || this.obj.type == "with") {
+      form.object = createValueGrip(this.obj.object,
+        this.registeredPool, this.threadActor.objectGrip);
+    }
+
+    // Is this the environment created for a function call?
+    if (this.obj.callee) {
+      form.function = createValueGrip(this.obj.callee,
+        this.registeredPool, this.threadActor.objectGrip);
+    }
+
+    // Shall we list this environment's bindings?
+    if (this.obj.type == "declarative") {
+      form.bindings = this._bindings();
+    }
+
+    return form;
+  },
+
+  /**
+   * Return the identifier bindings object as required by the remote protocol
+   * specification.
+   */
+  _bindings: function () {
+    let bindings = { arguments: [], variables: {} };
+
+    // TODO: this part should be removed in favor of the commented-out part
+    // below when getVariableDescriptor lands (bug 725815).
+    if (typeof this.obj.getVariable != "function") {
+    //if (typeof this.obj.getVariableDescriptor != "function") {
+      return bindings;
+    }
+
+    let parameterNames;
+    if (this.obj.callee) {
+      parameterNames = this.obj.callee.parameterNames;
+    } else {
+      parameterNames = [];
+    }
+    for (let name of parameterNames) {
+      let arg = {};
+      let value = this.obj.getVariable(name);
+
+      // TODO: this part should be removed in favor of the commented-out part
+      // below when getVariableDescriptor lands (bug 725815).
+      let desc = {
+        value: value,
+        configurable: false,
+        writable: !(value && value.optimizedOut),
+        enumerable: true
+      };
+
+      // let desc = this.obj.getVariableDescriptor(name);
+      let descForm = {
+        enumerable: true,
+        configurable: desc.configurable
+      };
+      if ("value" in desc) {
+        descForm.value = createValueGrip(desc.value,
+          this.registeredPool, this.threadActor.objectGrip);
+        descForm.writable = desc.writable;
+      } else {
+        descForm.get = createValueGrip(desc.get, this.registeredPool,
+          this.threadActor.objectGrip);
+        descForm.set = createValueGrip(desc.set, this.registeredPool,
+          this.threadActor.objectGrip);
+      }
+      arg[name] = descForm;
+      bindings.arguments.push(arg);
+    }
+
+    for (let name of this.obj.names()) {
+      if (bindings.arguments.some(function exists(element) {
+                                    return !!element[name];
+                                  })) {
+        continue;
+      }
+
+      let value = this.obj.getVariable(name);
+
+      // TODO: this part should be removed in favor of the commented-out part
+      // below when getVariableDescriptor lands.
+      let desc = {
+        value: value,
+        configurable: false,
+        writable: !(value &&
+                    (value.optimizedOut ||
+                     value.uninitialized ||
+                     value.missingArguments)),
+        enumerable: true
+      };
+
+      //let desc = this.obj.getVariableDescriptor(name);
+      let descForm = {
+        enumerable: true,
+        configurable: desc.configurable
+      };
+      if ("value" in desc) {
+        descForm.value = createValueGrip(desc.value,
+          this.registeredPool, this.threadActor.objectGrip);
+        descForm.writable = desc.writable;
+      } else {
+        descForm.get = createValueGrip(desc.get || undefined,
+          this.registeredPool, this.threadActor.objectGrip);
+        descForm.set = createValueGrip(desc.set || undefined,
+          this.registeredPool, this.threadActor.objectGrip);
+      }
+      bindings.variables[name] = descForm;
+    }
+
+    return bindings;
+  },
+
+  /**
+   * Handle a protocol request to change the value of a variable bound in this
+   * lexical environment.
+   *
+   * @param aRequest object
+   *        The protocol request object.
+   */
+  onAssign: function (aRequest) {
+    // TODO: enable the commented-out part when getVariableDescriptor lands
+    // (bug 725815).
+    /*let desc = this.obj.getVariableDescriptor(aRequest.name);
+
+    if (!desc.writable) {
+      return { error: "immutableBinding",
+               message: "Changing the value of an immutable binding is not " +
+                        "allowed" };
+    }*/
+
+    try {
+      this.obj.setVariable(aRequest.name, aRequest.value);
+    } catch (e) {
+      if (e instanceof Debugger.DebuggeeWouldRun) {
+        return { error: "threadWouldRun",
+                 cause: e.cause ? e.cause : "setter",
+                 message: "Assigning a value would cause the debuggee to run" };
+      } else {
+        throw e;
+      }
+    }
+    return { from: this.actorID };
+  },
+
+  /**
+   * Handle a protocol request to fully enumerate the bindings introduced by the
+   * lexical environment.
+   *
+   * @param aRequest object
+   *        The protocol request object.
+   */
+  onBindings: function (aRequest) {
+    return { from: this.actorID,
+             bindings: this._bindings() };
+  }
+};
+
+EnvironmentActor.prototype.requestTypes = {
+  "assign": EnvironmentActor.prototype.onAssign,
+  "bindings": EnvironmentActor.prototype.onBindings
+};
+
+exports.EnvironmentActor = EnvironmentActor;
--- a/devtools/server/actors/moz.build
+++ b/devtools/server/actors/moz.build
@@ -19,16 +19,17 @@ DevToolsModules(
     'child-process.js',
     'childtab.js',
     'chrome.js',
     'common.js',
     'csscoverage.js',
     'device.js',
     'director-manager.js',
     'director-registry.js',
+    'environment.js',
     'eventlooplag.js',
     'frame.js',
     'framerate.js',
     'gcli.js',
     'heap-snapshot-file.js',
     'highlighters.css',
     'highlighters.js',
     'inspector.js',
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const { Cc, Ci, Cu, Cr, components, ChromeWorker } = require("chrome");
 const { ActorPool, OriginalLocation, GeneratedLocation } = require("devtools/server/actors/common");
 const { BreakpointActor } = require("devtools/server/actors/breakpoint");
+const { EnvironmentActor } = require("devtools/server/actors/environment");
 const { FrameActor } = require("devtools/server/actors/frame");
 const { ObjectActor, createValueGrip, longStringGrip } = require("devtools/server/actors/object");
 const { DebuggerServer } = require("devtools/server/main");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 const { assert, dumpn, update, fetch } = DevToolsUtils;
 const { dirname, joinURI } = require("devtools/shared/path");
 const promise = require("promise");
 const PromiseDebugging = require("PromiseDebugging");
@@ -2976,222 +2977,16 @@ update(PauseScopedObjectActor.prototype,
     return {};
   }),
 });
 
 update(PauseScopedObjectActor.prototype.requestTypes, {
   "threadGrip": PauseScopedObjectActor.prototype.onThreadGrip,
 });
 
-/**
- * Creates an EnvironmentActor. EnvironmentActors are responsible for listing
- * the bindings introduced by a lexical environment and assigning new values to
- * those identifier bindings.
- *
- * @param Debugger.Environment aEnvironment
- *        The lexical environment that will be used to create the actor.
- * @param ThreadActor aThreadActor
- *        The parent thread actor that contains this environment.
- */
-function EnvironmentActor(aEnvironment, aThreadActor)
-{
-  this.obj = aEnvironment;
-  this.threadActor = aThreadActor;
-}
-
-EnvironmentActor.prototype = {
-  actorPrefix: "environment",
-
-  /**
-   * Return an environment form for use in a protocol message.
-   */
-  form: function () {
-    let form = { actor: this.actorID };
-
-    // What is this environment's type?
-    if (this.obj.type == "declarative") {
-      form.type = this.obj.callee ? "function" : "block";
-    } else {
-      form.type = this.obj.type;
-    }
-
-    // Does this environment have a parent?
-    if (this.obj.parent) {
-      form.parent = (this.threadActor
-                     .createEnvironmentActor(this.obj.parent,
-                                             this.registeredPool)
-                     .form());
-    }
-
-    // Does this environment reflect the properties of an object as variables?
-    if (this.obj.type == "object" || this.obj.type == "with") {
-      form.object = createValueGrip(this.obj.object,
-        this.registeredPool, this.threadActor.objectGrip);
-    }
-
-    // Is this the environment created for a function call?
-    if (this.obj.callee) {
-      form.function = createValueGrip(this.obj.callee,
-        this.registeredPool, this.threadActor.objectGrip);
-    }
-
-    // Shall we list this environment's bindings?
-    if (this.obj.type == "declarative") {
-      form.bindings = this._bindings();
-    }
-
-    return form;
-  },
-
-  /**
-   * Return the identifier bindings object as required by the remote protocol
-   * specification.
-   */
-  _bindings: function () {
-    let bindings = { arguments: [], variables: {} };
-
-    // TODO: this part should be removed in favor of the commented-out part
-    // below when getVariableDescriptor lands (bug 725815).
-    if (typeof this.obj.getVariable != "function") {
-    //if (typeof this.obj.getVariableDescriptor != "function") {
-      return bindings;
-    }
-
-    let parameterNames;
-    if (this.obj.callee) {
-      parameterNames = this.obj.callee.parameterNames;
-    } else {
-      parameterNames = [];
-    }
-    for (let name of parameterNames) {
-      let arg = {};
-      let value = this.obj.getVariable(name);
-
-      // TODO: this part should be removed in favor of the commented-out part
-      // below when getVariableDescriptor lands (bug 725815).
-      let desc = {
-        value: value,
-        configurable: false,
-        writable: !(value && value.optimizedOut),
-        enumerable: true
-      };
-
-      // let desc = this.obj.getVariableDescriptor(name);
-      let descForm = {
-        enumerable: true,
-        configurable: desc.configurable
-      };
-      if ("value" in desc) {
-        descForm.value = createValueGrip(desc.value,
-          this.registeredPool, this.threadActor.objectGrip);
-        descForm.writable = desc.writable;
-      } else {
-        descForm.get = createValueGrip(desc.get, this.registeredPool,
-          this.threadActor.objectGrip);
-        descForm.set = createValueGrip(desc.set, this.registeredPool,
-          this.threadActor.objectGrip);
-      }
-      arg[name] = descForm;
-      bindings.arguments.push(arg);
-    }
-
-    for (let name of this.obj.names()) {
-      if (bindings.arguments.some(function exists(element) {
-                                    return !!element[name];
-                                  })) {
-        continue;
-      }
-
-      let value = this.obj.getVariable(name);
-
-      // TODO: this part should be removed in favor of the commented-out part
-      // below when getVariableDescriptor lands.
-      let desc = {
-        value: value,
-        configurable: false,
-        writable: !(value &&
-                    (value.optimizedOut ||
-                     value.uninitialized ||
-                     value.missingArguments)),
-        enumerable: true
-      };
-
-      //let desc = this.obj.getVariableDescriptor(name);
-      let descForm = {
-        enumerable: true,
-        configurable: desc.configurable
-      };
-      if ("value" in desc) {
-        descForm.value = createValueGrip(desc.value,
-          this.registeredPool, this.threadActor.objectGrip);
-        descForm.writable = desc.writable;
-      } else {
-        descForm.get = createValueGrip(desc.get || undefined,
-          this.registeredPool, this.threadActor.objectGrip);
-        descForm.set = createValueGrip(desc.set || undefined,
-          this.registeredPool, this.threadActor.objectGrip);
-      }
-      bindings.variables[name] = descForm;
-    }
-
-    return bindings;
-  },
-
-  /**
-   * Handle a protocol request to change the value of a variable bound in this
-   * lexical environment.
-   *
-   * @param aRequest object
-   *        The protocol request object.
-   */
-  onAssign: function (aRequest) {
-    // TODO: enable the commented-out part when getVariableDescriptor lands
-    // (bug 725815).
-    /*let desc = this.obj.getVariableDescriptor(aRequest.name);
-
-    if (!desc.writable) {
-      return { error: "immutableBinding",
-               message: "Changing the value of an immutable binding is not " +
-                        "allowed" };
-    }*/
-
-    try {
-      this.obj.setVariable(aRequest.name, aRequest.value);
-    } catch (e) {
-      if (e instanceof Debugger.DebuggeeWouldRun) {
-        return { error: "threadWouldRun",
-                 cause: e.cause ? e.cause : "setter",
-                 message: "Assigning a value would cause the debuggee to run" };
-      } else {
-        throw e;
-      }
-    }
-    return { from: this.actorID };
-  },
-
-  /**
-   * Handle a protocol request to fully enumerate the bindings introduced by the
-   * lexical environment.
-   *
-   * @param aRequest object
-   *        The protocol request object.
-   */
-  onBindings: function (aRequest) {
-    return { from: this.actorID,
-             bindings: this._bindings() };
-  }
-};
-
-EnvironmentActor.prototype.requestTypes = {
-  "assign": EnvironmentActor.prototype.onAssign,
-  "bindings": EnvironmentActor.prototype.onBindings
-};
-
-exports.EnvironmentActor = EnvironmentActor;
-
 function hackDebugger(Debugger) {
   // TODO: Improve native code instead of hacking on top of it
 
   /**
    * Override the toString method in order to get more meaningful script output
    * for debugging the debugger.
    */
   Debugger.Script.prototype.toString = function() {
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -4,17 +4,18 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Services = require("Services");
 const { Cc, Ci, Cu } = require("chrome");
 const { DebuggerServer, ActorPool } = require("devtools/server/main");
-const { EnvironmentActor, ThreadActor } = require("devtools/server/actors/script");
+const { EnvironmentActor } = require("devtools/server/actors/environment");
+const { ThreadActor } = require("devtools/server/actors/script");
 const { ObjectActor, LongStringActor, createValueGrip, stringIsLong } = require("devtools/server/actors/object");
 const DevToolsUtils = require("devtools/shared/DevToolsUtils");
 
 loader.lazyRequireGetter(this, "NetworkMonitor", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "NetworkMonitorChild", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "ConsoleProgressListener", "devtools/shared/webconsole/network-monitor", true);
 loader.lazyRequireGetter(this, "events", "sdk/event/core");
 loader.lazyRequireGetter(this, "ServerLoggingListener", "devtools/shared/webconsole/server-logger", true);
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -923,19 +923,16 @@ pref("media.gmp-provider.enabled", true)
 pref("reader.color_scheme", "auto");
 
 // Color scheme values available in reader mode UI.
 pref("reader.color_scheme.values", "[\"dark\",\"auto\",\"light\"]");
 
 // Whether to use a vertical or horizontal toolbar.
 pref("reader.toolbar.vertical", false);
 
-// Whether or not to display buttons related to reading list in reader view.
-pref("browser.readinglist.enabled", true);
-
 // Telemetry settings.
 // Whether to use the unified telemetry behavior, requires a restart.
 pref("toolkit.telemetry.unified", false);
 
 // Unified AccessibleCarets (touch-caret and selection-carets).
 #ifdef NIGHTLY_BUILD
 pref("layout.accessiblecaret.enabled", true);
 #else
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -691,17 +691,16 @@ public class BrowserApp extends GeckoApp
             "CharEncoding:State",
             "Experiments:GetActive",
             "Favicon:CacheLoad",
             "Feedback:LastUrl",
             "Feedback:MaybeLater",
             "Feedback:OpenPlayStore",
             "Menu:Add",
             "Menu:Remove",
-            "Reader:Share",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch");
 
         Distribution distribution = Distribution.init(this);
 
@@ -1403,17 +1402,16 @@ public class BrowserApp extends GeckoApp
             "CharEncoding:State",
             "Experiments:GetActive",
             "Favicon:CacheLoad",
             "Feedback:LastUrl",
             "Feedback:MaybeLater",
             "Feedback:OpenPlayStore",
             "Menu:Add",
             "Menu:Remove",
-            "Reader:Share",
             "Sanitize:ClearHistory",
             "Sanitize:ClearSyncedTabs",
             "Settings:Show",
             "Telemetry:Gather",
             "Updater:Launch");
 
         if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
@@ -1724,21 +1722,16 @@ public class BrowserApp extends GeckoApp
         } else if ("Menu:Remove".equals(event)) {
             final int id = message.getInt("id") + ADDON_MENU_OFFSET;
             ThreadUtils.postToUiThread(new Runnable() {
                 @Override
                 public void run() {
                     removeAddonMenuItem(id);
                 }
             });
-
-        } else if ("Reader:Share".equals(event)) {
-            final String title = message.getString("title");
-            final String url = message.getString("url");
-            GeckoAppShell.openUriExternal(url, "text/plain", "", "", Intent.ACTION_SEND, title, false);
         } else if ("Sanitize:ClearHistory".equals(event)) {
             handleClearHistory(message.optBoolean("clearSearchHistory", false));
             callback.sendSuccess(true);
         } else if ("Sanitize:ClearSyncedTabs".equals(event)) {
             handleClearSyncedTabs();
             callback.sendSuccess(true);
         } else if ("Settings:Show".equals(event)) {
             final String resource =
--- a/mobile/android/base/java/org/mozilla/gecko/ReadingListHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/ReadingListHelper.java
@@ -48,17 +48,17 @@ public final class ReadingListHelper imp
     volatile boolean fetchInBackground = true;
 
     public ReadingListHelper(Context context, GeckoProfile profile, OnReadingListEventListener listener) {
         this.context = context;
         this.db = profile.getDB();
         this.readingListAccessor = db.getReadingListAccessor();
 
         EventDispatcher.getInstance().registerGeckoThreadListener((NativeEventListener) this,
-            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
+            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest");
 
 
         contentObserver = new ContentObserver(null) {
             @Override
             public void onChange(boolean selfChange) {
                 if (fetchInBackground) {
                     fetchContent();
                 }
@@ -67,45 +67,38 @@ public final class ReadingListHelper imp
 
         this.readingListAccessor.registerContentObserver(context, contentObserver);
 
         onReadingListEventListener = listener;
     }
 
     public void uninit() {
         EventDispatcher.getInstance().unregisterGeckoThreadListener((NativeEventListener) this,
-            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest", "Reader:ListStatusRequest", "Reader:RemoveFromList");
+            "Reader:AddToList", "Reader:UpdateList", "Reader:FaviconRequest");
 
         context.getContentResolver().unregisterContentObserver(contentObserver);
     }
 
     @Override
     public void handleMessage(final String event, final NativeJSObject message,
                               final EventCallback callback) {
         switch(event) {
+            // Added from web context menu.
             case "Reader:AddToList": {
                 handleAddToList(callback, message);
                 break;
             }
             case "Reader:UpdateList": {
                 handleUpdateList(message);
                 break;
             }
             case "Reader:FaviconRequest": {
                 handleReaderModeFaviconRequest(callback, message.getString("url"));
                 break;
             }
-            case "Reader:RemoveFromList": {
-                handleRemoveFromList(message.getString("url"));
-                break;
-            }
-            case "Reader:ListStatusRequest": {
-                handleReadingListStatusRequest(callback, message.getString("url"));
-                break;
-            }
         }
     }
 
     /**
      * A page can be added to the ReadingList by long-tap of the page-action
      * icon, or by tapping the readinglist-add icon in the ReaderMode banner.
      *
      * This method will only add new items, not update existing items.
@@ -226,54 +219,16 @@ public final class ReadingListHelper imp
                     }
                 }
                 callback.sendSuccess(args.toString());
             }
         }).execute();
     }
 
     /**
-     * A page can be removed from the ReadingList by panel context menu,
-     * or by tapping the readinglist-remove icon in the ReaderMode banner.
-     */
-    private void handleRemoveFromList(final String url) {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                readingListAccessor.removeReadingListItemWithURL(context.getContentResolver(), url);
-                handleEvent(ReadingListEvent.REMOVED, url);
-            }
-        });
-    }
-
-    /**
-     * Gecko (ReaderMode) requests the page ReadingList status, to display
-     * the proper ReaderMode banner icon (readinglist-add / readinglist-remove).
-     */
-    private void handleReadingListStatusRequest(final EventCallback callback, final String url) {
-        ThreadUtils.postToBackgroundThread(new Runnable() {
-            @Override
-            public void run() {
-                final int inReadingList = readingListAccessor.isReadingListItem(context.getContentResolver(), url) ? 1 : 0;
-
-                final JSONObject json = new JSONObject();
-                try {
-                    json.put("url", url);
-                    json.put("inReadingList", inReadingList);
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "JSON error - failed to return inReadingList status", e);
-                }
-
-                // Return the json object to fulfill the promise.
-                callback.sendSuccess(json.toString());
-            }
-        });
-    }
-
-    /**
      * Handle various reading list events (and display appropriate toasts).
      */
     private void handleEvent(final ReadingListEvent event, final String url) {
         ThreadUtils.postToUiThread(new Runnable() {
             @Override
             public void run() {
                 switch(event) {
                     case ADDED:
--- a/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
+++ b/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarDisplayLayout.java
@@ -115,16 +115,18 @@ public class ToolbarDisplayLayout extend
     // Default level (unverified pages) - globe icon:
     private static final int LEVEL_DEFAULT_GLOBE = 0;
     // Levels for displaying Mixed Content state icons.
     private static final int LEVEL_WARNING_MINOR = 3;
     private static final int LEVEL_LOCK_DISABLED = 4;
     // Levels for displaying Tracking Protection state icons.
     private static final int LEVEL_SHIELD_ENABLED = 5;
     private static final int LEVEL_SHIELD_DISABLED = 6;
+    // Icon used for about:home
+    private static final int LEVEL_SEARCH_ICON = 999;
 
     private final ForegroundColorSpan mBlockedColor;
 
     public ToolbarDisplayLayout(Context context, AttributeSet attrs) {
         super(context, attrs);
         setOrientation(HORIZONTAL);
 
         mActivity = (BrowserApp) context;
@@ -312,17 +314,20 @@ public class ToolbarDisplayLayout extend
         // SecurityMode.CHROMEUI has a different ordinal - hence we need to manually reset it here.
         // (We then continue and process the tracking / mixed content icons as usual, even for about: pages, as they
         //  can still load external sites.)
         if (securityMode == SecurityMode.CHROMEUI) {
             imageLevel = LEVEL_DEFAULT_GLOBE; // == SecurityMode.UNKNOWN.ordinal()
         }
 
         // Check to see if any protection was overridden first
-        if (loginInsecure) {
+        if (AboutPages.isTitlelessAboutPage(tab.getURL())) {
+            // We always want to just show a search icon on about:home
+            imageLevel = LEVEL_SEARCH_ICON;
+        } else if (loginInsecure) {
             imageLevel = LEVEL_LOCK_DISABLED;
         } else if (trackingMode == TrackingMode.TRACKING_CONTENT_LOADED) {
             imageLevel = LEVEL_SHIELD_DISABLED;
         } else if (trackingMode == TrackingMode.TRACKING_CONTENT_BLOCKED) {
             imageLevel = LEVEL_SHIELD_ENABLED;
         } else if (activeMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
             imageLevel = LEVEL_LOCK_DISABLED;
         } else if (displayMixedMode == MixedMode.MIXED_CONTENT_LOADED) {
--- a/mobile/android/base/resources/drawable/site_security_level.xml
+++ b/mobile/android/base/resources/drawable/site_security_level.xml
@@ -8,9 +8,11 @@
     <item android:maxLevel="0" android:drawable="@drawable/site_security_unknown"/>
     <item android:maxLevel="1" android:drawable="@drawable/lock_secure"/>
     <item android:maxLevel="2" android:drawable="@drawable/lock_secure"/>
     <item android:maxLevel="3" android:drawable="@drawable/warning_minor"/>
     <item android:maxLevel="4" android:drawable="@drawable/lock_disabled"/>
     <item android:maxLevel="5" android:drawable="@drawable/shield_enabled"/>
     <item android:maxLevel="6" android:drawable="@drawable/shield_disabled"/>
 
+    <!-- Special icon used for about:home -->
+    <item android:maxLevel="999" android:drawable="@drawable/search_icon_inactive" />
 </level-list>
--- a/mobile/android/chrome/content/Reader.js
+++ b/mobile/android/chrome/content/Reader.js
@@ -71,41 +71,25 @@ var Reader = {
   observe: function Reader_observe(aMessage, aTopic, aData) {
     switch (aTopic) {
       case "Reader:FetchContent": {
         let data = JSON.parse(aData);
         this._fetchContent(data.url, data.id);
         break;
       }
 
-      case "Reader:Added": {
-        let mm = window.getGroupMessageManager("browsers");
-        mm.broadcastAsyncMessage("Reader:Added", { url: aData });
-        break;
-      }
-
       case "Reader:Removed": {
         ReaderMode.removeArticleFromCache(aData).catch(e => Cu.reportError("Error removing article from cache: " + e));
-
-        let mm = window.getGroupMessageManager("browsers");
-        mm.broadcastAsyncMessage("Reader:Removed", { url: aData });
         break;
       }
     }
   },
 
   receiveMessage: function(message) {
     switch (message.name) {
-      case "Reader:AddToList": {
-        // If the article is coming from reader mode, we must have fetched it already.
-        let article = message.data.article;
-        article.status = this.STATUS_FETCHED_ARTICLE;
-        this._addArticleToReadingList(article);
-        break;
-      }
       case "Reader:ArticleGet":
         this._getArticle(message.data.url).then((article) => {
           // Make sure the target browser is still alive before trying to send data back.
           if (message.target.messageManager) {
             message.target.messageManager.sendAsyncMessage("Reader:ArticleData", { article: article });
           }
         }, e => {
           if (e && e.newURL) {
@@ -142,40 +126,16 @@ var Reader = {
           type: "Reader:FaviconRequest",
           url: message.data.url
         }).then(data => {
           message.target.messageManager.sendAsyncMessage("Reader:FaviconReturn", JSON.parse(data));
         });
         break;
       }
 
-      case "Reader:ListStatusRequest":
-        Messaging.sendRequestForResult({
-          type: "Reader:ListStatusRequest",
-          url: message.data.url
-        }).then((data) => {
-          message.target.messageManager.sendAsyncMessage("Reader:ListStatusData", JSON.parse(data));
-        });
-        break;
-
-      case "Reader:RemoveFromList":
-        Messaging.sendRequest({
-          type: "Reader:RemoveFromList",
-          url: message.data.url
-        });
-        break;
-
-      case "Reader:Share":
-        Messaging.sendRequest({
-          type: "Reader:Share",
-          url: message.data.url,
-          title: message.data.title
-        });
-        break;
-
       case "Reader:SystemUIVisibility":
         Messaging.sendRequest({
           type: "SystemUI:Visibility",
           visible: message.data.visible
         });
         break;
 
       case "Reader:ToolbarHidden":
@@ -299,29 +259,16 @@ var Reader = {
   _downloadAndCacheArticle: Task.async(function* (url) {
     let article = yield ReaderMode.downloadAndParseDocument(url);
     if (article != null) {
       yield ReaderMode.storeArticleInCache(article);
     }
     return article;
   }),
 
-  _addArticleToReadingList: function(article) {
-    Messaging.sendRequestForResult({
-      type: "Reader:AddToList",
-      url: truncate(article.url, MAX_URI_LENGTH),
-      title: truncate(article.title, MAX_TITLE_LENGTH),
-      length: article.length,
-      excerpt: article.excerpt,
-      status: article.status,
-    }).then((url) => {
-      ReaderMode.storeArticleInCache(article).catch(e => Cu.reportError("Error storing article in cache: " + e));
-    }).catch(Cu.reportError);
-  },
-
   /**
    * Gets an article for a given URL. This method will download and parse a document
    * if it does not find the article in the cache.
    *
    * @param url The article URL.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -150,17 +150,17 @@ var lazilyLoadedObserverScripts = [
   ["MemoryObserver", ["memory-pressure", "Memory:Dump"], "chrome://browser/content/MemoryObserver.js"],
   ["ConsoleAPI", ["console-api-log-event"], "chrome://browser/content/ConsoleAPI.js"],
   ["FindHelper", ["FindInPage:Opened", "FindInPage:Closed", "Tab:Selected"], "chrome://browser/content/FindHelper.js"],
   ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
-  ["Reader", ["Reader:FetchContent", "Reader:Added", "Reader:Removed"], "chrome://browser/content/Reader.js"],
+  ["Reader", ["Reader:FetchContent", "Reader:Removed"], "chrome://browser/content/Reader.js"],
   ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
 ];
 if (AppConstants.NIGHTLY_BUILD) {
   lazilyLoadedObserverScripts.push(
     ["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
       "chrome://browser/content/ActionBarHandler.js"]
   );
 }
@@ -186,24 +186,20 @@ lazilyLoadedObserverScripts.forEach(func
   notifications.forEach((notification) => {
     Services.obs.addObserver(observer, notification, false);
   });
 });
 
 // Lazily-loaded browser scripts that use message listeners.
 [
   ["Reader", [
-    ["Reader:AddToList", false],
     ["Reader:ArticleGet", false],
     ["Reader:DropdownClosed", true], // 'true' allows us to survive mid-air cycle-collection.
     ["Reader:DropdownOpened", false],
     ["Reader:FaviconRequest", false],
-    ["Reader:ListStatusRequest", false],
-    ["Reader:RemoveFromList", false],
-    ["Reader:Share", false],
     ["Reader:ToolbarHidden", false],
     ["Reader:SystemUIVisibility", false],
     ["Reader:UpdateReaderButton", false],
     ["Reader:SetIntPref", false],
     ["Reader:SetCharPref", false],
   ], "chrome://browser/content/Reader.js"],
 ].forEach(aScript => {
   let [name, messages, script] = aScript;
--- a/mobile/android/themes/core/aboutReaderControls.css
+++ b/mobile/android/themes/core/aboutReaderControls.css
@@ -50,50 +50,45 @@
   margin: 0px;
   margin-bottom: 32px;
 }
 
 /*======= Controls toolbar =======*/
 
 .toolbar {
   font-family: sans-serif;
-  transition-property: bottom;
-  transition-duration: 0.3s;
   position: fixed;
   width: 100%;
   left: 0;
   margin: 0;
   padding: 0;
+  bottom: 0;
   list-style: none;
-  background-color: #EBEBF0;
-  border-top: 1px solid #D7D9DB;
-  display: none;
-}
-
-.toolbar[visible] {
-  bottom: 0;
 }
 
 .toolbar > * {
   float: right;
-  width: 33%;
 }
 
 .button {
+  width: 56px;
+  height: 56px;
   display: block;
   background-position: center;
-  background-size: 30px 28px;
+  background-size: 26px 16px;
   background-repeat: no-repeat;
-  background-color: transparent;
+  background-color: #E66000;
+  border-radius: 10000px;
+  margin: 20px;
   border: 0;
-  width: 100%;
+  box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.40);
 }
 
 .button:active {
-  background-color: #D7D7DC;
+  background-color: #DC5600;
 }
 
 /* Remove dotted border when button is focused */
 .button::-moz-focus-inner,
 .dropdown-popup > div > button::-moz-focus-inner {
   border: 0;
 }
 
@@ -105,48 +100,41 @@
   left: 0;
   text-align: center;
   display: inline-block;
   list-style: none;
   margin: 0px;
   padding: 0px;
 }
 
-.dropdown li {
-  margin: 0px;
-  padding: 0px;
-}
-
-.dropdown-toggle {
-  margin: 0px;
-  padding: 0px;
-}
-
 /*======= Font style popup =======*/
 
 .dropdown-popup {
   position: absolute;
   left: 0;
-  width: 100%;
+  width: calc(100% - 30px);
+  margin: 15px;
   z-index: 1000;
   background: #EBEBF0;
   visibility: hidden;
-  border-top: 1px solid #D7D9DB;
+  border: 0;
+  border-radius: 4px;
+  box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.40);
 }
 
 /* Only used on desktop */
 .dropdown-popup > hr,
 .dropdown-arrow,
 #font-type-buttons > button > .name {
   display: none;
 }
 
 .open > .dropdown-popup {
   visibility: visible;
-  bottom: 100%;
+  bottom: 0;
 }
 
 #font-type-buttons,
 #font-size-buttons,
 #color-scheme-buttons {
   display: flex;
   flex-direction: row;
 }
@@ -235,179 +223,59 @@
 .light-button {
   color: #333333;
   background-color: #ffffff;
 }
 
 /*======= Toolbar icons =======*/
 
 /* desktop-only controls */
-.close-button,
-.list-button,
-.footer {
+.close-button {
   display: none;
 }
 
-.toggle-button.on {
-  background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-mdpi.png');
-}
-
-.toggle-button {
-  background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-mdpi.png');
-}
-
-.share-button {
-  background-image: url('chrome://browser/skin/images/reader-share-icon-mdpi.png');
-}
-
 .style-button {
-  background-image: url('chrome://browser/skin/images/reader-style-icon-mdpi.png');
-}
-
-.open .style-button {
-  background-image: url('chrome://browser/skin/images/reader-style-icon-active-mdpi.png');
+  background-image: url('chrome://browser/skin/images/reader-style-icon-hdpi.png');
 }
 
 .minus-button {
-  background-image: url('chrome://browser/skin/images/reader-minus-mdpi.png');
+  background-image: url('chrome://browser/skin/images/reader-minus-hdpi.png');
 }
 
 .plus-button {
-  background-image: url('chrome://browser/skin/images/reader-plus-mdpi.png');
-}
-
-@media screen and (min-resolution: 1.25dppx) {
-  .toggle-button.on {
-    background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-hdpi.png');
-  }
-
-  .toggle-button {
-    background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-hdpi.png');
-  }
-
-  .share-button {
-    background-image: url('chrome://browser/skin/images/reader-share-icon-hdpi.png');
-  }
-
-  .style-button {
-    background-image: url('chrome://browser/skin/images/reader-style-icon-hdpi.png');
-  }
-
-  .open .style-button {
-    background-image: url('chrome://browser/skin/images/reader-style-icon-active-hdpi.png');
-  }
-
-  .minus-button {
-    background-image: url('chrome://browser/skin/images/reader-minus-hdpi.png');
-  }
-
-  .plus-button {
-    background-image: url('chrome://browser/skin/images/reader-plus-hdpi.png');
-  }
+  background-image: url('chrome://browser/skin/images/reader-plus-hdpi.png');
 }
 
 @media screen and (min-resolution: 2dppx) {
-  .toggle-button.on {
-    background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-xhdpi.png');
-  }
-
-  .toggle-button {
-    background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-xhdpi.png');
-  }
-
-  .share-button {
-    background-image: url('chrome://browser/skin/images/reader-share-icon-xhdpi.png');
-  }
-
   .style-button {
     background-image: url('chrome://browser/skin/images/reader-style-icon-xhdpi.png');
   }
 
-  .open .style-button {
-    background-image: url('chrome://browser/skin/images/reader-style-icon-active-xhdpi.png');
-  }
-
   .minus-button {
     background-image: url('chrome://browser/skin/images/reader-minus-xhdpi.png');
   }
 
   .plus-button {
     background-image: url('chrome://browser/skin/images/reader-plus-xhdpi.png');
   }
 }
 
 @media screen and (min-resolution: 3dppx) {
-  .toggle-button.on {
-    background-image: url('chrome://browser/skin/images/reader-toggle-on-icon-xxhdpi.png');
-  }
-
-  .toggle-button {
-    background-image: url('chrome://browser/skin/images/reader-toggle-off-icon-xxhdpi.png');
-  }
-
-  .share-button {
-    background-image: url('chrome://browser/skin/images/reader-share-icon-xhdpi.png');
-  }
-
   .style-button {
     background-image: url('chrome://browser/skin/images/reader-style-icon-xxhdpi.png');
   }
 
-  .open .style-button {
-    background-image: url('chrome://browser/skin/images/reader-style-icon-active-xxhdpi.png');
-  }
-
   .minus-button {
     background-image: url('chrome://browser/skin/images/reader-minus-xxhdpi.png');
   }
 
   .plus-button {
     background-image: url('chrome://browser/skin/images/reader-plus-xxhdpi.png');
   }
 }
 
-@media screen and (orientation: portrait) {
-  .button {
-    height: 56px;
-  }
-
-  .toolbar {
-    bottom: -57px;
-  }
-}
-
-@media screen and (orientation: landscape) {
-  .button {
-    height: 40px;
-  }
-
-  .toolbar {
-    bottom: -41px;
+@media screen and (min-width: 960px) {
+  .dropdown-popup {
+    width: 350px;
+    left: auto;
+    right: 0;
   }
 }
-
-@media screen and (min-width: 960px) {
-  .button {
-    width: 56px;
-    height: 56px;
-  }
-
-  .toolbar {
-    bottom: -57px;
-  }
-
-  .toolbar > * {
-    width: 56px;
-  }
-
-  .dropdown-popup {
-    position: absolute;
-    width: 350px;
-    left: auto;
-    right: 15px;
-    bottom: -12px;
-    z-index: 1000;
-    background: #EBEBF0;
-    border: 1px solid #D7D9DB;
-    border-radius: 4px;
-    box-shadow: 0px 2px 4px 2px #BFBFBF;
-  }
-}
deleted file mode 100644
index cb46de3d1e532a527cce8dff24f60cdbba24a5d0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index ef53c28683bf39be6354caeef8ee3067a16e9a6e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c58461cdcf9c580ceeb5e7bcd948da18f843e94c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 2332164dea93cfc364de7c531a4c4b5f7881ef50..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index b2d752db355233f9b666bd61523c91443091e396..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6cd73a7d06fc91ae77bca5bf5aff0d27d72deeb6..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 308e78c25d3968232262b0ac62f2e7df0f962100..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index c1a2cac429c51ced17ee60d1b67e758decedc445..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 5b0a8109d8182dc3b21ae0fe6080d22e99b12a2b..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index d90f7e3f2244cd430653dad00c3cca0e4c99eb84..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
index 8bd8fea7c176e4592e1d033e470442aafebcc5d4..ea7578d286d8d6c51df478c0a84fc3e7eb25fd41
GIT binary patch
literal 891
zc$@)y1BCpEP)<h;3K|Lk000e1NJLTq001Wd000;W1^@s6d5`2x00001b5ch_0Itp)
z=>Px&HAzH4R7efQmRpEUQ547La~szrF>+}nN{z&L9GR(^V)B9q8JFqfneyO;@*-q%
zdyqy+BPqkU=RuN`C^g2&C6qi2QR7l06XWvvt#j7?tZ$$5%{hv(>VMYSYyH>SYoERM
zcfOL66q3v37J)WJO^TF(qi9p8KrRg2%n38m&N%QT>??To@tly5_E*A#QdC(9r_}I<
zVy`jd|7Y@+?&w?ls9IF#|LaI+rioeFP%nD5+D^^;*9|6Z>pE@VBKiz?YZ}TLJGIHm
zN`WkTA*coAAgPbS`Xpq)OCYjXqUe3WD13CI<ou-)xOLz(_ym0UjAJVplD1g2R(Opb
zEJ^4&>T_y&QZb0K$R_YpllGt+z*4XobO9&LN%ev_>|>hIWE1eUyGrOwHWp{YsWN;q
z3$~E)R3K+s<QpiDtCROZ7zuiSNJTz?!FXff4Iff$qK*4Dad~T23Yrr>!7GNa%+)Bi
z$3hqb?qxEW$BwM}6nZ}x1|(m8^A3Vy(n8<|aOX1@L+moXX~vG4tm3M!WK%R6tn{2&
z>4P}VpZKVC*wowNawBgJ(qw{lO4z_yz7W1lF^;)#DtW^l#@DY2Up{}epc>SG*&rg3
z{f8mjB)790WPoh1j~;xzz)98_#WW3#0PDduIB&p7FcTaF=RvZN{1Aw&=#Nc&Z@>y4
z?@)3EV})22yb7to&D@1O$&s-SX-rgpTU|WR1S*yE9$j=YArr4qu2Zp#A-Ka4(48Q1
z;PJ&a=WlDHcH7Ds>Qg&b=`C(I!K|?ZlS!vzY%~5<<k_2PLQyl{9EId^G$`dnlp5<f
zZoKu>C^KU*7XUfGA|Gh%X=tW<=9`1Q1jrBSGm6|zmtG>jd0&+)i(BjE$;ELpmgYyf
zfv0UBUWg%yZ9rsuG+NvP|7?&IJ|0;nt}@`hslxC-h4Sf=^Y&eWD!(rT4*48i2J-h?
z<UZI9{I_7|mBq-%TX@fbmFo<4g5i>YZni<}Ti^(|38bl3Z~!>zz-R<jlKjMZJ#7zr
zL1Q}UZIAB{$186%ffhf=SpH$xE2kzVenWm_2Sk{B1{2K(ayHvJ*DsPL{spB4uV@<0
Rx+(ww002ovPDHLkV1gv`q{aXM
deleted file mode 100644
index 4f6a91a84dfda97073d1cf7a2e6e30f316ff05b0..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
index 46e8645971b873b73976d07046ffca54ff678568..fb12ed007efec55bee2a427a084bb181b192db89
GIT binary patch
literal 1179
zc$@*71Z4Y(P)<h;3K|Lk000e1NJLTq001-q001Be1^@s6T|Zl900001b5ch_0Itp)
z=>Px(RY^oaR9Fesm}#g@Q547THRj1Y&qD}FGDYT+6cQryqexMdBxOkWAihwdkPKgt
zl8@%05aG)+6f)1#Lx>E|Z@stowa#+Rx#vElyXyb;8vbkVz0Tcd?|oi6m7|oDl%z@N
ztI}?z)9IJW&M%dM1_Lcy^Q!_gTPoS1t3iJYsu@~-m`uC?ya!e$=SKx*mQ*ptR^sd<
zhMFHHlRq3$(0BYT@}mkjOD5b5$izh+lr@3BOY^Ou#cKNV&@U=m)SLNM&{bE!7utGY
zuCsO1?|&C1hh$b^#yucia>Roiq+{{b!F~#=I=?jAiOp7axm5b@K;m%F95e>?!835n
z;V9$0Loc5&z((L@wC@GSe(!YcJK6#t&!r`=B9LY6R|0CJJSLKvM=s-}NURKAxUOG{
z?K?mp;rzOp>f=dUk{g3ls*^kDo4`U)2$lmK$1Ig;Nyo_WNE_vnrP-JXpkT{WeJTMZ
z$22FKPF@voQ{{^~DJ&2FBE!3ft(SaQTF^(r0o94TO93IaUd2U~t-WP{6l1`Z_t8pJ
zK@}ht8FFChRfri&eu28gM>@_`Uc}2G{GG-peNH#NI5TnajQ@j<&X~^HXuFa&Li7pv
z{7<?&B%WfBYZNX!U{4h<L|>!qICVe4?x2w($?h2?_f4Rcv7=2UdfxrK-)NgeF^S1R
zYh^$b|4;l3GT#go_<V_6awN%F(8mpag7y_Y+3|h@u>;_%<N9WXSN>MZ*q!K0uY}}X
zle`-IiO6S0xy~d%OY%Daxsk|u_Z)=DHqVzf^c^BgZV<We@w(?!ss`pbuqWt_w-{SC
ziHSgVx^9Yn4PE+@jcTUyXIJTTuzD1`&>JQ|BREe~o~XyOk5<Nj+aBx#KY$mx)vpBY
zd_U;1?~vyOT9Wi<_P{_e#vpGU&?1w|x(mTs2EMAq*XXlA=2qf|#g|(_BoqKy{~~e)
zk>!_2;C7ZIBP-&P;jS|LXV`Kn4HaUs1%>T$W9UiNzsMfWnUXR!`(L=%VO=!LLM9RV
zQ1CZ{yTtIP(a$5}OKgS}G+kxq^g_G5oho0{wjk<E8b(Ynt54WD-38$XBv6ohVnZF{
z98c<YfSt_P3+;=c&M>w&8+gUF#ks5qlUr&A3c@o5c;BwDgls-0R1D{k;f?2ERX08{
z?;2lIc=9n9C;AtS<CT66_>Zg4bX+^09UUciIIvP^Gaz)dB<u~ACpP{XU@efjX$c&x
zLQI2BV1Z)2L6=iH`O|Q-kQ~q-SdmNYAW+j!7+uCb3#@F$(F?Q&Bf%CRJMTwe|CM$U
zpI=p_m7m5DC-Se}T7D8yEgbn!D~{_(#pd{a@X5opPhC+n>M68#pj*cCOL>fX0W6cs
z{O5rm!?sdP=(cN!!b!SJGh_13-5^gc!B<Rw-aLmQN68SlvPl(z4?s&(jNYJ2l8NfX
tf2Q+v<zCqfbOrLM@(`S)h&!~2<tJ1I@~kD~=%4@q002ovPDHLkV1kjHG}Hh9
index 25e967a84d1b1d977d1ec0b477762d80ed106a56..57c6a1964ae8c45a64f41df93c9f94c72d3d04ff
GIT binary patch
literal 1791
zc$@+M1_1eqP)<h;3K|Lk000e1NJLTq002z@001ut1^@s6rZq?500001b5ch_0Itp)
z=>Px*xk*GpRA>e5nrUnmRTRf9LQ#vPf(0}ZaKl<_h>8ntA2d-28VJUKDEJah3~q4=
z;0F?o(ctpMEq>Bi0i(nPQ4=+$#<+peD2iM8P(eWiG>C|RLhJ7|Z_1hT?wz^wW+1fX
zB>%i~&vwr}bLY;TJFn)D0;#jJb1dN+O~|&InwphHcCZ;$fTDc{EPWN}5B{18P^m^+
zE!CD8*lVa?fg_oj5UZ4+>zgWOR6p>unpu|JgkV75td3{Y6yy?HB}aRT$Gyjii@H5$
z1&q8DVAD5Dm{E0Ln`35K(Z2{9dP7x;m}yff#Grs;$eMO-fL-r5VfMUdrc|qG>*m2t
zF4Y(b_UNj>j|a<*OgGQy=~F51?J2jLj<K3orsc?55(-mF(Di;5)8~6Vh2ZDQsQIl2
zJB%)BZiea6d$yWtFD(NfhwtagzZ3p=X4+bjp?{H|>B2i`Dt1d9^1?;raU4NU03*Qg
z^6wCSw}UO<7w|FpwDY@fiDaGxn%>tvqL`qShF_*_i{|O83Cssyntqd-?I>>s1B=e5
zD;|Msf!t=AuGgKRI25ec28#a=ewel|f+yxhz%Ci8+aPuoR-=l@Cl-UClTG)3)2`U7
zA$!Q!>E>Orpg9@@tpRg@TntMa5PSd@gGWIN_yqh3e6$wD;W<*ObS2<xrniNaR$c2_
z(s9u@E}yJ2@)!t~nS7$=c`(j(8~Gsc7}yP5G$(5=UVEYs1-ncym--yATVZT<^Gu&#
zsGMN)bU?$WKBck0)%ZlsKVa%Z^GLKKkSvy(WGd^uTFf8aY;A1#Ct}7K8^n^^a*K(n
zn;}K(k3*YwQv7YMl)Qs=8JA_T1s~aB9@iZS_AjB^<<ZX|!CH?lL_89sT(g1PcN61z
zPiH5&<e7@b0Og~}IVO;EJ&SBeO7=p&EyhrES4Or~{1&xX`}Szgr)Z|4F(`Yay@H~&
z1q`+&!KRd;OH-}JKrY&zG{Y4APagefk33AwEOQ-c&kpg%SN3eb0vE|PL3Zj@6aI&R
z1~5z`G$1}>fQvSS#&FsZ(H?2<M0S+Z*dfIBVOLgV+1(D8{U#y@U(tSZC6VR!oMi&M
zSzfP<*xU3iw&GCodhH)YU6yY8hZu7uB<NMfRyKzVj4Z9W4TB>cM$N<;$3^(Up(|WO
zU$Veo3@!v0fF@8Z(aB7(uj#2`v)&tx#E$?|z|&wg2qow3xi@kGM-TQ$JG9d*cCrJ{
zvuBHqfLKzf>%i;aAK)VS*dy<ykGqt}oJDiKm+9Vdlnvw>px3`8w0#>^REcyJ+S5E1
zS96QL;?V_($yZ|E4&*Fvu)|4~h*yE^9b4&7zNC$TcWs?EqL*{HY(&6&fTmU1BE+o;
zbh!>@__yNL;Vt5v0+xaf6EkYI!b}4d8)9Uco}Q^$5zwAb<$C)kp6vJ50T;=ML*^@s
z&s<;+5u*5S!<?HmQ1rzWjbP)Vp3A>7sAH272+<K^fr+b|nJx;Bk7#GbZ-uvSFwXR_
zE3|0#Nc$aUuNj3TDYuf3z6}jWfO|bb{z9HOyL2De7E?3=)dG8@JwtbvhbQ)@z(;4N
z#m2E7NO4?rN1DIZ6ChL3^b=9X5q=^#S_$Q;Q{)$<78TpFB`a{W#m2Q!7Sp7+57FnM
zq@vk#vzK}N?#R_!+*!6QJL976)-_!Hw68*^c3a1pP2?%ZQTvRXMI+&%^f<}%y3n+9
zH5u4e0{CXG90W{G>A?3uTA$*VTkR|wQKtdDJ6}WlLr^q5mk_X&pc9pEHl3K_ms{;C
zn!Pm_F*7=I_f{F8716p%ahK#WX8bW8orry#?7{doC-}Kkav)JX(V^?J+sV~OO!lO|
z2Z4{~`n++N;B!LmyX|A)up0pG1ip_m&UepP5NQy=4&v(yqXGr=*93edUx}P|1M=ZO
zUbuba-6Z2s2W|vE`kc%1&EBD6AV<ZMz<pp7(6pU~ye~?ULFaGYj+8=Tff3*qkVG$_
zyaJ2@u>%I$_k$#Q1Em~m$=!24xCuN1<ZU6MjW9O=-#e?EyTu9PXCv=`UkNNnI1j|5
zgtzdW2Yv^>BvE-u20J?H4PG|j-+8Um6>>pjryzHjeaDWx_rS<TTrR*OFi~bsmhXmg
zrH6n9P%X)Zxhv5s0?1KM_fwsRliZ&2$3j`dMuSlL0mHfY>r<8G$Xt6rSsuo#z-`X;
zLKz(JECXLjlJc~auk{@%34ersI%qZp6E`VZ5XIJrAddssh*{T2dNM1<g44^X%4Syz
h@-BsHFUxZM{0}KngH-?AavJ~u002ovPDHLkV1j56VCw(?
deleted file mode 100644
index 90402468bfe34e16eb245d6d0f55d3f921bc32cc..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index fd8f4b206aa9e4e12aab21c95584b708d38f7b41..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 8b57ad9fb0a4247c7b4553c9c84b8396963cc772..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index f3228599d381fc946bfabf6ea827bb8b7be79049..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 9495bc0b3de5b6c5f1d57e682b894eb8b9cad20d..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index b1991ac54b7ecab9dfeff755c69cc1595e1802e8..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 109e3571aa2fcb97235b74a3931c54ec8a4f0052..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 91446889788c7b6d2a7362dbfa850a8bd0791294..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/mobile/android/themes/core/jar.mn
+++ b/mobile/android/themes/core/jar.mn
@@ -85,41 +85,22 @@ chrome.jar:
   skin/images/cast-active-hdpi.png          (images/cast-active-hdpi.png)
   skin/images/mute-hdpi.png                 (images/mute-hdpi.png)
   skin/images/unmute-hdpi.png               (images/unmute-hdpi.png)
   skin/images/scrubber-hdpi.png             (images/scrubber-hdpi.png)
   skin/images/about-btn-darkgrey.png        (images/about-btn-darkgrey.png)
   skin/images/logo-hdpi.png                 (images/logo-hdpi.png)
   skin/images/wordmark-hdpi.png             (images/wordmark-hdpi.png)
   skin/images/config-plus.png               (images/config-plus.png)
-  skin/images/reader-minus-mdpi.png              (images/reader-minus-mdpi.png)
   skin/images/reader-minus-hdpi.png              (images/reader-minus-hdpi.png)
   skin/images/reader-minus-xhdpi.png             (images/reader-minus-xhdpi.png)
   skin/images/reader-minus-xxhdpi.png            (images/reader-minus-xxhdpi.png)
-  skin/images/reader-plus-mdpi.png               (images/reader-plus-mdpi.png)
   skin/images/reader-plus-hdpi.png               (images/reader-plus-hdpi.png)
   skin/images/reader-plus-xhdpi.png              (images/reader-plus-xhdpi.png)
   skin/images/reader-plus-xxhdpi.png             (images/reader-plus-xxhdpi.png)
-  skin/images/reader-toggle-on-icon-mdpi.png     (images/reader-toggle-on-icon-mdpi.png)
-  skin/images/reader-toggle-on-icon-hdpi.png     (images/reader-toggle-on-icon-hdpi.png)
-  skin/images/reader-toggle-on-icon-xhdpi.png    (images/reader-toggle-on-icon-xhdpi.png)
-  skin/images/reader-toggle-on-icon-xxhdpi.png   (images/reader-toggle-on-icon-xxhdpi.png)
-  skin/images/reader-toggle-off-icon-mdpi.png    (images/reader-toggle-off-icon-mdpi.png)
-  skin/images/reader-toggle-off-icon-hdpi.png    (images/reader-toggle-off-icon-hdpi.png)
-  skin/images/reader-toggle-off-icon-xhdpi.png   (images/reader-toggle-off-icon-xhdpi.png)
-  skin/images/reader-toggle-off-icon-xxhdpi.png  (images/reader-toggle-off-icon-xxhdpi.png)
-  skin/images/reader-share-icon-mdpi.png         (images/reader-share-icon-mdpi.png)
-  skin/images/reader-share-icon-hdpi.png         (images/reader-share-icon-hdpi.png)
-  skin/images/reader-share-icon-xhdpi.png        (images/reader-share-icon-xhdpi.png)
-  skin/images/reader-share-icon-xxhdpi.png       (images/reader-share-icon-xxhdpi.png)
-  skin/images/reader-style-icon-active-mdpi.png  (images/reader-style-icon-active-mdpi.png)
-  skin/images/reader-style-icon-active-hdpi.png  (images/reader-style-icon-active-hdpi.png)
-  skin/images/reader-style-icon-active-xhdpi.png (images/reader-style-icon-active-xhdpi.png)
-  skin/images/reader-style-icon-active-xxhdpi.png (images/reader-style-icon-active-xxhdpi.png)
-  skin/images/reader-style-icon-mdpi.png         (images/reader-style-icon-mdpi.png)
   skin/images/reader-style-icon-hdpi.png         (images/reader-style-icon-hdpi.png)
   skin/images/reader-style-icon-xhdpi.png        (images/reader-style-icon-xhdpi.png)
   skin/images/reader-style-icon-xxhdpi.png       (images/reader-style-icon-xxhdpi.png)
   skin/images/privatebrowsing-mask.png           (images/privatebrowsing-mask.png)
   skin/images/privatebrowsing-mask-and-shield.svg (images/privatebrowsing-mask-and-shield.svg)
   skin/images/icon_floaty_hdpi.png               (images/icon_floaty_hdpi.png)
   skin/images/icon_floaty_mdpi.png               (images/icon_floaty_mdpi.png)
   skin/images/icon_floaty_xhdpi.png              (images/icon_floaty_xhdpi.png)
--- a/services/common/hawkclient.js
+++ b/services/common/hawkclient.js
@@ -96,16 +96,21 @@ this.HawkClient = function(host) {
   // Clock offset in milliseconds between our client's clock and the date
   // reported in responses from our host.
   this._localtimeOffsetMsec = 0;
 }
 
 this.HawkClient.prototype = {
 
   /*
+   * A boolean for feature detection.
+   */
+  willUTF8EncodeRequests: HAWKAuthenticatedRESTRequest.prototype.willUTF8EncodeObjectRequests,
+
+  /*
    * Construct an error message for a response.  Private.
    *
    * @param restResponse
    *        A RESTResponse object from a RESTRequest
    *
    * @param error
    *        A string or object describing the error
    */
--- a/services/common/rest.js
+++ b/services/common/rest.js
@@ -100,16 +100,23 @@ RESTRequest.prototype = {
     Ci.nsIBadCertListener2,
     Ci.nsIInterfaceRequestor,
     Ci.nsIChannelEventSink
   ]),
 
   /*** Public API: ***/
 
   /**
+   * A constant boolean that indicates whether this object will automatically
+   * utf-8 encode request bodies passed as an object. Used for feature detection
+   * so, eg, loop can use the same source code for old and new Firefox versions.
+   */
+  willUTF8EncodeObjectRequests: true,
+
+  /**
    * URI for the request (an nsIURI object).
    */
   uri: null,
 
   /**
    * HTTP method (e.g. "GET")
    */
   method: null,
@@ -317,33 +324,51 @@ RESTRequest.prototype = {
       } else {
         this._log.trace("HTTP Header " + key + ": " + headers[key]);
       }
       channel.setRequestHeader(key, headers[key], false);
     }
 
     // Set HTTP request body.
     if (method == "PUT" || method == "POST" || method == "PATCH") {
-      // Convert non-string bodies into JSON.
+      // Convert non-string bodies into JSON with utf-8 encoding. If a string
+      // is passed we assume they've already encoded it.
+      let contentType = headers["content-type"];
       if (typeof data != "string") {
         data = JSON.stringify(data);
+        if (!contentType) {
+          contentType = "application/json";
+        }
+        if (!contentType.includes("charset")) {
+          data = CommonUtils.encodeUTF8(data);
+          contentType += "; charset=utf-8";
+        } else {
+          // If someone handed us an object but also a custom content-type
+          // it's probably confused. We could go to even further lengths to
+          // respect it, but this shouldn't happen in practice.
+          Cu.reportError("rest.js found an object to JSON.stringify but also a " +
+                         "content-type header with a charset specification. " +
+                         "This probably isn't going to do what you expect");
+        }
+      }
+      if (!contentType) {
+        contentType = "text/plain";
       }
 
       this._log.debug(method + " Length: " + data.length);
       if (this._log.level <= Log.Level.Trace) {
         this._log.trace(method + " Body: " + data);
       }
 
       let stream = Cc["@mozilla.org/io/string-input-stream;1"]
                      .createInstance(Ci.nsIStringInputStream);
       stream.setData(data, data.length);
 
-      let type = headers["content-type"] || "text/plain";
       channel.QueryInterface(Ci.nsIUploadChannel);
-      channel.setUploadStream(stream, type, data.length);
+      channel.setUploadStream(stream, contentType, data.length);
     }
     // We must set this after setting the upload stream, otherwise it
     // will always be 'PUT'. Yeah, I know.
     channel.requestMethod = method;
 
     // Before opening the channel, set the charset that serves as a hint
     // as to what the response might be encoded as.
     channel.contentCharset = this.charset;
--- a/services/common/tests/unit/test_restrequest.js
+++ b/services/common/tests/unit/test_restrequest.js
@@ -216,16 +216,50 @@ add_test(function test_get_utf8() {
       do_check_eq(request2.response.charset, "utf-8");
 
       server.stop(run_next_test);
     });
   });
 });
 
 /**
+ * Test HTTP POST data is encoded as UTF-8 by default.
+ */
+add_test(function test_post_utf8() {
+  // We setup a handler that responds with exactly what it received.
+  // Given we've already tested above that responses are correctly utf-8
+  // decoded we can surmise that the correct response coming back means the
+  // input must also have been encoded.
+  let server = httpd_setup({"/echo": function(req, res) {
+    res.setStatusLine(req.httpVersion, 200, "OK");
+    res.setHeader("Content-Type", req.getHeader("content-type"));
+    // Get the body as bytes and write them back without touching them
+    let sis = Cc["@mozilla.org/scriptableinputstream;1"]
+              .createInstance(Ci.nsIScriptableInputStream);
+    sis.init(req.bodyInputStream);
+    let body = sis.read(sis.available());
+    sis.close()
+    res.write(body);
+  }});
+
+  let data = {copyright: "\xa9"}; // \xa9 is the copyright symbol
+  let request1 = new RESTRequest(server.baseURI + "/echo");
+  request1.post(data, function(error) {
+    do_check_null(error);
+
+    do_check_eq(request1.response.status, 200);
+    deepEqual(JSON.parse(request1.response.body), data);
+    do_check_eq(request1.response.headers["content-type"],
+                "application/json; charset=utf-8")
+
+    server.stop(run_next_test);
+  });
+});
+
+/**
  * Test more variations of charset handling.
  */
 add_test(function test_charsets() {
   let response = "Hello World, I can't speak Russian";
 
   let contentType = "text/plain";
   let charset = true;
   let charsetSuffix = "; charset=us-ascii";
@@ -426,17 +460,17 @@ add_test(function test_put_json() {
 
     do_check_eq(this.status, this.COMPLETED);
     do_check_true(this.response.success);
     do_check_eq(this.response.status, 200);
     do_check_eq(this.response.body, "");
 
     do_check_eq(handler.request.method, "PUT");
     do_check_eq(handler.request.body, JSON.stringify(sample_data));
-    do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
+    do_check_eq(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
 
     server.stop(run_next_test);
   });
 });
 
 /**
  * The 'data' argument to POST, if not a string already, is automatically
  * stringified as JSON.
@@ -456,16 +490,42 @@ add_test(function test_post_json() {
 
     do_check_eq(this.status, this.COMPLETED);
     do_check_true(this.response.success);
     do_check_eq(this.response.status, 200);
     do_check_eq(this.response.body, "");
 
     do_check_eq(handler.request.method, "POST");
     do_check_eq(handler.request.body, JSON.stringify(sample_data));
+    do_check_eq(handler.request.getHeader("Content-Type"), "application/json; charset=utf-8");
+
+    server.stop(run_next_test);
+  });
+});
+
+/**
+ * The content-type will be text/plain without a charset if the 'data' argument
+ * to POST is already a string.
+ */
+add_test(function test_post_json() {
+  let handler = httpd_handler(200, "OK");
+  let server = httpd_setup({"/resource": handler});
+
+  let sample_data = "hello";
+  let request = new RESTRequest(server.baseURI + "/resource");
+  request.post(sample_data, function (error) {
+    do_check_eq(error, null);
+
+    do_check_eq(this.status, this.COMPLETED);
+    do_check_true(this.response.success);
+    do_check_eq(this.response.status, 200);
+    do_check_eq(this.response.body, "");
+
+    do_check_eq(handler.request.method, "POST");
+    do_check_eq(handler.request.body, sample_data);
     do_check_eq(handler.request.getHeader("Content-Type"), "text/plain");
 
     server.stop(run_next_test);
   });
 });
 
 /**
  * HTTP PUT with a custom Content-Type header.
--- a/services/fxaccounts/Credentials.jsm
+++ b/services/fxaccounts/Credentials.jsm
@@ -99,20 +99,17 @@ this.Credentials = Object.freeze({
 
     let hkdfSalt = options.hkdfSalt || HKDF_SALT;
     let hkdfLength = options.hkdfLength || HKDF_LENGTH;
     let hmacLength = options.hmacLength || HMAC_LENGTH;
     let hmacAlgorithm = options.hmacAlgorithm || HMAC_ALGORITHM;
     let stretchedPWLength = options.stretchedPassLength || STRETCHED_PW_LENGTH_BYTES;
     let pbkdf2Rounds = options.pbkdf2Rounds || PBKDF2_ROUNDS;
 
-    let result = {
-      emailUTF8: emailInput,
-      passwordUTF8: passwordInput,
-    };
+    let result = {};
 
     let password = CommonUtils.encodeUTF8(passwordInput);
     let salt = this.keyWordExtended("quickStretch", emailInput);
 
     let runnable = () => {
       let start = Date.now();
       let quickStretchedPW = CryptoUtils.pbkdf2Generate(
           password, salt, pbkdf2Rounds, stretchedPWLength, hmacAlgorithm, hmacLength);
--- a/services/fxaccounts/FxAccountsClient.jsm
+++ b/services/fxaccounts/FxAccountsClient.jsm
@@ -87,17 +87,17 @@ this.FxAccountsClient.prototype = {
    *                               email
    *        }
    */
   _createSession: function(path, email, password, getKeys=false,
                            retryOK=true) {
     return Credentials.setup(email, password).then((creds) => {
       let data = {
         authPW: CommonUtils.bytesAsHex(creds.authPW),
-        email: creds.emailUTF8,
+        email: email,
       };
       let keys = getKeys ? "?keys=true" : "";
 
       return this._request(path + keys, "POST", null, data).then(
         // Include the canonical capitalization of the email in the response so
         // the caller can set its signed-in user state accordingly.
         result => {
           result.email = data.email;
--- a/services/fxaccounts/tests/xpcshell/test_client.js
+++ b/services/fxaccounts/tests/xpcshell/test_client.js
@@ -168,31 +168,35 @@ add_task(function* test_signUp() {
   let creationMessage_withKey = JSON.stringify({
     uid: "uid",
     sessionToken: "sessionToken",
     keyFetchToken: "keyFetchToken"
   });
   let errorMessage = JSON.stringify({code: 400, errno: 101, error: "account exists"});
   let created = false;
 
+  // Note these strings must be unicode and not already utf-8 encoded.
+  let unicodeUsername = "andr\xe9@example.org"; // 'andré@example.org'
+  let unicodePassword = "p\xe4ssw\xf6rd"; // 'pässwörd'
   let server = httpd_setup({
     "/account/create": function(request, response) {
       let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+      body = CommonUtils.decodeUTF8(body);
       let jsonBody = JSON.parse(body);
 
       // https://github.com/mozilla/fxa-auth-server/wiki/onepw-protocol#wiki-test-vectors
 
       if (created) {
         // Error trying to create same account a second time
         response.setStatusLine(request.httpVersion, 400, "Bad request");
         response.bodyOutputStream.write(errorMessage, errorMessage.length);
         return;
       }
 
-      if (jsonBody.email == "andré@example.org") {
+      if (jsonBody.email == unicodeUsername) {
         do_check_eq("", request._queryString);
         do_check_eq(jsonBody.authPW, "247b675ffb4c46310bc87e26d712153abe5e1c90ef00a4784594f97ef54f2375");
 
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.bodyOutputStream.write(creationMessage_noKey,
                                         creationMessage_noKey.length);
         return;
       }
@@ -202,39 +206,42 @@ add_task(function* test_signUp() {
         do_check_eq(jsonBody.authPW, "e5c1cdfdaa5fcee06142db865b212cc8ba8abee2a27d639d42c139f006cdb930");
         created = true;
 
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.bodyOutputStream.write(creationMessage_withKey,
                                         creationMessage_withKey.length);
         return;
       }
+      // just throwing here doesn't make any log noise, so have an assertion
+      // fail instead.
+      do_check_true(false, "unexpected email: " + jsonBody.email);
     },
   });
 
   // Try to create an account without retrieving optional keys.
   let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signUp('andré@example.org', 'pässwörd');
+  let result = yield client.signUp(unicodeUsername, unicodePassword);
   do_check_eq("uid", result.uid);
   do_check_eq("sessionToken", result.sessionToken);
   do_check_eq(undefined, result.keyFetchToken);
   do_check_eq(result.unwrapBKey,
               "de6a2648b78284fcb9ffa81ba95803309cfba7af583c01a8a1a63e567234dd28");
 
   // Try to create an account retrieving optional keys.
   result = yield client.signUp('you@example.org', 'pässwörd', true);
   do_check_eq("uid", result.uid);
   do_check_eq("sessionToken", result.sessionToken);
   do_check_eq("keyFetchToken", result.keyFetchToken);
   do_check_eq(result.unwrapBKey,
               "f589225b609e56075d76eb74f771ff9ab18a4dc0e901e131ba8f984c7fb0ca8c");
 
   // Try to create an existing account.  Triggers error path.
   try {
-    result = yield client.signUp('andré@example.org', 'pässwörd');
+    result = yield client.signUp(unicodeUsername, unicodePassword);
     do_throw("Expected to catch an exception");
   } catch(expectedError) {
     do_check_eq(101, expectedError.errno);
   }
 
   yield deferredStop(server);
 });
 
@@ -253,22 +260,25 @@ add_task(function* test_signIn() {
   });
   let errorMessage_wrongCap = JSON.stringify({
     code: 400,
     errno: 120,
     error: "Incorrect email case",
     email: "you@example.com"
   });
 
+  // Note this strings must be unicode and not already utf-8 encoded.
+  let unicodeUsername = "m\xe9@example.com" // 'mé@example.com'
   let server = httpd_setup({
     "/account/login": function(request, response) {
       let body = CommonUtils.readBytesFromInputStream(request.bodyInputStream);
+      body = CommonUtils.decodeUTF8(body);
       let jsonBody = JSON.parse(body);
 
-      if (jsonBody.email == "mé@example.com") {
+      if (jsonBody.email == unicodeUsername) {
         do_check_eq("", request._queryString);
         do_check_eq(jsonBody.authPW, "08b9d111196b8408e8ed92439da49206c8ecfbf343df0ae1ecefcd1e0174a8b6");
         response.setStatusLine(request.httpVersion, 200, "OK");
         response.bodyOutputStream.write(sessionMessage_noKey,
                                         sessionMessage_noKey.length);
         return;
       }
       else if (jsonBody.email == "you@example.com") {
@@ -293,17 +303,17 @@ add_task(function* test_signIn() {
                                         errorMessage_notExistent.length);
         return;
       }
     },
   });
 
   // Login without retrieving optional keys
   let client = new FxAccountsClient(server.baseURI);
-  let result = yield client.signIn('mé@example.com', 'bigsecret');
+  let result = yield client.signIn(unicodeUsername, 'bigsecret');
   do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
   do_check_eq(result.unwrapBKey,
               "c076ec3f4af123a615157154c6e1d0d6293e514fd7b0221e32d50517ecf002b8");
   do_check_eq(undefined, result.keyFetchToken);
 
   // Login with retrieving optional keys
   result = yield client.signIn('you@example.com', 'bigsecret', true);
   do_check_eq(FAKE_SESSION_TOKEN, result.sessionToken);
--- a/services/fxaccounts/tests/xpcshell/test_credentials.js
+++ b/services/fxaccounts/tests/xpcshell/test_credentials.js
@@ -66,40 +66,34 @@ add_task(function* test_onepw_setup_cred
   do_check_eq(b2h(unwrapKey), "8ff58975be391338e4ec5d7138b5ed7b65c7d1bfd1f3a4f93e05aa47d5b72be9");
 });
 
 add_task(function* test_client_stretch_kdf() {
   let pbkdf2 = CryptoUtils.pbkdf2Generate;
   let hkdf = CryptoUtils.hkdf;
   let expected = vectors["client stretch-KDF"];
 
-  let emailUTF8 = h2s(expected.email);
-  let passwordUTF8 = h2s(expected.password);
+  let email = h2s(expected.email);
+  let password = h2s(expected.password);
 
   // Intermediate value from sjcl implementation in fxa-js-client
   // The key thing is the c3a9 sequence in "andré"
-  let salt = Credentials.keyWordExtended("quickStretch", emailUTF8);
+  let salt = Credentials.keyWordExtended("quickStretch", email);
   do_check_eq(b2h(salt), "6964656e746974792e6d6f7a696c6c612e636f6d2f7069636c2f76312f717569636b537472657463683a616e6472c3a9406578616d706c652e6f7267");
 
   let options = {
     stretchedPassLength: 32,
     pbkdf2Rounds: 1000,
     hmacAlgorithm: Ci.nsICryptoHMAC.SHA256,
     hmacLength: 32,
     hkdfSalt: h2b("00"),
     hkdfLength: 32,
   };
 
-  let results = yield Credentials.setup(emailUTF8, passwordUTF8, options);
-
-  do_check_eq(emailUTF8, results.emailUTF8,
-      "emailUTF8 is wrong");
-
-  do_check_eq(passwordUTF8, results.passwordUTF8,
-      "passwordUTF8 is wrong");
+  let results = yield Credentials.setup(email, password, options);
 
   do_check_eq(expected.quickStretchedPW, b2h(results.quickStretchedPW),
       "quickStretchedPW is wrong");
 
   do_check_eq(expected.authPW, b2h(results.authPW),
       "authPW is wrong");
 });
 
--- a/testing/eslint-plugin-mozilla/lib/helpers.js
+++ b/testing/eslint-plugin-mozilla/lib/helpers.js
@@ -309,66 +309,64 @@ module.exports = {
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsHeadFile(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsHeadFile: function(scope) {
-    var pathAndFilename = scope.getFilename();
+    var pathAndFilename = this.cleanUpPath(scope.getFilename());
 
     return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
   },
 
   /**
    * Check whether we might be in an xpcshell test.
    *
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsXpcshellTest(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsXpcshellTest: function(scope) {
-    var pathAndFilename = scope.getFilename();
+    var pathAndFilename = this.cleanUpPath(scope.getFilename());
 
     return /.*[\\/]test_.+\.js$/.test(pathAndFilename);
   },
 
   /**
    * Check whether we are in a browser mochitest.
    *
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsBrowserMochitest(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsBrowserMochitest: function(scope) {
-    var pathAndFilename = scope.getFilename();
+    var pathAndFilename = this.cleanUpPath(scope.getFilename());
 
     return /.*[\\/]browser_.+\.js$/.test(pathAndFilename);
   },
 
   /**
    * Check whether we are in a test of some kind.
    *
    * @param  {RuleContext} scope
    *         You should pass this from within a rule
    *         e.g. helpers.getIsTest(this)
    *
    * @return {Boolean}
    *         True or false
    */
   getIsTest: function(scope) {
-    var pathAndFilename = scope.getFilename();
-
     if (this.getIsXpcshellTest(scope)) {
       return true;
     }
 
     return this.getIsBrowserMochitest(scope);
   },
 
   /**
@@ -400,25 +398,34 @@ module.exports = {
    * executed by a text editor's plugin.
    * The value returned by context.getFileName() varies because of this.
    * This helper function makes sure to return an absolute file path for the
    * current context, by looking at process.cwd().
    * @param {Context} context
    * @return {String} The absolute path
    */
   getAbsoluteFilePath: function(context) {
-    var fileName = context.getFilename();
+    var fileName = this.cleanUpPath(context.getFilename());
     var cwd = process.cwd();
 
     if (path.isAbsolute(fileName)) {
       // Case 2: executed from the repo's root with mach:
       //   fileName: /path/to/mozilla/repo/a/b/c/d.js
       //   cwd: /path/to/mozilla/repo
       return fileName;
     } else {
       // Case 1: executed form in a nested directory, e.g. from a text editor:
       //   fileName: a/b/c/d.js
       //   cwd: /path/to/mozilla/repo/a/b/c
       var dirName = path.dirname(fileName);
       return cwd.slice(0, cwd.length - dirName.length) + fileName;
     }
+  },
+
+  /**
+   * When ESLint is run from SublimeText, paths retrieved from
+   * context.getFileName contain leading and trailing double-quote characters.
+   * These characters need to be removed.
+   */
+  cleanUpPath: function(path) {
+    return path.replace(/^"/, "").replace(/"$/, "");
   }
 };
--- a/testing/talos/talos/talos-powers/chrome.manifest
+++ b/testing/talos/talos/talos-powers/chrome.manifest
@@ -1,4 +1,5 @@
 content talos-powers chrome/
+content talos-powers-content content/ contentaccessible=yes
 component {f5d53443-d58d-4a2f-8df0-98525d4f91ad} components/TalosPowersService.js
 contract @mozilla.org/talos/talos-powers-service;1 {f5d53443-d58d-4a2f-8df0-98525d4f91ad}
 category profile-after-change TalosPowersService @mozilla.org/talos/talos-powers-service;1
--- a/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
+++ b/testing/talos/talos/talos-powers/chrome/talos-powers-content.js
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-var { interfaces: Ci } = Components;
+var { interfaces: Ci, utils: Cu } = Components;
 
 /**
  * Content that wants to quit the whole session should
  * fire the TalosQuitApplication custom event. This will
  * attempt to force-quit the browser.
  */
 addEventListener("TalosQuitApplication", event => {
   // If we're loaded in a low-priority background process, like
@@ -17,8 +17,29 @@ addEventListener("TalosQuitApplication",
   let priority = docShell.QueryInterface(Ci.nsIDocumentLoader)
                          .loadGroup
                          .QueryInterface(Ci.nsISupportsPriority)
                          .priority;
   if (priority != Ci.nsISupportsPriority.PRIORITY_LOWEST) {
     sendAsyncMessage("Talos:ForceQuit", event.detail);
   }
 });
+
+addEventListener("TalosContentProfilerCommand", (e) => {
+  let name = e.detail.name;
+  let data = e.detail.data;
+  sendAsyncMessage("TalosContentProfiler:Command", { name, data });
+});
+
+addMessageListener("TalosContentProfiler:Response", (msg) => {
+  let name = msg.data.name;
+  let data = msg.data.data;
+
+  let event = Cu.cloneInto({
+    bubbles: true,
+    detail: {
+      name: name,
+      data: data,
+    },
+  }, content);
+  content.dispatchEvent(
+    new content.CustomEvent("TalosContentProfilerResponse", event));
+});
\ No newline at end of file
--- a/testing/talos/talos/talos-powers/components/TalosPowersService.js
+++ b/testing/talos/talos/talos-powers/components/TalosPowersService.js
@@ -2,63 +2,204 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 const { interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+  "resource://gre/modules/osfile.jsm");
 
 const FRAME_SCRIPT = "chrome://talos-powers/content/talos-powers-content.js";
 
-function TalosPowersService() {};
+function TalosPowersService() {
+  this.wrappedJSObject = this;
+};
 
 TalosPowersService.prototype = {
   classDescription: "Talos Powers",
   classID: Components.ID("{f5d53443-d58d-4a2f-8df0-98525d4f91ad}"),
   contractID: "@mozilla.org/talos/talos-powers-service;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver]),
 
   observe(subject, topic, data) {
     switch(topic) {
       case "profile-after-change":
         // Note that this observation is registered in the chrome.manifest
         // for this add-on.
         this.init();
         break;
-      case "sessionstore-windows-restored":
-        this.inject();
-        break;
       case "xpcom-shutdown":
         this.uninit();
         break;
     }
   },
 
   init() {
-    // We want to defer any kind of work until after sessionstore has
-    // finished in order not to skew sessionstore test numbers.
-    Services.obs.addObserver(this, "sessionstore-windows-restored", false);
+    Services.mm.loadFrameScript(FRAME_SCRIPT, true);
+    Services.mm.addMessageListener("Talos:ForceQuit", this);
+    Services.mm.addMessageListener("TalosContentProfiler:Command", this);
     Services.obs.addObserver(this, "xpcom-shutdown", false);
   },
 
   uninit() {
-    Services.obs.removeObserver(this, "sessionstore-windows-restored", false);
     Services.obs.removeObserver(this, "xpcom-shutdown", false);
   },
 
-  inject() {
-    Services.mm.loadFrameScript(FRAME_SCRIPT, true);
-    Services.mm.addMessageListener("Talos:ForceQuit", this);
+  receiveMessage(message) {
+    switch(message.name) {
+      case "Talos:ForceQuit": {
+        this.forceQuit(message.data);
+        break;
+      }
+      case "TalosContentProfiler:Command": {
+        this.receiveProfileCommand(message);
+        break;
+      }
+    }
+  },
+
+  /**
+   * Enable the SPS profiler with some settings and then pause
+   * immediately.
+   *
+   * @param data (object)
+   *        A JavaScript object with the following properties:
+   *
+   *        entries (int):
+   *          The sampling buffer size in bytes.
+   *
+   *        interval (int):
+   *          The sampling interval in milliseconds.
+   *
+   *        threadsArray (array of strings):
+   *          The thread names to sample.
+   */
+  profilerBegin(data) {
+    Services.profiler
+            .StartProfiler(data.entries, data.interval,
+                           ["js", "leaf", "stackwalk", "threads"], 4,
+                           data.threadsArray, data.threadsArray.length);
+
+    Services.profiler.PauseSampling();
+  },
+
+  /**
+   * Assuming the Profiler is running, dumps the Profile from all sampled
+   * processes and threads to the disk. The Profiler will be stopped once
+   * the profiles have been dumped. This method returns a Promise that
+   * will resolve once this has occurred.
+   *
+   * @param profileFile (string)
+   *        The name of the file to write to.
+   *
+   * @returns Promise
+   */
+  profilerFinish(profileFile) {
+    return new Promise((resolve, reject) => {
+      Services.profiler.getProfileDataAsync().then((profile) => {
+        let encoder = new TextEncoder();
+        let array = encoder.encode(JSON.stringify(profile));
+
+        OS.File.writeAtomic(profileFile, array, {
+          tmpPath: profileFile + ".tmp",
+        }).then(() => {
+          Services.profiler.StopProfiler();
+          resolve();
+        });
+      }, (error) => {
+        Cu.reportError("Failed to gather profile: " + error);
+        // FIXME: We should probably send a message down to the
+        // child which causes it to reject the waiting Promise.
+        reject();
+      });
+    });
   },
 
-  receiveMessage(message) {
-    if (message.name == "Talos:ForceQuit") {
-      this.forceQuit(message.data);
+  /**
+   * Pauses the Profiler, optionally setting a parent process marker before
+   * doing so.
+   *
+   * @param marker (string, optional)
+   *        A marker to set before pausing.
+   */
+  profilerPause(marker=null) {
+    if (marker) {
+      Services.profiler.AddMarker(marker);
+    }
+
+    Services.profiler.PauseSampling();
+  },
+
+  /**
+   * Resumes a pausedProfiler, optionally setting a parent process marker
+   * after doing so.
+   *
+   * @param marker (string, optional)
+   *        A marker to set after resuming.
+   */
+  profilerResume(marker=null) {
+    Services.profiler.ResumeSampling();
+
+    if (marker) {
+      Services.profiler.AddMarker(marker);
+    }
+  },
+
+  /**
+   * Adds a marker to the Profile in the parent process.
+   */
+  profilerMarker(marker) {
+    Services.profiler.AddMarker(marker);
+  },
+
+  receiveProfileCommand(message) {
+    const ACK_NAME = "TalosContentProfiler:Response";
+    let mm = message.target.messageManager;
+    let name = message.data.name;
+    let data = message.data.data;
+
+    switch(name) {
+      case "Profiler:Begin": {
+        this.profilerBegin(data);
+        // profilerBegin will cause the parent to send an async message to any
+        // child processes to start profiling. Because messages are serviced
+        // in order, we know that by the time that the child services the
+        // ACK message, that the profiler has started in its process.
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Finish": {
+        // The test is done. Dump the profile.
+        let profileFile = data.profileFile;
+        this.profilerFinish(data.profileFile).then(() => {
+          mm.sendAsyncMessage(ACK_NAME, { name });
+        });
+        break;
+      }
+
+      case "Profiler:Pause": {
+        this.profilerPause(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Resume": {
+        this.profilerResume(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+        break;
+      }
+
+      case "Profiler:Marker": {
+        this.profilerMarker(data.marker);
+        mm.sendAsyncMessage(ACK_NAME, { name });
+      }
     }
   },
 
   forceQuit(messageData) {
     if (messageData && messageData.waitForSafeBrowsing) {
       let SafeBrowsing = Cu.import("resource://gre/modules/SafeBrowsing.jsm", {}).SafeBrowsing;
 
       let whenDone = () => {
new file mode 100644
--- /dev/null
+++ b/testing/talos/talos/talos-powers/content/TalosContentProfiler.js
@@ -0,0 +1,256 @@
+/* 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/. */
+
+/**
+ * This utility script is for instrumenting your Talos test for
+ * performance profiles while running within content. If your test
+ * is running in the parent process, you should use
+ * TalosParentProfiler.js instead to avoid the messaging overhead.
+ */
+
+var TalosContentProfiler;
+
+(function() {
+
+  // Whether or not this TalosContentProfiler object has had initFromObject
+  // or initFromURLQueryParams called on it. Any functions that will send
+  // events to the parent to change the behaviour of the SPS Profiler
+  // should only be called after calling either initFromObject or
+  // initFromURLQueryParams.
+  var initted = false;
+
+  // The subtest name that beginTest() was called with.
+  var currentTest = "unknown";
+
+  // Profiler settings.
+  var interval, entries, threadsArray, profileDir;
+
+  try {
+    // Outside of talos, this throws a security exception which no-op this file.
+    // (It's not required nor allowed for addons since Firefox 17)
+    // It's used inside talos from non-privileged pages (like during tscroll),
+    // and it works because talos disables all/most security measures.
+    netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  } catch (e) {}
+
+  Components.utils.import("resource://gre/modules/Services.jsm");
+
+  /**
+   * Emits a TalosContentProfiler prefixed event and then returns a Promise
+   * that resolves once a corresponding acknowledgement event is
+   * dispatched on our document.
+   *
+   * @param name
+   *        The name of the event that will be TalosContentProfiler prefixed and
+   *        eventually sent to the parent.
+   * @param data (optional)
+   *        The data that will be sent to the parent.
+   * @returns Promise
+   *        Resolves when a corresponding acknowledgement event is dispatched
+   *        on this document.
+   */
+  function sendEventAndWait(name, data={}) {
+    return new Promise((resolve) => {
+      var event = new CustomEvent("TalosContentProfilerCommand", {
+        bubbles: true,
+        detail: {
+          name: name,
+          data: data,
+        }
+      });
+      document.dispatchEvent(event);
+
+      addEventListener("TalosContentProfilerResponse", function onResponse(event) {
+        if (event.detail.name != name) {
+          return;
+        }
+
+        removeEventListener("TalosContentProfilerResponse", onResponse);
+
+        resolve(event.detail.data);
+      });
+    });
+  }
+
+  /**
+   * Parses an url query string into a JS object.
+   *
+   * @param locationSearch (string)
+   *        The location string to parse.
+   * @returns Object
+   *        The GET params from the location string as
+   *        key-value pairs in the Object.
+   */
+  function searchToObject(locationSearch) {
+    var pairs = locationSearch.substring(1).split("&");
+    var result = {};
+
+    for (var i in pairs) {
+      if (pairs[i] !== "") {
+        var pair = pairs[i].split("=");
+        result[decodeURIComponent(pair[0])] = decodeURIComponent(pair[1] || "");
+      }
+    }
+
+    return result;
+  }
+
+  TalosContentProfiler = {
+    /**
+     * Initialize the profiler using profiler settings supplied in a JS object.
+     *
+     * @param obj (object)
+     *   The following properties on the object are respected:
+     *     sps_profile_interval (int)
+     *     sps_profile_entries (int)
+     *     sps_profile_threads (string, comma separated list of threads to filter with)
+     *     sps_profile_dir (string)
+     */
+    initFromObject(obj={}) {
+      if (!initted) {
+        if (("sps_profile_dir" in obj) && typeof obj.sps_profile_dir == "string" &&
+            ("sps_profile_interval" in obj) && Number.isFinite(obj.sps_profile_interval * 1) &&
+            ("sps_profile_entries" in obj) && Number.isFinite(obj.sps_profile_entries * 1) &&
+            ("sps_profile_threads" in obj) && typeof obj.sps_profile_threads == "string") {
+          interval = obj.sps_profile_interval;
+          entries = obj.sps_profile_entries;
+          threadsArray = obj.sps_profile_threads.split(",");
+          profileDir = obj.sps_profile_dir;
+          initted = true;
+        } else {
+          console.error("Profiler could not init with object: " + JSON.stringify(obj));
+        }
+      }
+    },
+
+    /**
+     * Initialize the profiler using a string from a location string.
+     *
+     * @param locationSearch (string)
+     *        The location string to initialize with.
+     */
+    initFromURLQueryParams(locationSearch) {
+      this.initFromObject(searchToObject(locationSearch));
+    },
+
+    /**
+     * A Talos test is about to start. This will return a Promise that
+     * resolves once the Profiler has been initialized. Note that the
+     * SPS profiler will be paused immediately after starting and that
+     * resume() should be called in order to collect samples.
+     *
+     * @param testName (string)
+     *        The name of the test to use in Profiler markers.
+     * @returns Promise
+     *        Resolves once the SPS profiler has been initialized and paused.
+     */
+    beginTest(testName) {
+      if (initted) {
+        currentTest = testName;
+        return sendEventAndWait("Profiler:Begin", {