Merge m-c to b2g-inbound a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 02 Sep 2014 19:45:33 -0700
changeset 203310 fc096d338bc411a2084f4e1b429d3738ca5a395f
parent 203309 c26943fe0bab6f216f178506e173ca34c3ead0ca (current diff)
parent 203197 e58842c764dd10913e29f54e3cecd7654499a830 (diff)
child 203311 54dc449ecb277d2c07258fdb4d9008183adee645
push id48665
push userryanvm@gmail.com
push dateWed, 03 Sep 2014 20:40:15 +0000
treeherdermozilla-inbound@0da762e6868a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone35.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to b2g-inbound a=merge
browser/themes/linux/social/chat-icons.png
browser/themes/osx/social/chat-icons.png
browser/themes/osx/social/chat-icons@2x.png
browser/themes/windows/social/chat-icons.png
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1577,18 +1577,22 @@ pref("shumway.disabled", true);
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 // (This is intentionally on the high side; see bug 746055.)
 pref("image.mem.max_decoded_image_kb", 256000);
 
 // Enable by default development builds up until early beta
 #ifdef EARLY_BETA_OR_EARLIER
 pref("loop.enabled", true);
+pref("loop.throttled", false);
 #else
-pref("loop.enabled", false);
+pref("loop.enabled", true);
+pref("loop.throttled", true);
+pref("loop.soft_start_ticket_number", -1);
+pref("loop.soft_start_hostname", "soft-start.loop-dev.stage.mozaws.net");
 #endif
 
 pref("loop.server", "https://loop.services.mozilla.com");
 pref("loop.seenToS", "unseen");
 pref("loop.legal.ToS_url", "https://accounts.firefox.com/legal/terms");
 pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/");
 pref("loop.do_not_disturb", false);
 pref("loop.ringtone", "chrome://browser/content/loop/shared/sounds/Firefox-Long.ogg");
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -44,16 +44,22 @@ XPCOMUtils.defineLazyModuleGetter(this, 
       if (!Services.prefs.getBoolPref("loop.enabled")) {
         this.toolbarButton.node.hidden = true;
         return;
       }
 
       // Add observer notifications before the service is initialized
       Services.obs.addObserver(this, "loop-status-changed", false);
 
+      // If we're throttled, check to see if it's our turn to be unthrottled
+      if (Services.prefs.getBoolPref("loop.throttled")) {
+        this.toolbarButton.node.hidden = true;
+        MozLoopService.checkSoftStart(this.toolbarButton.node);
+        return;
+      }
 
       MozLoopService.initialize();
       this.updateToolbarState();
     },
 
     uninit: function() {
       Services.obs.removeObserver(this, "loop-status-changed");
     },
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -132,17 +132,19 @@
         PopupNotifications._reshowNotifications(this.content.popupnotificationanchor,
                                                 this.content);
         ]]></body>
       </method>
 
       <method name="swapDocShells">
         <parameter name="aTarget"/>
         <body><![CDATA[
-          aTarget.setAttribute('label', this.contentDocument.title);
+          aTarget.setAttribute("label", this.contentDocument.title);
+          if (this.getAttribute("dark") == "true")
+            aTarget.setAttribute("dark", "true");
           aTarget.src = this.src;
           aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
           aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
           this.content.swapDocShells(aTarget.content);
         ]]></body>
       </method>
 
       <method name="onTitlebarClick">
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -5,16 +5,21 @@
 "use strict";
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 // Invalid auth token as per
 // https://github.com/mozilla-services/loop-server/blob/45787d34108e2f0d87d74d4ddf4ff0dbab23501c/loop/errno.json#L6
 const INVALID_AUTH_TOKEN = 110;
 
+// Ticket numbers are 24 bits in length.
+// The highest valid ticket number is 16777214 (2^24 - 2), so that a "now
+// serving" number of 2^24 - 1 is greater than it.
+const MAX_SOFT_START_TICKET_NUMBER = 16777214;
+
 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/osfile.jsm", this);
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://gre/modules/FxAccountsOAuthClient.jsm");
 
 this.EXPORTED_SYMBOLS = ["MozLoopService"];
@@ -44,16 +49,21 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 
 XPCOMUtils.defineLazyModuleGetter(this, "MozLoopPushHandler",
                                   "resource:///modules/loop/MozLoopPushHandler.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gDNSService",
+                                   "@mozilla.org/network/dns-service;1",
+                                   "nsIDNSService");
+
+
 // The current deferred for the registration process. This is set if in progress
 // or the registration was successful. This is null if a registration attempt was
 // unsuccessful.
 let gRegisteredDeferred = null;
 let gPushHandler = null;
 let gHawkClient = null;
 let gRegisteredLoopServer = false;
 let gLocalizedStrings =  null;
