Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 28 Jan 2015 14:34:59 +0100
changeset 239646 b2d157947a805e5223570c9e6e9a8c81f42e2249
parent 239645 12d53e11bbde0b8086da04e753a637f0be3d4c4e (current diff)
parent 239605 d8260c1a0689da5f4f3252bf41063e8a03c8fbbc (diff)
child 239647 8778af5d1a0922b0e94005cb80a0bcaa1ad9a98c
push id500
push userjoshua.m.grant@gmail.com
push dateThu, 29 Jan 2015 01:48:36 +0000
milestone38.0a1
Merge mozilla-central to b2g-inbound
dom/base/CompositionStringSynthesizer.cpp
dom/base/CompositionStringSynthesizer.h
dom/interfaces/base/nsICompositionStringSynthesizer.idl
mobile/android/config/mozconfigs/android-armv6/debug
mobile/android/config/mozconfigs/android-armv6/l10n-nightly
mobile/android/config/mozconfigs/android-armv6/l10n-release
mobile/android/config/mozconfigs/android-armv6/nightly
mobile/android/config/mozconfigs/android-armv6/release
mobile/android/config/mozconfigs/android/debug
mobile/android/config/mozconfigs/android/l10n-nightly
mobile/android/config/mozconfigs/android/l10n-release
mobile/android/config/mozconfigs/android/nightly
mobile/android/config/mozconfigs/android/release
mobile/android/themes/core/images/reader-minus-icon-hdpi.png
mobile/android/themes/core/images/reader-minus-icon-mdpi.png
mobile/android/themes/core/images/reader-minus-icon-xhdpi.png
mobile/android/themes/core/images/reader-plus-icon-hdpi.png
mobile/android/themes/core/images/reader-plus-icon-mdpi.png
mobile/android/themes/core/images/reader-plus-icon-xhdpi.png
testing/web-platform/meta/html/dom/documents/resource-metadata-management/document-cookie.html.ini
xpcom/tests/TestUTF.cpp
xpcom/tests/UTFStrings.h
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-bug 1114669 removes nsIStreamCipher.idl, which requires a clobber according to bug 1114669
+Bug 917322 - Due to removing nsICompositionStringSynthesizer.idl
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -505,17 +505,17 @@ HyperTextAccessible::FindOffset(uint32_t
 
   const bool kIsJumpLinesOk = true; // okay to jump lines
   const bool kIsScrollViewAStop = false; // do not stop at scroll views
   const bool kIsKeyboardSelect = true; // is keyboard selection
   const bool kIsVisualBidi = false; // use visual order for bidi text
   nsPeekOffsetStruct pos(aAmount, aDirection, innerContentOffset,
                          nsPoint(0, 0), kIsJumpLinesOk, kIsScrollViewAStop,
                          kIsKeyboardSelect, kIsVisualBidi,
-                         aWordMovementType);
+                         false, aWordMovementType);
   nsresult rv = frameAtOffset->PeekOffset(&pos);
 
   // PeekOffset fails on last/first lines of the text in certain cases.
   if (NS_FAILED(rv) && aAmount == eSelectLine) {
     pos.mAmount = (aDirection == eDirNext) ? eSelectEndLine : eSelectBeginLine;
     frameAtOffset->PeekOffset(&pos);
   }
   if (!pos.mResultContent) {
--- a/b2g/components/HelperAppDialog.js
+++ b/b2g/components/HelperAppDialog.js
@@ -28,24 +28,16 @@ HelperAppLauncherDialog.prototype = {
   classID: Components.ID("{710322af-e6ae-4b0c-b2c9-1474a87b077e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIHelperAppLauncherDialog]),
 
   show: function(aLauncher, aContext, aReason) {
     aLauncher.MIMEInfo.preferredAction = Ci.nsIMIMEInfo.saveToDisk;
     aLauncher.saveToDisk(null, false);
   },
 
-  promptForSaveToFile: function(aLauncher,
-                                aContext,
-                                aDefaultFile,
-                                aSuggestedFileExt,
-                                aForcePrompt) {
-    throw Cr.NS_ERROR_NOT_AVAILABLE;
-  },
-
   promptForSaveToFileAsync: function(aLauncher,
                                      aContext,
                                      aDefaultFile,
                                      aSuggestedFileExt,
                                      aForcePrompt) {
     // Retrieve the user's default download directory.
     Task.spawn(function() {
       let file = null;
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1706,16 +1706,21 @@ pref("plain_text.wrap_long_lines", true)
 
 // If this turns true, Moz*Gesture events are not called stopPropagation()
 // before content.
 pref("dom.debug.propagate_gesture_events_through_content", false);
 
 // The request URL of the GeoLocation backend.
 pref("geo.wifi.uri", "https://location.services.mozilla.com/v1/geolocate?key=%MOZILLA_API_KEY%");
 
+// On Mac, the default geo provider is corelocation.
+#ifdef XP_MACOSX
+pref("geo.provider.use_corelocation", true);
+#endif
+
 // Necko IPC security checks only needed for app isolation for cookies/cache/etc:
 // currently irrelevant for desktop e10s
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // CustomizableUI state of the browser's user interface
--- a/browser/base/content/aboutNetError.xhtml
+++ b/browser/base/content/aboutNetError.xhtml
@@ -212,19 +212,25 @@
 
         if (err == "cspBlocked") {
           // Remove the "Try again" button for CSP violations, since it's
           // almost certainly useless. (Bug 553180)
           document.getElementById("errorTryAgain").style.display = "none";
         }
 
         window.addEventListener("AboutNetErrorOptions", function(evt) {
-        // Pinning errors are of type nssFailure2 (don't ask me why)
+        // Pinning errors are of type nssFailure2
           if (getErrorCode() == "nssFailure2" && !errTitle.hasAttribute("sslv3")) {
-          // TODO: and the pref is set...
+            var learnMoreLink = document.getElementById("learnMoreLink");
+            // nssFailure2 also gets us other non-overrideable errors. Choose
+            // a "learn more" link based on description:
+            if (getDescription().contains("mozilla_pkix_error_key_pinning_failure")) {
+              learnMoreLink.href = "https://support.mozilla.org/kb/certificate-pinning-reports";
+            }
+
             var options = JSON.parse(evt.detail);
             if (options && options.enabled) {
               var checkbox = document.getElementById('automaticallyReportInFuture');
               showCertificateErrorReporting();
               if (options.automatic) {
                 // set the checkbox
                 checkbox.checked = true;
               }
@@ -471,18 +477,17 @@
       </div>
 
       <div id="certificateErrorReportingPanel">
         <p>&errorReporting.longDesc;</p>
         <p>
           <input type="checkbox" id="automaticallyReportInFuture" />
           <label for="automaticallyReportInFuture" id="automaticallyReportInFuture">&errorReporting.automatic;</label>
         </p>
-        <!-- TODO add link to relevant page on sumo -->
-        <a href="https://support.mozilla.org/kb/certificate-pinning-reports" target="new">&errorReporting.learnMore;</a>
+        <a href="https://support.mozilla.org/kb/tls-error-reports" id="learnMoreLink" target="new">&errorReporting.learnMore;</a>
         <span id="reportingState">
           <button id="reportCertificateError">&errorReporting.report;</button>
           <button id="reportCertificateErrorRetry">&errorReporting.tryAgain;</button>
           <span id="reportSendingMessage">&errorReporting.sending;</span>
           <span id="reportSentMessage">&errorReporting.sent;</span>
         </span>
       </div>
 
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4756,28 +4756,32 @@ nsBrowserAccess.prototype = {
 
     if (aWhere == Ci.nsIBrowserDOMWindow.OPEN_DEFAULTWINDOW) {
       if (isExternal &&
           gPrefService.prefHasUserValue("browser.link.open_newwindow.override.external"))
         aWhere = gPrefService.getIntPref("browser.link.open_newwindow.override.external");
       else
         aWhere = gPrefService.getIntPref("browser.link.open_newwindow");
     }
+    let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
     switch (aWhere) {
       case Ci.nsIBrowserDOMWindow.OPEN_NEWWINDOW :
         // FIXME: Bug 408379. So how come this doesn't send the
         // referrer like the other loads do?
         var url = aURI ? aURI.spec : "about:blank";
+        let features = "all,dialog=no";
+        if (isPrivate) {
+          features += ",private";
+        }
         // Pass all params to openDialog to ensure that "url" isn't passed through
         // loadOneOrMoreURIs, which splits based on "|"
-        newWindow = openDialog(getBrowserURL(), "_blank", "all,dialog=no", url, null, null, null);
+        newWindow = openDialog(getBrowserURL(), "_blank", features, url, null, null, null);
         break;
       case Ci.nsIBrowserDOMWindow.OPEN_NEWTAB :
         let referrer = aOpener ? makeURI(aOpener.location.href) : null;
-        let isPrivate = PrivateBrowsingUtils.isWindowPrivate(aOpener || window);
         let browser = this._openURIInNewTab(aURI, referrer, isPrivate, isExternal);
         if (browser)
           newWindow = browser.contentWindow;
         break;
       default : // OPEN_CURRENTWINDOW or an illegal value
         newWindow = content;
         if (aURI) {
           let referrer = aOpener ? makeURI(aOpener.location.href) : null;
--- a/browser/base/content/test/general/browser_aboutHome.js
+++ b/browser/base/content/test/general/browser_aboutHome.js
@@ -384,27 +384,27 @@ let gTests = [
   desc: "Clicking suggestion list while composing",
   setup: function() {},
   run: function()
   {
     return Task.spawn(function* () {
       // Start composition and type "x"
       let input = gBrowser.contentDocument.getElementById("searchText");
       input.focus();
-      EventUtils.synthesizeComposition({ type: "compositionstart", data: "" });
-      EventUtils.synthesizeComposition({ type: "compositionupdate", data: "x" });
+      EventUtils.synthesizeComposition({ type: "compositionstart", data: "" },
+                                       gBrowser.contentWindow);
       EventUtils.synthesizeCompositionChange({
         composition: {
           string: "x",
           clauses: [
-            { length: 1, attr: EventUtils.COMPOSITION_ATTR_RAWINPUT }
+            { length: 1, attr: EventUtils.COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         caret: { start: 1, length: 0 }
-      });
+      }, gBrowser.contentWindow);
 
       // Wait for the search suggestions to become visible.
       let table =
         gBrowser.contentDocument.getElementById("searchSuggestionTable");
       let deferred = Promise.defer();
       let observer = new MutationObserver(() => {
         if (input.getAttribute("aria-expanded") == "true") {
           observer.disconnect();
--- a/browser/base/content/test/general/browser_searchSuggestionUI.js
+++ b/browser/base/content/test/general/browser_searchSuggestionUI.js
@@ -187,18 +187,16 @@ add_task(function* formHistory() {
   yield msg("reset");
 });
 
 add_task(function* composition() {
   yield setUp();
 
   let state = yield msg("startComposition", { data: "" });
   checkState(state, "", [], -1);
-  state = yield msg("updateComposition", { data: "x" });
-  checkState(state, "", [], -1);
   state = yield msg("changeComposition", { data: "x", waitForSuggestions: true });
   checkState(state, "x", ["xfoo", "xbar"], -1);
 
   // Mouse over the first suggestion.
   state = yield msg("mousemove", 0);
   checkState(state, "x", ["xfoo", "xbar"], 0);
 
   // Mouse over the second suggestion.
--- a/browser/base/content/test/general/searchSuggestionUI.js
+++ b/browser/base/content/test/general/searchSuggestionUI.js
@@ -23,34 +23,27 @@ let messageHandlers = {
   key: function (arg) {
     let keyName = typeof(arg) == "string" ? arg : arg.key;
     content.synthesizeKey(keyName, {});
     let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
     wait(ack);
   },
 
   startComposition: function (arg) {
-    let data = typeof(arg) == "string" ? arg : arg.data;
-    content.synthesizeComposition({ type: "compositionstart", data: data });
-    ack();
-  },
-
-  updateComposition: function (arg) {
-    let data = typeof(arg) == "string" ? arg : arg.data;
-    content.synthesizeComposition({ type: "compositionupdate", data: data });
+    content.synthesizeComposition({ type: "compositionstart", data: "" });
     ack();
   },
 
   changeComposition: function (arg) {
     let data = typeof(arg) == "string" ? arg : arg.data;
     content.synthesizeCompositionChange({
       composition: {
         string: data,
         clauses: [
-          { length: data.length, attr: content.COMPOSITION_ATTR_RAWINPUT }
+          { length: data.length, attr: content.COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
       caret: { start: data.length, length: 0 }
     });
     let wait = arg.waitForSuggestions ? waitForSuggestions : cb => cb();
     wait(ack);
   },
 
--- a/browser/base/content/urlbarBindings.xml
+++ b/browser/base/content/urlbarBindings.xml
@@ -1263,16 +1263,19 @@
       <handler event="command"><![CDATA[
         let target = event.originalTarget;
         if (target.classList.contains("addengine-item")) {
           // On success, hide and reshow the panel to show the new engine.
           let installCallback = {
             onSuccess: function(engine) {
               event.target.hidePopup();
               BrowserSearch.searchBar.openSuggestionsPanel();
+            },
+            onError: function(errorCode) {
+              Components.utils.reportError("Error adding search engine: " + errorCode);
             }
           }
           Services.search.addEngine(target.getAttribute("uri"),
                                     Ci.nsISearchEngine.DATA_XML,
                                     target.getAttribute("image"), false,
                                     installCallback);
         }
       ]]></handler>
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -46,23 +46,16 @@ const kPrefWebIDEInNavbar            = "
  * of providing onViewShowing and onViewHiding event handlers.
  */
 const kSubviewEvents = [
   "ViewShowing",
   "ViewHiding"
 ];
 
 /**
- * The method name to use for ES6 iteration. If Symbols are enabled in
- * this build, use Symbol.iterator; otherwise "@@iterator".
- */
-const JS_HAS_SYMBOLS = typeof Symbol === "function";
-const kIteratorSymbol = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-
-/**
  * The current version. We can use this to auto-add new default widgets as necessary.
  * (would be const but isn't because of testing purposes)
  */
 let kVersion = 4;
 
 /**
  * gPalette is a map of every widget that CustomizableUI.jsm knows about, keyed
  * on their IDs.
@@ -2705,17 +2698,17 @@ this.CustomizableUI = {
 
 
   /**
    * An iteratable property of windows managed by CustomizableUI.
    * Note that this can *only* be used as an iterator. ie:
    *     for (let window of CustomizableUI.windows) { ... }
    */
   windows: {
-    *[kIteratorSymbol]() {
+    *[Symbol.iterator]() {
       for (let [window,] of gBuildWindows)
         yield window;
     }
   },
 
   /**
    * Add a listener object that will get fired for various events regarding
    * customization.
--- a/browser/components/loop/content/libs/l10n.js
+++ b/browser/components/loop/content/libs/l10n.js
@@ -35,17 +35,16 @@
       return name in args ? args[name] : '{{' + name + '}}';
     });
   }
 
   // translate a string
   function translateString(key, args, fallback) {
     if (args && args.num) {
       var num = args && args.num;
-      delete args.num;
     }
     var data = getL10nData(key, num);
     if (!data && fallback)
       data = {textContent: fallback};
     if (!data)
       return '{{' + key + '}}';
     return substArguments(data.textContent, args);
   }
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -68,16 +68,17 @@
   <!-- Test scripts -->
   <script src="conversationAppStore_test.js"></script>
   <script src="client_test.js"></script>
   <script src="conversation_test.js"></script>
   <script src="panel_test.js"></script>
   <script src="roomViews_test.js"></script>
   <script src="conversationViews_test.js"></script>
   <script src="contacts_test.js"></script>
+  <script src="l10n_test.js"></script>
   <script>
     // Stop the default init functions running to avoid conflicts in tests
     document.removeEventListener('DOMContentLoaded', loop.panel.init);
     document.removeEventListener('DOMContentLoaded', loop.conversation.init);
 
     describe("Uncaught Error Check", function() {
       it("should load the tests without errors", function() {
         expect(uncaughtError && uncaughtError.message).to.be.undefined;
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/desktop-local/l10n_test.js
@@ -0,0 +1,37 @@
+/* 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 expect = chai.expect;
+
+describe("document.mozL10n", function() {
+  "use strict";
+
+  var fakeMozLoop;
+
+  beforeEach(function() {
+    fakeMozLoop = {
+      locale: "en-US",
+      getStrings: function(key) {
+        if (key === "plural") {
+          return '{"textContent":"{{num}} plural form;{{num}} plural forms"}';
+        }
+
+        return '{"textContent":"' + key + '"}';
+      },
+      getPluralForm: function(num, string) {
+        return string.split(";")[num === 0 ? 0 : 1];
+      }
+    };
+
+    document.mozL10n.initialize(fakeMozLoop);
+  });
+
+  it("should get a simple string", function() {
+    expect(document.mozL10n.get("test")).eql("test");
+  });
+
+  it("should get a plural form", function() {
+    expect(document.mozL10n.get("plural", {num:10})).eql("10 plural forms");
+  });
+});
--- a/browser/components/loop/test/functional/serversetup.py
+++ b/browser/components/loop/test/functional/serversetup.py
@@ -18,27 +18,27 @@ from config import *
 
 CONTENT_SERVER_COMMAND = ["make", "runserver"]
 CONTENT_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 CONTENT_SERVER_ENV.update({"PORT": str(CONTENT_SERVER_PORT),
                            "LOOP_SERVER_PORT": str(LOOP_SERVER_PORT)})
 
-WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
-              "/content/#call/{token}"
+ROOMS_WEB_APP_URL = "http://localhost:" + str(CONTENT_SERVER_PORT) + \
+  "/content/{token}"
 
 LOOP_SERVER_COMMAND = ["make", "runserver"]
 LOOP_SERVER_ENV = os.environ.copy()
 # Set PORT so that it does not interfere with any other
 # development server that might be running
 LOOP_SERVER_ENV.update({"NODE_ENV": "dev",
                         "PORT": str(LOOP_SERVER_PORT),
                         "SERVER_ADDRESS": "localhost:" + str(LOOP_SERVER_PORT),
-                        "WEB_APP_URL": WEB_APP_URL})
+                        "ROOMS_WEB_APP_URL": ROOMS_WEB_APP_URL})
 
 
 class LoopTestServers:
     def __init__(self):
         self.loop_server = self.start_loop_server()
         self.content_server = self.start_content_server()
 
     @staticmethod
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -122,25 +122,24 @@ class Test1BrowserCall(MarionetteTestCas
 
         # Join the room
         join_button = self.wait_for_element_displayed(By.CLASS_NAME,
                                                       "btn-join")
         join_button.click()
 
     # Assumes the standlone or the conversation window is selected first.
     def check_remote_video(self):
-        # TODO: This is disabled currently due to bug 1122486
-        # video_wrapper = self.wait_for_element_displayed(By.CSS_SELECTOR, ".media .OT_subscriber .OT_video-container", 20)
-        # video = self.wait_for_subelement_displayed(video_wrapper, By.TAG_NAME, "video")
+        video_wrapper = self.wait_for_element_displayed(
+            By.CSS_SELECTOR,
+            ".media .OT_subscriber .OT_video-container", 20)
+        video = self.wait_for_subelement_displayed(
+            video_wrapper, By.TAG_NAME, "video")
 
-        # self.wait_for_element_attribute_to_be_false(video, "paused")
-        # self.assertEqual(video.get_attribute("ended"), "false")
-
-        # Due to the above waits being disabled, we do a sleep.
-        sleep(15)
+        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_remote_video()
 
     def local_check_remote_video(self):
         self.switch_to_chatbox()
         self.check_remote_video()
--- a/browser/components/preferences/advanced.js
+++ b/browser/components/preferences/advanced.js
@@ -26,16 +26,22 @@ var gAdvancedPane = {
       advancedPrefs.selectedTab = document.getElementById(extraArgs["advancedTab"]);
     } else {
       var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
       if (preference.value !== null)
         advancedPrefs.selectedIndex = preference.value;
     }
 
 #ifdef MOZ_UPDATER
+    let onUnload = function () {
+      window.removeEventListener("unload", onUnload, false);
+      Services.prefs.removeObserver("app.update.", this);
+    }.bind(this);
+    window.addEventListener("unload", onUnload, false);
+    Services.prefs.addObserver("app.update.", this, false);
     this.updateReadPrefs();
 #endif
     this.updateOfflineApps();
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
@@ -817,10 +823,20 @@ var gAdvancedPane = {
   /**
    * Displays a dialog from which the user can manage his security devices.
    */
   showSecurityDevices: function ()
   {
     document.documentElement.openWindow("mozilla:devicemanager",
                                         "chrome://pippki/content/device_manager.xul",
                                         "", null);
-  }
+  },
+
+#ifdef MOZ_UPDATER
+  observe: function (aSubject, aTopic, aData) {
+    switch(aTopic) {
+      case "nsPref:changed":
+        this.updateReadPrefs();
+        break;
+    }
+  },
+#endif
 };
--- a/browser/components/preferences/in-content/advanced.js
+++ b/browser/components/preferences/in-content/advanced.js
@@ -24,16 +24,22 @@ var gAdvancedPane = {
     this._inited = true;
     var advancedPrefs = document.getElementById("advancedPrefs");
 
     var preference = document.getElementById("browser.preferences.advanced.selectedTabIndex");
     if (preference.value !== null)
         advancedPrefs.selectedIndex = preference.value;
 
 #ifdef MOZ_UPDATER
+    let onUnload = function () {
+      window.removeEventListener("unload", onUnload, false);
+      Services.prefs.removeObserver("app.update.", this);
+    }.bind(this);
+    window.addEventListener("unload", onUnload, false);
+    Services.prefs.addObserver("app.update.", this, false);
     this.updateReadPrefs();
 #endif
     this.updateOfflineApps();
 #ifdef MOZ_CRASHREPORTER
     this.initSubmitCrashes();
 #endif
     this.initTelemetry();
 #ifdef MOZ_SERVICES_HEALTHREPORT
@@ -836,10 +842,20 @@ var gAdvancedPane = {
   /**
    * Displays a dialog from which the user can manage his security devices.
    */
   showSecurityDevices: function ()
   {
     openDialog("chrome://pippki/content/device_manager.xul",
                "mozilla:devicemanager",
                "modal=yes", null);
-  }
+  },
+
+#ifdef MOZ_UPDATER
+  observe: function (aSubject, aTopic, aData) {
+    switch(aTopic) {
+      case "nsPref:changed":
+        this.updateReadPrefs();
+        break;
+    }
+  },
+#endif
 };
--- a/browser/components/uitour/UITour-lib.js
+++ b/browser/components/uitour/UITour-lib.js
@@ -93,16 +93,25 @@ if (typeof Mozilla == 'undefined') {
   };
 
 	Mozilla.UITour.registerPageID = function(pageID) {
 		_sendEvent('registerPageID', {
 			pageID: pageID
 		});
 	};
 
+	Mozilla.UITour.showHeartbeat = function(message, thankyouMessage, flowId, engagementURL) {
+		_sendEvent('showHeartbeat', {
+			message: message,
+			thankyouMessage: thankyouMessage,
+			flowId: flowId,
+			engagementURL: engagementURL
+		});
+	};
+
 	Mozilla.UITour.showHighlight = function(target, effect) {
 		_sendEvent('showHighlight', {
 			target: target,
 			effect: effect
 		});
 	};
 
 	Mozilla.UITour.hideHighlight = function() {
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -8,16 +8,18 @@ this.EXPORTED_SYMBOLS = ["UITour", "UITo
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
+Cu.importGlobalProperties(["URL"]);
+
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
   "resource://gre/modules/ResetProfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
@@ -113,21 +115,25 @@ this.UITour = {
                                                         "toolbarbutton-icon");
       },
       widgetName: "PanelUI-customize",
     }],
     ["devtools",    {query: "#developer-button"}],
     ["help",        {query: "#PanelUI-help"}],
     ["home",        {query: "#home-button"}],
     ["forget", {
+      allowAdd: true,
       query: "#panic-button",
       widgetName: "panic-button",
-      allowAdd: true,
     }],
-    ["loop",        {query: "#loop-button"}],
+    ["loop",        {
+      allowAdd: true,
+      query: "#loop-button",
+      widgetName: "loop-button",
+    }],
     ["loop-newRoom", {
       infoPanelPosition: "leftcenter topright",
       query: (aDocument) => {
         let loopBrowser = aDocument.querySelector("#loop-notification-panel > #loop-panel-iframe");
         if (!loopBrowser) {
           return null;
         }
         // Use the parentElement full-width container of the button so our arrow
@@ -324,17 +330,16 @@ this.UITour = {
       return;
     }
 
     Services.prefs.setCharPref(PREF_SEENPAGEIDS,
                                JSON.stringify([...this.seenPageIDs]));
   },
 
   onPageEvent: function(aMessage, aEvent) {
-    let contentDocument = null;
     let browser = aMessage.target;
     let window = browser.ownerDocument.defaultView;
     let tab = window.gBrowser.getTabForBrowser(browser);
     let messageManager = browser.messageManager;
 
     log.debug("onPageEvent:", aEvent.detail, aMessage);
 
     if (typeof aEvent.detail != "object") {
@@ -359,20 +364,16 @@ this.UITour = {
         !BACKGROUND_PAGE_ACTIONS_ALLOWED.has(action)) {
       log.warn("Ignoring disallowed action from a hidden page:", action);
       return false;
     }
 
     // Do this before bailing if there's no tab, so later we can pick up the pieces:
     window.gBrowser.tabContainer.addEventListener("TabSelect", this);
 
-    if (!window.gMultiProcessBrowser) { // Non-e10s. See bug 1089000.
-      contentDocument = browser.contentWindow.document;
-    }
-
     switch (action) {
       case "registerPageID": {
         // This is only relevant if Telemtry is enabled.
         if (!UITelemetry.enabled) {
           log.debug("registerPageID: Telemery disabled, not doing anything");
           break;
         }
 
@@ -386,16 +387,39 @@ this.UITour = {
 
         this.addSeenPageID(data.pageID);
         this.pageIDSourceBrowsers.set(browser, data.pageID);
         this.setTelemetryBucket(data.pageID);
 
         break;
       }
 
+      case "showHeartbeat": {
+        // Validate the input parameters.
+        if (typeof data.message !== "string" || data.message === "") {
+          log.error("showHeartbeat: Invalid message specified.");
+          break;
+        }
+
+        if (typeof data.thankyouMessage !== "string" || data.thankyouMessage === "") {
+          log.error("showHeartbeat: Invalid thank you message specified.");
+          break;
+        }
+
+        if (typeof data.flowId !== "string" || data.flowId === "") {
+          log.error("showHeartbeat: Invalid flowId specified.");
+          break;
+        }
+
+        // Finally show the Heartbeat UI.
+        this.showHeartbeat(window, messageManager, data.message, data.thankyouMessage, data.flowId,
+                           data.engagementURL);
+        break;
+      }
+
       case "showHighlight": {
         let targetPromise = this.getTarget(window, data.target);
         targetPromise.then(target => {
           if (!target.node) {
             log.error("UITour: Target could not be resolved: " + data.target);
             return;
           }
           let effect = undefined;
@@ -502,16 +526,17 @@ this.UITour = {
         try {
           uri = Services.io.newURI(data.url, null, null);
         } catch (e) {
           log.warn("startUrlbarCapture: Malformed URL specified");
           return false;
         }
 
         let secman = Services.scriptSecurityManager;
+        let contentDocument = browser.contentWindow.document;
         let principal = contentDocument.nodePrincipal;
         let flags = secman.DISALLOW_INHERIT_PRINCIPAL;
         try {
           secman.checkLoadURIWithPrincipal(principal, uri, flags);
         } catch (e) {
           log.warn("startUrlbarCapture: Orginating page doesn't have permission to open specified URL");
           return false;
         }
@@ -544,17 +569,17 @@ this.UITour = {
         this.setConfiguration(data.configuration, data.value);
         break;
       }
 
       case "showFirefoxAccounts": {
         // 'signup' is the only action that makes sense currently, so we don't
         // accept arbitrary actions just to be safe...
         // We want to replace the current tab.
-        contentDocument.location.href = "about:accounts?action=signup&entrypoint=uitour";
+        browser.loadURI("about:accounts?action=signup&entrypoint=uitour");
         break;
       }
 
       case "resetFirefox": {
         // Open a reset profile dialog window.
         ResetProfile.openConfirmationDialog(window);
         break;
       }
@@ -955,16 +980,147 @@ this.UITour = {
       LightweightThemeManager.previewTheme(data);
   },
 
   resetTheme: function() {
     LightweightThemeManager.resetPreview();
   },
 
   /**
+   * Show the Heartbeat UI to request user feedback. This function reports back to the
+   * caller using |notify|. The notification event name reflects the current status the UI
+   * is in (either "Heartbeat:NotificationOffered", "Heartbeat:NotificationClosed" or
+   * "Heartbeat:Voted"). When a "Heartbeat:Voted" event is notified the data payload contains
+   * a |score| field which holds the rating picked by the user.
+   * Please note that input parameters are already validated by the caller.
+   *
+   * @param aChromeWindow
+   *        The chrome window that the heartbeat notification is displayed in.
+   * @param aMessageManager
+   *        The message manager to communicate with the API caller.
+   * @param aMessage
+   *        The message, or question, to display on the notification.
+   * @param aThankyouMessage
+   *        The thank you message to display after user votes.
+   * @param aFlowId
+   *        An identifier for this rating flow. Please note that this is only used to
+   *        identify the notification box.
+   * @param [aEngagementURL]
+   *        The engagement URL to open in a new tab once user has voted. If this is null
+   *        or invalid, no new tab is opened.
+   */
+  showHeartbeat: function(aChromeWindow, aMessageManager, aMessage, aThankyouMessage, aFlowId,
+                          aEngagementURL = null) {
+    let nb = aChromeWindow.document.getElementById("high-priority-global-notificationbox");
+
+    // Create the notification. Prefix its ID to decrease the chances of collisions.
+    let notice = nb.appendNotification(aMessage, "heartbeat-" + aFlowId,
+      "chrome://branding/content/icon64.png", nb.PRIORITY_INFO_HIGH, null, function() {
+        // Let the consumer know the notification bar was closed. This also happens
+        // after voting.
+        this.notify("Heartbeat:NotificationClosed", { flowId: aFlowId, timestamp: Date.now() });
+    }.bind(this));
+
+    // Get the elements we need to style.
+    let messageImage =
+      aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageImage");
+    let messageText =
+      aChromeWindow.document.getAnonymousElementByAttribute(notice, "anonid", "messageText");
+
+    // Create the fragment holding the rating UI.
+    let frag = aChromeWindow.document.createDocumentFragment();
+
+    // Build the Heartbeat star rating.
+    const numStars = 5;
+    let ratingContainer = aChromeWindow.document.createElement("hbox");
+    ratingContainer.id = "star-rating-container";
+
+    for (let i = 0; i < numStars; i++) {
+      // Create a star rating element.
+      let ratingElement = aChromeWindow.document.createElement("toolbarbutton");
+
+      // Style it.
+      let starIndex = numStars - i;
+      ratingElement.className = "plain star-x";
+      ratingElement.id = "star" + starIndex;
+      ratingElement.setAttribute("data-score", starIndex);
+
+      // Add the click handler.
+      ratingElement.addEventListener("click", function (evt) {
+        let rating = Number(evt.target.getAttribute("data-score"), 10);
+
+        // Let the consumer know user voted.
+        this.notify("Heartbeat:Voted", { flowId: aFlowId, score: rating, timestamp: Date.now() });
+
+        // Display the Heart and make it pulse twice.
+        notice.image = "chrome://browser/skin/heartbeat-icon.svg";
+        notice.label = aThankyouMessage;
+        messageImage.classList.remove("pulse-onshow");
+        messageImage.classList.add("pulse-twice");
+
+        // Remove all the children of the notice (rating container
+        // and the flex).
+        while (notice.firstChild) {
+          notice.removeChild(notice.firstChild);
+        }
+
+        // Make sure that we have a valid URL. If we haven't, do not open the engagement page.
+        let engagementURL = null;
+        try {
+          engagementURL = new URL(aEngagementURL);
+        } catch (error) {
+          log.error("showHeartbeat: Invalid URL specified.");
+        }
+
+        // Just open the engagement tab if we have a valid engagement URL.
+        if (engagementURL) {
+          // Append the score data to the engagement URL.
+          engagementURL.searchParams.append("type", "stars");
+          engagementURL.searchParams.append("score", rating);
+          engagementURL.searchParams.append("flowid", aFlowId);
+
+          // Open the engagement URL in a new tab.
+          aChromeWindow.gBrowser.selectedTab =
+            aChromeWindow.gBrowser.addTab(engagementURL.toString(), {
+              owner: aChromeWindow.gBrowser.selectedTab,
+              relatedToCurrent: true
+            });
+        }
+
+        // Remove the notification bar after 3 seconds.
+        aChromeWindow.setTimeout(() => {
+          nb.removeNotification(notice);
+        }, 3000);
+      }.bind(this));
+
+      // Add it to the container.
+      ratingContainer.appendChild(ratingElement);
+    }
+
+    frag.appendChild(ratingContainer);
+
+    // Make sure the stars are not pushed to the right by the spacer.
+    let rightSpacer = aChromeWindow.document.createElement("spacer");
+    rightSpacer.flex = 20;
+    frag.appendChild(rightSpacer);
+
+    let leftSpacer = messageText.nextSibling;
+    leftSpacer.flex = 0;
+
+    // Append the fragment and apply the styling.
+    notice.appendChild(frag);
+    notice.classList.add("heartbeat");
+    messageImage.classList.add("heartbeat", "pulse-onshow");
+    messageText.classList.add("heartbeat");
+
+    // Let the consumer know the notification was shown.
+    this.notify("Heartbeat:NotificationOffered", { flowId: aFlowId, timestamp: Date.now() });
+  },
+
+  /**
    * @param aChromeWindow The chrome window that the highlight is in. Necessary since some targets
    *                      are in a sub-frame so the defaultView is not the same as the chrome
    *                      window.
    * @param aTarget    The element to highlight.
    * @param aEffect    (optional) The effect to use from UITour.highlightEffects or "none".
    * @see UITour.highlightEffects
    */
   showHighlight: function(aChromeWindow, aTarget, aEffect = "none") {
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -12,16 +12,18 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 # [browser_UITour3.js] Bug 1113038
 # skip-if = os == "linux" || e10s # Linux: Bug 986760, Bug 989101; e10s: Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_availableTargets.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_detach_tab.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
+[browser_UITour_heartbeat.js]
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_loop.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_modalDialog.js]
 run-if = os == "mac" # modal dialog disabling only working on OS X
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_observe.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_panel_close_annotation.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/uitour/test/browser_UITour_heartbeat.js
@@ -0,0 +1,231 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+let notificationBox = document.getElementById("high-priority-global-notificationbox");
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+/**
+ * Simulate a click on a rating element in the Heartbeat notification.
+ *
+ * @param aId
+ *        The id of the notification box.
+ * @param aScore
+ *        The score related to the rating element we want to click on.
+ */
+function simulateVote(aId, aScore) {
+  // UITour.jsm prefixes the notification box ID with "heartbeat-" to prevent collisions.
+  let notification = notificationBox.getNotificationWithValue("heartbeat-" + aId);
+
+  let ratingContainer = notification.childNodes[0];
+  ok(ratingContainer, "The notification has a valid rating container.");
+
+  let ratingElement = ratingContainer.getElementsByAttribute("data-score", aScore);
+  ok(ratingElement[0], "The rating container contains the requested rating element.");
+
+  ratingElement[0].click();
+}
+
+/**
+ * Remove the notification box.
+ *
+ * @param aId
+ *        The id of the notification box to remove.
+ */
+function cleanUpNotification(aId) {
+  let notification = notificationBox.getNotificationWithValue("heartbeat-" + aId);
+  notificationBox.removeNotification(notification);
+}
+
+let tests = [
+  /**
+   * Check that the "stars" heartbeat UI correctly shows and closes.
+   */
+  function test_heartbeat_stars_show(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let engagementURL = "http://example.com";
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          cleanUpNotification(flowId);
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+  },
+
+  /**
+   * Test that the heartbeat UI correctly works with null engagement URL.
+   */
+  function test_heartbeat_null_engagementURL(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 2);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+  },
+
+   /**
+   * Test that the heartbeat UI correctly works with an invalid, but non null, engagement URL.
+   */
+  function test_heartbeat_invalid_engagement_URL(done) {
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+    let invalidEngagementURL = "invalidEngagement";
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 2);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, originalTabCount, "No engagement tab should be opened.");
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, invalidEngagementURL);
+  },
+
+  /**
+   * Test that the score is correctly reported.
+   */
+  function test_heartbeat_stars_vote(done) {
+    const expectedScore = 4;
+    let flowId = "ui-ratefirefox-" + Math.random();
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, expectedScore);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(aData.score, expectedScore, "Should report a score of " + expectedScore);
+          done();
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, null);
+  },
+
+  /**
+   * Test that the engagement page is correctly opened when voting.
+   */
+  function test_heartbeat_engagement_tab(done) {
+    let engagementURL = "http://example.com";
+    let flowId = "ui-ratefirefox-" + Math.random();
+    let originalTabCount = gBrowser.tabs.length;
+    const expectedTabCount = originalTabCount + 1;
+
+    gContentAPI.observe(function (aEventName, aData) {
+      switch (aEventName) {
+        case "Heartbeat:NotificationOffered": {
+          info("'Heartbeat:Offered' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          // The UI was just shown. We can simulate a click on a rating element (i.e., "star").
+          simulateVote(flowId, 1);
+          break;
+        }
+        case "Heartbeat:Voted": {
+          info("'Heartbeat:Voted' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          break;
+        }
+        case "Heartbeat:NotificationClosed": {
+          info("'Heartbeat:NotificationClosed' notification received (timestamp " + aData.timestamp.toString() + ").");
+          ok(Number.isFinite(aData.timestamp), "Timestamp must be a number.");
+          is(gBrowser.tabs.length, expectedTabCount, "Engagement URL should open in a new tab.");
+          gBrowser.removeCurrentTab();
+          done();
+          break;
+        }
+        default:
+          // We are not expecting other states for this test.
+          ok(false, "Unexpected notification received: " + aEventName);
+      }
+    });
+
+    gContentAPI.showHeartbeat("How would you rate Firefox?", "Thank you!", flowId, engagementURL);
+  }
+];
--- a/browser/config/mozconfigs/win64/common-opt
+++ b/browser/config/mozconfigs/win64/common-opt
@@ -20,12 +20,15 @@ fi
 ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
 ac_add_options --with-mozilla-api-keyfile=/c/builds/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
 export MOZ_TELEMETRY_REPORTING=1
 
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
 . $topsrcdir/build/win64/mozconfig.vs2013
 
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
--- a/browser/config/mozconfigs/win64/debug
+++ b/browser/config/mozconfigs/win64/debug
@@ -15,16 +15,19 @@ if [ -f /c/builds/google-oauth-api.key ]
 else
   _google_oauth_api_keyfile=/e/builds/google-oauth-api.key
 fi
 ac_add_options --with-google-oauth-api-keyfile=${_google_oauth_api_keyfile}
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
+# Treat warnings as errors in directories with FAIL_ON_WARNINGS.
+ac_add_options --enable-warnings-as-errors
+
 # Package js shell.
 export MOZ_PACKAGE_JSSHELL=1
 
 . $topsrcdir/build/win64/mozconfig.vs2013
 
 . "$topsrcdir/build/mozconfig.cache"
 
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/devtools/debugger/debugger-panes.js
+++ b/browser/devtools/debugger/debugger-panes.js
@@ -3339,25 +3339,20 @@ LineResults.prototype = {
   line: 0,
   _sourceResults: null,
   _store: null,
   _target: null
 };
 
 /**
  * A generator-iterator over the global, source or line results.
- *
- * The method name depends on whether symbols are enabled in
- * this build. If so, use Symbol.iterator; otherwise "@@iterator".
  */
-const JS_HAS_SYMBOLS = typeof Symbol === "function";
-const ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-GlobalResults.prototype[ITERATOR_SYMBOL] =
-SourceResults.prototype[ITERATOR_SYMBOL] =
-LineResults.prototype[ITERATOR_SYMBOL] = function*() {
+GlobalResults.prototype[Symbol.iterator] =
+SourceResults.prototype[Symbol.iterator] =
+LineResults.prototype[Symbol.iterator] = function*() {
   yield* this._store;
 };
 
 /**
  * Gets the item associated with the specified element.
  *
  * @param nsIDOMNode aElement
  *        The element used to identify the item.
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -28,23 +28,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 const EventEmitter = devtools.require("devtools/toolkit/event-emitter");
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
 const bundle = Services.strings.createBundle("chrome://browser/locale/devtools/toolbox.properties");
 
 /**
- * The method name to use for ES6 iteration. If symbols are enabled in this
- * build, use Symbol.iterator; otherwise "@@iterator".
- */
-const JS_HAS_SYMBOLS = typeof Symbol === "function";
-const ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-
-/**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
 this.DevTools = function DevTools() {
   this._tools = new Map();     // Map<toolId, tool>
   this._themes = new Map();    // Map<themeId, theme>
   this._toolboxes = new Map(); // Map<target, toolbox>
 
@@ -495,17 +488,17 @@ DevTools.prototype = {
     // Cleaning down the toolboxes: i.e.
     //   for (let [target, toolbox] of this._toolboxes) toolbox.destroy();
     // Is taken care of by the gDevToolsBrowser.forgetBrowserWindow
   },
 
   /**
    * Iterator that yields each of the toolboxes.
    */
-  *[ITERATOR_SYMBOL]() {
+  *[Symbol.iterator]() {
     for (let toolbox of this._toolboxes) {
       yield toolbox;
     }
   }
 };
 
 /**
  * gDevTools is a singleton that controls the Firefox Developer Tools.
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -1289,19 +1289,23 @@ MarkupView.prototype = {
   /**
    * Tear down the markup panel.
    */
   destroy: function() {
     if (this._destroyer) {
       return this._destroyer;
     }
 
+    this._destroyer = promise.resolve();
+
     // Note that if the toolbox is closed, this will work fine, but will fail
     // in case the browser is closed and will trigger a noSuchActor message.
-    this._destroyer = this._hideBoxModel();
+    // We ignore the promise that |_hideBoxModel| returns, since we should still
+    // proceed with the rest of destruction if it fails.
+    this._hideBoxModel();
 
     this._elt.removeEventListener("click", this._onMouseClick, false);
 
     this._hoveredNode = null;
     this._inspector.toolbox.off("picker-node-hovered", this._onToolboxPickerHover);
 
     this.htmlEditor.destroy();
     this.htmlEditor = null;
--- a/browser/devtools/shared/widgets/VariablesView.jsm
+++ b/browser/devtools/shared/widgets/VariablesView.jsm
@@ -13,23 +13,16 @@ const LAZY_EMPTY_DELAY = 150; // ms
 const LAZY_EXPAND_DELAY = 50; // ms
 const SCROLL_PAGE_SIZE_DEFAULT = 0;
 const APPEND_PAGE_SIZE_DEFAULT = 500;
 const PAGE_SIZE_SCROLL_HEIGHT_RATIO = 100;
 const PAGE_SIZE_MAX_JUMPS = 30;
 const SEARCH_ACTION_MAX_DELAY = 300; // ms
 const ITEM_FLASH_DURATION = 300 // ms
 
-/**
- * The method name to use for ES6 iteration. If symbols are enabled in
- * this build, use Symbol.iterator; otherwise "@@iterator".
- */
-const JS_HAS_SYMBOLS = typeof Symbol === "function";
-const ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/devtools/ViewHelpers.jsm");
 Cu.import("resource://gre/modules/devtools/event-emitter.js");
 Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 let {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {});
 
@@ -3075,20 +3068,20 @@ Property.prototype = Heritage.extend(Var
     this._absoluteName = this.ownerView.absoluteName + "[\"" + this._nameString + "\"]";
     return this._absoluteName;
   }
 });
 
 /**
  * A generator-iterator over the VariablesView, Scopes, Variables and Properties.
  */
-VariablesView.prototype[ITERATOR_SYMBOL] =
-Scope.prototype[ITERATOR_SYMBOL] =
-Variable.prototype[ITERATOR_SYMBOL] =
-Property.prototype[ITERATOR_SYMBOL] = function*() {
+VariablesView.prototype[Symbol.iterator] =
+Scope.prototype[Symbol.iterator] =
+Variable.prototype[Symbol.iterator] =
+Property.prototype[Symbol.iterator] = function*() {
   yield* this._store;
 };
 
 /**
  * Forget everything recorded about added scopes, variables or properties.
  * @see VariablesView.commitHierarchy
  */
 VariablesView.prototype.clearHierarchy = function() {
--- a/browser/devtools/shared/widgets/ViewHelpers.jsm
+++ b/browser/devtools/shared/widgets/ViewHelpers.jsm
@@ -8,23 +8,16 @@
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cu = Components.utils;
 
 const PANE_APPEARANCE_DELAY = 50;
 const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
 const WIDGET_FOCUSABLE_NODES = new Set(["vbox", "hbox"]);
 
-/**
- * The method name to use for ES6 iteration. If symbols are enabled in
- * this build, use Symbol.iterator; otherwise "@@iterator".
- */
-const JS_HAS_SYMBOLS = typeof Symbol === "function";
-const ITERATOR_SYMBOL = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Timer.jsm");
 Cu.import("resource://gre/modules/devtools/DevToolsUtils.jsm");
 
 this.EXPORTED_SYMBOLS = [
   "Heritage", "ViewHelpers", "WidgetMethods",
   "setNamedTimeout", "clearNamedTimeout",
@@ -1731,12 +1724,12 @@ this.WidgetMethods = {
   _headerText: "",
   _preferredValue: "",
   _cachedCommandDispatcher: null
 };
 
 /**
  * A generator-iterator over all the items in this container.
  */
-Item.prototype[ITERATOR_SYMBOL] =
-WidgetMethods[ITERATOR_SYMBOL] = function*() {
+Item.prototype[Symbol.iterator] =
+WidgetMethods[Symbol.iterator] = function*() {
   yield* this._itemsByElement.values();
 };
--- a/browser/devtools/webaudioeditor/controller.js
+++ b/browser/devtools/webaudioeditor/controller.js
@@ -88,42 +88,34 @@ let WebAudioEditorController = {
    */
   reset: function () {
     $("#content").hidden = true;
     ContextView.resetUI();
     InspectorView.resetUI();
     PropertiesView.resetUI();
   },
 
-  // Since node create and connect are probably executed back to back,
-  // and the controller's `_onCreateNode` needs to look up type,
-  // the edge creation could be called before the graph node is actually
-  // created. This way, we can check and listen for the event before
-  // adding an edge.
-  _waitForNodeCreation: function (sourceActor, destActor) {
-    let deferred = defer();
-    let source = gAudioNodes.get(sourceActor.actorID);
-    let dest = gAudioNodes.get(destActor.actorID);
+  // Since node events (create, disconnect, connect) are all async,
+  // we have to make sure to wait that the node has finished creating
+  // before performing an operation on it.
+  getNode: function* (nodeActor) {
+    let id = nodeActor.actorID;
+    let node = gAudioNodes.get(id);
 
-    if (!source || !dest) {
+    if (!node) {
+      let { resolve, promise } = defer();
       gAudioNodes.on("add", function createNodeListener (createdNode) {
-        if (sourceActor.actorID === createdNode.id)
-          source = createdNode;
-        if (destActor.actorID === createdNode.id)
-          dest = createdNode;
-        if (source && dest) {
+        if (createdNode.id === id) {
           gAudioNodes.off("add", createNodeListener);
-          deferred.resolve([source, dest]);
+          resolve(createdNode);
         }
       });
+      node = yield promise;
     }
-    else {
-      deferred.resolve([source, dest]);
-    }
-    return deferred.promise;
+    return node;
   },
 
   /**
    * Fired when the devtools theme changes (light, dark, etc.)
    * so that the graph can update marker styling, as that
    * cannot currently be done with CSS.
    */
   _onThemeChange: function (event, data) {
@@ -197,35 +189,38 @@ let WebAudioEditorController = {
   _onDestroyNode: function (nodeActor) {
     gAudioNodes.remove(gAudioNodes.get(nodeActor.actorID));
   },
 
   /**
    * Called when a node is connected to another node.
    */
   _onConnectNode: Task.async(function* ({ source: sourceActor, dest: destActor }) {
-    let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor);
+    let source = yield WebAudioEditorController.getNode(sourceActor);
+    let dest = yield WebAudioEditorController.getNode(destActor);
     source.connect(dest);
   }),
 
   /**
    * Called when a node is conneceted to another node's AudioParam.
    */
   _onConnectParam: Task.async(function* ({ source: sourceActor, dest: destActor, param }) {
-    let [source, dest] = yield WebAudioEditorController._waitForNodeCreation(sourceActor, destActor);
+    let source = yield WebAudioEditorController.getNode(sourceActor);
+    let dest = yield WebAudioEditorController.getNode(destActor);
     source.connect(dest, param);
   }),
 
   /**
    * Called when a node is disconnected.
    */
-  _onDisconnectNode: function(nodeActor) {
-    let node = gAudioNodes.get(nodeActor.actorID);
+  _onDisconnectNode: Task.async(function* (nodeActor) {
+    let node = yield WebAudioEditorController.getNode(nodeActor);
     node.disconnect();
-  },
+  }),
 
   /**
    * Called when a node param is changed.
    */
-  _onChangeParam: function({ actor, param, value }) {
-    window.emit(EVENTS.CHANGE_PARAM, gAudioNodes.get(actor.actorID), param, value);
-  }
+  _onChangeParam: Task.async(function* ({ actor, param, value }) {
+    let node = yield WebAudioEditorController.getNode(actor);
+    window.emit(EVENTS.CHANGE_PARAM, node, param, value);
+  })
 };
--- a/browser/devtools/webaudioeditor/test/browser.ini
+++ b/browser/devtools/webaudioeditor/test/browser.ini
@@ -6,16 +6,17 @@ support-files =
   doc_simple-node-creation.html
   doc_buffer-and-array.html
   doc_media-node-creation.html
   doc_destroy-nodes.html
   doc_connect-param.html
   doc_connect-multi-param.html
   doc_iframe-context.html
   doc_automation.html
+  doc_bug_1125817.html
   440hz_sine.ogg
   head.js
 
 [browser_audionode-actor-get-param-flags.js]
 [browser_audionode-actor-get-params-01.js]
 [browser_audionode-actor-get-params-02.js]
 [browser_audionode-actor-get-set-param.js]
 [browser_audionode-actor-get-type.js]
@@ -34,16 +35,17 @@ support-files =
 [browser_wa_destroy-node-01.js]
 
 [browser_wa_first-run.js]
 [browser_wa_reset-01.js]
 [browser_wa_reset-02.js]
 [browser_wa_reset-03.js]
 [browser_wa_reset-04.js]
 [browser_wa_navigate.js]
+[browser_wa_controller-01.js]
 
 [browser_wa_graph-click.js]
 [browser_wa_graph-markers.js]
 [browser_wa_graph-render-01.js]
 [browser_wa_graph-render-02.js]
 [browser_wa_graph-render-03.js]
 [browser_wa_graph-render-04.js]
 [browser_wa_graph-render-05.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/browser_wa_controller-01.js
@@ -0,0 +1,27 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Bug 1125817
+ * Tests to ensure that disconnecting a node immediately
+ * after creating it does not fail.
+ */
+
+const BUG_1125817_URL = EXAMPLE_URL + "doc_bug_1125817.html";
+
+add_task(function*() {
+  let { target, panel } = yield initWebAudioEditor(BUG_1125817_URL);
+  let { panelWin } = panel;
+  let { gFront, $, $$, EVENTS, gAudioNodes } = panelWin;
+
+  reload(target);
+
+  let [actors] = yield Promise.all([
+    once(gAudioNodes, "add", 2),
+    once(gAudioNodes, "disconnect")
+  ]);
+
+  ok(true, "Successfully disconnected a just-created node.");
+
+  yield teardown(target);
+});
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webaudioeditor/test/doc_bug_1125817.html
@@ -0,0 +1,23 @@
+<!-- Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/publicdomain/zero/1.0/ -->
+<!doctype html>
+
+<html>
+  <head>
+    <meta charset="utf-8"/>
+    <title>Web Audio Editor test page</title>
+  </head>
+
+  <body>
+
+    <script type="text/javascript;version=1.8">
+      "use strict";
+
+      let ctx = new AudioContext();
+      let osc = ctx.createOscillator();
+      osc.frequency.value = 200;
+      osc.disconnect();
+    </script>
+  </body>
+
+</html>
--- a/browser/metro/components/HelperAppDialog.js
+++ b/browser/metro/components/HelperAppDialog.js
@@ -133,20 +133,16 @@ HelperAppLauncherDialog.prototype = {
                                                     "save-download",
                                                     URI_GENERIC_ICON_DOWNLOAD,
                                                     notificationBox.PRIORITY_WARNING_HIGH,
                                                     buttons);
     let messageContainer = document.getAnonymousElementByAttribute(newBar, "anonid", "messageText");
     messageContainer.appendChild(fragment);
   },
 
-  promptForSaveToFile: function hald_promptForSaveToFile(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
-    throw new Components.Exception("Async version must be used", Cr.NS_ERROR_NOT_AVAILABLE);
-  },
-
   promptForSaveToFileAsync: function hald_promptForSaveToFileAsync(aLauncher, aContext, aDefaultFile, aSuggestedFileExt, aForcePrompt) {
     let file = null;
     let prefs = Services.prefs;
 
     Task.spawn(function() {
       if (!aForcePrompt) {
         // Check to see if the user wishes to auto save to the default download
         // folder without prompting. Note that preference might not be set.
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -29,16 +29,19 @@ browser.jar:
   skin/classic/browser/click-to-play-warning-stripes.png
   skin/classic/browser/content-contextmenu.svg
   skin/classic/browser/dots.png                             (../shared/dots.png)
   skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-64.png
+  skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
+  skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
+  skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
   skin/classic/browser/identity.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-mixed-active.png
   skin/classic/browser/identity-icons-https-mixed-display.png
   skin/classic/browser/Info.png
   skin/classic/browser/magnifier.png                        (../shared/magnifier.png)
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -31,16 +31,19 @@ browser.jar:
   skin/classic/browser/dots.png                             (../shared/dots.png)
   skin/classic/browser/dots@2x.png                          (../shared/dots@2x.png)
 * skin/classic/browser/engineManager.css                    (engineManager.css)
   skin/classic/browser/fullscreen-darknoise.png
   skin/classic/browser/Geolocation-16.png
   skin/classic/browser/Geolocation-16@2x.png
   skin/classic/browser/Geolocation-64.png
   skin/classic/browser/Geolocation-64@2x.png
+  skin/classic/browser/heartbeat-icon.svg                   (../shared/heartbeat-icon.svg)
+  skin/classic/browser/heartbeat-star-lit.svg               (../shared/heartbeat-star-lit.svg)
+  skin/classic/browser/heartbeat-star-off.svg               (../shared/heartbeat-star-off.svg)
   skin/classic/browser/identity.png
   skin/classic/browser/identity@2x.png
   skin/classic/browser/identity-icons-generic.png
   skin/classic/browser/identity-icons-generic@2x.png
   skin/classic/browser/identity-icons-https.png
   skin/classic/browser/identity-icons-https@2x.png
   skin/classic/browser/identity-icons-https-ev.png
   skin/classic/browser/identity-icons-https-ev@2x.png
--- a/browser/themes/shared/UITour.inc.css
+++ b/browser/themes/shared/UITour.inc.css
@@ -190,8 +190,115 @@
   .SearchHighlight .dot {
     background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 100%, 100%, 18);
   }
 
   .SearchHighlight .dot.filled {
     background-image: -moz-image-rect(url("chrome://browser/skin/dots@2x.png"), 0, 14, 100%, 0);
   }
 }
+
+/* Notification overrides for Heartbeat UI */
+
+notification.heartbeat {
+  background-color: #F1F1F1;
+%ifdef XP_MACOSX
+  background-image: linear-gradient(-179deg, #FBFBFB 0%, #EBEBEB 100%);
+%endif
+  box-shadow: 0px 1px 0px 0px rgba(0,0,0,0.35);
+}
+
+@keyframes pulse-onshow {
+ 0% {
+   opacity: 0;
+   transform: scale(1.0);
+ }
+ 25% {
+   opacity: 1;
+   transform: scale(1.1);
+ }
+ 50% {
+   transform: scale(1.0);
+ }
+ 75% {
+   transform: scale(1.1);
+ }
+ 100% {
+   transform: scale(1.0);
+ }
+}
+
+@keyframes pulse-twice {
+ 0% {
+   transform: scale(1.1);
+ }
+ 50% {
+   transform: scale(0.8);
+ }
+ 100% {
+   transform: scale(1);
+ }
+}
+
+.messageText.heartbeat {
+  color: #333333;
+  font-weight: normal;
+  font-family: "Lucida Grande", Segoe, Ubuntu;
+  font-size: 14px;
+  line-height: 16px;
+  text-shadow: none;
+}
+
+.messageImage.heartbeat {
+  width: 36px;
+  height: 36px;
+  -moz-margin-end: 10px;
+}
+
+.messageImage.heartbeat.pulse-onshow {
+  animation-name: pulse-onshow;
+  animation-duration: 1.5s;
+  animation-iteration-count: 1;
+  animation-timing-function: cubic-bezier(.7,1.8,.9,1.1);
+}
+
+.messageImage.heartbeat.pulse-twice {
+  animation-name: pulse-twice;
+  animation-duration: 1s;
+  animation-iteration-count: 2;
+  animation-timing-function: linear;
+}
+
+/* Heartbeat UI Rating Star Classes */
+.heartbeat > #star-rating-container {
+  display: -moz-box;
+}
+
+.heartbeat > #star-rating-container > #star5 {
+  -moz-box-ordinal-group: 5;
+}
+
+.heartbeat > #star-rating-container > #star4 {
+  -moz-box-ordinal-group: 4;
+}
+
+.heartbeat > #star-rating-container > #star3 {
+  -moz-box-ordinal-group: 3;
+}
+
+.heartbeat > #star-rating-container > #star2 {
+  -moz-box-ordinal-group: 2;
+}
+
+.heartbeat > #star-rating-container > .star-x  {
+  background: url("chrome://browser/skin/heartbeat-star-off.svg");
+  cursor: pointer;
+  width: 24px;
+  height: 24px;
+}
+
+.heartbeat > #star-rating-container > .star-x:hover,
+.heartbeat > #star-rating-container > .star-x:hover ~ .star-x {
+  background: url("chrome://browser/skin/heartbeat-star-lit.svg");
+  cursor: pointer;
+  width: 24px;
+  height: 24px;
+}
--- a/browser/themes/shared/aboutNetError.css
+++ b/browser/themes/shared/aboutNetError.css
@@ -23,16 +23,17 @@ ul > li, ol > li {
   margin-bottom: .5em;
 }
 
 ul {
   list-style: disc;
 }
 
 #errorPageContainer {
+  position: relative;
   min-width: 320px;
   max-width: 512px;
 }
 
 #errorTitleText {
   background: url("aboutNetError_info.svg") left 0 no-repeat;
   background-size: 1.2em;
   -moz-margin-start: -2em;
@@ -69,53 +70,59 @@ ul {
  * not-allowed. Override the disabled cursor behaviour since we will never show
  * the button disabled as the initial state. */
 button:disabled {
   cursor: pointer;
 }
 
 div#certificateErrorReporting {
   display: none;
-  float:right;
+  float: right;
   /* Align with the "Try Again" button */
-  margin-top:24px;
-  margin-right:24px;
-}
-
-div#certificateErrorReporting a,
-div#certificateErrorReportingPanel a {
-  color: #0095DD;
+  margin-top: 24px;
+  -moz-margin-end: 24px;
 }
 
 div#certificateErrorReporting a {
   text-decoration: none;
 }
 
 div#certificateErrorReporting a:hover {
   text-decoration: underline;
 }
 
 span.downArrow {
-  font-size: 0.9em;
+  display: inline-block;
+  vertical-align: middle;
+  font-size: 0.6em;
+  -moz-margin-start: 0.5em;
+  transform: scaleY(0.7);
 }
 
 div#certificateErrorReportingPanel {
   /* Hidden until the link is clicked */
   display: none;
   background-color: white;
   border: 1px lightgray solid;
   /* Don't use top padding because the default p style has top padding, and it
    * makes the overall div look uneven */
   padding: 0 12px 12px 12px;
   box-shadow: 0 0 4px #ddd;
-  position: relative;
+  font-size: 0.9em;
+  position: absolute;
   width: 75%;
+  margin-top: 10px;
+}
+
+div#certificateErrorReportingPanel:-moz-dir(ltr) {
   left: 34%;
-  font-size: 0.9em;
-  top: 8px;
+}
+
+div#certificateErrorReportingPanel:-moz-dir(rtl) {
+  right: 0;
 }
 
 span#hostname {
   font-weight: bold;
 }
 
 #automaticallyReportInFuture {
   cursor: pointer;
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-icon.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="288px" height="248px" viewBox="0 0 288 248" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
+    <title>  + Line 14</title>
+    <desc>Created with Sketch.</desc>
+    <defs>
+        <path id="path-1" d="M144,246.857143 C141.214272,246.857143 138.857152,245.892867 136.928571,243.964286 L36.6428571,147.214286 C35.5714232,146.357139 34.0982237,144.964295 32.2232143,143.035714 C30.3482049,141.107133 27.3750204,137.59824 23.3035714,132.508929 C19.2321225,127.419617 15.5893018,122.196455 12.375,116.839286 C9.16069821,111.482116 6.29465545,105.000038 3.77678571,97.3928571 C1.25891598,89.7856763 0,82.392893 0,75.2142857 C0,51.6427393 6.80350339,33.2143521 20.4107143,19.9285714 C34.0179252,6.64279071 52.8213086,0 76.8214286,0 C83.4643189,0 90.2410369,1.1517742 97.1517857,3.45535714 C104.062535,5.75894009 110.491042,8.86605187 116.4375,12.7767857 C122.383958,16.6875196 127.499979,20.3571257 131.785714,23.7857143 C136.07145,27.2143029 140.142838,30.8571236 144,34.7142857 C147.857162,30.8571236 151.92855,27.2143029 156.214286,23.7857143 C160.500021,20.3571257 165.616042,16.6875196 171.5625,12.7767857 C177.508958,8.86605187 183.937465,5.75894009 190.848214,3.45535714 C197.758963,1.1517742 204.535681,0 211.178571,0 C235.178691,0 253.982075,6.64279071 267.589286,19.9285714 C281.196497,33.2143521 288,51.6427393 288,75.2142857 C288,98.8929755 275.732266,122.999877 251.196429,147.535714 L151.071429,243.964286 C149.142847,245.892867 146.785728,246.857143 144,246.857143 L144,246.857143 Z"/>
+    </defs>
+    <g id="Prompt---Spec" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="--+-Line-14" sketch:type="MSLayerGroup" transform="translate(0.000000, -1.000000)">
+            <path d="M144,248.571429 C141.214272,248.571429 138.857152,247.607152 136.928571,245.678571 L36.6428571,148.928571 C35.5714232,148.071424 34.0982237,146.678581 32.2232143,144.75 C30.3482049,142.821419 27.3750204,139.312525 23.3035714,134.223214 C19.2321225,129.133903 15.5893018,123.910741 12.375,118.553571 C9.16069821,113.196402 6.29465545,106.714324 3.77678571,99.1071429 C1.25891598,91.499962 0,84.1071788 0,76.9285714 C0,53.357025 6.80350339,34.9286379 20.4107143,21.6428571 C34.0179252,8.35707643 52.8213086,1.71428571 76.8214286,1.71428571 C83.4643189,1.71428571 90.2410369,2.86605991 97.1517857,5.16964286 C104.062535,7.4732258 110.491042,10.5803376 116.4375,14.4910714 C122.383958,18.4018053 127.499979,22.0714114 131.785714,25.5 C136.07145,28.9285886 140.142838,32.5714093 144,36.4285714 C147.857162,32.5714093 151.92855,28.9285886 156.214286,25.5 C160.500021,22.0714114 165.616042,18.4018053 171.5625,14.4910714 C177.508958,10.5803376 183.937465,7.4732258 190.848214,5.16964286 C197.758963,2.86605991 204.535681,1.71428571 211.178571,1.71428571 C235.178691,1.71428571 253.982075,8.35707643 267.589286,21.6428571 C281.196497,34.9286379 288,53.357025 288,76.9285714 C288,100.607261 275.732266,124.714163 251.196429,149.25 L151.071429,245.678571 C149.142847,247.607152 146.785728,248.571429 144,248.571429 L144,248.571429 Z" id="-" fill="#D74345" sketch:type="MSShapeGroup"/>
+            <g id="Line-14" transform="translate(0.000000, 0.714286)">
+                <mask id="mask-2" sketch:name="Mask" fill="white">
+                    <use xlink:href="#path-1"/>
+                </mask>
+                <use id="Mask" sketch:type="MSShapeGroup" xlink:href="#path-1"/>
+                <path d="M-166,115.135254 C-166,115.135254 0.595052083,115.135254 2.9765625,115.135254 L91.9101562,115.135254 L97.9638977,100.101562 L105.430695,115.135254 L114.893585,115.135254 L131.129913,189.53125 L148.161163,57 L165.348663,131.027344 L172.272491,115.135254 L250.84967,115.135254 L428.259813,115.135254" stroke="#FFFFFF" stroke-width="6" stroke-linecap="round" stroke-linejoin="round" sketch:type="MSShapeGroup" mask="url(#mask-2)">
+                    <g transform="translate(131.129906, 123.265625) scale(1, -1) translate(-131.129906, -123.265625) "/>
+                </path>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-lit.svg
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Toolbar"
+   x="0px"
+   y="0px"
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 16 16"
+   xml:space="preserve"
+   inkscape:version="0.48.5 r10040"
+   width="100%"
+   height="100%"
+   sodipodi:docname="star-lit.svg"><metadata
+   id="metadata4111"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs4109" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="1440"
+   inkscape:window-height="838"
+   id="namedview4107"
+   showgrid="false"
+   inkscape:zoom="41.7193"
+   inkscape:cx="6.7712219"
+   inkscape:cy="7.3971752"
+   inkscape:window-x="-8"
+   inkscape:window-y="-8"
+   inkscape:window-maximized="1"
+   inkscape:current-layer="Toolbar" />
+<g
+   id="g3926">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-238.2,6h-6.1l2.7-2.7c0.4-0.4,0.5-1.1,0.2-1.4l-1.2-1.2   c-0.3-0.3-1-0.3-1.4,0.2l-6.4,6.4c-0.1,0.1-0.1,0.2-0.2,0.2l0,0c0,0,0,0,0,0c0,0.1-0.1,0.1-0.1,0.1c0,0.1-0.1,0.2-0.1,0.3   c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0.1,0.3c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0.1,0.1,0.1,0.2,0.2,0.2l6.4,6.4   c0.4,0.4,1.1,0.5,1.4,0.2l1.2-1.2c0.3-0.3,0.3-1-0.2-1.4l-2.8-2.8h6.2c0.6,0,1-0.4,1-1V7C-237.2,6.4-237.6,6-238.2,6z"
+   id="path3928" />
+</g>
+<g
+   id="g3930">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-212.7,6.3l-1,1c-0.2,0.2-0.2,0.4-0.2,0.7c0,0.3,0,0.5,0.2,0.7   l0.8,0.9l4.5,4.5c0.5,0.5,1.2,0.6,1.6,0.2l0.8-0.9c0.4-0.4,0.3-1.1-0.2-1.6l-1.8-1.8h4.7c0.6,0,1-0.5,1-1V7c0-0.6-0.5-1-1-1h-4.8   l1.9-1.9c0.5-0.5,0.6-1.3,0.2-1.6l-0.8-0.9c-0.4-0.4-1.1-0.3-1.6,0.2L-212.7,6.3z"
+   id="path3932" />
+</g>
+<g
+   id="g3934">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-167.3,9.7l1-1c0.2-0.2,0.2-0.4,0.2-0.7c0-0.3,0-0.5-0.2-0.7   l-0.8-0.9l-4.5-4.5c-0.5-0.5-1.2-0.6-1.6-0.2l-0.8,0.9c-0.4,0.4-0.3,1.1,0.2,1.6l1.8,1.8h-4.7c-0.6,0-1,0.5-1,1v2c0,0.6,0.5,1,1,1   h4.8l-1.9,1.9c-0.5,0.5-0.6,1.3-0.2,1.6l0.8,0.9c0.4,0.4,1.1,0.3,1.6-0.2L-167.3,9.7z"
+   id="path3936" />
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-129.5,8h-7l2.8-2.8c-0.7-0.5-1.4-0.7-2.3-0.7c-2.2,0-4,1.8-4,4  c0,2.2,1.8,4,4,4c1.4,0,2.7-0.7,3.4-1.9l2.3,1c-1.1,2-3.2,3.4-5.7,3.4c-3.6,0-6.5-2.9-6.5-6.5c0-3.6,2.9-6.5,6.5-6.5  c1.5,0,2.9,0.5,4.1,1.4l2.4-2.4V8z"
+   id="path3938" />
+<g
+   id="g3940">
+	<g
+   id="g3942">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   points="-93.6,3.8 -95.8,1.7 -100,5.9 -104.3,1.6 -106.4,3.8    -102.1,8 -106.3,12.2 -104.1,14.3 -99.9,10.2 -95.7,14.4 -93.6,12.2 -97.8,8.1   "
+   id="polygon3944" />
+	</g>
+</g>
+<g
+   id="g3946">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-64,1l-8,7h2l6-5l6,5h2L-64,1z M-69,8v7h4v-5h2v5h4V8l-5-4L-69,8   z"
+   id="path3948" />
+</g>
+<path
+   fill="#00A3F2"
+   d="M-28,3.6l1,2l0.5,0.9l1,0.2l2.3,0.4l-1.7,1.8l-0.7,0.7l0.1,1l0.4,2.4l-2-1l-0.9-0.5l-0.9,0.5l-2,1l0.4-2.4  l0.1-1l-0.7-0.7l-1.7-1.8l2.4-0.4l1-0.2l0.5-0.9L-28,3.6 M-28,0c-0.3,0-0.6,0.2-0.8,0.7l-2,4.1l-4.3,0.7c-1,0.2-1.2,0.9-0.5,1.6  l3.1,3.3l-0.7,4.6c-0.1,0.7,0.2,1.1,0.7,1.1c0.2,0,0.4-0.1,0.6-0.2l3.9-2.1l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1  l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C-27.4,0.2-27.7,0-28,0L-28,0z"
+   id="path3950" />
+<path
+   fill="#00A3F2"
+   d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16  c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6  l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"
+   id="path3952"
+   style="fill:#0095dd;fill-opacity:1" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M44,15.2c-4,0-7.2-3.2-7.2-7.2c0-4,3.2-7.2,7.2-7.2  c4,0,7.2,3.2,7.2,7.2C51.2,12,48,15.2,44,15.2z M44,3c-2.8,0-5,2.2-5,5c0,2.7,2.2,5,5,5c2.8,0,5-2.2,5-5C49,5.3,46.8,3,44,3z   M43.7,8.9C43.3,8.8,43,8.4,43,8V5c0-0.6,0.4-1,1-1c0.6,0,1,0.4,1,1v2.8c1.1,1.1,2,3.2,2,3.2S44.8,10,43.7,8.9z"
+   id="path3954" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M86.7,9.1l-5.6,5.5C80.8,14.9,80.4,15,80,15  c-0.4,0-0.8-0.1-1.1-0.4l-5.6-5.5C72.7,8.5,72.9,8,73.8,8H77l0-6c0-0.6,0.4-1,1-1h4c0.6,0,1,0.4,1,1v6h3.2C87.1,8,87.3,8.5,86.7,9.1  z"
+   id="path3956" />
+<path
+   fill="#00A3F2"
+   d="M-241,52c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-241z"
+   id="path3958" />
+<g
+   id="g3960">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.4,41.9h-0.3v0h-14.4v0h-0.3c-0.2,0-0.4,0.2-0.4,0.4c0,0,0.3,2.2,0.5,4.4   c0.2,2.5,0.2,4.2,0.2,4.2c0,0.2,0.2,0.4,0.4,0.4h13.7c0.2,0,0.4-0.2,0.4-0.4c0,0,0.1-2.1,0.2-4.2c0.1-2.2,0.5-4.4,0.5-4.4   C-200.1,42.1-200.2,41.9-200.4,41.9z"
+   id="path3962" />
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.8,41.3v-2c0-0.3-0.2-0.5-0.5-0.5l-6.7,0l-0.8-1.1c0,0-0.6-0.9-1.2-0.9h-4.2   c-0.6,0-1,0.5-1,1v1l0,2.5H-200.8z"
+   id="path3964" />
+</g>
+<g
+   id="g3966">
+	<g
+   id="g3968">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-169,37h-7.5c-0.8,0-1.5,0.7-1.5,1.5v11c0,0.8,0.7,1.5,1.5,1.5    h9c0.8,0,1.5-0.7,1.5-1.5V40L-169,37z M-170,41v-3.3l3.3,3.3H-170z"
+   id="path3970" />
+	</g>
+</g>
+<g
+   id="g3972">
+	<g
+   id="g3974">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-132.5,38c-0.4,0.2-0.9,0.6-1.3,1.2c-0.3,0.4-0.5,0.9-0.8,1.3    c0.7,0.3,1.3,0.8,1.7,1.4c0.4,0.7,0.7,1.5,0.6,2.3c-0.1,1.3-1,2.4-2.1,3c-0.5-0.5-1.4-2.1-1.4-2.1c0,0,0,1.8-0.6,3    c-0.4,0.8-0.9,1.5-1.6,2.1c1,0.4,2,0.6,3,0.6l0-0.1c0,0,0.1,0.1,0.1,0.1c2.1,0,4.1-0.3,4.1-0.3s-0.9-0.6-1.3-0.9    c1.3-0.7,2.2-2,2.5-3.3c0.2-0.5,0.3-1.1,0.4-1.7C-128.7,41.9-130.2,39.3-132.5,38z M-139.8,44.2c0-1.4,0.8-2.8,2.2-3.5    c0.5,0.5,1.6,2,1.6,2s0-3.6,1.2-5.6c-4.3-0.5-6.8,0.3-6.8,0.3s1.2,0.4,1.6,0.8c-0.1,0.1-0.2,0.1-0.3,0.2c-1,0.7-1.7,1.6-2.1,2.7    c-0.3,0.6-0.5,1.3-0.5,2c-0.3,2.8,1.1,5.4,3.4,6.7c0.4-0.3,0.8-0.6,1.2-1.1c0.4-0.4,0.7-0.9,1-1.4    C-138.7,46.9-139.7,45.7-139.8,44.2z"
+   id="path3976" />
+	</g>
+</g>
+<g
+   id="g3978">
+	<g
+   id="g3980">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-96.6,41.5c-3.3-4-9.6-4.5-9.6-4.5c-0.4,0-0.8,0.3-0.8,0.7v1.4    c0,0.4,0.3,0.7,0.8,0.7c0,0,4.5,0.1,7.1,3.2c3.5,3.6,3.2,7.2,3.2,7.2c0,0.4,0.3,0.8,0.8,0.8h1.5c0.4,0,0.7-0.3,0.7-0.8    C-93,50.2-93.3,44.5-96.6,41.5z M-106.2,42c-0.4,0-0.8,0.3-0.8,0.7V44c0,0.4,0.3,0.7,0.8,0.7c0,0,2.4,0.1,3.8,1.4    c1.9,1.7,1.8,4.2,1.8,4.2c0,0.4,0.2,0.8,0.6,0.8h1.5c0.4,0,0.5-0.3,0.5-0.8c0,0-0.4-3.9-2.5-5.9C-102.5,42.4-106.2,42-106.2,42z     M-105,47c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2C-103,47.9-103.9,47-105,47z"
+   id="path3982" />
+	</g>
+</g>
+<path
+   fill="none"
+   d="M-102.6,34.4c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-102.6z"
+   id="path3984" />
+<g
+   id="g3986">
+	<g
+   id="g3988">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-63,46.7l-0.3-0.1L-63,52l2.6-3.8L-63,46.7z M-63.3,46l0.3,0    l6,2.9V36l-14,11l4.6-0.6L-63,52l-1.9-5.7l-0.3-0.1l0.3,0L-65,46l7-8L-63.3,46L-63.3,46z"
+   id="path3990" />
+	</g>
+</g>
+<g
+   id="g3992">
+	<g
+   id="g3994">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-23,37h-10c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h10    c1.1,0,2-0.9,2-2V39C-21,37.9-21.9,37-23,37z M-23,46c0,1.7-1.3,2-3,2h-4c-1.7,0-3-1.3-3-3v-4c0-1.7,1.3-3,3-3h4c1.7,0,3,1.3,3,3    V46z"
+   id="path3996" />
+	</g>
+</g>
+<path
+   fill="#00A3F2"
+   d="M-23.7,42.6c0-0.8-0.2-1.5-0.6-2c-0.4-0.5-1-0.7-1.7-0.7c-0.4,0-0.8,0.1-1.1,0.3s-0.6,0.4-0.8,0.8  c-0.2-0.4-0.4-0.6-0.7-0.8c-0.3-0.2-0.7-0.2-1.2-0.2c-0.4,0-0.7,0-1.1,0.1s-0.7,0.2-1,0.4l0.3,0.7c0.6-0.3,1.2-0.5,1.7-0.5  c0.4,0,0.8,0.1,1,0.3c0.2,0.2,0.3,0.6,0.3,1.1v0.4l-1,0c-0.9,0-1.6,0.2-2.1,0.5c-0.5,0.3-0.7,0.8-0.7,1.4c0,0.6,0.2,1,0.5,1.3  c0.3,0.3,0.8,0.5,1.4,0.5c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.5,1-1c0.5,0.9,1.2,1.3,2.3,1.3c0.4,0,0.7,0,1-0.1  c0.3-0.1,0.6-0.2,0.9-0.3v-0.8c-0.3,0.1-0.6,0.3-0.9,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-1.2,0-1.8-0.7-1.8-2.2h3.9V42.6z M-28.5,43.6  c0,0.6-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.3,0.5c-0.3,0-0.6-0.1-0.8-0.2c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.4,0.2-0.7,0.5-0.9  c0.3-0.2,0.8-0.3,1.5-0.3l0.9,0V43.6z M-27.5,42.4c0-0.6,0.2-1,0.4-1.3c0.3-0.3,0.6-0.5,1.1-0.5c0.4,0,0.8,0.2,1,0.5  c0.2,0.3,0.4,0.8,0.4,1.3H-27.5z"
+   id="path3998" />
+<g
+   id="g4000">
+	<g
+   id="g4002">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M14.5,37h-13C0.7,37,0,37.7,0,38.5v11C0,50.3,0.7,51,1.5,51h13    c0.8,0,1.5-0.7,1.5-1.5v-11C16,37.7,15.3,37,14.5,37z M6.5,38C6.8,38,7,38.2,7,38.5C7,38.8,6.8,39,6.5,39C6.2,39,6,38.8,6,38.5    C6,38.2,6.2,38,6.5,38z M4.4,38c0.3,0,0.5,0.2,0.5,0.5c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C3.9,38.2,4.2,38,4.4,38z     M2.5,38C2.8,38,3,38.2,3,38.5C3,38.8,2.8,39,2.5,39C2.2,39,2,38.8,2,38.5C2,38.2,2.2,38,2.5,38z M14,48c0,0.6-0.4,1-1,1H3    c-0.6,0-1-0.4-1-1v-6c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V48z"
+   id="path4004" />
+	</g>
+</g>
+<path
+   fill="#00A3F2"
+   d="M51.2,45.9L51.2,45.9c-0.4-0.1-0.7-0.3-1-0.6c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1-0.1-0.1-0.1-0.2-0.2  c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.1-0.1-0.2c-0.3-0.6-0.4-1.5-0.5-2.4c-0.3-2.2-0.1-3.8-3-3.8h-4.4c-2.9,0-2.6,1.6-2.9,3.8  c-0.1,0.9-0.3,1.8-0.6,2.4c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.3c0,0,0,0-0.1,0.1c-0.1,0.2-0.2,0.3-0.4,0.4  c-0.3,0.2-0.5,0.4-0.9,0.5l0,0C36.4,46,36,46.3,36,46.8v1.9c0,0.5,0.4,0.9,0.9,0.9h14.1c0.5,0,0.9-0.4,0.9-0.9v-1.9  C52,46.3,51.6,46,51.2,45.9z M46.4,43.8c0,0.1-0.1,0.2-0.2,0.2h-1.6v1.6c0,0.1-0.1,0.2-0.2,0.2h-0.5c-0.1,0-0.2-0.1-0.2-0.2V44h-1.6  c-0.1,0-0.2-0.1-0.2-0.2v-0.5c0-0.1,0.1-0.2,0.2-0.2h1.6v-1.6c0-0.1,0.1-0.2,0.2-0.2h0.5c0.1,0,0.2,0.1,0.2,0.2v1.6h1.6  c0.1,0,0.2,0.1,0.2,0.2V43.8z"
+   id="path4006" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M87.7,42c-0.1,1.1,0.2,2.5-1.3,4.4c-1.6,2.1-3.1,2.1-3.4,2.1  c-1.8-0.1-2-1.5-3-1.5c-0.9,0-1.6,1.4-3,1.5c-0.3,0-1.9,0-3.4-2c-1.5-1.9-1.2-3.3-1.3-4.4S72,39.7,72,39.7s0.7,0.7,1.6,0.8  c0.9,0.1,1.1-0.3,3-0.9c2.1-0.6,3.4,1.9,3.4,1.9s1.4-2.4,3.4-1.9c1.9,0.6,2,0.9,2.9,0.9c0.9-0.1,1.7-0.8,1.7-0.8S87.8,40.9,87.7,42z   M76.9,42.5c-1.1-0.3-1.6,0.2-2.1,0.4C74.4,43,74,43.1,74,43.1s0.1,0.7,1.2,1.2c1.1,0.6,3.5,0.3,3.5,0.3S78.9,42.9,76.9,42.5z   M85.2,42.9c-0.5-0.2-1-0.6-2.1-0.4c-2,0.4-1.8,2.1-1.8,2.1s2.4,0.3,3.5-0.3c1.1-0.6,1.2-1.2,1.2-1.2S85.6,43,85.2,42.9z"
+   id="path4008" />
+<g
+   id="g4010">
+	<g
+   id="g4012">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-237.5,84.9l-3.3-3.3c0.5-0.9,0.9-1.9,0.9-2.9c0-3-2.4-5.5-5.5-5.5    c-3,0-5.5,2.4-5.5,5.5c0,3,2.4,5.5,5.5,5.5c1.1,0,2.1-0.3,3-0.9l3.3,3.3c0.4,0.4,1,0.4,1.4,0l0.2-0.2    C-237.1,85.9-237.1,85.3-237.5,84.9z M-245.4,82c-1.8,0-3.3-1.5-3.3-3.4c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4    C-242,80.5-243.5,82-245.4,82z"
+   id="path4014" />
+	</g>
+</g>
+<g
+   id="g4016">
+	<g
+   id="g4018">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-214.5,86.3l0.2,0.2c0.4,0.4,1,0.4,1.4,0l3.3-3.3c0.9,0.6,1.9,0.9,3,0.9    c3,0,5.5-2.4,5.5-5.5c0-3-2.4-5.5-5.5-5.5c-3,0-5.5,2.4-5.5,5.5c0,1.1,0.3,2.1,0.9,2.9l-3.3,3.3    C-214.9,85.3-214.9,85.9-214.5,86.3z M-210,78.6c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4c0,1.9-1.5,3.4-3.3,3.4    C-208.5,82-210,80.5-210,78.6z"
+   id="path4020" />
+	</g>
+</g>
+<g
+   id="g4022">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-165,77.5h-1v-2c0-0.6-0.4-1-1-1v-1c0-0.6-0.4-1-1-1h-8   c-0.6,0-1,0.4-1,1v1c-0.6,0-1,0.4-1,1v2h-1c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h2v-1h0.5l-1.5,3h12l-1.5-3h0.5v1h2c0.6,0,1-0.4,1-1   v-5C-164,77.9-164.4,77.5-165,77.5z M-176.5,80.5h-1c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5h1c0.3,0,0.5,0.2,0.5,0.5   C-176,80.2-176.2,80.5-176.5,80.5z M-176,85.5l0.9-2h6.2l0.9,2H-176z M-168,77.5c0,0.6-0.4,1-1,1h-6c-0.6,0-1-0.4-1-1v-3   c0-0.6,0.4-1,1-1h6c0.6,0,1,0.4,1,1V77.5z"
+   id="path4024" />
+</g>
+<g
+   id="g4026">
+	<path
+   fill="#00A3F2"
+   d="M-135,73l1.9,1.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6l1.9,1.9v-6H-135z    M-138.5,80.4l-2.6,2.6L-143,81v6h6l-1.9-1.9l2.6-2.6c0.5-0.5,0.5-1.4-0.1-2C-137.1,79.9-138,79.9-138.5,80.4z"
+   id="path4028" />
+</g>
+<g
+   id="g4030">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-93.5,73.5c-0.6-0.6-1.5-0.6-2-0.1l-2.6,2.6L-100,74v6h6   l-1.9-1.9l2.6-2.6C-92.9,75-92.9,74.1-93.5,73.5z M-104.1,81.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6   l1.9,1.9v-6h-6L-104.1,81.9z"
+   id="path4032" />
+</g>
+<g
+   id="g4034">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M14.6,75.1l-2.3,2.3c-0.5,0.5-1.3,0.5-1.7,0   c-0.5-0.5-0.5-1.2,0-1.7l2.4-2.3c-0.6-0.3-1.2-0.5-1.9-0.5C8.8,72.9,7,74.7,7,77c0,0.5,0.1,1.1,0.3,1.5l-5.9,5.8   c-0.6,0.6-0.6,1.7,0,2.3c0.6,0.6,1.7,0.6,2.3,0l6-5.9c0.4,0.2,0.9,0.3,1.4,0.3c2.2,0,4.1-1.8,4.1-4.1   C15.1,76.3,14.9,75.7,14.6,75.1z M2.5,86.4c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9   C3.4,86,3,86.4,2.5,86.4z"
+   id="path4036" />
+</g>
+<path
+   fill="#00A3F2"
+   d="M-22.7,83.9l-1.1-1.1c0.2-0.3,0.3-0.6,0.5-0.9c0.1-0.3,0.2-0.6,0.3-0.9l1.6,0c0.3,0,0.5-0.2,0.5-0.5l0-1  c0-0.3-0.2-0.5-0.5-0.5l-1.6,0c-0.1-0.6-0.4-1.2-0.7-1.8l1.1-1.1c0.2-0.2,0.2-0.5,0-0.7l-0.7-0.7c-0.2-0.2-0.5-0.2-0.7,0l-1.1,1.1  c-0.3-0.2-0.6-0.3-0.9-0.5c-0.3-0.1-0.6-0.2-0.9-0.3l0-1.6c0-0.3-0.2-0.5-0.5-0.5l-1,0c-0.3,0-0.5,0.2-0.5,0.5l0,1.6  c-0.6,0.1-1.2,0.4-1.8,0.7l-1.1-1.1c-0.2-0.2-0.5-0.2-0.7,0l-0.7,0.7c-0.2,0.2-0.2,0.5,0,0.7l1.1,1.1c-0.2,0.3-0.3,0.6-0.5,0.9  c-0.1,0.3-0.2,0.6-0.3,0.9l-1.6,0c-0.3,0-0.5,0.2-0.5,0.5l0,1c0,0.3,0.2,0.5,0.5,0.5l1.6,0c0.1,0.6,0.4,1.2,0.7,1.8l-1.1,1.1  c-0.2,0.2-0.2,0.5,0,0.7l0.7,0.7c0.2,0.2,0.5,0.2,0.7,0l1.1-1.1c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.1,0.6,0.2,0.9,0.3l0,1.6  c0,0.3,0.2,0.5,0.5,0.5l1,0c0.3,0,0.5-0.2,0.5-0.5l0-1.6c0.6-0.1,1.2-0.4,1.8-0.7l1.1,1.1c0.2,0.2,0.5,0.2,0.7,0l0.7-0.7  C-22.5,84.4-22.5,84.1-22.7,83.9z M-28.8,82.1c-1.2-0.5-1.7-1.8-1.3-3c0.5-1.2,1.8-1.7,3-1.3c1.2,0.5,1.7,1.8,1.3,3  C-26.4,82-27.7,82.6-28.8,82.1z"
+   id="path4038" />
+<g
+   id="g4040">
+	<g
+   id="g4042">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-62,77h-4c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h4    c0.6,0,1-0.4,1-1v-4C-61,77.4-61.4,77-62,77z M-62,81c0,0.6-0.4,1-1,1h-2c-0.6,0-1-0.4-1-1v-1c0-0.6,0.4-1,1-1h2c0.6,0,1,0.4,1,1    V81z M-64,87l3-3h-6L-64,87z M-67,76h6l-3-3L-67,76z M-60,76.9v6l3-3L-60,76.9z M-68,82.9v-6l-3,3L-68,82.9z"
+   id="path4044" />
+	</g>
+</g>
+<g
+   id="g4046">
+	<g
+   id="g4048">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M37.5,76.5h13c0.8,0,1.5-0.7,1.5-1.5c0-0.8-0.7-1.5-1.5-1.5h-13    c-0.8,0-1.5,0.7-1.5,1.5C36,75.8,36.7,76.5,37.5,76.5z M50.5,78.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,79.2,51.3,78.5,50.5,78.5z M50.5,83.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,84.2,51.3,83.5,50.5,83.5z"
+   id="path4050" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M85.6,87c-0.8,0-1.5-0.2-2.3-1.3c-0.8-1.1-1.7-2.5-1.7-2.5  s-0.7-0.9-1.1-1.6c-0.4-0.7-1.1-0.5-1.1-0.5s-2.8-4.6-3.3-5.4c-0.7-1,0.6-2.7,0.6-2.7l4.4,7c0,0,1.3,1.9,1.9,2.3  c0.5,0.4,1.4-0.4,2.8,0.9C87.7,85,87.1,87,85.6,87z M85.4,84.1c-0.9-1-1.7-0.9-1.9-0.6c-0.2,0.3,0,1.2,0.4,1.7  c0.4,0.5,0.8,0.7,1.4,0.7C85.9,86,86.4,85.2,85.4,84.1z M81.6,79.4l-1.2-1.8l2.9-4.6c0,0,1.2,1.7,0.6,2.7  C83.6,76.1,82.5,78,81.6,79.4z M77,82.3c0.3-0.3,1-1.1,1.4-1.7l0.8,1.2c-0.4,0.6-0.9,1.4-0.9,1.4s-0.9,1.4-1.7,2.5  c-0.8,1.1-1.5,1.3-2.3,1.3c-1.4,0-2.1-2-0.1-3.8C75.6,82,76.5,82.7,77,82.3z M74.6,84.1c-0.9,1-0.4,1.8,0.2,1.8c0.6,0,1-0.2,1.4-0.7  c0.4-0.5,0.6-1.5,0.4-1.7C76.3,83.2,75.5,83.1,74.6,84.1z"
+   id="path4052" />
+<g
+   id="g4054">
+	<g
+   id="g4056">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-243,111c0-0.1-2-2-2-2c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h4v-6.1c0-0.3,0.2-0.5,0.5-0.5h2.5V111z M-246,112v-2l2,2H-246z M-239,113c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1c0,0,0-4.8,0-7C-237,115-239,113-239,113z M-240,116v-2l2,2H-240z"
+   id="path4058" />
+	</g>
+</g>
+<g
+   id="g4060">
+	<g
+   id="g4062">
+		<path
+   fill="#00A3F2"
+   d="M-204.5,111h-1.3l0,0c0,0-0.2-2-2.2-2c-2,0-2.2,2-2.2,2l0,0h-1.3c-0.8,0-1.5,0.7-1.5,1.5v9    c0,0.8,0.7,1.5,1.5,1.5h7c0.8,0,1.5-0.7,1.5-1.5v-9C-203,111.7-203.7,111-204.5,111z M-210.7,112.1l0.8-0.4l0.4-0.2l0-0.4    c0-0.2,0.2-1.3,1.5-1.3c1.2,0,1.4,1.1,1.5,1.3l0,0.4l0.4,0.2l0.8,0.4l0.3,0.7h-6.1L-210.7,112.1z M-210.1,120.4l-2.8-4.9l3.3-1.9    h4.3l1.8,3.1L-210.1,120.4z"
+   id="path4064" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-177,115h10v2h-10V115z"
+   id="path4066" />
+<g
+   id="g4068">
+	<g
+   id="g4070">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   points="-131,115 -135,115 -135,111 -137,111 -137,115 -141,115    -141,117 -137,117 -137,121 -135,121 -135,117 -131,117   "
+   id="polygon4072" />
+	</g>
+</g>
+<g
+   id="g4074">
+	<path
+   fill="#00A3F2"
+   d="M-94,111.7l-3,2.7v-2c0-0.8-0.6-1.4-1.3-1.4h-7.4c-0.7,0-1.3,0.6-1.3,1.4v7.2c0,0.8,0.6,1.4,1.3,1.4h7.4   c0.7,0,1.3-0.6,1.3-1.4v-2.1l3,2.7c0.3,0.3,0.6,0.4,1,0.3v-9.1C-93.3,111.4-93.7,111.5-94,111.7z"
+   id="path4076" />
+</g>
+<g
+   id="g4078">
+	<g
+   id="g4080">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-61.9,112h-3l3.8,4l-3.8,4h3.1l3.8-4L-61.9,112z M-66.9,112h-3    l3.8,4l-3.8,4h3.1l3.8-4L-66.9,112z"
+   id="path4082" />
+	</g>
+</g>
+<g
+   id="g4084">
+	<g
+   id="g4086">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-23,111.5h-2l-3-3l-3,3h-2c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2    h10c1.1,0,2-0.9,2-2v-8C-21,112.4-21.9,111.5-23,111.5z M-32,121.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,121.5-32,121.5z     M-32,118.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,118.5-32,118.5z M-32,115.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1    S-31.4,115.5-32,115.5z M-23,121.5h-7v-2h7V121.5z M-23,118.5h-7v-2h7V118.5z M-23,115.5h-7v-2h7V115.5z"
+   id="path4088" />
+	</g>
+</g>
+<g
+   id="g4090">
+	<g
+   id="g4092">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M2,115h7c0.6,0,1-0.4,1-1v-4c0-0.6-0.4-1-1-1H2c-0.6,0-1,0.4-1,1    v4C1,114.6,1.4,115,2,115z M14,109h-2c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h2c0.6,0,1-0.4,1-1v-4C15,109.4,14.6,109,14,109z     M14,116H8c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1v-5C15,116.4,14.6,116,14,116z M5,116H2c-0.6,0-1,0.4-1,1v5    c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-5C6,116.4,5.6,116,5,116z"
+   id="path4094" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M45.8,116c0,0-0.6,0.8-1.8,0.8c-1.2,0-1.8-0.8-1.8-0.8l-6-5.1  c0.3-0.5,0.9-0.9,1.6-0.9h12.4c0.7,0,1.3,0.4,1.6,0.9L45.8,116z M42.2,117.7c0,0,0.6,0.8,1.8,0.8c1.2,0,1.8-0.8,1.8-0.8l6.2-5.4v8  c0,0.9-0.8,1.7-1.8,1.7H37.8c-1,0-1.8-0.8-1.8-1.7v-8L42.2,117.7z"
+   id="path4096" />
+<g
+   id="g4098">
+	<g
+   id="g4100">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-237.5,145h-13c-0.8,0-1.5,0.7-1.5,1.5v11    c0,0.8,0.7,1.5,1.5,1.5h13c0.8,0,1.5-0.7,1.5-1.5v-11C-236,145.7-236.7,145-237.5,145z M-245.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C-246,146.2-245.8,146-245.5,146z M-247.6,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-248.1,146.2-247.8,146-247.6,146z M-249.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-250,146.2-249.8,146-249.5,146z M-250,156v-6c0-0.6,0.4-1,1-1h7v8h-7    C-249.6,157-250,156.6-250,156z M-238,156c0,0.6-0.4,1-1,1h-1v-8h1c0.6,0,1,0.4,1,1V156z"
+   id="path4102" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#00A3F2"
+   d="M-213.2,156c-1.3-1.2-2.2-2.8-2.2-4.6c0-3.6,3.3-6.5,7.4-6.5  c4.1,0,7.4,2.9,7.4,6.5c0,3.6-3.3,6.5-7.4,6.5c-0.8,0-1.6-0.1-2.4-0.3c-1.8,0.7-4.3,1.7-4.5,1.4C-213.9,157.9-213.5,156.8-213.2,156  z"
+   id="path4104" />
+<rect
+   id="_x3C_Slice_x3E_"
+   fill="none"
+   width="16"
+   height="16" />
+</svg>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/heartbeat-star-off.svg
@@ -0,0 +1,428 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Generator: Adobe Illustrator 18.1.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   version="1.1"
+   id="Toolbar"
+   x="0px"
+   y="0px"
+   viewBox="0 0 16 16"
+   enable-background="new 0 0 16 16"
+   xml:space="preserve"
+   inkscape:version="0.48.5 r10040"
+   width="100%"
+   height="100%"
+   sodipodi:docname="star-off.svg"><metadata
+   id="metadata5255"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /></cc:Work></rdf:RDF></metadata><defs
+   id="defs5253" /><sodipodi:namedview
+   pagecolor="#ffffff"
+   bordercolor="#666666"
+   borderopacity="1"
+   objecttolerance="10"
+   gridtolerance="10"
+   guidetolerance="10"
+   inkscape:pageopacity="0"
+   inkscape:pageshadow="2"
+   inkscape:window-width="710"
+   inkscape:window-height="480"
+   id="namedview5251"
+   showgrid="false"
+   inkscape:zoom="14.75"
+   inkscape:cx="7.6064963"
+   inkscape:cy="8"
+   inkscape:window-x="0"
+   inkscape:window-y="0"
+   inkscape:window-maximized="0"
+   inkscape:current-layer="Toolbar" />
+<g
+   id="g5070">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-238.2,6h-6.1l2.7-2.7c0.4-0.4,0.5-1.1,0.2-1.4l-1.2-1.2   c-0.3-0.3-1-0.3-1.4,0.2l-6.4,6.4c-0.1,0.1-0.1,0.2-0.2,0.2l0,0c0,0,0,0,0,0c0,0.1-0.1,0.1-0.1,0.1c0,0.1-0.1,0.2-0.1,0.3   c0,0,0,0.1,0,0.1c0,0,0,0.1,0,0.1c0,0.1,0,0.2,0.1,0.3c0,0.1,0.1,0.1,0.1,0.1c0,0,0,0,0,0l0,0c0.1,0.1,0.1,0.2,0.2,0.2l6.4,6.4   c0.4,0.4,1.1,0.5,1.4,0.2l1.2-1.2c0.3-0.3,0.3-1-0.2-1.4l-2.8-2.8h6.2c0.6,0,1-0.4,1-1V7C-237.2,6.4-237.6,6-238.2,6z"
+   id="path5072" />
+</g>
+<g
+   id="g5074">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-212.7,6.3l-1,1c-0.2,0.2-0.2,0.4-0.2,0.7c0,0.3,0,0.5,0.2,0.7   l0.8,0.9l4.5,4.5c0.5,0.5,1.2,0.6,1.6,0.2l0.8-0.9c0.4-0.4,0.3-1.1-0.2-1.6l-1.8-1.8h4.7c0.6,0,1-0.5,1-1V7c0-0.6-0.5-1-1-1h-4.8   l1.9-1.9c0.5-0.5,0.6-1.3,0.2-1.6l-0.8-0.9c-0.4-0.4-1.1-0.3-1.6,0.2L-212.7,6.3z"
+   id="path5076" />
+</g>
+<g
+   id="g5078">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-167.3,9.7l1-1c0.2-0.2,0.2-0.4,0.2-0.7c0-0.3,0-0.5-0.2-0.7   l-0.8-0.9l-4.5-4.5c-0.5-0.5-1.2-0.6-1.6-0.2l-0.8,0.9c-0.4,0.4-0.3,1.1,0.2,1.6l1.8,1.8h-4.7c-0.6,0-1,0.5-1,1v2c0,0.6,0.5,1,1,1   h4.8l-1.9,1.9c-0.5,0.5-0.6,1.3-0.2,1.6l0.8,0.9c0.4,0.4,1.1,0.3,1.6-0.2L-167.3,9.7z"
+   id="path5080" />
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-129.5,8h-7l2.8-2.8c-0.7-0.5-1.4-0.7-2.3-0.7c-2.2,0-4,1.8-4,4  c0,2.2,1.8,4,4,4c1.4,0,2.7-0.7,3.4-1.9l2.3,1c-1.1,2-3.2,3.4-5.7,3.4c-3.6,0-6.5-2.9-6.5-6.5c0-3.6,2.9-6.5,6.5-6.5  c1.5,0,2.9,0.5,4.1,1.4l2.4-2.4V8z"
+   id="path5082" />
+<g
+   id="g5084">
+	<g
+   id="g5086">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   points="-93.6,3.8 -95.8,1.7 -100,5.9 -104.3,1.6 -106.4,3.8     -102.1,8 -106.3,12.2 -104.1,14.3 -99.9,10.2 -95.7,14.4 -93.6,12.2 -97.8,8.1   "
+   id="polygon5088" />
+	</g>
+</g>
+<g
+   id="g5090">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-64,1l-8,7h2l6-5l6,5h2L-64,1z M-69,8v7h4v-5h2v5h4V8l-5-4L-69,8   z"
+   id="path5092" />
+</g>
+<path
+   fill="#231F20"
+   d="M-28,3.6l1,2l0.5,0.9l1,0.2l2.3,0.4l-1.7,1.8l-0.7,0.7l0.1,1l0.4,2.4l-2-1l-0.9-0.5l-0.9,0.5l-2,1l0.4-2.4  l0.1-1l-0.7-0.7l-1.7-1.8l2.4-0.4l1-0.2l0.5-0.9L-28,3.6 M-28,0c-0.3,0-0.6,0.2-0.8,0.7l-2,4.1l-4.3,0.7c-1,0.2-1.2,0.9-0.5,1.6  l3.1,3.3l-0.7,4.6c-0.1,0.7,0.2,1.1,0.7,1.1c0.2,0,0.4-0.1,0.6-0.2l3.9-2.1l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1  l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6l-4.3-0.7l-2-4.1C-27.4,0.2-27.7,0-28,0L-28,0z"
+   id="path5094" />
+<path
+   fill="#231F20"
+   d="M8,0C7.7,0,7.4,0.2,7.2,0.7l-2,4.1L0.9,5.5c-1,0.2-1.2,0.9-0.5,1.6l3.1,3.3l-0.7,4.6C2.7,15.6,3,16,3.4,16  c0.2,0,0.4-0.1,0.6-0.2L8,13.7l3.9,2.1c0.2,0.1,0.5,0.2,0.6,0.2c0.5,0,0.8-0.4,0.7-1.1l-0.7-4.6l3.1-3.3c0.7-0.7,0.4-1.4-0.5-1.6  l-4.3-0.7l-2-4.1C8.6,0.2,8.3,0,8,0L8,0z"
+   id="path5096"
+   style="fill:#c0c0c0;fill-opacity:1" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M44,15.2c-4,0-7.2-3.2-7.2-7.2c0-4,3.2-7.2,7.2-7.2  c4,0,7.2,3.2,7.2,7.2C51.2,12,48,15.2,44,15.2z M44,3c-2.8,0-5,2.2-5,5c0,2.7,2.2,5,5,5c2.8,0,5-2.2,5-5C49,5.3,46.8,3,44,3z   M43.7,8.9C43.3,8.8,43,8.4,43,8V5c0-0.6,0.4-1,1-1c0.6,0,1,0.4,1,1v2.8c1.1,1.1,2,3.2,2,3.2S44.8,10,43.7,8.9z"
+   id="path5098" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M86.7,9.1l-5.6,5.5C80.8,14.9,80.4,15,80,15  c-0.4,0-0.8-0.1-1.1-0.4l-5.6-5.5C72.7,8.5,72.9,8,73.8,8H77l0-6c0-0.6,0.4-1,1-1h4c0.6,0,1,0.4,1,1v6h3.2C87.1,8,87.3,8.5,86.7,9.1  z"
+   id="path5100" />
+<path
+   fill="#231F20"
+   d="M-241,52c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-241z"
+   id="path5102" />
+<g
+   id="g5104">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.4,41.9h-0.3v0h-14.4v0h-0.3c-0.2,0-0.4,0.2-0.4,0.4c0,0,0.3,2.2,0.5,4.4   c0.2,2.5,0.2,4.2,0.2,4.2c0,0.2,0.2,0.4,0.4,0.4h13.7c0.2,0,0.4-0.2,0.4-0.4c0,0,0.1-2.1,0.2-4.2c0.1-2.2,0.5-4.4,0.5-4.4   C-200.1,42.1-200.2,41.9-200.4,41.9z"
+   id="path5106" />
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-200.8,41.3v-2c0-0.3-0.2-0.5-0.5-0.5l-6.7,0l-0.8-1.1c0,0-0.6-0.9-1.2-0.9h-4.2   c-0.6,0-1,0.5-1,1v1l0,2.5H-200.8z"
+   id="path5108" />
+</g>
+<g
+   id="g5110">
+	<g
+   id="g5112">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-169,37h-7.5c-0.8,0-1.5,0.7-1.5,1.5v11c0,0.8,0.7,1.5,1.5,1.5    h9c0.8,0,1.5-0.7,1.5-1.5V40L-169,37z M-170,41v-3.3l3.3,3.3H-170z"
+   id="path5114" />
+	</g>
+</g>
+<g
+   id="g5116">
+	<g
+   id="g5118">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-132.5,38c-0.4,0.2-0.9,0.6-1.3,1.2c-0.3,0.4-0.5,0.9-0.8,1.3    c0.7,0.3,1.3,0.8,1.7,1.4c0.4,0.7,0.7,1.5,0.6,2.3c-0.1,1.3-1,2.4-2.1,3c-0.5-0.5-1.4-2.1-1.4-2.1c0,0,0,1.8-0.6,3    c-0.4,0.8-0.9,1.5-1.6,2.1c1,0.4,2,0.6,3,0.6l0-0.1c0,0,0.1,0.1,0.1,0.1c2.1,0,4.1-0.3,4.1-0.3s-0.9-0.6-1.3-0.9    c1.3-0.7,2.2-2,2.5-3.3c0.2-0.5,0.3-1.1,0.4-1.7C-128.7,41.9-130.2,39.3-132.5,38z M-139.8,44.2c0-1.4,0.8-2.8,2.2-3.5    c0.5,0.5,1.6,2,1.6,2s0-3.6,1.2-5.6c-4.3-0.5-6.8,0.3-6.8,0.3s1.2,0.4,1.6,0.8c-0.1,0.1-0.2,0.1-0.3,0.2c-1,0.7-1.7,1.6-2.1,2.7    c-0.3,0.6-0.5,1.3-0.5,2c-0.3,2.8,1.1,5.4,3.4,6.7c0.4-0.3,0.8-0.6,1.2-1.1c0.4-0.4,0.7-0.9,1-1.4    C-138.7,46.9-139.7,45.7-139.8,44.2z"
+   id="path5120" />
+	</g>
+</g>
+<g
+   id="g5122">
+	<g
+   id="g5124">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-96.6,41.5c-3.3-4-9.6-4.5-9.6-4.5c-0.4,0-0.8,0.3-0.8,0.7v1.4    c0,0.4,0.3,0.7,0.8,0.7c0,0,4.5,0.1,7.1,3.2c3.5,3.6,3.2,7.2,3.2,7.2c0,0.4,0.3,0.8,0.8,0.8h1.5c0.4,0,0.7-0.3,0.7-0.8    C-93,50.2-93.3,44.5-96.6,41.5z M-106.2,42c-0.4,0-0.8,0.3-0.8,0.7V44c0,0.4,0.3,0.7,0.8,0.7c0,0,2.4,0.1,3.8,1.4    c1.9,1.7,1.8,4.2,1.8,4.2c0,0.4,0.2,0.8,0.6,0.8h1.5c0.4,0,0.5-0.3,0.5-0.8c0,0-0.4-3.9-2.5-5.9C-102.5,42.4-106.2,42-106.2,42z     M-105,47c-1.1,0-2,0.9-2,2c0,1.1,0.9,2,2,2c1.1,0,2-0.9,2-2C-103,47.9-103.9,47-105,47z"
+   id="path5126" />
+	</g>
+</g>
+<path
+   fill="none"
+   d="M-102.6,34.4c0.5,0,1-0.4,1-1v-4c0,0,0.1-0.8,0.8-0.8c0.6,0,0.6,0.8,1.8,0.8c0.6,0,1.5-0.2,1.5-2  c0-1.8-0.9-2-1.5-2c-1.1,0-1.1,0.8-1.8,0.8c-0.6,0-0.8-0.8-0.8-0.8v-2c0-0.6-0.4-1-1-1h-3c0,0-0.8-0.1-0.8-0.8  c0-0.6,0.8-0.6,0.8-1.8c0-0.6-0.2-1.5-2-1.5c-1.8,0-2,0.9-2,1.5c0,1.1,0.8,1.1,0.8,1.8c0,0.6-0.8,0.8-0.8,0.8h-3c-0.5,0-1,0.4-1,1  l0,2.5c0,0-0.1,1.5,1.1,1.5c0.8,0,0.9-1,1.9-1c0.5,0,1,0.5,1,1.6c0,1-0.5,1.6-1,1.6c-1,0-1.1-1-1.9-1c-1.2,0-1.1,1.5-1.1,1.5l0,3.5  c0,0.6,0.4,1,1,1h3.8c0,0,1.5,0.1,1.5-1.1c0-0.8-1-0.9-1-1.9c0-0.5,0.7-1.2,1.8-1.2c1,0,1.8,0.7,1.8,1.2c0,1-1,1.1-1,1.9  c0,1.2,1.5,1.1,1.5,1.1H-102.6z"
+   id="path5128" />
+<g
+   id="g5130">
+	<g
+   id="g5132">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-63,46.7l-0.3-0.1L-63,52l2.6-3.8L-63,46.7z M-63.3,46l0.3,0    l6,2.9V36l-14,11l4.6-0.6L-63,52l-1.9-5.7l-0.3-0.1l0.3,0L-65,46l7-8L-63.3,46L-63.3,46z"
+   id="path5134" />
+	</g>
+</g>
+<g
+   id="g5136">
+	<g
+   id="g5138">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-23,37h-10c-1.1,0-2,0.9-2,2v10c0,1.1,0.9,2,2,2h10    c1.1,0,2-0.9,2-2V39C-21,37.9-21.9,37-23,37z M-23,46c0,1.7-1.3,2-3,2h-4c-1.7,0-3-1.3-3-3v-4c0-1.7,1.3-3,3-3h4c1.7,0,3,1.3,3,3    V46z"
+   id="path5140" />
+	</g>
+</g>
+<path
+   fill="#231F20"
+   d="M-23.7,42.6c0-0.8-0.2-1.5-0.6-2c-0.4-0.5-1-0.7-1.7-0.7c-0.4,0-0.8,0.1-1.1,0.3s-0.6,0.4-0.8,0.8  c-0.2-0.4-0.4-0.6-0.7-0.8c-0.3-0.2-0.7-0.2-1.2-0.2c-0.4,0-0.7,0-1.1,0.1s-0.7,0.2-1,0.4l0.3,0.7c0.6-0.3,1.2-0.5,1.7-0.5  c0.4,0,0.8,0.1,1,0.3c0.2,0.2,0.3,0.6,0.3,1.1v0.4l-1,0c-0.9,0-1.6,0.2-2.1,0.5c-0.5,0.3-0.7,0.8-0.7,1.4c0,0.6,0.2,1,0.5,1.3  c0.3,0.3,0.8,0.5,1.4,0.5c0.5,0,0.9-0.1,1.3-0.3c0.4-0.2,0.7-0.5,1-1c0.5,0.9,1.2,1.3,2.3,1.3c0.4,0,0.7,0,1-0.1  c0.3-0.1,0.6-0.2,0.9-0.3v-0.8c-0.3,0.1-0.6,0.3-0.9,0.3c-0.3,0.1-0.6,0.1-0.9,0.1c-1.2,0-1.8-0.7-1.8-2.2h3.9V42.6z M-28.5,43.6  c0,0.6-0.2,1-0.5,1.3c-0.3,0.3-0.7,0.5-1.3,0.5c-0.3,0-0.6-0.1-0.8-0.2c-0.2-0.2-0.3-0.4-0.3-0.8c0-0.4,0.2-0.7,0.5-0.9  c0.3-0.2,0.8-0.3,1.5-0.3l0.9,0V43.6z M-27.5,42.4c0-0.6,0.2-1,0.4-1.3c0.3-0.3,0.6-0.5,1.1-0.5c0.4,0,0.8,0.2,1,0.5  c0.2,0.3,0.4,0.8,0.4,1.3H-27.5z"
+   id="path5142" />
+<g
+   id="g5144">
+	<g
+   id="g5146">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M14.5,37h-13C0.7,37,0,37.7,0,38.5v11C0,50.3,0.7,51,1.5,51h13    c0.8,0,1.5-0.7,1.5-1.5v-11C16,37.7,15.3,37,14.5,37z M6.5,38C6.8,38,7,38.2,7,38.5C7,38.8,6.8,39,6.5,39C6.2,39,6,38.8,6,38.5    C6,38.2,6.2,38,6.5,38z M4.4,38c0.3,0,0.5,0.2,0.5,0.5c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C3.9,38.2,4.2,38,4.4,38z     M2.5,38C2.8,38,3,38.2,3,38.5C3,38.8,2.8,39,2.5,39C2.2,39,2,38.8,2,38.5C2,38.2,2.2,38,2.5,38z M14,48c0,0.6-0.4,1-1,1H3    c-0.6,0-1-0.4-1-1v-6c0-0.6,0.4-1,1-1h10c0.6,0,1,0.4,1,1V48z"
+   id="path5148" />
+	</g>
+</g>
+<path
+   fill="#231F20"
+   d="M51.2,45.9L51.2,45.9c-0.4-0.1-0.7-0.3-1-0.6c-0.1-0.1-0.1-0.1-0.2-0.2c-0.1-0.1-0.1-0.1-0.2-0.2  c0-0.1-0.1-0.1-0.1-0.2c0-0.1-0.1-0.1-0.1-0.2c-0.3-0.6-0.4-1.5-0.5-2.4c-0.3-2.2-0.1-3.8-3-3.8h-4.4c-2.9,0-2.6,1.6-2.9,3.8  c-0.1,0.9-0.3,1.8-0.6,2.4c0,0,0,0,0,0c-0.1,0.1-0.1,0.2-0.2,0.3c0,0,0,0-0.1,0.1c-0.1,0.2-0.2,0.3-0.4,0.4  c-0.3,0.2-0.5,0.4-0.9,0.5l0,0C36.4,46,36,46.3,36,46.8v1.9c0,0.5,0.4,0.9,0.9,0.9h14.1c0.5,0,0.9-0.4,0.9-0.9v-1.9  C52,46.3,51.6,46,51.2,45.9z M46.4,43.8c0,0.1-0.1,0.2-0.2,0.2h-1.6v1.6c0,0.1-0.1,0.2-0.2,0.2h-0.5c-0.1,0-0.2-0.1-0.2-0.2V44h-1.6  c-0.1,0-0.2-0.1-0.2-0.2v-0.5c0-0.1,0.1-0.2,0.2-0.2h1.6v-1.6c0-0.1,0.1-0.2,0.2-0.2h0.5c0.1,0,0.2,0.1,0.2,0.2v1.6h1.6  c0.1,0,0.2,0.1,0.2,0.2V43.8z"
+   id="path5150" />
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M87.7,42c-0.1,1.1,0.2,2.5-1.3,4.4c-1.6,2.1-3.1,2.1-3.4,2.1  c-1.8-0.1-2-1.5-3-1.5c-0.9,0-1.6,1.4-3,1.5c-0.3,0-1.9,0-3.4-2c-1.5-1.9-1.2-3.3-1.3-4.4S72,39.7,72,39.7s0.7,0.7,1.6,0.8  c0.9,0.1,1.1-0.3,3-0.9c2.1-0.6,3.4,1.9,3.4,1.9s1.4-2.4,3.4-1.9c1.9,0.6,2,0.9,2.9,0.9c0.9-0.1,1.7-0.8,1.7-0.8S87.8,40.9,87.7,42z   M76.9,42.5c-1.1-0.3-1.6,0.2-2.1,0.4C74.4,43,74,43.1,74,43.1s0.1,0.7,1.2,1.2c1.1,0.6,3.5,0.3,3.5,0.3S78.9,42.9,76.9,42.5z   M85.2,42.9c-0.5-0.2-1-0.6-2.1-0.4c-2,0.4-1.8,2.1-1.8,2.1s2.4,0.3,3.5-0.3c1.1-0.6,1.2-1.2,1.2-1.2S85.6,43,85.2,42.9z"
+   id="path5152" />
+<g
+   id="g5154">
+	<g
+   id="g5156">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-237.5,84.9l-3.3-3.3c0.5-0.9,0.9-1.9,0.9-2.9c0-3-2.4-5.5-5.5-5.5    c-3,0-5.5,2.4-5.5,5.5c0,3,2.4,5.5,5.5,5.5c1.1,0,2.1-0.3,3-0.9l3.3,3.3c0.4,0.4,1,0.4,1.4,0l0.2-0.2    C-237.1,85.9-237.1,85.3-237.5,84.9z M-245.4,82c-1.8,0-3.3-1.5-3.3-3.4c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4    C-242,80.5-243.5,82-245.4,82z"
+   id="path5158" />
+	</g>
+</g>
+<g
+   id="g5160">
+	<g
+   id="g5162">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M-214.5,86.3l0.2,0.2c0.4,0.4,1,0.4,1.4,0l3.3-3.3c0.9,0.6,1.9,0.9,3,0.9    c3,0,5.5-2.4,5.5-5.5c0-3-2.4-5.5-5.5-5.5c-3,0-5.5,2.4-5.5,5.5c0,1.1,0.3,2.1,0.9,2.9l-3.3,3.3    C-214.9,85.3-214.9,85.9-214.5,86.3z M-210,78.6c0-1.9,1.5-3.4,3.3-3.4c1.8,0,3.3,1.5,3.3,3.4c0,1.9-1.5,3.4-3.3,3.4    C-208.5,82-210,80.5-210,78.6z"
+   id="path5164" />
+	</g>
+</g>
+<g
+   id="g5166">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-165,77.5h-1v-2c0-0.6-0.4-1-1-1v-1c0-0.6-0.4-1-1-1h-8   c-0.6,0-1,0.4-1,1v1c-0.6,0-1,0.4-1,1v2h-1c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h2v-1h0.5l-1.5,3h12l-1.5-3h0.5v1h2c0.6,0,1-0.4,1-1   v-5C-164,77.9-164.4,77.5-165,77.5z M-176.5,80.5h-1c-0.3,0-0.5-0.2-0.5-0.5c0-0.3,0.2-0.5,0.5-0.5h1c0.3,0,0.5,0.2,0.5,0.5   C-176,80.2-176.2,80.5-176.5,80.5z M-176,85.5l0.9-2h6.2l0.9,2H-176z M-168,77.5c0,0.6-0.4,1-1,1h-6c-0.6,0-1-0.4-1-1v-3   c0-0.6,0.4-1,1-1h6c0.6,0,1,0.4,1,1V77.5z"
+   id="path5168" />
+</g>
+<g
+   id="g5170">
+	<path
+   fill="#231F20"
+   d="M-135,73l1.9,1.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6l1.9,1.9v-6H-135z    M-138.5,80.4l-2.6,2.6L-143,81v6h6l-1.9-1.9l2.6-2.6c0.5-0.5,0.5-1.4-0.1-2C-137.1,79.9-138,79.9-138.5,80.4z"
+   id="path5172" />
+</g>
+<g
+   id="g5174">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-93.5,73.5c-0.6-0.6-1.5-0.6-2-0.1l-2.6,2.6L-100,74v6h6   l-1.9-1.9l2.6-2.6C-92.9,75-92.9,74.1-93.5,73.5z M-104.1,81.9l-2.6,2.6c-0.5,0.5-0.5,1.4,0.1,2c0.6,0.6,1.5,0.6,2,0.1l2.6-2.6   l1.9,1.9v-6h-6L-104.1,81.9z"
+   id="path5176" />
+</g>
+<g
+   id="g5178">
+	<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M14.6,75.1l-2.3,2.3c-0.5,0.5-1.3,0.5-1.7,0   c-0.5-0.5-0.5-1.2,0-1.7l2.4-2.3c-0.6-0.3-1.2-0.5-1.9-0.5C8.8,72.9,7,74.7,7,77c0,0.5,0.1,1.1,0.3,1.5l-5.9,5.8   c-0.6,0.6-0.6,1.7,0,2.3c0.6,0.6,1.7,0.6,2.3,0l6-5.9c0.4,0.2,0.9,0.3,1.4,0.3c2.2,0,4.1-1.8,4.1-4.1   C15.1,76.3,14.9,75.7,14.6,75.1z M2.5,86.4c-0.5,0-0.9-0.4-0.9-0.9c0-0.5,0.4-0.9,0.9-0.9c0.5,0,0.9,0.4,0.9,0.9   C3.4,86,3,86.4,2.5,86.4z"
+   id="path5180" />
+</g>
+<path
+   fill="#231F20"
+   d="M-22.7,83.9l-1.1-1.1c0.2-0.3,0.3-0.6,0.5-0.9c0.1-0.3,0.2-0.6,0.3-0.9l1.6,0c0.3,0,0.5-0.2,0.5-0.5l0-1  c0-0.3-0.2-0.5-0.5-0.5l-1.6,0c-0.1-0.6-0.4-1.2-0.7-1.8l1.1-1.1c0.2-0.2,0.2-0.5,0-0.7l-0.7-0.7c-0.2-0.2-0.5-0.2-0.7,0l-1.1,1.1  c-0.3-0.2-0.6-0.3-0.9-0.5c-0.3-0.1-0.6-0.2-0.9-0.3l0-1.6c0-0.3-0.2-0.5-0.5-0.5l-1,0c-0.3,0-0.5,0.2-0.5,0.5l0,1.6  c-0.6,0.1-1.2,0.4-1.8,0.7l-1.1-1.1c-0.2-0.2-0.5-0.2-0.7,0l-0.7,0.7c-0.2,0.2-0.2,0.5,0,0.7l1.1,1.1c-0.2,0.3-0.3,0.6-0.5,0.9  c-0.1,0.3-0.2,0.6-0.3,0.9l-1.6,0c-0.3,0-0.5,0.2-0.5,0.5l0,1c0,0.3,0.2,0.5,0.5,0.5l1.6,0c0.1,0.6,0.4,1.2,0.7,1.8l-1.1,1.1  c-0.2,0.2-0.2,0.5,0,0.7l0.7,0.7c0.2,0.2,0.5,0.2,0.7,0l1.1-1.1c0.3,0.2,0.6,0.3,0.9,0.5c0.3,0.1,0.6,0.2,0.9,0.3l0,1.6  c0,0.3,0.2,0.5,0.5,0.5l1,0c0.3,0,0.5-0.2,0.5-0.5l0-1.6c0.6-0.1,1.2-0.4,1.8-0.7l1.1,1.1c0.2,0.2,0.5,0.2,0.7,0l0.7-0.7  C-22.5,84.4-22.5,84.1-22.7,83.9z M-28.8,82.1c-1.2-0.5-1.7-1.8-1.3-3c0.5-1.2,1.8-1.7,3-1.3c1.2,0.5,1.7,1.8,1.3,3  C-26.4,82-27.7,82.6-28.8,82.1z"
+   id="path5182" />
+<g
+   id="g5184">
+	<g
+   id="g5186">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-62,77h-4c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h4    c0.6,0,1-0.4,1-1v-4C-61,77.4-61.4,77-62,77z M-62,81c0,0.6-0.4,1-1,1h-2c-0.6,0-1-0.4-1-1v-1c0-0.6,0.4-1,1-1h2c0.6,0,1,0.4,1,1    V81z M-64,87l3-3h-6L-64,87z M-67,76h6l-3-3L-67,76z M-60,76.9v6l3-3L-60,76.9z M-68,82.9v-6l-3,3L-68,82.9z"
+   id="path5188" />
+	</g>
+</g>
+<g
+   id="g5190">
+	<g
+   id="g5192">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   d="M37.5,76.5h13c0.8,0,1.5-0.7,1.5-1.5c0-0.8-0.7-1.5-1.5-1.5h-13    c-0.8,0-1.5,0.7-1.5,1.5C36,75.8,36.7,76.5,37.5,76.5z M50.5,78.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,79.2,51.3,78.5,50.5,78.5z M50.5,83.5h-13c-0.8,0-1.5,0.7-1.5,1.5c0,0.8,0.7,1.5,1.5,1.5h13    c0.8,0,1.5-0.7,1.5-1.5C52,84.2,51.3,83.5,50.5,83.5z"
+   id="path5194" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M85.6,87c-0.8,0-1.5-0.2-2.3-1.3c-0.8-1.1-1.7-2.5-1.7-2.5  s-0.7-0.9-1.1-1.6c-0.4-0.7-1.1-0.5-1.1-0.5s-2.8-4.6-3.3-5.4c-0.7-1,0.6-2.7,0.6-2.7l4.4,7c0,0,1.3,1.9,1.9,2.3  c0.5,0.4,1.4-0.4,2.8,0.9C87.7,85,87.1,87,85.6,87z M85.4,84.1c-0.9-1-1.7-0.9-1.9-0.6c-0.2,0.3,0,1.2,0.4,1.7  c0.4,0.5,0.8,0.7,1.4,0.7C85.9,86,86.4,85.2,85.4,84.1z M81.6,79.4l-1.2-1.8l2.9-4.6c0,0,1.2,1.7,0.6,2.7  C83.6,76.1,82.5,78,81.6,79.4z M77,82.3c0.3-0.3,1-1.1,1.4-1.7l0.8,1.2c-0.4,0.6-0.9,1.4-0.9,1.4s-0.9,1.4-1.7,2.5  c-0.8,1.1-1.5,1.3-2.3,1.3c-1.4,0-2.1-2-0.1-3.8C75.6,82,76.5,82.7,77,82.3z M74.6,84.1c-0.9,1-0.4,1.8,0.2,1.8c0.6,0,1-0.2,1.4-0.7  c0.4-0.5,0.6-1.5,0.4-1.7C76.3,83.2,75.5,83.1,74.6,84.1z"
+   id="path5196" />
+<g
+   id="g5198">
+	<g
+   id="g5200">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-243,111c0-0.1-2-2-2-2c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h4v-6.1c0-0.3,0.2-0.5,0.5-0.5h2.5V111z M-246,112v-2l2,2H-246z M-239,113c-1.7,0-5,0-5,0c-0.6,0-1,0.4-1,1v8    c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1c0,0,0-4.8,0-7C-237,115-239,113-239,113z M-240,116v-2l2,2H-240z"
+   id="path5202" />
+	</g>
+</g>
+<g
+   id="g5204">
+	<g
+   id="g5206">
+		<path
+   fill="#231F20"
+   d="M-204.5,111h-1.3l0,0c0,0-0.2-2-2.2-2c-2,0-2.2,2-2.2,2l0,0h-1.3c-0.8,0-1.5,0.7-1.5,1.5v9    c0,0.8,0.7,1.5,1.5,1.5h7c0.8,0,1.5-0.7,1.5-1.5v-9C-203,111.7-203.7,111-204.5,111z M-210.7,112.1l0.8-0.4l0.4-0.2l0-0.4    c0-0.2,0.2-1.3,1.5-1.3c1.2,0,1.4,1.1,1.5,1.3l0,0.4l0.4,0.2l0.8,0.4l0.3,0.7h-6.1L-210.7,112.1z M-210.1,120.4l-2.8-4.9l3.3-1.9    h4.3l1.8,3.1L-210.1,120.4z"
+   id="path5208" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-177,115h10v2h-10V115z"
+   id="path5210" />
+<g
+   id="g5212">
+	<g
+   id="g5214">
+		<polygon
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   points="-131,115 -135,115 -135,111 -137,111 -137,115 -141,115     -141,117 -137,117 -137,121 -135,121 -135,117 -131,117   "
+   id="polygon5216" />
+	</g>
+</g>
+<g
+   id="g5218">
+	<path
+   fill="#231F20"
+   d="M-94,111.7l-3,2.7v-2c0-0.8-0.6-1.4-1.3-1.4h-7.4c-0.7,0-1.3,0.6-1.3,1.4v7.2c0,0.8,0.6,1.4,1.3,1.4h7.4   c0.7,0,1.3-0.6,1.3-1.4v-2.1l3,2.7c0.3,0.3,0.6,0.4,1,0.3v-9.1C-93.3,111.4-93.7,111.5-94,111.7z"
+   id="path5220" />
+</g>
+<g
+   id="g5222">
+	<g
+   id="g5224">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-61.9,112h-3l3.8,4l-3.8,4h3.1l3.8-4L-61.9,112z M-66.9,112h-3    l3.8,4l-3.8,4h3.1l3.8-4L-66.9,112z"
+   id="path5226" />
+	</g>
+</g>
+<g
+   id="g5228">
+	<g
+   id="g5230">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-23,111.5h-2l-3-3l-3,3h-2c-1.1,0-2,0.9-2,2v8c0,1.1,0.9,2,2,2    h10c1.1,0,2-0.9,2-2v-8C-21,112.4-21.9,111.5-23,111.5z M-32,121.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,121.5-32,121.5z     M-32,118.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1S-31.4,118.5-32,118.5z M-32,115.5c-0.6,0-1-0.4-1-1s0.4-1,1-1s1,0.4,1,1    S-31.4,115.5-32,115.5z M-23,121.5h-7v-2h7V121.5z M-23,118.5h-7v-2h7V118.5z M-23,115.5h-7v-2h7V115.5z"
+   id="path5232" />
+	</g>
+</g>
+<g
+   id="g5234">
+	<g
+   id="g5236">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M2,115h7c0.6,0,1-0.4,1-1v-4c0-0.6-0.4-1-1-1H2c-0.6,0-1,0.4-1,1    v4C1,114.6,1.4,115,2,115z M14,109h-2c-0.6,0-1,0.4-1,1v4c0,0.6,0.4,1,1,1h2c0.6,0,1-0.4,1-1v-4C15,109.4,14.6,109,14,109z     M14,116H8c-0.6,0-1,0.4-1,1v5c0,0.6,0.4,1,1,1h6c0.6,0,1-0.4,1-1v-5C15,116.4,14.6,116,14,116z M5,116H2c-0.6,0-1,0.4-1,1v5    c0,0.6,0.4,1,1,1h3c0.6,0,1-0.4,1-1v-5C6,116.4,5.6,116,5,116z"
+   id="path5238" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M45.8,116c0,0-0.6,0.8-1.8,0.8c-1.2,0-1.8-0.8-1.8-0.8l-6-5.1  c0.3-0.5,0.9-0.9,1.6-0.9h12.4c0.7,0,1.3,0.4,1.6,0.9L45.8,116z M42.2,117.7c0,0,0.6,0.8,1.8,0.8c1.2,0,1.8-0.8,1.8-0.8l6.2-5.4v8  c0,0.9-0.8,1.7-1.8,1.7H37.8c-1,0-1.8-0.8-1.8-1.7v-8L42.2,117.7z"
+   id="path5240" />
+<g
+   id="g5242">
+	<g
+   id="g5244">
+		<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-237.5,145h-13c-0.8,0-1.5,0.7-1.5,1.5v11    c0,0.8,0.7,1.5,1.5,1.5h13c0.8,0,1.5-0.7,1.5-1.5v-11C-236,145.7-236.7,145-237.5,145z M-245.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5c-0.3,0-0.5-0.2-0.5-0.5C-246,146.2-245.8,146-245.5,146z M-247.6,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-248.1,146.2-247.8,146-247.6,146z M-249.5,146c0.3,0,0.5,0.2,0.5,0.5    c0,0.3-0.2,0.5-0.5,0.5s-0.5-0.2-0.5-0.5C-250,146.2-249.8,146-249.5,146z M-250,156v-6c0-0.6,0.4-1,1-1h7v8h-7    C-249.6,157-250,156.6-250,156z M-238,156c0,0.6-0.4,1-1,1h-1v-8h1c0.6,0,1,0.4,1,1V156z"
+   id="path5246" />
+	</g>
+</g>
+<path
+   fill-rule="evenodd"
+   clip-rule="evenodd"
+   fill="#231F20"
+   d="M-213.2,156c-1.3-1.2-2.2-2.8-2.2-4.6c0-3.6,3.3-6.5,7.4-6.5  c4.1,0,7.4,2.9,7.4,6.5c0,3.6-3.3,6.5-7.4,6.5c-0.8,0-1.6-0.1-2.4-0.3c-1.8,0.7-4.3,1.7-4.5,1.4C-213.9,157.9-213.5,156.8-213.2,156  z"
+   id="path5248" />
+<rect
+   id="_x3C_Slice_x3E_"
+   fill="none"
+   width="16"
+   height="16" />
+</svg>
\ No newline at end of file
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -135,8 +135,100 @@ menu.subviewbutton > .menu-right:-moz-lo
     #zoom-controls@inAnyPanel@,
     #edit-controls@inAnyPanel@ > toolbarbutton,
     #zoom-controls@inAnyPanel@ > toolbarbutton {
       border-radius: 0;
     }
   }
 }
 %endif
+
+@media not all and (-moz-windows-default-theme) {
+  #edit-controls@inAnyPanel@ > #copy-button,
+  #zoom-controls@inAnyPanel@ > #zoom-reset-button,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton {
+    border: 1px solid transparent;
+  }
+
+  panelview .toolbarbutton-1@buttonStateHover@,
+  toolbarbutton.subviewbutton@buttonStateHover@,
+  menu.subviewbutton@menuStateHover@,
+  menuitem.subviewbutton@menuStateHover@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateHover@,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateHover@ {
+    border-color: ThreeDLightShadow !important;
+  }
+
+  panelview:not(#PanelUI-mainView) .toolbarbutton-1@buttonStateHover@,
+  toolbarbutton.subviewbutton@buttonStateHover@,
+  menu.subviewbutton@menuStateHover@,
+  menuitem.subviewbutton@menuStateHover@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateHover@ {
+    background-color: Highlight;
+    color: highlighttext;
+  }
+
+  panelview .toolbarbutton-1:-moz-any(@buttonStateActive@,[checked=true]),
+  toolbarbutton.subviewbutton@buttonStateActive@,
+  menu.subviewbutton@menuStateActive@,
+  menuitem.subviewbutton@menuStateActive@,
+  .widget-overflow-list .toolbarbutton-1@buttonStateActive@,
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton@buttonStateActive@ {
+    background-color: Highlight;
+    border-color: ThreeDLightShadow;
+    color: highlighttext;
+    box-shadow: none;
+  }
+
+  panelview .toolbarbutton-1[disabled],
+  toolbarbutton.subviewbutton[disabled],
+  menu.subviewbutton[disabled],
+  menuitem.subviewbutton[disabled],
+  .widget-overflow-list .toolbarbutton-1[disabled],
+  .toolbaritem-combined-buttons@inAnyPanel@ > toolbarbutton[disabled] {
+    text-shadow: none;
+  }
+
+  #PanelUI-fxa-status,
+  #PanelUI-help,
+  #PanelUI-customize {
+    border: 1px solid transparent;
+  }
+
+  #PanelUI-fxa-status:not([disabled]):hover,
+  #PanelUI-help:not([disabled]):hover,
+  #PanelUI-customize:hover,
+  #PanelUI-fxa-status:not([disabled]):hover:active,
+  #PanelUI-help:not([disabled]):hover:active,
+  #PanelUI-customize:hover:active {
+    border-color: ThreeDLightShadow;
+    box-shadow: none;
+  }
+
+  #BMB_bookmarksPopup .menu-text,
+  #BMB_bookmarksPopup menupopup {
+    color: -moz-FieldText;
+  }
+
+  #BMB_bookmarksPopup .subviewbutton[disabled=true] > .menu-text {
+    color: GrayText;
+  }
+
+  #BMB_bookmarksPopup menupopup[placespopup=true] > hbox {
+    box-shadow: none;
+    background: -moz-field;
+    border: 1px solid ThreeDShadow;
+  }
+
+  .subviewbutton.panel-subview-footer,
+  #BMB_bookmarksPopup .subviewbutton.panel-subview-footer {
+    color: ButtonText;
+  }
+
+  .subviewbutton@menuStateHover@,
+  menuitem.panel-subview-footer@menuStateHover@,
+  .subviewbutton.panel-subview-footer@buttonStateHover@,
+  .subviewbutton.panel-subview-footer@buttonStateActive@,
+  #BMB_bookmarksPopup .panel-subview-footer@menuStateHover@ > .menu-text {
+    background-color: Highlight;
+    color: highlighttext !important;
+  }
+}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -31,16 +31,19 @@ browser.jar:
         skin/classic/browser/click-to-play-warning-stripes.png
         skin/classic/browser/content-contextmenu.svg
         skin/classic/browser/dots.png                                (../shared/dots.png)
         skin/classic/browser/dots@2x.png                             (../shared/dots@2x.png)
 *       skin/classic/browser/engineManager.css
         skin/classic/browser/fullscreen-darknoise.png
         skin/classic/browser/Geolocation-16.png
         skin/classic/browser/Geolocation-64.png
+        skin/classic/browser/heartbeat-icon.svg                      (../shared/heartbeat-icon.svg)
+        skin/classic/browser/heartbeat-star-lit.svg                  (../shared/heartbeat-star-lit.svg)
+        skin/classic/browser/heartbeat-star-off.svg                  (../shared/heartbeat-star-off.svg)
         skin/classic/browser/Info.png
         skin/classic/browser/identity.png
         skin/classic/browser/identity-icons-generic.png
         skin/classic/browser/identity-icons-https.png
         skin/classic/browser/identity-icons-https-ev.png
         skin/classic/browser/identity-icons-https-mixed-active.png
         skin/classic/browser/identity-icons-https-mixed-display.png
         skin/classic/browser/keyhole-forward-mask.svg
@@ -489,16 +492,19 @@ browser.jar:
         skin/classic/aero/browser/click-to-play-warning-stripes.png
 *       skin/classic/aero/browser/content-contextmenu.svg
         skin/classic/aero/browser/dots.png                           (../shared/dots.png)
         skin/classic/aero/browser/dots@2x.png                        (../shared/dots@2x.png)
 *       skin/classic/aero/browser/engineManager.css
         skin/classic/aero/browser/fullscreen-darknoise.png
         skin/classic/aero/browser/Geolocation-16.png
         skin/classic/aero/browser/Geolocation-64.png
+        skin/classic/aero/browser/heartbeat-icon.svg                 (../shared/heartbeat-icon.svg)
+        skin/classic/aero/browser/heartbeat-star-lit.svg             (../shared/heartbeat-star-lit.svg)
+        skin/classic/aero/browser/heartbeat-star-off.svg             (../shared/heartbeat-star-off.svg)
         skin/classic/aero/browser/Info.png                           (Info-aero.png)
         skin/classic/aero/browser/identity.png                       (identity-aero.png)
         skin/classic/aero/browser/identity-icons-generic.png
         skin/classic/aero/browser/identity-icons-https.png
         skin/classic/aero/browser/identity-icons-https-ev.png
         skin/classic/aero/browser/identity-icons-https-mixed-active.png
         skin/classic/aero/browser/identity-icons-https-mixed-display.png
         skin/classic/aero/browser/keyhole-forward-mask.svg
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -9630,19 +9630,23 @@ nsDocShell::InternalLoad(nsIURI * aURI,
             nsCOMPtr<nsPIDOMWindow> win = GetWindow();
             NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE);
 
             nsDependentString name(aWindowTarget);
             nsCOMPtr<nsIDOMWindow> newWin;
             nsAutoCString spec;
             if (aURI)
                 aURI->GetSpec(spec);
+            nsAutoString features;
+            if (mInPrivateBrowsing) {
+              features.AssignLiteral("private");
+            }
             rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec),
                                      name,          // window name
-                                     EmptyString(), // Features
+                                     features,
                                      getter_AddRefs(newWin));
 
             // In some cases the Open call doesn't actually result in a new
             // window being opened.  We can detect these cases by examining the
             // document in |newWin|, if any.
             nsCOMPtr<nsPIDOMWindow> piNewWin = do_QueryInterface(newWin);
             if (piNewWin) {
                 nsCOMPtr<nsIDocument> newDoc = piNewWin->GetExtantDoc();
rename from dom/base/CompositionStringSynthesizer.cpp
rename to dom/base/TextInputProcessor.cpp
--- a/dom/base/CompositionStringSynthesizer.cpp
+++ b/dom/base/TextInputProcessor.cpp
@@ -1,160 +1,430 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "CompositionStringSynthesizer.h"
-#include "nsContentUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextInputProcessor.h"
 #include "nsIDocShell.h"
-#include "nsIFrame.h"
-#include "nsIPresShell.h"
 #include "nsIWidget.h"
 #include "nsPIDOMWindow.h"
-#include "nsView.h"
-#include "mozilla/TextEvents.h"
+#include "nsPresContext.h"
+
+using namespace mozilla::widget;
 
 namespace mozilla {
-namespace dom {
+
+/******************************************************************************
+ * TextInputProcessorNotification
+ ******************************************************************************/
+
+class TextInputProcessorNotification MOZ_FINAL :
+        public nsITextInputProcessorNotification
+{
+public:
+  explicit TextInputProcessorNotification(const char* aType)
+    : mType(aType)
+  {
+  }
+
+  NS_DECL_ISUPPORTS
 
-NS_IMPL_ISUPPORTS(CompositionStringSynthesizer,
-                  nsICompositionStringSynthesizer)
+  NS_IMETHOD GetType(nsACString& aType) MOZ_OVERRIDE MOZ_FINAL
+  {
+    aType = mType;
+    return NS_OK;
+  }
+
+protected:
+  ~TextInputProcessorNotification() { }
+
+private:
+  nsAutoCString mType;
+
+  TextInputProcessorNotification() { }
+};
+
+NS_IMPL_ISUPPORTS(TextInputProcessorNotification,
+                  nsITextInputProcessorNotification)
+
+/******************************************************************************
+ * TextInputProcessor
+ ******************************************************************************/
 
-CompositionStringSynthesizer::CompositionStringSynthesizer(
-                                nsPIDOMWindow* aWindow)
+NS_IMPL_ISUPPORTS(TextInputProcessor,
+                  nsITextInputProcessor,
+                  TextEventDispatcherListener,
+                  nsISupportsWeakReference)
+
+TextInputProcessor::TextInputProcessor()
+  : mDispatcher(nullptr)
+  , mForTests(false)
+{
+}
+
+TextInputProcessor::~TextInputProcessor()
 {
-  mWindow = do_GetWeakReference(aWindow);
-  mClauses = new TextRangeArray();
-  ClearInternal();
+  if (mDispatcher && mDispatcher->IsComposing()) {
+    // If this is composing and not canceling the composition, nobody can steal
+    // the rights of TextEventDispatcher from this instance.  Therefore, this
+    // needs to cancel the composition here.
+    if (NS_SUCCEEDED(IsValidStateForComposition())) {
+      nsRefPtr<TextEventDispatcher> kungFuDeathGrip(mDispatcher);
+      nsEventStatus status = nsEventStatus_eIgnore;
+      mDispatcher->CommitComposition(status, &EmptyString());
+    }
+  }
+}
+
+NS_IMETHODIMP
+TextInputProcessor::Init(nsIDOMWindow* aWindow,
+                         nsITextInputProcessorCallback* aCallback,
+                         bool* aSucceeded)
+{
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  if (NS_WARN_IF(!aCallback)) {
+    *aSucceeded = false;
+    return NS_ERROR_INVALID_ARG;
+  }
+  return InitInternal(aWindow, aCallback, false, *aSucceeded);
 }
 
-CompositionStringSynthesizer::~CompositionStringSynthesizer()
+NS_IMETHODIMP
+TextInputProcessor::InitForTests(nsIDOMWindow* aWindow,
+                                 nsITextInputProcessorCallback* aCallback,
+                                 uint8_t aOptionalArgc,
+                                 bool* aSucceeded)
+{
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsITextInputProcessorCallback* callback =
+    aOptionalArgc >= 1 ? aCallback : nullptr;
+  return InitInternal(aWindow, callback, true, *aSucceeded);
+}
+
+nsresult
+TextInputProcessor::InitInternal(nsIDOMWindow* aWindow,
+                                 nsITextInputProcessorCallback* aCallback,
+                                 bool aForTests,
+                                 bool& aSucceeded)
 {
+  aSucceeded = false;
+  if (NS_WARN_IF(!aWindow)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  nsCOMPtr<nsPIDOMWindow> pWindow(do_QueryInterface(aWindow));
+  if (NS_WARN_IF(!pWindow)) {
+    return NS_ERROR_INVALID_ARG;
+  }
+  nsCOMPtr<nsIDocShell> docShell(pWindow->GetDocShell());
+  if (NS_WARN_IF(!docShell)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsRefPtr<nsPresContext> presContext;
+  nsresult rv = docShell->GetPresContext(getter_AddRefs(presContext));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (NS_WARN_IF(!presContext)) {
+    return NS_ERROR_FAILURE;
+  }
+  nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget();
+  if (NS_WARN_IF(!widget)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsRefPtr<TextEventDispatcher> dispatcher = widget->GetTextEventDispatcher();
+  MOZ_RELEASE_ASSERT(dispatcher, "TextEventDispatcher must not be null");
+
+  // If the instance was initialized and is being initialized for same
+  // dispatcher and same purpose, we don't need to initialize the dispatcher
+  // again.
+  if (mDispatcher && dispatcher == mDispatcher && aCallback == mCallback &&
+      aForTests == mForTests) {
+    aSucceeded = true;
+    return NS_OK;
+  }
+
+  // If this instance is composing, don't allow to initialize again.
+  if (mDispatcher && mDispatcher->IsComposing()) {
+    return NS_ERROR_ALREADY_INITIALIZED;
+  }
+
+  // And also if another instance is composing with the new dispatcher, it'll
+  // fail to steal its ownership.  Then, we should not throw an exception,
+  // just return false.
+  if (dispatcher->IsComposing()) {
+    return NS_OK;
+  }
+
+  // This instance has finished preparing to link to the dispatcher.  Therefore,
+  // let's forget the old dispatcher and purpose.
+  UnlinkFromTextEventDispatcher();
+
+  if (aForTests) {
+    rv = dispatcher->InitForTests(this);
+  } else {
+    rv = dispatcher->Init(this);
+  }
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  mDispatcher = dispatcher;
+  mCallback = aCallback;
+  mForTests = aForTests;
+  aSucceeded = true;
+  return NS_OK;
 }
 
 void
-CompositionStringSynthesizer::ClearInternal()
+TextInputProcessor::UnlinkFromTextEventDispatcher()
 {
-  mString.Truncate();
-  mClauses->Clear();
-  mCaret.mRangeType = 0;
+  mDispatcher = nullptr;
+  mForTests = false;
+  if (mCallback) {
+    nsCOMPtr<nsITextInputProcessorCallback> callback(mCallback);
+    mCallback = nullptr;
+
+    nsRefPtr<TextInputProcessorNotification> notification =
+      new TextInputProcessorNotification("notify-detached");
+    bool result = false;
+    callback->OnNotify(this, notification, &result);
+  }
 }
 
-nsIWidget*
-CompositionStringSynthesizer::GetWidget()
+nsresult
+TextInputProcessor::IsValidStateForComposition()
 {
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
-  if (!window) {
-    return nullptr;
-  }
-  nsIDocShell *docShell = window->GetDocShell();
-  if (!docShell) {
-    return nullptr;
-  }
-  nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell();
-  if (!presShell) {
-    return nullptr;
+  if (NS_WARN_IF(!mDispatcher)) {
+    return NS_ERROR_NOT_INITIALIZED;
   }
-  nsIFrame* frame = presShell->GetRootFrame();
-  if (!frame) {
-    return nullptr;
-  }
-  return frame->GetView()->GetNearestWidget(nullptr);
-}
 
-NS_IMETHODIMP
-CompositionStringSynthesizer::SetString(const nsAString& aString)
-{
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
+  nsresult rv = mDispatcher->GetState();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
 
-  mString = aString;
   return NS_OK;
 }
 
 NS_IMETHODIMP
-CompositionStringSynthesizer::AppendClause(uint32_t aLength,
-                                           uint32_t aAttribute)
+TextInputProcessor::StartComposition(bool* aSucceeded)
 {
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
-
-  switch (aAttribute) {
-    case ATTR_RAWINPUT:
-    case ATTR_SELECTEDRAWTEXT:
-    case ATTR_CONVERTEDTEXT:
-    case ATTR_SELECTEDCONVERTEDTEXT: {
-      TextRange textRange;
-      textRange.mStartOffset =
-        mClauses->IsEmpty() ? 0 : mClauses->LastElement().mEndOffset;
-      textRange.mEndOffset = textRange.mStartOffset + aLength;
-      textRange.mRangeType = aAttribute;
-      mClauses->AppendElement(textRange);
-      return NS_OK;
-    }
-    default:
-      return NS_ERROR_INVALID_ARG;
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  *aSucceeded = false;
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
   }
-}
-
-NS_IMETHODIMP
-CompositionStringSynthesizer::SetCaret(uint32_t aOffset, uint32_t aLength)
-{
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
-
-  mCaret.mStartOffset = aOffset;
-  mCaret.mEndOffset = mCaret.mStartOffset + aLength;
-  mCaret.mRangeType = NS_TEXTRANGE_CARETPOSITION;
+  nsEventStatus status = nsEventStatus_eIgnore;
+  rv = mDispatcher->StartComposition(status);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  *aSucceeded = status != nsEventStatus_eConsumeNoDefault &&
+                  mDispatcher && mDispatcher->IsComposing();
   return NS_OK;
 }
 
 NS_IMETHODIMP
-CompositionStringSynthesizer::DispatchEvent(bool* aDefaultPrevented)
+TextInputProcessor::SetPendingCompositionString(const nsAString& aString)
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return mDispatcher->SetPendingCompositionString(aString);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::AppendClauseToPendingComposition(uint32_t aLength,
+                                                     uint32_t aAttribute)
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  switch (aAttribute) {
+    case ATTR_RAW_CLAUSE:
+    case ATTR_SELECTED_RAW_CLAUSE:
+    case ATTR_CONVERTED_CLAUSE:
+    case ATTR_SELECTED_CLAUSE:
+      break;
+    default:
+      return NS_ERROR_INVALID_ARG;
+  }
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return mDispatcher->AppendClauseToPendingComposition(aLength, aAttribute);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::SetCaretInPendingComposition(uint32_t aOffset)
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  return mDispatcher->SetCaretInPendingComposition(aOffset, 0);
+}
+
+NS_IMETHODIMP
+TextInputProcessor::FlushPendingComposition(bool* aSucceeded)
+{
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  *aSucceeded = false;
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  nsEventStatus status = nsEventStatus_eIgnore;
+  rv = mDispatcher->FlushPendingComposition(status);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+  return rv;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CommitComposition(const nsAString& aCommitString,
+                                      uint8_t aOptionalArgc,
+                                      bool* aSucceeded)
+{
+  MOZ_RELEASE_ASSERT(aSucceeded, "aSucceeded must not be nullptr");
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  const nsAString* commitString =
+    aOptionalArgc >= 1 ? &aCommitString : nullptr;
+  return CommitCompositionInternal(commitString, aSucceeded);
+}
+
+nsresult
+TextInputProcessor::CommitCompositionInternal(const nsAString* aCommitString,
+                                              bool* aSucceeded)
 {
-  NS_ENSURE_ARG_POINTER(aDefaultPrevented);
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  NS_ENSURE_TRUE(widget && !widget->Destroyed(), NS_ERROR_NOT_AVAILABLE);
+  if (aSucceeded) {
+    *aSucceeded = false;
+  }
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  nsEventStatus status = nsEventStatus_eIgnore;
+  rv = mDispatcher->CommitComposition(status, aCommitString);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  if (aSucceeded) {
+    *aSucceeded = status != nsEventStatus_eConsumeNoDefault;
+  }
+  return rv;
+}
+
+NS_IMETHODIMP
+TextInputProcessor::CancelComposition()
+{
+  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
+  return CancelCompositionInternal();
+}
+
+nsresult
+TextInputProcessor::CancelCompositionInternal()
+{
+  nsRefPtr<TextEventDispatcher> kungfuDeathGrip(mDispatcher);
+  nsresult rv = IsValidStateForComposition();
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+  nsEventStatus status = nsEventStatus_eIgnore;
+  return mDispatcher->CommitComposition(status, &EmptyString());
+}
 
-  if (!nsContentUtils::IsCallerChrome()) {
-    return NS_ERROR_DOM_SECURITY_ERR;
+NS_IMETHODIMP
+TextInputProcessor::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+                              const IMENotification& aNotification)
+{
+  // If This is called while this is being initialized, ignore the call.
+  if (!mDispatcher) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+             "Wrong TextEventDispatcher notifies this");
+  NS_ASSERTION(mForTests || mCallback,
+               "mCallback can be null only when IME is initialized for tests");
+  if (mCallback) {
+    nsRefPtr<TextInputProcessorNotification> notification;
+    switch (aNotification.mMessage) {
+      case REQUEST_TO_COMMIT_COMPOSITION: {
+        NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                     "Why is this requested without composition?");
+        notification = new TextInputProcessorNotification("request-to-commit");
+        break;
+      }
+      case REQUEST_TO_CANCEL_COMPOSITION: {
+        NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                     "Why is this requested without composition?");
+        notification = new TextInputProcessorNotification("request-to-cancel");
+        break;
+      }
+      case NOTIFY_IME_OF_FOCUS:
+        notification = new TextInputProcessorNotification("notify-focus");
+        break;
+      case NOTIFY_IME_OF_BLUR:
+        notification = new TextInputProcessorNotification("notify-blur");
+        break;
+      default:
+        return NS_ERROR_NOT_IMPLEMENTED;
+    }
+    MOZ_RELEASE_ASSERT(notification);
+    bool result = false;
+    nsresult rv = mCallback->OnNotify(this, notification, &result);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+    return result ? NS_OK : NS_ERROR_FAILURE;
   }
 
-  if (!mClauses->IsEmpty() &&
-      mClauses->LastElement().mEndOffset != mString.Length()) {
-    NS_WARNING("Sum of length of the all clauses must be same as the string "
-               "length");
-    ClearInternal();
-    return NS_ERROR_ILLEGAL_VALUE;
-  }
-  if (mCaret.mRangeType == NS_TEXTRANGE_CARETPOSITION) {
-    if (mCaret.mEndOffset > mString.Length()) {
-      NS_WARNING("Caret position is out of the composition string");
-      ClearInternal();
-      return NS_ERROR_ILLEGAL_VALUE;
+  switch (aNotification.mMessage) {
+    case REQUEST_TO_COMMIT_COMPOSITION: {
+      NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                   "Why is this requested without composition?");
+      CommitCompositionInternal();
+      return NS_OK;
     }
-    mClauses->AppendElement(mCaret);
+    case REQUEST_TO_CANCEL_COMPOSITION: {
+      NS_ASSERTION(aTextEventDispatcher->IsComposing(),
+                   "Why is this requested without composition?");
+      CancelCompositionInternal();
+      return NS_OK;
+    }
+    default:
+      return NS_ERROR_NOT_IMPLEMENTED;
   }
-
-  WidgetCompositionEvent compChangeEvent(true, NS_COMPOSITION_CHANGE, widget);
-  compChangeEvent.time = PR_IntervalNow();
-  compChangeEvent.mData = mString;
-  if (!mClauses->IsEmpty()) {
-    compChangeEvent.mRanges = mClauses;
-  }
-
-  // XXX How should we set false for this on b2g?
-  compChangeEvent.mFlags.mIsSynthesizedForTests = true;
-
-  nsEventStatus status = nsEventStatus_eIgnore;
-  nsresult rv = widget->DispatchEvent(&compChangeEvent, status);
-  *aDefaultPrevented = (status == nsEventStatus_eConsumeNoDefault);
-
-  ClearInternal();
-
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
 }
 
-} // namespace dom
+NS_IMETHODIMP_(void)
+TextInputProcessor::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher)
+{
+  // If This is called while this is being initialized, ignore the call.
+  if (!mDispatcher) {
+    return;
+  }
+  MOZ_ASSERT(aTextEventDispatcher == mDispatcher,
+             "Wrong TextEventDispatcher notifies this");
+  UnlinkFromTextEventDispatcher();
+}
+
 } // namespace mozilla
rename from dom/base/CompositionStringSynthesizer.h
rename to dom/base/TextInputProcessor.h
--- a/dom/base/CompositionStringSynthesizer.h
+++ b/dom/base/TextInputProcessor.h
@@ -1,45 +1,58 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef mozilla_dom_compositionstringsynthesizer_h__
-#define mozilla_dom_compositionstringsynthesizer_h__
+#ifndef mozilla_dom_textinputprocessor_h_
+#define mozilla_dom_textinputprocessor_h_
 
-#include "nsICompositionStringSynthesizer.h"
-#include "nsString.h"
-#include "nsWeakReference.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/TextRange.h"
-
-class nsIWidget;
-class nsPIDOMWindow;
+#include "mozilla/TextEventDispatcherListener.h"
+#include "nsITextInputProcessor.h"
+#include "nsITextInputProcessorCallback.h"
 
 namespace mozilla {
-namespace dom {
+
+namespace widget{
+class TextEventDispatcher;
+} // namespace widget
 
-class CompositionStringSynthesizer MOZ_FINAL :
-  public nsICompositionStringSynthesizer
+class TextInputProcessor MOZ_FINAL : public nsITextInputProcessor
+                                   , public widget::TextEventDispatcherListener
 {
+  typedef mozilla::widget::IMENotification IMENotification;
+  typedef mozilla::widget::TextEventDispatcher TextEventDispatcher;
+
 public:
-  explicit CompositionStringSynthesizer(nsPIDOMWindow* aWindow);
+  TextInputProcessor();
 
   NS_DECL_ISUPPORTS
-  NS_DECL_NSICOMPOSITIONSTRINGSYNTHESIZER
+  NS_DECL_NSITEXTINPUTPROCESSOR
+
+  // TextEventDispatcherListener
+  NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+                       const IMENotification& aNotification) MOZ_OVERRIDE;
+  NS_IMETHOD_(void)
+    OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) MOZ_OVERRIDE;
+
+protected:
+  virtual ~TextInputProcessor();
 
 private:
-  ~CompositionStringSynthesizer();
+  nsresult InitInternal(nsIDOMWindow* aWindow,
+                        nsITextInputProcessorCallback* aCallback,
+                        bool aForTests,
+                        bool& aSucceeded);
+  nsresult CommitCompositionInternal(const nsAString* aCommitString = nullptr,
+                                     bool* aSucceeded = nullptr);
+  nsresult CancelCompositionInternal();
+  nsresult IsValidStateForComposition();
+  void UnlinkFromTextEventDispatcher();
 
-  nsWeakPtr mWindow; // refers an instance of nsPIDOMWindow
-  nsString mString;
-  nsRefPtr<TextRangeArray> mClauses;
-  TextRange mCaret;
-
-  nsIWidget* GetWidget();
-  void ClearInternal();
+  TextEventDispatcher* mDispatcher; // [Weak]
+  nsCOMPtr<nsITextInputProcessorCallback> mCallback;
+  bool mForTests;
 };
 
-} // namespace dom
 } // namespace mozilla
 
-#endif // #ifndef mozilla_dom_compositionstringsynthesizer_h__
+#endif // #ifndef mozilla_dom_textinputprocessor_h_
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -133,16 +133,17 @@ if CONFIG['MOZ_WEBRTC']:
     EXPORTS += [
         'nsDOMDataChannel.h',
         'nsDOMDataChannelDeclarations.h',
     ]
 
 EXPORTS.mozilla += [
     'CORSMode.h',
     'FeedWriterEnabled.h',
+    'TextInputProcessor.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'AnonymousContent.h',
     'Attr.h',
     'BarProps.h',
     'BlobSet.h',
     'ChildIterator.h',
@@ -196,17 +197,16 @@ EXPORTS.mozilla.dom += [
 ]
 
 UNIFIED_SOURCES += [
     'AnonymousContent.cpp',
     'Attr.cpp',
     'BarProps.cpp',
     'ChildIterator.cpp',
     'Comment.cpp',
-    'CompositionStringSynthesizer.cpp',
     'Console.cpp',
     'Crypto.cpp',
     'DirectionalityUtils.cpp',
     'DocumentFragment.cpp',
     'DocumentType.cpp',
     'DOMCursor.cpp',
     'DOMError.cpp',
     'DOMException.cpp',
@@ -257,17 +257,16 @@ UNIFIED_SOURCES += [
     'nsDOMSerializer.cpp',
     'nsDOMSettableTokenList.cpp',
     'nsDOMTokenList.cpp',
     'nsDOMWindowList.cpp',
     'nsElementFrameLoaderOwner.cpp',
     'nsFocusManager.cpp',
     'nsFormData.cpp',
     'nsFrameLoader.cpp',
-    'nsFrameMessageManager.cpp',
     'nsGenConImageContent.cpp',
     'nsGenericDOMDataNode.cpp',
     'nsGkAtoms.cpp',
     'nsGlobalWindowCommands.cpp',
     'nsHistory.cpp',
     'nsHostObjectProtocolHandler.cpp',
     'nsHostObjectURI.cpp',
     'nsHTMLContentSerializer.cpp',
@@ -318,16 +317,17 @@ UNIFIED_SOURCES += [
     'PerformanceEntry.cpp',
     'PerformanceResourceTiming.cpp',
     'ResponsiveImageSelector.cpp',
     'ScriptSettings.cpp',
     'ShadowRoot.cpp',
     'StyleSheetList.cpp',
     'SubtleCrypto.cpp',
     'Text.cpp',
+    'TextInputProcessor.cpp',
     'ThirdPartyUtil.cpp',
     'TreeWalker.cpp',
     'URL.cpp',
     'URLSearchParams.cpp',
     'WebSocket.cpp',
     'WindowNamedPropertiesHandler.cpp',
 ]
 
@@ -339,16 +339,18 @@ if CONFIG['MOZ_WEBRTC']:
 # these files couldn't be in UNIFIED_SOURCES for now for reasons given below:
 SOURCES += [
     # this file doesn't like windows.h
     'MessagePort.cpp',
     # Because of OS X headers.
     'nsContentUtils.cpp',
     # this file doesn't like windows.h
     'nsDOMWindowUtils.cpp',
+    # Conflicts with windows.h's definition of SendMessage.
+    'nsFrameMessageManager.cpp',
     # This file has a #error "Never include windows.h in this file!"
     'nsGlobalWindow.cpp',
     # Conflicts with windows.h's definition of LoadImage.
     'nsImageLoadingContent.cpp',
     # Because of OS X headers.
     'nsObjectLoadingContent.cpp',
     # nsPluginArray.cpp includes npapi.h indirectly, and that includes a lot of system headers
     'nsPluginArray.cpp',
--- a/dom/base/nsContentList.cpp
+++ b/dom/base/nsContentList.cpp
@@ -218,19 +218,16 @@ NS_GetContentList(nsINode* aRootNode,
   if (!gContentListHashTable.IsInitialized()) {
     PL_DHashTableInit(&gContentListHashTable, &hash_table_ops,
                       sizeof(ContentListHashEntry));
   }
 
   ContentListHashEntry *entry = nullptr;
   // First we look in our hashtable.  Then we create a content list if needed
   if (gContentListHashTable.IsInitialized()) {
-
-    // A PL_DHashTableAdd is equivalent to a PL_DHashTableLookup for cases
-    // when the entry is already in the hashtable.
     entry = static_cast<ContentListHashEntry *>
                        (PL_DHashTableAdd(&gContentListHashTable, &hashKey));
     if (entry)
       list = entry->mContentList;
   }
 
   if (!list) {
     // We need to create a ContentList and add it to our new entry, if
@@ -239,18 +236,17 @@ NS_GetContentList(nsINode* aRootNode,
     nsCOMPtr<nsIAtom> htmlAtom;
     if (aMatchNameSpaceId == kNameSpaceID_Unknown) {
       nsAutoString lowercaseName;
       nsContentUtils::ASCIIToLower(aTagname, lowercaseName);
       htmlAtom = do_GetAtom(lowercaseName);
     } else {
       htmlAtom = xmlAtom;
     }
-    list = new nsContentList(aRootNode, aMatchNameSpaceId,
-                             htmlAtom, xmlAtom);
+    list = new nsContentList(aRootNode, aMatchNameSpaceId, htmlAtom, xmlAtom);
     if (entry) {
       entry->mContentList = list;
     }
   }
 
   sRecentlyUsedContentLists[recentlyUsedCacheIndex] = list;
   return list.forget();
 }
@@ -330,18 +326,16 @@ GetFuncStringContentList(nsINode* aRootN
                       sizeof(FuncStringContentListHashEntry));
   }
 
   FuncStringContentListHashEntry *entry = nullptr;
   // First we look in our hashtable.  Then we create a content list if needed
   if (gFuncStringContentListHashTable.IsInitialized()) {
     nsFuncStringCacheKey hashKey(aRootNode, aFunc, aString);
 
-    // A PL_DHashTableAdd is equivalent to a PL_DHashTableLookup for cases
-    // when the entry is already in the hashtable.
     entry = static_cast<FuncStringContentListHashEntry *>
                        (PL_DHashTableAdd(&gFuncStringContentListHashTable,
                                          &hashKey));
     if (entry) {
       list = entry->mContentList;
 #ifdef DEBUG
       MOZ_ASSERT_IF(list, list->mType == ListType::sType);
 #endif
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3936,18 +3936,18 @@ nsContentUtils::TraverseListenerManager(
 {
   if (!sEventListenerManagersHash.IsInitialized()) {
     // We're already shut down, just return.
     return;
   }
 
   EventListenerManagerMapEntry *entry =
     static_cast<EventListenerManagerMapEntry *>
-               (PL_DHashTableLookup(&sEventListenerManagersHash, aNode));
-  if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+               (PL_DHashTableSearch(&sEventListenerManagersHash, aNode));
+  if (entry) {
     CycleCollectionNoteChild(cb, entry->mListenerManager.get(),
                              "[via hash] mListenerManager");
   }
 }
 
 EventListenerManager*
 nsContentUtils::GetListenerManagerForNode(nsINode *aNode)
 {
@@ -3976,43 +3976,43 @@ nsContentUtils::GetListenerManagerForNod
 }
 
 EventListenerManager*
 nsContentUtils::GetExistingListenerManagerForNode(const nsINode *aNode)
 {
   if (!aNode->HasFlag(NODE_HAS_LISTENERMANAGER)) {
     return nullptr;
   }
-  
+
   if (!sEventListenerManagersHash.IsInitialized()) {
     // We're already shut down, don't bother creating an event listener
     // manager.
 
     return nullptr;
   }
 
   EventListenerManagerMapEntry *entry =
     static_cast<EventListenerManagerMapEntry *>
-               (PL_DHashTableLookup(&sEventListenerManagersHash, aNode));
-  if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+               (PL_DHashTableSearch(&sEventListenerManagersHash, aNode));
+  if (entry) {
     return entry->mListenerManager;
   }
 
   return nullptr;
 }
 
 /* static */
 void
 nsContentUtils::RemoveListenerManager(nsINode *aNode)
 {
   if (sEventListenerManagersHash.IsInitialized()) {
     EventListenerManagerMapEntry *entry =
       static_cast<EventListenerManagerMapEntry *>
-                 (PL_DHashTableLookup(&sEventListenerManagersHash, aNode));
-    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+                 (PL_DHashTableSearch(&sEventListenerManagersHash, aNode));
+    if (entry) {
       nsRefPtr<EventListenerManager> listenerManager;
       listenerManager.swap(entry->mListenerManager);
       // Remove the entry and *then* do operations that could cause further
       // modification of sEventListenerManagersHash.  See bug 334177.
       PL_DHashTableRawRemove(&sEventListenerManagersHash, entry);
       if (listenerManager) {
         listenerManager->Disconnect();
       }
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -8,17 +8,16 @@
 
 #include "mozilla/layers/CompositorChild.h"
 #include "mozilla/layers/LayerTransactionChild.h"
 #include "nsPresContext.h"
 #include "nsDOMClassInfoID.h"
 #include "nsError.h"
 #include "nsIDOMEvent.h"
 #include "nsQueryContentEventResult.h"
-#include "CompositionStringSynthesizer.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsFocusManager.h"
 #include "nsFrameManager.h"
 #include "nsRefreshDriver.h"
 #include "mozilla/dom/Touch.h"
 #include "mozilla/PendingPlayerTracker.h"
 #include "nsIObjectLoadingContent.h"
@@ -35,16 +34,17 @@
 #include "nsCharsetSource.h"
 #include "nsJSEnvironment.h"
 #include "nsJSUtils.h"
 
 #include "mozilla/EventStateManager.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
+#include "mozilla/TextEventDispatcher.h"
 #include "mozilla/TouchEvents.h"
 
 #include "nsViewManager.h"
 
 #include "nsIDOMHTMLCanvasElement.h"
 #include "nsLayoutUtils.h"
 #include "nsComputedDOMStyle.h"
 #include "nsIPresShell.h"
@@ -2129,90 +2129,16 @@ InitEvent(WidgetGUIEvent& aEvent, Layout
 {
   if (aPt) {
     aEvent.refPoint = *aPt;
   }
   aEvent.time = PR_IntervalNow();
 }
 
 NS_IMETHODIMP
-nsDOMWindowUtils::SendCompositionEvent(const nsAString& aType,
-                                       const nsAString& aData,
-                                       const nsAString& aLocale)
-{
-  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-
-  // get the widget to send the event to
-  nsCOMPtr<nsIWidget> widget = GetWidget();
-  if (!widget) {
-    return NS_ERROR_FAILURE;
-  }
-
-  uint32_t msg;
-  if (aType.EqualsLiteral("compositionstart")) {
-    msg = NS_COMPOSITION_START;
-  } else if (aType.EqualsLiteral("compositionend")) {
-    // Now we don't support manually dispatching composition end with this
-    // API.  A compositionend is dispatched when this is called with
-    // compositioncommitasis or compositioncommit automatically.  For backward
-    // compatibility, this shouldn't return error in this case.
-    NS_WARNING("Don't call nsIDOMWindowUtils.sendCompositionEvent() for "
-               "compositionend.  Instead, use it with compositioncommitasis or "
-               "compositioncommit.  Then, compositionend will be automatically "
-               "dispatched.");
-    return NS_OK;
-  } else if (aType.EqualsLiteral("compositionupdate")) {
-    // Now we don't support manually dispatching composition update with this
-    // API.  A compositionupdate is dispatched when a DOM text event modifies
-    // composition string automatically.  For backward compatibility, this
-    // shouldn't return error in this case.
-    NS_WARNING("Don't call nsIDOMWindowUtils.sendCompositionEvent() for "
-               "compositionupdate since it's ignored and the event is "
-               "fired automatically when it's necessary");
-    return NS_OK;
-  } else if (aType.EqualsLiteral("compositioncommitasis")) {
-    msg = NS_COMPOSITION_COMMIT_AS_IS;
-  } else if (aType.EqualsLiteral("compositioncommit")) {
-    msg = NS_COMPOSITION_COMMIT;
-  } else {
-    return NS_ERROR_FAILURE;
-  }
-
-  WidgetCompositionEvent compositionEvent(true, msg, widget);
-  InitEvent(compositionEvent);
-  if (msg != NS_COMPOSITION_START && msg != NS_COMPOSITION_COMMIT_AS_IS) {
-    compositionEvent.mData = aData;
-  }
-
-  compositionEvent.mFlags.mIsSynthesizedForTests = true;
-
-  nsEventStatus status;
-  nsresult rv = widget->DispatchEvent(&compositionEvent, status);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsDOMWindowUtils::CreateCompositionStringSynthesizer(
-                    nsICompositionStringSynthesizer** aResult)
-{
-  NS_ENSURE_ARG_POINTER(aResult);
-  *aResult = nullptr;
-
-  MOZ_RELEASE_ASSERT(nsContentUtils::IsCallerChrome());
-
-  nsCOMPtr<nsPIDOMWindow> window = do_QueryReferent(mWindow);
-  NS_ENSURE_TRUE(window, NS_ERROR_NOT_AVAILABLE);
-
-  NS_ADDREF(*aResult = new CompositionStringSynthesizer(window));
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsDOMWindowUtils::SendQueryContentEvent(uint32_t aType,
                                         uint32_t aOffset, uint32_t aLength,
                                         int32_t aX, int32_t aY,
                                         uint32_t aAdditionalFlags,
                                         nsIQueryContentEventResult **aResult)
 {
   *aResult = nullptr;
 
--- a/dom/base/nsDOMWindowUtils.h
+++ b/dom/base/nsDOMWindowUtils.h
@@ -52,16 +52,18 @@ private:
   nsTArray<nsCOMPtr<nsIDOMNode> > mNodes;
   nsTArray<bool> mNodeIsRoot;
   uint32_t mLength;
 };
 
 class nsDOMWindowUtils MOZ_FINAL : public nsIDOMWindowUtils,
                                    public nsSupportsWeakReference
 {
+  typedef mozilla::widget::TextEventDispatcher
+    TextEventDispatcher;
 public:
   explicit nsDOMWindowUtils(nsGlobalWindow *aWindow);
   NS_DECL_ISUPPORTS
   NS_DECL_NSIDOMWINDOWUTILS
 
 protected:
   ~nsDOMWindowUtils();
 
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3954,19 +3954,19 @@ nsDocument::SetSubDocumentFor(Element* a
   NS_ENSURE_TRUE(aElement, NS_ERROR_UNEXPECTED);
 
   if (!aSubDoc) {
     // aSubDoc is nullptr, remove the mapping
 
     if (mSubDocuments) {
       SubDocMapEntry *entry =
         static_cast<SubDocMapEntry*>
-                   (PL_DHashTableLookup(mSubDocuments, aElement));
-
-      if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+                   (PL_DHashTableSearch(mSubDocuments, aElement));
+
+      if (entry) {
         PL_DHashTableRawRemove(mSubDocuments, entry);
       }
     }
   } else {
     if (!mSubDocuments) {
       // Create a new hashtable
 
       static const PLDHashTableOps hash_table_ops =
@@ -4010,19 +4010,19 @@ nsDocument::SetSubDocumentFor(Element* a
 }
 
 nsIDocument*
 nsDocument::GetSubDocumentFor(nsIContent *aContent) const
 {
   if (mSubDocuments && aContent->IsElement()) {
     SubDocMapEntry *entry =
       static_cast<SubDocMapEntry*>
-                 (PL_DHashTableLookup(mSubDocuments, aContent->AsElement()));
-
-    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+                 (PL_DHashTableSearch(mSubDocuments, aContent->AsElement()));
+
+    if (entry) {
       return entry->mSubDocument;
     }
   }
 
   return nullptr;
 }
 
 static PLDHashOperator
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -222,16 +222,19 @@ public:
    */
   void ApplySandboxFlags(uint32_t sandboxFlags);
 
   void GetURL(nsString& aURL);
 
   void ActivateUpdateHitRegion();
   void DeactivateUpdateHitRegion();
 
+  // Properly retrieves documentSize of any subdocument type.
+  nsresult GetWindowDimensions(nsIntRect& aRect);
+
 private:
 
   void SetOwnerContent(mozilla::dom::Element* aContent);
 
   bool ShouldUseRemoteProcess();
 
   /**
    * Is this a frameloader for a bona fide <iframe mozbrowser> or
@@ -277,19 +280,16 @@ private:
 
   /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
   nsresult MaybeCreateDocShell();
   nsresult EnsureMessageManager();
 
-  // Properly retrieves documentSize of any subdocument type.
-  nsresult GetWindowDimensions(nsIntRect& aRect);
-
   // Updates the subdocument position and size. This gets called only
   // when we have our own in-process DocShell.
   void UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame);
   nsresult CheckURILoad(nsIURI* aURI);
   void FireErrorEvent();
   nsresult ReallyStartLoadingInternal();
 
   // Return true if remote browser created; nothing else to do
--- a/dom/base/nsPropertyTable.cpp
+++ b/dom/base/nsPropertyTable.cpp
@@ -89,19 +89,20 @@ nsPropertyTable::DeleteAllPropertiesFor(
 
 nsresult
 nsPropertyTable::TransferOrDeleteAllPropertiesFor(nsPropertyOwner aObject,
                                                   nsPropertyTable *aOtherTable)
 {
   nsresult rv = NS_OK;
   for (PropertyList* prop = mPropertyList; prop; prop = prop->mNext) {
     if (prop->mTransfer) {
-      PropertyListMapEntry *entry = static_cast<PropertyListMapEntry*>
-                                               (PL_DHashTableLookup(&prop->mObjectValueMap, aObject));
-      if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+      PropertyListMapEntry *entry =
+          static_cast<PropertyListMapEntry*>
+                     (PL_DHashTableSearch(&prop->mObjectValueMap, aObject));
+      if (entry) {
         rv = aOtherTable->SetProperty(aObject, prop->mName,
                                       entry->value, prop->mDtorFunc,
                                       prop->mDtorData, prop->mTransfer);
         if (NS_FAILED(rv)) {
           DeleteAllPropertiesFor(aObject);
           aOtherTable->DeleteAllPropertiesFor(aObject);
 
           break;
@@ -120,20 +121,20 @@ nsPropertyTable::TransferOrDeleteAllProp
 
 void
 nsPropertyTable::Enumerate(nsPropertyOwner aObject,
                            NSPropertyFunc aCallback, void *aData)
 {
   PropertyList* prop;
   for (prop = mPropertyList; prop; prop = prop->mNext) {
     PropertyListMapEntry *entry = static_cast<PropertyListMapEntry*>
-      (PL_DHashTableLookup(&prop->mObjectValueMap, aObject));
-    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+      (PL_DHashTableSearch(&prop->mObjectValueMap, aObject));
+    if (entry) {
       aCallback(const_cast<void*>(aObject.get()), prop->mName, entry->value,
-                 aData);
+                aData);
     }
   }
 }
 
 struct PropertyEnumeratorData
 {
   nsIAtom* mName;
   NSPropertyFunc mCallBack;
@@ -167,19 +168,20 @@ nsPropertyTable::GetPropertyInternal(nsP
                                      nsresult   *aResult)
 {
   NS_PRECONDITION(aPropertyName && aObject, "unexpected null param");
   nsresult rv = NS_PROPTABLE_PROP_NOT_THERE;
   void *propValue = nullptr;
 
   PropertyList* propertyList = GetPropertyListFor(aPropertyName);
   if (propertyList) {
-    PropertyListMapEntry *entry = static_cast<PropertyListMapEntry*>
-                                             (PL_DHashTableLookup(&propertyList->mObjectValueMap, aObject));
-    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+    PropertyListMapEntry *entry =
+        static_cast<PropertyListMapEntry*>
+                   (PL_DHashTableSearch(&propertyList->mObjectValueMap, aObject));
+    if (entry) {
       propValue = entry->value;
       if (aRemove) {
         // don't call propertyList->mDtorFunc.  That's the caller's job now.
         PL_DHashTableRawRemove(&propertyList->mObjectValueMap, entry);
       }
       rv = NS_OK;
     }
   }
@@ -320,19 +322,20 @@ nsPropertyTable::PropertyList::Destroy()
   // Enumerate any remaining object/value pairs and destroy the value object
   if (mDtorFunc)
     PL_DHashTableEnumerate(&mObjectValueMap, DestroyPropertyEnumerator, this);
 }
 
 bool
 nsPropertyTable::PropertyList::DeletePropertyFor(nsPropertyOwner aObject)
 {
-  PropertyListMapEntry *entry = static_cast<PropertyListMapEntry*>
-                                           (PL_DHashTableLookup(&mObjectValueMap, aObject));
-  if (!PL_DHASH_ENTRY_IS_BUSY(entry))
+  PropertyListMapEntry *entry =
+      static_cast<PropertyListMapEntry*>
+                 (PL_DHashTableSearch(&mObjectValueMap, aObject));
+  if (!entry)
     return false;
 
   void* value = entry->value;
   PL_DHashTableRawRemove(&mObjectValueMap, entry);
 
   if (mDtorFunc)
     mDtorFunc(const_cast<void*>(aObject.get()), mName, value, mDtorData);
 
--- a/dom/base/nsScriptNameSpaceManager.cpp
+++ b/dom/base/nsScriptNameSpaceManager.cpp
@@ -167,20 +167,19 @@ nsScriptNameSpaceManager::RemoveFromHash
 nsGlobalNameStruct*
 nsScriptNameSpaceManager::GetConstructorProto(const nsGlobalNameStruct* aStruct)
 {
   NS_ASSERTION(aStruct->mType == nsGlobalNameStruct::eTypeExternalConstructorAlias,
                "This function only works on constructor aliases!");
   if (!aStruct->mAlias->mProto) {
     GlobalNameMapEntry *proto =
       static_cast<GlobalNameMapEntry *>
-                 (PL_DHashTableLookup(&mGlobalNames,
+                 (PL_DHashTableSearch(&mGlobalNames,
                                       &aStruct->mAlias->mProtoName));
-
-    if (PL_DHASH_ENTRY_IS_BUSY(proto)) {
+    if (proto) {
       aStruct->mAlias->mProto = &proto->mGlobalName;
     }
   }
   return aStruct->mAlias->mProto;
 }
 
 nsresult
 nsScriptNameSpaceManager::FillHash(nsICategoryManager *aCategoryManager,
@@ -380,19 +379,19 @@ nsScriptNameSpaceManager::Init()
 }
 
 nsGlobalNameStruct*
 nsScriptNameSpaceManager::LookupNameInternal(const nsAString& aName,
                                              const char16_t **aClassName)
 {
   GlobalNameMapEntry *entry =
     static_cast<GlobalNameMapEntry *>
-               (PL_DHashTableLookup(&mGlobalNames, &aName));
+               (PL_DHashTableSearch(&mGlobalNames, &aName));
 
-  if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+  if (entry) {
     if (aClassName) {
       *aClassName = entry->mKey.get();
     }
     return &entry->mGlobalName;
   }
 
   if (aClassName) {
     *aClassName = nullptr;
@@ -400,23 +399,19 @@ nsScriptNameSpaceManager::LookupNameInte
   return nullptr;
 }
 
 const nsGlobalNameStruct*
 nsScriptNameSpaceManager::LookupNavigatorName(const nsAString& aName)
 {
   GlobalNameMapEntry *entry =
     static_cast<GlobalNameMapEntry *>
-               (PL_DHashTableLookup(&mNavigatorNames, &aName));
+               (PL_DHashTableSearch(&mNavigatorNames, &aName));
 
-  if (!PL_DHASH_ENTRY_IS_BUSY(entry)) {
-    return nullptr;
-  }
-
-  return &entry->mGlobalName;
+  return entry ? &entry->mGlobalName : nullptr;
 }
 
 nsresult
 nsScriptNameSpaceManager::RegisterClassName(const char *aClassName,
                                             int32_t aDOMClassInfoID,
                                             bool aPrivileged,
                                             bool aXBLAllowed,
                                             const char16_t **aResult)
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -12,16 +12,17 @@ support-files =
   file_bug990812-1.xul
   file_bug990812-2.xul
   file_bug990812-3.xul
   file_bug990812-4.xul
   file_bug990812-5.xul
   fileconstructor_file.png
   frame_bug814638.xul
   host_bug814638.xul
+  window_nsITextInputProcessor.xul
   title_window.xul
 
 [test_bug206691.xul]
 [test_bug339494.xul]
 [test_bug357450.xul]
 [test_bug380418.html]
 [test_bug380418.html^headers^]
 [test_bug383430.html]
@@ -55,9 +56,10 @@ skip-if = buildapp == 'mulet'
 [test_bug990812.xul]
 [test_bug1063837.xul]
 [test_cpows.xul]
 skip-if = buildapp == 'mulet'
 [test_document_register.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_fileconstructor_tempfile.xul]
+[test_nsITextInputProcessor.xul]
 [test_title.xul]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/test_nsITextInputProcessor.xul
@@ -0,0 +1,32 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window title="Testing nsITextInputProcessor behavior"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+<input id="input" type="text"/><br/>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+SimpleTest.waitForExplicitFinish();
+window.open("window_nsITextInputProcessor.xul", "_blank", 
+            "chrome,width=600,height=600");
+document.getElementById("input").focus();
+
+]]>
+</script>
+</window>
new file mode 100644
--- /dev/null
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -0,0 +1,1038 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
+                 type="text/css"?>
+<window title="Testing nsITextInputProcessor behavior"
+  xmlns:html="http://www.w3.org/1999/xhtml"
+  xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+  onunload="onunload();">
+<script type="application/javascript"
+        src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
+<body  xmlns="http://www.w3.org/1999/xhtml">
+<p id="display">
+<input id="input" type="text"/><br/>
+<iframe id="iframe" width="300" height="150"
+        src="data:text/html,&lt;textarea id='textarea' cols='20' rows='4'&gt;&lt;/textarea&gt;"></iframe><br/>
+</p>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+</pre>
+</body>
+
+<script class="testbody" type="application/javascript">
+<![CDATA[
+
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
+
+SimpleTest.waitForFocus(runTests, window);
+
+function ok(aCondition, aMessage)
+{
+  SimpleTest.ok(aCondition, aMessage);
+}
+
+function is(aLeft, aRight, aMessage)
+{
+  SimpleTest.is(aLeft, aRight, aMessage);
+}
+
+function isnot(aLeft, aRight, aMessage)
+{
+  SimpleTest.isnot(aLeft, aRight, aMessage);
+}
+
+function todo_is(aLeft, aRight, aMessage)
+{
+  SimpleTest.todo_is(aLeft, aRight, aMessage);
+}
+
+function finish()
+{
+  window.close();
+}
+
+function onunload()
+{
+  SimpleTest.finish();
+}
+
+var iframe = document.getElementById("iframe");
+var childWindow = iframe.contentWindow;
+var textareaInFrame;
+var input = document.getElementById("input");
+var otherWindow = window.opener;
+var otherDocument = otherWindow.document;
+var inputInChildWindow = otherDocument.getElementById("input");
+
+function createTIP()
+{
+  return Components.classes["@mozilla.org/text-input-processor;1"].
+           createInstance(Components.interfaces.nsITextInputProcessor);
+}
+
+function runInitMethodTests()
+{
+  var description = "runInitMethodTest: ";
+  input.value = "";
+  input.focus();
+
+  var simpleCallback = function (aTIP, aNotification)
+  {
+    switch (aNotification.type) {
+      case "request-to-commit":
+        aTIP.commitComposition();
+        break;
+      case "request-to-cancel":
+        aTIP.cancelComposition();
+        break;
+    }
+    return true;
+  };
+
+  var TIP1 = createTIP();
+  var TIP2 = createTIP();
+  isnot(TIP1, TIP2,
+        description + "TIP instances should be different");
+
+  // init() and initForTests() can take ownership if there is no composition.
+  ok(TIP1.init(window, simpleCallback),
+     description + "TIP1.init(window) should succeed because there is no composition");
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests(window) should succeed because there is no composition");
+  ok(TIP2.init(window, simpleCallback),
+     description + "TIP2.init(window) should succeed because there is no composition");
+  ok(TIP2.initForTests(window),
+     description + "TIP2.initForTests(window) should succeed because there is no composition");
+
+  // Start composition with TIP1, then, other TIPs cannot take ownership during a composition.
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests() should succeed because there is no composition");
+  var composingStr = "foo";
+  TIP1.setPendingCompositionString(composingStr);
+  TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+  ok(TIP1.flushPendingComposition(),
+     description + "TIP1.flushPendingComposition() should return true becuase it should be valid composition");
+  is(input.value, composingStr,
+     description + "The input element should have composing string");
+
+  // Composing nsITextInputProcessor instance shouldn't allow initialize it again.
+  try {
+    TIP1.init(window, simpleCallback);
+    ok(false,
+       "TIP1.init(window) should cause throwing an exception because it's composing with different purpose");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ALREADY_INITIALIZED"),
+       description + "TIP1.init(window) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing for tests");
+  }
+  try {
+    TIP1.initForTests(otherWindow);
+    ok(false,
+       "TIP1.initForTests(otherWindow) should cause throwing an exception because it's composing on different window");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ALREADY_INITIALIZED"),
+       description + "TIP1.init(otherWindow) should cause throwing an exception including NS_ERROR_ALREADY_INITIALIZED because it's composing on this window");
+  }
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests(window) should succeed because TextEventDispatcher was initialized with same purpose");
+  ok(TIP1.initForTests(childWindow),
+     description + "TIP1.initForTests(childWindow) should succeed because TextEventDispatcher was initialized with same purpose and is shared by window and childWindow");
+  ok(!TIP2.init(window, simpleCallback),
+     description + "TIP2.init(window) should not succeed because there is composition synthesized by TIP1");
+  ok(!TIP2.initForTests(window),
+     description + "TIP2.initForTests(window) should not succeed because there is composition synthesized by TIP1");
+  ok(!TIP2.init(childWindow, simpleCallback),
+     description + "TIP2.init(childWindow) should not succeed because there is composition synthesized by TIP1");
+  ok(!TIP2.initForTests(childWindow),
+     description + "TIP2.initForTests(childWindow) should not succeed because there is composition synthesized by TIP1");
+  ok(TIP2.init(otherWindow, simpleCallback),
+     description + "TIP2.init(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+  ok(TIP2.initForTests(otherWindow),
+     description + "TIP2.initForTests(otherWindow) should succeed because there is composition synthesized by TIP1 but it's in other window");
+
+  // Let's confirm that the composing string is NOT committed by above tests.
+  ok(TIP1.commitComposition(),
+     description + "TIP1.commitString() should succeed because there should be composing string");
+  is(input.value, composingStr,
+     description + "TIP1.commitString() without specifying commit string should be committed with the last composing string");
+
+  ok(TIP1.init(window, simpleCallback),
+     description + "TIP1.init() should succeed because there is no composition #2");
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests() should succeed because there is no composition #2");
+  ok(TIP2.initForTests(window),
+     description + "TIP2.initForTests() should succeed because the composition was already committed #2");
+
+  // Let's check if startComposition() throws an exception after ownership is strolen.
+  input.value = "";
+  try {
+    TIP1.startComposition();
+    ok(false,
+       description + "TIP1.startComposition() should cause throwing an exception because TIP2 took the ownership");
+    TIP1.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_NOT_INITIALIZED"),
+       description + "TIP1.startComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+  } finally {
+    is(input.value, "",
+       description + "The input element should not have commit string");
+  }
+
+  // Let's check if flushPendingComposition() throws an exception after ownership is stolen.
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests() should succeed because there is no composition");
+  ok(TIP2.initForTests(window),
+     description + "TIP2.initForTests() should succeed because there is no composition");
+  input.value = "";
+  try {
+    TIP1.setPendingCompositionString(composingStr);
+    TIP1.appendClauseToPendingComposition(composingStr.length, TIP1.ATTR_RAW_CLAUSE);
+    TIP1.flushPendingComposition()
+    ok(false,
+       description + "TIP1.flushPendingComposition() should cause throwing an exception because TIP2 took the ownership");
+    TIP1.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_NOT_INITIALIZED"),
+       description + "TIP1.flushPendingComposition() should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+  } finally {
+    is(input.value, "",
+       description + "The input element should not have commit string");
+  }
+
+  // Let's check if commitComposition("bar") throws an exception after ownership is stolen.
+  ok(TIP1.initForTests(window),
+     description + "TIP1.initForTests() should succeed because there is no composition");
+  ok(TIP2.initForTests(window),
+     description + "TIP2.initForTests() should succeed because there is no composition");
+  input.value = "";
+  try {
+    TIP1.commitComposition("bar");
+    ok(false,
+       description + "TIP1.commitComposition(\"bar\") should cause throwing an exception because TIP2 took the ownership");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_NOT_INITIALIZED"),
+       description + "TIP1.commitComposition(\"bar\") should cause throwing an exception including NS_ERROR_NOT_INITIALIZED");
+  } finally {
+    is(input.value, "",
+       description + "The input element should not have commit string");
+  }
+
+  // aCallback of nsITextInputProcessor.init() must not be omitted.
+  try {
+    TIP1.init(window);
+    ok(false,
+       description + "TIP1.init(window) should be failed since aCallback is omitted");
+  } catch (e) {
+    ok(e.message.contains("Not enough arguments"),
+       description + "TIP1.init(window) should cause throwing an exception including \"Not enough arguments\" since aCallback is omitted");
+  }
+
+  // aCallback of nsITextInputProcessor.init() must not be undefined.
+  try {
+    TIP1.init(window, undefined);
+    ok(false,
+       description + "TIP1.init(window, undefined) should be failed since aCallback is undefined");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "TIP1.init(window, undefined) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is undefined");
+  }
+
+  // aCallback of nsITextInputProcessor.init() must not be null.
+  try {
+    TIP1.init(window, null);
+    ok(false,
+       description + "TIP1.init(window, null) should be failed since aCallback is null");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "TIP1.init(window, null) should cause throwing an exception including NS_ERROR_ILLEGAL_VALUE since aCallback is null");
+  }
+}
+
+function runReleaseTests()
+{
+  var description = "runReleaseTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.initForTests(window),
+     description + "TIP.initForTests() should succeed");
+
+  input.value = "";
+  input.focus();
+
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  is(input.value, "foo",
+     description + "the input should have composition string");
+
+  // Release the TIP
+  TIP = null;
+  // Needs to run GC forcibly for testing this.
+  SpecialPowers.gc();
+
+  is(input.value, "",
+     description + "the input should be empty because the composition should be canceled");
+
+  TIP = createTIP();
+  ok(TIP.initForTests(window),
+     description + "TIP.initForTests() should succeed #2");
+}
+
+function runCompositionTests()
+{
+  var description = "runCompositionTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.initForTests(window),
+     description + "TIP.initForTests() should succeed");
+
+  var events;
+
+  function reset()
+  {
+    events = [];
+  }
+
+  function handler(aEvent)
+  {
+    events.push({ "type": aEvent.type, "data": aEvent.data });
+  }
+
+  window.addEventListener("compositionstart", handler, false);
+  window.addEventListener("compositionupdate", handler, false);
+  window.addEventListener("compositionend", handler, false);
+
+  input.value = "";
+  input.focus();
+
+  // nsITextInputProcessor.startComposition()
+  reset();
+  TIP.startComposition();
+  is(events.length, 1,
+     description + "startComposition() should cause only compositionstart");
+  is(events[0].type, "compositionstart",
+     description + "startComposition() should cause only compositionstart");
+  is(input.value, "",
+     description + "startComposition() shouldn't modify the focused editor");
+
+  // Setting composition string "foo" as a raw clause
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 1,
+     description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "flushPendingComposition() after startComposition() should cause compositionupdate");
+  is(events[0].data, "foo",
+     description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+  is(input.value, "foo",
+     description + "modifying composition string should cause modifying the focused editor");
+
+  // Changing the raw clause to a selected clause
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_SELECTED_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 0,
+     description + "flushPendingComposition() changing only clause information shouldn't cause compositionupdate");
+  is(input.value, "foo",
+     description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+  // Separating the selected clause to two clauses
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+  TIP.setCaretInPendingComposition(2);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 0,
+     description + "flushPendingComposition() separating a clause information shouldn't cause compositionupdate");
+  is(input.value, "foo",
+     description + "separating composition clause shouldn't cause modifying the focused editor");
+
+  // Modifying the composition string
+  TIP.setPendingCompositionString("FOo");
+  TIP.appendClauseToPendingComposition(2, TIP.ATTR_SELECTED_CLAUSE);
+  TIP.appendClauseToPendingComposition(1, TIP.ATTR_CONVERTED_CLAUSE);
+  TIP.setCaretInPendingComposition(2);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 1,
+     description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "flushPendingComposition() causing modifying composition string should cause compositionupdate");
+  is(events[0].data, "FOo",
+     description + "compositionupdate caused by flushPendingComposition() should have new composition string in its data");
+  is(input.value, "FOo",
+     description + "modifying composition clause shouldn't cause modifying the focused editor");
+
+  // Committing the composition string
+  reset();
+  TIP.commitComposition();
+  is(events.length, 1,
+     description + "commitComposition() should cause compositionend but shoudn't cause compositionupdate");
+  is(events[0].type, "compositionend",
+     description + "commitComposition() should cause compositionend");
+  is(events[0].data, "FOo",
+     description + "compositionend caused by commitComposition() should have the committed string in its data");
+  is(input.value, "FOo",
+     description + "commitComposition() shouldn't cause modifying the focused editor");
+
+  // Starting new composition without a call of startComposition()
+  TIP.setPendingCompositionString("bar");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 2,
+     description + "flushPendingComposition() without a call of startComposition() should cause both compositionstart and compositionupdate");
+  is(events[0].type, "compositionstart",
+     description + "flushPendingComposition() without a call of startComposition() should cause compositionstart");
+  is(events[1].type, "compositionupdate",
+     description + "flushPendingComposition() without a call of startComposition() should cause compositionupdate after compositionstart");
+  is(events[1].data, "bar",
+     description + "compositionupdate caused by flushPendingComposition() without a call of startComposition() should have the composition string in its data");
+  is(input.value, "FOobar",
+     description + "new composition string should cause appending composition string to the focused editor");
+
+  // Canceling the composition
+  reset();
+  TIP.cancelComposition();
+  is(events.length, 2,
+     description + "cancelComposition() should cause both compositionupdate and compositionend");
+  is(events[0].type, "compositionupdate",
+     description + "cancelComposition() should cause compositionupdate");
+  is(events[0].data, "",
+     description + "compositionupdate caused by cancelComposition() should have empty string in its data");
+  is(events[1].type, "compositionend",
+     description + "cancelComposition() should cause compositionend after compositionupdate");
+  is(events[1].data, "",
+     description + "compositionend caused by cancelComposition() should have empty string in its data");
+  is(input.value, "FOo",
+     description + "canceled composition string should be removed from the focused editor");
+
+  // Starting composition explicitly and canceling it
+  reset();
+  TIP.startComposition();
+  TIP.cancelComposition();
+  is(events.length, 2,
+     description + "canceling composition immediately after startComposition() should cause compositionstart and compositionend");
+  is(events[0].type, "compositionstart",
+     description + "canceling composition immediately after startComposition() should cause compositionstart first");
+  is(events[1].type, "compositionend",
+     description + "canceling composition immediately after startComposition() should cause compositionend after compositionstart");
+  is(events[1].data, "",
+     description + "compositionend caused by canceling composition should have empty string in its data");
+  is(input.value, "FOo",
+     description + "canceling composition shouldn't modify the focused editor");
+
+  // Create composition for next test.
+  TIP.setPendingCompositionString("bar");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.flushPendingComposition();
+  is(input.value, "FOobar",
+     description + "The focused editor should have new composition string \"bar\"");
+
+  // Allow to set empty composition string
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 1,
+     description + "making composition string empty should cause only compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "making composition string empty should cause compositionupdate");
+  is(events[0].data, "",
+     description + "compositionupdate caused by making composition string empty should have empty string in its data");
+
+  // Allow to insert new composition string without compositionend/compositionstart
+  TIP.setPendingCompositionString("buzz");
+  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+
+  reset();
+  TIP.flushPendingComposition();
+  is(events.length, 1,
+     description + "modifying composition string from empty string should cause only compositionupdate");
+  is(events[0].type, "compositionupdate",
+     description + "modifying composition string from empty string should cause compositionupdate");
+  is(events[0].data, "buzz",
+     description + "compositionupdate caused by modifying composition string from empty string should have new composition string in its data");
+  is(input.value, "FOobuzz",
+     description + "new composition string should be appended to the focused editor");
+
+  // Committing with different string
+  reset();
+  TIP.commitComposition("bar");
+  is(events.length, 2,
+     description + "committing with different string should cause compositionupdate and compositionend");
+  is(events[0].type, "compositionupdate",
+     description + "committing with different string should cause compositionupdate first");
+  is(events[0].data, "bar",
+     description + "compositionupdate caused by committing with different string should have the committing string in its data");
+  is(events[1].type, "compositionend",
+     description + "committing with different string should cause compositionend after compositionupdate");
+  is(events[1].data, "bar",
+     description + "compositionend caused by committing with different string should have the committing string in its data");
+  is(input.value, "FOobar",
+     description + "new committed string should be appended to the focused editor");
+
+  // Appending new composition string
+  TIP.setPendingCompositionString("buzz");
+  TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+  TIP.flushPendingComposition();
+  is(input.value, "FOobarbuzz",
+     description + "new composition string should be appended to the focused editor");
+
+  // Committing with same string
+  reset();
+  TIP.commitComposition("buzz");
+  is(events.length, 1,
+     description + "committing with same string should cause only compositionend");
+  is(events[0].type, "compositionend",
+     description + "committing with same string should cause compositionend");
+  is(events[0].data, "buzz",
+     description + "compositionend caused by committing with same string should have the committing string in its data");
+  is(input.value, "FOobarbuzz",
+     description + "new committed string should be appended to the focused editor");
+
+  // Inserting commit string directly
+  reset();
+  TIP.commitComposition("boo!");
+  is(events.length, 3,
+     description + "committing text directly should cause compositionstart, compositionupdate and compositionend");
+  is(events[0].type, "compositionstart",
+     description + "committing text directly should cause compositionstart first");
+  is(events[1].type, "compositionupdate",
+     description + "committing text directly should cause compositionupdate after compositionstart");
+  is(events[1].data, "boo!",
+     description + "compositionupdate caused by committing text directly should have the committing text in its data");
+  is(events[2].type, "compositionend",
+     description + "committing text directly should cause compositionend after compositionupdate");
+  is(events[2].data, "boo!",
+     description + "compositionend caused by committing text directly should have the committing text in its data");
+  is(input.value, "FOobarbuzzboo!",
+     description + "committing text directly should append the committing text to the focused editor");
+
+  window.removeEventListener("compositionstart", handler, false);
+  window.removeEventListener("compositionupdate", handler, false);
+  window.removeEventListener("compositionend", handler, false);
+}
+
+function runErrorTests()
+{
+  var description = "runErrorTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.initForTests(window),
+     description + "TIP.initForTests() should succeed");
+
+  input.value = "";
+  input.focus();
+
+  // startComposition() should throw an exception if there is already a composition
+  TIP.startComposition();
+  try {
+    TIP.startComposition();
+    ok(false,
+       description + "startComposition() should fail if it was already called");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_FAILURE"),
+       description + "startComposition() should cause NS_ERROR_FAILURE if there is already composition");
+  } finally {
+    TIP.cancelComposition();
+  }
+
+  // cancelComposition() should throw an exception if there is no composition
+  try {
+    TIP.cancelComposition();
+    ok(false,
+       description + "cancelComposition() should fail if there is no composition");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_FAILURE"),
+       description + "cancelComposition() should cause NS_ERROR_FAILURE if there is no composition");
+  }
+
+  // commitComposition() without commit string should throw an exception if there is no composition
+  try {
+    TIP.commitComposition();
+    ok(false,
+       description + "commitComposition() should fail if there is no composition");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_FAILURE"),
+       description + "commitComposition() should cause NS_ERROR_FAILURE if there is no composition");
+  }
+
+  // commitComposition("") should throw an exception if there is no composition
+  try {
+    TIP.commitComposition("");
+    ok(false,
+       description + "commitComposition(\"\") should fail if there is no composition");
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_FAILURE"),
+       description + "commitComposition(\"\") should cause NS_ERROR_FAILURE if there is no composition");
+  }
+
+  // Pending composition string should allow to flush without clause information (for compatibility)
+  try {
+    TIP.setPendingCompositionString("foo");
+    TIP.flushPendingComposition();
+    ok(true,
+       description + "flushPendingComposition() should succeed even if appendClauseToPendingComposition() has never been called");
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(false,
+       description + "flushPendingComposition() shouldn't cause an exception even if appendClauseToPendingComposition() has never been called");
+  }
+
+  // Pending composition string must be filled by clause information
+  try {
+    TIP.setPendingCompositionString("foo");
+    TIP.appendClauseToPendingComposition(2, TIP.ATTR_RAW_CLAUSE);
+    TIP.flushPendingComposition();
+    ok(false,
+       description + "flushPendingComposition() should fail if appendClauseToPendingComposition() doesn't fill all composition string");
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() doesn't fill all composition string");
+  }
+
+  // Pending composition string must not be shorter than appended clause length
+  try {
+    TIP.setPendingCompositionString("foo");
+    TIP.appendClauseToPendingComposition(4, TIP.ATTR_RAW_CLAUSE);
+    TIP.flushPendingComposition();
+    ok(false,
+       description + "flushPendingComposition() should fail if appendClauseToPendingComposition() appends longer clause information");
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if appendClauseToPendingComposition() appends longer clause information");
+  }
+
+  // Pending composition must not have clause information with empty string
+  try {
+    TIP.appendClauseToPendingComposition(1, TIP.ATTR_RAW_CLAUSE);
+    TIP.flushPendingComposition();
+    ok(false,
+       description + "flushPendingComposition() should fail if there is a clause with empty string");
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if there is a clause with empty string");
+  }
+
+  // Appending a clause whose length is 0 should cause an exception
+  try {
+    TIP.appendClauseToPendingComposition(0, TIP.ATTR_RAW_CLAUSE);
+    ok(false,
+       description + "appendClauseToPendingComposition() should fail if the length is 0");
+    TIP.flushPendingComposition();
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the length is 0");
+  }
+
+  // Appending a clause whose attribute is invalid should cause an exception
+  try {
+    TIP.setPendingCompositionString("foo");
+    TIP.appendClauseToPendingComposition(3, 0);
+    ok(false,
+       description + "appendClauseToPendingComposition() should fail if the attribute is invalid");
+    TIP.flushPendingComposition();
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "appendClauseToPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if the attribute is invalid");
+  }
+
+  // Setting caret position outside of composition string should cause an exception
+  try {
+    TIP.setPendingCompositionString("foo");
+    TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+    TIP.setCaretInPendingComposition(4);
+    TIP.flushPendingComposition();
+    ok(false,
+       description + "flushPendingComposition() should fail if caret position is out of composition string");
+    TIP.cancelComposition();
+  } catch (e) {
+    ok(e.message.contains("NS_ERROR_ILLEGAL_VALUE"),
+       description + "flushPendingComposition() should cause NS_ERROR_ILLEGAL_VALUE if caret position is out of composition string");
+  }
+}
+
+function runCommitCompositionTests()
+{
+  var description = "runCommitCompositionTests(): ";
+
+  var TIP = createTIP();
+  ok(TIP.initForTests(window),
+     description + "TIP.initForTests() should succeed");
+
+  input.focus();
+
+  // commitComposition() should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  TIP.commitComposition();
+  is(input.value, "foo",
+     description + "commitComposition() should commit the composition with the last data");
+
+  // commitComposition("") should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  TIP.commitComposition("");
+  is(input.value, "",
+     description + "commitComposition(\"\") should commit the composition with empty string");
+
+  // commitComposition(null) should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  TIP.commitComposition(null);
+  is(input.value, "",
+     description + "commitComposition(null) should commit the composition with empty string");
+
+  // commitComposition(undefined) should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  TIP.commitComposition(undefined);
+  todo_is(input.value, "foo",
+          description + "commitComposition(undefined) should commit the composition with the last data");
+
+  function doCommit(aText)
+  {
+    TIP.commitComposition(aText);
+  }
+
+  // doCommit() should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommit();
+  todo_is(input.value, "foo",
+          description + "doCommit() should commit the composition with the last data");
+
+  // doCommit("") should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommit("");
+  is(input.value, "",
+     description + "doCommit(\"\") should commit the composition with empty string");
+
+  // doCommit(null) should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommit(null);
+  is(input.value, "",
+     description + "doCommit(null) should commit the composition with empty string");
+
+  // doCommit(undefined) should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommit(undefined);
+  todo_is(input.value, "foo",
+          description + "doCommit(undefined) should commit the composition with the last data");
+
+  function doCommitWithNullCheck(aText)
+  {
+    TIP.commitComposition(aText ? aText : "");
+  }
+
+  // doCommitWithNullCheck() should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommitWithNullCheck();
+  is(input.value, "",
+     description + "doCommitWithNullCheck() should commit the composition with empty string");
+
+  // doCommitWithNullCheck("") should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommitWithNullCheck("");
+  is(input.value, "",
+     description + "doCommitWithNullCheck(\"\") should commit the composition with empty string");
+
+  // doCommitWithNullCheck(null) should commit the composition with empty string.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommitWithNullCheck(null);
+  is(input.value, "",
+     description + "doCommitWithNullCheck(null) should commit the composition with empty string");
+
+  // doCommitWithNullCheck(undefined) should commit the composition with the last data.
+  input.value = "";
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  doCommitWithNullCheck(undefined);
+  is(input.value, "",
+     description + "doCommitWithNullCheck(undefined) should commit the composition with empty string");
+}
+
+function runUnloadTests1(aNextTest)
+{
+  var description = "runUnloadTests1(): ";
+
+  var TIP1 = createTIP();
+  ok(TIP1.initForTests(childWindow),
+     description + "TIP1.initForTests() should succeed");
+
+  var oldSrc = iframe.src;
+  var parentWindow = window;
+
+  iframe.addEventListener("load", function (aEvent) {
+    ok(true, description + "dummy page is loaded");
+    iframe.removeEventListener("load", arguments.callee, true);
+    childWindow = iframe.contentWindow;
+    textareaInFrame = null;
+    iframe.addEventListener("load", function () {
+      ok(true, description + "old iframe is restored");
+      // And also restore the iframe information with restored contents.
+      iframe.removeEventListener("DOMContentLoaded", arguments.callee, true);
+      childWindow = iframe.contentWindow;
+      textareaInFrame = iframe.contentDocument.getElementById("textarea");
+      setTimeout(aNextTest, 0);
+    }, true);
+
+    // The composition should be committed internally.  So, another TIP should
+    // be able to steal the rights to using TextEventDispatcher.
+    var TIP2 = createTIP();
+    ok(TIP2.initForTests(parentWindow),
+       description + "TIP2.initForTests() should succeed");
+
+    input.focus();
+    input.value = "";
+
+    TIP2.setPendingCompositionString("foo");
+    TIP2.appendClauseToPendingComposition(3, TIP2.ATTR_RAW_CLAUSE);
+    TIP2.setCaretInPendingComposition(3);
+    TIP2.flushPendingComposition();
+    is(input.value, "foo",
+       description + "the input in the parent document should have composition string");
+
+    TIP2.cancelComposition();
+
+    // Restore the old iframe content.
+    iframe.src = oldSrc;
+  }, true);
+
+  // Start composition in the iframe.
+  textareaInFrame.value = "";
+  textareaInFrame.focus();
+
+  TIP1.setPendingCompositionString("foo");
+  TIP1.appendClauseToPendingComposition(3, TIP1.ATTR_RAW_CLAUSE);
+  TIP1.setCaretInPendingComposition(3);
+  TIP1.flushPendingComposition();
+  is(textareaInFrame.value, "foo",
+     description + "the textarea in the iframe should have composition string");
+
+  // Load different web page on the frame.
+  iframe.src = "data:text/html,<body>dummy page</body>";
+}
+
+function runUnloadTests2(aNextTest)
+{
+  var description = "runUnloadTests2(): ";
+
+  var TIP = createTIP();
+  ok(TIP.initForTests(childWindow),
+     description + "TIP.initForTests() should succeed");
+
+  var oldSrc = iframe.src;
+  var parentWindow = window;
+
+  iframe.addEventListener("load", function (aEvent) {
+    ok(true, description + "dummy page is loaded");
+    iframe.removeEventListener("load", arguments.callee, true);
+    childWindow = iframe.contentWindow;
+    textareaInFrame = null;
+    iframe.addEventListener("load", function () {
+      ok(true, description + "old iframe is restored");
+      // And also restore the iframe information with restored contents.
+      iframe.removeEventListener("load", arguments.callee, true);
+      childWindow = iframe.contentWindow;
+      textareaInFrame = iframe.contentDocument.getElementById("textarea");
+      setTimeout(aNextTest, 0);
+    }, true);
+
+    input.focus();
+    input.value = "";
+
+    // TIP should be still available in the same top level widget.
+    TIP.setPendingCompositionString("bar");
+    TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+    TIP.setCaretInPendingComposition(3);
+    TIP.flushPendingComposition();
+    is(input.value, "bar",
+       description + "the input in the parent document should have composition string");
+
+    TIP.cancelComposition();
+
+    // Restore the old iframe content.
+    iframe.src = oldSrc;
+  }, true);
+
+  // Start composition in the iframe.
+  textareaInFrame.value = "";
+  textareaInFrame.focus();
+
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.setCaretInPendingComposition(3);
+  TIP.flushPendingComposition();
+  is(textareaInFrame.value, "foo",
+     description + "the textarea in the iframe should have composition string");
+
+  // Load different web page on the frame.
+  iframe.src = "data:text/html,<body>dummy page</body>";
+}
+
+function runCallbackTests(aForTests)
+{
+  var description = "runCallbackTests(aForTests=" + aForTests + "): ";
+
+  input.value = "";
+  input.focus();
+  input.blur();
+
+  var TIP = createTIP();
+  var notifications = [];
+  function callback(aTIP, aNotification)
+  {
+    switch (aNotification.type) {
+      case "request-to-commit":
+        aTIP.commitComposition();
+        break;
+      case "request-to-cancel":
+        aTIP.cancelComposition();
+        break;
+    }
+    if (aTIP == TIP) {
+      notifications.push(aNotification);
+    }
+    return true;
+  }
+
+  function dumpUnexpectedNotifications(aExpectedCount)
+  {
+    if (notifications.length <= aExpectedCount) {
+      return;
+    }
+    for (var i = aExpectedCount; i < notifications.length; i++) {
+      ok(false,
+         description + "Unexpected notification: " + notifications[i].type);
+    }
+  }
+
+  if (aForTests) {
+    TIP.initForTests(window, callback);
+  } else {
+    TIP.init(window, callback);
+  }
+
+  notifications = [];
+  input.focus();
+  is(notifications.length, 1,
+     description + "input.focus() should cause a notification");
+  is(notifications[0].type, "notify-focus",
+     description + "input.focus() should cause \"notify-focus\"");
+  dumpUnexpectedNotifications(1);
+
+  notifications = [];
+  input.blur();
+  is(notifications.length, 1,
+     description + "input.blur() should cause a notification");
+  is(notifications[0].type, "notify-blur",
+     description + "input.blur() should cause \"notify-focus\"");
+  dumpUnexpectedNotifications(1);
+
+  input.focus();
+  TIP.setPendingCompositionString("foo");
+  TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
+  TIP.flushPendingComposition();
+  notifications = [];
+  synthesizeMouseAtCenter(input, {});
+  is(notifications.length, 1,
+     description + "synthesizeMouseAtCenter(input, {}) during composition should cause a notification");
+  is(notifications[0].type, "request-to-commit",
+     description + "synthesizeMouseAtCenter(input, {}) during composition should cause \"request-to-commit\"");
+  dumpUnexpectedNotifications(1);
+
+  notifications = [];
+  var TIP2 = createTIP();
+  if (aForTests) {
+    TIP2.initForTests(window, callback);
+  } else {
+    TIP2.init(window, callback);
+  }
+  is(notifications.length, 1,
+     description + "Initializing another TIP should cause a notification");
+  is(notifications[0].type, "notify-detached",
+     description + "Initializing another TIP should cause \"notify-detached\"");
+  dumpUnexpectedNotifications(1);
+}
+
+function runTests()
+{
+  textareaInFrame = iframe.contentDocument.getElementById("textarea");
+
+  runInitMethodTests();
+  runReleaseTests();
+  runCompositionTests();
+  runErrorTests();
+  runCommitCompositionTests();
+  runCallbackTests(false);
+  runCallbackTests(true);
+  runUnloadTests1(function () {
+    runUnloadTests2(function () {
+      finish();
+    });
+  });
+}
+
+]]>
+</script>
+
+</window>
--- a/dom/bindings/test/test_sequence_detection.html
+++ b/dom/bindings/test/test_sequence_detection.html
@@ -11,19 +11,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript">
 
   /** Test for Bug 1066432 **/
   SimpleTest.waitForExplicitFinish();
   SpecialPowers.pushPrefEnv({set: [['dom.expose_test_interfaces', true]]}, function() {
     var testInterfaceJS = new TestInterfaceJS();
     ok(testInterfaceJS, "got a TestInterfaceJS object");
 
-    var JS_HAS_SYMBOLS = typeof Symbol === "function";
-    var std_iterator = JS_HAS_SYMBOLS ? Symbol.iterator : "@@iterator";
-    var nonIterableObject = {[std_iterator]: 5};
+    var nonIterableObject = {[Symbol.iterator]: 5};
 
     try {
       testInterfaceJS.testSequenceOverload(nonIterableObject);
       ok(false, "Should have thrown in the overload case");  // see long comment above!
     } catch (e) {
       ise(e.name, "TypeError", "Should get a TypeError for the overload case");
       ok(e.message.contains("not iterable"),
          "Should have a message about being non-iterable in the overload case");
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -1519,17 +1519,20 @@ protected:
 
     bool mNeedsFakeNoAlpha;
 
     struct ScopedMaskWorkaround {
         WebGLContext& mWebGL;
         const bool mNeedsChange;
 
         static bool NeedsChange(WebGLContext& webgl) {
-            return webgl.mNeedsFakeNoAlpha &&
+            // We should only be doing this if we're about to draw to the backbuffer, but
+            // the backbuffer needs to have this fake-no-alpha workaround.
+            return !webgl.mBoundDrawFramebuffer &&
+                   webgl.mNeedsFakeNoAlpha &&
                    webgl.mColorWriteMask[3] != false;
         }
 
         explicit ScopedMaskWorkaround(WebGLContext& webgl);
 
         ~ScopedMaskWorkaround();
     };
 
--- a/dom/events/MouseEvent.cpp
+++ b/dom/events/MouseEvent.cpp
@@ -461,16 +461,22 @@ MouseEvent::MozPressure() const
 NS_IMETHODIMP
 MouseEvent::GetMozPressure(float* aPressure)
 {
   NS_ENSURE_ARG_POINTER(aPressure);
   *aPressure = MozPressure();
   return NS_OK;
 }
 
+bool
+MouseEvent::HitCluster() const
+{
+  return mEvent->AsMouseEventBase()->hitCluster;
+}
+
 uint16_t
 MouseEvent::MozInputSource() const
 {
   return mEvent->AsMouseEventBase()->inputSource;
 }
 
 NS_IMETHODIMP
 MouseEvent::GetMozInputSource(uint16_t* aInputSource)
--- a/dom/events/MouseEvent.h
+++ b/dom/events/MouseEvent.h
@@ -78,16 +78,17 @@ public:
   {
     return GetMovementPoint().x;
   }
   int32_t MozMovementY()
   {
     return GetMovementPoint().y;
   }
   float MozPressure() const;
+  bool HitCluster() const;
   uint16_t MozInputSource() const;
   void InitNSMouseEvent(const nsAString& aType,
                         bool aCanBubble, bool aCancelable,
                         nsIDOMWindow* aView, int32_t aDetail, int32_t aScreenX,
                         int32_t aScreenY, int32_t aClientX, int32_t aClientY,
                         bool aCtrlKey, bool aAltKey, bool aShiftKey,
                         bool aMetaKey, uint16_t aButton,
                         EventTarget* aRelatedTarget,
--- a/dom/events/TextComposition.cpp
+++ b/dom/events/TextComposition.cpp
@@ -335,45 +335,24 @@ TextComposition::RequestToCommit(nsIWidg
     AutoRestore<bool> saveRequestingCommit(mIsRequestingCommit);
     if (aDiscard) {
       mIsRequestingCancel = true;
       mIsRequestingCommit = false;
     } else {
       mIsRequestingCancel = false;
       mIsRequestingCommit = true;
     }
-    if (!mIsSynthesizedForTests) {
-      // FYI: CompositionEvents caused by a call of NotifyIME() may be
-      //      discarded by PresShell if it's not safe to dispatch the event.
-      nsresult rv =
-        aWidget->NotifyIME(IMENotification(aDiscard ?
-                                             REQUEST_TO_CANCEL_COMPOSITION :
-                                             REQUEST_TO_COMMIT_COMPOSITION));
-      if (rv == NS_ERROR_NOT_IMPLEMENTED) {
-        return rv;
-      }
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
-    } else {
-      // Emulates to commit or cancel the composition
-      // FYI: These events may be discarded by PresShell if it's not safe to
-      //      dispatch the event.
-      nsCOMPtr<nsIWidget> widget(aWidget);
-      nsAutoString commitData(aDiscard ? EmptyString() : lastData);
-      bool isChanging = commitData != mLastData;
-      uint32_t message =
-        isChanging ? NS_COMPOSITION_COMMIT : NS_COMPOSITION_COMMIT_AS_IS;
-      WidgetCompositionEvent commitEvent(true, message, widget);
-      if (commitEvent.message == NS_COMPOSITION_COMMIT) {
-        commitEvent.mData = commitData;
-      }
-      commitEvent.mFlags.mIsSynthesizedForTests = true;
-      nsEventStatus status = nsEventStatus_eIgnore;
-      widget->DispatchEvent(&commitEvent, status);
+    // FYI: CompositionEvents caused by a call of NotifyIME() may be
+    //      discarded by PresShell if it's not safe to dispatch the event.
+    nsresult rv =
+      aWidget->NotifyIME(IMENotification(aDiscard ?
+                                           REQUEST_TO_CANCEL_COMPOSITION :
+                                           REQUEST_TO_COMMIT_COMPOSITION));
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
     }
   }
 
   mRequestedToCommitOrCancel = true;
 
   // If the request is performed synchronously, this must be already destroyed.
   if (Destroyed()) {
     return NS_OK;
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -804,17 +804,17 @@ nsresult nsGeolocationService::Init()
   // do_getService gets hold of the already initialized component and starts
   // processing location requests immediately.
   // do_Createinstance will create multiple instances of the provider which is not right.
   // bug 993041
   mProvider = do_GetService(GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID);
 #endif
 
 #ifdef MOZ_WIDGET_COCOA
-  if (Preferences::GetBool("geo.provider.use_corelocation", false)) {
+  if (Preferences::GetBool("geo.provider.use_corelocation", true)) {
     mProvider = new CoreLocationLocationProvider();
   }
 #endif
 
 #ifdef XP_WIN
   if (Preferences::GetBool("geo.provider.ms-windows-location", false)) {
     mProvider = new WindowsLocationProvider();
   }
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -1220,16 +1220,42 @@ nsHTMLDocument::Scripts()
 NS_IMETHODIMP
 nsHTMLDocument::GetCookie(nsAString& aCookie)
 {
   ErrorResult rv;
   GetCookie(aCookie, rv);
   return rv.ErrorCode();
 }
 
+already_AddRefed<nsIChannel>
+nsHTMLDocument::CreateDummyChannelForCookies(nsIURI* aCodebaseURI)
+{
+  // The cookie service reads the privacy status of the channel we pass to it in
+  // order to determine which cookie database to query.  In some cases we don't
+  // have a proper channel to hand it to the cookie service though.  This
+  // function creates a dummy channel that is not used to load anything, for the
+  // sole purpose of handing it to the cookie service.  DO NOT USE THIS CHANNEL
+  // FOR ANY OTHER PURPOSE.
+  MOZ_ASSERT(!mChannel);
+
+  nsCOMPtr<nsIChannel> channel;
+  NS_NewChannel(getter_AddRefs(channel), aCodebaseURI, this,
+                nsILoadInfo::SEC_NORMAL,
+                nsIContentPolicy::TYPE_INVALID);
+  nsCOMPtr<nsIPrivateBrowsingChannel> pbChannel =
+    do_QueryInterface(channel);
+  nsCOMPtr<nsIDocShell> docShell(mDocumentContainer);
+  nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+  if (!pbChannel || !loadContext) {
+    return nullptr;
+  }
+  pbChannel->SetPrivate(loadContext->UsePrivateBrowsing());
+  return channel.forget();
+}
+
 void
 nsHTMLDocument::GetCookie(nsAString& aCookie, ErrorResult& rv)
 {
   aCookie.Truncate(); // clear current cookie in case service fails;
                       // no cookie isn't an error condition.
 
   if (mDisableCookieAccess) {
     return;
@@ -1252,18 +1278,26 @@ nsHTMLDocument::GetCookie(nsAString& aCo
 
     if (!codebaseURI) {
       // Document's principal is not a codebase (may be system), so
       // can't set cookies
 
       return;
     }
 
+    nsCOMPtr<nsIChannel> channel(mChannel);
+    if (!channel) {
+      channel = CreateDummyChannelForCookies(codebaseURI);
+      if (!channel) {
+        return;
+      }
+    }
+
     nsXPIDLCString cookie;
-    service->GetCookieString(codebaseURI, mChannel, getter_Copies(cookie));
+    service->GetCookieString(codebaseURI, channel, getter_Copies(cookie));
     // CopyUTF8toUTF16 doesn't handle error
     // because it assumes that the input is valid.
     nsContentUtils::ConvertStringFromEncoding(NS_LITERAL_CSTRING("UTF-8"),
                                               cookie, aCookie);
   }
 }
 
 NS_IMETHODIMP
@@ -1297,18 +1331,26 @@ nsHTMLDocument::SetCookie(const nsAStrin
 
     if (!codebaseURI) {
       // Document's principal is not a codebase (may be system), so
       // can't set cookies
 
       return;
     }
 
+    nsCOMPtr<nsIChannel> channel(mChannel);
+    if (!channel) {
+      channel = CreateDummyChannelForCookies(codebaseURI);
+      if (!channel) {
+        return;
+      }
+    }
+
     NS_ConvertUTF16toUTF8 cookie(aCookie);
-    service->SetCookieString(codebaseURI, nullptr, cookie.get(), mChannel);
+    service->SetCookieString(codebaseURI, nullptr, cookie.get(), channel);
   }
 }
 
 NS_IMETHODIMP
 nsHTMLDocument::Open(const nsAString& aContentTypeOrUrl,
                      const nsAString& aReplaceOrName,
                      const nsAString& aFeatures,
                      JSContext* cx, uint8_t aOptionalArgCount,
--- a/dom/html/nsHTMLDocument.h
+++ b/dom/html/nsHTMLDocument.h
@@ -270,16 +270,19 @@ protected:
   void WriteCommon(JSContext *cx,
                    const mozilla::dom::Sequence<nsString>& aText,
                    bool aNewlineTerminate,
                    mozilla::ErrorResult& rv);
 
   nsresult CreateAndAddWyciwygChannel(void);
   nsresult RemoveWyciwygChannel(void);
 
+  // This should *ONLY* be used in GetCookie/SetCookie.
+  already_AddRefed<nsIChannel> CreateDummyChannelForCookies(nsIURI* aCodebaseURI);
+
   /**
    * Like IsEditingOn(), but will flush as needed first.
    */
   bool IsEditingOnAfterFlush();
 
   void *GenerateParserKey(void);
 
   nsRefPtr<nsContentList> mImages;
--- a/dom/html/test/browser.ini
+++ b/dom/html/test/browser.ini
@@ -3,8 +3,13 @@ support-files =
   bug592641_img.jpg
   file_bug649778.html
   file_bug649778.html^headers^
 
 [browser_bug592641.js]
 [browser_bug649778.js]
 skip-if = e10s # Bug ?????? - leaked until shutdown [nsGlobalWindow #16 about:blank]
 [browser_bug1081537.js]
+[browser_bug1108547.js]
+support-files =
+  file_bug1108547-1.html
+  file_bug1108547-2.html
+  file_bug1108547-3.html
new file mode 100644
--- /dev/null
+++ b/dom/html/test/browser_bug1108547.js
@@ -0,0 +1,109 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function test() {
+  waitForExplicitFinish();
+
+  runPass("file_bug1108547-2.html", function() {
+    runPass("file_bug1108547-3.html", function() {
+      finish();
+    });
+  });
+}
+
+function runPass(getterFile, finishedCallback) {
+  var rootDir = "http://mochi.test:8888/browser/dom/html/test/";
+  var testBrowser;
+  var privateWin;
+
+  function whenDelayedStartupFinished(win, callback) {
+    let topic = "browser-delayed-startup-finished";
+    Services.obs.addObserver(function onStartup(aSubject) {
+      if (win != aSubject)
+        return;
+
+      Services.obs.removeObserver(onStartup, topic);
+      executeSoon(callback);
+    }, topic, false);
+  }
+
+  // First, set the cookie in a normal window.
+  gBrowser.selectedTab = gBrowser.addTab(rootDir + "file_bug1108547-1.html");
+  gBrowser.selectedBrowser.addEventListener("load", afterOpenCookieSetter, true);
+
+  function afterOpenCookieSetter() {
+    gBrowser.selectedBrowser.removeEventListener("load", afterOpenCookieSetter, true);
+    gBrowser.removeCurrentTab();
+
+    // Now, open a private window.
+    privateWin = OpenBrowserWindow({private: true});
+      whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened);
+  }
+
+  function afterPrivateWindowOpened() {
+    // In the private window, open the getter file, and wait for a new tab to be opened.
+    privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + getterFile);
+    testBrowser = privateWin.gBrowser.selectedBrowser;
+    privateWin.gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened, true);
+  }
+
+  function onNewTabOpened() {
+    // When the new tab is opened, wait for it to load.
+    privateWin.gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened, true);
+    privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser.addEventListener("load", onNewTabLoaded, true);
+  }
+
+  function onNewTabLoaded() {
+    privateWin.gBrowser.tabs[privateWin.gBrowser.tabs.length - 1].linkedBrowser.removeEventListener("load", onNewTabLoaded, true);
+
+    // Now, ensure that the private tab doesn't have access to the cookie set in normal mode.
+    is(testBrowser.contentDocument.getElementById("result").textContent, "",
+       "Shouldn't have access to the cookies");
+
+    // We're done with the private window, close it.
+    privateWin.close();
+
+    // Clear all cookies.
+    Cc["@mozilla.org/cookiemanager;1"].getService(Ci.nsICookieManager).removeAll();
+
+    // Open a new private window, this time to set a cookie inside it.
+    privateWin = OpenBrowserWindow({private: true});
+      whenDelayedStartupFinished(privateWin, afterPrivateWindowOpened2);
+  }
+
+  function afterPrivateWindowOpened2() {
+    // In the private window, open the setter file, and wait for it to load.
+    privateWin.gBrowser.selectedTab = privateWin.gBrowser.addTab(rootDir + "file_bug1108547-1.html");
+    privateWin.gBrowser.selectedBrowser.addEventListener("load", afterOpenCookieSetter2, true);
+  }
+
+  function afterOpenCookieSetter2() {
+    // We're done with the private window now, close it.
+    privateWin.close();
+
+    // Now try to read the cookie in a normal window, and wait for a new tab to be opened.
+    gBrowser.selectedTab = gBrowser.addTab(rootDir + getterFile);
+    testBrowser = gBrowser.selectedBrowser;
+    gBrowser.tabContainer.addEventListener("TabOpen", onNewTabOpened2, true);
+  }
+
+  function onNewTabOpened2() {
+    // When the new tab is opened, wait for it to load.
+    gBrowser.tabContainer.removeEventListener("TabOpen", onNewTabOpened2, true);
+    gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser.addEventListener("load", onNewTabLoaded2, true);
+  }
+
+  function onNewTabLoaded2() {
+    gBrowser.tabs[gBrowser.tabs.length - 1].linkedBrowser.removeEventListener("load", onNewTabLoaded2, true);
+
+    // Now, ensure that the normal tab doesn't have access to the cookie set in private mode.
+    is(testBrowser.contentDocument.getElementById("result").textContent, "",
+       "Shouldn't have access to the cookies");
+
+    // Remove both of the tabs opened here.
+    gBrowser.removeCurrentTab();
+    gBrowser.removeCurrentTab();
+
+    finishedCallback();
+  }
+}
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_bug1108547-1.html
@@ -0,0 +1,4 @@
+<!DOCTYPE html>
+<script>
+document.cookie = "foo=bar";
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_bug1108547-2.html
@@ -0,0 +1,6 @@
+<!DOCTYPE html>
+<body onload="document.querySelector('form').submit();">
+<form action="javascript:opener.document.getElementById('result').textContent = document.cookie;" target="_blank">
+</form>
+<div id="result">not tested yet</div>
+</body>
new file mode 100644
--- /dev/null
+++ b/dom/html/test/file_bug1108547-3.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<body onload="document.querySelector('a').click();">
+<a href="javascript:opener.document.getElementById('result').textContent = document.cookie;" target="_blank">test</a>
+<div id="result">not tested yet</div>
+</body>
--- a/dom/html/test/test_formelements.html
+++ b/dom/html/test/test_formelements.html
@@ -27,33 +27,26 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 772869 **/
 var x = $("f").elements;
 x.something = "another";
 names = [];
 for (var name in x) {
   names.push(name);
 }
-var JS_HAS_SYMBOLS = typeof Symbol === "function";
-is(names.length, JS_HAS_SYMBOLS ? 9 : 10,
-   "Should have 9 enumerated names (or 10 with '@@iterator')");
+is(names.length, 9, "Should have 9 enumerated names");
 is(names[0], "0", "Enum entry 1");
 is(names[1], "1", "Enum entry 2");
 is(names[2], "2", "Enum entry 3");
 is(names[3], "3", "Enum entry 4");
 is(names[4], "4", "Enum entry 5");
 is(names[5], "something", "Enum entry 6");
 is(names[6], "namedItem", "Enum entry 7");
 is(names[7], "item", "Enum entry 8");
-if (JS_HAS_SYMBOLS) {
-  is(names[8], "length", "Enum entry 9");
-} else {
-  is(names[8], "@@iterator", "Enum entry 9");
-  is(names[9], "length", "Enum entry 10");
-}
+is(names[8], "length", "Enum entry 9");
 
 names = Object.getOwnPropertyNames(x);
 is(names.length, 10, "Should have 10 items");
 // Now sort entries 5 through 8, for comparison purposes.  We don't sort the
 // whole array, because we want to make sure the ordering between the parts
 // is correct
 temp = names.slice(5, 9);
 temp.sort();
--- a/dom/html/test/test_htmlcollection.html
+++ b/dom/html/test/test_htmlcollection.html
@@ -23,32 +23,25 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 772869 **/
 var x = document.getElementsByClassName("foo");
 x.something = "another";
 var names = [];
 for (var name in x) {
   names.push(name);
 }
-var JS_HAS_SYMBOLS = typeof Symbol === "function";
-is(names.length, JS_HAS_SYMBOLS ? 8 : 9,
-   "Should have 8 enumerated names (or 9 with '@@iterator')");
+is(names.length, 8, "Should have 8 enumerated names");
 is(names[0], "0", "Enum entry 1")
 is(names[1], "1", "Enum entry 2")
 is(names[2], "2", "Enum entry 3")
 is(names[3], "3", "Enum entry 4")
 is(names[4], "something", "Enum entry 5")
 is(names[5], "item", "Enum entry 6")
 is(names[6], "namedItem", "Enum entry 7")
-if (JS_HAS_SYMBOLS) {
-  is(names[7], "length", "Enum entry 8");
-} else {
-  is(names[7], "@@iterator", "Enum entry 8");
-  is(names[8], "length", "Enum entry 9");
-}
+is(names[7], "length", "Enum entry 8");
 
 names = Object.getOwnPropertyNames(x);
 is(names.length, 9, "Should have 9 items");
 is(names[0], "0", "Entry 1")
 is(names[1], "1", "Entry 2")
 is(names[2], "2", "Entry 3")
 is(names[3], "3", "Entry 4")
 is(names[4], "x", "Entry 5")
--- a/dom/html/test/test_named_options.html
+++ b/dom/html/test/test_named_options.html
@@ -37,30 +37,25 @@ is(names[5], "y", "Entry 6")
 is(names[6], "z", "Entry 7")
 is(names[7], "w", "Entry 8")
 is(names[8], "loopy", "Entry 9")
 
 var names2 = [];
 for (var name in opt) {
   names2.push(name);
 }
-var JS_HAS_SYMBOLS = typeof Symbol === "function";
-is(names2.length, JS_HAS_SYMBOLS ? 11 : 12,
-   "Should have eleven enumerated names (or twelve with '@@iterator')");
+is(names2.length, 11, "Should have eleven enumerated names");
 is(names2[0], "0", "Enum entry 1")
 is(names2[1], "1", "Enum entry 2")
 is(names2[2], "2", "Enum entry 3")
 is(names2[3], "3", "Enum entry 4")
 is(names2[4], "loopy", "Enum entry 5")
 is(names2[5], "add", "Enum entrry 6")
 is(names2[6], "remove", "Enum entry 7")
 is(names2[7], "length", "Enum entry 8")
 is(names2[8], "selectedIndex", "Enum entry 9")
 is(names2[9], "item", "Enum entry 10")
 is(names2[10], "namedItem", "Enum entry 11")
-if (!JS_HAS_SYMBOLS) {
-  is(names2[11], "@@iterator", "Enum entry 12");
-}
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/html/test/test_rowscollection.html
+++ b/dom/html/test/test_rowscollection.html
@@ -33,34 +33,27 @@ https://bugzilla.mozilla.org/show_bug.cg
 
 /** Test for Bug 772869 **/
 var x = $("f").rows;
 x.something = "another";
 var names = [];
 for (var name in x) {
   names.push(name);
 }
-var JS_HAS_SYMBOLS = typeof Symbol === "function";
-is(names.length, JS_HAS_SYMBOLS ? 10 : 11,
-   "Should have 10 enumerated names (or 11 with '@@iterator')");
+is(names.length, 10, "Should have 10 enumerated names");
 is(names[0], "0", "Enum entry 1")
 is(names[1], "1", "Enum entry 2")
 is(names[2], "2", "Enum entry 3")
 is(names[3], "3", "Enum entry 4")
 is(names[4], "4", "Enum entry 5")
 is(names[5], "5", "Enum entry 6")
 is(names[6], "something", "Enum entry 7")
 is(names[7], "item", "Enum entry 8")
 is(names[8], "namedItem", "Enum entry 9")
-if (JS_HAS_SYMBOLS) {
-  is(names[9], "length", "Enum entry 10");
-} else {
-  is(names[9], "@@iterator", "Enum entry 10");
-  is(names[10], "length", "Enum entry 11");
-}
+is(names[9], "length", "Enum entry 10");
 
 names = Object.getOwnPropertyNames(x);
 is(names.length, 11, "Should have 11 items");
 is(names[0], "0", "Entry 1")
 is(names[1], "1", "Entry 2")
 is(names[2], "2", "Entry 3")
 is(names[3], "3", "Entry 4")
 is(names[4], "4", "Entry 5")
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -1202,25 +1202,53 @@ function replaceSurroundingText(element,
     // Insert the text to be replaced with.
     editor.insertText(text);
   }
   return true;
 }
 
 let CompositionManager =  {
   _isStarted: false,
+  _textInputProcessor: null,
   _clauseAttrMap: {
     'raw-input':
-      Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT,
+      Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE,
     'selected-raw-text':
-      Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDRAWTEXT,
+      Ci.nsITextInputProcessor.ATTR_SELECTED_RAW_CLAUSE,
     'converted-text':
-      Ci.nsICompositionStringSynthesizer.ATTR_CONVERTEDTEXT,
+      Ci.nsITextInputProcessor.ATTR_CONVERTED_CLAUSE,
     'selected-converted-text':
-      Ci.nsICompositionStringSynthesizer.ATTR_SELECTEDCONVERTEDTEXT
+      Ci.nsITextInputProcessor.ATTR_SELECTED_CLAUSE
+  },
+
+  _callback: function cm_callback(aTIP, aNotification)
+  {
+    try {
+      switch (aNotification.type) {
+        case "request-to-commit":
+          aTIP.commitComposition();
+          break;
+        case "request-to-cancel":
+          aTIP.cancelComposition();
+          break;
+      }
+    } catch (e) {
+      return false;
+    }
+    return true;
+  },
+
+  _prepareTextInputProcessor: function cm_prepareTextInputProcessor(aWindow)
+  {
+    if (!this._textInputProcessor) {
+      this._textInputProcessor =
+        Cc["@mozilla.org/text-input-processor;1"].
+          createInstance(Ci.nsITextInputProcessor);
+    }
+    return this._textInputProcessor.init(aWindow, this._callback);
   },
 
   setComposition: function cm_setComposition(element, text, cursor, clauses) {
     // Check parameters.
     if (!element) {
       return;
     }
     let len = text.length;
@@ -1237,52 +1265,53 @@ let CompositionManager =  {
           // Make sure the total clauses length is not bigger than that of the
           // composition string.
           if (clauseLength > remainingLength) {
             clauseLength = remainingLength;
           }
           remainingLength -= clauseLength;
           clauseLens.push(clauseLength);
           clauseAttrs.push(this._clauseAttrMap[clauses[i].selectionType] ||
-                           Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
+                           Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
         }
       }
       // If the total clauses length is less than that of the composition
       // string, extend the last clause to the end of the composition string.
       if (remainingLength > 0) {
         clauseLens[clauseLens.length - 1] += remainingLength;
       }
     } else {
       clauseLens.push(len);
-      clauseAttrs.push(Ci.nsICompositionStringSynthesizer.ATTR_RAWINPUT);
-    }
-
-    // Start composition if need to.
-    if (!this._isStarted) {
-      this._isStarted = true;
-      domWindowUtils.sendCompositionEvent('compositionstart', '', '');
+      clauseAttrs.push(Ci.nsITextInputProcessor.ATTR_RAW_CLAUSE);
     }
 
+    let win = element.ownerDocument.defaultView;
+    if (!this._prepareTextInputProcessor(win)) {
+      return;
+    }
     // Update the composing text.
-    let compositionString = domWindowUtils.createCompositionStringSynthesizer();
-    compositionString.setString(text);
+    this._textInputProcessor.setPendingCompositionString(text);
     for (var i = 0; i < clauseLens.length; i++) {
-      compositionString.appendClause(clauseLens[i], clauseAttrs[i]);
+      if (!clauseLens[i]) {
+        continue;
+      }
+      this._textInputProcessor.appendClauseToPendingComposition(clauseLens[i],
+                                                                clauseAttrs[i]);
     }
     if (cursor >= 0) {
-      compositionString.setCaret(cursor, 0);
+      this._textInputProcessor.setCaretInPendingComposition(cursor);
     }
-    compositionString.dispatchEvent();
+    this._isStarted = this._textInputProcessor.flushPendingComposition();
   },
 
   endComposition: function cm_endComposition(text) {
     if (!this._isStarted) {
       return;
     }
-    domWindowUtils.sendCompositionEvent('compositioncommit', text, '');
+    this._textInputProcessor.commitComposition(text ? text : "");
     this._isStarted = false;
   },
 
   // Composition ends due to external actions.
   onCompositionEnd: function cm_onCompositionEnd() {
     if (!this._isStarted) {
       return;
     }
--- a/dom/interfaces/base/moz.build
+++ b/dom/interfaces/base/moz.build
@@ -2,17 +2,16 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_SOURCES += [
     'domstubs.idl',
     'nsIBrowserDOMWindow.idl',
-    'nsICompositionStringSynthesizer.idl',
     'nsIContentPermissionPrompt.idl',
     'nsIContentPrefService.idl',
     'nsIContentPrefService2.idl',
     'nsIContentURIGrouper.idl',
     'nsIDOMChromeWindow.idl',
     'nsIDOMClientRect.idl',
     'nsIDOMClientRectList.idl',
     'nsIDOMConstructor.idl',
@@ -31,12 +30,14 @@ XPIDL_SOURCES += [
     'nsIFrameRequestCallback.idl',
     'nsIIdleObserver.idl',
     'nsIQueryContentEventResult.idl',
     'nsIRemoteBrowser.idl',
     'nsIServiceWorkerManager.idl',
     'nsIStructuredCloneContainer.idl',
     'nsITabChild.idl',
     'nsITabParent.idl',
+    'nsITextInputProcessor.idl',
+    'nsITextInputProcessorCallback.idl',
 ]
 
 XPIDL_MODULE = 'dom_base'
 
deleted file mode 100644
--- a/dom/interfaces/base/nsICompositionStringSynthesizer.idl
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-/**
- * Stores composition clauses information and caret information for synthesizing
- * composition string.
- */
-
-[scriptable, uuid(9a7d7851-8c0a-4061-9edc-60d6693f86c9)]
-interface nsICompositionStringSynthesizer : nsISupports
-{
-  /**
-   * Set composition string or committed string.
-   */
-  void setString(in AString aString);
-
-  // NOTE: These values must be same to NS_TEXTRANGE_* in TextEvents.h
-  const unsigned long ATTR_RAWINPUT              = 0x02;
-  const unsigned long ATTR_SELECTEDRAWTEXT       = 0x03;
-  const unsigned long ATTR_CONVERTEDTEXT         = 0x04;
-  const unsigned long ATTR_SELECTEDCONVERTEDTEXT = 0x05;
-
-  /**
-   * Append a clause.
-   *
-   * TODO: Should be able to specify custom clause style.
-   */
-  void appendClause(in unsigned long aLength,
-                    in unsigned long aAttribute);
-
-  /**
-   * Set caret information.
-   */
-  void setCaret(in unsigned long aOffset,
-                in unsigned long aLength);
-
-  /**
-   * Synthesize composition string with given information by dispatching
-   * a proper event.
-   *
-   * If clauses have never been set, this dispatches a commit event.
-   * If clauses are not filled all over the composition string, this throw an
-   * error.
-   *
-   * After dispatching event, this clears all the information about the
-   * composition string. So, you can reuse this instance.
-   */
-  bool dispatchEvent();
-};
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -41,22 +41,21 @@ interface nsIQueryContentEventResult;
 interface nsIDOMWindow;
 interface nsIDOMBlob;
 interface nsIDOMFile;
 interface nsIFile;
 interface nsIDOMClientRect;
 interface nsIURI;
 interface nsIDOMEventTarget;
 interface nsIRunnable;
-interface nsICompositionStringSynthesizer;
 interface nsITranslationNodeList;
 interface nsIJSRAIIHelper;
 interface nsIContentPermissionRequest;
 
-[scriptable, uuid(0281e107-394d-4c96-830b-2f830b07c6c0)]
+[scriptable, uuid(04db2684-f9ed-4d70-827d-3d5b87825238)]
 interface nsIDOMWindowUtils : nsISupports {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -1031,43 +1030,16 @@ interface nsIDOMWindowUtils : nsISupport
    *        "copy", "paste", "delete", "undo", "redo", or "pasteTransferable".
    * @param aTransferable an instance of nsITransferable when aType is
    *        "pasteTransferable"
    */
   void sendContentCommandEvent(in AString aType,
                                [optional] in nsITransferable aTransferable);
 
   /**
-   * Synthesize a composition event to the window.
-   *
-   * Cannot be accessed from unprivileged context (not content-accessible)
-   * Will throw a DOM security error if called without chrome privileges.
-   *
-   * @param aType     The event type: "compositionstart",
-   *                  "compositioncommitasis", or "compositioncommit".
-   * @param aData     The data property value.  Note that this isn't applied
-   *                  for compositionstart event because its value is the
-   *                  selected text which is automatically computed. And also
-   *                  this isn't applied for compositioncommitasis because
-   *                  the last data will be set automatically.
-   * @param aLocale   The locale property value.
-   */
-  void sendCompositionEvent(in AString aType,
-                            in AString aData,
-                            in AString aLocale);
-
-  /**
-   * Creating synthesizer of composition string on the window.
-   *
-   * Cannot be accessed from unprivileged context (not content-accessible)
-   * Will throw a DOM security error if called without chrome privileges.
-   */
-  nsICompositionStringSynthesizer createCompositionStringSynthesizer();
-
-  /**
    * If sendQueryContentEvent()'s aAdditionalFlags argument is
    * QUERY_CONTENT_FLAG_USE_XP_LINE_BREAK, plain text generated from content
    * is created with "\n".
    * Otherwise, platform dependent.  E.g., on Windows, "\r\n" is used.
    * aOffset and aLength are offset and length in/of the plain text content.
    * This flag also affects the result values such as offset, length and string.
    */
   const unsigned long QUERY_CONTENT_FLAG_USE_NATIVE_LINE_BREAK = 0x0000;
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsITextInputProcessor.idl
@@ -0,0 +1,274 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsIDOMWindow;
+interface nsITextInputProcessorCallback;
+
+/**
+ * An nsITextInputProcessor instance is associated with a top level widget which
+ * handles native IME.  It's associated by calling init() or initForTests().
+ * While an instance has composition, nobody can steal the rights to make
+ * composition on the top level widget.  In other words, if another instance is
+ * composing on a top level widget, either init() or initForTests() returns
+ * false (i.e., not throws an exception).
+ *
+ * NOTE: See nsITextInputProcessorCallback.idl for examples of |callback| in
+ *       following examples,
+ *
+ * Example #1 JS-IME can start composition like this:
+ *
+ *   var TIP = Components.classes["@mozilla.org/text-input-processor;1"].
+ *               createInstance(Components.interfaces.nsITextInputProcessor);
+ *   if (!TIP.init(window, callback)) {
+ *     return; // You failed to get the rights to make composition
+ *   }
+ *   // Set new composition string first
+ *   TIP.setPendingCompositionString("some-words-are-inputted");
+ *   // Set clause information.
+ *   TIP.appendClauseToPendingComposition(23, TIP.ATTR_RAW_CLAUSE);
+ *   // Set caret position, this is optional.
+ *   TIP.setCaretInPendingComposition(23);
+ *   // Flush the pending composition
+ *   if (!TIP.flushPendingComposition()) {
+ *     // If it returns false, it fails to start composition.
+ *     return;
+ *   }
+ *
+ * Example #2 JS-IME can separate composition string to two or more clauses:
+ *
+ *   // First, set composition string again
+ *   TIP.setPendingCompositionString("some-words-are-inputted");
+ *   // Then, if "are" is selected to convert, there are 3 clauses:
+ *   TIP.appendClauseToPendingComposition(11, TIP.ATTR_CONVERTED_CLAUSE);
+ *   TIP.appendClauseToPendingComposition(3,  TIP.ATTR_SELECTED_CLAUSE);
+ *   TIP.appendClauseToPendingComposition(9,  TIP.ATTR_CONVERTED_CLAUSE);
+ *   // Show caret at the beginning of the selected clause
+ *   TIP.setCaretInPendingComposition(11);
+ *   // Flush the pending composition.  Note that if there is a composition,
+ *   // flushPendingComposition() won't return false.
+ *   TIP.flushPendingComposition();
+ *
+ * Example #3 JS-IME can commit composition with specific string with this:
+ *
+ *   // First, there is a composition.
+ *   TIP.setPendingCompositionString("some-words-directly-inputted");
+ *   TIP.appendClauseToPendingComposition(28, TIP.ATTR_RAW_CLAUSE);
+ *   TIP.flushPendingComposition();
+ *   // This is useful when user selects a commit string from candidate list UI
+ *   // which is provided by JS-IME.
+ *   TIP.commitComposition("selected-words-from-candidate-list");
+ *
+ * Example #4 JS-IME can commit composition with the last composition string
+ *            without specifying commit string:
+ *
+ *   // First, there is a composition.
+ *   TIP.setPendingCompositionString("some-words-will-be-commited");
+ *   TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE);
+ *   TIP.flushPendingComposition();
+ *   // This is useful when user just type Enter key.
+ *   TIP.commitComposition();
+ *
+ * Example #5 JS-IME can cancel composition with this:
+ *
+ *   // First, there is a composition.
+ *   TIP.setPendingCompositionString("some-words-will-be-canceled");
+ *   TIP.appendClauseToPendingComposition(27, TIP.ATTR_RAW_CLAUSE);
+ *   TIP.flushPendingComposition();
+ *   // This is useful when user doesn't want to commit the composition.
+ *   // FYI: This is same as TIP.commitComposition("") for now.
+ *   TIP.cancelComposition();
+ *
+ * Example #6 JS-IME can insert text only with commitComposition():
+ *
+ *   if (!TIP.init(window, callback)) {
+ *     return; // You failed to get the rights to make composition
+ *   }
+ *   TIP.commitComposition("Some words");
+ *
+ * Example #7 JS-IME can start composition explicitly:
+ *
+ *   if (!TIP.init(window, callback)) {
+ *     return; // You failed to get the rights to make composition
+ *   }
+ *   // If JS-IME don't want to show composing string in the focused editor,
+ *   // JS-IME can dispatch only compositionstart event with this.
+ *   if (!TIP.startComposition()) {
+ *     // Failed to start composition.
+ *     return;
+ *   }
+ *   // And when user selects a result from UI of JS-IME, commit with it.
+ *   TIP.commitComposition("selected-words");
+ */
+
+[scriptable, builtinclass, uuid(8c20753c-8339-4e9c-86c5-ae30f1b456c3)]
+interface nsITextInputProcessor : nsISupports
+{
+  /**
+   * When you create an instance, you must call init() first except when you
+   * created the instance for automated tests.
+   *
+   * @param aWindow         A DOM window.  The instance will look for a top
+   *                        level widget from this.
+   * @param aCallback       Callback interface which handles requests to
+   *                        IME and notifications to IME.  This must not be
+   *                        null.
+   * @return                If somebody uses internal text input service for a
+   *                        composition, this returns false.  Otherwise, returns
+   *                        true.  I.e., only your TIP can create composition
+   *                        when this returns true.  If this returns false,
+   *                        your TIP should wait next chance.
+   */
+  boolean init(in nsIDOMWindow aWindow,
+               in nsITextInputProcessorCallback aCallback);
+
+  /**
+   * When you create an instance for automated test, you must call
+   * initForTest(), first.  See init() for more detail of this.
+   * Note that aCallback can be null.  If it's null, nsITextInputProcessor
+   * implementation will handle them automatically.
+   */
+  [optional_argc] boolean
+    initForTests(in nsIDOMWindow aWindow,
+                 [optional] in nsITextInputProcessorCallback aCallback);
+
+  /**
+   * startComposition() dispatches compositionstart event explicitly.
+   * IME does NOT need to call this typically since compositionstart event
+   * is automatically dispatched by sendPendingComposition() if
+   * compositionstart event hasn't been dispatched yet.  If this is called
+   * when compositionstart has already been dispatched, this throws an
+   * exception.
+   *
+   * @return                Returns true if composition starts normally.
+   *                        Otherwise, returns false because it might be
+   *                        canceled by the web application.
+   */
+  boolean startComposition();
+
+  /**
+   * Set new composition string.  Pending composition will be flushed by
+   * a call of flushPendingComposition().  However, if the new composition
+   * string isn't empty, you need to call appendClauseToPendingComposition() to
+   * fill all characters of aString with one or more clauses before flushing.
+   * Note that if you need to commit or cancel composition, use
+   * commitComposition() or cancelComposition().
+   */
+  void setPendingCompositionString(in DOMString aString);
+
+  // ATTR_RAW_CLAUSE means that the clause hasn't been selected nor converted
+  // yet.
+  const unsigned long ATTR_RAW_CLAUSE           = 0x02;
+  // ATTR_SELECTED_RAW_CLAUSE means that the clause hasn't been converted yet
+  // but is selected for converting to the other string.
+  const unsigned long ATTR_SELECTED_RAW_CLAUSE  = 0x03;
+  // ATTR_CONVERTED_CLAUSE means that the clause has already been converted but
+  // is not selected.  This does NOT mean that this clause isn't modifiable.
+  const unsigned long ATTR_CONVERTED_CLAUSE     = 0x04;
+  // ATTR_SELECTED_CLAUSE means that the clause has already been converted and
+  // is selected.  In other words, the clause is being converted.
+  const unsigned long ATTR_SELECTED_CLAUSE      = 0x05;
+
+  /**
+   * Append a clause to the pending composition.
+   *
+   * If you need to fill the pending composition string with a clause, you
+   * should call this once.  For example:
+   *   appendClauseToPendingComposition(compositionString.length,
+   *                                    ATTR_RAW_CLAUSE);
+   * is enough.  If you need to separate the pending composition string to
+   * multiple clauses, you need to call this multiple times. For example,
+   * if your pending composition string has three clauses and the second clause
+   * is being converted:
+   *  appendClauseToPendingComposition(firstClauseLength,
+   *                                   ATTR_CONVERTED_CLAUSE);
+   *  appendClauseToPendingComposition(secondClauseLength,
+   *                                   ATTR_SELECTED_CLAUSE);
+   *  appendClauseToPendingComposition(thirdClauseLength,
+   *                                   ATTR_CONVERTED_CLAUSE);
+   * Note that if sum of aLength mismatches length of the pending composition
+   * string, flushPendingComposition() will throw an exception.  I.e.,
+   * |firstClauseLength + secondClauseLength + thirdClauseLength| must be
+   * same as the length of pending composition string.
+   *
+   * TODO: Should be able to specify custom clause style.
+   *
+   * @param aLength         Length of the clause.
+   * @param aAttribute      One of ATTR_* constants.
+   */
+  void appendClauseToPendingComposition(in unsigned long aLength,
+                                        in unsigned long aAttribute);
+
+  /**
+   * Set caret offset in the pending composition string.  If you don't need to
+   * show a caret, you don't need to call this.
+   *
+   * @param aOffset         Caret offset in the pending composition string.
+   *                        This must be between 0 and length of the pending
+   *                        composition string.
+   */
+  void setCaretInPendingComposition(in unsigned long aOffset);
+
+  /**
+   * flushPendingComposition() must be called after
+   * setPendingCompositionString() and appendClauseToPendingComposition()
+   * (setCaretInPendingComposition() is optional) are called.
+   *
+   * Note that compositionstart will be automatically dispatched if this is
+   * called when there is no composition.
+   *
+   * Note that if sum of lengths of appended clauses are not same as composition
+   * string or caret offset is larger than the composition string length, this
+   * throws an exception.
+   *
+   * @return                Returns true if there is a composition already or
+   *                        starting composition automatically.
+   *                        Otherwise, i.e., if it cannot start composition
+   *                        automatically, e.g., canceled by web apps, returns
+   *                        false.
+   */
+  boolean flushPendingComposition();
+
+  /**
+   * commitComposition() will commit composition.
+   * If there is a composition which is started by the instance, this commits
+   * the composition.  Otherwise, throws an exception.
+   *
+   * Note that if you tries to commit composition without specifying commit
+   * string when there is no composition, this throws an exception.
+   *
+   * @param aCommitString   This is optional.  If this is not specified,
+   *                        composition will be committed with the last
+   *                        composing string.  Otherwise, committed with
+   *                        the specified string.
+   * @return                Returns true if there is a composition already or
+   *                        starting composition automatically.
+   *                        Otherwise, i.e., if it cannot start composition
+   *                        automatically, e.g., canceled by web apps, returns
+   *                        false.
+   */
+  [optional_argc]
+    boolean commitComposition([optional] in DOMString aCommitString);
+
+  /**
+   * cancelComposition() will cancel composition.  This is for now the same as
+   * calling commitComposition("").  However, in the future, this might work
+   * better.  If your IME needs to cancel composition, use this instead of
+   * commitComposition().
+   *
+   * Note that if you tries to cancel composition when there is no composition,
+   * this throws an exception.
+   */
+  void cancelComposition();
+};
+
+%{C++
+#define TEXT_INPUT_PROCESSOR_CID \
+  { 0xcaaab47f, 0x1e31, 0x478e, \
+    { 0x89, 0x19, 0x97, 0x09, 0x04, 0xe9, 0xcb, 0x72 } }
+#define TEXT_INPUT_PROCESSOR_CONTRACTID \
+  "@mozilla.org/text-input-processor;1"
+%}
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/base/nsITextInputProcessorCallback.idl
@@ -0,0 +1,102 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsISupports.idl"
+
+interface nsITextInputProcessor;
+
+/**
+ * nsITextInputProcessorNotification stores the type of notification to IME and
+ * its detail.  See each explanation of attribute for the detail.
+ */
+
+[scriptable, builtinclass, uuid(c0ce1add-82bb-45ab-b99a-42cfba7fd5d7)]
+interface nsITextInputProcessorNotification : nsISupports
+{
+  /**
+   * type attribute represents what's notified or requested.  Value must be
+   * one of following values:
+   *
+   * "request-to-commit"  (required to be handled)
+   *   This is requested when Gecko believes that active composition should be
+   *   committed.  nsITextInputProcessorCallback::onNotify() has to handle this
+   *   notification.
+   *
+   * "request-to-cancel" (required to be handled)
+   *   This is requested when Gecko believes that active composition should be
+   *   canceled.  I.e., composition should be committed with empty string.
+   *   nsITextInputProcessorCallback::onNotify() has to handle this
+   *   notification.
+   *
+   * "notify-detached" (optional)
+   *   This is notified when the callback is detached from
+   *   nsITextInputProcessor.
+   *
+   * "notify-focus" (optional)
+   *   This is notified when an editable editor gets focus and Gecko starts
+   *   to observe changes in the content. E.g., selection changes.
+   *   IME shouldn't change DOM tree, focus nor something when this is notified.
+   *
+   * "notify-blur" (optional)
+   *   This is notified when an editable editor loses focus and Gecko stops
+   *   observing the changes in the content.
+   */
+  readonly attribute ACString type;
+};
+
+/**
+ * nsITextInputProcessorCallback is a callback interface for JS to implement
+ * IME.  IME implemented by JS can implement onNotify() function and must send
+ * it to nsITextInputProcessor at initializing.  Then, onNotify() will be
+ * called with nsITextInputProcessorNotification instance.
+ * The reason why onNotify() uses string simply is that if we will support
+ * other notifications such as text changes and selection changes, we need to
+ * notify IME of some other information.  Then, only changing
+ * nsITextInputProcessorNotification interface is better for compatibility.
+ */
+
+[scriptable, function, uuid(23d5f242-adb5-46f1-8766-90d1bf0383df)]
+interface nsITextInputProcessorCallback : nsISupports
+{
+  /**
+   * When Gecko notifies IME of something or requests something to IME,
+   * this is called.
+   *
+   * @param aTextInputProcessor Reference to the nsITextInputProcessor service
+   *                            which is the original receiver of the request
+   *                            or notification.
+   * @param aNotification       Stores type of notifications and additional
+   *                            information.
+   * @return                    Return true if it succeeded or does nothing.
+   *                            Otherwise, return false.
+   *
+   * Example #1 The simplest implementation of nsITextInputProcessorCallback is:
+   *
+   *   function simpleCallback(aTIP, aNotification)
+   *   {
+   *     try {
+   *       switch (aNotification.type) {
+   *         case "request-to-commit":
+   *           aTIP.commitComposition();
+   *           break;
+   *         case "request-to-cancel":
+   *           aTIP.cancelComposition();
+   *           break;
+   *       }
+   *     } catch (e) {
+   *       return false;
+   *     }
+   *     return true;
+   *   }
+   *
+   *   var TIP = Components.classes["@mozilla.org/text-input-processor;1"].
+   *               createInstance(Components.interfaces.nsITextInputProcessor);
+   *   if (!TIP.init(window, simpleCallback)) {
+   *     return;
+   *   }
+   */
+  boolean onNotify(in nsITextInputProcessor aTextInputProcessor,
+                   in nsITextInputProcessorNotification aNotification);
+};
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -71,16 +71,17 @@
 #include "LoadContext.h"
 #include "nsNetCID.h"
 #include "nsIAuthInformation.h"
 #include "nsIAuthPromptCallback.h"
 #include "nsAuthInformationHolder.h"
 #include "nsICancelable.h"
 #include "gfxPrefs.h"
 #include "nsILoginManagerPrompter.h"
+#include "nsPIWindowRoot.h"
 #include <algorithm>
 
 using namespace mozilla::dom;
 using namespace mozilla::ipc;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::services;
 using namespace mozilla::widget;
@@ -311,17 +312,37 @@ TabParent::RemoveTabParentFromTable(uint
     delete sLayerToTabParentTable;
     sLayerToTabParentTable = nullptr;
   }
 }
 
 void
 TabParent::SetOwnerElement(Element* aElement)
 {
+  // If we held previous content then unregister for its events.
+  if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) {
+    nsCOMPtr<nsPIDOMWindow> window = mFrameElement->OwnerDoc()->GetWindow();
+    nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
+    if (eventTarget) {
+      eventTarget->RemoveEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
+                                       this, false);
+    }
+  }
+
+  // Update to the new content, and register to listen for events from it.
   mFrameElement = aElement;
+  if (mFrameElement && mFrameElement->OwnerDoc()->GetWindow()) {
+    nsCOMPtr<nsPIDOMWindow> window = mFrameElement->OwnerDoc()->GetWindow();
+    nsCOMPtr<EventTarget> eventTarget = window->GetTopWindowRoot();
+    if (eventTarget) {
+      eventTarget->AddEventListener(NS_LITERAL_STRING("MozUpdateWindowPos"),
+                                    this, false, false);
+    }
+  }
+
   TryCacheDPIAndScale();
 }
 
 void
 TabParent::GetAppType(nsAString& aOut)
 {
   aOut.Truncate();
   nsCOMPtr<Element> elem = do_QueryInterface(mFrameElement);
@@ -347,16 +368,18 @@ TabParent::IsVisible()
 
 void
 TabParent::Destroy()
 {
   if (mIsDestroyed) {
     return;
   }
 
+  SetOwnerElement(nullptr);
+
   // If this fails, it's most likely due to a content-process crash,
   // and auto-cleanup will kick in.  Otherwise, the child side will
   // destroy itself and send back __delete__().
   unused << SendDestroy();
 
   if (RenderFrameParent* frame = GetRenderFrame()) {
     RemoveTabParentFromTable(frame->GetLayersId());
     frame->Destroy();
@@ -795,18 +818,19 @@ TabParent::UpdateDimensions(const nsIntR
   ScreenOrientation orientation = config.orientation();
 
   if (!mUpdatedDimensions || mOrientation != orientation ||
       mDimensions != size || !mRect.IsEqualEdges(rect)) {
     mUpdatedDimensions = true;
     mRect = rect;
     mDimensions = size;
     mOrientation = orientation;
-
-    unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, aChromeDisp);
+    mChromeDisp = aChromeDisp;
+
+    unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, mChromeDisp);
   }
 }
 
 void
 TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   if (!mIsDestroyed) {
     unused << SendUpdateFrame(aFrameMetrics);
@@ -2545,16 +2569,37 @@ TabParent::AllocPPluginWidgetParent()
 
 bool
 TabParent::DeallocPPluginWidgetParent(mozilla::plugins::PPluginWidgetParent* aActor)
 {
   delete aActor;
   return true;
 }
 
+nsresult
+TabParent::HandleEvent(nsIDOMEvent* aEvent)
+{
+  nsAutoString eventType;
+  aEvent->GetType(eventType);
+
+  if (eventType.EqualsLiteral("MozUpdateWindowPos")) {
+    // This event is sent when the widget moved.  Therefore we only update
+    // the position.
+    nsRefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+    if (!frameLoader) {
+      return NS_OK;
+    }
+    nsIntRect windowDims;
+    NS_ENSURE_SUCCESS(frameLoader->GetWindowDimensions(windowDims), NS_ERROR_FAILURE);
+    UpdateDimensions(windowDims, mDimensions, mChromeDisp);
+    return NS_OK;
+  }
+  return NS_OK;
+}
+
 class FakeChannel MOZ_FINAL : public nsIChannel,
                               public nsIAuthPromptCallback,
                               public nsIInterfaceRequestor,
                               public nsILoadContext
 {
 public:
   FakeChannel(const nsCString& aUri, uint64_t aCallbackId, Element* aElement)
     : mCallbackId(aCallbackId)
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -17,16 +17,17 @@
 #include "nsIBrowserDOMWindow.h"
 #include "nsISecureBrowserUI.h"
 #include "nsITabParent.h"
 #include "nsIXULBrowserWindow.h"
 #include "nsWeakReference.h"
 #include "Units.h"
 #include "WritingModes.h"
 #include "js/TypeDecls.h"
+#include "nsIDOMEventListener.h"
 
 class nsFrameLoader;
 class nsIContent;
 class nsIPrincipal;
 class nsIURI;
 class nsIWidget;
 class nsILoadContext;
 class nsIDocShell;
@@ -52,17 +53,18 @@ struct IMENotification;
 
 namespace dom {
 
 class ClonedMessageData;
 class nsIContentParent;
 class Element;
 struct StructuredCloneData;
 
-class TabParent : public PBrowserParent 
+class TabParent : public PBrowserParent
+                , public nsIDOMEventListener
                 , public nsITabParent 
                 , public nsIAuthPromptProvider
                 , public nsISecureBrowserUI
                 , public nsSupportsWeakReference
                 , public TabContext
 {
     typedef mozilla::dom::ClonedMessageData ClonedMessageData;
     typedef mozilla::layout::ScrollingBehavior ScrollingBehavior;
@@ -93,16 +95,19 @@ public:
      */
     bool IsVisible();
 
     nsIBrowserDOMWindow *GetBrowserDOMWindow() { return mBrowserDOMWindow; }
     void SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserDOMWindow) {
         mBrowserDOMWindow = aBrowserDOMWindow;
     }
 
+    // nsIDOMEventListener interfaces 
+    NS_DECL_NSIDOMEVENTLISTENER
+
     already_AddRefed<nsILoadContext> GetLoadContext();
 
     nsIXULBrowserWindow* GetXULBrowserWindow();
 
     /**
      * Return the TabParent that has decided it wants to capture an
      * event series for fast-path dispatch to its subprocess, if one
      * has.
@@ -420,16 +425,17 @@ protected:
     nsIntRect mIMEEditorRect;
 
     // The number of event series we're currently capturing.
     int32_t mEventCaptureDepth;
 
     nsIntRect mRect;
     nsIntSize mDimensions;
     ScreenOrientation mOrientation;
+    nsIntPoint mChromeDisp;
     float mDPI;
     CSSToLayoutDeviceScale mDefaultScale;
     bool mShown;
     bool mUpdatedDimensions;
 
 private:
     already_AddRefed<nsFrameLoader> GetFrameLoader() const;
     layout::RenderFrameParent* GetRenderFrame();
--- a/dom/media/test/test_bug495300.html
+++ b/dom/media/test/test_bug495300.html
@@ -23,31 +23,36 @@ function filename(uri) {
 
 function mediaEnded(event) {
   ok(true, "Got expected 'ended' event: " + filename(event.target.currentSrc));
 
   if (event.target._expectedDuration)
     ok(Math.abs(event.target.currentTime - event.target._expectedDuration) < 0.1,
        "currentTime equals duration: " + filename(event.target.currentSrc));
 
+  event.target.removeEventListener("ended", mediaEnded, false);
   manager.finished(event.target.token);
+  removeNodeAndSource(event.target);
+}
+
+function mediaLoadedmetadata(event) {
+  event.target.currentTime = event.target.duration;
+  event.target.removeEventListener("loadedmetadata", mediaLoadedmetadata, false);
 }
 
 function startTest(test, token) {
   var elemType = /^audio/.test(test.type) ? "audio" : "video";
   var v1 = document.createElement(elemType);
   v1.preload = "auto";
 
   v1.src = test.name;
   if (test.duration) {
     v1._expectedDuration = test.duration;
   }
-  v1.addEventListener("loadedmetadata", function (event) {
-    event.target.currentTime = event.target.duration;
-  }, false);
+  v1.addEventListener("loadedmetadata", mediaLoadedmetadata, false);
   v1.addEventListener("ended", mediaEnded, false);
   v1.load();
 
   v1.token = token;
   manager.started(token);
 }
 
 manager.runTests(gSeekTests, startTest);
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -1767,22 +1767,22 @@ NPObjWrapper_ObjectMoved(JSObject *obj, 
     return;
   }
 
   NPObject *npobj = (NPObject *)::JS_GetPrivate(obj);
   if (!npobj) {
     return;
   }
 
-  // Calling PL_DHashTableLookup() will not result in GC.
+  // Calling PL_DHashTableSearch() will not result in GC.
   JS::AutoSuppressGCAnalysis nogc;
 
   NPObjWrapperHashEntry *entry = static_cast<NPObjWrapperHashEntry *>
-    (PL_DHashTableLookup(&sNPObjWrappers, npobj));
-  MOZ_ASSERT(PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj);
+    (PL_DHashTableSearch(&sNPObjWrappers, npobj));
+  MOZ_ASSERT(entry && entry->mJSObj);
   MOZ_ASSERT(entry->mJSObj == old);
   entry->mJSObj = obj;
 }
 
 static bool
 NPObjWrapper_Call(JSContext *cx, unsigned argc, JS::Value *vp)
 {
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
@@ -1824,19 +1824,19 @@ nsNPObjWrapper::OnDestroy(NPObject *npob
 
   if (!sNPObjWrappers.IsInitialized()) {
     // No hash yet (or any more), no used wrappers available.
 
     return;
   }
 
   NPObjWrapperHashEntry *entry = static_cast<NPObjWrapperHashEntry *>
-    (PL_DHashTableLookup(&sNPObjWrappers, npobj));
-
-  if (PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj) {
+    (PL_DHashTableSearch(&sNPObjWrappers, npobj));
+
+  if (entry && entry->mJSObj) {
     // Found a live NPObject wrapper, null out its JSObjects' private
     // data.
 
     ::JS_SetPrivate(entry->mJSObj, nullptr);
 
     // Remove the npobj from the hash now that it went away.
     PL_DHashTableRawRemove(&sNPObjWrappers, entry);
 
@@ -1884,17 +1884,17 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JS
 
   if (!entry) {
     // Out of memory
     JS_ReportOutOfMemory(cx);
 
     return nullptr;
   }
 
-  if (PL_DHASH_ENTRY_IS_BUSY(entry) && entry->mJSObj) {
+  if (entry->mJSObj) {
     // Found a live NPObject wrapper. It may not be in the same compartment
     // as cx, so we need to wrap it before returning it.
     JS::Rooted<JSObject*> obj(cx, entry->mJSObj);
     if (!JS_WrapObject(cx, &obj)) {
       return nullptr;
     }
     return obj;
   }
@@ -1908,19 +1908,17 @@ nsNPObjWrapper::GetNewOrUsed(NPP npp, JS
 
   JS::Rooted<JSObject*> obj(cx, ::JS_NewObject(cx, js::Jsvalify(&sNPObjectJSWrapperClass),
                                                JS::NullPtr(), JS::NullPtr()));
 
   if (generation != sNPObjWrappers.Generation()) {
       // Reload entry if the JS_NewObject call caused a GC and reallocated
       // the table (see bug 445229). This is guaranteed to succeed.
 
-      entry = static_cast<NPObjWrapperHashEntry *>
-        (PL_DHashTableLookup(&sNPObjWrappers, npobj));
-      NS_ASSERTION(entry && PL_DHASH_ENTRY_IS_BUSY(entry),
+      NS_ASSERTION(PL_DHashTableSearch(&sNPObjWrappers, npobj),
                    "Hashtable didn't find what we just added?");
   }
 
   if (!obj) {
     // OOM? Remove the stale entry from the hash.
 
     PL_DHashTableRawRemove(&sNPObjWrappers, entry);
 
@@ -2043,17 +2041,17 @@ LookupNPP(NPObject *npobj)
   if (npobj->_class == &nsJSObjWrapper::sJSObjWrapperNPClass) {
     nsJSObjWrapper* o = static_cast<nsJSObjWrapper*>(npobj);
     return o->mNpp;
   }
 
   NPObjWrapperHashEntry *entry = static_cast<NPObjWrapperHashEntry *>
     (PL_DHashTableAdd(&sNPObjWrappers, npobj));
 
-  if (PL_DHASH_ENTRY_IS_FREE(entry)) {
+  if (!entry) {
     return nullptr;
   }
 
   NS_ASSERTION(entry->mNpp, "Live NPObject entry w/o an NPP!");
 
   return entry->mNpp;
 }
 
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -784,28 +784,27 @@ NPBool nsPluginInstanceOwner::ConvertPoi
   nsPoint windowPosition = AsNsPoint(rootWidget->GetWindowPosition()) / scaleFactor;
 
   // Window size is tab size + chrome size.
   nsIntRect tabContentBounds;
   NS_ENSURE_SUCCESS(puppetWidget->GetBounds(tabContentBounds), false);
   tabContentBounds.ScaleInverseRoundOut(scaleFactor);
   int32_t windowH = tabContentBounds.height + int(chromeSize.y);
 
-  // This is actually relative to window-chrome.
   nsPoint pluginPosition = AsNsPoint(pluginFrame->GetScreenRect().TopLeft());
 
   // Convert (sourceX, sourceY) to 'real' (not PuppetWidget) screen space.
   // In OSX, the Y-axis increases upward, which is the reverse of ours.
   // We want OSX coordinates for window and screen so those equations are swapped.
   nsPoint sourcePoint(sourceX, sourceY);
   nsPoint screenPoint;
   switch (sourceSpace) {
     case NPCoordinateSpacePlugin:
-      screenPoint = sourcePoint + pluginFrame->GetContentRectRelativeToSelf().TopLeft() +
-        chromeSize + pluginPosition + windowPosition;
+      screenPoint = sourcePoint + pluginPosition +
+        pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel();
       break;
     case NPCoordinateSpaceWindow:
       screenPoint = nsPoint(sourcePoint.x, windowH-sourcePoint.y) +
         windowPosition;
       break;
     case NPCoordinateSpaceFlippedWindow:
       screenPoint = sourcePoint + windowPosition;
       break;
@@ -818,18 +817,18 @@ NPBool nsPluginInstanceOwner::ConvertPoi
     default:
       return false;
   }
 
   // Convert from screen to dest space.
   nsPoint destPoint;
   switch (destSpace) {
     case NPCoordinateSpacePlugin:
-      destPoint = screenPoint - pluginFrame->GetContentRectRelativeToSelf().TopLeft() -
-        chromeSize - pluginPosition - windowPosition;
+      destPoint = screenPoint - pluginPosition -
+        pluginFrame->GetContentRectRelativeToSelf().TopLeft() / nsPresContext::AppUnitsPerCSSPixel();
       break;
     case NPCoordinateSpaceWindow:
       destPoint = screenPoint - windowPosition;
       destPoint.y = windowH - destPoint.y;
       break;
     case NPCoordinateSpaceFlippedWindow:
       destPoint = screenPoint - windowPosition;
       break;
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -407,16 +407,17 @@ PluginModuleChromeParent::LoadModule(con
     bool launched = parent->mSubprocess->Launch(Move(onLaunchedRunnable),
                                                 enableSandbox);
     if (!launched) {
         // We never reached open
         parent->mShutdown = true;
         return nullptr;
     }
     parent->mIsFlashPlugin = aPluginTag->mIsFlashPlugin;
+    parent->mIsBlocklisted = aPluginTag->GetBlocklistState() != 0;
     if (!parent->mIsStartingAsync) {
         int32_t launchTimeoutSecs = Preferences::GetInt(kLaunchTimeoutPref, 0);
         if (!parent->mSubprocess->WaitUntilConnected(launchTimeoutSecs * 1000)) {
             parent->mShutdown = true;
             return nullptr;
         }
     }
     TimeStamp launchEnd = TimeStamp::Now();
@@ -465,17 +466,17 @@ PluginModuleChromeParent::OnProcessLaunc
     { // Scope for lock
         mozilla::MutexAutoLock lock(mCrashReporterMutex);
         mCrashReporter = CrashReporter();
     }
 #endif
 #endif
 
 #ifdef XP_WIN
-    if (mIsFlashPlugin &&
+    if (!mIsBlocklisted && mIsFlashPlugin &&
         Preferences::GetBool("dom.ipc.plugins.flash.disable-protected-mode", false)) {
         SendDisableFlashProtectedMode();
     }
 #endif
 
     if (mInitOnAsyncConnect) {
         mInitOnAsyncConnect = false;
 #if defined(XP_WIN)
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -511,14 +511,15 @@ private:
     friend class LaunchedTask;
 
     bool                mInitOnAsyncConnect;
     nsresult            mAsyncInitRv;
     NPError             mAsyncInitError;
     dom::ContentParent* mContentParent;
     nsCOMPtr<nsIObserver> mOfflineObserver;
     bool mIsFlashPlugin;
+    bool mIsBlocklisted;
 };
 
 } // namespace plugins
 } // namespace mozilla
 
 #endif // mozilla_plugins_PluginModuleParent_h
--- a/dom/storage/DOMStorageDBThread.cpp
+++ b/dom/storage/DOMStorageDBThread.cpp
@@ -36,17 +36,17 @@ namespace dom {
 
 DOMStorageDBBridge::DOMStorageDBBridge()
 {
 }
 
 
 DOMStorageDBThread::DOMStorageDBThread()
 : mThread(nullptr)
-, mMonitor("DOMStorageThreadMonitor")
+, mThreadObserver(new ThreadObserver())
 , mStopIOThread(false)
 , mWALModeEnabled(false)
 , mDBReady(false)
 , mStatus(NS_OK)
 , mWorkerStatements(mWorkerConnection)
 , mReaderStatements(mReaderConnection)
 , mDirtyEpoch(0)
 , mFlushImmediately(false)
@@ -71,17 +71,17 @@ DOMStorageDBThread::Init()
 
   // Ensure mozIStorageService init on the main thread first.
   nsCOMPtr<mozIStorageService> service =
     do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Need to keep the lock to avoid setting mThread later then
   // the thread body executes.
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
                             PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
                             262144);
   if (!mThread) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
@@ -93,17 +93,17 @@ DOMStorageDBThread::Shutdown()
 {
   if (!mThread) {
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
 
   {
-    MonitorAutoLock monitor(mMonitor);
+    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
     // After we stop, no other operations can be accepted
     mFlushImmediately = true;
     mStopIOThread = true;
     monitor.Notify();
   }
 
   PR_JoinThread(mThread);
@@ -125,17 +125,17 @@ DOMStorageDBThread::SyncPreload(DOMStora
   }
 
   // Bypass sync load when an update is pending in the queue to write, we would
   // get incosistent data in the cache.  Also don't allow sync main-thread preload
   // when DB open and init is still pending on the background thread.
   if (mDBReady && mWALModeEnabled) {
     bool pendingTasks;
     {
-      MonitorAutoLock monitor(mMonitor);
+      MonitorAutoLock monitor(mThreadObserver->GetMonitor());
       pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
                      mPendingTasks.IsScopeClearPending(aCache->Scope());
     }
 
     if (!pendingTasks) {
       // WAL is enabled, thus do the load synchronously on the main thread.
       DBOperation preload(DBOperation::opPreload, aCache);
       preload.PerformAndFinalize(this);
@@ -152,25 +152,25 @@ DOMStorageDBThread::SyncPreload(DOMStora
   if (NS_SUCCEEDED(rv)) {
     aCache->LoadWait();
   }
 }
 
 void
 DOMStorageDBThread::AsyncFlush()
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   mFlushImmediately = true;
   monitor.Notify();
 }
 
 bool
 DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   return mScopesHavingData.Contains(aScope);
 }
 
 namespace { // anon
 
 PLDHashOperator
 GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg)
 {
@@ -180,36 +180,36 @@ GetScopesHavingDataEnum(nsCStringHashKey
   return PL_DHASH_NEXT;
 }
 
 } // anon
 
 void
 DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
   mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes);
 }
 
 nsresult
 DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
 {
-  MonitorAutoLock monitor(mMonitor);
+  MonitorAutoLock monitor(mThreadObserver->GetMonitor());
 
   // Sentinel to don't forget to delete the operation when we exit early.
   nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
 
   if (mStopIOThread) {
     // Thread use after shutdown demanded.
     MOZ_ASSERT(false);
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (NS_FAILED(mStatus)) {
-    MonitorAutoUnlock unlock(mMonitor);
+    MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
     aOperation->Finalize(mStatus);
     return mStatus;
   }
 
   switch (aOperation->Type()) {
   case DBOperation::opPreload:
   case DBOperation::opPreloadUrgent:
     if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
@@ -220,17 +220,17 @@ DOMStorageDBThread::InsertDBOp(DOMStorag
       // before the pending flush, we would have got an inconsistent cache content.
       mFlushImmediately = true;
     } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
       // The scope is scheduled to be cleared, so just quickly load as empty.
       // We need to do this to prevent load of the DB data before the scope has
       // actually been cleared from the database.  Preloads are processed
       // immediately before update and clear operations on the database that
       // are flushed periodically in batches.
-      MonitorAutoUnlock unlock(mMonitor);
+      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
       aOperation->Finalize(NS_OK);
       return NS_OK;
     }
     // NO BREAK
 
   case DBOperation::opGetUsage:
     if (aOperation->Type() == DBOperation::opPreloadUrgent) {
       SetHigherPriority(); // Dropped back after urgent preload execution
@@ -287,59 +287,111 @@ DOMStorageDBThread::ThreadFunc(void* aAr
   mozilla::IOInterposer::UnregisterCurrentThread();
 }
 
 void
 DOMStorageDBThread::ThreadFunc()
 {
   nsresult rv = InitDatabase();
 
-  MonitorAutoLock lockMonitor(mMonitor);
+  MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
 
   if (NS_FAILED(rv)) {
     mStatus = rv;
     mStopIOThread = true;
     return;
   }
 
-  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) {
+  // Create an nsIThread for the current PRThread, so we can observe runnables
+  // dispatched to it.
+  nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
+  nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
+  MOZ_ASSERT(threadInternal); // Should always succeed.
+  threadInternal->SetObserver(mThreadObserver);
+
+  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
+                    mPendingTasks.HasTasks() ||
+                    mThreadObserver->HasPendingEvents())) {
+    // Process xpcom events first.
+    while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
+      mThreadObserver->ClearPendingEvents();
+      MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
+      bool processedEvent;
+      do {
+        rv = thread->ProcessNextEvent(false, &processedEvent);
+      } while (NS_SUCCEEDED(rv) && processedEvent);
+    }
+
     if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
       // Flush time is up or flush has been forced, do it now.
       UnscheduleFlush();
       if (mPendingTasks.Prepare()) {
         {
-          MonitorAutoUnlock unlockMonitor(mMonitor);
+          MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
           rv = mPendingTasks.Execute(this);
         }
 
         if (!mPendingTasks.Finalize(rv)) {
           mStatus = rv;
           NS_WARNING("localStorage DB access broken");
         }
       }
       NotifyFlushCompletion();
     } else if (MOZ_LIKELY(mPreloads.Length())) {
       nsAutoPtr<DBOperation> op(mPreloads[0]);
       mPreloads.RemoveElementAt(0);
       {
-        MonitorAutoUnlock unlockMonitor(mMonitor);
+        MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
         op->PerformAndFinalize(this);
       }
 
       if (op->Type() == DBOperation::opPreloadUrgent) {
         SetDefaultPriority(); // urgent preload unscheduled
       }
     } else if (MOZ_UNLIKELY(!mStopIOThread)) {
       lockMonitor.Wait(TimeUntilFlush());
     }
   } // thread loop
 
   mStatus = ShutdownDatabase();
+
+  if (threadInternal) {
+    threadInternal->SetObserver(nullptr);
+  }
 }
 
+
+NS_IMPL_ISUPPORTS(DOMStorageDBThread::ThreadObserver, nsIThreadObserver)
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal *thread)
+{
+  MonitorAutoLock lock(mMonitor);
+  mHasPendingEvents = true;
+  lock.Notify();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal *thread,
+                                       bool mayWait,
+                                       uint32_t recursionDepth)
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+DOMStorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal *thread,
+                                          uint32_t recursionDepth,
+                                          bool eventWasProcessed)
+{
+  return NS_OK;
+}
+
+
 extern void
 ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
 
 namespace { // anon
 
 class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction
 {
   ~nsReverseStringSQLFunction() {}
@@ -503,17 +555,17 @@ DOMStorageDBThread::InitDatabase()
   NS_ENSURE_SUCCESS(rv, rv);
   mozStorageStatementScoper scope(stmt);
 
   while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
     nsAutoCString foundScope;
     rv = stmt->GetUTF8String(0, foundScope);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    MonitorAutoLock monitor(mMonitor);
+    MonitorAutoLock monitor(mThreadObserver->GetMonitor());
     mScopesHavingData.PutEntry(foundScope);
   }
 
   return NS_OK;
 }
 
 nsresult
 DOMStorageDBThread::SetJournalMode(bool aIsWal)
@@ -643,17 +695,17 @@ DOMStorageDBThread::ScheduleFlush()
 {
   if (mDirtyEpoch) {
     return; // Already scheduled
   }
 
   mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
 
   // Wake the monitor from indefinite sleep...
-  mMonitor.Notify();
+  (mThreadObserver->GetMonitor()).Notify();
 }
 
 void
 DOMStorageDBThread::UnscheduleFlush()
 {
   // We are just about to do the flush, drop flags
   mFlushImmediately = false;
   mDirtyEpoch = 0;
--- a/dom/storage/DOMStorageDBThread.h
+++ b/dom/storage/DOMStorageDBThread.h
@@ -10,16 +10,17 @@
 #include "prinrval.h"
 #include "nsTArray.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/storage/StatementCache.h"
 #include "nsString.h"
 #include "nsCOMPtr.h"
 #include "nsClassHashtable.h"
 #include "nsIFile.h"
+#include "nsIThreadInternal.h"
 
 class mozIStorageConnection;
 
 namespace mozilla {
 namespace dom {
 
 class DOMStorageCacheBridge;
 class DOMStorageUsageBridge;
@@ -203,16 +204,44 @@ public:
 
     // Collection of all tasks, valid only between Prepare() and Execute()
     nsTArray<nsAutoPtr<DBOperation> > mExecList;
 
     // Number of failing flush attempts
     uint32_t mFlushFailureCount;
   };
 
+  class ThreadObserver MOZ_FINAL : public nsIThreadObserver
+  {
+    NS_DECL_THREADSAFE_ISUPPORTS
+    NS_DECL_NSITHREADOBSERVER
+
+    ThreadObserver()
+      : mHasPendingEvents(false)
+      , mMonitor("DOMStorageThreadMonitor")
+    {
+    }
+
+    bool HasPendingEvents() {
+      mMonitor.AssertCurrentThreadOwns();
+      return mHasPendingEvents;
+    }
+    void ClearPendingEvents() {
+      mMonitor.AssertCurrentThreadOwns();
+      mHasPendingEvents = false;
+    }
+    Monitor& GetMonitor() { return mMonitor; }
+
+  private:
+    virtual ~ThreadObserver() {}
+    bool mHasPendingEvents;
+    // The monitor we drive the thread with
+    Monitor mMonitor;
+  };
+
 public:
   DOMStorageDBThread();
   virtual ~DOMStorageDBThread() {}
 
   virtual nsresult Init();
   virtual nsresult Shutdown();
 
   virtual void AsyncPreload(DOMStorageCacheBridge* aCache, bool aPriority = false)
@@ -245,20 +274,21 @@ public:
 
   virtual bool ShouldPreloadScope(const nsACString& aScope);
   virtual void GetScopesHavingData(InfallibleTArray<nsCString>* aScopes);
 
 private:
   nsCOMPtr<nsIFile> mDatabaseFile;
   PRThread* mThread;
 
-  // The monitor we drive the thread with
-  Monitor mMonitor;
+  // Used to observe runnables dispatched to our thread and to monitor it.
+  nsRefPtr<ThreadObserver> mThreadObserver;
 
-  // Flag to stop, protected by the monitor
+  // Flag to stop, protected by the monitor returned by
+  // mThreadObserver->GetMonitor().
   bool mStopIOThread;
 
   // Whether WAL is enabled
   bool mWALModeEnabled;
 
   // Whether DB has already been open, avoid races between main thread reads
   // and pending DB init in the background I/O thread
   bool mDBReady;
--- a/dom/system/mac/CoreLocationLocationProvider.h
+++ b/dom/system/mac/CoreLocationLocationProvider.h
@@ -27,14 +27,31 @@ class CoreLocationLocationProvider
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIGEOLOCATIONPROVIDER
 
   CoreLocationLocationProvider();
   void NotifyError(uint16_t aErrorCode);
   void Update(nsIDOMGeoPosition* aSomewhere);
+  void CreateMLSFallbackProvider();
+  void CancelMLSFallbackProvider();
+
 private:
   virtual ~CoreLocationLocationProvider() {};
 
   CoreLocationObjects* mCLObjects;
   nsCOMPtr<nsIGeolocationUpdate> mCallback;
+  nsCOMPtr<nsIGeolocationProvider> mMLSFallbackProvider;
+
+  class MLSUpdate : public nsIGeolocationUpdate
+  {
+  public:
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIGEOLOCATIONUPDATE
+
+    explicit MLSUpdate(CoreLocationLocationProvider& parentProvider);
+
+  private:
+    CoreLocationLocationProvider& mParentLocationProvider;
+    virtual ~MLSUpdate() {}
+  };
 };
--- a/dom/system/mac/CoreLocationLocationProvider.mm
+++ b/dom/system/mac/CoreLocationLocationProvider.mm
@@ -26,80 +26,148 @@
 using namespace mozilla;
 
 static const CLLocationAccuracy kHIGH_ACCURACY = kCLLocationAccuracyBest;
 static const CLLocationAccuracy kDEFAULT_ACCURACY = kCLLocationAccuracyNearestTenMeters;
 
 @interface LocationDelegate : NSObject <CLLocationManagerDelegate>
 {
   CoreLocationLocationProvider* mProvider;
+  NSTimer* mHandoffTimer;
 }
 
 - (id)init:(CoreLocationLocationProvider*)aProvider;
 - (void)locationManager:(CLLocationManager*)aManager
   didFailWithError:(NSError *)aError;
+- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)locations;
 
-/* XXX (ggp) didUpdateToLocation is supposedly deprecated in favor of
- * locationManager:didUpdateLocations, which is undocumented and didn't seem to
- * work for me. This should be changed in the future, though.
- */
-- (void)locationManager:(CLLocationManager*)aManager
-  didUpdateToLocation:(CLLocation *)aNewLocation
-  fromLocation:(CLLocation *)aOldLocation;
 @end
 
 @implementation LocationDelegate
 - (id) init:(CoreLocationLocationProvider*) aProvider
 {
   if (self = [super init]) {
     mProvider = aProvider;
   }
 
   return self;
 }
 
+- (void)shutdownHandoffTimer
+{
+  if (!mHandoffTimer) {
+    return;
+  }
+
+  [mHandoffTimer invalidate];
+  mHandoffTimer = nil;
+}
+
+- (void)handoffToGeoIPProvider
+{
+  // Single-shot timers are invalid once executed and are released by the run loop
+  mHandoffTimer = nil;
+
+  mProvider->CreateMLSFallbackProvider();
+}
+
 - (void)locationManager:(CLLocationManager*)aManager
   didFailWithError:(NSError *)aError
 {
   nsCOMPtr<nsIConsoleService> console =
     do_GetService(NS_CONSOLESERVICE_CONTRACTID);
 
   NS_ENSURE_TRUE_VOID(console);
 
   NSString* message =
     [@"Failed to acquire position: " stringByAppendingString: [aError localizedDescription]];
 
   console->LogStringMessage(NS_ConvertUTF8toUTF16([message UTF8String]).get());
 
-  uint16_t err = nsIDOMGeoPositionError::POSITION_UNAVAILABLE;
   if ([aError code] == kCLErrorDenied) {
-    err = nsIDOMGeoPositionError::PERMISSION_DENIED;
+    mProvider->NotifyError(nsIDOMGeoPositionError::PERMISSION_DENIED);
+    return;
   }
 
-  mProvider->NotifyError(err);
+  if (!mHandoffTimer) {
+    // The CL provider does not fallback to GeoIP, so use NetworkGeolocationProvider for this.
+    // The concept here is: on error, hand off geolocation to MLS, which will then report
+    // back a location or error. We can't call this with no delay however, as this method
+    // is called with an error code of 0 in both failed geolocation cases, and also when
+    // geolocation is not immediately available.
+    // The 2 sec delay is arbitrarily large enough that CL has a reasonable head start and
+    // if it is likely to succeed, it should complete before the MLS provider.
+    // Take note that in locationManager:didUpdateLocations: the handoff to MLS is stopped.
+    mHandoffTimer = [NSTimer scheduledTimerWithTimeInterval:2.0
+                                                     target:self
+                                                   selector:@selector(handoffToGeoIPProvider)
+                                                   userInfo:nil
+                                                    repeats:NO];
+  }
 }
 
-- (void)locationManager:(CLLocationManager*)aManager
-  didUpdateToLocation:(CLLocation *)aNewLocation
-  fromLocation:(CLLocation *)aOldLocation
+- (void)locationManager:(CLLocationManager*)aManager didUpdateLocations:(NSArray*)aLocations
 {
+  if (aLocations.count < 1) {
+    return;
+  }
+
+  [self shutdownHandoffTimer];
+  mProvider->CancelMLSFallbackProvider();
+
+  CLLocation* location = [aLocations objectAtIndex:0];
+
   nsCOMPtr<nsIDOMGeoPosition> geoPosition =
-    new nsGeoPosition(aNewLocation.coordinate.latitude,
-                      aNewLocation.coordinate.longitude,
-                      aNewLocation.altitude,
-                      aNewLocation.horizontalAccuracy,
-                      aNewLocation.verticalAccuracy,
-                      aNewLocation.course,
-                      aNewLocation.speed,
+    new nsGeoPosition(location.coordinate.latitude,
+                      location.coordinate.longitude,
+                      location.altitude,
+                      location.horizontalAccuracy,
+                      location.verticalAccuracy,
+                      location.course,
+                      location.speed,
                       PR_Now());
 
   mProvider->Update(geoPosition);
 }
 @end
 
+NS_IMPL_ISUPPORTS(CoreLocationLocationProvider::MLSUpdate, nsIGeolocationUpdate);
+
+CoreLocationLocationProvider::MLSUpdate::MLSUpdate(CoreLocationLocationProvider& parentProvider)
+  : mParentLocationProvider(parentProvider)
+{
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::MLSUpdate::Update(nsIDOMGeoPosition *position)
+{
+  nsCOMPtr<nsIDOMGeoPositionCoords> coords;
+  position->GetCoords(getter_AddRefs(coords));
+  if (!coords) {
+    return NS_ERROR_FAILURE;
+  }
+
+  mParentLocationProvider.Update(position);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::MLSUpdate::LocationUpdatePending()
+{
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+CoreLocationLocationProvider::MLSUpdate::NotifyError(uint16_t error)
+{
+  mParentLocationProvider.NotifyError(error);
+  return NS_OK;
+}
+
+
 class CoreLocationObjects {
 public:
   NS_METHOD Init(CoreLocationLocationProvider* aProvider) {
     mLocationManager = [[CLLocationManager alloc] init];
     NS_ENSURE_TRUE(mLocationManager, NS_ERROR_NOT_AVAILABLE);
 
     mLocationDelegate = [[LocationDelegate alloc] init:aProvider];
     NS_ENSURE_TRUE(mLocationDelegate, NS_ERROR_NOT_AVAILABLE);
@@ -122,32 +190,34 @@ public:
 
   LocationDelegate* mLocationDelegate;
   CLLocationManager* mLocationManager;
 };
 
 NS_IMPL_ISUPPORTS(CoreLocationLocationProvider, nsIGeolocationProvider)
 
 CoreLocationLocationProvider::CoreLocationLocationProvider()
-  : mCLObjects(nullptr)
+  : mCLObjects(nullptr), mMLSFallbackProvider(nullptr)
 {
 }
 
 NS_IMETHODIMP
 CoreLocationLocationProvider::Startup()
 {
   if (!mCLObjects) {
     nsAutoPtr<CoreLocationObjects> clObjs(new CoreLocationObjects());
 
     nsresult rv = clObjs->Init(this);
     NS_ENSURE_SUCCESS(rv, rv);
 
     mCLObjects = clObjs.forget();
   }
 
+  // Must be stopped before starting or response (success or failure) is not guaranteed
+  [mCLObjects->mLocationManager stopUpdatingLocation];
   [mCLObjects->mLocationManager startUpdatingLocation];
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CoreLocationLocationProvider::Watch(nsIGeolocationUpdate* aCallback)
 {
   if (mCallback) {
@@ -158,20 +228,27 @@ CoreLocationLocationProvider::Watch(nsIG
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CoreLocationLocationProvider::Shutdown()
 {
   NS_ENSURE_STATE(mCLObjects);
 
+  [mCLObjects->mLocationDelegate shutdownHandoffTimer];
   [mCLObjects->mLocationManager stopUpdatingLocation];
 
   delete mCLObjects;
   mCLObjects = nullptr;
+
+  if (mMLSFallbackProvider) {
+    mMLSFallbackProvider->Shutdown();
+    mMLSFallbackProvider = nullptr;
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CoreLocationLocationProvider::SetHighAccuracy(bool aEnable)
 {
   NS_ENSURE_STATE(mCLObjects);
 
@@ -189,8 +266,35 @@ CoreLocationLocationProvider::Update(nsI
   }
 }
 
 void
 CoreLocationLocationProvider::NotifyError(uint16_t aErrorCode)
 {
   mCallback->NotifyError(aErrorCode);
 }
+
+void
+CoreLocationLocationProvider::CreateMLSFallbackProvider()
+{
+  if (mMLSFallbackProvider) {
+    return;
+  }
+
+  mMLSFallbackProvider = do_CreateInstance("@mozilla.org/geolocation/mls-provider;1");
+  if (mMLSFallbackProvider) {
+    nsresult rv = mMLSFallbackProvider->Startup();
+    if (NS_SUCCEEDED(rv)) {
+      mMLSFallbackProvider->Watch(new CoreLocationLocationProvider::MLSUpdate(*this));
+    }
+  }
+}
+
+void
+CoreLocationLocationProvider::CancelMLSFallbackProvider()
+{
+  if (!mMLSFallbackProvider) {
+    return;
+  }
+
+  mMLSFallbackProvider->Shutdown();
+  mMLSFallbackProvider = nullptr;
+}
--- a/dom/webidl/MouseEvent.webidl
+++ b/dom/webidl/MouseEvent.webidl
@@ -101,11 +101,13 @@ partial interface MouseEvent
                                        boolean ctrlKeyArg,
                                        boolean altKeyArg,
                                        boolean shiftKeyArg,
                                        boolean metaKeyArg,
                                        short buttonArg,
                                        EventTarget? relatedTargetArg,
                                        float pressure,
                                        unsigned short inputSourceArg);
+  [ChromeOnly]
+  readonly attribute boolean hitCluster; // True when touch occurs in a cluster of links
 
 };
 
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -773,19 +773,19 @@ XULDocument::AddBroadcastListenerFor(Ele
         if (! mBroadcasterMap) {
             aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
             return;
         }
     }
 
     BroadcasterMapEntry* entry =
         static_cast<BroadcasterMapEntry*>
-                   (PL_DHashTableLookup(mBroadcasterMap, &aBroadcaster));
-
-    if (PL_DHASH_ENTRY_IS_FREE(entry)) {
+                   (PL_DHashTableSearch(mBroadcasterMap, &aBroadcaster));
+
+    if (!entry) {
         entry =
             static_cast<BroadcasterMapEntry*>
                        (PL_DHashTableAdd(mBroadcasterMap, &aBroadcaster));
 
         if (! entry) {
             aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
             return;
         }
@@ -839,19 +839,19 @@ XULDocument::RemoveBroadcastListenerFor(
 {
     // If we haven't added any broadcast listeners, then there sure
     // aren't any to remove.
     if (! mBroadcasterMap)
         return;
 
     BroadcasterMapEntry* entry =
         static_cast<BroadcasterMapEntry*>
-                   (PL_DHashTableLookup(mBroadcasterMap, &aBroadcaster));
-
-    if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+                   (PL_DHashTableSearch(mBroadcasterMap, &aBroadcaster));
+
+    if (entry) {
         nsCOMPtr<nsIAtom> attr = do_GetAtom(aAttr);
         for (int32_t i = entry->mListeners.Count() - 1; i >= 0; --i) {
             BroadcastListener* bl =
                 static_cast<BroadcastListener*>(entry->mListeners[i]);
 
             nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
 
             if (blListener == &aListener && bl->mAttribute == attr) {
@@ -953,27 +953,27 @@ XULDocument::AttributeChanged(nsIDocumen
     // Might not need this, but be safe for now.
     nsCOMPtr<nsIMutationObserver> kungFuDeathGrip(this);
 
     // XXXbz check aNameSpaceID, dammit!
     // See if we need to update our ref map.
     if (aAttribute == nsGkAtoms::ref) {
         AddElementToRefMap(aElement);
     }
-    
+
     nsresult rv;
 
     // Synchronize broadcast listeners
     if (mBroadcasterMap &&
         CanBroadcast(aNameSpaceID, aAttribute)) {
         BroadcasterMapEntry* entry =
             static_cast<BroadcasterMapEntry*>
-                       (PL_DHashTableLookup(mBroadcasterMap, aElement));
-
-        if (PL_DHASH_ENTRY_IS_BUSY(entry)) {
+                       (PL_DHashTableSearch(mBroadcasterMap, aElement));
+
+        if (entry) {
             // We've got listeners: push the value.
             nsAutoString value;
             bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
 
             int32_t i;
             for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
                 BroadcastListener* bl =
                     static_cast<BroadcastListener*>(entry->mListeners[i]);
@@ -4155,18 +4155,18 @@ XULDocument::BroadcastAttributeChangeFro
 
     if (!mBroadcasterMap || !CanBroadcast(aNameSpaceID, aAttribute))
         return rv;
 
     if (!aNode->IsElement())
         return rv;
 
     BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>
-        (PL_DHashTableLookup(mBroadcasterMap, aNode->AsElement()));
-    if (!PL_DHASH_ENTRY_IS_BUSY(entry))
+        (PL_DHashTableSearch(mBroadcasterMap, aNode->AsElement()));
+    if (!entry)
         return rv;
 
     // We've got listeners: push the value.
     int32_t i;
     for (i = entry->mListeners.Count() - 1; i >= 0; --i) {
         BroadcastListener* bl = static_cast<BroadcastListener*>
             (entry->mListeners[i]);
 
--- a/dom/xul/templates/nsContentSupportMap.h
+++ b/dom/xul/templates/nsContentSupportMap.h
@@ -27,44 +27,45 @@ public:
     nsresult Put(nsIContent* aElement, nsTemplateMatch* aMatch) {
         if (!mMap.IsInitialized())
             return NS_ERROR_NOT_INITIALIZED;
 
         PLDHashEntryHdr* hdr = PL_DHashTableAdd(&mMap, aElement);
         if (!hdr)
             return NS_ERROR_OUT_OF_MEMORY;
 
-        Entry* entry = reinterpret_cast<Entry*>(hdr);
+        Entry* entry = static_cast<Entry*>(hdr);
         NS_ASSERTION(entry->mMatch == nullptr, "over-writing entry");
         entry->mContent = aElement;
         entry->mMatch   = aMatch;
-        return NS_OK; }
+        return NS_OK;
+    }
 
     bool Get(nsIContent* aElement, nsTemplateMatch** aMatch) {
         if (!mMap.IsInitialized())
             return false;
 
-        PLDHashEntryHdr* hdr = PL_DHashTableLookup(&mMap, aElement);
-        if (PL_DHASH_ENTRY_IS_FREE(hdr))
+        PLDHashEntryHdr* hdr = PL_DHashTableSearch(&mMap, aElement);
+        if (!hdr)
             return false;
 
-        Entry* entry = reinterpret_cast<Entry*>(hdr);
+        Entry* entry = static_cast<Entry*>(hdr);
         *aMatch = entry->mMatch;
-        return true; }
+        return true;
+    }
 
     nsresult Remove(nsIContent* aElement);
 
     void Clear() { Finish(); Init(); }
 
 protected:
     PLDHashTable mMap;
 
     void Init();
     void Finish();
 
-    struct Entry {
-        PLDHashEntryHdr  mHdr;
+    struct Entry : public PLDHashEntryHdr {
         nsIContent*      mContent;
         nsTemplateMatch* mMatch;
     };
 };
 
 #endif
--- a/dom/xul/templates/nsTemplateMap.h
+++ b/dom/xul/templates/nsTemplateMap.h
@@ -6,18 +6,17 @@
 #ifndef nsTemplateMap_h__
 #define nsTemplateMap_h__
 
 #include "pldhash.h"
 #include "nsXULElement.h"
 
 class nsTemplateMap {
 protected:
-    struct Entry {
-        PLDHashEntryHdr mHdr;
+    struct Entry : public PLDHashEntryHdr {
         nsIContent*     mContent;
         nsIContent*     mTemplate;
     };
 
     PLDHashTable mTable;
 
     void
     Init()
@@ -30,21 +29,20 @@ protected:
 
 public:
     nsTemplateMap() { Init(); }
 
     ~nsTemplateMap() { Finish(); }
 
     void
     Put(nsIContent* aContent, nsIContent* aTemplate) {
-        NS_ASSERTION(PL_DHASH_ENTRY_IS_FREE(PL_DHashTableLookup(&mTable, aContent)),
+        NS_ASSERTION(!PL_DHashTableSearch(&mTable, aContent),
                      "aContent already in map");
 
-        Entry* entry =
-            reinterpret_cast<Entry*>(PL_DHashTableAdd(&mTable, aContent));
+        Entry* entry = static_cast<Entry*>(PL_DHashTableAdd(&mTable, aContent));
 
         if (entry) {
             entry->mContent = aContent;
             entry->mTemplate = aTemplate;
         }
     }
 
     void
@@ -57,19 +55,19 @@ public:
             Remove(child);
         }
     }
 
 
     void
     GetTemplateFor(nsIContent* aContent, nsIContent** aResult) {
         Entry* entry =
-            reinterpret_cast<Entry*>(PL_DHashTableLookup(&mTable, aContent));
+            static_cast<Entry*>(PL_DHashTableSearch(&mTable, aContent));
 
-        if (PL_DHASH_ENTRY_IS_BUSY(&entry->mHdr))
+        if (entry)
             NS_IF_ADDREF(*aResult = entry->mTemplate);
         else
             *aResult = nullptr;
     }
 
     void
     Clear() { Finish(); Init(); }
 };
--- a/editor/libeditor/tests/test_bug1026397.html
+++ b/editor/libeditor/tests/test_bug1026397.html
@@ -41,23 +41,22 @@ function runTests()
     }
     input.selectionStart = input.selectionEnd = aCaretOffset;
     if (aAdditionalExplanation) {
       aAdditionalExplanation = " " + aAdditionalExplanation;
     } else {
       aAdditionalExplanation = "";
     }
 
-    synthesizeComposition({ type: "compositionstart" });
     synthesizeCompositionChange(
       { "composition":
         { "string": aInsertString,
           "clauses":
           [
-            { "length": aInsertString.length, "attr": COMPOSITION_ATTR_RAWINPUT }
+            { "length": aInsertString.length, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         "caret": { "start": aInsertString.length, "length": 0 }
       });
     is(input.value, aExpectedValueDuringComposition,
        "The value of input whose maxlength is " + maxLengthStr + " should be "
          + aExpectedValueDuringComposition + " during composition" + aAdditionalExplanation);
     synthesizeComposition({ type: "compositioncommitasis" });
--- a/editor/libeditor/tests/test_bug1109465.html
+++ b/editor/libeditor/tests/test_bug1109465.html
@@ -42,17 +42,17 @@ SimpleTest.waitForFocus(function() {
   // Compose an IME string
   synthesizeComposition({ type: "compositionstart" });
   var composingString = "\u306B";
   synthesizeCompositionChange(
     { "composition":
       { "string": composingString,
         "clauses":
         [
-          { "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
+          { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
         ]
       },
       "caret": { "start": 1, "length": 0 }
     });
   synthesizeComposition({ type: "compositioncommitasis" });
   is(t.value, "foo\u306B\nbar", "Correct value after composition");
 
   // Now undo to test that the transaction merger has correctly detected the
--- a/editor/libeditor/tests/test_bug697842.html
+++ b/editor/libeditor/tests/test_bug697842.html
@@ -31,65 +31,71 @@ function runTests()
 {
   var editor = document.getElementById("editor");
   editor.focus();
 
   SimpleTest.executeSoon(function() {
     var composingString = "";
 
     function handler(aEvent) {
-      if (aEvent.type != "text") {
-        is(aEvent.data, composingString, "mismatch composition string");
+      switch (aEvent.type) {
+        case "compositionstart":
+          // Selected string at starting composition must be empty in this test.
+          is(aEvent.data, "", "mismatch selected string");
+          break;
+        case "compositionupdate":
+        case "compositionend":
+          is(aEvent.data, composingString, "mismatch composition string");
+          break;
+        default:
+          break;
       }
       aEvent.stopPropagation();
       aEvent.preventDefault();
     }
 
     editor.addEventListener("compositionstart", handler, true);
     editor.addEventListener("compositionend", handler, true);
     editor.addEventListener("compositionupdate", handler, true);
     editor.addEventListener("text", handler, true);
 
-    // start composition
-    synthesizeComposition({ type: "compositionstart" });
-
     // input first character
     composingString = "\u306B";
     synthesizeCompositionChange(
       { "composition":
         { "string": composingString,
           "clauses":
           [
-            { "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
+            { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         "caret": { "start": 1, "length": 0 }
       });
 
     // input second character
     composingString = "\u306B\u3085";
     synthesizeCompositionChange(
       { "composition":
         { "string": composingString,
           "clauses":
           [
-            { "length": 2, "attr": COMPOSITION_ATTR_RAWINPUT }
+            { "length": 2, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         "caret": { "start": 2, "length": 0 }
       });
 
     // convert them
     synthesizeCompositionChange(
       { "composition":
         { "string": composingString,
           "clauses":
           [
             { "length": 2,
-              "attr": COMPOSITION_ATTR_SELECTEDCONVERTEDTEXT }
+              "attr": COMPOSITION_ATTR_SELECTED_CLAUSE }
           ]
         },
         "caret": { "start": 2, "length": 0 }
       });
 
     synthesizeComposition({ type: "compositioncommitasis" });
 
     is(editor.innerHTML, composingString,
--- a/editor/libeditor/tests/test_bug795785.html
+++ b/editor/libeditor/tests/test_bug795785.html
@@ -109,30 +109,29 @@ function doKeyEventTest(aElement, aEleme
 
 function doCompositionTest(aElement, aElementDescription, aCallback)
 {
   aElement.focus();
   aElement.scrollTop = 0;
   hitEventLoop(function () {
     is(aElement.scrollTop, 0,
        aElementDescription + "'s scrollTop isn't 0");
-    synthesizeComposition({ type: "compositionstart" });
     var str = "Web \u958b\u767a\u8005\u306e\u7686\u3055\u3093\u306f\u3001" +
               "Firefox \u306b\u5b9f\u88c5\u3055\u308c\u3066\u3044\u308b HTML5" +
               " \u3084 CSS \u306e\u65b0\u6a5f\u80fd\u3092\u6d3b\u7528\u3059" +
               "\u308b\u3053\u3068\u3067\u3001\u9b45\u529b\u3042\u308b Web " +
               "\u30b5\u30a4\u30c8\u3084\u9769\u65b0\u7684\u306a Web \u30a2" +
               "\u30d7\u30ea\u30b1\u30fc\u30b7\u30e7\u30f3\u3092\u3088\u308a" +
               "\u77ed\u6642\u9593\u3067\u7c21\u5358\u306b\u4f5c\u6210\u3067" +
               "\u304d\u307e\u3059\u3002";
     synthesizeCompositionChange({
         composition: {
           string: str,
           clauses: [
-            { length: str.length, attr: COMPOSITION_ATTR_RAWINPUT }
+            { length: str.length, attr: COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         caret: { start: str.length, length: 0 }
       });
     hitEventLoop(function () {
       isnot(aElement.scrollTop, 0,
             aElementDescription + " was not scrolled by composition");
       synthesizeComposition({ type: "compositioncommit", data: "" });
--- a/editor/libeditor/tests/test_contenteditable_text_input_handling.html
+++ b/editor/libeditor/tests/test_contenteditable_text_input_handling.html
@@ -214,25 +214,23 @@ function runTests()
     testDispatchedKeyEvent(inputInEditor);
     testDispatchedKeyEvent(textareaInEditor);
 
     if (!aFocus._isEditable) {
       return;
     }
 
     // IME
-    // start composition
-    synthesizeComposition({ type: "compositionstart" });
     // input first character
     synthesizeCompositionChange(
       { "composition":
         { "string": "\u3089",
           "clauses":
           [
-            { "length": 1, "attr": COMPOSITION_ATTR_RAWINPUT }
+            { "length": 1, "attr": COMPOSITION_ATTR_RAW_CLAUSE }
           ]
         },
         "caret": { "start": 1, "length": 0 }
       });
     var queryText = synthesizeQueryTextContent(0, 100);
     ok(queryText, "query text event result is null" + when);
     if (!queryText) {
       return;
--- a/embedding/components/commandhandler/nsCommandParams.cpp
+++ b/embedding/components/commandhandler/nsCommandParams.cpp
@@ -212,30 +212,25 @@ nsCommandParams::RemoveValue(const char*
   // return NS_OK unconditionally.
   (void)PL_DHashTableRemove(&mValuesHash, (void *)aName);
   return NS_OK;
 }
 
 nsCommandParams::HashEntry*
 nsCommandParams::GetNamedEntry(const char* aName)
 {
-  HashEntry *foundEntry =
-    (HashEntry *)PL_DHashTableLookup(&mValuesHash, (void *)aName);
-  if (PL_DHASH_ENTRY_IS_BUSY(foundEntry)) {
-    return foundEntry;
-  }
-  return nullptr;
+  return (HashEntry *)PL_DHashTableSearch(&mValuesHash, (void *)aName);
 }
 
 nsCommandParams::HashEntry*
 nsCommandParams::GetOrMakeEntry(const char* aName, uint8_t entryType)
 {
   HashEntry *foundEntry =
-    (HashEntry *)PL_DHashTableLookup(&mValuesHash, (void *)aName);
-  if (PL_DHASH_ENTRY_IS_BUSY(foundEntry)) { // reuse existing entry
+    (HashEntry *)PL_DHashTableSearch(&mValuesHash, (void *)aName);
+  if (foundEntry) { // reuse existing entry
     foundEntry->Reset(entryType);
     return foundEntry;
   }
 
   foundEntry = (HashEntry *)PL_DHashTableAdd(&mValuesHash, (void *)aName);
   if (!foundEntry) {
     return nullptr;
   }
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -157,16 +157,17 @@ struct DrawSurfaceOptions {
  */
 class GradientStops : public RefCounted<GradientStops>
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStops)
   virtual ~GradientStops() {}
 
   virtual BackendType GetBackendType() const = 0;
+  virtual bool IsValid() const { return true; }
 
 protected:
   GradientStops() {}
 };
 
 /**
  * This is the base class for 'patterns'. Patterns describe the pixels used as
  * the source for a masked composition operation that is done by the different
--- a/gfx/2d/DrawTargetD2D.cpp
+++ b/gfx/2d/DrawTargetD2D.cpp
@@ -1295,17 +1295,17 @@ DrawTargetD2D::CreateGradientStops(Gradi
                                       byRef(stopCollection));
   delete [] stops;
 
   if (FAILED(hr)) {
     gfxWarning() << "Failed to create GradientStopCollection. Code: " << hexa(hr);
     return nullptr;
   }
 
-  return new GradientStopsD2D(stopCollection);
+  return new GradientStopsD2D(stopCollection, Factory::GetDirect3D11Device());
 }
 
 TemporaryRef<FilterNode>
 DrawTargetD2D::CreateFilter(FilterType aType)
 {
   RefPtr<ID2D1DeviceContext> dc;
   HRESULT hr = mRT->QueryInterface((ID2D1DeviceContext**)byRef(dc));
 
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -720,17 +720,17 @@ DrawTargetD2D1::CreateGradientStops(Grad
                                       byRef(stopCollection));
   delete [] stops;
 
   if (FAILED(hr)) {
     gfxWarning() << *this << ": Failed to create GradientStopCollection. Code: " << hexa(hr);
     return nullptr;
   }
 
-  return new GradientStopsD2D(stopCollection);
+  return new GradientStopsD2D(stopCollection, Factory::GetDirect3D11Device());
 }
 
 TemporaryRef<FilterNode>
 DrawTargetD2D1::CreateFilter(FilterType aType)
 {
   return FilterNodeD2D1::Create(mDC, aType);
 }
 
@@ -1223,16 +1223,22 @@ DrawTargetD2D1::CreateBrushForPattern(co
       return colBrush.forget();
     }
 
     mDC->CreateLinearGradientBrush(D2D1::LinearGradientBrushProperties(D2DPoint(pat->mBegin),
                                                                        D2DPoint(pat->mEnd)),
                                    D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)),
                                    stops->mStopCollection,
                                    byRef(gradBrush));
+
+    if (!gradBrush) {
+      gfxWarning() << "Couldn't create gradient brush.";
+      return CreateTransparentBlackBrush();
+    }
+
     return gradBrush.forget();
   }
   if (aPattern.GetType() == PatternType::RADIAL_GRADIENT) {
     RefPtr<ID2D1RadialGradientBrush> gradBrush;
     const RadialGradientPattern *pat =
       static_cast<const RadialGradientPattern*>(&aPattern);
 
     GradientStopsD2D *stops = static_cast<GradientStopsD2D*>(pat->mStops.get());
@@ -1246,16 +1252,21 @@ DrawTargetD2D1::CreateBrushForPattern(co
     mDC->CreateRadialGradientBrush(
       D2D1::RadialGradientBrushProperties(D2DPoint(pat->mCenter2),
                                           D2DPoint(pat->mCenter1 - pat->mCenter2),
                                           pat->mRadius2, pat->mRadius2),
       D2D1::BrushProperties(aAlpha, D2DMatrix(pat->mMatrix)),
                             stops->mStopCollection,
                             byRef(gradBrush));
 
+    if (!gradBrush) {
+      gfxWarning() << "Couldn't create gradient brush.";
+      return CreateTransparentBlackBrush();
+    }
+
     return gradBrush.forget();
   }
   if (aPattern.GetType() == PatternType::SURFACE) {
     const SurfacePattern *pat =
       static_cast<const SurfacePattern*>(&aPattern);
 
     if (!pat->mSurface) {
       gfxDebug() << "No source surface specified for surface pattern";
@@ -1274,16 +1285,18 @@ DrawTargetD2D1::CreateBrushForPattern(co
       // We will do a partial upload of the sampling restricted area from GetImageForSurface.
       samplingBounds = D2D1::RectF(0, 0, pat->mSamplingRect.width, pat->mSamplingRect.height);
     } else {
       samplingBounds = D2D1::RectF(0, 0,
                                    Float(pat->mSurface->GetSize().width),
                                    Float(pat->mSurface->GetSize().height));
     }
 
+    MOZ_ASSERT(pat->mSurface->IsValid());
+
     RefPtr<ID2D1ImageBrush> imageBrush;
     RefPtr<ID2D1Image> image = GetImageForSurface(pat->mSurface, mat, pat->mExtendMode, !pat->mSamplingRect.IsEmpty() ? &pat->mSamplingRect : nullptr);
 
     if (!image) {
       return CreateTransparentBlackBrush();
     }
 
     mDC->CreateImageBrush(image,
--- a/gfx/2d/GradientStopsD2D.h
+++ b/gfx/2d/GradientStopsD2D.h
@@ -12,25 +12,29 @@
 
 namespace mozilla {
 namespace gfx {
 
 class GradientStopsD2D : public GradientStops
 {
 public:
   MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GradientStopsD2D)
-  GradientStopsD2D(ID2D1GradientStopCollection *aStopCollection)
+  GradientStopsD2D(ID2D1GradientStopCollection *aStopCollection, ID3D11Device *aDevice)
     : mStopCollection(aStopCollection)
+    , mDevice(aDevice)
   {}
 
   virtual BackendType GetBackendType() const { return BackendType::DIRECT2D; }
 
+  virtual bool IsValid() const MOZ_FINAL{ return mDevice == Factory::GetDirect3D11Device(); }
+
 private:
   friend class DrawTargetD2D;
   friend class DrawTargetD2D1;
 
   mutable RefPtr<ID2D1GradientStopCollection> mStopCollection;
+  RefPtr<ID3D11Device> mDevice;
 };
 
 }
 }
 
 #endif /* MOZILLA_GFX_GRADIENTSTOPSD2D_H_ */
--- a/gfx/gl/GLContextGLX.h
+++ b/gfx/gl/GLContextGLX.h
@@ -11,17 +11,17 @@
 #include "GLXLibrary.h"
 
 namespace mozilla {
 namespace gl {
 
 class GLContextGLX : public GLContext
 {
 public:
-    MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextGLX)
+    MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextGLX, MOZ_OVERRIDE)
     static already_AddRefed<GLContextGLX>
     CreateGLContext(const SurfaceCaps& caps,
                     GLContextGLX* shareContext,
                     bool isOffscreen,
                     Display* display,
                     GLXDrawable drawable,
                     GLXFBConfig cfg,
                     bool deleteDrawable,
--- a/gfx/gl/GLContextWGL.h
+++ b/gfx/gl/GLContextWGL.h
@@ -11,17 +11,17 @@
 #include "WGLLibrary.h"
 
 namespace mozilla {
 namespace gl {
 
 class GLContextWGL : public GLContext
 {
 public:
-    MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextWGL)
+    MOZ_DECLARE_REFCOUNTED_VIRTUAL_TYPENAME(GLContextWGL, MOZ_OVERRIDE)
     // From Window: (possibly for offscreen!)
     GLContextWGL(const SurfaceCaps& caps,
                  GLContext* sharedContext,
                  bool isOffscreen,
                  HDC aDC,
                  HGLRC aContext,
                  HWND aWindow = nullptr);
 
--- a/gfx/layers/client/ClientLayerManager.cpp
+++ b/gfx/layers/client/ClientLayerManager.cpp
@@ -202,21 +202,26 @@ ClientLayerManager::BeginTransactionWith
   targetBounds.x = targetBounds.y = 0;
   mForwarder->BeginTransaction(targetBounds, mTargetRotation, orientation);
 
   // If we're drawing on behalf of a context with async pan/zoom
   // enabled, then the entire buffer of painted layers might be
   // composited (including resampling) asynchronously before we get
   // a chance to repaint, so we have to ensure that it's all valid
   // and not rotated.
+  //
+  // Desktop does not support async zoom yet, so we ignore this for those
+  // platforms.
+#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK)
   if (mWidget) {
     if (dom::TabChild* window = mWidget->GetOwningTabChild()) {
       mCompositorMightResample = window->IsAsyncPanZoomEnabled();
     }
   }
+#endif
 
   // If we have a non-default target, we need to let our shadow manager draw
   // to it. This will happen at the end of the transaction.
   if (aTarget && XRE_GetProcessType() == GeckoProcessType_Default) {
     mShadowTarget = aTarget;
   } else {
     NS_ASSERTION(!aTarget,
                  "Content-process ClientLayerManager::BeginTransactionWithTarget not supported");
--- a/gfx/layers/client/ClientTiledPaintedLayer.cpp
+++ b/gfx/layers/client/ClientTiledPaintedLayer.cpp
@@ -413,30 +413,46 @@ ClientTiledPaintedLayer::RenderLayer()
   }
 
   if (!ClientManager()->IsRepeatTransaction()) {
     // Only paint the mask layer on the first transaction.
     if (GetMaskLayer()) {
       ToClientLayer(GetMaskLayer())->RenderLayer();
     }
 
+    // For more complex cases we need to calculate a bunch of metrics before we
+    // can do the paint.
+    BeginPaint();
+    if (mPaintData.mPaintFinished) {
+      return;
+    }
+
     // In some cases we can take a fast path and just be done with it.
     if (UseFastPath()) {
       TILING_LOG("TILING %p: Taking fast-path\n", this);
       mValidRegion = neededRegion;
+
+      // Make sure that tiles that fall outside of the visible region or outside of the
+      // critical displayport are discarded on the first update. Also make sure that we
+      // only draw stuff inside the critical displayport on the first update.
+      if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
+        mValidRegion.And(mValidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+        invalidRegion.And(invalidRegion, LayerIntRect::ToUntyped(mPaintData.mCriticalDisplayPort));
+      }
+
+      if (invalidRegion.IsEmpty()) {
+        EndPaint();
+        return;
+      }
+
+      mContentClient->mTiledBuffer.SetFrameResolution(mPaintData.mResolution);
       mContentClient->mTiledBuffer.PaintThebes(mValidRegion, invalidRegion, callback, data);
       ClientManager()->Hold(this);
       mContentClient->UseTiledLayerBuffer(TiledContentClient::TILED_BUFFER);
-      return;
-    }
-
-    // For more complex cases we need to calculate a bunch of metrics before we
-    // can do the paint.
-    BeginPaint();
-    if (mPaintData.mPaintFinished) {
+      EndPaint();
       return;
     }
 
     // Make sure that tiles that fall outside of the visible region or outside of the
     // critical displayport are discarded on the first update. Also make sure that we
     // only draw stuff inside the critical displayport on the first update.
     mValidRegion.And(mValidRegion, neededRegion);
     if (!mPaintData.mCriticalDisplayPort.IsEmpty()) {
--- a/gfx/thebes/gfxFT2FontList.cpp
+++ b/gfx/thebes/gfxFT2FontList.cpp
@@ -710,23 +710,20 @@ public:
 
     virtual void
     GetInfoForFile(const nsCString& aFileName, nsCString& aFaceList,
                    uint32_t *aTimestamp, uint32_t *aFilesize)
     {
         if (!mMap.IsInitialized()) {
             return;
         }
-        PLDHashEntryHdr *hdr =
-            PL_DHashTableLookup(&mMap, aFileName.get());
-        if (!hdr) {
-            return;
-        }
-        FNCMapEntry* entry = static_cast<FNCMapEntry*>(hdr);
-        if (entry && entry->mFilesize) {
+        FNCMapEntry *entry =
+            static_cast<FNCMapEntry*>(PL_DHashTableSearch(&mMap,
+                                                          aFileName.get()));
+        if (entry) {
             *aTimestamp = entry->mTimestamp;
             *aFilesize = entry->mFilesize;
             aFaceList.Assign(entry->mFaces);
             // this entry does correspond to an existing file
             // (although it might not be up-to-date, in which case
             // it will get overwritten via CacheFileInfo)
             entry->mFileExists = true;
         }
--- a/gfx/thebes/gfxGradientCache.cpp
+++ b/gfx/thebes/gfxGradientCache.cpp
@@ -180,17 +180,25 @@ static GradientCache* gGradientCache = n
 GradientStops *
 gfxGradientCache::GetGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
 {
   if (!gGradientCache) {
     gGradientCache = new GradientCache();
   }
   GradientCacheData* cached =
     gGradientCache->Lookup(aStops, aExtend, aDT->GetBackendType());
-  return cached ? cached->mStops : nullptr;
+  if (cached && cached->mStops) {
+    if (!cached->mStops->IsValid()) {
+      gGradientCache->NotifyExpired(cached);
+    } else {
+      return cached->mStops;
+    }
+  }
+
+  return nullptr;
 }
 
 GradientStops *
 gfxGradientCache::GetOrCreateGradientStops(const DrawTarget *aDT, nsTArray<GradientStop>& aStops, ExtendMode aExtend)
 {
   RefPtr<GradientStops> gs = GetGradientStops(aDT, aStops, aExtend);
   if (!gs) {
     gs = aDT->CreateGradientStops(aStops.Elements(), aStops.Length(), aExtend);
--- a/gfx/thebes/gfxPlatform.h
+++ b/gfx/thebes/gfxPlatform.h
@@ -668,32 +668,33 @@ protected:
     // max number of entries in word cache
     int32_t mWordCacheMaxEntries;
 
     uint32_t mTotalSystemMemory;
 
     // Hardware vsync source. Only valid on parent process
     nsRefPtr<mozilla::gfx::VsyncSource> mVsyncSource;
 
+    mozilla::RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
+
 private:
     /**
      * Start up Thebes.
      */
     static void Init();
 
     static void CreateCMSOutputProfile();
 
     static void GetCMSOutputProfileData(void *&mem, size_t &size);
 
     friend void RecordingPrefChanged(const char *aPrefName, void *aClosure);
 
     virtual void GetPlatformCMSOutputProfile(void *&mem, size_t &size);
 
     nsRefPtr<gfxASurface> mScreenReferenceSurface;
-    mozilla::RefPtr<mozilla::gfx::DrawTarget> mScreenReferenceDrawTarget;
     nsTArray<uint32_t> mCJKPrefLangs;
     nsCOMPtr<nsIObserver> mSRGBOverrideObserver;
     nsCOMPtr<nsIObserver> mFontPrefsObserver;
     nsCOMPtr<nsIObserver> mMemoryPressureObserver;
 
     // The preferred draw target backend to use for canvas
     mozilla::gfx::BackendType mPreferredCanvasBackend;
     // The fallback draw target backend to use for canvas, if the preferred backend fails
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -241,16 +241,17 @@ private:
   DECL_GFX_PREF(Live, "image.mem.decodeondraw",                ImageMemDecodeOnDraw, bool, true);
   DECL_GFX_PREF(Live, "image.mem.discardable",                 ImageMemDiscardable, bool, false);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.discard_factor", ImageMemSurfaceCacheDiscardFactor, uint32_t, 1);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.max_size_kb",    ImageMemSurfaceCacheMaxSizeKB, uint32_t, 100 * 1024);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.min_expiration_ms", ImageMemSurfaceCacheMinExpirationMS, uint32_t, 60*1000);
   DECL_GFX_PREF(Once, "image.mem.surfacecache.size_factor",    ImageMemSurfaceCacheSizeFactor, uint32_t, 64);
   DECL_GFX_PREF(Live, "image.mozsamplesize.enabled",           ImageMozSampleSizeEnabled, bool, false);
   DECL_GFX_PREF(Once, "image.multithreaded_decoding.limit",    ImageMTDecodingLimit, int32_t, -1);
+  DECL_GFX_PREF(Live, "image.single-color-optimization.enabled", ImageSingleColorOptimizationEnabled, bool, true);
 
   DECL_GFX_PREF(Once, "layers.acceleration.disabled",          LayersAccelerationDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps",          LayersDrawFPS, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.print-histogram",  FPSPrintHistogram, bool, false);
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-video.enabled",            AsyncVideoEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.async-video-oop.enabled",        AsyncVideoOOPEnabled, bool, true);
--- a/gfx/thebes/gfxWindowsPlatform.cpp
+++ b/gfx/thebes/gfxWindowsPlatform.cpp
@@ -381,25 +381,28 @@ gfxWindowsPlatform::GetDPIScale()
 }
 
 void
 gfxWindowsPlatform::UpdateRenderMode()
 {
 /* Pick the default render mode for
  * desktop.
  */
+    bool didReset = false;
     if (DidRenderingDeviceReset()) {
       mD3D11DeviceInitialized = false;
       mD3D11Device = nullptr;
       mD3D11ContentDevice = nullptr;
       mAdapter = nullptr;
 
       imgLoader::Singleton()->ClearCache(true);
       imgLoader::Singleton()->ClearCache(false);
       Factory::SetDirect3D11Device(nullptr);
+
+      didReset = true;
     }
 
     mRenderMode = RENDER_GDI;
 
     bool isVistaOrHigher = IsVistaOrLater();
 
     bool safeMode = false;
     nsCOMPtr<nsIXULRuntime> xr = do_GetService("@mozilla.org/xre/runtime;1");
@@ -507,16 +510,20 @@ gfxWindowsPlatform::UpdateRenderMode()
         defaultBackend = BackendType::DIRECT2D;
       }
     } else {
       canvasMask |= BackendTypeBit(BackendType::SKIA);
     }
     contentMask |= BackendTypeBit(BackendType::SKIA);
     InitBackendPrefs(canvasMask, defaultBackend,
                      contentMask, defaultBackend);
+
+    if (didReset) {
+      mScreenReferenceDrawTarget = CreateOffscreenContentDrawTarget(IntSize(1, 1), SurfaceFormat::B8G8R8A8);
+    }
 }
 
 #ifdef CAIRO_HAS_D2D_SURFACE
 HRESULT
 gfxWindowsPlatform::CreateDevice(nsRefPtr<IDXGIAdapter1> &adapter1,
                                  int featureLevelIndex)
 {
   nsModuleHandle d3d10module(LoadLibrarySystem32(L"d3d10_1.dll"));
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -416,18 +416,18 @@ nsresult imgFrame::Optimize()
 
   // Don't do single-color opts on non-premult data.
   // Cairo doesn't support non-premult single-colors.
   if (mNonPremult)
     return NS_OK;
 
   /* Figure out if the entire image is a constant color */
 
-  // this should always be true
-  if (mImageSurface->Stride() == mSize.width * 4) {
+  if (gfxPrefs::ImageSingleColorOptimizationEnabled() &&
+      mImageSurface->Stride() == mSize.width * 4) {
     uint32_t *imgData = (uint32_t*) ((uint8_t *)mVBufPtr);
     uint32_t firstPixel = * (uint32_t*) imgData;
     uint32_t pixelCount = mSize.width * mSize.height + 1;
 
     while (--pixelCount && *imgData++ == firstPixel)
       ;
 
     if (pixelCount == 0) {
--- a/intl/locale/unix/nsDateTimeFormatUnix.h
+++ b/intl/locale/unix/nsDateTimeFormatUnix.h
@@ -19,38 +19,38 @@ class nsDateTimeFormatUnix : public nsID
 public: 
   NS_DECL_THREADSAFE_ISUPPORTS 
 
   // performs a locale sensitive date formatting operation on the time_t parameter
   NS_IMETHOD FormatTime(nsILocale* locale, 
                         const nsDateFormatSelector  dateFormatSelector, 
                         const nsTimeFormatSelector timeFormatSelector, 
                         const time_t  timetTime, 
-                        nsAString& stringOut); 
+                        nsAString& stringOut) MOZ_OVERRIDE;
 
   // performs a locale sensitive date formatting operation on the struct tm parameter
   NS_IMETHOD FormatTMTime(nsILocale* locale, 
                         const nsDateFormatSelector  dateFormatSelector, 
                         const nsTimeFormatSelector timeFormatSelector, 
                         const struct tm*  tmTime, 
-                        nsAString& stringOut); 
+                        nsAString& stringOut) MOZ_OVERRIDE;
 
   // performs a locale sensitive date formatting operation on the PRTime parameter
   NS_IMETHOD FormatPRTime(nsILocale* locale, 
                           const nsDateFormatSelector  dateFormatSelector, 
                           const nsTimeFormatSelector timeFormatSelector, 
                           const PRTime  prTime, 
-                          nsAString& stringOut);
+                          nsAString& stringOut) MOZ_OVERRIDE;
 
   // performs a locale sensitive date formatting operation on the PRExplodedTime parameter
   NS_IMETHOD FormatPRExplodedTime(nsILocale* locale, 
                                   const nsDateFormatSelector  dateFormatSelector, 
                                   const nsTimeFormatSelector timeFormatSelector, 
                                   const PRExplodedTime*  explodedTime, 
-                                  nsAString& stringOut); 
+                                  nsAString& stringOut) MOZ_OVERRIDE;
 
 
   nsDateTimeFormatUnix() {mLocale.Truncate();mAppLocale.Truncate();}
 
 private:
   virtual ~nsDateTimeFormatUnix() {}
 
   // init this interface to a specified locale
--- a/js/src/builtin/MapObject.cpp
+++ b/js/src/builtin/MapObject.cpp
@@ -1072,24 +1072,19 @@ MapObject::initClass(JSContext *cx, JSOb
     if (proto) {
         // Define the "entries" method.
         JSFunction *fun = JS_DefineFunction(cx, proto, "entries", entries, 0, 0);
         if (!fun)
             return nullptr;
 
         // Define its alias.
         RootedValue funval(cx, ObjectValue(*fun));
-#if JS_HAS_SYMBOLS
         RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
         if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
             return nullptr;
-#else
-        if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, 0))
-            return nullptr;
-#endif
     }
     return proto;
 }
 
 template <class Range>
 static void
 MarkKey(Range &r, const HashableValue &key, JSTracer *trc)
 {
@@ -1725,24 +1720,19 @@ SetObject::initClass(JSContext *cx, JSOb
         if (!fun)
             return nullptr;
 
         // Define its aliases.
         RootedValue funval(cx, ObjectValue(*fun));
         if (!JS_DefineProperty(cx, proto, "keys", funval, 0))
             return nullptr;
 
-#if JS_HAS_SYMBOLS
         RootedId iteratorId(cx, SYMBOL_TO_JSID(cx->wellKnownSymbols().iterator));
         if (!JS_DefinePropertyById(cx, proto, iteratorId, funval, 0))
             return nullptr;
-#else
-        if (!JS_DefineProperty(cx, proto, js_std_iterator_str, funval, 0))
-            return nullptr;
-#endif
     }
     return proto;
 }
 
 
 bool
 SetObject::keys(JSContext *cx, HandleObject obj, JS::AutoValueVector *keys)
 {
--- a/js/src/builtin/TypedArray.js
+++ b/js/src/builtin/TypedArray.js
@@ -165,16 +165,51 @@ function TypedArrayFindIndex(predicate, 
         if (callFunction(predicate, T, O[k], k, O))
             return k;
     }
 
     // Step 10.
     return -1;
 }
 
+// ES6 draft rev31 (2015-01-15) 22.1.3.10 %TypedArray%.prototype.forEach(callbackfn[,thisArg])
+function TypedArrayForEach(callbackfn, thisArg = undefined) {
+    // This function is not generic.
+    if (!IsObject(this) || !IsTypedArray(this)) {
+	return callFunction(CallTypedArrayMethodIfWrapped, this, callbackfn, thisArg,
+			    "TypedArrayForEach");
+    }
+
+    // Step 1-2.
+    var O = this;
+
+    // Step 3-4.
+    var len = TypedArrayLength(O);
+
+    // Step 5.
+    if (arguments.length === 0)
+	ThrowError(JSMSG_MISSING_FUN_ARG, 0, 'TypedArray.prototype.forEach');
+    if (!IsCallable(callbackfn))
+	ThrowError(JSMSG_NOT_FUNCTION, DecompileArg(0, callbackfn));
+
+    // Step 6.
+    var T = thisArg;
+
+    // Step 7-8.
+    // Step 7, 8a (implicit) and 8e.
+    for (var k = 0; k < len; k++) {
+	// Step 8b-8c are unnecessary since the condition always holds true for TypedArray.
+	// Step 8d.
+	callFunction(callbackfn, T, O[k], k, O);
+    }
+
+    // Step 9.
+    return undefined;
+}
+
 // ES6 draft rev29 (2014/12/06) 22.2.3.13 %TypedArray%.prototype.indexOf(searchElement[, fromIndex]).
 function TypedArrayIndexOf(searchElement, fromIndex = 0) {
     // This function is not generic.
     if (!IsObject(this) || !IsTypedArray(this)) {
         return callFunction(CallTypedArrayMethodIfWrapped, this, searchElement, fromIndex,
                             "TypedArrayIndexOf");
     }
 
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/bigTextNodes.js
@@ -0,0 +1,25 @@
+window.tests.set('bigTextNodes', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [ textNode, textNode, ... ]",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbagePerFrame: "8",
+    defaultGarbageTotal: "8",
+
+    makeGarbage: (N) => {
+        var a = [];
+        var s = "x";
+        for (var i = 0; i < 16; i++)
+            s = s + s;
+        for (var i = 0; i < N; i++)
+            a.push(document.createTextNode(s));
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/events.js
@@ -0,0 +1,25 @@
+window.tests.set('events', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [ textNode, textNode, ... ]",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbagePerFrame: "100K",
+    defaultGarbageTotal: "8",
+
+    makeGarbage: (N) => {
+        var a = [];
+        for (var i = 0; i < N; i++) {
+            var e = document.createEvent("Events");
+            e.initEvent("TestEvent", true, true);
+            a.push(e);
+        }
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/expandoEvents.js
@@ -0,0 +1,26 @@
+window.tests.set('expandoEvents', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [ textNode, textNode, ... ]",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbagePerFrame: "100K",
+    defaultGarbageTotal: "8",
+
+    makeGarbage: (N) => {
+        var a = [];
+        for (var i = 0; i < N; i++) {
+            var e = document.createEvent("Events");
+            e.initEvent("TestEvent", true, true);
+            e.color = ["tuna"];
+            a.push(e);
+        }
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayBuffer.js
@@ -0,0 +1,23 @@
+window.tests.set('globalArrayBuffer', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = ArrayBuffer(N); # (large malloc data)",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbageTotal: "8K",
+    defaultGarbagePerFrame: "4M",
+
+    makeGarbage: (N) => {
+        var ab = new ArrayBuffer(N);
+        var view = new Uint8Array(ab);
+        view[0] = 1;
+        view[N - 1] = 2;
+        garbage[garbageIndex++] = ab;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayFgFinalized.js
@@ -0,0 +1,23 @@
+window.tests.set('globalArrayFgFinalized', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [ new Map, new Map, ... ]; # (foreground finalized)",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbageTotal: "8K",
+    defaultGarbagePerFrame: "1M",
+
+    makeGarbage: (N) => {
+        var arr = [];
+        for (var i = 0; i < N; i++) {
+            arr.push(new Map);
+        }
+        garbage[garbageIndex++] = arr;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/globalArrayLargeObject.js
@@ -0,0 +1,23 @@
+window.tests.set('globalArrayLargeObject', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = { LARGE }; # (large slots)",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbageTotal: "8K",
+    defaultGarbagePerFrame: "200K",
+
+    makeGarbage: (N) => {
+        var obj = {};
+        for (var i = 0; i < N; i++) {
+            obj["key" + i] = i;
+        }
+        garbage[garbageIndex++] = obj;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/pairCyclicWeakMap.js
@@ -0,0 +1,33 @@
+window.tests.set('pairCyclicWeakMap', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "wm1[k1] = k2; wm2[k2] = k3; wm1[k3] = k4; wm2[k4] = ...",
+
+    defaultGarbagePerFrame: "1K",
+    defaultGarbageTotal: "1K",
+
+    load: (N) => { garbage = new Array(N); },
+
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    makeGarbage: (M) => {
+        var wm1 = new WeakMap();
+        var wm2 = new WeakMap();
+        var initialKey = {};
+        var key = initialKey;
+        var value = {};
+        for (var i = 0; i < M/2; i++) {
+            wm1.set(key, value);
+            key = value;
+            value = {};
+            wm2.set(key, value);
+            key = value;
+            value = {};
+        }
+        garbage[garbageIndex++] = [ initialKey, wm1, wm2 ];
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/selfCyclicWeakMap.js
@@ -0,0 +1,29 @@
+window.tests.set('selfCyclicWeakMap', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var wm = new WeakMap(); wm[k1] = k2; wm[k2] = k3; ...",
+
+    defaultGarbagePerFrame: "1K",
+    defaultGarbageTotal: "1K",
+
+    load: (N) => { garbage = new Array(N); },
+
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    makeGarbage: (M) => {
+        var wm = new WeakMap();
+        var initialKey = {};
+        var key = initialKey;
+        var value = {};
+        for (var i = 0; i < M; i++) {
+            wm.set(key, value);
+            key = value;
+            value = {};
+        }
+        garbage[garbageIndex++] = [ initialKey, wm ];
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/benchmarks/textNodes.js
@@ -0,0 +1,23 @@
+window.tests.set('textNodes', (function() {
+var garbage = [];
+var garbageIndex = 0;
+return {
+    description: "var foo = [ textNode, textNode, ... ]",
+
+    load: (N) => { garbage = new Array(N); },
+    unload: () => { garbage = []; garbageIndex = 0; },
+
+    defaultGarbagePerFrame: "100K",
+    defaultGarbageTotal: "8",
+
+    makeGarbage: (N) => {
+        var a = [];
+        for (var i = 0; i < N; i++) {
+            a.push(document.createTextNode("t" + i));
+        }
+        garbage[garbageIndex++] = a;
+        if (garbageIndex == garbage.length)
+            garbageIndex = 0;
+    }
+};
+})());
new file mode 100644
--- /dev/null
+++ b/js/src/devtools/gc-ubench/harness.js
@@ -0,0 +1,414 @@
+// Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly.
+var numSamples = 500;
+var delays = new Array(numSamples);
+var sampleIndex = 0;
+var sampleTime = 16; // ms
+var gHistogram = new Map(); // {ms: count}
+
+// Draw state.
+var stopped = 0;
+var start;
+var prev;
+var ctx;
+
+// Current test state.
+var activeTest = undefined;
+var testDuration = undefined; // ms
+var testState = 'idle';  // One of 'idle' or 'running'.
+var testStart = undefined; // ms
+var testQueue = [];
+
+// Global defaults
+var globalDefaultGarbageTotal = "8M";
+var globalDefaultGarbagePerFrame = "8K";
+
+function xpos(index)
+{
+    return index * 2;
+}
+
+function ypos(delay)
+{
+    var r = 525 - Math.log(delay) * 64;
+    if (r < 5) return 5;
+    return r;
+}
+
+function drawHBar(delay, color, label)
+{
+    ctx.fillStyle = color;
+    ctx.strokeStyle = color;
+    ctx.fillText(label, xpos(numSamples) + 4, ypos(delay) + 3);
+
+    ctx.beginPath();
+    ctx.moveTo(xpos(0), ypos(delay));
+    ctx.lineTo(xpos(numSamples), ypos(delay));
+    ctx.stroke();
+    ctx.strokeStyle = 'rgb(0,0,0)';
+    ctx.fillStyle = 'rgb(0,0,0)';
+}
+
+function drawScale(delay)
+{
+    drawHBar(delay, 'rgb(150,150,150)', `${delay}ms`);
+}
+
+function draw60fps() {
+    drawHBar(1000/60, '#00cf61', '60fps');
+}
+
+function draw30fps() {
+    drawHBar(1000/30, '#cf0061', '30fps');
+}
+
+function drawGraph()
+{
+    ctx.clearRect(0, 0, 1100, 550);
+
+    drawScale(10);
+    draw60fps();
+    drawScale(20);
+    drawScale(30);
+    draw30fps();
+    drawScale(50);
+    drawScale(100);
+    drawScale(200);
+    drawScale(400);
+    drawScale(800);
+
+    var worst = 0, worstpos = 0;
+    ctx.beginPath();
+    for (var i = 0; i < numSamples; i++) {
+        ctx.lineTo(xpos(i), ypos(delays[i]));
+        if (delays[i] >= worst) {
+            worst = delays[i];
+            worstpos = i;
+        }
+    }
+    ctx.stroke();
+
+    ctx.fillStyle = 'rgb(255,0,0)';
+    if (worst)
+        ctx.fillText(''+worst.toFixed(2)+'ms', xpos(worstpos) - 10, ypos(worst) - 14);
+
+    ctx.beginPath();
+    var where = sampleIndex % numSamples;
+    ctx.arc(xpos(where), ypos(delays[where]), 5, 0, Math.PI*2, true);
+    ctx.fill();
+    ctx.fillStyle = 'rgb(0,0,0)';
+
+    ctx.fillText('Time', 550, 420);
+    ctx.save();
+    ctx.rotate(Math.PI/2);
+    ctx.fillText('Pause between frames (log scale)', 150, -1060);
+    ctx.restore();
+}
+
+function stopstart()
+{
+    if (stopped) {
+        window.requestAnimationFrame(handler);
+        prev = performance.now();
+        start += prev - stopped;
+        document.getElementById('stop').value = 'Pause';
+        stopped = 0;
+    } else {
+        document.getElementById('stop').value = 'Resume';
+        stopped = performance.now();
+    }
+}
+
+function handler(timestamp)
+{
+    if (stopped)
+        return;
+
+    if (testState === 'running' && (timestamp - testStart) > testDuration)
+        end_test(timestamp);
+
+    if (testState == 'running')
+        document.getElementById("test-progress").textContent = ((testDuration - (timestamp - testStart))/1000).toFixed(1) + " sec";
+
+    activeTest.makeGarbage(activeTest.garbagePerFrame);
+
+    var elt = document.getElementById('data');
+    var delay = timestamp - prev;
+    prev = timestamp;
+
+    // Take the histogram at 10us intervals so that we have enough resolution to capture.
+    // a 16.66[666] target with adequate accuracy.
+    update_histogram(gHistogram, Math.round(delay * 100));
+
+    var t = timestamp - start;
+    var newIndex = Math.round(t / sampleTime);
+    while (sampleIndex < newIndex) {
+        sampleIndex++;
+        delays[sampleIndex % numSamples] = delay;
+    }
+
+    drawGraph();
+    window.requestAnimationFrame(handler);
+}
+
+function update_histogram(histogram, delay)
+{
+    var current = histogram.has(delay) ? histogram.get(delay) : 0;
+    histogram.set(delay, ++current);
+}
+
+function reset_draw_state()
+{
+    for (var i = 0; i < numSamples; i++)
+        delays[i] = 0;
+    start = prev = performance.now();
+    sampleIndex = 0;
+}
+
+function onunload()
+{
+    if (activeTest)
+        activeTest.unload();
+    activeTest = undefined;
+}
+
+function onload()
+{
+    // Load initial test duration.
+    duration_changed();
+
+    // Load initial garbage size.
+    garbage_total_changed();
+    garbage_per_frame_changed();
+
+    // Populate the test selection dropdown.
+    var select = document.getElementById("test-selection");
+    for (var [name, test] of tests) {
+        test.name = name;
+        var option = document.createElement("option");
+        option.id = name;
+        option.text = name;
+        option.title = test.description;
+        select.add(option);
+    }
+
+    // Load the initial test.
+    change_active_test('noAllocation');
+
+    // Polyfill rAF.
+    var requestAnimationFrame =
+        window.requestAnimationFrame || window.mozRequestAnimationFrame ||
+        window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
+    window.requestAnimationFrame = requestAnimationFrame;
+
+    // Acquire our canvas.
+    var canvas = document.getElementById('graph');
+    ctx = canvas.getContext('2d');
+
+    // Start drawing.
+    reset_draw_state();
+    window.requestAnimationFrame(handler);
+}
+
+function run_one_test()
+{
+    start_test_cycle([activeTest.name]);
+}
+
+function run_all_tests()
+{
+    start_test_cycle(tests.keys());
+}
+
+function start_test_cycle(tests_to_run)
+{
+    // Convert from an iterable to an array for pop.
+    testQueue = [];
+    for (var key of tests_to_run)
+        testQueue.push(key);
+    testState = 'running';
+    testStart = performance.now();
+    gHistogram.clear();
+
+    start_test(testQueue.shift());
+    reset_draw_state();
+}
+
+function start_test(testName)
+{
+    change_active_test(testName);
+    console.log(`Running test: ${testName}`);
+    document.getElementById("test-selection").value = testName;
+}
+
+function end_test(timestamp)
+{
+    document.getElementById("test-progress").textContent = "(not running)";
+    report_test_result(activeTest, gHistogram);
+    gHistogram.clear();
+    console.log(`Ending test ${activeTest.name}`);
+    if (testQueue.length) {
+        start_test(testQueue.shift());
+        testStart = timestamp;
+    } else {
+        testState = 'idle';
+        testStart = 0;
+    }
+    reset_draw_state();
+}
+
+function report_test_result(test, histogram)
+{
+    var resultList = document.getElementById('results-display');
+    var resultElem = document.createElement("div");
+    var score = compute_test_score(histogram);
+    var sparks = compute_test_spark_histogram(histogram);
+    var params = `(${format_units(test.garbagePerFrame)},${format_units(test.garbageTotal)})`;
+    resultElem.innerHTML = `${score.toFixed(3)} ms/s : ${sparks} : ${test.name}${params} - ${test.description}`;
+    resultList.appendChild(resultElem);
+}
+
+// Compute a score based on the total ms we missed frames by per second.
+function compute_test_score(histogram)
+{
+    var score = 0;
+    for (var [delay, count] of histogram) {
+        delay = delay / 100;
+        score += Math.abs((delay - 16.66) * count);
+    }
+    score = score / (testDuration / 1000);
+    return Math.round(score * 1000) / 1000;
+}
+
+// Build a spark-lines histogram for the test results to show with the aggregate score.
+function compute_test_spark_histogram(histogram)
+{
+    var ranges = [
+        [-99999999, 16.6],
+        [16.6, 16.8],
+        [16.8, 25],
+        [25, 33.4],
+        [33.4, 60],
+        [60, 100],
+        [100, 300],
+        [300, 99999999],
+    ];
+    var rescaled = new Map();
+    for (var [delay, count] of histogram) {
+        delay = delay / 100;
+        for (var i = 0; i < ranges.length; ++i) {
+            var low = ranges[i][0];
+            var high = ranges[i][1];
+            if (low <= delay && delay < high) {
+                update_histogram(rescaled, i);
+                break;
+            }
+        }
+    }
+    var total = 0;
+    for (var [i, count] of rescaled)
+        total += count;
+    var sparks = "▁▂▃▄▅▆▇█";
+    var colors = ['#aaaa00', '#007700', '#dd0000', '#ff0000',
+                  '#ff0000', '#ff0000', '#ff0000', '#ff0000'];
+    var line = "";
+    for (var i = 0; i < ranges.length; ++i) {
+        var amt = rescaled.has(i) ? rescaled.get(i) : 0;
+        var spark = sparks.charAt(parseInt(amt/total*8));
+        line += `<span style="color:${colors[i]}">${spark}</span>`;
+    }
+    return line;
+}
+
+function reload_active_test()
+{
+    activeTest.unload();
+    activeTest.load(activeTest.garbageTotal);
+}
+
+function change_active_test(new_test_name)
+{
+    if (activeTest)
+        activeTest.unload();
+    activeTest = tests.get(new_test_name);
+
+    if (!activeTest.garbagePerFrame)
+        activeTest.garbagePerFrame = activeTest.defaultGarbagePerFrame || globalDefaultGarbagePerFrame;
+    if (!activeTest.garbageTotal)
+        activeTest.garbageTotal = activeTest.defaultGarbageTotal || globalDefaultGarbageTotal;
+
+    document.getElementById("garbage-per-frame").value = format_units(activeTest.garbagePerFrame);
+    document.getElementById("garbage-total").value = format_units(activeTest.garbageTotal);
+
+    activeTest.load(activeTest.garbageTotal);
+}
+
+function duration_changed()
+{
+    var durationInput = document.getElementById('test-duration');
+    testDuration = parseInt(durationInput.value) * 1000;
+    console.log(`Updated test duration to: ${testDuration / 1000} seconds`);
+}
+
+function test_changed()
+{
+    var select = document.getElementById("test-selection");
+    console.log(`Switching to test: ${select.value}`);
+    change_active_test(select.value);
+    gHistogram.clear();
+    reset_draw_state();
+}
+
+function parse_units(v)
+{
+    if (v.length == 0)
+        return NaN;
+    var lastChar = v[v.length - 1].toLowerCase();
+    if (!isNaN(parseFloat(lastChar)))
+        return parseFloat(v);
+    var units = parseFloat(v.substr(0, v.length - 1));
+    if (lastChar == "k")
+        return units * 1e3;
+    if (lastChar == "m")
+        return units * 1e6;
+    if (lastChar == "g")
+        return units * 1e9;
+    return NaN;
+}
+
+function format_units(n)
+{
+    n = String(n);
+    if (n.length > 9 && n.substr(-9) == "000000000")
+        return n.substr(0, n.length - 9) + "G";
+    else if (n.length > 9 && n.substr(-6) == "000000")
+        return n.substr(0, n.length - 6) + "M";
+    else if (n.length > 3 && n.substr(-3) == "000")
+        return n.substr(0, n.length - 3) + "K";
+    else
+        return String(n);
+}
+
+function garbage_total_changed()
+{
+    var value = parse_units(document.getElementById('garbage-total').value);
+    if (isNaN(value))
+        return;
+    if (activeTest) {
+        activeTest.garbageTotal = value;
+        console.log(`Updated garbage-total to ${activeTest.garbageTotal} items`);
+        reload_active_test();
+    }
+    gHistogram.clear();
+    reset_draw_state();
+}
+
+function garbage_per_frame_changed()
+{
+    var value = parse_units(document.getElementById('garbage-per-frame').value);
+    if (isNaN(value))
+        return;
+    if (activeTest) {
+        activeTest.garbagePerFrame = value;
+        console.log(`Updated garbage-per-frame to ${activeTest.garbagePerFrame} items`);
+    }
+}
--- a/js/src/devtools/gc-ubench/index.html
+++ b/js/src/devtools/gc-ubench/index.html
@@ -1,428 +1,67 @@
 <html>
 <head>
   <title>GC uBench</title>
   <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
-</head>
-<body onload="onload()" onunload="onunload()">
-
-<!-- Include benchmark modules. -->
-<script>var tests = new Map();</script>
-<script src="benchmarks/noAllocation.js"></script>
-<script src="benchmarks/globalArrayNewObject.js"></script>
-<script src="benchmarks/globalArrayArrayLiteral.js"></script>
-<script src="benchmarks/globalArrayLargeArray.js"></script>
-<script src="benchmarks/globalArrayObjectLiteral.js"></script>
-<script src="benchmarks/globalArrayReallocArray.js"></script>
-
-<!-- Harness implementation. -->
-<script>
-// Per-frame time sampling infra. Also GC'd: hopefully will not perturb things too badly.
-var numSamples = 500;
-var delays = new Array(numSamples);
-var sampleIndex = 0;
-var sampleTime = 16; // ms
-var gHistogram = new Map(); // {ms: count}
-
-// Draw state.
-var stopped = 0;
-var start;
-var prev;
-var ctx;
-
-// Current test state.
-var garbagePerFrame = undefined;
-var garbageTotal = undefined;
-var activeTest = undefined;
-var testDuration = undefined; // ms
-var testState = 'idle';  // One of 'idle' or 'running'.
-var testStart = undefined; // ms
-var testQueue = [];
-
-function xpos(index)
-{
-    return index * 2;
-}
-
-function ypos(delay)
-{
-    var r = 525 - Math.log(delay) * 64;
-    if (r < 5) return 5;
-    return r;
-}
-
-function drawHBar(delay, color, label)
-{
-    ctx.fillStyle = color;
-    ctx.strokeStyle = color;
-    ctx.fillText(label, xpos(numSamples) + 4, ypos(delay) + 3);
-
-    ctx.beginPath();
-    ctx.moveTo(xpos(0), ypos(delay));
-    ctx.lineTo(xpos(numSamples), ypos(delay));
-    ctx.stroke();
-    ctx.strokeStyle = 'rgb(0,0,0)';
-    ctx.fillStyle = 'rgb(0,0,0)';
-}
-
-function drawScale(delay)
-{
-    drawHBar(delay, 'rgb(150,150,150)', `${delay}ms`);
-}
-
-function draw60fps() {
-    drawHBar(1000/60, '#00cf61', '60fps');
-}
-
-function draw30fps() {
-    drawHBar(1000/30, '#cf0061', '30fps');
-}
-
-function drawGraph()
-{
-    ctx.clearRect(0, 0, 1100, 550);
-
-    drawScale(10);
-    draw60fps();
-    drawScale(20);
-    drawScale(30);
-    draw30fps();
-    drawScale(50);
-    drawScale(100);
-    drawScale(200);
-    drawScale(400);
-    drawScale(800);
-
-    var worst = 0, worstpos = 0;
-    ctx.beginPath();
-    for (var i = 0; i < numSamples; i++) {
-        ctx.lineTo(xpos(i), ypos(delays[i]));
-        if (delays[i] >= worst) {
-            worst = delays[i];
-            worstpos = i;
-        }
-    }
-    ctx.stroke();
-
-    ctx.fillStyle = 'rgb(255,0,0)';
-    if (worst)
-        ctx.fillText(''+worst+'ms', xpos(worstpos) - 10, ypos(worst) - 14);
-
-    ctx.beginPath();
-    var where = sampleIndex % numSamples;
-    ctx.arc(xpos(where), ypos(delays[where]), 5, 0, Math.PI*2, true);
-    ctx.fill();
-    ctx.fillStyle = 'rgb(0,0,0)';
-
-    ctx.fillText('Time', 550, 420);
-    ctx.save();
-    ctx.rotate(Math.PI/2);
-    ctx.fillText('Pause between frames (log scale)', 150, -1060);
-    ctx.restore();
-}
-
-function stopstart()
-{
-    if (stopped) {
-        window.requestAnimationFrame(handler);
-        prev = performance.now();
-        start += prev - stopped;
-        document.getElementById('stop').value = 'Pause';
-        stopped = 0;
-    } else {
-        document.getElementById('stop').value = 'Resume';
-        stopped = performance.now();
-    }
-}
-
-function handler(timestamp)
-{
-    if (stopped)
-        return;
-
-    if (testState === 'running' && (timestamp - testStart) > testDuration)
-        end_test(timestamp);
-
-    activeTest.makeGarbage(garbagePerFrame);
-
-    var elt = document.getElementById('data');
-    var delay = timestamp - prev;
-    prev = timestamp;
-
-    // Take the histogram at 10us intervals so that we have enough resolution to capture.
-    // a 16.66[666] target with adequate accuracy.
-    update_histogram(gHistogram, Math.round(delay * 100));
-
-    var t = timestamp - start;
-    var newIndex = Math.round(t / sampleTime);
-    while (sampleIndex < newIndex) {
-        sampleIndex++;
-        delays[sampleIndex % numSamples] = delay;
-    }
-
-    drawGraph();
-    window.requestAnimationFrame(handler);
-}
-
-function update_histogram(histogram, delay)
-{
-    var current = histogram.has(delay) ? histogram.get(delay) : 0;
-    histogram.set(delay, ++current);
-}
-
-function reset_draw_state()
-{
-    for (var i = 0; i < numSamples; i++)
-        delays[i] = 0;
-    start = prev = performance.now();
-    sampleIndex = 0;
-}
-
-function onunload()
-{
-    activeTest.unload();
-    activeTest = undefined;
-}
-
-function onload()
-{
-    // Load initial test duration.
-    duration_changed();
-
-    // Load initial garbage size.
-    garbage_total_changed();
-    garbage_per_frame_changed();
 
-    // Populate the test selection dropdown.
-    var select = document.getElementById("test-selection");
-    for (var [name, test] of tests) {
-        test.name = name;
-        var option = document.createElement("option");
-        option.id = name;
-        option.text = name;
-        option.title = test.description;
-        select.add(option);
-    }
-
-    // Load the initial test.
-    activeTest = tests.get('noAllocation');
-    activeTest.load(garbageTotal);
-
-    // Polyfill rAF.
-    var requestAnimationFrame =
-        window.requestAnimationFrame || window.mozRequestAnimationFrame ||
-        window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
-    window.requestAnimationFrame = requestAnimationFrame;
-
-    // Acquire our canvas.
-    var canvas = document.getElementById('graph');
-    ctx = canvas.getContext('2d');
-
-    // Start drawing.
-    reset_draw_state();
-    window.requestAnimationFrame(handler);
-}
-
-function run_one_test()
-{
-    start_test_cycle([activeTest.name]);
-}
-
-function run_all_tests()
-{
-    start_test_cycle(tests.keys());
-}
-
-function start_test_cycle(tests_to_run)
-{
-    // Convert from an iterable to an array for pop.
-    testQueue = [];
-    for (var key of tests_to_run)
-        testQueue.push(key);
-    testState = 'running';
-    testStart = performance.now();
-    gHistogram.clear();
-
-    change_active_test(testQueue.pop());
-    console.log(`Running test: ${activeTest.name}`);
-    reset_draw_state();
-}
-
-function end_test(timestamp)
-{
-    report_test_result(activeTest, gHistogram);
-    gHistogram.clear();
-    console.log(`Ending test ${activeTest.name}`);
-    if (testQueue.length) {
-        change_active_test(testQueue.pop());
-        console.log(`Running test: ${activeTest.name}`);
-        testStart = timestamp;
-    } else {
-        testState = 'idle';
-        testStart = 0;
-    }
-    reset_draw_state();
-}
-
-function report_test_result(test, histogram)
-{
-    var resultList = document.getElementById('results-display');
-    var resultElem = document.createElement("div");
-    var score = compute_test_score(histogram);
-    var sparks = compute_test_spark_histogram(histogram);
-    resultElem.innerHTML = `${score} ms/s : ${sparks} : ${test.name} - ${test.description}`;
-    resultList.appendChild(resultElem);
-}
-
-// Compute a score based on the total ms we missed frames by per second.
-function compute_test_score(histogram)
-{
-    var score = 0;
-    for (var [delay, count] of histogram) {
-        delay = delay / 100;
-        score += Math.abs((delay - 16.66) * count);
-    }
-    score = score / (testDuration / 1000);
-    return Math.round(score * 1000) / 1000;
-}
+  <!-- Include benchmark modules. -->
+  <script>var tests = new Map();</script>
+  <script src="benchmarks/noAllocation.js"></script>
+  <script src="benchmarks/globalArrayNewObject.js"></script>
+  <script src="benchmarks/globalArrayArrayLiteral.js"></script>
+  <script src="benchmarks/globalArrayLargeArray.js"></script>
+  <script src="benchmarks/globalArrayLargeObject.js"></script>
+  <script src="benchmarks/globalArrayObjectLiteral.js"></script>
+  <script src="benchmarks/globalArrayReallocArray.js"></script>
+  <script src="benchmarks/globalArrayBuffer.js"></script>
+  <script src="benchmarks/globalArrayFgFinalized.js"></script>
+  <script src="benchmarks/selfCyclicWeakMap.js"></script>
+  <script src="benchmarks/pairCyclicWeakMap.js"></script>
+  <script src="benchmarks/textNodes.js"></script>
+  <script src="benchmarks/bigTextNodes.js"></script>
+  <script src="benchmarks/events.js"></script>
+  <script src="benchmarks/expandoEvents.js"></script>
 
-// Build a spark-lines histogram for the test results to show with the aggregate score.
-function compute_test_spark_histogram(histogram)
-{
-    var ranges = [
-        [-99999999, 16.6],
-        [16.6, 16.8],
-        [16.8, 25],
-        [25, 33.4],
-        [33.4, 60],
-        [60, 100],
-        [100, 300],
-        [300, 99999999],
-    ];
-    var rescaled = new Map();
-    for (var [delay, count] of histogram) {
-        delay = delay / 100;
-        for (var i = 0; i < ranges.length; ++i) {
-            var low = ranges[i][0];
-            var high = ranges[i][1];
-            if (low <= delay && delay < high) {
-                update_histogram(rescaled, i);
-                break;
-            }
-        }
-    }
-    var total = 0;
-    for (var [i, count] of rescaled)
-        total += count;
-    var sparks = "▁▂▃▄▅▆▇█";
-    var colors = ['#aaaa00', '#007700', '#dd0000', '#ff0000',
-                  '#ff0000', '#ff0000', '#ff0000', '#ff0000'];
-    var line = "";
-    for (var i = 0; i < ranges.length; ++i) {
-        var amt = rescaled.has(i) ? rescaled.get(i) : 0;
-        var spark = sparks.charAt(parseInt(amt/total*8));
-        line += `<span style="color:${colors[i]}">${spark}</span>`;
-    }
-    return line;
-}
-
-function reload_active_test()
-{
-    activeTest.unload();
-    activeTest.load(garbageTotal);
-}
+  <script src="harness.js"></script>
 
-function change_active_test(new_test_name)
-{
-    activeTest.unload();
-    activeTest = tests.get(new_test_name);
-    activeTest.load(garbageTotal);
-}
-
-function duration_changed()
-{
-    var durationInput = document.getElementById('test-duration');
-    testDuration = parseInt(durationInput.value) * 1000;
-    console.log(`Updated test duration to: ${testDuration / 1000} seconds`);
-}
-
-function testchanged()
-{
-    var select = document.getElementById("test-selection");
-    console.log(`Switching to test: ${select.value}`);
-    change_active_test(select.value);
-    gHistogram.clear();
-    reset_draw_state();
-}
+</head>
 
-function garbage_total_changed()
-{
-    var mult = parseInt(document.getElementById('garbage-total-unit').value);
-    var base = parseInt(document.getElementById('garbage-total-size').value);
-    if (isNaN(mult) || isNaN(base))
-        return;
-    garbageTotal = base * mult;
-    console.log(`Updated garbage-total to ${garbageTotal} items`);
-    if (activeTest)
-        reload_active_test();
-    gHistogram.clear();
-    reset_draw_state();
-}
-
-function garbage_per_frame_changed()
-{
-    var mult = parseInt(document.getElementById('garbage-per-frame-unit').value);
-    var base = parseInt(document.getElementById('garbage-per-frame-size').value);
-    if (isNaN(mult) || isNaN(base))
-        return;
-    garbagePerFrame = base * mult;
-    console.log(`Updated garbage-per-frame to ${garbagePerFrame} items`);
-}
-</script>
-
-<script>
-</script>
+<body onload="onload()" onunload="onunload()">
 
 <canvas id="graph" width="1080" height="550" style="padding-left:10px"></canvas>
 
 <div>
     <input type="button" id="stop" value="Pause" onclick="stopstart()"></input>
 </div>
 
 <div>
-    Duration: <input type="text" id="test-duration" size=3 value="8" oninput="duration_changed()"></input>s
+    Duration: <input type="text" id="test-duration" size="3" value="8" onchange="duration_changed()"></input>s
     <input type="button" id="test-one" value="Run Test" onclick="run_one_test()"></input>
     <input type="button" id="test-all" value="Run All Tests" onclick="run_all_tests()"></input>
 </div>
 
 <div>
-    Currently running test:
-    <select id="test-selection" required onchange="testchanged()"></select>
+    Currently running test load:
+    <select id="test-selection" required onchange="test_changed()"></select>
 </div>
 
 <div>
-    Garbage items per frame:
-    <input type="text" id="garbage-per-frame-size" size=5 value="8"
-           oninput="garbage_per_frame_changed()"></input>
-    <select id="garbage-per-frame-unit" required>
-        <option value="1"></option>
-        <option value="1000" selected="selected">K</option>
-        <option value="1000000">M</option>
-    </select>
+    &nbsp;&nbsp;&nbsp;&nbsp;Time remaining: <span id="test-progress">(not running)</span>
+</div
+
+<div>
+    &nbsp;&nbsp;&nbsp;&nbsp;Garbage items per frame:
+    <input type="text" id="garbage-per-frame" size="5" value="8K"
+           onchange="garbage_per_frame_changed()"></input>
 </div>