@@ -449,16 +459,18 @@ let MozLoopServiceInternal = {
       // in about:blank and then get lost.
       // Sadly we can't use chatbox.promiseChatLoaded() as promise chaining
       // involves event loop spins, which means it might be too late.
       // Have we already done it?
       if (chatbox.contentWindow.navigator.mozLoop) {
         return;
       }
 
+      chatbox.setAttribute("dark", true);
+
       chatbox.addEventListener("DOMContentLoaded", function loaded(event) {
         if (event.target != chatbox.contentDocument) {
           return;
         }
         chatbox.removeEventListener("DOMContentLoaded", loaded, true);
 
         let window = chatbox.contentWindow;
         injectLoopAPI(window);
@@ -627,51 +639,157 @@ this.MozLoopService = {
 
   resetFxA: function() {
     gFxAOAuthClientPromise = null;
     gFxAOAuthClient = null;
     gFxAOAuthTokenData = null;
   },
 #endif
 
+  _DNSService: gDNSService,
+
   set initializeTimerFunc(value) {
     gInitializeTimerFunc = value;
   },
 
   /**
    * Initialized the loop service, and starts registration with the
    * push and loop servers.
    */
   initialize: function() {
     // Don't do anything if loop is not enabled.
-    if (!Services.prefs.getBoolPref("loop.enabled")) {
+    if (!Services.prefs.getBoolPref("loop.enabled") ||
+        Services.prefs.getBoolPref("loop.throttled")) {
       return;
     }
 
     // If expiresTime is in the future then kick-off registration.
     if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
       gInitializeTimerFunc();
     }
   },
 
   /**
+   * If we're operating the service in "soft start" mode, and this browser
+   * isn't already activated, check whether it's time for it to become active.
+   * If so, activate the loop service.
+   *
+   * @param {Object} buttonNode DOM node representing the Loop button -- if we
+   *                            change from inactive to active, we need this
+   *                            in order to unhide the Loop button.
+   * @param {Function} doneCb   [optional] Callback that is called when the
+   *                            check has completed.
+   */
+  checkSoftStart(buttonNode, doneCb) {
+    if (!Services.prefs.getBoolPref("loop.throttled")) {
+      if (typeof(doneCb) == "function") {
+        doneCb(new Error("Throttling is not active"));
+      }
+      return;
+    }
+
+    if (Services.io.offline) {
+      if (typeof(doneCb) == "function") {
+        doneCb(new Error("Cannot check soft-start value: browser is offline"));
+      }
+      return;
+    }
+
+    let ticket = Services.prefs.getIntPref("loop.soft_start_ticket_number");
+    if (!ticket || ticket > MAX_SOFT_START_TICKET_NUMBER || ticket < 0) {
+      // Ticket value isn't valid (probably isn't set up yet) -- pick a random
+      // number from 1 to MAX_SOFT_START_TICKET_NUMBER, inclusive, and write it
+      // into prefs.
+      ticket = Math.floor(Math.random() * MAX_SOFT_START_TICKET_NUMBER) + 1;
+      // Floating point numbers can be imprecise, so we need to deal with
+      // the case that Math.random() effectively rounds to 1.0
+      if (ticket > MAX_SOFT_START_TICKET_NUMBER) {
+        ticket = MAX_SOFT_START_TICKET_NUMBER;
+      }
+      Services.prefs.setIntPref("loop.soft_start_ticket_number", ticket);
+    }
+
+    let onLookupComplete = (request, record, status) => {
+      // We don't bother checking errors -- if the DNS query fails,
+      // we just don't activate this time around. We'll check again on
+      // next startup.
+      if (!Components.isSuccessCode(status)) {
+        if (typeof(doneCb) == "function") {
+          doneCb(new Error("Error in DNS Lookup: " + status));
+        }
+        return;
+      }
+
+      let address = record.getNextAddrAsString().split(".");
+      if (address.length != 4) {
+        if (typeof(doneCb) == "function") {
+          doneCb(new Error("Invalid IP address"));
+        }
+        return;
+      }
+
+      if (address[0] != 127) {
+        if (typeof(doneCb) == "function") {
+          doneCb(new Error("Throttling IP address is not on localhost subnet"));
+        }
+        return
+      }
+
+      // Can't use bitwise operations here because JS treats all bitwise
+      // operations as 32-bit *signed* integers.
+      let now_serving = ((parseInt(address[1]) * 0x10000) +
+                         (parseInt(address[2]) * 0x100) +
+                         parseInt(address[3]));
+
+      if (now_serving > ticket) {
+        // Hot diggity! It's our turn! Activate the service.
+        console.log("MozLoopService: Activating Loop via soft-start");
+        Services.prefs.setBoolPref("loop.throttled", false);
+        buttonNode.hidden = false;
+        this.initialize();
+      }
+      if (typeof(doneCb) == "function") {
+        doneCb(null);
+      }
+    };
+
+    // We use DNS to propagate the slow-start value, since it has well-known
+    // scaling properties. Ideally, this would use something more semantic,
+    // like a TXT record; but we don't support TXT in our DNS resolution (see
+    // Bug 14328), so we instead treat the lowest 24 bits of the IP address
+    // corresponding to our "slow start DNS name" as a 24-bit integer. To
+    // ensure that these addresses aren't routable, the highest 8 bits must
+    // be "127" (reserved for localhost).
+    let host = Services.prefs.getCharPref("loop.soft_start_hostname");
+    let task = this._DNSService.asyncResolve(host,
+                                             this._DNSService.RESOLVE_DISABLE_IPV6,
+                                             onLookupComplete,
+                                             Services.tm.mainThread);
+  },
+
+
+  /**
    * Starts registration of Loop with the push server, and then will register
    * with the Loop server. It will return early if already registered.
    *
    * @param {Object} mockPushHandler Optional, test-only mock push handler. Used
    *                                 to allow mocking of the MozLoopPushHandler.
    * @returns {Promise} a promise that is resolved with no params on completion, or
    *          rejected with an error code or string.
    */
   register: function(mockPushHandler) {
     // Don't do anything if loop is not enabled.
     if (!Services.prefs.getBoolPref("loop.enabled")) {
       throw new Error("Loop is not enabled");
     }
 
+    if (Services.prefs.getBoolPref("loop.throttled")) {
+      throw new Error("Loop is disabled by the soft-start mechanism");
+    }
+
     return MozLoopServiceInternal.promiseRegisteredWithServers(mockPushHandler);
   },
 
   /**
    * Used to note a call url expiry time. If the time is later than the current
    * latest expiry time, then the stored expiry time is increased. For times
    * sooner, this function is a no-op; this ensures we always have the latest
    * expiry time for a url.
--- a/browser/components/loop/test/mochitest/browser.ini
+++ b/browser/components/loop/test/mochitest/browser.ini
@@ -7,11 +7,12 @@ support-files =
 [browser_CardDavImporter.js]
 [browser_fxa_login.js]
 skip-if = !debug
 [browser_loop_fxa_server.js]
 [browser_LoopContacts.js]
 [browser_mozLoop_appVersionInfo.js]
 [browser_mozLoop_prefs.js]
 [browser_mozLoop_doNotDisturb.js]
+[browser_mozLoop_softStart.js]
 skip-if = buildapp == 'mulet'
 [browser_toolbarbutton.js]
 [browser_mozLoop_pluralStrings.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/mochitest/browser_mozLoop_softStart.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const SOFT_START_HOSTNAME = "soft-start.example.invalid";
+
+let MockDNSService = {
+  RESOLVE_DISABLE_IPV6: 32,
+  nowServing: 0,
+  resultCode: 0,
+  ipFirstOctet: 127,
+
+  getNowServingAddress: function() {
+    let ip = this.ipFirstOctet + "." +
+             ((this.nowServing >>> 16) & 0xFF) + "." +
+             ((this.nowServing >>> 8) & 0xFF) + "." +
+             ((this.nowServing) & 0xFF);
+    info("Using 'now serving' of " + this.nowServing + " (" + ip + ")");
+    return ip;
+  },
+
+  asyncResolve: function(host, flags, callback) {
+    let mds = this;
+    Assert.equal(flags, this.RESOLVE_DISABLE_IPV6,
+                 "AAAA lookup should be disabled");
+    Assert.equal(host, SOFT_START_HOSTNAME,
+                 "Configured hostname should be used");
+    callback(null,
+             {getNextAddrAsString: mds.getNowServingAddress.bind(mds)},
+             this.resultCode);
+  }
+};
+
+// We need an unfrozen copy of the LoopService so we can monkeypatch it.
+let LoopService = {};
+for (var prop in MozLoopService) {
+  if (MozLoopService.hasOwnProperty(prop)) {
+    LoopService[prop] = MozLoopService[prop];
+  }
+}
+LoopService._DNSService = MockDNSService;
+
+let MockButton = {
+  hidden: true
+};
+
+let runCheck = function(expectError) {
+  return new Promise((resolve, reject) => {
+    LoopService.checkSoftStart(MockButton, error => {
+      if ((!!error) != (!!expectError)) {
+        reject(error);
+      } else {
+        resolve(error);
+      }
+    })
+  });
+}
+
+add_task(function* test_mozLoop_softStart() {
+  // Set associated variables to proper values
+  Services.prefs.setBoolPref("loop.throttled", true);
+  Services.prefs.setCharPref("loop.soft_start_hostname", SOFT_START_HOSTNAME);
+  Services.prefs.setIntPref("loop.soft_start_ticket_number", -1);
+
+  let throttled;
+  let ticket;
+
+  info("Ensure that we pick a valid ticket number.");
+  yield runCheck();
+  throttled = Services.prefs.getBoolPref("loop.throttled");
+  ticket = Services.prefs.getIntPref("loop.soft_start_ticket_number");
+  Assert.equal(MockButton.hidden, true, "Button should still be hidden");
+  Assert.equal(throttled, true, "Feature should still be throttled");
+  Assert.notEqual(ticket, -1, "Ticket should be changed");
+  Assert.ok((ticket < 16777214 && ticket > 0), "Ticket should be in range");
+
+  // Try some "interesting" ticket numbers
+  for (ticket of [1, 256, 65535, 10000000, 16777214]) {
+    MockButton.hidden = true;
+    Services.prefs.setBoolPref("loop.throttled", true);
+    Services.prefs.setBoolPref("loop.soft_start", true);
+    Services.prefs.setIntPref("loop.soft_start_ticket_number", ticket);
+
+    info("Ensure that we don't activate when the now serving " +
+         "number is less than our value.");
+    MockDNSService.nowServing = ticket - 1;
+    yield runCheck();
+    throttled = Services.prefs.getBoolPref("loop.throttled");
+    Assert.equal(MockButton.hidden, true, "Button should still be hidden");
+    Assert.equal(throttled, true, "Feature should still be throttled");
+
+    info("Ensure that we don't activate when the now serving " +
+         "number is equal to our value");
+    MockDNSService.nowServing = ticket;
+    yield runCheck();
+    throttled = Services.prefs.getBoolPref("loop.throttled");
+    Assert.equal(MockButton.hidden, true, "Button should still be hidden");
+    Assert.equal(throttled, true, "Feature should still be throttled");
+
+    info("Ensure that we *do* activate when the now serving " +
+         "number is greater than our value");
+    MockDNSService.nowServing = ticket + 1;
+    yield runCheck();
+    throttled = Services.prefs.getBoolPref("loop.throttled");
+    Assert.equal(MockButton.hidden, false, "Button should not be hidden");
+    Assert.equal(throttled, false, "Feature should be unthrottled");
+  }
+
+  info("Check DNS error behavior");
+  MockDNSService.nowServing = 0;
+  MockDNSService.resultCode = 0x80000000;
+  Services.prefs.setBoolPref("loop.throttled", true);
+  Services.prefs.setBoolPref("loop.soft_start", true);
+  MockButton.hidden = true;
+  yield runCheck(true);
+  throttled = Services.prefs.getBoolPref("loop.throttled");
+  Assert.equal(MockButton.hidden, true, "Button should be hidden");
+  Assert.equal(throttled, true, "Feature should be throttled");
+
+  info("Check DNS misconfiguration behavior");
+  MockDNSService.nowServing = ticket + 1;
+  MockDNSService.resultCode = 0;
+  MockDNSService.ipFirstOctet = 6;
+  Services.prefs.setBoolPref("loop.throttled", true);
+  Services.prefs.setBoolPref("loop.soft_start", true);
+  MockButton.hidden = true;
+  yield runCheck(true);
+  throttled = Services.prefs.getBoolPref("loop.throttled");
+  Assert.equal(MockButton.hidden, true, "Button should be hidden");
+  Assert.equal(throttled, true, "Feature should be throttled");
+});
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -151,17 +151,17 @@ browser.jar:
   skin/classic/browser/preferences/in-content/icons.png       (../shared/incontentprefs/icons.png)
   skin/classic/browser/preferences/in-content/icons@2x.png    (../shared/incontentprefs/icons@2x.png)
   skin/classic/browser/preferences/applications.css   (preferences/applications.css)
   skin/classic/browser/preferences/aboutPermissions.css (preferences/aboutPermissions.css)
   skin/classic/browser/social/services-16.png         (social/services-16.png)
   skin/classic/browser/social/services-64.png         (social/services-64.png)
   skin/classic/browser/social/share-button.png        (social/share-button.png)
   skin/classic/browser/social/share-button-active.png (social/share-button-active.png)
-  skin/classic/browser/social/chat-icons.png          (social/chat-icons.png)
+  skin/classic/browser/social/chat-icons.svg          (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png        (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png        (../shared/social/gear_clicked.png)
   skin/classic/browser/tabbrowser/alltabs.png         (tabbrowser/alltabs.png)
   skin/classic/browser/tabbrowser/alltabs-inverted.png (tabbrowser/alltabs-inverted.png)
   skin/classic/browser/tabbrowser/connecting.png      (tabbrowser/connecting.png)
   skin/classic/browser/tabbrowser/loading.png         (tabbrowser/loading.png)
   skin/classic/browser/tabbrowser/tab-active-middle.png     (tabbrowser/tab-active-middle.png)
   skin/classic/browser/tabbrowser/tab-arrow-left.png        (tabbrowser/tab-arrow-left.png)
deleted file mode 100644
index cc895f93c29f2b3601ddd1af3bf3a9a042eda15c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -255,18 +255,17 @@ browser.jar:
   skin/classic/browser/preferences/in-content/icons.png       (../shared/incontentprefs/icons.png)
   skin/classic/browser/preferences/in-content/icons@2x.png    (../shared/incontentprefs/icons@2x.png)
   skin/classic/browser/preferences/applications.css         (preferences/applications.css)
   skin/classic/browser/preferences/aboutPermissions.css     (preferences/aboutPermissions.css)
   skin/classic/browser/social/services-16.png               (social/services-16.png)
   skin/classic/browser/social/services-16@2x.png            (social/services-16@2x.png)
   skin/classic/browser/social/services-64.png               (social/services-64.png)
   skin/classic/browser/social/services-64@2x.png            (social/services-64@2x.png)
-  skin/classic/browser/social/chat-icons.png                             (social/chat-icons.png)
-  skin/classic/browser/social/chat-icons@2x.png                          (social/chat-icons@2x.png)
+  skin/classic/browser/social/chat-icons.svg                             (../shared/social/chat-icons.svg)
   skin/classic/browser/social/gear_default.png                           (../shared/social/gear_default.png)
   skin/classic/browser/social/gear_clicked.png                           (../shared/social/gear_clicked.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon.png             (tabbrowser/alltabs-box-bkgnd-icon.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted.png    (tabbrowser/alltabs-box-bkgnd-icon-inverted.png)
   skin/classic/browser/tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png (tabbrowser/alltabs-box-bkgnd-icon-inverted@2x.png)
   skin/classic/browser/tabbrowser/newtab.png                             (tabbrowser/newtab.png)
   skin/classic/browser/tabbrowser/newtab@2x.png                          (tabbrowser/newtab@2x.png)
   skin/classic/browser/tabbrowser/newtab-inverted.png                    (tabbrowser/newtab-inverted.png)
deleted file mode 100644
index cc895f93c29f2b3601ddd1af3bf3a9a042eda15c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 4afab888b14686c8856f651928f20bc6776f92aa..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/social/chat-icons.svg
@@ -0,0 +1,45 @@
+<?xml version="1.0"?>
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     x="0px" y="0px"
+     viewBox="-3 -3 16 16"
+     enable-background="new 0 0 16 16"
+     xml:space="preserve">
+<style>
+use:not(:target) {
+  display: none;
+}
+
+use {
+  fill: #c1c1c1;
+}
+
+use[id$="-active"] {
+  fill: #c1c1c1;
+}
+
+use[id$="-disabled"] {
+  fill: #c1c1c1;
+}
+</style>
+<defs>
+  <polygon id="close-shape" fill-rule="evenodd" clip-rule="evenodd" points="10,1.717 8.336,0.049 5.024,3.369 1.663,0 0,1.668
+    3.36,5.037 0.098,8.307 1.762,9.975 5.025,6.705 8.311,10 9.975,8.332 6.688,5.037"/>
+  <path id="dropdown-shape" fill-rule="evenodd" clip-rule="evenodd" d="M9,3L4.984,7L1,3H9z"/>
+  <polygon id="expand-shape" fill-rule="evenodd" clip-rule="evenodd" points="10,0 4.838,0 6.506,1.669 0,8.175 1.825,10 8.331,3.494
+    10,5.162"/>
+  <rect id="minimize-shape" y="3.6" fill-rule="evenodd" clip-rule="evenodd" width="10" height="2.8"/>
+</defs>
+<use id="close"               xlink:href="#close-shape"/>
+<use id="close-active"        xlink:href="#close-shape"/>
+<use id="close-disabled"      xlink:href="#close-shape"/>
+<use id="expand"              xlink:href="#expand-shape"/>
+<use id="expand-active"       xlink:href="#expand-shape"/>
+<use id="expand-disabled"     xlink:href="#expand-shape"/>
+<use id="minimize"            xlink:href="#minimize-shape"/>
+<use id="minimize-active"     xlink:href="#minimize-shape"/>
+<use id="minimize-disabled"   xlink:href="#minimize-shape"/>
+</svg>
--- a/browser/themes/shared/social/chat.inc.css
+++ b/browser/themes/shared/social/chat.inc.css
@@ -42,150 +42,80 @@
   max-height: 16px;
   max-width: 16px;
   padding: 0;
 }
 
 .chat-toolbarbutton {
   -moz-appearance: none;
   border: none;
-  padding: 0;
+  padding: 0 3px;
   margin: 0;
   background: none;
-  width: 16px;
+}
+
+.chat-toolbarbutton:hover {
+  background-color: rgba(255,255,255,.35);
+}
+
+.chat-toolbarbutton:hover:active {
+  background-color: rgba(255,255,255,.5);
 }
 
 .chat-toolbarbutton > .toolbarbutton-text {
   display: none;
 }
 
 .chat-toolbarbutton > .toolbarbutton-icon {
-  width: inherit;
+  width: 16px;
+  height: 16px;
 }
 
 .chat-close-button {
-  list-style-image: url('chrome://browser/skin/social/chat-icons.png');
-  -moz-image-region: rect(0, 16px, 16px, 0);
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#close");
 }
 
-.chat-close-button:hover {
-  -moz-image-region: rect(0, 32px, 16px, 16px);
-}
-
-.chat-close-button:hover:active {
-  -moz-image-region: rect(0, 48px, 16px, 32px);
+.chat-close-button:-moz-any(:hover,:hover:active) {
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#close-active");
 }
 
 .chat-minimize-button {
-  list-style-image: url('chrome://browser/skin/social/chat-icons.png');
-  -moz-image-region: rect(16px, 16px, 32px, 0);
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize");
 }
 
-.chat-minimize-button:hover {
-  -moz-image-region: rect(16px, 32px, 32px, 16px);
-}
-
-.chat-minimize-button:hover:active {
-  -moz-image-region: rect(16px, 48px, 32px, 32px);
+.chat-minimize-button:-moz-any(:hover,:hover:active) {
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#minimize-active");
 }
 
 .chat-swap-button {
-  list-style-image: url('chrome://browser/skin/social/chat-icons.png');
-  -moz-image-region: rect(48px, 16px, 64px, 0);
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand");
+  transform: rotate(180deg);
 }
 
-.chat-swap-button:hover {
-  -moz-image-region: rect(48px, 32px, 64px, 16px);
-}
-
-.chat-swap-button:hover:active {
-  -moz-image-region: rect(48px, 48px, 64px, 32px);
+.chat-swap-button:-moz-any(:hover,:hover:active) {
+  list-style-image: url("chrome://browser/skin/social/chat-icons.svg#expand-active");
 }
 
 chatbar > chatbox > .chat-titlebar > .chat-swap-button {
-  list-style-image: url('chrome://browser/skin/social/chat-icons.png');
-  -moz-image-region: rect(32px, 16px, 48px, 0);
-}
-
-chatbar > chatbox > .chat-titlebar > .chat-swap-button:hover {
-  -moz-image-region: rect(32px, 32px, 48px, 16px);
-}
-
-chatbar > chatbox > .chat-titlebar > .chat-swap-button:hover:active {
-  -moz-image-region: rect(32px, 48px, 48px, 32px);
-}
-
-@media (min-resolution: 2dppx) {
-  .chat-close-button {
-    list-style-image: url('chrome://browser/skin/social/chat-icons@2x.png');
-    -moz-image-region: rect(0, 32px, 32px, 0);
-  }
-
-  .chat-close-button:hover {
-    -moz-image-region: rect(0, 64px, 32px, 32px);
-  }
-
-  .chat-close-button:hover:active {
-    -moz-image-region: rect(0, 96px, 32px, 64px);
-  }
-
-  .chat-minimize-button {
-    list-style-image: url('chrome://browser/skin/social/chat-icons@2x.png');
-    -moz-image-region: rect(32px, 32px, 64px, 0);
-  }
-
-  .chat-minimize-button:hover {
-    -moz-image-region: rect(32px, 64px, 64px, 32px);
-  }
-
-  .chat-minimize-button:hover:active {
-    -moz-image-region: rect(32px, 96px, 64px, 64px);
-  }
-
-  .chat-swap-button {
-    list-style-image: url('chrome://browser/skin/social/chat-icons@2x.png');
-    -moz-image-region: rect(96px, 32px, 128px, 0);
-  }
-
-  .chat-swap-button:hover {
-    -moz-image-region: rect(96px, 64px, 128px, 32px);
-  }
-
-  .chat-swap-button:hover:active {
-    -moz-image-region: rect(96px, 96px, 128px, 64px);
-  }
-
-  chatbar > chatbox > .chat-titlebar > .chat-swap-button {
-    list-style-image: url('chrome://browser/skin/social/chat-icons@2x.png');
-    -moz-image-region: rect(64px, 32px, 96px, 0);
-  }
-
-  chatbar > chatbox > .chat-titlebar > .chat-swap-button:hover {
-    -moz-image-region: rect(64px, 64px, 96px, 32px);
-  }
-
-  chatbar > chatbox > .chat-titlebar > .chat-swap-button:hover:active {
-    -moz-image-region: rect(64px, 96px, 96px, 64px);
-  }
+  transform: none;
 }
 
 .chat-title {
   font-weight: bold;
   color: black;
   text-shadow: none;
   cursor: inherit;
 }
 
 .chat-titlebar {
-  height: 20px;
-  min-height: 20px;
+  height: 30px;
+  min-height: 30px;
   width: 100%;
   margin: 0;
-  padding: 2px;
-  -moz-padding-start: 6px;
+  padding: 7px 6px;
   border: none;
   border-bottom: 1px solid #ccc;
   cursor: pointer;
 }
 
 .chat-titlebar > .notification-anchor-icon {
   margin-left: 2px;
   margin-right: 2px;
@@ -197,16 +127,28 @@ chatbar > chatbox > .chat-titlebar > .ch
 
 .chat-titlebar[activity] {
   background-image: radial-gradient(ellipse closest-side at center, rgb(255,255,255), rgba(255,255,255,0));
   background-repeat: no-repeat;
   background-size: 100% 20px;
   background-position: 0 -10px;
 }
 
+chatbox[dark=true] > .chat-titlebar,
+chatbox[dark=true] > .chat-titlebar[selected] {
+  border-bottom: none;
+  background-color: #000;
+  background-image: none;
+}
+
+chatbox[dark=true] > .chat-titlebar > hbox > .chat-title {
+  font-weight: normal;
+  color: #c1c1c1;
+}
+
 .chat-frame {
   padding: 0;
   margin: 0;
   overflow: hidden;
 }
 
 .chatbar-button {
   list-style-image: url("chrome://browser/skin/social/services-16.png");
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -177,17 +177,17 @@ browser.jar:
 *       skin/classic/browser/preferences/in-content/preferences.css  (preferences/in-content/preferences.css)
         skin/classic/browser/preferences/in-content/favicon.ico      (../shared/incontentprefs/favicon.ico)
         skin/classic/browser/preferences/in-content/icons.png        (../shared/incontentprefs/icons.png)
         skin/classic/browser/preferences/in-content/icons@2x.png     (../shared/incontentprefs/icons@2x.png)
         skin/classic/browser/preferences/applications.css            (preferences/applications.css)
         skin/classic/browser/preferences/aboutPermissions.css        (preferences/aboutPermissions.css)
         skin/classic/browser/social/services-16.png                  (social/services-16.png)
         skin/classic/browser/social/services-64.png                  (social/services-64.png)
-        skin/classic/browser/social/chat-icons.png                   (social/chat-icons.png)
+        skin/classic/browser/social/chat-icons.svg                   (../shared/social/chat-icons.svg)
         skin/classic/browser/social/gear_default.png                 (../shared/social/gear_default.png)
         skin/classic/browser/social/gear_clicked.png                 (../shared/social/gear_clicked.png)
         skin/classic/browser/tabbrowser/newtab.png                   (tabbrowser/newtab.png)
         skin/classic/browser/tabbrowser/newtab-inverted.png          (tabbrowser/newtab-inverted.png)
         skin/classic/browser/tabbrowser/connecting.png               (tabbrowser/connecting.png)
         skin/classic/browser/tabbrowser/loading.png                  (tabbrowser/loading.png)
         skin/classic/browser/tabbrowser/tab-active-middle.png        (tabbrowser/tab-active-middle.png)
         skin/classic/browser/tabbrowser/tab-active-middle@2x.png     (tabbrowser/tab-active-middle@2x.png)
@@ -596,17 +596,17 @@ browser.jar:
 *       skin/classic/aero/browser/preferences/in-content/preferences.css (preferences/in-content/preferences.css)
         skin/classic/aero/browser/preferences/in-content/favicon.ico      (../shared/incontentprefs/favicon.ico)
         skin/classic/aero/browser/preferences/in-content/icons.png       (../shared/incontentprefs/icons.png)
         skin/classic/aero/browser/preferences/in-content/icons@2x.png    (../shared/incontentprefs/icons@2x.png)
         skin/classic/aero/browser/preferences/applications.css       (preferences/applications.css)
         skin/classic/aero/browser/preferences/aboutPermissions.css   (preferences/aboutPermissions.css)
         skin/classic/aero/browser/social/services-16.png             (social/services-16.png)
         skin/classic/aero/browser/social/services-64.png             (social/services-64.png)
-        skin/classic/aero/browser/social/chat-icons.png              (social/chat-icons.png)
+        skin/classic/aero/browser/social/chat-icons.svg              (../shared/social/chat-icons.svg)
         skin/classic/aero/browser/social/gear_default.png            (../shared/social/gear_default.png)
         skin/classic/aero/browser/social/gear_clicked.png            (../shared/social/gear_clicked.png)
         skin/classic/aero/browser/tabbrowser/newtab.png              (tabbrowser/newtab.png)
         skin/classic/aero/browser/tabbrowser/newtab-inverted.png     (tabbrowser/newtab-inverted.png)
         skin/classic/aero/browser/tabbrowser/connecting.png          (tabbrowser/connecting.png)
         skin/classic/aero/browser/tabbrowser/loading.png             (tabbrowser/loading.png)
         skin/classic/aero/browser/tabbrowser/tab-active-middle.png   (tabbrowser/tab-active-middle.png)
         skin/classic/aero/browser/tabbrowser/tab-active-middle@2x.png (tabbrowser/tab-active-middle@2x.png)
deleted file mode 100644
index cc895f93c29f2b3601ddd1af3bf3a9a042eda15c..0000000000000000000000000000000000000000
GIT binary patch
literal 0
Hc$@<O00001
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -2699,16 +2699,25 @@ EventStateManager::PostHandleEvent(nsPre
       if (mouseEvent->button == WidgetMouseEvent::eLeftButton &&
           !sNormalLMouseEventInProcess) {
         // We got a mouseup event while a mousedown event was being processed.
         // Make sure that the capturing content is cleared.
         nsIPresShell::SetCapturingContent(nullptr, 0);
         break;
       }
 
+      // For remote content, capture the event in the parent process at the
+      // <xul:browser remote> element. This will ensure that subsequent mousemove/mouseup
+      // events will continue to be dispatched to this element and therefore forwarded
+      // to the child.
+      if (dispatchedToContentProcess && !nsIPresShell::GetCapturingContent()) {
+        nsIContent* content = mCurrentTarget ? mCurrentTarget->GetContent() : nullptr;
+        nsIPresShell::SetCapturingContent(content, 0);
+      }
+
       nsCOMPtr<nsIContent> activeContent;
       if (nsEventStatus_eConsumeNoDefault != *aStatus) {
         nsCOMPtr<nsIContent> newFocus;      
         bool suppressBlur = false;
         if (mCurrentTarget) {
           mCurrentTarget->GetContentForEvent(aEvent, getter_AddRefs(newFocus));
           const nsStyleUserInterface* ui = mCurrentTarget->StyleUserInterface();
           activeContent = mCurrentTarget->GetContent();
--- a/mobile/android/base/MediaCastingBar.java
+++ b/mobile/android/base/MediaCastingBar.java
@@ -43,17 +43,17 @@ public class MediaCastingBar extends Rel
 
         mMediaPlay = (ImageButton) content.findViewById(R.id.media_play);
         mMediaPlay.setOnClickListener(this);
         mMediaPause = (ImageButton) content.findViewById(R.id.media_pause);
         mMediaPause.setOnClickListener(this);
         mMediaStop = (ImageButton) content.findViewById(R.id.media_stop);
         mMediaStop.setOnClickListener(this);
 
-        mCastingTo = (TextView) content.findViewById(R.id.media_casting_to);
+        mCastingTo = (TextView) content.findViewById(R.id.media_sending_to);
 
         // Capture clicks on the rest of the view to prevent them from
         // leaking into other views positioned below.
         content.setOnClickListener(this);
 
         mInflated = true;
     }
 
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -296,19 +296,19 @@ size. -->
 
 <!-- Localization note (find_text, find_prev, find_next, find_close) : These strings are used
      as alternate text for accessibility. They are not visible in the UI. -->
 <!ENTITY find_text "Find in Page">
 <!ENTITY find_prev "Previous">
 <!ENTITY find_next "Next">
 <!ENTITY find_close "Close">
 
-<!-- Localization note (media_casting_to, media_play, media_pause, media_stop) : These strings are used
+<!-- Localization note (media_sending_to, media_play, media_pause, media_stop) : These strings are used
      as alternate text for accessibility. They are not visible in the UI. -->
-<!ENTITY media_casting_to "Casting to Device">
+<!ENTITY media_sending_to "Sending to Device">
 <!ENTITY media_play "Play">
 <!ENTITY media_pause "Pause">
 <!ENTITY media_stop "Stop">
 
 <!ENTITY contextmenu_open_new_tab "Open in New Tab">
 <!ENTITY contextmenu_open_private_tab "Open in Private Tab">
 <!ENTITY contextmenu_remove "Remove">
 <!ENTITY contextmenu_add_to_launcher "Add to Home Screen">
--- a/mobile/android/base/resources/layout/media_casting.xml
+++ b/mobile/android/base/resources/layout/media_casting.xml
@@ -14,28 +14,28 @@
 
         <ImageButton android:id="@+id/media_pause"
                      style="@style/FindBar.ImageButton"
                      android:contentDescription="@string/media_pause"
                      android:src="@drawable/media_bar_pause"/>
 
     </RelativeLayout>
 
-    <TextView android:id="@+id/media_casting_to"
+    <TextView android:id="@+id/media_sending_to"
               android:layout_width="wrap_content"
               android:layout_height="wrap_content"
               android:layout_marginLeft="5dip"
               android:layout_marginRight="5dip"
               android:layout_alignParentLeft="true"
               android:layout_toLeftOf="@id/media_controls"
               android:layout_centerVertical="true"
               android:singleLine="true"
               android:ellipsize="end"
               android:textColor="#FFFFFFFF"
-              android:contentDescription="@string/media_casting_to"/>
+              android:contentDescription="@string/media_sending_to"/>
 
     <ImageButton android:id="@+id/media_stop"
                  style="@style/FindBar.ImageButton"
                  android:contentDescription="@string/media_stop"
                  android:layout_alignParentRight="true"
                  android:src="@drawable/media_bar_stop"/>
 
 </merge>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -100,17 +100,17 @@
   <string name="page">&page;</string>
   <string name="tools">&tools;</string>
 
   <string name="find_text">&find_text;</string>
   <string name="find_prev">&find_prev;</string>
   <string name="find_next">&find_next;</string>
   <string name="find_close">&find_close;</string>
 
-  <string name="media_casting_to">&media_casting_to;</string>
+  <string name="media_sending_to">&media_sending_to;</string>
   <string name="media_play">&media_play;</string>
   <string name="media_pause">&media_pause;</string>
   <string name="media_stop">&media_stop;</string>
 
   <string name="overlay_share_send_other">&overlay_share_send_other;</string>
   <string name="overlay_share_label">&overlay_share_label;</string>
   <string name="overlay_share_bookmark_btn_label">&overlay_share_bookmark_btn_label;</string>
   <string name="overlay_share_reading_list_btn_label">&overlay_share_reading_list_btn_label;</string>
--- a/mobile/android/base/tests/robocop_head.js
+++ b/mobile/android/base/tests/robocop_head.js
@@ -1162,17 +1162,17 @@ function JavaBridge(obj) {
 };
 
 JavaBridge.prototype = {
 
   _Services: Components.utils.import(
     "resource://gre/modules/Services.jsm", {}).Services,
 
   _sendMessageToJava: Components.utils.import(
-    "resource://gre/modules/Messaging.jsm", {}).sendMessageToJava,
+    "resource://gre/modules/Messaging.jsm", {}).Messaging.sendRequest,
 
   _sendMessage: function (innerType, args) {
     this._sendMessageToJava({
       type: this._EVENT_TYPE,
       innerType: innerType,
       method: args[0],
       args: Array.prototype.slice.call(args, 1),
     });
--- a/mobile/android/base/tests/roboextender/robocop_home_banner.html
+++ b/mobile/android/base/tests/roboextender/robocop_home_banner.html
@@ -16,30 +16,30 @@ function start() {
 }
 
 var messageId;
 
 function addMessage() {
   messageId = Home.banner.add({
     text: TEXT,
     onclick: function() {
-      sendMessageToJava({ type: "TestHomeBanner:MessageClicked" });
+      Messaging.sendRequest({ type: "TestHomeBanner:MessageClicked" });
     },
     onshown: function() {
-      sendMessageToJava({ type: "TestHomeBanner:MessageShown" });
+      Messaging.sendRequest({ type: "TestHomeBanner:MessageShown" });
     },
     ondismiss: function() {
-      sendMessageToJava({ type: "TestHomeBanner:MessageDismissed" });
+      Messaging.sendRequest({ type: "TestHomeBanner:MessageDismissed" });
     }
   });
-  sendMessageToJava({ type: "TestHomeBanner:MessageAdded" });
+  Messaging.sendRequest({ type: "TestHomeBanner:MessageAdded" });
 }
 
 function removeMessage() {
   Home.banner.remove(messageId);
-  sendMessageToJava({ type: "TestHomeBanner:MessageRemoved" });
+  Messaging.sendRequest({ type: "TestHomeBanner:MessageRemoved" });
 }
 
     </script>
   </head>
   <body onload="start();">
   </body>
 </html>
--- a/mobile/android/base/tests/roboextender/testSelectionHandler.html
+++ b/mobile/android/base/tests/roboextender/testSelectionHandler.html
@@ -208,17 +208,17 @@ function testCloseSelection() {
 }
 
 /* =================================================================================
  *
  * After finish of all selection tests, wrap up and go home.
  *
  */
 function finishTests() {
-  sendMessageToJava({
+  Messaging.sendRequest({
     type: "Robocop:testSelectionHandler",
     result: true,
     msg: "Done!",
     done: true
   });
 }
 
 /* ============================== Utility functions ======================
--- a/mobile/android/base/tests/testEventDispatcher.js
+++ b/mobile/android/base/tests/testEventDispatcher.js
@@ -23,17 +23,17 @@ function send_test_message(type) {
 
   // Make a copy
   let outerObject = JSON.parse(JSON.stringify(innerObject));
 
   outerObject.type = type;
   outerObject.object = innerObject;
   outerObject.objectArray = [null, innerObject];
 
-  sendMessageToJava(outerObject);
+  Messaging.sendRequest(outerObject);
 }
 
 function send_message_for_response(type, response) {
   sendMessageToJava({
     type: type,
     response: response,
   }, (success, error) => {
     if (response === "success") {
--- a/mobile/android/base/tests/testGeckoRequest.js
+++ b/mobile/android/base/tests/testGeckoRequest.js
@@ -3,38 +3,38 @@ Components.utils.import("resource://gre/
 let java = new JavaBridge(this);
 
 do_register_cleanup(() => {
   java.disconnect();
 });
 do_test_pending();
 
 function add_request_listener(message) {
-  RequestService.addListener(function (data) {
+  Messaging.addListener(function (data) {
     return { result: data + "bar" };
   }, message);
 }
 
 function add_exception_listener(message) {
-  RequestService.addListener(function (data) {
+  Messaging.addListener(function (data) {
     throw "error!";
   }, message);
 }
 
 function add_second_request_listener(message) {
   let exceptionCaught = false;
 
   try {
-    RequestService.addListener(() => {}, message);
+    Messaging.addListener(() => {}, message);
   } catch (e) {
     exceptionCaught = true;
   }
 
   do_check_true(exceptionCaught);
 }
 
 function remove_request_listener(message) {
-  RequestService.removeListener(message);
+  Messaging.removeListener(message);
 }
 
 function finish_test() {
   do_test_finished();
 }
--- a/mobile/android/base/util/NativeEventListener.java
+++ b/mobile/android/base/util/NativeEventListener.java
@@ -10,14 +10,14 @@ import org.mozilla.gecko.mozglue.Robocop
 @RobocopTarget
 public interface NativeEventListener {
     /**
      * Handles a message sent from Gecko.
      *
      * @param event    The name of the event being sent.
      * @param message  The message data.
      * @param callback The callback interface for this message. A callback is provided only if the
-     *                 originating sendMessageToJava call included a callback argument; otherwise,
+     *                 originating Messaging.sendRequest call included a callback argument; otherwise,
      *                 callback will be null. All listeners for a given event are given the same
      *                 callback object, and exactly one listener must handle the callback.
      */
     void handleMessage(String event, NativeJSObject message, EventCallback callback);
 }
--- a/mobile/android/chrome/content/CastingApps.js
+++ b/mobile/android/chrome/content/CastingApps.js
@@ -57,17 +57,17 @@ var CastingApps = {
     SimpleServiceDiscovery.registerTarget(rokuTarget);
     SimpleServiceDiscovery.registerTarget(fireflyTarget);
     SimpleServiceDiscovery.registerTarget(mediaPlayerTarget);
 
     // Search for devices continuously every 120 seconds
     SimpleServiceDiscovery.search(120 * 1000);
 
     this._castMenuId = NativeWindow.contextmenus.add(
-      Strings.browser.GetStringFromName("contextmenu.castToScreen"),
+      Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
       this.filterCast,
       this.handleContextMenu.bind(this)
     );
 
     Services.obs.addObserver(this, "Casting:Play", false);
     Services.obs.addObserver(this, "Casting:Pause", false);
     Services.obs.addObserver(this, "Casting:Stop", false);
     Services.obs.addObserver(this, "Casting:Mirror", false);
@@ -424,24 +424,24 @@ var CastingApps = {
     }
 
     // We check for two state here:
     // 1. The video is actively being cast
     // 2. The video is allowed to be cast and is currently playing
     // Both states have the same action: Show the cast page action
     if (aVideo.mozIsCasting) {
       this.pageAction.id = PageActions.add({
-        title: Strings.browser.GetStringFromName("contextmenu.castToScreen"),
+        title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
         icon: "drawable://casting_active",
         clickCallback: this.pageAction.click,
         important: true
       });
     } else if (aVideo.mozAllowCasting) {
       this.pageAction.id = PageActions.add({
-        title: Strings.browser.GetStringFromName("contextmenu.castToScreen"),
+        title: Strings.browser.GetStringFromName("contextmenu.sendToDevice"),
         icon: "drawable://casting",
         clickCallback: this.pageAction.click,
         important: true
       });
     }
   },
 
   prompt: function(aCallback, aFilterFunc) {
@@ -458,17 +458,17 @@ var CastingApps = {
       }
     });
 
     if (items.length == 0) {
       return;
     }
 
     let prompt = new Prompt({
-      title: Strings.browser.GetStringFromName("casting.prompt")
+      title: Strings.browser.GetStringFromName("casting.sendToDevice")
     }).setSingleChoiceItems(items).show(function(data) {
       let selected = data.button;
       let service = selected == -1 ? null : filteredServices[selected];
       if (aCallback)
         aCallback(service);
     });
   },
 
@@ -558,27 +558,27 @@ var CastingApps = {
 
   // RemoteMedia callback API methods
   onRemoteMediaStart: function(aRemoteMedia) {
     if (!this.session) {
       return;
     }
 
     aRemoteMedia.load(this.session.data);
-    sendMessageToJava({ type: "Casting:Started", device: this.session.service.friendlyName });
+    Messaging.sendRequest({ type: "Casting:Started", device: this.session.service.friendlyName });
 
     let video = this.session.videoRef.get();
     if (video) {
       this._sendEventToVideo(video, { active: true });
       this._updatePageAction(video);
     }
   },
 
   onRemoteMediaStop: function(aRemoteMedia) {
-    sendMessageToJava({ type: "Casting:Stopped" });
+    Messaging.sendRequest({ type: "Casting:Stopped" });
     this._shutdown();
   },
 
   onRemoteMediaStatus: function(aRemoteMedia) {
     if (!this.session) {
       return;
     }
 
--- a/mobile/android/chrome/content/PermissionsHelper.js
+++ b/mobile/android/chrome/content/PermissionsHelper.js
@@ -83,17 +83,17 @@ var PermissionsHelper = {
         this._currentPermissions = permissions;
 
         let host;
         try {
           host = uri.host;
         } catch(e) {
           host = uri.spec;
         }
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Permissions:Data",
           host: host,
           permissions: permissions
         });
         break;
  
       case "Permissions:Clear":
         // An array of the indices of the permissions we want to clear
--- a/mobile/android/chrome/content/SelectionHandler.js
+++ b/mobile/android/chrome/content/SelectionHandler.js
@@ -193,40 +193,40 @@ var SelectionHandler = {
         } else {
           Cu.reportError("Ignored \"TextSelection:Position\" message during invalid selection status");
         }
 
         break;
       }
 
       case "TextSelection:Get":
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "TextSelection:Data",
           requestId: aData,
           text: this._getSelectedText()
         });
         break;
     }
   },
 
   // Ignore selectionChange notifications during handle dragging, disable dynamic
   // IME text compositions (autoSuggest, autoCorrect, etc)
   _startDraggingHandles: function sh_startDraggingHandles() {
     if (!this._draggingHandles) {
       this._draggingHandles = true;
-      sendMessageToJava({ type: "TextSelection:DraggingHandle", dragging: true });
+      Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: true });
     }
   },
 
   // Act on selectionChange notifications when not dragging handles, allow dynamic
   // IME text compositions (autoSuggest, autoCorrect, etc)
   _stopDraggingHandles: function sh_stopDraggingHandles() {
     if (this._draggingHandles) {
       this._draggingHandles = false;
-      sendMessageToJava({ type: "TextSelection:DraggingHandle", dragging: false });
+      Messaging.sendRequest({ type: "TextSelection:DraggingHandle", dragging: false });
     }
   },
 
   handleEvent: function sh_handleEvent(aEvent) {
     switch (aEvent.type) {
       case "scroll":
         // Maintain position when top-level document is scrolled
         this._positionHandlesOnChange();
@@ -336,17 +336,17 @@ var SelectionHandler = {
                                                                       scroll.Y + aOptions.y,
                                                                       positions)) {
         this._closeSelection();
         return false;
     }
 
     // Determine position and show handles, open actionbar
     this._positionHandles(positions);
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "TextSelection:ShowHandles",
       handles: [this.HANDLE_TYPE_START, this.HANDLE_TYPE_END]
     });
     this._updateMenu();
     return true;
   },
 
   /*
@@ -541,17 +541,17 @@ var SelectionHandler = {
           order: this._getValue(action, "order", 0)
         };
         actions.push(a);
       }
     }
 
     actions.sort((a, b) => b.order - a.order);
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "TextSelection:Update",
       actions: actions
     });
   },
 
   /*
    * Actionbar methods.
    */
@@ -712,17 +712,17 @@ var SelectionHandler = {
     BrowserApp.deck.addEventListener("keyup", this, false);
     BrowserApp.deck.addEventListener("compositionupdate", this, false);
     BrowserApp.deck.addEventListener("compositionend", this, false);
 
     this._activeType = this.TYPE_CURSOR;
 
     // Determine position and show caret, open actionbar
     this._positionHandles();
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "TextSelection:ShowHandles",
       handles: [this.HANDLE_TYPE_MIDDLE]
     });
     this._updateMenu();
 
     return true;
   },
 
@@ -926,17 +926,17 @@ var SelectionHandler = {
       NativeWindow.toast.show(Strings.browser.GetStringFromName("selectionHelper.textCopied"), "short");
     }
     this._closeSelection();
   },
 
   shareSelection: function sh_shareSelection() {
     let selectedText = this._getSelectedText();
     if (selectedText.length) {
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Share:Text",
         text: selectedText
       });
     }
     this._closeSelection();
   },
 
   searchSelection: function sh_searchSelection() {
@@ -995,17 +995,17 @@ var SelectionHandler = {
         selection.collapseToStart();
       }
     }
   },
 
   _deactivate: function sh_deactivate() {
     this._stopDraggingHandles();
     // Hide handle/caret, close actionbar
-    sendMessageToJava({ type: "TextSelection:HideHandles" });
+    Messaging.sendRequest({ type: "TextSelection:HideHandles" });
 
     this._removeObservers();
 
     // Only observed for caret positioning
     if (this._activeType == this.TYPE_CURSOR) {
       Services.obs.removeObserver(this, "TextSelection:UpdateCaretPos");
       BrowserApp.deck.removeEventListener("keyup", this);
       BrowserApp.deck.removeEventListener("compositionupdate", this);
@@ -1151,17 +1151,17 @@ var SelectionHandler = {
   // Position handles, allow for re-position, in case user drags handle
   // to invalid position, then releases, we can put it back where it started
   // positions is an array of objects with data about handle positions,
   // which we get from _getHandlePositions.
   _positionHandles: function sh_positionHandles(positions) {
     if (!positions) {
       positions = this._getHandlePositions(this._getScrollPos());
     }
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "TextSelection:PositionHandles",
       positions: positions,
       rtl: this._isRTL
     });
     this._prevHandlePositions = positions;
 
     // Text state transitions (text <--> no text) will affect selection context and actionbar display
     let currTargetElementHasText = (this._targetElement.textLength > 0);
--- a/mobile/android/chrome/content/ZoomHelper.js
+++ b/mobile/android/chrome/content/ZoomHelper.js
@@ -32,23 +32,23 @@ var ZoomHelper = {
 
     rect.type = "Browser:ZoomToRect";
     rect.x = Math.max(viewport.cssPageLeft, rect.x  - fudge + leftAdjustment);
     rect.y = Math.max(topPos, viewport.cssPageTop);
     rect.w = viewport.cssWidth;
     rect.h = viewport.cssHeight;
     rect.animate = false;
 
-    sendMessageToJava(rect);
+    Messaging.sendRequest(rect);
     BrowserApp.selectedTab._mReflozPoint = null;
   },
 
   zoomOut: function() {
     BrowserEventHandler.resetMaxLineBoxWidth();
-    sendMessageToJava({ type: "Browser:ZoomToPageWidth" });
+    Messaging.sendRequest({ type: "Browser:ZoomToPageWidth" });
   },
 
   isRectZoomedIn: function(aRect, aViewport) {
     // This function checks to see if the area of the rect visible in the
     // viewport (i.e. the "overlapArea" variable below) is approximately
     // the max area of the rect we can show. It also checks that the rect
     // is actually on-screen by testing the left and right edges of the rect.
     // In effect, this tells us whether or not zooming in to this rect
@@ -140,11 +140,11 @@ var ZoomHelper = {
         rect.y = cssTapY - (rect.h / 2);
       }
     }
 
     if (rect.w > viewport.cssWidth || rect.h > viewport.cssHeight) {
       BrowserEventHandler.resetMaxLineBoxWidth();
     }
 
-    sendMessageToJava(rect);
+    Messaging.sendRequest(rect);
   },
 };
--- a/mobile/android/chrome/content/aboutFeedback.js
+++ b/mobile/android/chrome/content/aboutFeedback.js
@@ -43,17 +43,17 @@ function init() {
 	updateActiveSection(aEvent.state ? aEvent.state.section : "intro")
   }, false);
 
   // Fill "Last visited site" input with most recent history entry URL.
   Services.obs.addObserver(function observer(aSubject, aTopic, aData) {
 	document.getElementById("last-url").value = aData;
   }, "Feedback:LastUrl", false);
 
-  sendMessageToJava({ type: "Feedback:LastUrl" });
+  Messaging.sendRequest({ type: "Feedback:LastUrl" });
 }
 
 function uninit() {
   Services.obs.removeObserver(this, "Feedback:LastUrl");
 }
 
 function switchSection(aSection) {
   history.pushState({ section: aSection }, aSection);
@@ -61,25 +61,25 @@ function switchSection(aSection) {
 }
 
 function updateActiveSection(aSection) {
   document.querySelector("section[active]").removeAttribute("active");
   document.getElementById(aSection).setAttribute("active", true);
 }
 
 function openPlayStore() {
-  sendMessageToJava({ type: "Feedback:OpenPlayStore" });
+  Messaging.sendRequest({ type: "Feedback:OpenPlayStore" });
 
   window.close();
 }
 
 function maybeLater() {
   window.close();
 
-  sendMessageToJava({ type: "Feedback:MaybeLater" });
+  Messaging.sendRequest({ type: "Feedback:MaybeLater" });
 }
 
 function sendFeedback(aEvent) {
   // Prevent the page from reloading.
   aEvent.preventDefault();
 
   let section = history.state.section;
 
--- a/mobile/android/chrome/content/aboutHealthReport.js
+++ b/mobile/android/chrome/content/aboutHealthReport.js
@@ -83,17 +83,17 @@ let healthReportWrapper = {
       this.injectData("prefs", prefs);
     } catch (e) {
       this.reportFailure(this.ERROR_PREFS_FAILED);
     }
   },
 
   refreshPayload: function () {
     console.log("AboutHealthReport: page requested fresh payload.");
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: EVENT_HEALTH_REQUEST,
     });
   },
 
   updatePayload: function (data) {
     healthReportWrapper.injectData("payload", data);
     // Data is supposed to be a string, so the length should be
     // defined.  Just in case, we do this after injecting the data.
@@ -115,25 +115,25 @@ let healthReportWrapper = {
     };
 
     let iframe = document.getElementById("remote-report");
     iframe.contentWindow.postMessage(data, reportUrl);
   },
 
   showSettings: function () {
     console.log("AboutHealthReport: showing settings.");
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Settings:Show",
       resource: "preferences_vendor",
     });
   },
 
   launchUpdater: function () {
     console.log("AboutHealthReport: launching updater.");
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Updater:Launch",
     });
   },
 
   handleRemoteCommand: function (evt) {
     switch (evt.detail.command) {
       case "DisableDataSubmission":
         this.onOptOut();
--- a/mobile/android/chrome/content/aboutReader.js
+++ b/mobile/android/chrome/content/aboutReader.js
@@ -1,14 +1,15 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 let Ci = Components.interfaces, Cc = Components.classes, Cu = Components.utils;
 
+Cu.import("resource://gre/modules/Messaging.jsm");
 Cu.import("resource://gre/modules/Services.jsm")
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
                                   "resource://gre/modules/UITelemetry.jsm");
 
 XPCOMUtils.defineLazyGetter(window, "gChromeWin", function ()
   window.QueryInterface(Ci.nsIInterfaceRequestor)
@@ -303,17 +304,17 @@ AboutReader.prototype = {
     if (this._isReadingListItem == 1) {
       classes.add("on");
     } else {
       classes.remove("on");
     }
   },
 
   _requestReadingListStatus: function Reader_requestReadingListStatus() {
-    gChromeWin.sendMessageToJava({
+    Messaging.sendRequest({
       type: "Reader:ListStatusRequest",
       url: this._article.url
     });
   },
 
   _onReaderToggle: function Reader_onToggle() {
     if (!this._article)
       return;
@@ -330,17 +331,17 @@ AboutReader.prototype = {
         if (success) {
           result = gChromeWin.Reader.READER_ADD_SUCCESS;
           UITelemetry.addEvent("save.1", "button", uptime, "reader");
         }
 
         let json = JSON.stringify({ fromAboutReader: true, url: this._article.url });
         Services.obs.notifyObservers(null, "Reader:Add", json);
 
-        gChromeWin.sendMessageToJava({
+        Messaging.sendRequest({
           type: "Reader:Added",
           result: result,
           title: this._article.title,
           url: this._article.url,
           length: this._article.length,
           excerpt: this._article.excerpt
         });
       }.bind(this));
@@ -354,17 +355,17 @@ AboutReader.prototype = {
       UITelemetry.addEvent("unsave.1", "button", null, "reader");
     }
   },
 
   _onShare: function Reader_onShare() {
     if (!this._article)
       return;
 
-    gChromeWin.sendMessageToJava({
+    Messaging.sendRequest({
       type: "Reader:Share",
       url: this._article.url,
       title: this._article.title
     });
 
     UITelemetry.addEvent("share.1", "list", null);
   },
 
@@ -519,24 +520,24 @@ AboutReader.prototype = {
     }
   },
 
   _toggleToolbarVisibility: function Reader_toggleToolbarVisibility() {
     this._setToolbarVisibility(!this._getToolbarVisibility());
   },
 
   _setBrowserToolbarVisiblity: function Reader_setBrowserToolbarVisiblity(visible) {
-    gChromeWin.sendMessageToJava({
+    Messaging.sendRequest({
       type: "BrowserToolbar:Visibility",
       visible: visible
     });
   },
 
   _setSystemUIVisibility: function Reader_setSystemUIVisibility(visible) {
-    gChromeWin.sendMessageToJava({
+    Messaging.sendRequest({
       type: "SystemUI:Visibility",
       visible: visible
     });
   },
 
   _loadFromURL: function Reader_loadFromURL(url) {
     this._showProgressDelayed();
 
@@ -555,17 +556,17 @@ AboutReader.prototype = {
       if (article)
         this._showContent(article);
       else
         this._showError(gStrings.GetStringFromName("aboutReader.loadError"));
     }.bind(this));
   },
 
   _requestFavicon: function Reader_requestFavicon() {
-    gChromeWin.sendMessageToJava({
+    Messaging.sendRequest({
       type: "Reader:FaviconRequest",
       url: this._article.url
     });
   },
 
   _loadFavicon: function Reader_loadFavicon(url, faviconUrl) {
     if (this._article.url !== url)
       return;
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -22,17 +22,17 @@ Cu.import("resource://gre/modules/UITele
 
 #ifdef ACCESSIBILITY
 Cu.import("resource://gre/modules/accessibility/AccessFu.jsm");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
                                   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "DebuggerServer",
                                   "resource://gre/modules/devtools/dbg-server.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "UserAgentOverrides",
                                   "resource://gre/modules/UserAgentOverrides.jsm");
 
@@ -301,17 +301,17 @@ var BrowserApp = {
     window.QueryInterface(Ci.nsIDOMChromeWindow).browserDOMWindow = new nsBrowserAccess();
     dump("zerdatime " + Date.now() + " - browser chrome startup finished.");
 
     this.deck = document.getElementById("browsers");
     this.deck.addEventListener("DOMContentLoaded", function BrowserApp_delayedStartup() {
       try {
         BrowserApp.deck.removeEventListener("DOMContentLoaded", BrowserApp_delayedStartup, false);
         Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
-        sendMessageToJava({ type: "Gecko:DelayedStartup" });
+        Messaging.sendRequest({ type: "Gecko:DelayedStartup" });
 
         // Queue up some other performance-impacting initializations
         Services.tm.mainThread.dispatch(function() {
           // Init LoginManager
           Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager);
         }, Ci.nsIThread.DISPATCH_NORMAL);
 
 #ifdef MOZ_SAFE_BROWSING
@@ -361,29 +361,29 @@ var BrowserApp = {
     Services.obs.addObserver(this, "Webapps:AutoUninstall", false);
     Services.obs.addObserver(this, "sessionstore-state-purge-complete", false);
 
     function showFullScreenWarning() {
       NativeWindow.toast.show(Strings.browser.GetStringFromName("alertFullScreenToast"), "short");
     }
 
     window.addEventListener("fullscreen", function() {
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: window.fullScreen ? "ToggleChrome:Show" : "ToggleChrome:Hide"
       });
     }, false);
 
     window.addEventListener("mozfullscreenchange", function(e) {
       // This event gets fired on the document and its entire ancestor chain
       // of documents. When enabling fullscreen, it is fired on the top-level
       // document first and goes down; when disabling the order is reversed
       // (per spec). This means the last event on enabling will be for the innermost
       // document, which will have mozFullScreenElement set correctly.
       let doc = e.target;
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: doc.mozFullScreen ? "DOMFullScreen:Start" : "DOMFullScreen:Stop",
         rootElement: (doc.mozFullScreen && doc.mozFullScreenElement == doc.documentElement)
       });
 
       if (doc.mozFullScreen)
         showFullScreenWarning();
     }, false);
 
@@ -456,17 +456,17 @@ var BrowserApp = {
     if (this.isGuest) {
       // Disable extension installs
       Services.prefs.setIntPref("extensions.enabledScopes", 1);
       Services.prefs.setIntPref("extensions.autoDisableScopes", 1);
       Services.prefs.setBoolPref("xpinstall.enabled", false);
     }
 
     // notify java that gecko has loaded
-    sendMessageToJava({ type: "Gecko:Ready" });
+    Messaging.sendRequest({ type: "Gecko:Ready" });
   },
 
   get _startupStatus() {
     delete this._startupStatus;
 
     let savedMilestone = null;
     try {
       savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
@@ -482,17 +482,17 @@ var BrowserApp = {
     return this._startupStatus;
   },
 
   /**
    * Pass this a locale string, such as "fr" or "es_ES".
    */
   setLocale: function (locale) {
     console.log("browser.js: requesting locale set: " + locale);
-    sendMessageToJava({ type: "Locale:Set", locale: locale });
+    Messaging.sendRequest({ type: "Locale:Set", locale: locale });
   },
 
   _initRuntime: function(status, url, callback) {
     let sandbox = {};
     Services.scriptloader.loadSubScript("chrome://browser/content/WebappRT.js", sandbox);
     window.WebappRT = sandbox.WebappRT;
     WebappRT.init(status, url, callback);
   },
@@ -627,42 +627,42 @@ var BrowserApp = {
     });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"),
       NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.emailLinkContext),
       function(aTarget) {
         UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_email");
 
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Contact:Add",
           email: url
         });
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.addToContacts"),
       NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.phoneNumberLinkContext),
       function(aTarget) {
         UITelemetry.addEvent("action.1", "contextmenu", null, "web_contact_phone");
 
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Contact:Add",
           phone: url
         });
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.bookmarkLink"),
       NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.linkBookmarkableContext),
       function(aTarget) {
         UITelemetry.addEvent("action.1", "contextmenu", null, "web_bookmark");
 
         let url = NativeWindow.contextmenus._getLinkURL(aTarget);
         let title = aTarget.textContent || aTarget.title || url;
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Bookmark:Insert",
           url: url,
           title: title
         });
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.playMedia"),
       NativeWindow.contextmenus.mediaContext("media-paused"),
@@ -768,17 +768,17 @@ var BrowserApp = {
       });
 
     NativeWindow.contextmenus.add(Strings.browser.GetStringFromName("contextmenu.setImageAs"),
       NativeWindow.contextmenus._disableInGuest(NativeWindow.contextmenus.imageSaveableContext),
       function(aTarget) {
         UITelemetry.addEvent("action.1", "contextmenu", null, "web_background_image");
 
         let src = aTarget.src;
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Image:SetAs",
           url: src
         });
       });
 
     NativeWindow.contextmenus.add(
       function(aTarget) {
         if (aTarget instanceof HTMLVideoElement) {
@@ -958,17 +958,17 @@ var BrowserApp = {
     try {
       aBrowser.loadURIWithFlags(aURI, flags, referrerURI, charset, postData);
     } catch(e) {
       if (tab) {
         let message = {
           type: "Content:LoadError",
           tabID: tab.id
         };
-        sendMessageToJava(message);
+        Messaging.sendRequest(message);
         dump("Handled load error: " + e)
       }
     }
   },
 
   addTab: function addTab(aURI, aParams) {
     aParams = aParams || {};
 
@@ -1005,17 +1005,17 @@ var BrowserApp = {
       Cu.reportError("Error trying to close tab (tab doesn't exist)");
       return;
     }
 
     let message = {
       type: "Tab:Close",
       tabID: aTab.id
     };
-    sendMessageToJava(message);
+    Messaging.sendRequest(message);
   },
 
   _loadWebapp: function(aMessage) {
 
     this._initRuntime(this._startupStatus, aMessage.url, aUrl => {
       this.manifestUrl = aMessage.url;
       this.addTab(aUrl, { title: aMessage.name });
     });
@@ -1075,17 +1075,17 @@ var BrowserApp = {
     // There's nothing to do if the tab is already selected
     if (aTab == this.selectedTab)
       return;
 
     let message = {
       type: "Tab:Select",
       tabID: aTab.id
     };
-    sendMessageToJava(message);
+    Messaging.sendRequest(message);
   },
 
   /**
    * Gets an open tab with the given URL.
    *
    * @param  aURL URL to look for
    * @return the tab with the given URL, or null if no such tab exists
    */
@@ -1318,17 +1318,17 @@ var BrowserApp = {
           pref.type = "string";
           pref.value = pref.value.toString();
           break;
       }
 
       prefs.push(pref);
     }
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Preferences:Data",
       requestId: aRequestId,    // opaque request identifier, can be any string/int/whatever
       preferences: prefs
     });
   },
 
   setPreferences: function setPreferences(aPref) {
     let json = JSON.parse(aPref);
@@ -1424,26 +1424,26 @@ var BrowserApp = {
           promises.push(Sanitizer.clearItem("sessions"));
           break;
         default:
           promises.push(Sanitizer.clearItem(key));
       }
     }
 
     Promise.all(promises).then(function() {
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Sanitize:Finished",
         success: true
       });
 
       if (callback) {
         callback();
       }
     }).catch(function(err) {
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Sanitize:Finished",
         error: err,
         success: false
       });
 
       if (callback) {
         callback();
       }
@@ -1616,17 +1616,17 @@ var BrowserApp = {
         // perform a keyword search on the selected tab.
         this.selectedTab.userSearch = aData;
 
         // Don't store queries in private browsing mode.
         let isPrivate = PrivateBrowsingUtils.isWindowPrivate(this.selectedTab.browser.contentWindow);
         let query = isPrivate ? "" : aData;
 
         let engine = aSubject.QueryInterface(Ci.nsISearchEngine);
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Search:Keyword",
           identifier: engine.identifier,
           name: engine.name,
           query: query
         });
         break;
 
       case "Browser:Quit":
@@ -1677,21 +1677,21 @@ var BrowserApp = {
       case "FormHistory:Init": {
         // Force creation/upgrade of formhistory.sqlite
         FormHistory.count({});
         Services.obs.removeObserver(this, "FormHistory:Init");
         break;
       }
 
       case "sessionstore-state-purge-complete":
-        sendMessageToJava({ type: "Session:StatePurged" });
+        Messaging.sendRequest({ type: "Session:StatePurged" });
         break;
 
       case "gather-telemetry":
-        sendMessageToJava({ type: "Telemetry:Gather" });
+        Messaging.sendRequest({ type: "Telemetry:Gather" });
         break;
 
       case "Viewport:FixedMarginsChanged":
         gViewportMargins = JSON.parse(aData);
         this.selectedTab.updateViewportSize(gScreenWidth);
         break;
 
       case "nsPref:changed":
@@ -1866,25 +1866,25 @@ var NativeWindow = {
     Services.obs.removeObserver(this, "Menu:Clicked");
     Services.obs.removeObserver(this, "Doorhanger:Reply");
     Services.obs.removeObserver(this, "Toast:Click", false);
     Services.obs.removeObserver(this, "Toast:Hidden", false);
     this.contextmenus.uninit();
   },
 
   loadDex: function(zipFile, implClass) {
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Dex:Load",
       zipfile: zipFile,
       impl: implClass || "Main"
     });
   },
 
   unloadDex: function(zipFile) {
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Dex:Unload",
       zipfile: zipFile
     });
   },
 
   toast: {
     _callbacks: {},
     show: function(aMessage, aDuration, aOptions) {
@@ -1908,17 +1908,17 @@ var NativeWindow = {
           // If the caller specified a button, make sure we convert any chrome urls
           // to jar:jar urls so that the frontend can show them
           msg.button.icon = resolveGeckoURI(aOptions.button.icon);
         };
 
         this._callbacks[msg.button.id] = aOptions.button.callback;
       }
 
-      sendMessageToJava(msg);
+      Messaging.sendRequest(msg);
     }
   },
 
   menu: {
     _callbacks: [],
     _menuId: 1,
     toolsMenuID: -1,
     add: function() {
@@ -1933,31 +1933,31 @@ var NativeWindow = {
           };
       } else {
          throw "Incorrect number of parameters";
       }
 
       options.type = "Menu:Add";
       options.id = this._menuId;
 
-      sendMessageToJava(options);
+      Messaging.sendRequest(options);
       this._callbacks[this._menuId] = options.callback;
       this._menuId++;
       return this._menuId - 1;
     },
 
     remove: function(aId) {
-      sendMessageToJava({ type: "Menu:Remove", id: aId });
+      Messaging.sendRequest({ type: "Menu:Remove", id: aId });
     },
 
     update: function(aId, aOptions) {
       if (!aOptions)
         return;
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Menu:Update", 
         id: aId,
         options: aOptions
       });
     }
   },
 
   doorhanger: {
@@ -1997,21 +1997,21 @@ var NativeWindow = {
         type: "Doorhanger:Add",
         message: aMessage,
         value: aValue,
         buttons: aButtons,
         // use the current tab if none is provided
         tabID: aTabID || BrowserApp.selectedTab.id,
         options: aOptions || {}
       };
-      sendMessageToJava(json);
+      Messaging.sendRequest(json);
     },
 
     hide: function(aValue, aTabID) {
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Doorhanger:Remove",
         value: aValue,
         tabID: aTabID
       });
     }
   },
 
   observe: function(aSubject, aTopic, aData) {
@@ -3191,17 +3191,17 @@ Tab.prototype = {
         external: ("external" in aParams) ? aParams.external : false,
         selected: ("selected" in aParams) ? aParams.selected : true,
         title: truncate(title, MAX_TITLE_LENGTH),
         delayLoad: aParams.delayLoad || false,
         desktopMode: this.desktopMode,
         isPrivate: isPrivate,
         stub: stub
       };
-      sendMessageToJava(message);
+      Messaging.sendRequest(message);
 
       this.overscrollController = new OverscrollController(this);
     }
 
     this.browser.contentWindow.controllers.insertControllerAt(0, this.overscrollController);
 
     let flags = Ci.nsIWebProgress.NOTIFY_STATE_ALL |
                 Ci.nsIWebProgress.NOTIFY_LOCATION |
@@ -3255,17 +3255,17 @@ Tab.prototype = {
 
       try {
         this.browser.loadURIWithFlags(aURL, flags, referrerURI, charset, postData);
       } catch(e) {
         let message = {
           type: "Content:LoadError",
           tabID: this.id
         };
-        sendMessageToJava(message);
+        Messaging.sendRequest(message);
         dump("Handled load error: " + e);
       }
     }
   },
 
   /**
    * Retrieves the font size in twips for a given element.
    */
@@ -3346,17 +3346,17 @@ Tab.prototype = {
 
   /** 
    * Reloads the tab with the desktop mode setting.
    */
   reloadWithMode: function (aDesktopMode) {
     // Set desktop mode for tab and send change to Java
     if (this.desktopMode != aDesktopMode) {
       this.desktopMode = aDesktopMode;
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "DesktopMode:Changed",
         desktopMode: aDesktopMode,
         tabID: this.id
       });
     }
 
     // Only reload the page for http/https schemes
     let currentURI = this.browser.currentURI;
@@ -3775,17 +3775,17 @@ Tab.prototype = {
         let errorType = "";
         if (docURI.startsWith("about:certerror"))
           errorType = "certerror";
         else if (docURI.startsWith("about:blocked"))
           errorType = "blocked"
         else if (docURI.startsWith("about:neterror"))
           errorType = "neterror";
 
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "DOMContentLoaded",
           tabID: this.id,
           bgColor: backgroundColor,
           errorType: errorType,
           metadata: this.metatags
         });
 
         this.metatags = null;
@@ -3886,17 +3886,17 @@ Tab.prototype = {
           }
 
           let json = {
             type: "Link:Favicon",
             tabID: this.id,
             href: resolveGeckoURI(target.href),
             size: maxSize
           };
-          sendMessageToJava(json);
+          Messaging.sendRequest(json);
         } else if (list.indexOf("[alternate]") != -1 && aEvent.type == "DOMLinkAdded") {
           let type = target.type.toLowerCase().replace(/^\s+|\s*(?:;.*)?$/g, "");
           let isFeed = (type == "application/rss+xml" || type == "application/atom+xml");
 
           if (!isFeed)
             return;
 
           try {
@@ -3906,17 +3906,17 @@ Tab.prototype = {
             if (!this.browser.feeds)
               this.browser.feeds = [];
             this.browser.feeds.push({ href: target.href, title: target.title, type: type });
 
             let json = {
               type: "Link:Feed",
               tabID: this.id
             };
-            sendMessageToJava(json);
+            Messaging.sendRequest(json);
           } catch (e) {}
         } else if (list.indexOf("[search]" != -1) && aEvent.type == "DOMLinkAdded") {
           let type = target.type && target.type.toLowerCase();
 
           // Replace all starting or trailing spaces or spaces before "*;" globally w/ "".
           type = type.replace(/^\s+|\s*(?:;.*)?$/g, "");
 
           // Check that type matches opensearch.
@@ -3960,47 +3960,47 @@ Tab.prototype = {
 
             // Broadcast message that this tab contains search engines that should be visible.
             let newEngineMessage = {
               type: "Link:OpenSearch",
               tabID: this.id,
               visible: true
             };
 
-            sendMessageToJava(newEngineMessage);
+            Messaging.sendRequest(newEngineMessage);
           }
         }
         break;
       }
 
       case "DOMTitleChanged": {
         if (!aEvent.isTrusted)
           return;
 
         // ignore on frames and other documents
         if (aEvent.originalTarget != this.browser.contentDocument)
           return;
 
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "DOMTitleChanged",
           tabID: this.id,
           title: truncate(aEvent.target.title, MAX_TITLE_LENGTH)
         });
         break;
       }
 
       case "DOMWindowClose": {
         if (!aEvent.isTrusted)
           return;
 
         // Find the relevant tab, and close it from Java
         if (this.browser.contentWindow == aEvent.target) {
           aEvent.preventDefault();
 
-          sendMessageToJava({
+          Messaging.sendRequest({
             type: "Tab:Close",
             tabID: this.id
           });
         }
         break;
       }
 
       case "DOMWillOpenModalDialog": {
@@ -4060,17 +4060,17 @@ Tab.prototype = {
         break;
       }
 
       case "pageshow": {
         // only send pageshow for the top-level document
         if (aEvent.originalTarget.defaultView != this.browser.contentWindow)
           return;
 
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "Content:PageShow",
           tabID: this.id
         });
 
         if (!aEvent.persisted && Services.prefs.getBoolPref("browser.ui.linkify.phone")) {
           if (!this._linkifier)
             this._linkifier = new Linkifier();
           this._linkifier.linkifyNumbers(this.browser.contentWindow.document);
@@ -4107,17 +4107,17 @@ Tab.prototype = {
             } else {
               this.readerActive = true;
             }
             return;
           }
 
           this.savedArticle = article;
 
-          sendMessageToJava({
+          Messaging.sendRequest({
             type: "Content:ReaderEnabled",
             tabID: this.id
           });
 
           if(this.readerActive)
             this.readerActive = false;
 
           if(!this.readerEnabled)
@@ -4171,17 +4171,17 @@ Tab.prototype = {
       let message = {
         type: "Content:StateChange",
         tabID: this.id,
         uri: truncate(uri, MAX_URI_LENGTH),
         state: aStateFlags,
         restoring: restoring,
         success: success
       };
-      sendMessageToJava(message);
+      Messaging.sendRequest(message);
     }
   },
 
   onLocationChange: function(aWebProgress, aRequest, aLocationURI, aFlags) {
     let contentWin = aWebProgress.DOMWindow;
 
     // Browser webapps may load content inside iframes that can not reach across the app/frame boundary
     // i.e. even though the page is loaded in an iframe window.top != webapp
@@ -4253,17 +4253,17 @@ Tab.prototype = {
       tabID: this.id,
       uri: truncate(fixedURI.spec, MAX_URI_LENGTH),
       userSearch: this.userSearch || "",
       baseDomain: baseDomain,
       contentType: (contentType ? contentType : ""),
       sameDocument: sameDocument
     };
 
-    sendMessageToJava(message);
+    Messaging.sendRequest(message);
 
     // The search term is only valid for this location change event, so reset it here.
     this.userSearch = "";
 
     if (!sameDocument) {
       // XXX This code assumes that this is the earliest hook we have at which
       // browser.contentDocument is changed to the new document we're loading
 
@@ -4295,17 +4295,17 @@ Tab.prototype = {
     let identity = IdentityHandler.checkIdentity(aState, this.browser);
 
     let message = {
       type: "Content:SecurityChange",
       tabID: this.id,
       identity: identity
     };
 
-    sendMessageToJava(message);
+    Messaging.sendRequest(message);
   },
 
   onProgressChange: function(aWebProgress, aRequest, aCurSelfProgress, aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress) {
   },
 
   onStatusChange: function(aBrowser, aWebProgress, aRequest, aStatus, aMessage) {
   },
 
@@ -4322,17 +4322,17 @@ Tab.prototype = {
       if ("url" in aParams)
         message.url = aParams.url;
       if ("index" in aParams)
         message.index = aParams.index;
       if ("numEntries" in aParams)
         message.numEntries = aParams.numEntries;
     }
 
-    sendMessageToJava(message);
+    Messaging.sendRequest(message);
   },
 
   _getGeckoZoom: function() {
     let res = {x: {}, y: {}};
     let cwu = this.browser.contentWindow.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
     cwu.getResolution(res.x, res.y);
     let zoom = res.x.value * window.devicePixelRatio;
     return zoom;
@@ -4559,17 +4559,17 @@ Tab.prototype = {
     this.lastPageSizeAfterViewportRemeasure = {
       width: viewport.pageRight - viewport.pageLeft,
       height: viewport.pageBottom - viewport.pageTop
     };
   },
 
   sendViewportMetadata: function sendViewportMetadata() {
     let metadata = this.metadata;
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Tab:ViewportMetadata",
       allowZoom: metadata.allowZoom,
       allowDoubleTapZoom: metadata.allowDoubleTapZoom,
       defaultZoom: metadata.defaultZoom || window.devicePixelRatio,
       minZoom: metadata.minZoom || 0,
       maxZoom: metadata.maxZoom || 0,
       isRTL: metadata.isRTL,
       tabID: this.id
@@ -4777,17 +4777,17 @@ var BrowserEventHandler = {
 
       if (this._scrollableElement != null) {
         // Discard if it's the top-level scrollable, we let Java handle this
         // The top-level scrollable is the body in quirks mode and the html element
         // in standards mode
         let doc = BrowserApp.selectedBrowser.contentDocument;
         let rootScrollable = (doc.compatMode === "BackCompat" ? doc.body : doc.documentElement);
         if (this._scrollableElement != rootScrollable) {
-          sendMessageToJava({ type: "Panning:Override" });
+          Messaging.sendRequest({ type: "Panning:Override" });
         }
       }
     }
 
     if (!ElementTouchHelper.isElementClickable(closest, null, false))
       closest = ElementTouchHelper.elementFromPoint(aEvent.changedTouches[0].screenX,
                                                     aEvent.changedTouches[0].screenY);
     if (!closest)
@@ -4817,17 +4817,17 @@ var BrowserEventHandler = {
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "dom-touch-listener-added") {
       let tab = BrowserApp.getTabForWindow(aSubject.top);
       if (!tab || tab.hasTouchListener)
         return;
 
       tab.hasTouchListener = true;
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Tab:HasTouchListener",
         tabID: tab.id
       });
       return;
     } else if (aTopic == "nsPref:changed") {
       if (aData == "browser.zoom.reflowOnZoom") {
         this.updateReflozPref();
       }
@@ -4867,30 +4867,30 @@ var BrowserEventHandler = {
         if (this._firstScrollEvent) {
           while (this._scrollableElement != null &&
                  !this._elementCanScroll(this._scrollableElement, x, y))
             this._scrollableElement = this._findScrollableElement(this._scrollableElement, false);
 
           let doc = BrowserApp.selectedBrowser.contentDocument;
           if (this._scrollableElement == null ||
               this._scrollableElement == doc.documentElement) {
-            sendMessageToJava({ type: "Panning:CancelOverride" });
+            Messaging.sendRequest({ type: "Panning:CancelOverride" });
             return;
           }
 
           this._firstScrollEvent = false;
         }
 
         // Scroll the scrollable element
         if (this._elementCanScroll(this._scrollableElement, x, y)) {
           this._scrollElementBy(this._scrollableElement, x, y);
-          sendMessageToJava({ type: "Gesture:ScrollAck", scrolled: true });
+          Messaging.sendRequest({ type: "Gesture:ScrollAck", scrolled: true });
           SelectionHandler.subdocumentScrolled(this._scrollableElement);
         } else {
-          sendMessageToJava({ type: "Gesture:ScrollAck", scrolled: false });
+          Messaging.sendRequest({ type: "Gesture:ScrollAck", scrolled: false });
         }
 
         break;
       }
 
       case "Gesture:CancelTouch":
         this._cancelTapHighlight();
         break;
@@ -5796,17 +5796,17 @@ var FormAssistant = {
       let suggestions = autoCompleteSuggestions.concat(listSuggestions);
 
       // Return false if there are no suggestions to show
       if (!suggestions.length) {
         aCallback(false);
         return;
       }
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type:  "FormAssist:AutoComplete",
         suggestions: suggestions,
         rect: ElementTouchHelper.getBoundingContentRect(aElement)
       });
 
       // Keep track of input element so we can fill it in if the user
       // selects an autocomplete suggestion
       this._currentInputElement = aElement;
@@ -5831,27 +5831,27 @@ var FormAssistant = {
   },
 
   // Sends a validation message and position data for an element to the Java UI.
   // Returns true if there's a validation message to show, false otherwise.
   _showValidationMessage: function _sendValidationMessage(aElement) {
     if (!this._isValidateable(aElement))
       return false;
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "FormAssist:ValidationMessage",
       validationMessage: aElement.validationMessage,
       rect: ElementTouchHelper.getBoundingContentRect(aElement)
     });
 
     return true;
   },
 
   _hideFormAssistPopup: function _hideFormAssistPopup() {
-    sendMessageToJava({ type: "FormAssist:Hide" });
+    Messaging.sendRequest({ type: "FormAssist:Hide" });
   }
 };
 
 /**
  * An object to watch for Gecko status changes -- add-on installs, pref changes
  * -- and reflect them back to Java.
  */
 let HealthReportStatusListener = {
@@ -5912,17 +5912,17 @@ let HealthReportStatusListener = {
           case this.PREF_BLOCKLIST_ENABLED:
             response.value = Services.prefs.getBoolPref(aData);
             break;
           default:
             console.log("Unexpected pref in HealthReportStatusListener: " + aData);
             return;
         }
 
-        sendMessageToJava(response);
+        Messaging.sendRequest(response);
         break;
     }
   },
 
   MILLISECONDS_PER_DAY: 24 * 60 * 60 * 1000,
 
   COPY_FIELDS: [
     "blocklistState",
@@ -5971,17 +5971,17 @@ let HealthReportStatusListener = {
     return o;
   },
 
   notifyJava: function (aAddon, aNeedsRestart, aAction="Addons:Change") {
     let json = this.jsonForAddon(aAddon);
     if (this._shouldIgnore(aAddon)) {
       json.ignore = true;
     }
-    sendMessageToJava({ type: aAction, id: aAddon.id, json: json });
+    Messaging.sendRequest({ type: aAction, id: aAddon.id, json: json });
   },
 
   // Add-on listeners.
   onEnabling: function (aAddon, aNeedsRestart) {
     this.notifyJava(aAddon, aNeedsRestart);
   },
   onDisabling: function (aAddon, aNeedsRestart) {
     this.notifyJava(aAddon, aNeedsRestart);
@@ -6034,17 +6034,17 @@ let HealthReportStatusListener = {
           jsonP[pref] = {
             pref: pref,
             value: Services.prefs.getCharPref(pref),
             isUserSet: Services.prefs.prefHasUserValue(pref),
           };
         }
 
         console.log("Sending snapshot message.");
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "HealthReport:Snapshot",
           json: {
             addons: jsonA,
             prefs: jsonP,
           },
         });
       }.bind(this));
   },
@@ -6688,17 +6688,17 @@ var CharacterEncoding = {
   },
 
   sendState: function sendState() {
     let showCharEncoding = "false";
     try {
       showCharEncoding = Services.prefs.getComplexValue("browser.menu.showCharacterEncoding", Ci.nsIPrefLocalizedString).data;
     } catch (e) { /* Optional */ }
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "CharEncoding:State",
       visible: showCharEncoding
     });
   },
 
   getEncoding: function getEncoding() {
     function infoToCharset(info) {
       return { code: info.value, title: info.label };
@@ -6722,17 +6722,17 @@ var CharacterEncoding = {
 
     for (let i = 0; i < charsetCount; i++) {
       if (this._charsets[i].code === docCharset) {
         selected = i;
         break;
       }
     }
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "CharEncoding:Data",
       charsets: this._charsets,
       selected: selected
     });
   },
 
   setEncoding: function setEncoding(aEncoding) {
     let browser = BrowserApp.selectedBrowser;
@@ -6934,17 +6934,17 @@ OverscrollController.prototype = {
     return (this.tab.getViewport().y == 0);
   },
 
   isCommandEnabled : function isCommandEnabled(aCommand) {
     return this.supportsCommand(aCommand);
   },
 
   doCommand : function doCommand(aCommand){
-    sendMessageToJava({ type: "ToggleChrome:Focus" });
+    Messaging.sendRequest({ type: "ToggleChrome:Focus" });
   },
 
   onEvent : function onEvent(aEvent) { }
 };
 
 var SearchEngines = {
   _contextMenuId: null,
   PREF_SUGGEST_ENABLED: "browser.search.suggest.enabled",
@@ -7028,17 +7028,17 @@ var SearchEngines = {
     // the default engine because we only show suggestions for the default engine in the UI.
     let engine = Services.search.defaultEngine;
     if (engine.supportsResponseType("application/x-suggestions+json")) {
       suggestEngine = engine.name;
       suggestTemplate = engine.getSubmission("__searchTerms__", "application/x-suggestions+json").uri.spec;
     }
 
     // By convention, the currently configured default engine is at position zero in searchEngines.
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "SearchEngines:Data",
       searchEngines: searchEngines,
       suggest: {
         engine: suggestEngine,
         template: suggestTemplate,
         enabled: Services.prefs.getBoolPref(this.PREF_SUGGEST_ENABLED),
         prompted: Services.prefs.getBoolPref(this.PREF_SUGGEST_PROMPTED)
       }
@@ -7112,17 +7112,17 @@ var SearchEngines = {
       if (engines.length < 1) {
         // Broadcast message that there are no more add-able search engines.
         let newEngineMessage = {
           type: "Link:OpenSearch",
           tabID: tab.id,
           visible: false
         };
 
-        sendMessageToJava(newEngineMessage);
+        Messaging.sendRequest(newEngineMessage);
       }
     }).bind(this));
   },
 
   addOpenSearchEngine: function addOpenSearchEngine(engine) {
     Services.search.addEngine(engine.url, Ci.nsISearchEngine.DATA_XML, engine.iconURL, false, {
       onSuccess: function() {
         // Display a toast confirming addition of new search engine.
@@ -7407,23 +7407,23 @@ let Reader = {
     Services.obs.addObserver(this, "Reader:Add", false);
     Services.obs.addObserver(this, "Reader:Remove", false);
 
     Services.prefs.addObserver("reader.parse-on-load.", this, false);
   },
 
   pageAction: {
     readerModeCallback: function(){
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Reader:Click",
       });
     },
 
     readerModeActiveCallback: function(){
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Reader:LongClick",
       });
 
       UITelemetry.addEvent("save.1", "pageaction", null, "reader");
     },
   },
 
   updatePageAction: function(tab) {
@@ -7487,17 +7487,17 @@ let Reader = {
         } else {
           throw new Error("Reader:Add requires a tabID or an URL as argument");
         }
 
         let sendResult = function(result, article) {
           article = article || {};
           this.log("Reader:Add success=" + result + ", url=" + url + ", title=" + article.title + ", excerpt=" + article.excerpt);
 
-          sendMessageToJava({
+          Messaging.sendRequest({
             type: "Reader:Added",
             result: result,
             title: truncate(article.title, MAX_TITLE_LENGTH),
             url: truncate(url, MAX_URI_LENGTH),
             length: article.length,
             excerpt: article.excerpt
           });
         }.bind(this);
@@ -7535,17 +7535,17 @@ let Reader = {
 
         if (!("url" in args)) {
           throw new Error("Reader:Remove requires URL as an argument");
         }
 
         this.removeArticleFromCache(args.url, function(success) {
           this.log("Reader:Remove success=" + success + ", url=" + args.url);
           if (success && args.notify) {
-            sendMessageToJava({
+            Messaging.sendRequest({
               type: "Reader:Removed",
               url: args.url
             });
           }
         }.bind(this));
         break;
       }
 
@@ -8180,17 +8180,17 @@ var Distribution = {
     for (let key in localizeablePrefsOverrides) {
       try {
         let value = localizeablePrefsOverrides[key];
         localizedString.data = "data:text/plain," + key + "=" + value;
         defaults.setComplexValue(key, Ci.nsIPrefLocalizedString, localizedString);
       } catch (e) { /* ignore bad prefs and move on */ }
     }
 
-    sendMessageToJava({ type: "Distribution:Set:OK" });
+    Messaging.sendRequest({ type: "Distribution:Set:OK" });
   },
 
   // aFile is an nsIFile
   // aCallback takes the parsed JSON object as a parameter
   readJSON: function dc_readJSON(aFile, aCallback) {
     Task.spawn(function() {
       let bytes = yield OS.File.read(aFile.path);
       let raw = new TextDecoder().decode(bytes) || "";
--- a/mobile/android/components/ContentDispatchChooser.js
+++ b/mobile/android/components/ContentDispatchChooser.js
@@ -59,17 +59,17 @@ ContentDispatchChooser.prototype =
           button: {
             label: searchText,
             callback: function() {
               let message = {
                 type: "Intent:Open",
                 url: "market://search?q=" + aURI.scheme,
               };
 
-              sendMessageToJava(message);
+              Messaging.sendRequest(message);
             }
           }
         });
       }
     }
   },
 };
 
--- a/mobile/android/components/SessionStore.js
+++ b/mobile/android/components/SessionStore.js
@@ -12,17 +12,17 @@ Cu.import("resource://gre/modules/Servic
 
 #ifdef MOZ_CRASHREPORTER
 XPCOMUtils.defineLazyServiceGetter(this, "CrashReporter",
   "@mozilla.org/xre/app-info;1", "nsICrashReporter");
 #endif
 
 XPCOMUtils.defineLazyModuleGetter(this, "Task", "resource://gre/modules/Task.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "OS", "resource://gre/modules/osfile.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", "resource://gre/modules/Messaging.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils", "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 function dump(a) {
   Services.console.logStringMessage(a);
 }
 
 // -----------------------------------------------------------------------
 // Session Store
@@ -139,17 +139,17 @@ SessionStore.prototype = {
 
               if (window.BrowserApp.tabs.length == 0) {
                 window.BrowserApp.addTab("about:home", {
                   selected: true
                 });
               }
 
               // Let Java know we're done restoring tabs so tabs added after this can be animated
-              sendMessageToJava({
+              Messaging.sendRequest({
                 type: "Session:RestoreEnd"
               });
             }.bind(this)
           };
           Services.obs.addObserver(restoreCleanup, "sessionstore-windows-restored", false);
 
           // Do a restore, triggered by Java
           let data = JSON.parse(aData);
@@ -449,17 +449,17 @@ SessionStore.prototype = {
       }
     }
 
     // Write only non-private data to disk
     this._writeFile(this._sessionFile, JSON.stringify(normalData), aAsync);
 
     // If we have private data, send it to Java; otherwise, send null to
     // indicate that there is no private data
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "PrivateBrowsing:Data",
       session: (privateData.windows.length > 0 && privateData.windows[0].tabs.length > 0) ? JSON.stringify(privateData) : null
     });
 
     this._lastSaveTime = Date.now();
   },
 
   _getCurrentState: function ss_getCurrentState() {
@@ -938,17 +938,17 @@ SessionStore.prototype = {
         // Get the url and title for the last entry in the session history.
         let lastEntry = tab.entries[tab.entries.length - 1];
         return {
           url: lastEntry.url,
           title: lastEntry.title || ""
         };
       });
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "ClosedTabs:Data",
       tabs: tabs
     });
   },
 
   getTabValue: function ss_getTabValue(aTab, aKey) {
     let browser = aTab.browser;
     let data = browser.__SS_extdata || {};
--- a/mobile/android/components/TabSource.js
+++ b/mobile/android/components/TabSource.js
@@ -7,17 +7,17 @@
 const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
                                   "resource://gre/modules/Prompt.jsm");
 
-XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
                                   "resource://gre/modules/Messaging.jsm");
 
 function TabSource() {
 }
 
 TabSource.prototype = {
   classID: Components.ID("{5850c76e-b916-4218-b99a-31f004e0a7e7}"),
   classDescription: "Fennec Tab Source",
@@ -67,25 +67,25 @@ TabSource.prototype = {
     return tabs[result].browser.contentWindow;
   },
 
   notifyStreamStart: function(window) {
     let app = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
     let tabs = app.tabs;
     for (var i in tabs) {
       if (tabs[i].browser.contentWindow == window) {
-        sendMessageToJava({ type: "Tab:StreamStart", tabID: tabs[i].id });
+        Messaging.sendRequest({ type: "Tab:StreamStart", tabID: tabs[i].id });
       }
     }
   },
 
   notifyStreamStop: function(window) {
     let app = Services.wm.getMostRecentWindow("navigator:browser").BrowserApp;
     let tabs = app.tabs;
     for (let i in tabs) {
       if (tabs[i].browser.contentWindow == window) {
-        sendMessageToJava({ type: "Tab:StreamStop", tabID: tabs[i].id });
+        Messaging.sendRequest({ type: "Tab:StreamStop", tabID: tabs[i].id });
       }
     }
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TabSource]);
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -206,17 +206,19 @@ password.dontSave=Don't save
 # This is not a string to translate. If users frequently use the "Character Encoding"
 # menu, set this to "true". Otherwise, you can leave it as "false".
 browser.menu.showCharacterEncoding=false
 
 # Text Selection
 selectionHelper.textCopied=Text copied to clipboard
 
 # Casting
-casting.prompt=Cast to Device
+# LOCALIZATION NOTE (casting.sendToDevice): Label that will be used in the
+# dialog/prompt.
+casting.sendToDevice=Send to Device
 casting.mirrorTab=Mirror Tab
 casting.mirrorTabStop=Stop Mirror
 
 # Context menu
 contextmenu.openInNewTab=Open Link in New Tab
 contextmenu.openInPrivateTab=Open Link in Private Tab
 contextmenu.addToReadingList=Add to Reading List
 contextmenu.share=Share
@@ -242,17 +244,19 @@ contextmenu.playMedia=Play
 contextmenu.pauseMedia=Pause
 contextmenu.shareMedia=Share Video
 contextmenu.showControls2=Show Controls
 contextmenu.mute=Mute
 contextmenu.unmute=Unmute
 contextmenu.saveVideo=Save Video
 contextmenu.saveAudio=Save Audio
 contextmenu.addToContacts=Add to Contacts
-contextmenu.castToScreen=Cast to Screen
+# LOCALIZATION NOTE (contextmenu.sendToDevice):
+# The label that will be used in the contextmenu and the pageaction
+contextmenu.sendToDevice=Send to Device
 
 contextmenu.copy=Copy
 contextmenu.cut=Cut
 contextmenu.selectAll=Select All
 contextmenu.paste=Paste
 
 contextmenu.call=Call
 
@@ -379,9 +383,9 @@ browser.menu.context.mailto = Mail
 # "Subscribe to page" prompts created in FeedHandler.js
 feedHandler.chooseFeed=Choose feed
 feedHandler.subscribeWith=Subscribe with
 
 # LOCALIZATION NOTE (nativeWindow.deprecated):
 # This string is shown in the console when someone uses deprecated NativeWindow apis.
 # %1$S=name of the api that's deprecated, %2$S=New API to use. This may be a url to
 # a file they should import or the name of an api.
-nativeWindow.deprecated=%S is deprecated. Please use %S instead
\ No newline at end of file
+nativeWindow.deprecated=%S is deprecated. Please use %S instead
--- a/mobile/android/modules/Accounts.jsm
+++ b/mobile/android/modules/Accounts.jsm
@@ -67,14 +67,14 @@ let Accounts = Object.freeze({
    *
    * Optional extras are passed, as a JSON string, to the Firefox
    * Account Getting Started activity in the extras bundle of the
    * activity launch intent, under the key "extras".
    *
    * There is no return value from this method.
    */
   launchSetup: function (extras) {
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Accounts:Create",
       extras: extras,
     });
   },
 });
--- a/mobile/android/modules/HelperApps.jsm
+++ b/mobile/android/modules/HelperApps.jsm
@@ -6,16 +6,19 @@
 const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Prompt",
                                   "resource://gre/modules/Prompt.jsm");
 
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging",
+                                  "resource://gre/modules/Messaging.jsm");
+
 XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava",
                                   "resource://gre/modules/Messaging.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "ContentAreaUtils", function() {
   let ContentAreaUtils = {};
   Services.scriptloader.loadSubScript("chrome://global/content/contentAreaUtils.js", ContentAreaUtils);
   return ContentAreaUtils;
 });
@@ -147,17 +150,17 @@ var HelperApps =  {
       sendMessageToJava(msg, function(data) {
         callback(parseData(data));
       });
     }
   },
 
   launchUri: function launchUri(uri) {
     let msg = this._getMessage("Intent:Open", uri);
-    sendMessageToJava(msg);
+    Messaging.sendRequest(msg);
   },
 
   _parseApps: function _parseApps(appInfo) {
     // appInfo -> {apps: [app1Label, app1Default, app1PackageName, app1ActivityName, app2Label, app2Defaut, ...]}
     // see GeckoAppShell.java getHandlersForIntent function for details
     const numAttr = 4; // 4 elements per ResolveInfo: label, default, package name, activity name.
 
     let apps = [];
@@ -197,17 +200,17 @@ var HelperApps =  {
             callback(data);
         });
     } else {
         let msg = this._getMessage("Intent:Open", uri, {
             packageName: app.packageName,
             className: app.activityName
         });
 
-        sendMessageToJava(msg);
+        Messaging.sendRequest(msg);
     }
   },
 
   _sendMessageSync: function(msg) {
     let res = null;
     sendMessageToJava(msg, function(data) {
       res = data;
     });
--- a/mobile/android/modules/Home.jsm
+++ b/mobile/android/modules/Home.jsm
@@ -89,17 +89,17 @@ let HomeBanner = (function () {
       totalWeight += message.weight;
       message.totalWeight = totalWeight;
     }
 
     let threshold = Math.random() * totalWeight;
     for (let key in _messages) {
       let message = _messages[key];
       if (threshold < message.totalWeight) {
-        sendMessageToJava({
+        Messaging.sendRequest({
           type: "HomeBanner:Data",
           id: message.id,
           text: message.text,
           iconURI: message.iconURI
         });
         return;
       }
     }
@@ -209,17 +209,17 @@ let HomePanels = (function () {
           try {
             panels.push(_generatePanel(id));
           } catch(e) {
             Cu.reportError("Home.panels: Invalid options, panel.id = " + id + ": " + e);
           }
         }
       }
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "HomePanels:Data",
         panels: panels,
         requestId: requestId
       });
     },
 
     "HomePanels:Authenticate": function handlePanelsAuthenticate(id) {
       // Generate panel options to get auth handler.
@@ -424,35 +424,35 @@ let HomePanels = (function () {
       _assertPanelExists(id);
 
       delete _registeredPanels[id];
     },
 
     install: function(id) {
       _assertPanelExists(id);
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "HomePanels:Install",
         panel: _generatePanel(id)
       });
     },
 
     uninstall: function(id) {
       _assertPanelExists(id);
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "HomePanels:Uninstall",
         id: id
       });
     },
 
     update: function(id) {
       _assertPanelExists(id);
 
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "HomePanels:Update",
         panel: _generatePanel(id)
       });
     },
 
     setAuthenticated: function(id, isAuthenticated) {
       _assertPanelExists(id);
 
--- a/mobile/android/modules/HomeProvider.jsm
+++ b/mobile/android/modules/HomeProvider.jsm
@@ -295,17 +295,17 @@ function refreshDataset(datasetId) {
   if (gRefreshTimers[datasetId]) {
     return;
   }
 
   let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
   timer.initWithCallback(function(timer) {
     delete gRefreshTimers[datasetId];
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "HomePanels:RefreshDataset",
       datasetId: datasetId
     });
   }, 100, Ci.nsITimer.TYPE_ONE_SHOT);
 
   gRefreshTimers[datasetId] = timer;
 }
 
--- a/mobile/android/modules/Messaging.jsm
+++ b/mobile/android/modules/Messaging.jsm
@@ -4,74 +4,51 @@
 "use strict"
 
 const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
-this.EXPORTED_SYMBOLS = ["sendMessageToJava", "RequestService"];
+this.EXPORTED_SYMBOLS = ["sendMessageToJava", "Messaging"];
 
 XPCOMUtils.defineLazyServiceGetter(this, "uuidgen",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
 function sendMessageToJava(aMessage, aCallback) {
-  if (aCallback) {
-    let id = uuidgen.generateUUID().toString();
-    let obs = {
-      observe: function(aSubject, aTopic, aData) {
-        let data = JSON.parse(aData);
-        if (data.__guid__ != id) {
-          return;
-        }
-
-        Services.obs.removeObserver(obs, aMessage.type + ":Response", false);
+  Cu.reportError("sendMessageToJava is deprecated. Use Messaging API instead.");
 
-        if (data.status === "cancel") {
-          // No Java-side listeners handled our callback.
-          return;
-        }
-
-        aCallback(data.status === "success" ? data.response : null,
-                  data.status === "error"   ? data.response : null);
-      }
-    }
-
-    aMessage.__guid__ = id;
-    Services.obs.addObserver(obs, aMessage.type + ":Response", false);
-  }
-
-  return Services.androidBridge.handleGeckoMessage(aMessage);
+  Messaging.sendRequest(aMessage, aCallback);
 }
 
-let RequestService = {
+let Messaging = {
   /**
    * Add a listener for the given message.
    *
    * Only one request listener can be registered for a given message.
    *
    * Example usage:
-   *   RequestService.addListener({
+   *   Messaging.addListener({
    *     // aMessage is the message name.
    *     // aData is data sent from Java with the request.
    *     // The return value is used to respond to the request. The return
    *     //   type *must* be an instance of Object.
    *     onRequest: function (aMessage, aData) {
    *       if (aData == "foo") {
    *         return { response: "bar" };
    *       }
    *       return {};
    *     }
    *   }, "Demo:Request");
    *
    * The listener may also be a generator function, useful for performing a
    * task asynchronously. For example:
-   *   RequestService.addListener({
+   *   Messaging.addListener({
    *     onRequest: function* (aMessage, aData) {
    *       yield new Promise(resolve => setTimeout(resolve, 2000));
    *       return { response: "bar" };
    *     }
    *   }, "Demo:Request");
    *
    * @param aListener Listener object with an onRequest function (see example
    *                  usage above).
@@ -84,16 +61,51 @@ let RequestService = {
   /**
    * Removes a listener for a given message.
    *
    * @param aMessage The event to stop listening for.
    */
   removeListener: function (aMessage) {
     requestHandler.removeListener(aMessage);
   },
+
+  /**
+   * Sends a request to Java.
+   *
+   * @param aMessage  Message to send; must be an object with a "type" property
+   * @param aCallback Callback function, required if this request expects a response.
+   */
+  sendRequest: function (aMessage, aCallback) {
+    if (aCallback) {
+      let id = uuidgen.generateUUID().toString();
+      let obs = {
+        observe: function(aSubject, aTopic, aData) {
+          let data = JSON.parse(aData);
+          if (data.__guid__ != id) {
+            return;
+          }
+
+          Services.obs.removeObserver(obs, aMessage.type + ":Response", false);
+
+          if (data.status === "cancel") {
+            // No Java-side listeners handled our callback.
+            return;
+          }
+
+          aCallback(data.status === "success" ? data.response : null,
+                    data.status === "error"   ? data.response : null);
+        }
+      }
+
+      aMessage.__guid__ = id;
+      Services.obs.addObserver(obs, aMessage.type + ":Response", false);
+    }
+
+    return Services.androidBridge.handleGeckoMessage(aMessage);
+  },
 };
 
 let requestHandler = {
   _listeners: {},
 
   addListener: function (aListener, aMessage) {
     if (aMessage in this._listeners) {
       throw new Error("Error in addListener: A listener already exists for message " + aMessage);
@@ -130,14 +142,14 @@ let requestHandler = {
       if (typeof result !== "object" || result === null) {
         throw new Error("Gecko request listener did not return an object");
       }
       response = result;
     } catch (e) {
       Cu.reportError(e);
     }
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Gecko:Request" + wrapper.id,
       response: response
     });
   })
 };
--- a/mobile/android/modules/NetErrorHelper.jsm
+++ b/mobile/android/modules/NetErrorHelper.jsm
@@ -96,17 +96,17 @@ handlers.wifi = {
     UITelemetry.addEvent("neterror.1", "button", "wifitoggle");
     // Show indeterminate progress while we wait for the network.
     node.disabled = true;
     node.classList.add("inProgress");
 
     this.node = Cu.getWeakReference(node);
     Services.obs.addObserver(this, "network:link-status-changed", true);
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Wifi:Enable"
     });
   },
 
   observe: function(subject, topic, data) {
     let node = this.node.get();
     if (!node) {
       return;
--- a/mobile/android/modules/OrderedBroadcast.jsm
+++ b/mobile/android/modules/OrderedBroadcast.jsm
@@ -65,16 +65,16 @@ function sendOrderedBroadcast(action, to
       // This is called from within a notified observer, so we don't
       // need to take special pains to catch exceptions.
       theCallback(theData, theToken, theAction);
     },
   };
 
   Services.obs.addObserver(observer, responseEvent, false);
 
-  sendMessageToJava({
+  Messaging.sendRequest({
     type: "OrderedBroadcast:Send",
     action: action,
     responseEvent: responseEvent,
     token: { callbackId: callbackId, data: token || null },
     permission: permission,
   });
 };
--- a/mobile/android/modules/PageActions.jsm
+++ b/mobile/android/modules/PageActions.jsm
@@ -62,17 +62,17 @@ var PageActions = {
       if (this._items[aData].longClickCallback) {
         this._items[aData].longClickCallback();
       }
     }
   },
 
   add: function(aOptions) {
     let id = uuidgen.generateUUID().toString();
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "PageActions:Add",
       id: id,
       title: aOptions.title,
       icon: resolveGeckoURI(aOptions.icon),
       important: "important" in aOptions ? aOptions.important : false
     });
 
     this._items[id] = {
@@ -80,17 +80,17 @@ var PageActions = {
       longClickCallback: aOptions.longClickCallback
     };
 
     this._maybeInit();
     return id;
   },
 
   remove: function(id) {
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "PageActions:Remove",
       id: id
     });
 
     delete this._items[id];
     this._maybeUninit();
   }
 }
--- a/mobile/android/modules/SharedPreferences.jsm
+++ b/mobile/android/modules/SharedPreferences.jsm
@@ -71,17 +71,17 @@ function SharedPreferencesImpl(options =
   this._scope = options.scope;
   this._profileName = options.profileName;
   this._branch = options.branch;
   this._observers = {};
 }
 
 SharedPreferencesImpl.prototype = Object.freeze({
   _set: function _set(prefs) {
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "SharedPreferences:Set",
       preferences: prefs,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 
@@ -201,17 +201,17 @@ SharedPreferencesImpl.prototype = Object
   },
 
   _installAndroidListener: function _installAndroidListener() {
     if (this._listening)
       return;
     this._listening = true;
 
     Services.obs.addObserver(this, "SharedPreferences:Changed", false);
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "SharedPreferences:Observe",
       enable: true,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 
@@ -238,17 +238,17 @@ SharedPreferencesImpl.prototype = Object
   },
 
   _uninstallAndroidListener: function _uninstallAndroidListener() {
     if (!this._listening)
       return;
     this._listening = false;
 
     Services.obs.removeObserver(this, "SharedPreferences:Changed");
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "SharedPreferences:Observe",
       enable: false,
       scope: this._scope,
       profileName: this._profileName,
       branch: this._branch,
     });
   },
 });
--- a/mobile/android/modules/WebappManager.jsm
+++ b/mobile/android/modules/WebappManager.jsm
@@ -17,16 +17,17 @@ Cu.import("resource://gre/modules/NetUti
 Cu.import("resource://gre/modules/FileUtils.jsm");
 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
 Cu.import("resource://gre/modules/Webapps.jsm");
 Cu.import("resource://gre/modules/osfile.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "Notifications", "resource://gre/modules/Notifications.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Messaging", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "sendMessageToJava", "resource://gre/modules/Messaging.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm", "resource://gre/modules/PluralForm.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "Strings", function() {
   return Services.strings.createBundle("chrome://browser/locale/webapp.properties");
 });
 
 /**
@@ -188,33 +189,33 @@ this.WebappManager = {
         this._postInstall(aData.profilePath, aManifest, aData.app.origin,
                           aData.app.apkPackageName, aData.app.manifestURL);
       }).bind(this));
     });
   },
 
   _postInstall: function(aProfilePath, aNewManifest, aOrigin, aApkPackageName, aManifestURL) {
     // aOrigin may now point to the app: url that hosts this app.
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Webapps:Postinstall",
       apkPackageName: aApkPackageName,
       origin: aOrigin,
     });
   },
 
   askUninstall: function(aData) {
     // Android does not currently support automatic uninstalling of apps.
     // See bug 1019054.
     DOMApplicationRegistry.denyUninstall(aData, "NOT_SUPPORTED");
   },
 
   launch: function({ apkPackageName }) {
     debug("launch: " + apkPackageName);
 
-    sendMessageToJava({
+    Messaging.sendRequest({
       type: "Webapps:Launch",
       packageName: apkPackageName,
     });
   },
 
   uninstall: Task.async(function*(aData, aMessageManager) {
     debug("uninstall: " + aData.manifestURL);
 
@@ -231,17 +232,17 @@ this.WebappManager = {
       throw new Error("app not found in registry");
     }
 
     // If the APK is installed, then _getAPKVersions will return a version
     // for it, so we can use that function to determine its install status.
     let apkVersions = yield this._getAPKVersions([ app.apkPackageName ]);
     if (app.apkPackageName in apkVersions) {
       debug("APK is installed; requesting uninstallation");
-      sendMessageToJava({
+      Messaging.sendRequest({
         type: "Webapps:UninstallApk",
         apkPackageName: app.apkPackageName,
       });
 
       // We don't need to call DOMApplicationRegistry.doUninstall at this point,
       // because the APK uninstall listener will call autoUninstall once the APK
       // is uninstalled; and if the user cancels the APK uninstallation, then we
       // shouldn't remove the app from the registry anyway.
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
@@ -81,19 +81,25 @@ public class SearchEngineManager impleme
      * @param callback a SearchEngineCallback to be called after successfully looking
      *                 up the search engine. This will run on the UI thread.
      */
     private void getEngineFromPrefs(final SearchEngineCallback callback) {
         final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
             @Override
             protected SearchEngine doInBackground(Void... params) {
                 String identifier = GeckoSharedPrefs.forApp(context).getString(SearchPreferenceActivity.PREF_SEARCH_ENGINE_KEY, null);
-                if (TextUtils.isEmpty(identifier)) {
-                    identifier = context.getResources().getString(R.string.default_engine_identifier);
+                if (!TextUtils.isEmpty(identifier)) {
+                    try {
+                        return createEngine(identifier);
+                    } catch (IllegalArgumentException e) {
+                        Log.e(LOG_TAG, "Exception creating search engine from pref. Falling back to default engine.", e);
+                    }
                 }
+
+                identifier = context.getResources().getString(R.string.default_engine_identifier);
                 return createEngine(identifier);
             }
 
             @Override
             protected void onPostExecute(SearchEngine engine) {
                 // Only touch engine on the main thread.
                 SearchEngineManager.this.engine = engine;
                 if (callback != null) {
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -237,12 +237,13 @@ user_pref("browser.aboutHomeSnippets.upd
 user_pref("dom.mozApps.debug", true);
 
 // Don't fetch or send directory tiles data from real servers
 user_pref("browser.newtabpage.directory.source", 'data:application/json,{"testing":1}');
 user_pref("browser.newtabpage.directory.ping", "");
 
 // Enable Loop
 user_pref("loop.enabled", true);
+user_pref("loop.throttled", false);
 
 // Ensure UITour won't hit the network
 user_pref("browser.uitour.pinnedTabUrl", "http://%(server)s/uitour-dummy/pinnedTab");
 user_pref("browser.uitour.url", "http://%(server)s/uitour-dummy/tour");
--- a/toolkit/components/thumbnails/PageThumbsProtocol.js
+++ b/toolkit/components/thumbnails/PageThumbsProtocol.js
@@ -47,16 +47,17 @@ Protocol.prototype = {
    */
   get defaultPort() -1,
 
   /**
    * The flags specific to this protocol implementation.
    */
   get protocolFlags() {
     return Ci.nsIProtocolHandler.URI_DANGEROUS_TO_LOAD |
+           Ci.nsIProtocolHandler.URI_IS_LOCAL_RESOURCE |
            Ci.nsIProtocolHandler.URI_NORELATIVE |
            Ci.nsIProtocolHandler.URI_NOAUTH;
   },
 
   /**
    * Creates a new URI object that is suitable for loading by this protocol.
    * @param aSpec The URI string in UTF8 encoding.
    * @param aOriginCharset The charset of the document from which the URI originated.