Merge m-c to b2g-inbound a=merge CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Thu, 09 Oct 2014 17:10:50 -0700
changeset 209680 e072068b48810a76782fb70becc5ba5140366457
parent 209679 3f1d24a1666446fbb7759a00165514f308b8738c (current diff)
parent 209672 95d1486223f7c222b70f6878348ea0be3a66b821 (diff)
child 209704 b91b22431613232f0818721e064002575a04ff9f
child 209718 43eb7c38b3cbca8a043a2351571bf6209c88a565
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewersmerge
milestone35.0a1
Merge m-c to b2g-inbound a=merge CLOSED TREE
mobile/android/search/res/color/facet_button_text_color.xml
mobile/android/search/res/drawable-hdpi/ic_action_settings.png
mobile/android/search/res/drawable-hdpi/ic_widget_new_tab.png
mobile/android/search/res/drawable-hdpi/ic_widget_search.png
mobile/android/search/res/drawable-hdpi/network_error.png
mobile/android/search/res/drawable-hdpi/search_clear.png
mobile/android/search/res/drawable-hdpi/search_fox.png
mobile/android/search/res/drawable-hdpi/search_history.png
mobile/android/search/res/drawable-hdpi/search_icon_active.png
mobile/android/search/res/drawable-hdpi/search_icon_inactive.png
mobile/android/search/res/drawable-hdpi/search_launcher.png
mobile/android/search/res/drawable-hdpi/search_plus.png
mobile/android/search/res/drawable-hdpi/widget_bg.9.png
mobile/android/search/res/drawable-mdpi/ic_action_settings.png
mobile/android/search/res/drawable-mdpi/ic_widget_new_tab.png
mobile/android/search/res/drawable-mdpi/ic_widget_search.png
mobile/android/search/res/drawable-mdpi/network_error.png
mobile/android/search/res/drawable-mdpi/search_clear.png
mobile/android/search/res/drawable-mdpi/search_fox.png
mobile/android/search/res/drawable-mdpi/search_history.png
mobile/android/search/res/drawable-mdpi/search_icon_active.png
mobile/android/search/res/drawable-mdpi/search_icon_inactive.png
mobile/android/search/res/drawable-mdpi/search_launcher.png
mobile/android/search/res/drawable-mdpi/search_plus.png
mobile/android/search/res/drawable-mdpi/widget_bg.9.png
mobile/android/search/res/drawable-xhdpi/ic_action_settings.png
mobile/android/search/res/drawable-xhdpi/ic_widget_new_tab.png
mobile/android/search/res/drawable-xhdpi/ic_widget_search.png
mobile/android/search/res/drawable-xhdpi/network_error.png
mobile/android/search/res/drawable-xhdpi/search_clear.png
mobile/android/search/res/drawable-xhdpi/search_fox.png
mobile/android/search/res/drawable-xhdpi/search_history.png
mobile/android/search/res/drawable-xhdpi/search_icon_active.png
mobile/android/search/res/drawable-xhdpi/search_icon_inactive.png
mobile/android/search/res/drawable-xhdpi/search_launcher.png
mobile/android/search/res/drawable-xhdpi/search_plus.png
mobile/android/search/res/drawable-xhdpi/widget_bg.9.png
mobile/android/search/res/drawable-xxhdpi/ic_action_settings.png
mobile/android/search/res/drawable-xxhdpi/ic_widget_new_tab.png
mobile/android/search/res/drawable-xxhdpi/ic_widget_search.png
mobile/android/search/res/drawable-xxhdpi/network_error.png
mobile/android/search/res/drawable-xxhdpi/search_clear.png
mobile/android/search/res/drawable-xxhdpi/search_fox.png
mobile/android/search/res/drawable-xxhdpi/search_history.png
mobile/android/search/res/drawable-xxhdpi/search_icon_active.png
mobile/android/search/res/drawable-xxhdpi/search_icon_inactive.png
mobile/android/search/res/drawable-xxhdpi/search_launcher.png
mobile/android/search/res/drawable-xxhdpi/search_plus.png
mobile/android/search/res/drawable-xxxhdpi/search_launcher.png
mobile/android/search/res/drawable/edit_text_default.xml
mobile/android/search/res/drawable/edit_text_focused.xml
mobile/android/search/res/drawable/facet_button_background.xml
mobile/android/search/res/drawable/facet_button_background_default.xml
mobile/android/search/res/drawable/facet_button_background_pressed.xml
mobile/android/search/res/drawable/progressbar.xml
mobile/android/search/res/drawable/search_row_background.xml
mobile/android/search/res/drawable/widget_button_left.xml
mobile/android/search/res/drawable/widget_button_left_default.xml
mobile/android/search/res/drawable/widget_button_left_pressed.xml
mobile/android/search/res/drawable/widget_button_middle.xml
mobile/android/search/res/drawable/widget_button_middle_pressed.xml
mobile/android/search/res/drawable/widget_button_right.xml
mobile/android/search/res/drawable/widget_button_right_pressed.xml
mobile/android/search/res/layout/keyguard_widget.xml
mobile/android/search/res/layout/search_activity_main.xml
mobile/android/search/res/layout/search_bar.xml
mobile/android/search/res/layout/search_empty.xml
mobile/android/search/res/layout/search_fragment_post_search.xml
mobile/android/search/res/layout/search_fragment_pre_search.xml
mobile/android/search/res/layout/search_history_row.xml
mobile/android/search/res/layout/search_sugestions.xml
mobile/android/search/res/layout/search_suggestions_row.xml
mobile/android/search/res/layout/search_widget.xml
mobile/android/search/res/values-v13/search_styles.xml
mobile/android/search/res/values-v16/search_styles.xml
mobile/android/search/res/values/search_attrs.xml
mobile/android/search/res/values/search_colors.xml
mobile/android/search/res/values/search_dimens.xml
mobile/android/search/res/values/search_styles.xml
mobile/android/search/res/xml/search_preferences.xml
mobile/android/search/res/xml/search_widget_info.xml
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1618,16 +1618,19 @@ pref("loop.debug.websocket", false);
 pref("loop.debug.sdk", false);
 #ifdef DEBUG
 pref("loop.CSP", "default-src 'self' about: file: chrome: http://localhost:*; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net http://localhost:* ws://localhost:*");
 #else
 pref("loop.CSP", "default-src 'self' about: file: chrome:; img-src 'self' data: http://www.gravatar.com/ about: file: chrome:; font-src 'none'; connect-src wss://*.tokbox.com https://*.opentok.com https://*.tokbox.com wss://*.mozilla.com https://*.mozilla.org wss://*.mozaws.net");
 #endif
 pref("loop.oauth.google.redirect_uri", "urn:ietf:wg:oauth:2.0:oob:auto");
 pref("loop.oauth.google.scope", "https://www.google.com/m8/feeds");
+pref("loop.rooms.enabled", false);
+pref("loop.fxa_oauth.tokendata", "");
+pref("loop.fxa_oauth.profile", "");
 
 // serverURL to be assigned by services team
 pref("services.push.serverURL", "wss://push.services.mozilla.com/");
 
 pref("social.sidebar.unload_timeout_ms", 10000);
 
 pref("dom.identity.enabled", false);
 
--- a/browser/base/content/test/general/test_contextmenu.html
+++ b/browser/base/content/test/general/test_contextmenu.html
@@ -637,20 +637,20 @@ function runTest(testNum) {
         selectInputText(select_inputtext_password); // Select text prior to opening context menu.
         openContextMenuFor(select_inputtext_password); // Invoke context menu for next test.
     },
 
     function () {
         // Context menu for selected text in input[type="password"]
         checkContextMenu(["context-undo",        false,
                           "---",                 null,
-                          "context-cut",         true,
-                          "context-copy",        true,
+                          "context-cut",         false,
+                          "context-copy",        false,
                           "context-paste",       null, // ignore clipboard state
-                          "context-delete",      true,
+                          "context-delete",      false,
                           "---",                 null,
                           "context-selectall",   true,
                           "---",                 null,
                           "spell-check-enabled", true,
                           //spell checker is shown on input[type="password"] on this testcase
                           "spell-dictionaries",  true,
                               ["spell-check-dictionary-en-US", true,
                                "---",                          null,
--- a/browser/components/loop/GoogleImporter.jsm
+++ b/browser/components/loop/GoogleImporter.jsm
@@ -456,18 +456,20 @@ this.GoogleImporter.prototype = {
       }
     }
 
     let orgNodes = entry.getElementsByTagNameNS(kNS_GD, "organization");
     if (orgNodes.length) {
       contact.org = [];
       contact.jobTitle = [];
       for (let [,orgNode] of Iterator(orgNodes)) {
-        contact.org.push(orgNode.getElementsByTagNameNS(kNS_GD, "orgName")[0].firstChild.nodeValue);
-        contact.jobTitle.push(orgNode.getElementsByTagNameNS(kNS_GD, "orgTitle")[0].firstChild.nodeValue);
+        let orgElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgName")[0];
+        let titleElement = orgNode.getElementsByTagNameNS(kNS_GD, "orgTitle")[0];
+        contact.org.push(orgElement ? orgElement.firstChild.nodeValue : "")
+        contact.jobTitle.push(titleElement ? titleElement.firstChild.nodeValue : "");
       }
     }
 
     contact.category = ["google"];
 
     // Basic sanity checking: make sure the name field isn't empty
     if (!("name" in contact) || contact.name[0].length == 0) {
       if (("familyName" in contact) && ("givenName" in contact)) {
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -72,29 +72,37 @@ XPCOMUtils.defineLazyGetter(this, "log",
   let ConsoleAPI = Cu.import("resource://gre/modules/devtools/Console.jsm", {}).ConsoleAPI;
   let consoleOptions = {
     maxLogLevel: Services.prefs.getCharPref(PREF_LOG_LEVEL).toLowerCase(),
     prefix: "Loop",
   };
   return new ConsoleAPI(consoleOptions);
 });
 
+function setJSONPref(aName, aValue) {
+  let value = !!aValue ? JSON.stringify(aValue) : "";
+  Services.prefs.setCharPref(aName, value);
+}
+
+function getJSONPref(aName) {
+  let value = Services.prefs.getCharPref(aName);
+  return !!value ? JSON.parse(value) : null;
+}
+
 // 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 gLocalizedStrings =  null;
+let gLocalizedStrings = null;
 let gInitializeTimer = null;
 let gFxAEnabled = true;
 let gFxAOAuthClientPromise = null;
 let gFxAOAuthClient = null;
-let gFxAOAuthTokenData = null;
-let gFxAOAuthProfile = null;
 let gErrors = new Map();
 
  /**
  * Attempts to open a websocket.
  *
  * A new websocket interface is used each time. If an onStop callback
  * was received, calling asyncOpen() on the same interface will
  * trigger a "alreay open socket" exception even though the channel
@@ -302,16 +310,48 @@ let MozLoopServiceInternal = {
   /**
    * Returns true if the expiry time is in the future.
    */
   urlExpiryTimeIsInFuture: function() {
     return this.expiryTimeSeconds * 1000 > Date.now();
   },
 
   /**
+   * Retrieves MozLoopService Firefox Accounts OAuth token.
+   *
+   * @return {Object} OAuth token
+   */
+  get fxAOAuthTokenData() {
+    return getJSONPref("loop.fxa_oauth.tokendata");
+  },
+
+  /**
+   * Sets MozLoopService Firefox Accounts OAuth token.
+   * If the tokenData is being cleared, will also clear the
+   * profile since the profile is dependent on the token data.
+   *
+   * @param {Object} aTokenData OAuth token
+   */
+  set fxAOAuthTokenData(aTokenData) {
+    setJSONPref("loop.fxa_oauth.tokendata", aTokenData);
+    if (!aTokenData) {
+      this.fxAOAuthProfile = null;
+    }
+  },
+
+  /**
+   * Sets MozLoopService Firefox Accounts Profile data.
+   *
+   * @param {Object} aProfileData Profile data
+   */
+  set fxAOAuthProfile(aProfileData) {
+    setJSONPref("loop.fxa_oauth.profile", aProfileData);
+  },
+
+  /**
    * Retrieves MozLoopService "do not disturb" pref value.
    *
    * @return {Boolean} aFlag
    */
   get doNotDisturb() {
     return Services.prefs.getBoolPref("loop.do_not_disturb");
   },
 
@@ -415,19 +455,18 @@ let MozLoopServiceInternal = {
     }
 
     gRegisteredDeferred = Promise.defer();
     // We grab the promise early in case .initialize or its results sets
     // it back to null on error.
     let result = gRegisteredDeferred.promise;
 
     gPushHandler = mockPushHandler || MozLoopPushHandler;
-
     gPushHandler.initialize(this.onPushRegistered.bind(this),
-      this.onHandleNotification.bind(this));
+                            this.onHandleNotification.bind(this));
 
     return result;
   },
 
   /**
    * Performs a hawk based request to the loop server.
    *
    * @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
@@ -570,22 +609,25 @@ let MozLoopServiceInternal = {
       return;
     }
 
     this.registerWithLoopServer(LOOP_SESSION_TYPE.GUEST, pushUrl).then(() => {
       // storeSessionToken could have rejected and nulled the promise if the token was malformed.
       if (!gRegisteredDeferred) {
         return;
       }
-      gRegisteredDeferred.resolve();
+      gRegisteredDeferred.resolve("registered to guest status");
       // No need to clear the promise here, everything was good, so we don't need
       // to re-register.
-    }, (error) => {
+    }, error => {
       log.error("Failed to register with Loop server: ", error);
-      gRegisteredDeferred.reject(error.errno);
+      // registerWithLoopServer may have already made this null.
+      if (gRegisteredDeferred) {
+        gRegisteredDeferred.reject(error);
+      }
       gRegisteredDeferred = null;
     });
   },
 
   /**
    * Registers with the Loop server either as a guest or a FxA user.
    *
    * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
@@ -611,16 +653,18 @@ let MozLoopServiceInternal = {
           // Authorization failed, invalid token, we need to try again with a new token.
           if (retry) {
             return this.registerWithLoopServer(sessionType, pushUrl, false);
           }
         }
 
         log.error("Failed to register with the loop server. Error: ", error);
         this.setError("registration", error);
+        gRegisteredDeferred.reject(error);
+        gRegisteredDeferred = null;
         throw error;
       }
     );
   },
 
   /**
    * Unregisters from the Loop server either as a guest or a FxA user.
    *
@@ -1064,66 +1108,101 @@ let MozLoopServiceInternal = {
       deferred.resolve(result);
     } else {
       deferred.reject("Invalid token data");
     }
   },
 };
 Object.freeze(MozLoopServiceInternal);
 
-let gInitializeTimerFunc = () => {
-  // Kick off the push notification service into registering after a timeout
-  // this ensures we're not doing too much straight after the browser's finished
+let gInitializeTimerFunc = (deferredInitialization, mockPushHandler, mockWebSocket) => {
+  // Kick off the push notification service into registering after a timeout.
+  // This ensures we're not doing too much straight after the browser's finished
   // starting up.
   gInitializeTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-  gInitializeTimer.initWithCallback(() => {
-    MozLoopService.register();
+  gInitializeTimer.initWithCallback(Task.async(function* initializationCallback() {
+    yield MozLoopService.register(mockPushHandler, mockWebSocket).then(Task.async(function*() {
+      if (!MozLoopServiceInternal.fxAOAuthTokenData) {
+        log.debug("MozLoopService: Initialized without an already logged-in account");
+        deferredInitialization.resolve("initialized to guest status");
+        return;
+      }
+
+      log.debug("MozLoopService: Initializing with already logged-in account");
+      let registeredPromise =
+            MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA,
+                                                          gPushHandler.pushUrl);
+      registeredPromise.then(() => {
+        deferredInitialization.resolve("initialized to logged-in status");
+      }, error => {
+        log.debug("MozLoopService: error logging in using cached auth token");
+        MozLoopServiceInternal.setError("login", error);
+        deferredInitialization.reject("error logging in using cached auth token");
+      });
+    }), error => {
+      log.debug("MozLoopService: Failure of initial registration", error);
+      deferredInitialization.reject(error);
+    });
     gInitializeTimer = null;
-  },
+  }),
   MozLoopServiceInternal.initialRegistrationDelayMilliseconds, Ci.nsITimer.TYPE_ONE_SHOT);
 };
 
 /**
  * Public API
  */
 this.MozLoopService = {
   _DNSService: gDNSService,
 
   set initializeTimerFunc(value) {
     gInitializeTimerFunc = value;
   },
 
   /**
    * Initialized the loop service, and starts registration with the
    * push and loop servers.
+   *
+   * @return {Promise}
    */
-  initialize: function() {
-
+  initialize: Task.async(function*(mockPushHandler, mockWebSocket) {
     // Do this here, rather than immediately after definition, so that we can
     // stub out API functions for unit testing
     Object.freeze(this);
 
     // Don't do anything if loop is not enabled.
     if (!Services.prefs.getBoolPref("loop.enabled") ||
         Services.prefs.getBoolPref("loop.throttled")) {
-      return;
+      return Promise.reject("loop is not enabled");
     }
 
     if (Services.prefs.getPrefType("loop.fxa.enabled") == Services.prefs.PREF_BOOL) {
       gFxAEnabled = Services.prefs.getBoolPref("loop.fxa.enabled");
       if (!gFxAEnabled) {
-        this.logOutFromFxA();
+        yield this.logOutFromFxA();
       }
     }
 
-    // If expiresTime is in the future then kick-off registration.
-    if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
-      gInitializeTimerFunc();
+    // If expiresTime is not in the future and the user hasn't
+    // previously authenticated then skip registration.
+    if (!MozLoopServiceInternal.urlExpiryTimeIsInFuture() &&
+        !MozLoopServiceInternal.fxAOAuthTokenData) {
+      return Promise.resolve("registration not needed");
     }
-  },
+
+    let deferredInitialization = Promise.defer();
+    gInitializeTimerFunc(deferredInitialization, mockPushHandler, mockWebSocket);
+
+    return deferredInitialization.promise.catch(error => {
+      if (typeof(error) == "object") {
+        // This never gets cleared since there is no UI to recover. Only restarting will work.
+        MozLoopServiceInternal.setError("initialization", error);
+      }
+      throw error;
+    });
+  }),
 
   /**
    * 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
@@ -1243,17 +1322,17 @@ this.MozLoopService = {
   },
 
   /**
    * 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.
    *
-   * This is used to deterimine whether or not we should be registering with the
+   * This is used to determine whether or not we should be registering with the
    * push server on start.
    *
    * @param {Integer} expiryTimeSeconds The seconds since epoch of the expiry time
    *                                    of the url.
    */
   noteCallUrlExpiry: function(expiryTimeSeconds) {
     MozLoopServiceInternal.expiryTimeSeconds = expiryTimeSeconds;
   },
@@ -1300,18 +1379,26 @@ this.MozLoopService = {
   set doNotDisturb(aFlag) {
     MozLoopServiceInternal.doNotDisturb = aFlag;
   },
 
   get fxAEnabled() {
     return gFxAEnabled;
   },
 
+  /**
+   * Gets the user profile, but only if there is
+   * tokenData present. Without tokenData, the
+   * profile is meaningless.
+   *
+   * @return {Object}
+   */
   get userProfile() {
-    return gFxAOAuthProfile;
+    return getJSONPref("loop.fxa_oauth.tokendata") &&
+           getJSONPref("loop.fxa_oauth.profile");
   },
 
   get errors() {
     return MozLoopServiceInternal.errors;
   },
 
   get log() {
     return log;
@@ -1430,55 +1517,55 @@ this.MozLoopService = {
   /**
    * Start the FxA login flow using the OAuth client and params from the Loop server.
    *
    * The caller should be prepared to handle rejections related to network, server or login errors.
    *
    * @return {Promise} that resolves when the FxA login flow is complete.
    */
   logInToFxA: function() {
-    log.debug("logInToFxA with gFxAOAuthTokenData:", !!gFxAOAuthTokenData);
-    if (gFxAOAuthTokenData) {
-      return Promise.resolve(gFxAOAuthTokenData);
+    log.debug("logInToFxA with fxAOAuthTokenData:", !!MozLoopServiceInternal.fxAOAuthTokenData);
+    if (MozLoopServiceInternal.fxAOAuthTokenData) {
+      return Promise.resolve(MozLoopServiceInternal.fxAOAuthTokenData);
     }
 
     return MozLoopServiceInternal.promiseFxAOAuthAuthorization().then(response => {
       return MozLoopServiceInternal.promiseFxAOAuthToken(response.code, response.state);
     }).then(tokenData => {
-      gFxAOAuthTokenData = tokenData;
+      MozLoopServiceInternal.fxAOAuthTokenData = tokenData;
       return tokenData;
     }).then(tokenData => {
       return gRegisteredDeferred.promise.then(Task.async(function*() {
         if (gPushHandler.pushUrl) {
           yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, gPushHandler.pushUrl);
         } else {
           throw new Error("No pushUrl for FxA registration");
         }
         MozLoopServiceInternal.clearError("login");
         MozLoopServiceInternal.clearError("profile");
-        return gFxAOAuthTokenData;
+        return MozLoopServiceInternal.fxAOAuthTokenData;
       }));
     }).then(tokenData => {
       let client = new FxAccountsProfileClient({
         serverURL: gFxAOAuthClient.parameters.profile_uri,
         token: tokenData.access_token
       });
       client.fetchProfile().then(result => {
-        gFxAOAuthProfile = result;
+        MozLoopServiceInternal.fxAOAuthProfile = result;
         MozLoopServiceInternal.notifyStatusChanged("login");
       }, error => {
         log.error("Failed to retrieve profile", error);
         this.setError("profile", error);
-        gFxAOAuthProfile = null;
+        MozLoopServiceInternal.fxAOAuthProfile = null;
         MozLoopServiceInternal.notifyStatusChanged();
       });
       return tokenData;
     }).catch(error => {
-      gFxAOAuthTokenData = null;
-      gFxAOAuthProfile = null;
+      MozLoopServiceInternal.fxAOAuthTokenData = null;
+      MozLoopServiceInternal.fxAOAuthProfile = null;
       throw error;
     }).catch((error) => {
       MozLoopServiceInternal.setError("login", error);
       // Re-throw for testing
       throw error;
     });
   },
 
@@ -1493,18 +1580,18 @@ this.MozLoopService = {
     log.debug("logOutFromFxA");
     if (gPushHandler && gPushHandler.pushUrl) {
       yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA,
                                                             gPushHandler.pushUrl);
     } else {
       MozLoopServiceInternal.clearSessionToken(LOOP_SESSION_TYPE.FXA);
     }
 
-    gFxAOAuthTokenData = null;
-    gFxAOAuthProfile = null;
+    MozLoopServiceInternal.fxAOAuthTokenData = null;
+    MozLoopServiceInternal.fxAOAuthProfile = null;
 
     // Reset the client since the initial promiseFxAOAuthParameters() call is
     // what creates a new session.
     gFxAOAuthClient = null;
     gFxAOAuthClientPromise = null;
 
     // clearError calls notifyStatusChanged so should be done last when the
     // state is clean.
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -9,39 +9,55 @@
 
 var loop = loop || {};
 loop.panel = (function(_, mozL10n) {
   "use strict";
 
   var sharedViews = loop.shared.views;
   var sharedModels = loop.shared.models;
   var sharedMixins = loop.shared.mixins;
+  var sharedActions = loop.shared.actions;
   var Button = sharedViews.Button;
   var ButtonGroup = sharedViews.ButtonGroup;
   var ContactsList = loop.contacts.ContactsList;
   var ContactDetailsForm = loop.contacts.ContactDetailsForm;
   var __ = mozL10n.get; // aliasing translation function as __ for concision
 
   var TabView = React.createClass({displayName: 'TabView',
-    getInitialState: function() {
+    propTypes: {
+      buttonsHidden: React.PropTypes.bool,
+      // The selectedTab prop is used by the UI showcase.
+      selectedTab: React.PropTypes.string
+    },
+
+    getDefaultProps: function() {
       return {
+        buttonsHidden: false,
         selectedTab: "call"
       };
     },
 
+    getInitialState: function() {
+      return {selectedTab: this.props.selectedTab};
+    },
+
     handleSelectTab: function(event) {
       var tabName = event.target.dataset.tabName;
       this.setState({selectedTab: tabName});
     },
 
     render: function() {
       var cx = React.addons.classSet;
       var tabButtons = [];
       var tabs = [];
       React.Children.forEach(this.props.children, function(tab, i) {
+        // Filter out null tabs (eg. rooms when the feature is disabled)
+        if (!tab) {
+          return;
+        }
         var tabName = tab.props.name;
         var isSelected = (this.state.selectedTab == tabName);
         if (!tab.props.hidden) {
           tabButtons.push(
             React.DOM.li({className: cx({selected: isSelected}), 
                 key: i, 
                 'data-tab-name': tabName, 
                 onClick: this.handleSelectTab})
@@ -438,26 +454,145 @@ loop.panel = (function(_, mozL10n) {
         React.DOM.p({className: "user-identity"}, 
           this.props.displayName
         )
       );
     }
   });
 
   /**
+   * Room list entry.
+   */
+  var RoomEntry = React.createClass({displayName: 'RoomEntry',
+    propTypes: {
+      openRoom: React.PropTypes.func.isRequired,
+      room:     React.PropTypes.instanceOf(loop.store.Room).isRequired
+    },
+
+    shouldComponentUpdate: function(nextProps, nextState) {
+      return nextProps.room.ctime > this.props.room.ctime;
+    },
+
+    handleClickRoom: function(event) {
+      event.preventDefault();
+      this.props.openRoom(this.props.room);
+    },
+
+    _isActive: function() {
+      // XXX bug 1074679 will implement this properly
+      return this.props.room.currSize > 0;
+    },
+
+    render: function() {
+      var room = this.props.room;
+      var roomClasses = React.addons.classSet({
+        "room-entry": true,
+        "room-active": this._isActive()
+      });
+
+      return (
+        React.DOM.div({className: roomClasses}, 
+          React.DOM.h2(null, 
+            React.DOM.span({className: "room-notification"}), 
+            room.roomName
+          ), 
+          React.DOM.p(null, 
+            React.DOM.a({ref: "room", href: "#", onClick: this.handleClickRoom}, 
+              room.roomUrl
+            )
+          )
+        )
+      );
+    }
+  });
+
+  /**
+   * Room list.
+   */
+  var RoomList = React.createClass({displayName: 'RoomList',
+    mixins: [Backbone.Events],
+
+    propTypes: {
+      store: React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      rooms: React.PropTypes.array
+    },
+
+    getInitialState: function() {
+      var storeState = this.props.store.getStoreState();
+      return {
+        error: this.props.error || storeState.error,
+        rooms: this.props.rooms || storeState.rooms,
+      };
+    },
+
+    componentWillMount: function() {
+      this.listenTo(this.props.store, "change", this._onRoomListChanged);
+
+      this.props.dispatcher.dispatch(new sharedActions.GetAllRooms());
+    },
+
+    componentWillUnmount: function() {
+      this.stopListening(this.props.store);
+    },
+
+    _onRoomListChanged: function() {
+      var storeState = this.props.store.getStoreState();
+      this.setState({
+        error: storeState.error,
+        rooms: storeState.rooms
+      });
+    },
+
+    _getListHeading: function() {
+      var numRooms = this.state.rooms.length;
+      if (numRooms === 0) {
+        return mozL10n.get("rooms_list_no_current_conversations");
+      }
+      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
+    },
+
+    openRoom: function(room) {
+      // XXX implement me; see bug 1074678
+    },
+
+    render: function() {
+      if (this.state.error) {
+        // XXX Better end user reporting of errors.
+        console.error(this.state.error);
+      }
+
+      return (
+        React.DOM.div({className: "room-list"}, 
+          React.DOM.h1(null, this._getListHeading()), 
+          
+            this.state.rooms.map(function(room, i) {
+              return RoomEntry({key: i, room: room, openRoom: this.openRoom});
+            }, this)
+          
+        )
+      );
+    }
+  });
+
+  /**
    * Panel view.
    */
   var PanelView = React.createClass({displayName: 'PanelView',
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
       showTabButtons: React.PropTypes.bool,
+      selectedTab: React.PropTypes.string,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      roomListStore:
+        React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired
     },
 
     getInitialState: function() {
       return {
         userProfile: this.props.userProfile || navigator.mozLoop.userProfile,
       };
     },
 
@@ -493,16 +628,32 @@ loop.panel = (function(_, mozL10n) {
       if (profile != this.state.userProfile) {
         // On profile change (login, logout), switch back to the default tab.
         this.selectTab("call");
       }
       this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
+    /**
+     * The rooms feature is hidden by default for now. Once it gets mainstream,
+     * this method can be safely removed.
+     */
+    _renderRoomsTab: function() {
+      if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
+        return null;
+      }
+      return (
+        Tab({name: "rooms"}, 
+          RoomList({dispatcher: this.props.dispatcher, 
+                    store: this.props.roomListStore})
+        )
+      );
+    },
+
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
     selectTab: function(name) {
       this.refs.tabView.setState({ selectedTab: name });
     },
@@ -522,25 +673,27 @@ loop.panel = (function(_, mozL10n) {
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
       var displayName = this.state.userProfile && this.state.userProfile.email ||
                         __("display_name_guest");
       return (
         React.DOM.div(null, 
           NotificationListView({notifications: this.props.notifications, 
                                 clearOnDocumentHidden: true}), 
-          TabView({ref: "tabView", buttonsHidden: !this.state.userProfile && !this.props.showTabButtons}, 
+          TabView({ref: "tabView", selectedTab: this.props.selectedTab, 
+            buttonsHidden: !this.state.userProfile && !this.props.showTabButtons}, 
             Tab({name: "call"}, 
               React.DOM.div({className: "content-area"}, 
                 CallUrlResult({client: this.props.client, 
                                notifications: this.props.notifications, 
                                callUrl: this.props.callUrl}), 
                 ToSView(null)
               )
             ), 
+            this._renderRoomsTab(), 
             Tab({name: "contacts"}, 
               ContactsList({selectTab: this.selectTab, 
                             startForm: this.startForm})
             ), 
             Tab({name: "contacts_add", hidden: true}, 
               ContactDetailsForm({ref: "contacts_add", mode: "add", 
                                   selectTab: this.selectTab})
             ), 
@@ -570,21 +723,29 @@ loop.panel = (function(_, mozL10n) {
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     var client = new loop.Client();
-    var notifications = new sharedModels.NotificationCollection()
+    var notifications = new sharedModels.NotificationCollection();
+    var dispatcher = new loop.Dispatcher();
+    var roomListStore = new loop.store.RoomListStore({
+      mozLoop: navigator.mozLoop,
+      dispatcher: dispatcher
+    });
 
     React.renderComponent(PanelView({
       client: client, 
-      notifications: notifications}), document.querySelector("#main"));
+      notifications: notifications, 
+      roomListStore: roomListStore, 
+      dispatcher: dispatcher}
+    ), document.querySelector("#main"));
 
     document.body.classList.add(loop.shared.utils.getTargetPlatform());
     document.body.setAttribute("dir", mozL10n.getDirection());
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
@@ -592,14 +753,15 @@ loop.panel = (function(_, mozL10n) {
 
   return {
     init: init,
     UserIdentity: UserIdentity,
     AuthLink: AuthLink,
     AvailabilityDropdown: AvailabilityDropdown,
     CallUrlResult: CallUrlResult,
     PanelView: PanelView,
+    RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     ToSView: ToSView
   };
 })(_, document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.panel.init);
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -9,39 +9,55 @@
 
 var loop = loop || {};
 loop.panel = (function(_, mozL10n) {
   "use strict";
 
   var sharedViews = loop.shared.views;
   var sharedModels = loop.shared.models;
   var sharedMixins = loop.shared.mixins;
+  var sharedActions = loop.shared.actions;
   var Button = sharedViews.Button;
   var ButtonGroup = sharedViews.ButtonGroup;
   var ContactsList = loop.contacts.ContactsList;
   var ContactDetailsForm = loop.contacts.ContactDetailsForm;
   var __ = mozL10n.get; // aliasing translation function as __ for concision
 
   var TabView = React.createClass({
-    getInitialState: function() {
+    propTypes: {
+      buttonsHidden: React.PropTypes.bool,
+      // The selectedTab prop is used by the UI showcase.
+      selectedTab: React.PropTypes.string
+    },
+
+    getDefaultProps: function() {
       return {
+        buttonsHidden: false,
         selectedTab: "call"
       };
     },
 
+    getInitialState: function() {
+      return {selectedTab: this.props.selectedTab};
+    },
+
     handleSelectTab: function(event) {
       var tabName = event.target.dataset.tabName;
       this.setState({selectedTab: tabName});
     },
 
     render: function() {
       var cx = React.addons.classSet;
       var tabButtons = [];
       var tabs = [];
       React.Children.forEach(this.props.children, function(tab, i) {
+        // Filter out null tabs (eg. rooms when the feature is disabled)
+        if (!tab) {
+          return;
+        }
         var tabName = tab.props.name;
         var isSelected = (this.state.selectedTab == tabName);
         if (!tab.props.hidden) {
           tabButtons.push(
             <li className={cx({selected: isSelected})}
                 key={i}
                 data-tab-name={tabName}
                 onClick={this.handleSelectTab} />
@@ -438,26 +454,145 @@ loop.panel = (function(_, mozL10n) {
         <p className="user-identity">
           {this.props.displayName}
         </p>
       );
     }
   });
 
   /**
+   * Room list entry.
+   */
+  var RoomEntry = React.createClass({
+    propTypes: {
+      openRoom: React.PropTypes.func.isRequired,
+      room:     React.PropTypes.instanceOf(loop.store.Room).isRequired
+    },
+
+    shouldComponentUpdate: function(nextProps, nextState) {
+      return nextProps.room.ctime > this.props.room.ctime;
+    },
+
+    handleClickRoom: function(event) {
+      event.preventDefault();
+      this.props.openRoom(this.props.room);
+    },
+
+    _isActive: function() {
+      // XXX bug 1074679 will implement this properly
+      return this.props.room.currSize > 0;
+    },
+
+    render: function() {
+      var room = this.props.room;
+      var roomClasses = React.addons.classSet({
+        "room-entry": true,
+        "room-active": this._isActive()
+      });
+
+      return (
+        <div className={roomClasses}>
+          <h2>
+            <span className="room-notification" />
+            {room.roomName}
+          </h2>
+          <p>
+            <a ref="room" href="#" onClick={this.handleClickRoom}>
+              {room.roomUrl}
+            </a>
+          </p>
+        </div>
+      );
+    }
+  });
+
+  /**
+   * Room list.
+   */
+  var RoomList = React.createClass({
+    mixins: [Backbone.Events],
+
+    propTypes: {
+      store: React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      rooms: React.PropTypes.array
+    },
+
+    getInitialState: function() {
+      var storeState = this.props.store.getStoreState();
+      return {
+        error: this.props.error || storeState.error,
+        rooms: this.props.rooms || storeState.rooms,
+      };
+    },
+
+    componentWillMount: function() {
+      this.listenTo(this.props.store, "change", this._onRoomListChanged);
+
+      this.props.dispatcher.dispatch(new sharedActions.GetAllRooms());
+    },
+
+    componentWillUnmount: function() {
+      this.stopListening(this.props.store);
+    },
+
+    _onRoomListChanged: function() {
+      var storeState = this.props.store.getStoreState();
+      this.setState({
+        error: storeState.error,
+        rooms: storeState.rooms
+      });
+    },
+
+    _getListHeading: function() {
+      var numRooms = this.state.rooms.length;
+      if (numRooms === 0) {
+        return mozL10n.get("rooms_list_no_current_conversations");
+      }
+      return mozL10n.get("rooms_list_current_conversations", {num: numRooms});
+    },
+
+    openRoom: function(room) {
+      // XXX implement me; see bug 1074678
+    },
+
+    render: function() {
+      if (this.state.error) {
+        // XXX Better end user reporting of errors.
+        console.error(this.state.error);
+      }
+
+      return (
+        <div className="room-list">
+          <h1>{this._getListHeading()}</h1>
+          {
+            this.state.rooms.map(function(room, i) {
+              return <RoomEntry key={i} room={room} openRoom={this.openRoom} />;
+            }, this)
+          }
+        </div>
+      );
+    }
+  });
+
+  /**
    * Panel view.
    */
   var PanelView = React.createClass({
     propTypes: {
       notifications: React.PropTypes.object.isRequired,
       client: React.PropTypes.object.isRequired,
       // Mostly used for UI components showcase and unit tests
       callUrl: React.PropTypes.string,
       userProfile: React.PropTypes.object,
       showTabButtons: React.PropTypes.bool,
+      selectedTab: React.PropTypes.string,
+      dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
+      roomListStore:
+        React.PropTypes.instanceOf(loop.store.RoomListStore).isRequired
     },
 
     getInitialState: function() {
       return {
         userProfile: this.props.userProfile || navigator.mozLoop.userProfile,
       };
     },
 
@@ -493,16 +628,32 @@ loop.panel = (function(_, mozL10n) {
       if (profile != this.state.userProfile) {
         // On profile change (login, logout), switch back to the default tab.
         this.selectTab("call");
       }
       this.setState({userProfile: profile});
       this.updateServiceErrors();
     },
 
+    /**
+     * The rooms feature is hidden by default for now. Once it gets mainstream,
+     * this method can be safely removed.
+     */
+    _renderRoomsTab: function() {
+      if (!navigator.mozLoop.getLoopBoolPref("rooms.enabled")) {
+        return null;
+      }
+      return (
+        <Tab name="rooms">
+          <RoomList dispatcher={this.props.dispatcher}
+                    store={this.props.roomListStore} />
+        </Tab>
+      );
+    },
+
     startForm: function(name, contact) {
       this.refs[name].initForm(contact);
       this.selectTab(name);
     },
 
     selectTab: function(name) {
       this.refs.tabView.setState({ selectedTab: name });
     },
@@ -522,25 +673,27 @@ loop.panel = (function(_, mozL10n) {
     render: function() {
       var NotificationListView = sharedViews.NotificationListView;
       var displayName = this.state.userProfile && this.state.userProfile.email ||
                         __("display_name_guest");
       return (
         <div>
           <NotificationListView notifications={this.props.notifications}
                                 clearOnDocumentHidden={true} />
-          <TabView ref="tabView" buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
+          <TabView ref="tabView" selectedTab={this.props.selectedTab}
+            buttonsHidden={!this.state.userProfile && !this.props.showTabButtons}>
             <Tab name="call">
               <div className="content-area">
                 <CallUrlResult client={this.props.client}
                                notifications={this.props.notifications}
                                callUrl={this.props.callUrl} />
                 <ToSView />
               </div>
             </Tab>
+            {this._renderRoomsTab()}
             <Tab name="contacts">
               <ContactsList selectTab={this.selectTab}
                             startForm={this.startForm} />
             </Tab>
             <Tab name="contacts_add" hidden={true}>
               <ContactDetailsForm ref="contacts_add" mode="add"
                                   selectTab={this.selectTab} />
             </Tab>
@@ -570,21 +723,29 @@ loop.panel = (function(_, mozL10n) {
    * Panel initialisation.
    */
   function init() {
     // Do the initial L10n setup, we do this before anything
     // else to ensure the L10n environment is setup correctly.
     mozL10n.initialize(navigator.mozLoop);
 
     var client = new loop.Client();
-    var notifications = new sharedModels.NotificationCollection()
+    var notifications = new sharedModels.NotificationCollection();
+    var dispatcher = new loop.Dispatcher();
+    var roomListStore = new loop.store.RoomListStore({
+      mozLoop: navigator.mozLoop,
+      dispatcher: dispatcher
+    });
 
     React.renderComponent(<PanelView
       client={client}
-      notifications={notifications} />, document.querySelector("#main"));
+      notifications={notifications}
+      roomListStore={roomListStore}
+      dispatcher={dispatcher}
+    />, document.querySelector("#main"));
 
     document.body.classList.add(loop.shared.utils.getTargetPlatform());
     document.body.setAttribute("dir", mozL10n.getDirection());
 
     // Notify the window that we've finished initalization and initial layout
     var evtObject = document.createEvent('Event');
     evtObject.initEvent('loopPanelInitialized', true, false);
     window.dispatchEvent(evtObject);
@@ -592,14 +753,15 @@ loop.panel = (function(_, mozL10n) {
 
   return {
     init: init,
     UserIdentity: UserIdentity,
     AuthLink: AuthLink,
     AvailabilityDropdown: AvailabilityDropdown,
     CallUrlResult: CallUrlResult,
     PanelView: PanelView,
+    RoomList: RoomList,
     SettingsDropdown: SettingsDropdown,
     ToSView: ToSView
   };
 })(_, document.mozL10n);
 
 document.addEventListener('DOMContentLoaded', loop.panel.init);
--- a/browser/components/loop/content/panel.html
+++ b/browser/components/loop/content/panel.html
@@ -20,13 +20,17 @@
     <script type="text/javascript" src="loop/shared/libs/jquery-2.1.0.js"></script>
     <script type="text/javascript" src="loop/shared/libs/lodash-2.4.1.js"></script>
     <script type="text/javascript" src="loop/shared/libs/backbone-1.1.2.js"></script>
 
     <script type="text/javascript" src="loop/shared/js/utils.js"></script>
     <script type="text/javascript" src="loop/shared/js/models.js"></script>
     <script type="text/javascript" src="loop/shared/js/mixins.js"></script>
     <script type="text/javascript" src="loop/shared/js/views.js"></script>
+    <script type="text/javascript" src="loop/shared/js/validate.js"></script>
+    <script type="text/javascript" src="loop/shared/js/actions.js"></script>
+    <script type="text/javascript" src="loop/shared/js/dispatcher.js"></script>
+    <script type="text/javascript" src="loop/shared/js/roomListStore.js"></script>
     <script type="text/javascript" src="loop/js/client.js"></script>
     <script type="text/javascript;version=1.8" src="loop/js/contacts.js"></script>
     <script type="text/javascript" src="loop/js/panel.js"></script>
  </body>
 </html>
--- a/browser/components/loop/content/shared/css/panel.css
+++ b/browser/components/loop/content/shared/css/panel.css
@@ -1,12 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+body {
+  background: none;
+}
+
 /* Panel styles */
 
 .panel {
   /* hide the extra margin space that the panel resizer now wants to show */
   overflow: hidden;
 }
 
 /* Notifications displayed over tabs */
@@ -21,17 +25,16 @@
 
 /* Tabs and tab selection buttons */
 
 .tab-view {
   display: flex;
   flex-direction: row;
   padding: 10px;
   border-bottom: 1px solid #ccc;
-  background-color: #fbfbfb;
   color: #000;
   border-top-right-radius: 2px;
   border-top-left-radius: 2px;
   list-style: none;
 }
 
 .tab-view > li {
   flex: 1;
@@ -115,16 +118,80 @@
   box-shadow: none;
 }
 
 .content-area input:not(.pristine):invalid {
   border-color: #d74345;
   box-shadow: 0 0 4px #c43c3e;
 }
 
+/* Rooms */
+.room-list {
+  background: #f5f5f5;
+}
+
+.room-list > h1 {
+  font-weight: bold;
+  color: #999;
+  padding: .5rem 1rem;
+  border-bottom: 1px solid #ddd;
+}
+
+.room-list > .room-entry {
+  padding: 1rem 1rem 0 .5rem;
+}
+
+.room-list > .room-entry > h2 {
+  font-size: .85rem;
+  color: #777;
+}
+
+.room-list > .room-entry.room-active > h2 {
+  font-weight: bold;
+  color: #000;
+}
+
+.room-list > .room-entry > h2 > .room-notification {
+  display: inline-block;
+  background: transparent;
+  width: 8px;
+  height: 8px;
+  border-radius: 50%;
+  margin-right: .3rem;
+}
+
+.room-list > .room-entry.room-active > h2 > .room-notification {
+  background-color: #00a0ec;
+}
+
+.room-list > .room-entry:hover {
+  background: #f1f1f1;
+}
+
+.room-list > .room-entry:not(:last-child) {
+  border-bottom: 1px solid #ddd;
+}
+
+.room-list > .room-entry > p {
+  margin: 0;
+  padding: .2em 0 1rem .8rem;
+}
+
+.room-list > .room-entry > p > a {
+  color: #777;
+  opacity: .5;
+  transition: opacity .1s ease-in-out 0s;
+  text-decoration: none;
+}
+
+.room-list > .room-entry > p > a:hover {
+  opacity: 1;
+  text-decoration: underline;
+}
+
 /* Buttons */
 
 .button-group {
   display: flex;
   flex-direction: row;
   width: 100%;
 }
 
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -113,11 +113,18 @@ loop.shared.actions = (function() {
     /**
      * Used to mute or unmute a stream
      */
     SetMute: Action.define("setMute", {
       // The part of the stream to enable, e.g. "audio" or "video"
       type: String,
       // Whether or not to enable the stream.
       enabled: Boolean
+    }),
+
+    /**
+     * Retrieves room list.
+     * XXX: should move to some roomActions module - refs bug 1079284
+     */
+    GetAllRooms: Action.define("getAllRooms", {
     })
   };
 })();
--- a/browser/components/loop/content/shared/js/conversationStore.js
+++ b/browser/components/loop/content/shared/js/conversationStore.js
@@ -1,42 +1,43 @@
 /* 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/. */
 
 /* global loop:true */
 
 var loop = loop || {};
-loop.store = (function() {
+loop.store = loop.store || {};
 
+loop.store.ConversationStore = (function() {
   var sharedActions = loop.shared.actions;
   var CALL_TYPES = loop.shared.utils.CALL_TYPES;
 
   /**
    * Websocket states taken from:
    * https://docs.services.mozilla.com/loop/apis.html#call-progress-state-change-progress
    */
-  var WS_STATES = {
+  var WS_STATES = loop.store.WS_STATES = {
     // The call is starting, and the remote party is not yet being alerted.
     INIT: "init",
     // The called party is being alerted.
     ALERTING: "alerting",
     // The call is no longer being set up and has been aborted for some reason.
     TERMINATED: "terminated",
     // The called party has indicated that he has answered the call,
     // but the media is not yet confirmed.
     CONNECTING: "connecting",
     // One of the two parties has indicated successful media set up,
     // but the other has not yet.
     HALF_CONNECTED: "half-connected",
     // Both endpoints have reported successfully establishing media.
     CONNECTED: "connected"
   };
 
-  var CALL_STATES = {
+  var CALL_STATES = loop.store.CALL_STATES = {
     // The initial state of the view.
     INIT: "cs-init",
     // The store is gathering the call data from the server.
     GATHER: "cs-gather",
     // The initial data has been gathered, the websocket is connecting, or has
     // connected, and waiting for the other side to connect to the server.
     CONNECTING: "cs-connecting",
     // The websocket has received information that we're now alerting
@@ -47,17 +48,16 @@ loop.store = (function() {
     // The call ended successfully.
     FINISHED: "cs-finished",
     // The user has finished with the window.
     CLOSE: "cs-close",
     // The call was terminated due to an issue during connection.
     TERMINATED: "cs-terminated"
   };
 
-
   var ConversationStore = Backbone.Model.extend({
     defaults: {
       // The current state of the call
       callState: CALL_STATES.INIT,
       // The reason if a call was terminated
       callStateReason: undefined,
       // The error information, if there was a failure
       error: undefined,
@@ -397,14 +397,10 @@ loop.store = (function() {
           break;
         }
       }
 
       this.dispatcher.dispatch(action);
     }
   });
 
-  return {
-    CALL_STATES: CALL_STATES,
-    ConversationStore: ConversationStore,
-    WS_STATES: WS_STATES
-  };
+  return ConversationStore;
 })();
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/content/shared/js/roomListStore.js
@@ -0,0 +1,171 @@
+/* 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/. */
+
+/* global loop:true */
+
+var loop = loop || {};
+loop.store = loop.store || {};
+
+(function() {
+  "use strict";
+
+  /**
+   * Room validation schema. See validate.js.
+   * @type {Object}
+   */
+  var roomSchema = {
+    roomToken: String,
+    roomUrl:   String,
+    roomName:  String,
+    maxSize:   Number,
+    currSize:  Number,
+    ctime:     Number
+  };
+
+  /**
+   * Temporary sample raw room list data.
+   * XXX Should be removed when we plug the real mozLoop API for rooms.
+   *     See bug 1074664.
+   * @type {Array}
+   */
+  var temporaryRawRoomList = [{
+    roomToken: "_nxD4V4FflQ",
+    roomUrl: "http://sample/_nxD4V4FflQ",
+    roomName: "First Room Name",
+    maxSize: 2,
+    currSize: 0,
+    ctime: 1405517546
+  }, {
+    roomToken: "QzBbvGmIZWU",
+    roomUrl: "http://sample/QzBbvGmIZWU",
+    roomName: "Second Room Name",
+    maxSize: 2,
+    currSize: 0,
+    ctime: 1405517418
+  }, {
+    roomToken: "3jKS_Els9IU",
+    roomUrl: "http://sample/3jKS_Els9IU",
+    roomName: "Third Room Name",
+    maxSize: 3,
+    clientMaxSize: 2,
+    currSize: 1,
+    ctime: 1405518241
+  }];
+
+  /**
+   * Room type. Basically acts as a typed object constructor.
+   *
+   * @param {Object} values Room property values.
+   */
+  function Room(values) {
+    var validatedData = new loop.validate.Validator(roomSchema || {})
+                                         .validate(values || {});
+    for (var prop in validatedData) {
+      this[prop] = validatedData[prop];
+    }
+  }
+
+  loop.store.Room = Room;
+
+  /**
+   * Room store.
+   *
+   * Options:
+   * - {loop.Dispatcher} dispatcher The dispatcher for dispatching actions and
+   *                                registering to consume actions.
+   * - {mozLoop}         mozLoop    The MozLoop API object.
+   *
+   * @extends {Backbone.Events}
+   * @param {Object} options Options object.
+   */
+  function RoomListStore(options) {
+    options = options || {};
+    this.storeState = {error: null, rooms: []};
+
+    if (!options.dispatcher) {
+      throw new Error("Missing option dispatcher");
+    }
+    this.dispatcher = options.dispatcher;
+
+    if (!options.mozLoop) {
+      throw new Error("Missing option mozLoop");
+    }
+    this.mozLoop = options.mozLoop;
+
+    this.dispatcher.register(this, [
+      "getAllRooms",
+      "openRoom"
+    ]);
+  }
+
+  RoomListStore.prototype = _.extend({
+    /**
+     * Retrieves current store state.
+     *
+     * @return {Object}
+     */
+    getStoreState: function() {
+      return this.storeState;
+    },
+
+    /**
+     * Updates store states and trigger a "change" event.
+     *
+     * @param {Object} state The new store state.
+     */
+    setStoreState: function(state) {
+      this.storeState = state;
+      this.trigger("change");
+    },
+
+    /**
+     * Proxy to navigator.mozLoop.rooms.getAll.
+     * XXX Could probably be removed when bug 1074664 lands.
+     *
+     * @param  {Function} cb Callback(error, roomList)
+     */
+    _fetchRoomList: function(cb) {
+      // Faking this.mozLoop.rooms until it's available; bug 1074664.
+      if (!this.mozLoop.hasOwnProperty("rooms")) {
+        cb(null, temporaryRawRoomList);
+        return;
+      }
+      this.mozLoop.rooms.getAll(cb);
+    },
+
+    /**
+     * Maps and sorts the raw room list received from the mozLoop API.
+     *
+     * @param  {Array} rawRoomList Raw room list.
+     * @return {Array}
+     */
+    _processRawRoomList: function(rawRoomList) {
+      if (!rawRoomList) {
+        return [];
+      }
+      return rawRoomList
+        .map(function(rawRoom) {
+          return new Room(rawRoom);
+        })
+        .slice()
+        .sort(function(a, b) {
+          return b.ctime - a.ctime;
+        });
+    },
+
+    /**
+     * Gather the list of all available rooms from the MozLoop API.
+     */
+    getAllRooms: function() {
+      this._fetchRoomList(function(err, rawRoomList) {
+        this.setStoreState({
+          error: err,
+          rooms: this._processRawRoomList(rawRoomList)
+        });
+      }.bind(this));
+    }
+  }, Backbone.Events);
+
+  loop.store.RoomListStore = RoomListStore;
+})();
--- a/browser/components/loop/jar.mn
+++ b/browser/components/loop/jar.mn
@@ -50,16 +50,17 @@ browser.jar:
   content/browser/loop/shared/img/audio-call-avatar.svg         (content/shared/img/audio-call-avatar.svg)
   content/browser/loop/shared/img/icons-10x10.svg               (content/shared/img/icons-10x10.svg)
   content/browser/loop/shared/img/icons-14x14.svg               (content/shared/img/icons-14x14.svg)
   content/browser/loop/shared/img/icons-16x16.svg               (content/shared/img/icons-16x16.svg)
 
   # Shared scripts
   content/browser/loop/shared/js/actions.js           (content/shared/js/actions.js)
   content/browser/loop/shared/js/conversationStore.js (content/shared/js/conversationStore.js)
+  content/browser/loop/shared/js/roomListStore.js     (content/shared/js/roomListStore.js)
   content/browser/loop/shared/js/dispatcher.js        (content/shared/js/dispatcher.js)
   content/browser/loop/shared/js/feedbackApiClient.js (content/shared/js/feedbackApiClient.js)
   content/browser/loop/shared/js/models.js            (content/shared/js/models.js)
   content/browser/loop/shared/js/mixins.js            (content/shared/js/mixins.js)
   content/browser/loop/shared/js/otSdkDriver.js       (content/shared/js/otSdkDriver.js)
   content/browser/loop/shared/js/views.js             (content/shared/js/views.js)
   content/browser/loop/shared/js/utils.js             (content/shared/js/utils.js)
   content/browser/loop/shared/js/validate.js          (content/shared/js/validate.js)
--- a/browser/components/loop/test/desktop-local/index.html
+++ b/browser/components/loop/test/desktop-local/index.html
@@ -38,16 +38,17 @@
   <script src="../../content/shared/js/models.js"></script>
   <script src="../../content/shared/js/mixins.js"></script>
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/actions.js"></script>
   <script src="../../content/shared/js/validate.js"></script>
   <script src="../../content/shared/js/dispatcher.js"></script>
   <script src="../../content/shared/js/otSdkDriver.js"></script>
+  <script src="../../content/shared/js/roomListStore.js"></script>
   <script src="../../content/js/client.js"></script>
   <script src="../../content/js/conversationViews.js"></script>
   <script src="../../content/js/conversation.js"></script>
   <script type="text/javascript;version=1.8" src="../../content/js/contacts.js"></script>
   <script src="../../content/js/panel.js"></script>
 
   <!-- Test scripts -->
   <script src="client_test.js"></script>
--- a/browser/components/loop/test/desktop-local/panel_test.js
+++ b/browser/components/loop/test/desktop-local/panel_test.js
@@ -2,23 +2,24 @@
  * 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/. */
 
 /*jshint newcap:false*/
 /*global loop, sinon */
 
 var expect = chai.expect;
 var TestUtils = React.addons.TestUtils;
+var sharedActions = loop.shared.actions;
 
 describe("loop.panel", function() {
   "use strict";
 
   var sandbox, notifications, fakeXHR, requests = [];
 
-  beforeEach(function() {
+  beforeEach(function(done) {
     sandbox = sinon.sandbox.create();
     fakeXHR = sandbox.useFakeXMLHttpRequest();
     requests = [];
     // https://github.com/cjohansen/Sinon.JS/issues/393
     fakeXHR.xhr.onCreate = function (xhr) {
       requests.push(xhr);
     };
     notifications = new loop.shared.models.NotificationCollection();
@@ -27,31 +28,37 @@ describe("loop.panel", function() {
       doNotDisturb: true,
       fxAEnabled: true,
       getStrings: function() {
         return JSON.stringify({textContent: "fakeText"});
       },
       get locale() {
         return "en-US";
       },
+      getLoopBoolPref: sandbox.stub(),
       setLoopCharPref: sandbox.stub(),
       getLoopCharPref: sandbox.stub().returns("unseen"),
+      getPluralForm: function() {
+        return "fakeText";
+      },
       copyString: sandbox.stub(),
       noteCallUrlExpiry: sinon.spy(),
       composeEmail: sinon.spy(),
       telemetryAdd: sinon.spy(),
       contacts: {
         getAll: function(callback) {
           callback(null, []);
         },
         on: sandbox.stub()
       }
     };
 
     document.mozL10n.initialize(navigator.mozLoop);
+    // XXX prevent a race whenever mozL10n hasn't been initialized yet
+    setTimeout(done, 0);
   });
 
   afterEach(function() {
     delete navigator.mozLoop;
     sandbox.restore();
   });
 
   describe("#init", function() {
@@ -121,64 +128,129 @@ describe("loop.panel", function() {
         TestUtils.Simulate.click(availableMenuOption);
 
         expect(view.state.showMenu).eql(true);
       });
     });
   });
 
   describe("loop.panel.PanelView", function() {
-    var fakeClient, callUrlData, view, callTab, contactsTab;
+    var fakeClient, dispatcher, roomListStore, callUrlData;
 
     beforeEach(function() {
       callUrlData = {
         callUrl: "http://call.invalid/",
         expiresAt: 1000
       };
 
       fakeClient = {
         requestCallUrl: function(_, cb) {
           cb(null, callUrlData);
         }
       };
 
-      view = TestUtils.renderIntoDocument(loop.panel.PanelView({
+      dispatcher = new loop.Dispatcher();
+      roomListStore = new loop.store.RoomListStore({
+        dispatcher: dispatcher,
+        mozLoop: navigator.mozLoop
+      });
+    });
+
+    function createTestPanelView() {
+      return TestUtils.renderIntoDocument(loop.panel.PanelView({
         notifications: notifications,
         client: fakeClient,
         showTabButtons: true,
+        dispatcher: dispatcher,
+        roomListStore: roomListStore
       }));
-
-      [callTab, contactsTab] =
-        TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
-    });
+    }
 
     describe('TabView', function() {
-      it("should select contacts tab when clicking tab button", function() {
-        TestUtils.Simulate.click(
-          view.getDOMNode().querySelector('li[data-tab-name="contacts"]'));
+      var view, callTab, roomsTab, contactsTab;
+
+      describe("loop.rooms.enabled on", function() {
+        beforeEach(function() {
+          navigator.mozLoop.getLoopBoolPref = function(pref) {
+            if (pref === "rooms.enabled") {
+              return true;
+            }
+          };
+
+          view = createTestPanelView();
+
+          [callTab, roomsTab, contactsTab] =
+            TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
+        });
+
+        it("should select contacts tab when clicking tab button", function() {
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
 
-        expect(contactsTab.getDOMNode().classList.contains("selected"))
-          .to.be.true;
+          expect(contactsTab.getDOMNode().classList.contains("selected"))
+            .to.be.true;
+        });
+
+        it("should select rooms tab when clicking tab button", function() {
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector("li[data-tab-name=\"rooms\"]"));
+
+          expect(roomsTab.getDOMNode().classList.contains("selected"))
+            .to.be.true;
+        });
+
+        it("should select call tab when clicking tab button", function() {
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
+
+          expect(callTab.getDOMNode().classList.contains("selected"))
+            .to.be.true;
+        });
       });
 
-      it("should select call tab when clicking tab button", function() {
-        TestUtils.Simulate.click(
-          view.getDOMNode().querySelector('li[data-tab-name="call"]'));
+      describe("loop.rooms.enabled off", function() {
+        beforeEach(function() {
+          navigator.mozLoop.getLoopBoolPref = function(pref) {
+            if (pref === "rooms.enabled") {
+              return false;
+            }
+          };
+
+          view = createTestPanelView();
+
+          [callTab, contactsTab] =
+            TestUtils.scryRenderedDOMComponentsWithClass(view, "tab");
+        });
 
-        expect(callTab.getDOMNode().classList.contains("selected"))
-          .to.be.true;
+        it("should select contacts tab when clicking tab button", function() {
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector("li[data-tab-name=\"contacts\"]"));
+
+          expect(contactsTab.getDOMNode().classList.contains("selected"))
+            .to.be.true;
+        });
+
+        it("should select call tab when clicking tab button", function() {
+          TestUtils.Simulate.click(
+            view.getDOMNode().querySelector("li[data-tab-name=\"call\"]"));
+
+          expect(callTab.getDOMNode().classList.contains("selected"))
+            .to.be.true;
+        });
       });
     });
 
     describe("AuthLink", function() {
       it("should trigger the FxA sign in/up process when clicking the link",
         function() {
           navigator.mozLoop.loggedInToFxA = false;
           navigator.mozLoop.logInToFxA = sandbox.stub();
 
+          var view = createTestPanelView();
+
           TestUtils.Simulate.click(
             view.getDOMNode().querySelector(".signin-link a"));
 
           sinon.assert.calledOnce(navigator.mozLoop.logInToFxA);
         });
 
       it("should be hidden if FxA is not enabled",
         function() {
@@ -188,18 +260,16 @@ describe("loop.panel", function() {
       });
 
       afterEach(function() {
         navigator.mozLoop.fxAEnabled = true;
       });
     });
 
     describe("SettingsDropdown", function() {
-      var view;
-
       beforeEach(function() {
         navigator.mozLoop.logInToFxA = sandbox.stub();
         navigator.mozLoop.logOutFromFxA = sandbox.stub();
         navigator.mozLoop.openFxASettings = sandbox.stub();
       });
 
       afterEach(function() {
         navigator.mozLoop.fxAEnabled = true;
@@ -283,16 +353,18 @@ describe("loop.panel", function() {
           view.getDOMNode().querySelector(".icon-signout"));
 
         sinon.assert.calledOnce(navigator.mozLoop.logOutFromFxA);
       });
     });
 
     describe("#render", function() {
       it("should render a ToSView", function() {
+        var view = createTestPanelView();
+
         TestUtils.findRenderedComponentWithType(view, loop.panel.ToSView);
       });
     });
   });
 
   describe("loop.panel.CallUrlResult", function() {
     var fakeClient, callUrlData, view;
 
@@ -545,16 +617,44 @@ describe("loop.panel", function() {
 
         sinon.assert.calledOnce(notifications.errorL10n);
         sinon.assert.calledWithExactly(notifications.errorL10n,
                                        "unable_retrieve_url");
       });
     });
   });
 
+  describe("loop.panel.RoomList", function() {
+    var roomListStore, dispatcher;
+
+    beforeEach(function() {
+      dispatcher = new loop.Dispatcher();
+      roomListStore = new loop.store.RoomListStore({
+        dispatcher: dispatcher,
+        mozLoop: navigator.mozLoop
+      });
+    });
+
+    function createTestComponent() {
+      return TestUtils.renderIntoDocument(loop.panel.RoomList({
+        store: roomListStore,
+        dispatcher: dispatcher
+      }));
+    }
+
+    it("should dispatch a GetAllRooms action on mount", function() {
+      var dispatch = sandbox.stub(dispatcher, "dispatch");
+
+      createTestComponent();
+
+      sinon.assert.calledOnce(dispatch);
+      sinon.assert.calledWithExactly(dispatch, new sharedActions.GetAllRooms());
+    });
+  });
+
   describe('loop.panel.ToSView', function() {
 
     it("should render when the value of loop.seenToS is not set", function() {
       var view = TestUtils.renderIntoDocument(loop.panel.ToSView());
 
       TestUtils.findRenderedDOMComponentWithClass(view, "terms-service");
     });
 
--- a/browser/components/loop/test/functional/test_1_browser_call.py
+++ b/browser/components/loop/test/functional/test_1_browser_call.py
@@ -126,22 +126,22 @@ class Test1BrowserCall(MarionetteTestCas
         # expect a video container on desktop side
         video = self.wait_for_element_displayed(By.CLASS_NAME, "media")
         self.assertEqual(video.tag_name, "div", "expect a video container")
 
     def hangup_call_and_verify_feedback(self):
         self.marionette.set_context("chrome")
         button = self.marionette.find_element(By.CLASS_NAME, "btn-hangup")
 
-        # XXX For whatever reason, the click doesn't take effect unless we
-        # wait for a bit (even if we wait for the element to actually be
-        # displayed first, which we're not currently bothering with).  It's
-        # not entirely clear whether the click is being delivered in this case,
-        # or whether there's a Marionette bug here.
-        sleep(2)
+        # XXX bug 1080095 For whatever reason, the click doesn't take effect
+        # unless we wait for a bit (even if we wait for the element to
+        # actually be displayed first, which we're not currently bothering
+        # with).  It's not entirely clear whether the click is being
+        # delivered in this case, or whether there's a Marionette bug here.
+        sleep(5)
         button.click()
 
         # check that the feedback form is displayed
         feedback_form = self.wait_for_element_displayed(By.CLASS_NAME, "faces")
         self.assertEqual(feedback_form.tag_name, "div", "expect feedback form")
 
     def test_1_browser_call(self):
         self.switch_to_panel()
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -2,21 +2,16 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Test FxA logins with Loop.
  */
 
 "use strict";
 
-const {
-  gFxAOAuthTokenData,
-  gFxAOAuthProfile,
-} = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
-
 const BASE_URL = "http://mochi.test:8888/browser/browser/components/loop/test/mochitest/loop_fxa.sjs?";
 
 function* checkFxA401() {
   let err = MozLoopService.errors.get("login");
   ise(err.code, 401, "Check error code");
   ise(err.friendlyMessage, getLoopString("could_not_authenticate"),
       "Check friendlyMessage");
   ise(err.friendlyDetails, getLoopString("password_changed_question"),
@@ -206,16 +201,18 @@ add_task(function* registrationWithInval
   Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
 
   let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
   yield tokenPromise.then(body => {
     ok(false, "Promise should have rejected");
   },
   error => {
     is(error.code, 400, "Check error code");
+    checkFxAOAuthTokenData(null);
+    is(MozLoopService.userProfile, null, "Profile should be empty after invalid login");
   });
 });
 
 add_task(function* registrationWith401() {
   yield resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
@@ -227,16 +224,18 @@ add_task(function* registrationWith401()
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
   let tokenPromise = MozLoopServiceInternal.promiseFxAOAuthToken("code1", "state");
   yield tokenPromise.then(body => {
     ok(false, "Promise should have rejected");
   },
   error => {
     is(error.code, 401, "Check error code");
+    checkFxAOAuthTokenData(null);
+    is(MozLoopService.userProfile, null, "Profile should be empty after invalid login");
   });
 
   yield checkFxA401();
 });
 
 add_task(function* basicAuthorizationAndRegistration() {
   yield resetFxA();
   let params = {
@@ -316,17 +315,17 @@ add_task(function* loginWithParams401() 
   yield MozLoopService.register(mockPushHandler);
 
   let loginPromise = MozLoopService.logInToFxA();
   yield loginPromise.then(tokenData => {
     ok(false, "Promise should have rejected");
   },
   error => {
     ise(error.code, 401, "Check error code");
-    ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
+    checkFxAOAuthTokenData(null);
   });
 
   yield checkFxA401();
 });
 
 add_task(function* logoutWithIncorrectPushURL() {
   yield resetFxA();
   let pushURL = "http://www.example.com/";
@@ -382,13 +381,13 @@ add_task(function* loginWithRegistration
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
   let loginPromise = MozLoopService.logInToFxA();
   yield loginPromise.then(tokenData => {
     ok(false, "Promise should have rejected");
   },
   error => {
     ise(error.code, 401, "Check error code");
-    ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
+    checkFxAOAuthTokenData(null);
   });
 
   yield checkFxA401();
 });
--- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -4,74 +4,76 @@
 /**
  * Test the toolbar button states.
  */
 
 "use strict";
 
 registerCleanupFunction(function*() {
   MozLoopService.doNotDisturb = false;
-  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.fxAOAuthProfile = null;
   yield MozLoopServiceInternal.clearError("testing");
 });
 
 add_task(function* test_doNotDisturb() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopService.doNotDisturb = true;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
   yield MozLoopService.doNotDisturb = false;
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
 });
 
 add_task(function* test_doNotDisturb_with_login() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopService.doNotDisturb = true;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
-  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+  MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
   yield MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
   yield loadLoopPanel();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state after opening panel");
   let loopPanel = document.getElementById("loop-notification-panel");
   loopPanel.hidePopup();
   yield MozLoopService.doNotDisturb = false;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
-  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.fxAOAuthTokenData = null;
   yield MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
 });
 
 add_task(function* test_error() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopServiceInternal.setError("testing", {});
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
   yield MozLoopServiceInternal.clearError("testing");
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
 });
 
 add_task(function* test_error_with_login() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopServiceInternal.setError("testing", {});
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
-  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
   MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
   yield MozLoopServiceInternal.clearError("testing");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
-  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.fxAOAuthProfile = null;
   MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
 });
 
 add_task(function* test_active() {
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
-  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+  MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
   yield MozLoopServiceInternal.notifyStatusChanged("login");
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
   yield loadLoopPanel();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state after opening panel");
   let loopPanel = document.getElementById("loop-notification-panel");
   loopPanel.hidePopup();
-  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.fxAOAuthTokenData = null;
   MozLoopServiceInternal.notifyStatusChanged();
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
 });
 
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -115,37 +115,36 @@ function promiseOAuthParamsSetup(baseURL
   return deferred.promise;
 }
 
 function* resetFxA() {
   let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
   global.gHawkClient = null;
   global.gFxAOAuthClientPromise = null;
   global.gFxAOAuthClient = null;
-  global.gFxAOAuthTokenData = null;
-  global.gFxAOAuthProfile = null;
+  MozLoopServiceInternal.fxAOAuthProfile = null;
+  MozLoopServiceInternal.fxAOAuthTokenData = null;
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   Services.prefs.clearUserPref(fxASessionPref);
   MozLoopService.errors.clear();
   let notified = promiseObserverNotified("loop-status-changed");
   MozLoopServiceInternal.notifyStatusChanged();
   yield notified;
 }
 
-function setInternalLoopGlobal(aName, aValue) {
-  let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
-  global[aName] = aValue;
+function checkFxAOAuthTokenData(aValue) {
+  ise(MozLoopServiceInternal.fxAOAuthTokenData, aValue, "fxAOAuthTokenData should be " + aValue);
 }
 
 function checkLoggedOutState() {
   let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
   ise(global.gFxAOAuthClientPromise, null, "gFxAOAuthClientPromise should be cleared");
-  ise(global.gFxAOAuthProfile, null, "gFxAOAuthProfile should be cleared");
+  ise(MozLoopService.userProfile, null, "fxAOAuthProfile should be cleared");
   ise(global.gFxAOAuthClient, null, "gFxAOAuthClient should be cleared");
-  ise(global.gFxAOAuthTokenData, null, "gFxAOAuthTokenData should be cleared");
+  checkFxAOAuthTokenData(null);
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   ise(Services.prefs.getPrefType(fxASessionPref), Services.prefs.PREF_INVALID,
       "FxA hawk session should be cleared anyways");
 }
 
 function promiseDeletedOAuthParams(baseURL) {
   let deferred = Promise.defer();
   let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
--- a/browser/components/loop/test/shared/index.html
+++ b/browser/components/loop/test/shared/index.html
@@ -39,27 +39,29 @@
   <script src="../../content/shared/js/views.js"></script>
   <script src="../../content/shared/js/websocket.js"></script>
   <script src="../../content/shared/js/feedbackApiClient.js"></script>
   <script src="../../content/shared/js/validate.js"></script>
   <script src="../../content/shared/js/actions.js"></script>
   <script src="../../content/shared/js/dispatcher.js"></script>
   <script src="../../content/shared/js/otSdkDriver.js"></script>
   <script src="../../content/shared/js/conversationStore.js"></script>
+  <script src="../../content/shared/js/roomListStore.js"></script>
 
   <!-- Test scripts -->
   <script src="models_test.js"></script>
   <script src="mixins_test.js"></script>
   <script src="utils_test.js"></script>
   <script src="views_test.js"></script>
   <script src="websocket_test.js"></script>
   <script src="feedbackApiClient_test.js"></script>
   <script src="validate_test.js"></script>
   <script src="dispatcher_test.js"></script>
   <script src="conversationStore_test.js"></script>
   <script src="otSdkDriver_test.js"></script>
+  <script src="roomListStore_test.js"></script>
   <script>
     mocha.run(function () {
       $("#mocha").append("<p id='complete'>Complete.</p>");
     });
   </script>
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/shared/roomListStore_test.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+var expect = chai.expect;
+
+describe("loop.store.Room", function () {
+  "use strict";
+  describe("#constructor", function() {
+    it("should validate room values", function() {
+      expect(function() {
+        new loop.store.Room();
+      }).to.Throw(Error, /missing required/);
+    });
+  });
+});
+
+describe("loop.store.RoomListStore", function () {
+  "use strict";
+
+  var sharedActions = loop.shared.actions;
+  var sandbox, dispatcher;
+
+  beforeEach(function() {
+    sandbox = sinon.sandbox.create();
+    dispatcher = new loop.Dispatcher();
+  });
+
+  afterEach(function() {
+    sandbox.restore();
+  });
+
+  describe("#constructor", function() {
+    it("should throw an error if the dispatcher is missing", function() {
+      expect(function() {
+        new loop.store.RoomListStore({mozLoop: {}});
+      }).to.Throw(/dispatcher/);
+    });
+
+    it("should throw an error if mozLoop is missing", function() {
+      expect(function() {
+        new loop.store.RoomListStore({dispatcher: dispatcher});
+      }).to.Throw(/mozLoop/);
+    });
+  });
+
+  describe("#getAllRooms", function() {
+    var store, fakeMozLoop;
+    var fakeRoomList = [{
+      roomToken: "_nxD4V4FflQ",
+      roomUrl: "http://sample/_nxD4V4FflQ",
+      roomName: "First Room Name",
+      maxSize: 2,
+      currSize: 0,
+      ctime: 1405517546
+    }, {
+      roomToken: "QzBbvGmIZWU",
+      roomUrl: "http://sample/QzBbvGmIZWU",
+      roomName: "Second Room Name",
+      maxSize: 2,
+      currSize: 0,
+      ctime: 1405517418
+    }, {
+      roomToken: "3jKS_Els9IU",
+      roomUrl: "http://sample/3jKS_Els9IU",
+      roomName: "Third Room Name",
+      maxSize: 3,
+      clientMaxSize: 2,
+      currSize: 1,
+      ctime: 1405518241
+    }];
+
+    beforeEach(function() {
+      fakeMozLoop = {
+        rooms: {
+          getAll: function(cb) {
+            cb(null, fakeRoomList);
+          }
+        }
+      };
+      store = new loop.store.RoomListStore({
+        dispatcher: dispatcher,
+        mozLoop: fakeMozLoop
+      });
+    });
+
+    it("should trigger a list:changed event", function(done) {
+      store.on("change", function() {
+        done();
+      });
+
+      dispatcher.dispatch(new sharedActions.GetAllRooms());
+    });
+
+    it("should fetch the room list from the mozLoop API", function(done) {
+      store.once("change", function() {
+        expect(store.getStoreState().error).to.be.a.null;
+        expect(store.getStoreState().rooms).to.have.length.of(3);
+        done();
+      });
+
+      dispatcher.dispatch(new sharedActions.GetAllRooms());
+    });
+
+    it("should order the room list using ctime desc", function(done) {
+      store.once("change", function() {
+        var storeState = store.getStoreState();
+        expect(storeState.error).to.be.a.null;
+        expect(storeState.rooms[0].ctime).eql(1405518241);
+        expect(storeState.rooms[1].ctime).eql(1405517546);
+        expect(storeState.rooms[2].ctime).eql(1405517418);
+        done();
+      });
+
+      dispatcher.dispatch(new sharedActions.GetAllRooms());
+    });
+
+    it("should report an error", function() {
+      fakeMozLoop.rooms.getAll = function(cb) {
+        cb("fakeError");
+      };
+
+      store.once("change", function() {
+        var storeState = store.getStoreState();
+        expect(storeState.error).eql("fakeError");
+      });
+
+      dispatcher.dispatch(new sharedActions.GetAllRooms());
+    });
+  });
+});
--- a/browser/components/loop/test/xpcshell/test_loopservice_busy.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_busy.js
@@ -47,19 +47,20 @@ function test_send_busy_on_call() {
 
 add_test(test_send_busy_on_call); //FXA call accepted, Guest call rejected
 add_test(test_send_busy_on_call); //No FXA call, first Guest call accepted, second rejected
 
 function run_test()
 {
   setupFakeLoopServer();
 
-  // Setup fake login (profile) state so we get FxA requests.
-  const serviceGlobal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
-  serviceGlobal.gFxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
+  // Setup fake login state so we get FxA requests.
+  const MozLoopServiceInternal = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).MozLoopServiceInternal;
+  MozLoopServiceInternal.fxAOAuthTokenData = {token_type:"bearer",access_token:"1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",scope:"profile"};
+  MozLoopServiceInternal.fxAOAuthProfile = {email: "test@example.com", uid: "abcd1234"};
 
   // For each notification received from the PushServer, MozLoopService will first query
   // for any pending calls on the FxA hawk session and then again using the guest session.
   // A pair of response objects in the callsResponses array will be consumed for each
   // notification. The even calls object is for the FxA session, the odd the Guest session.
 
   let callsRespCount = 0;
   let callsResponses = [
@@ -97,16 +98,16 @@ function run_test()
     response.finish();
   });
 
   do_register_cleanup(function() {
     // Revert original Chat.open implementation
     Chat.open = openChatOrig;
 
     // Revert fake login state
-    serviceGlobal.gFxAOAuthProfile = null;
+    MozLoopServiceInternal.fxAOAuthTokenData = null;
 
     // clear test pref
     Services.prefs.clearUserPref("loop.seenToS");
   });
 
   run_next_test();
 }
--- a/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_initialize.js
@@ -5,18 +5,19 @@ var startTimerCalled = false;
 
 /**
  * Tests that registration doesn't happen when the expiry time is
  * not set.
  */
 add_task(function test_initialize_no_expiry() {
   startTimerCalled = false;
 
-  MozLoopService.initialize();
-
+  let initializedPromise = yield MozLoopService.initialize();
+  Assert.equal(initializedPromise, "registration not needed",
+               "Promise should be fulfilled");
   Assert.equal(startTimerCalled, false,
     "should not register when no expiry time is set");
 });
 
 /**
  * Tests that registration doesn't happen when the expiry time is
  * in the past.
  */
--- a/browser/components/loop/test/xpcshell/test_loopservice_registration.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_registration.js
@@ -35,17 +35,17 @@ add_test(function test_register_offline(
 add_test(function test_register_websocket_success_loop_server_fail() {
   mockPushHandler.registrationResult = null;
 
   MozLoopService.register(mockPushHandler).then(() => {
     do_throw("should not succeed when loop server registration fails");
   }, err => {
     // 404 is an expected failure indicated by the lack of route being set
     // up on the Loop server mock. This is added in the next test.
-    Assert.equal(err, 404, "Expected no errors in websocket registration");
+    Assert.equal(err.errno, 404, "Expected no errors in websocket registration");
 
     run_next_test();
   });
 });
 
 /**
  * Tests that we get a success response when both websocket and Loop server
  * registration are complete.
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/test_loopservice_restart.js
@@ -0,0 +1,111 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+const FAKE_FXA_TOKEN_DATA = JSON.stringify({
+  "token_type": "bearer",
+  "access_token": "1bad3e44b12f77a88fe09f016f6a37c42e40f974bc7a8b432bb0d2f0e37e1752",
+  "scope": "profile"
+});
+const FAKE_FXA_PROFILE = JSON.stringify({
+  "email": "test@example.com",
+  "uid": "999999994d9f4b08a2cbfc0999999999",
+  "avatar": null
+});
+const LOOP_FXA_TOKEN_PREF = "loop.fxa_oauth.tokendata";
+const LOOP_FXA_PROFILE_PREF = "loop.fxa_oauth.profile";
+const LOOP_URL_EXPIRY_PREF = "loop.urlsExpiryTimeSeconds";
+const LOOP_INITIAL_DELAY_PREF = "loop.initialDelay";
+
+/**
+ * This file is to test restart+reauth.
+ */
+
+add_task(function test_initialize_with_expired_urls_and_no_auth_token() {
+  // Set time to be 2 seconds in the past.
+  var nowSeconds = Date.now() / 1000;
+  Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, nowSeconds - 2);
+  Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+
+  yield MozLoopService.initialize(mockPushHandler).then((msg) => {
+    Assert.equal(msg, "registration not needed", "Initialize should not register when the " +
+                                                 "URLs are expired and there are no auth tokens");
+  }, (error) => {
+    Assert.ok(false, error, "should have resolved the promise that initialize returned");
+  });
+});
+
+add_task(function test_initialize_with_urls_and_no_auth_token() {
+  Services.prefs.setIntPref(LOOP_URL_EXPIRY_PREF, Date.now() / 1000 + 10);
+  Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+
+  loopServer.registerPathHandler("/registration", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+  });
+
+  yield MozLoopService.initialize(mockPushHandler).then((msg) => {
+    Assert.equal(msg, "initialized to guest status", "Initialize should register as a " +
+                                                     "guest when no auth tokens but expired URLs");
+  }, (error) => {
+    Assert.ok(false, error, "should have resolved the promise that initialize returned");
+  });
+});
+
+add_task(function test_initialize_with_invalid_fxa_token() {
+  Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
+  Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
+
+  // Only need to implement the FxA registration because the previous
+  // test registered as a guest.
+  loopServer.registerPathHandler("/registration", (request, response) => {
+    response.setStatusLine(null, 401, "Unauthorized");
+    response.write(JSON.stringify({
+      code: 401,
+      errno: 110,
+      error: "Unauthorized",
+      message: "Unknown credentials",
+    }));
+  });
+
+  yield MozLoopService.initialize(mockPushHandler).then(() => {
+    Assert.ok(false, "Initializing with an invalid token should reject the promise");
+  },
+  (error) => {
+    let pushHandler = Cu.import("resource:///modules/loop/MozLoopService.jsm", {}).gPushHandler;
+    Assert.equal(pushHandler.pushUrl, kEndPointUrl, "Push URL should match");
+    Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), "",
+                 "FXA pref should be cleared if token was invalid");
+    Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), "",
+                 "FXA profile pref should be cleared if token was invalid");
+  });
+});
+
+add_task(function test_initialize_with_fxa_token() {
+  Services.prefs.setCharPref(LOOP_FXA_PROFILE_PREF, FAKE_FXA_PROFILE);
+  Services.prefs.setCharPref(LOOP_FXA_TOKEN_PREF, FAKE_FXA_TOKEN_DATA);
+  loopServer.registerPathHandler("/registration", (request, response) => {
+    response.setStatusLine(null, 200, "OK");
+  });
+
+  yield MozLoopService.initialize(mockPushHandler).then(() => {
+    Assert.equal(Services.prefs.getCharPref(LOOP_FXA_TOKEN_PREF), FAKE_FXA_TOKEN_DATA,
+                 "FXA pref should still be set after initialization");
+    Assert.equal(Services.prefs.getCharPref(LOOP_FXA_PROFILE_PREF), FAKE_FXA_PROFILE,
+                 "FXA profile should still be set after initialization");
+  });
+});
+
+function run_test() {
+  setupFakeLoopServer();
+  // Note, this is just used to speed up the test.
+  Services.prefs.setIntPref(LOOP_INITIAL_DELAY_PREF, 0);
+  mockPushHandler.pushUrl = kEndPointUrl;
+
+  do_register_cleanup(function() {
+    Services.prefs.clearUserPref(LOOP_INITIAL_DELAY_PREF);
+    Services.prefs.clearUserPref(LOOP_FXA_TOKEN_PREF);
+    Services.prefs.clearUserPref(LOOP_FXA_PROFILE_PREF);
+    Services.prefs.clearUserPref(LOOP_URL_EXPIRY_PREF);
+  });
+
+  run_next_test();
+};
--- a/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
+++ b/browser/components/loop/test/xpcshell/test_loopservice_token_invalid.js
@@ -13,21 +13,18 @@ add_test(function test_registration_inva
     // Due to the way the time stamp checking code works in hawkclient, we expect a couple
     // of authorization requests before we reset the token.
     if (request.hasHeader("Authorization")) {
       authorizationAttempts++;
       response.setStatusLine(null, 401, "Unauthorized");
       response.write(JSON.stringify({
         code: 401,
         errno: 110,
-        error: {
-          error: "Unauthorized",
-          message: "Unknown credentials",
-          statusCode: 401
-        }
+        error: "Unauthorized",
+        message: "Unknown credentials",
       }));
     } else {
       // We didn't have an authorization header, so check the pref has been cleared.
       Assert.equal(Services.prefs.prefHasUserValue(LOOP_HAWK_PREF), false);
       response.setStatusLine(null, 200, "OK");
       response.setHeader("Hawk-Session-Token", fakeSessionToken2, false);
     }
     response.processAsync();
--- a/browser/components/loop/test/xpcshell/xpcshell.ini
+++ b/browser/components/loop/test/xpcshell/xpcshell.ini
@@ -10,13 +10,14 @@ skip-if = toolkit == 'gonk'
 [test_loopservice_dnd.js]
 [test_loopservice_expiry.js]
 [test_loopservice_hawk_errors.js]
 [test_loopservice_loop_prefs.js]
 [test_loopservice_initialize.js]
 [test_loopservice_locales.js]
 [test_loopservice_notification.js]
 [test_loopservice_registration.js]
+[test_loopservice_restart.js]
 [test_loopservice_token_invalid.js]
 [test_loopservice_token_save.js]
 [test_loopservice_token_send.js]
 [test_loopservice_token_validation.js]
 [test_loopservice_busy.js]
--- a/browser/components/loop/ui/fake-mozLoop.js
+++ b/browser/components/loop/ui/fake-mozLoop.js
@@ -4,17 +4,22 @@
 
 /**
  * Faking the mozLoop object which doesn't exist in regular web pages.
  * @type {Object}
  */
 navigator.mozLoop = {
   ensureRegistered: function() {},
   getLoopCharPref: function() {},
-  getLoopBoolPref: function() {},
+  getLoopBoolPref: function(pref) {
+    // Ensure UI for rooms is displayed in the showcase.
+    if (pref === "rooms.enabled") {
+      return true;
+    }
+  },
   releaseCallData: function() {},
   contacts: {
     getAll: function(callback) {
       callback(null, []);
     },
     on: function() {}
   }
 };
--- a/browser/components/loop/ui/index.html
+++ b/browser/components/loop/ui/index.html
@@ -33,17 +33,20 @@
     <script src="../content/shared/libs/backbone-1.1.2.js"></script>
     <script src="../content/shared/js/feedbackApiClient.js"></script>
     <script src="../content/shared/js/actions.js"></script>
     <script src="../content/shared/js/utils.js"></script>
     <script src="../content/shared/js/models.js"></script>
     <script src="../content/shared/js/mixins.js"></script>
     <script src="../content/shared/js/views.js"></script>
     <script src="../content/shared/js/websocket.js"></script>
+    <script src="../content/shared/js/validate.js"></script>
+    <script src="../content/shared/js/dispatcher.js"></script>
     <script src="../content/shared/js/conversationStore.js"></script>
+    <script src="../content/shared/js/roomListStore.js"></script>
     <script src="../content/js/conversationViews.js"></script>
     <script src="../content/js/client.js"></script>
     <script src="../standalone/content/js/webapp.js"></script>
     <script type="text/javascript;version=1.8" src="../content/js/contacts.js"></script>
     <script>
       if (!loop.contacts) {
         // For browsers that don't support ES6 without special flags (all but Fx
         // at the moment), we shim the contacts namespace with its most barebone
--- a/browser/components/loop/ui/ui-showcase.css
+++ b/browser/components/loop/ui/ui-showcase.css
@@ -64,19 +64,26 @@
   margin: 1.5em 0;
 }
 
 .showcase > section .example > h3 {
   font-size: 1.2em;
   font-weight: bold;
   border-bottom: 1px dashed #aaa;
   margin: 1em 0;
+  margin-top: -14em;
+  padding-top: 14em;
   text-align: left;
 }
 
+.showcase > section .example > h3 a {
+  text-decoration: none;
+  color: #555;
+}
+
 .showcase p.note {
   margin: 0;
   padding: 0;
   color: #666;
   font-style: italic;
 }
 
 .override-position * {
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -51,16 +51,22 @@
   // Feedback API client configured to send data to the stage input server,
   // which is available at https://input.allizom.org
   var stageFeedbackApiClient = new loop.FeedbackAPIClient(
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
+  var dispatcher = new loop.Dispatcher();
+  var roomListStore = new loop.store.RoomListStore({
+    dispatcher: dispatcher,
+    mozLoop: {}
+  });
+
   // Local mocks
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
@@ -88,21 +94,28 @@
   errNotifications.add({
     level: "error",
     message: "Could Not Authenticate",
     details: "Did you change your password?",
     detailsButtonLabel: "Retry",
   });
 
   var Example = React.createClass({displayName: 'Example',
+    makeId: function(prefix) {
+      return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
+    },
+
     render: function() {
       var cx = React.addons.classSet;
       return (
         React.DOM.div({className: "example"}, 
-          React.DOM.h3(null, this.props.summary), 
+          React.DOM.h3({id: this.makeId()}, 
+            this.props.summary, 
+            React.DOM.a({href: this.makeId("#")}, " ¶")
+          ), 
           React.DOM.div({className: cx({comp: true, dashed: this.props.dashed}), 
                style: this.props.style || {}}, 
             this.props.children
           )
         )
       );
     }
   });
@@ -145,36 +158,55 @@
       return (
         ShowCase(null, 
           Section({name: "PanelView"}, 
             React.DOM.p({className: "note"}, 
               React.DOM.strong(null, "Note:"), " 332px wide."
             ), 
             Example({summary: "Call URL retrieved", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications, 
-                         callUrl: "http://invalid.example.url/"})
+                         callUrl: "http://invalid.example.url/", 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
             ), 
             Example({summary: "Call URL retrieved - authenticated", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications, 
                          callUrl: "http://invalid.example.url/", 
-                         userProfile: {email: "test@example.com"}})
+                         userProfile: {email: "test@example.com"}, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
             ), 
             Example({summary: "Pending call url retrieval", dashed: "true", style: {width: "332px"}}, 
-              PanelView({client: mockClient, notifications: notifications})
+              PanelView({client: mockClient, notifications: notifications, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
             ), 
             Example({summary: "Pending call url retrieval - authenticated", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: notifications, 
-                         userProfile: {email: "test@example.com"}})
+                         userProfile: {email: "test@example.com"}, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
             ), 
             Example({summary: "Error Notification", dashed: "true", style: {width: "332px"}}, 
-              PanelView({client: mockClient, notifications: errNotifications})
+              PanelView({client: mockClient, notifications: errNotifications, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
             ), 
             Example({summary: "Error Notification - authenticated", dashed: "true", style: {width: "332px"}}, 
               PanelView({client: mockClient, notifications: errNotifications, 
-                         userProfile: {email: "test@example.com"}})
+                         userProfile: {email: "test@example.com"}, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore})
+            ), 
+            Example({summary: "Room list tab", dashed: "true", style: {width: "332px"}}, 
+              PanelView({client: mockClient, notifications: notifications, 
+                         userProfile: {email: "test@example.com"}, 
+                         dispatcher: dispatcher, 
+                         roomListStore: roomListStore, 
+                         selectedTab: "rooms"})
             )
           ), 
 
           Section({name: "IncomingCallView"}, 
             Example({summary: "Default / incoming video call", dashed: "true", style: {width: "260px", height: "254px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 IncomingCallView({model: mockConversationModel, 
                                   video: true})
@@ -242,41 +274,45 @@
                                      publishStream: noop})
               )
             )
           ), 
 
           Section({name: "PendingConversationView"}, 
             Example({summary: "Pending conversation view (connecting)", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
-                PendingConversationView({websocket: mockWebSocket})
+                PendingConversationView({websocket: mockWebSocket, 
+                                         dispatcher: dispatcher})
               )
             ), 
             Example({summary: "Pending conversation view (ringing)", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
-                PendingConversationView({websocket: mockWebSocket, callState: "ringing"})
+                PendingConversationView({websocket: mockWebSocket, 
+                                         dispatcher: dispatcher, 
+                                         callState: "ringing"})
               )
             )
           ), 
 
           Section({name: "PendingConversationView (Desktop)"}, 
             Example({summary: "Connecting", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
                 DesktopPendingConversationView({callState: "gather", 
-                                                contact: mockContact})
+                                                contact: mockContact, 
+                                                dispatcher: dispatcher})
               )
             )
           ), 
 
           Section({name: "CallFailedView"}, 
             Example({summary: "Call Failed", dashed: "true", 
                      style: {width: "260px", height: "265px"}}, 
               React.DOM.div({className: "fx-embedded"}, 
-                CallFailedView(null)
+                CallFailedView({dispatcher: dispatcher})
               )
             )
           ), 
 
           Section({name: "StartConversationView"}, 
             Example({summary: "Start conversation view", dashed: "true"}, 
               React.DOM.div({className: "standalone"}, 
                 StartConversationView({conversation: mockConversationModel, 
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -51,16 +51,22 @@
   // Feedback API client configured to send data to the stage input server,
   // which is available at https://input.allizom.org
   var stageFeedbackApiClient = new loop.FeedbackAPIClient(
     "https://input.allizom.org/api/v1/feedback", {
       product: "Loop"
     }
   );
 
+  var dispatcher = new loop.Dispatcher();
+  var roomListStore = new loop.store.RoomListStore({
+    dispatcher: dispatcher,
+    mozLoop: {}
+  });
+
   // Local mocks
 
   var mockContact = {
     name: ["Mr Smith"],
     email: [{
       value: "smith@invalid.com"
     }]
   };
@@ -88,21 +94,28 @@
   errNotifications.add({
     level: "error",
     message: "Could Not Authenticate",
     details: "Did you change your password?",
     detailsButtonLabel: "Retry",
   });
 
   var Example = React.createClass({
+    makeId: function(prefix) {
+      return (prefix || "") + this.props.summary.toLowerCase().replace(/\s/g, "-");
+    },
+
     render: function() {
       var cx = React.addons.classSet;
       return (
         <div className="example">
-          <h3>{this.props.summary}</h3>
+          <h3 id={this.makeId()}>
+            {this.props.summary}
+            <a href={this.makeId("#")}>&nbsp;¶</a>
+          </h3>
           <div className={cx({comp: true, dashed: this.props.dashed})}
                style={this.props.style || {}}>
             {this.props.children}
           </div>
         </div>
       );
     }
   });
@@ -145,36 +158,55 @@
       return (
         <ShowCase>
           <Section name="PanelView">
             <p className="note">
               <strong>Note:</strong> 332px wide.
             </p>
             <Example summary="Call URL retrieved" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={notifications}
-                         callUrl="http://invalid.example.url/" />
+                         callUrl="http://invalid.example.url/"
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
             </Example>
             <Example summary="Call URL retrieved - authenticated" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={notifications}
                          callUrl="http://invalid.example.url/"
-                         userProfile={{email: "test@example.com"}} />
+                         userProfile={{email: "test@example.com"}}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
             </Example>
             <Example summary="Pending call url retrieval" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={notifications} />
+              <PanelView client={mockClient} notifications={notifications}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
             </Example>
             <Example summary="Pending call url retrieval - authenticated" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={notifications}
-                         userProfile={{email: "test@example.com"}} />
+                         userProfile={{email: "test@example.com"}}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
             </Example>
             <Example summary="Error Notification" dashed="true" style={{width: "332px"}}>
-              <PanelView client={mockClient} notifications={errNotifications}/>
+              <PanelView client={mockClient} notifications={errNotifications}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
             </Example>
             <Example summary="Error Notification - authenticated" dashed="true" style={{width: "332px"}}>
               <PanelView client={mockClient} notifications={errNotifications}
-                         userProfile={{email: "test@example.com"}} />
+                         userProfile={{email: "test@example.com"}}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore} />
+            </Example>
+            <Example summary="Room list tab" dashed="true" style={{width: "332px"}}>
+              <PanelView client={mockClient} notifications={notifications}
+                         userProfile={{email: "test@example.com"}}
+                         dispatcher={dispatcher}
+                         roomListStore={roomListStore}
+                         selectedTab="rooms" />
             </Example>
           </Section>
 
           <Section name="IncomingCallView">
             <Example summary="Default / incoming video call" dashed="true" style={{width: "260px", height: "254px"}}>
               <div className="fx-embedded">
                 <IncomingCallView model={mockConversationModel}
                                   video={true} />
@@ -242,41 +274,45 @@
                                      publishStream={noop} />
               </Example>
             </div>
           </Section>
 
           <Section name="PendingConversationView">
             <Example summary="Pending conversation view (connecting)" dashed="true">
               <div className="standalone">
-                <PendingConversationView websocket={mockWebSocket}/>
+                <PendingConversationView websocket={mockWebSocket}
+                                         dispatcher={dispatcher} />
               </div>
             </Example>
             <Example summary="Pending conversation view (ringing)" dashed="true">
               <div className="standalone">
-                <PendingConversationView websocket={mockWebSocket} callState="ringing"/>
+                <PendingConversationView websocket={mockWebSocket}
+                                         dispatcher={dispatcher}
+                                         callState="ringing"/>
               </div>
             </Example>
           </Section>
 
           <Section name="PendingConversationView (Desktop)">
             <Example summary="Connecting" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
                 <DesktopPendingConversationView callState={"gather"}
-                                                contact={mockContact} />
+                                                contact={mockContact}
+                                                dispatcher={dispatcher} />
               </div>
             </Example>
           </Section>
 
           <Section name="CallFailedView">
             <Example summary="Call Failed" dashed="true"
                      style={{width: "260px", height: "265px"}}>
               <div className="fx-embedded">
-                <CallFailedView />
+                <CallFailedView dispatcher={dispatcher} />
               </div>
             </Example>
           </Section>
 
           <Section name="StartConversationView">
             <Example summary="Start conversation view" dashed="true">
               <div className="standalone">
                 <StartConversationView conversation={mockConversationModel}
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -738,17 +738,20 @@ BrowserGlue.prototype = {
       SignInToWebsiteUX.uninit();
     }
 #endif
     webrtcUI.uninit();
     FormValidationHandler.uninit();
 
     // XXX: Temporary hack to allow Loop FxA login after a restart to work.
     // Remove this once bug 1071247 is deployed.
-    Services.prefs.clearUserPref("loop.hawk-session-token.fxa");
+    if (Services.prefs.getPrefType("loop.autologin-after-restart") != Ci.nsIPrefBranch.PREF_BOOL ||
+        !Services.prefs.getBoolPref("loop.autologin-after-restart")) {
+      Services.prefs.clearUserPref("loop.hawk-session-token.fxa");
+    }
   },
 
   // All initial windows have opened.
   _onWindowsRestored: function BG__onWindowsRestored() {
     // Show update notification, if needed.
     if (Services.prefs.prefHasUserValue("app.update.postupdate"))
       this._showUpdateNotification();
 
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -401,16 +401,20 @@ DevTools.prototype = {
       });
     }
     else {
       // No toolbox for target, create one
       toolbox = new devtools.Toolbox(target, toolId, hostType, hostOptions);
 
       this._toolboxes.set(target, toolbox);
 
+      toolbox.once("destroy", () => {
+        this.emit("toolbox-destroy", target);
+      });
+
       toolbox.once("destroyed", () => {
         this._toolboxes.delete(target);
         this.emit("toolbox-destroyed", target);
       });
 
       // If toolId was passed in, it will already be selected before the
       // open promise resolves.
       toolbox.open().then(() => {
@@ -424,28 +428,28 @@ DevTools.prototype = {
 
   /**
    * Return the toolbox for a given target.
    *
    * @param  {object} target
    *         Target value e.g. the target that owns this toolbox
    *
    * @return {Toolbox} toolbox
-   *         The toobox that is debugging the given target
+   *         The toolbox that is debugging the given target
    */
   getToolbox: function DT_getToolbox(target) {
     return this._toolboxes.get(target);
   },
 
   /**
    * Close the toolbox for a given target
    *
    * @return promise
    *         This promise will resolve to false if no toolbox was found
-   *         associated to the target. true, if the toolbox was successfuly
+   *         associated to the target. true, if the toolbox was successfully
    *         closed.
    */
   closeToolbox: function DT_closeToolbox(target) {
     let toolbox = this._toolboxes.get(target);
     if (toolbox == null) {
       return promise.resolve(false);
     }
     return toolbox.destroy().then(() => true);
@@ -601,21 +605,21 @@ let gDevToolsBrowser = {
 
   /**
    * This function is for the benefit of Tools:{toolId} commands,
    * triggered from the WebDeveloper menu and keyboard shortcuts.
    *
    * selectToolCommand's behavior:
    * - if the toolbox is closed,
    *   we open the toolbox and select the tool
-   * - if the toolbox is open, and the targetted tool is not selected,
+   * - if the toolbox is open, and the targeted tool is not selected,
    *   we select it
-   * - if the toolbox is open, and the targetted tool is selected,
+   * - if the toolbox is open, and the targeted tool is selected,
    *   and the host is NOT a window, we close the toolbox
-   * - if the toolbox is open, and the targetted tool is selected,
+   * - if the toolbox is open, and the targeted tool is selected,
    *   and the host is a window, we raise the toolbox window
    */
   selectToolCommand: function(gBrowser, toolId) {
     let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
     let toolbox = gDevTools.getToolbox(target);
     let toolDefinition = gDevTools.getToolDefinition(toolId);
 
     if (toolbox &&
--- a/browser/devtools/framework/test/browser.ini
+++ b/browser/devtools/framework/test/browser.ini
@@ -3,16 +3,17 @@ subsuite = devtools
 support-files =
   browser_toolbox_options_disable_js.html
   browser_toolbox_options_disable_js_iframe.html
   browser_toolbox_options_disable_cache.sjs
   head.js
   doc_theme.css
 
 [browser_devtools_api.js]
+[browser_devtools_api_destroy.js]
 skip-if = e10s # Bug 1070837 - devtools/framework/toolbox.js |doc| getter not e10s friendly
 [browser_dynamic_tool_enabling.js]
 [browser_keybindings.js]
 [browser_new_activation_workflow.js]
 [browser_target_events.js]
 [browser_target_remote.js]
 [browser_two_tabs.js]
 [browser_toolbox_dynamic_registration.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/framework/test/browser_devtools_api_destroy.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// Tests devtools API
+
+const Cu = Components.utils;
+
+function test() {
+  addTab("about:blank").then(runTests);
+}
+
+function runTests(aTab) {
+  let toolDefinition = {
+    id: "testTool",
+    visibilityswitch: "devtools.testTool.enabled",
+    isTargetSupported: function() true,
+    url: "about:blank",
+    label: "someLabel",
+    build: function(iframeWindow, toolbox) {
+      let deferred = promise.defer();
+      executeSoon(() => {
+        deferred.resolve({
+          target: toolbox.target,
+          toolbox: toolbox,
+          isReady: true,
+          destroy: function(){},
+        });
+      });
+      return deferred.promise;
+    },
+  };
+
+  gDevTools.registerTool(toolDefinition);
+
+  let collectedEvents = [];
+
+  let target = TargetFactory.forTab(aTab);
+  gDevTools.showToolbox(target, toolDefinition.id).then(function(toolbox) {
+    let panel = toolbox.getPanel(toolDefinition.id);
+    ok(panel, "Tool open");
+
+    gDevTools.once("toolbox-destroy", (event, toolbox, iframe) => {
+      collectedEvents.push(event);
+    });
+
+    gDevTools.once(toolDefinition.id + "-destroy", (event, toolbox, iframe) => {
+      collectedEvents.push("gDevTools-" + event);
+    });
+
+    toolbox.once("destroy", (event) => {
+      collectedEvents.push(event);
+    });
+
+    toolbox.once(toolDefinition.id + "-destroy", (event) => {
+      collectedEvents.push("toolbox-" + event);
+    });
+
+    toolbox.destroy().then(function() {
+      is(collectedEvents.join(":"),
+        "toolbox-destroy:destroy:gDevTools-testTool-destroy:toolbox-testTool-destroy",
+        "Found the right amount of collected events.");
+
+      gDevTools.unregisterTool(toolDefinition.id);
+      gBrowser.removeCurrentTab();
+
+      executeSoon(function() {
+        finish();
+      });
+    });
+  });
+}
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -1511,16 +1511,18 @@ Toolbox.prototype = {
    */
   destroy: function() {
     // If several things call destroy then we give them all the same
     // destruction promise so we're sure to destroy only once
     if (this._destroyer) {
       return this._destroyer;
     }
 
+    this.emit("destroy");
+
     this._target.off("navigate", this._refreshHostTitle);
     this._target.off("frame-update", this._updateFrames);
     this.off("select", this._refreshHostTitle);
     this.off("host-changed", this._refreshHostTitle);
 
     gDevTools.off("tool-registered", this._toolRegistered);
     gDevTools.off("tool-unregistered", this._toolUnregistered);
 
@@ -1532,16 +1534,19 @@ Toolbox.prototype = {
       this.webconsolePanel.removeEventListener("resize",
         this._saveSplitConsoleHeight);
     }
     this.closeButton.removeEventListener("command", this.destroy, true);
 
     let outstanding = [];
     for (let [id, panel] of this._toolPanels) {
       try {
+        gDevTools.emit(id + "-destroy", this, panel);
+        this.emit(id + "-destroy", panel);
+
         outstanding.push(panel.destroy());
       } catch (e) {
         // We don't want to stop here if any panel fail to close.
         console.error("Panel " + id + ":", e);
       }
     }
 
     // Now that we are closing the toolbox we can re-enable JavaScript for the
--- a/browser/devtools/inspector/test/browser_inspector_menu.js
+++ b/browser/devtools/inspector/test/browser_inspector_menu.js
@@ -180,29 +180,32 @@ let test = asyncTest(function* () {
     info("Testing that 'Paste Outer HTML' menu item works.");
     clipboard.set("this was pasted");
 
     let node = getNode("h1");
     yield selectNode(node, inspector);
 
     contextMenuClick(getContainerForRawNode(inspector.markup, node).tagLine);
 
+    let onNodeReselected = inspector.markup.once("reselectedonremoved");
     let menu = inspector.panelDoc.getElementById("node-menu-pasteouterhtml");
     dispatchCommandEvent(menu);
 
     info("Waiting for inspector selection to update");
-    yield inspector.selection.once("new-node");
+    yield onNodeReselected;
 
     ok(content.document.body.outerHTML.contains(clipboard.get()),
        "Clipboard content was pasted into the node's outer HTML.");
     ok(!getNode("h1", { expectNoMatch: true }), "The original node was removed.");
   }
 
   function* testDeleteNode() {
     info("Testing 'Delete Node' menu item for normal elements.");
+
+    yield selectNode("p", inspector);
     let deleteNode = inspector.panelDoc.getElementById("node-menu-delete");
     ok(deleteNode, "the popup menu has a delete menu item");
 
     let updated = inspector.once("inspector-updated");
 
     info("Triggering 'Delete Node' and waiting for inspector to update");
     dispatchCommandEvent(deleteNode);
     yield updated;
--- a/browser/devtools/markupview/markup-view.js
+++ b/browser/devtools/markupview/markup-view.js
@@ -615,18 +615,16 @@ MarkupView.prototype = {
     return container;
   },
 
   /**
    * Mutation observer used for included nodes.
    */
   _mutationObserver: function(aMutations) {
     let requiresLayoutChange = false;
-    let reselectParent;
-    let reselectChildIndex;
 
     for (let mutation of aMutations) {
       let type = mutation.type;
       let target = mutation.target;
 
       if (mutation.type === "documentUnload") {
         // Treat this as a childList change of the child (maybe the protocol
         // should do this).
@@ -646,61 +644,32 @@ MarkupView.prototype = {
       if (type === "attributes" || type === "characterData") {
         container.update();
 
         // Auto refresh style properties on selected node when they change.
         if (type === "attributes" && container.selected) {
           requiresLayoutChange = true;
         }
       } else if (type === "childList") {
-        let isFromOuterHTML = mutation.removed.some((n) => {
-          return n === this._outerHTMLNode;
-        });
-
-        // Keep track of which node should be reselected after mutations.
-        if (isFromOuterHTML) {
-          reselectParent = target;
-          reselectChildIndex = this._outerHTMLChildIndex;
-
-          delete this._outerHTMLNode;
-          delete this._outerHTMLChildIndex;
-        }
-
         container.childrenDirty = true;
         // Update the children to take care of changes in the markup view DOM.
-        this._updateChildren(container, {flash: !isFromOuterHTML});
+        this._updateChildren(container, {flash: true});
       }
     }
 
     if (requiresLayoutChange) {
       this._inspector.immediateLayoutChange();
     }
     this._waitForChildren().then((nodes) => {
       this._flashMutatedNodes(aMutations);
       this._inspector.emit("markupmutation", aMutations);
 
       // Since the htmlEditor is absolutely positioned, a mutation may change
       // the location in which it should be shown.
       this.htmlEditor.refresh();
-
-      // If a node has had its outerHTML set, the parent node will be selected.
-      // Reselect the original node immediately.
-      if (this._inspector.selection.nodeFront === reselectParent) {
-        this.walker.children(reselectParent).then((o) => {
-          let node = o.nodes[reselectChildIndex];
-          let container = this.getContainer(node);
-          if (node && container) {
-            this.markNodeAsSelected(node, "outerhtml");
-            if (container.hasChildren) {
-              this.expandNode(node);
-            }
-          }
-        });
-
-      }
     });
   },
 
   /**
    * React to display-change events from the walker
    * @param {Array} nodes An array of nodeFronts
    */
   _onDisplayChange: function(nodes) {
@@ -842,66 +811,102 @@ MarkupView.prototype = {
         longstr.release().then(null, console.error);
         def.resolve(outerHTML);
       });
     });
     return def.promise;
   },
 
   /**
-   * Retrieve the index of a child within its parent's children list.
-   * @param aNode The NodeFront to find the index of.
-   * @returns A promise that will be resolved with the integer index.
-   *          If the child cannot be found, returns -1
+   * Listen to mutations, expect a given node to be removed and try and select
+   * the node that sits at the same place instead.
+   * This is useful when changing the outerHTML or the tag name so that the
+   * newly inserted node gets selected instead of the one that just got removed.
    */
-  getNodeChildIndex: function(aNode) {
-    let def = promise.defer();
-    let parentNode = aNode.parentNode();
+  reselectOnRemoved: function(removedNode, reason) {
+    // Only allow one removed node reselection at a time, so that when there are
+    // more than 1 request in parallel, the last one wins.
+    this.cancelReselectOnRemoved();
+
+    // Get the removedNode index in its parent node to reselect the right node.
+    let isHTMLTag = removedNode.tagName.toLowerCase() === "html";
+    let oldContainer = this.getContainer(removedNode);
+    let parentContainer = this.getContainer(removedNode.parentNode());
+    let childIndex = parentContainer.getChildContainers().indexOf(oldContainer);
+
+    let onMutations = this._removedNodeObserver = (e, mutations) => {
+      let isNodeRemovalMutation = false;
+      for (let mutation of mutations) {
+        let containsRemovedNode = mutation.removed &&
+                                  mutation.removed.some(n => n === removedNode);
+        if (mutation.type === "childList" && (containsRemovedNode || isHTMLTag)) {
+          isNodeRemovalMutation = true;
+          break;
+        }
+      }
+      if (!isNodeRemovalMutation) {
+        return;
+      }
 
-    // Node may have been removed from the DOM, instead of throwing an error,
-    // return -1 indicating that it isn't inside of its parent children list.
-    if (!parentNode) {
-      def.resolve(-1);
-    } else {
-      this.walker.children(parentNode).then(children => {
-        def.resolve(children.nodes.indexOf(aNode));
-      });
+      this._inspector.off("markupmutation", onMutations);
+      this._removedNodeObserver = null;
+
+      // Don't select the new node if the user has already changed the current
+      // selection.
+      if (this._inspector.selection.nodeFront === parentContainer.node ||
+          (this._inspector.selection.nodeFront === removedNode && isHTMLTag)) {
+        let childContainers = parentContainer.getChildContainers();
+        if (childContainers && childContainers[childIndex]) {
+          this.markNodeAsSelected(childContainers[childIndex].node, reason);
+          if (childContainers[childIndex].hasChildren) {
+            this.expandNode(childContainers[childIndex].node);
+          }
+          this.emit("reselectedonremoved");
+        }
+      }
+    };
+
+    // Start listening for mutations until we find a childList change that has
+    // removedNode removed.
+    this._inspector.on("markupmutation", onMutations);
+  },
+
+  /**
+   * Make sure to stop listening for node removal markupmutations and not
+   * reselect the corresponding node when that happens.
+   * Useful when the outerHTML/tagname edition failed.
+   */
+  cancelReselectOnRemoved: function() {
+    if (this._removedNodeObserver) {
+      this._inspector.off("markupmutation", this._removedNodeObserver);
+      this._removedNodeObserver = null;
+      this.emit("canceledreselectonremoved");
     }
-
-    return def.promise;
   },
 
   /**
    * Replace the outerHTML of any node displayed in the inspector with
    * some other HTML code
    * @param aNode node which outerHTML will be replaced.
    * @param newValue The new outerHTML to set on the node.
    * @param oldValue The old outerHTML that will be used if the user undos the update.
    * @returns A promise that will resolve when the outer HTML has been updated.
    */
   updateNodeOuterHTML: function(aNode, newValue, oldValue) {
     let container = this._containers.get(aNode);
     if (!container) {
       return promise.reject();
     }
 
-    let def = promise.defer();
-
-    this.getNodeChildIndex(aNode).then((i) => {
-      this._outerHTMLChildIndex = i;
-      this._outerHTMLNode = aNode;
-
-      container.undo.do(() => {
-        this.walker.setOuterHTML(aNode, newValue).then(def.resolve, def.reject);
-      }, () => {
-        this.walker.setOuterHTML(aNode, oldValue).then(def.resolve, def.reject);
-      });
+    // Changing the outerHTML removes the node which outerHTML was changed.
+    // Listen to this removal to reselect the right node afterwards.
+    this.reselectOnRemoved(aNode, "outerhtml");
+    return this.walker.setOuterHTML(aNode, newValue).then(null, () => {
+      this.cancelReselectOnRemoved();
     });
-
-    return def.promise;
   },
 
   /**
    * Open an editor in the UI to allow editing of a node's outerHTML.
    * @param aNode The NodeFront to edit.
    */
   beginEditingOuterHTML: function(aNode) {
     this.getNodeOuterHTML(aNode).then((oldValue)=> {
@@ -1422,16 +1427,28 @@ MarkupContainer.prototype = {
     if (aValue) {
       this.expander.style.visibility = "visible";
     } else {
       this.expander.style.visibility = "hidden";
     }
   },
 
   /**
+   * If the node has children, return the list of containers for all these
+   * children.
+   */
+  getChildContainers: function() {
+    if (!this.hasChildren) {
+      return null;
+    }
+
+    return [...this.children.children].map(node => node.container);
+  },
+
+  /**
    * True if the node has been visually expanded in the tree.
    */
   get expanded() {
     return !this.elt.classList.contains("collapsed");
   },
 
   set expanded(aValue) {
     if (!this.expander) {
@@ -1823,17 +1840,25 @@ function RootContainer(aMarkupView, aNod
   this.node = aNode;
   this.toString = () => "[root container]";
 }
 
 RootContainer.prototype = {
   hasChildren: true,
   expanded: true,
   update: function() {},
-  destroy: function() {}
+  destroy: function() {},
+
+  /**
+   * If the node has children, return the list of containers for all these
+   * children.
+   */
+  getChildContainers: function() {
+    return [...this.children.children].map(node => node.container);
+  }
 };
 
 /**
  * Creates an editor for non-editable nodes.
  */
 function GenericEditor(aContainer, aNode) {
   this.container = aContainer;
   this.markup = this.container.markup;
@@ -1964,23 +1989,19 @@ function ElementEditor(aContainer, aNode
   this.closeTag = null;
   this.attrList = null;
   this.newAttr = null;
   this.closeElt = null;
 
   // Create the main editor
   this.template("element", this);
 
-  if (aNode.isLocal_toBeDeprecated()) {
-    this.rawNode = aNode.rawNode();
-  }
-
   // Make the tag name editable (unless this is a remote node or
   // a document element)
-  if (this.rawNode && !aNode.isDocumentElement) {
+  if (!aNode.isDocumentElement) {
     this.tag.setAttribute("tabindex", "0");
     editableField({
       element: this.tag,
       trigger: "dblclick",
       stopOnReturn: true,
       done: this.onTagEdit.bind(this),
     });
   }
@@ -2202,67 +2223,29 @@ ElementEditor.prototype = {
     } else {
       aUndoMods.removeAttribute(aName);
     }
   },
 
   /**
    * Called when the tag name editor has is done editing.
    */
-  onTagEdit: function(aVal, aCommit) {
-    if (!aCommit || aVal == this.rawNode.tagName) {
-      return;
-    }
-
-    // Create a new element with the same attributes as the
-    // current element and prepare to replace the current node
-    // with it.
-    try {
-      var newElt = nodeDocument(this.rawNode).createElement(aVal);
-    } catch(x) {
-      // Failed to create a new element with that tag name, ignore
-      // the change.
+  onTagEdit: function(newTagName, isCommit) {
+    if (!isCommit || newTagName == this.node.tagName ||
+        !("editTagName" in this.markup.walker)) {
       return;
     }
 
-    let attrs = this.rawNode.attributes;
-
-    for (let i = 0 ; i < attrs.length; i++) {
-      newElt.setAttribute(attrs[i].name, attrs[i].value);
-    }
-    let newFront = this.markup.walker.frontForRawNode(newElt);
-    let newContainer = this.markup.importNode(newFront);
-
-    // Retain the two nodes we care about here so we can undo.
-    let walker = this.markup.walker;
-    promise.all([
-      walker.retainNode(newFront), walker.retainNode(this.node)
-    ]).then(() => {
-      function swapNodes(aOld, aNew) {
-        aOld.parentNode.insertBefore(aNew, aOld);
-        while (aOld.firstChild) {
-          aNew.appendChild(aOld.firstChild);
-        }
-        aOld.parentNode.removeChild(aOld);
-      }
-
-      this.container.undo.do(() => {
-        swapNodes(this.rawNode, newElt);
-        this.markup.setNodeExpanded(newFront, this.container.expanded);
-        if (this.container.selected) {
-          this.markup.navigate(newContainer);
-        }
-      }, () => {
-        swapNodes(newElt, this.rawNode);
-        this.markup.setNodeExpanded(this.node, newContainer.expanded);
-        if (newContainer.selected) {
-          this.markup.navigate(this.container);
-        }
-      });
-    }).then(null, console.error);
+    // Changing the tagName removes the node. Make sure the replacing node gets
+    // selected afterwards.
+    this.markup.reselectOnRemoved(this.node, "edittagname");
+    this.markup.walker.editTagName(this.node, newTagName).then(null, () => {
+      // Failed to edit the tag name, cancel the reselection.
+      this.markup.cancelReselectOnRemoved();
+    });
   },
 
   destroy: function() {}
 };
 
 function nodeDocument(node) {
   return node.ownerDocument ||
     (node.nodeType == Ci.nsIDOMNode.DOCUMENT_NODE ? node : null);
--- a/browser/devtools/markupview/test/browser.ini
+++ b/browser/devtools/markupview/test/browser.ini
@@ -75,19 +75,19 @@ skip-if = e10s # Bug 1040751 - CodeMirro
 [browser_markupview_node_not_displayed_02.js]
 [browser_markupview_pagesize_01.js]
 [browser_markupview_pagesize_02.js]
 skip-if = e10s # Bug 1036409 - The last selected node isn't reselected
 [browser_markupview_search_01.js]
 [browser_markupview_tag_edit_01.js]
 [browser_markupview_tag_edit_02.js]
 [browser_markupview_tag_edit_03.js]
-skip-if = e10s # Bug 1036421 - Tag editing isn't remote-safe
 [browser_markupview_tag_edit_04.js]
 [browser_markupview_tag_edit_05.js]
 [browser_markupview_tag_edit_06.js]
 [browser_markupview_tag_edit_07.js]
 [browser_markupview_tag_edit_08.js]
 [browser_markupview_tag_edit_09.js]
+[browser_markupview_tag_edit_10.js]
 [browser_markupview_textcontent_edit_01.js]
 [browser_markupview_toggle_01.js]
 [browser_markupview_toggle_02.js]
 [browser_markupview_toggle_03.js]
--- a/browser/devtools/markupview/test/browser_markupview_html_edit_03.js
+++ b/browser/devtools/markupview/test/browser_markupview_html_edit_03.js
@@ -102,56 +102,55 @@ function testF2Commits(inspector) {
 }
 
 function* testBody(inspector) {
   let body = getNode("body");
   let bodyHTML = '<body id="updated"><p></p></body>';
   let bodyFront = yield getNodeFront("body", inspector);
   let doc = content.document;
 
-  let mutated = inspector.once("markupmutation");
-  inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
-
-  let mutations = yield mutated;
+  let onReselected = inspector.markup.once("reselectedonremoved");
+  yield inspector.markup.updateNodeOuterHTML(bodyFront, bodyHTML, body.outerHTML);
+  yield onReselected;
 
   is(getNode("body").outerHTML, bodyHTML, "<body> HTML has been updated");
   is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
 
   yield inspector.once("inspector-updated");
 }
 
 function* testHead(inspector) {
   let head = getNode("head");
+  yield selectNode("head", inspector);
+
   let headHTML = '<head id="updated"><title>New Title</title><script>window.foo="bar";</script></head>';
   let headFront = yield getNodeFront("head", inspector);
   let doc = content.document;
 
-  let mutated = inspector.once("markupmutation");
-  inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
-
-  let mutations = yield mutated;
+  let onReselected = inspector.markup.once("reselectedonremoved");
+  yield inspector.markup.updateNodeOuterHTML(headFront, headHTML, head.outerHTML);
+  yield onReselected;
 
   is(doc.title, "New Title", "New title has been added");
   is(doc.defaultView.foo, undefined, "Script has not been executed");
   is(doc.querySelector("head").outerHTML, headHTML, "<head> HTML has been updated");
   is(doc.querySelectorAll("body").length, 1, "no extra <body>s have been added");
 
   yield inspector.once("inspector-updated");
 }
 
 function* testDocumentElement(inspector) {
   let doc = content.document;
   let docElement = doc.documentElement;
   let docElementHTML = '<html id="updated" foo="bar"><head><title>Updated from document element</title><script>window.foo="bar";</script></head><body><p>Hello</p></body></html>';
   let docElementFront = yield inspector.markup.walker.documentElement();
 
-  let mutated = inspector.once("markupmutation");
-  inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
-
-  let mutations = yield mutated;
+  let onReselected = inspector.markup.once("reselectedonremoved");
+  yield inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
+  yield onReselected;
 
   is(doc.title, "Updated from document element", "New title has been added");
   is(doc.defaultView.foo, undefined, "Script has not been executed");
   is(doc.documentElement.id, "updated", "<html> ID has been updated");
   is(doc.documentElement.className, "", "<html> class has been updated");
   is(doc.documentElement.getAttribute("foo"), "bar", "<html> attribute has been updated");
   is(doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
   is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
@@ -160,20 +159,19 @@ function* testDocumentElement(inspector)
 }
 
 function* testDocumentElement2(inspector) {
   let doc = content.document;
   let docElement = doc.documentElement;
   let docElementHTML = '<html class="updated" id="somethingelse"><head><title>Updated again from document element</title><script>window.foo="bar";</script></head><body><p>Hello again</p></body></html>';
   let docElementFront = yield inspector.markup.walker.documentElement();
 
-  let mutated = inspector.once("markupmutation");
+  let onReselected = inspector.markup.once("reselectedonremoved");
   inspector.markup.updateNodeOuterHTML(docElementFront, docElementHTML, docElement.outerHTML);
-
-  let mutations = yield mutated;
+  yield onReselected;
 
   is(doc.title, "Updated again from document element", "New title has been added");
   is(doc.defaultView.foo, undefined, "Script has not been executed");
   is(doc.documentElement.id, "somethingelse", "<html> ID has been updated");
   is(doc.documentElement.className, "updated", "<html> class has been updated");
   is(doc.documentElement.getAttribute("foo"), null, "<html> attribute has been removed");
   is(doc.documentElement.outerHTML, docElementHTML, "<html> HTML has been updated");
   is(doc.querySelectorAll("head").length, 1, "no extra <head>s have been added");
--- a/browser/devtools/markupview/test/browser_markupview_tag_edit_03.js
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_03.js
@@ -1,17 +1,17 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Tests that a node's tagname can be edited in the markup-view
 
-const TEST_URL = "data:text/html,<div id='retag-me'><div id='retag-me-2'></div></div>";
+const TEST_URL = "data:text/html;charset=utf-8,<div id='retag-me'><div id='retag-me-2'></div></div>";
 
 let test = asyncTest(function*() {
   let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
 
   yield inspector.markup.expandAll();
 
   info("Selecting the test node");
   let node = content.document.querySelector("#retag-me");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/markupview/test/browser_markupview_tag_edit_10.js
@@ -0,0 +1,33 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests that invalid tagname updates are handled correctly
+
+const TEST_URL = "data:text/html;charset=utf-8,<div></div>";
+
+let test = asyncTest(function*() {
+  let {toolbox, inspector} = yield addTab(TEST_URL).then(openInspector);
+  yield inspector.markup.expandAll();
+  yield selectNode("div", inspector);
+
+  info("Updating the DIV tagname to an invalid value");
+  let container = yield getContainerForSelector("div", inspector);
+  let onCancelReselect = inspector.markup.once("canceledreselectonremoved");
+  let tagEditor = container.editor.tag;
+  setEditableFieldValue(tagEditor, "<<<", inspector);
+  yield onCancelReselect;
+  ok(true, "The markup-view emitted the canceledreselectonremoved event");
+  is(inspector.selection.nodeFront, container.node, "The test DIV is still selected");
+
+  info("Updating the DIV tagname to a valid value this time");
+  let onReselect = inspector.markup.once("reselectedonremoved");
+  setEditableFieldValue(tagEditor, "span", inspector);
+  yield onReselect;
+  ok(true, "The markup-view emitted the reselectedonremoved event");
+
+  let spanFront = yield getNodeFront("span", inspector);
+  is(inspector.selection.nodeFront, spanFront, "The seelected node is now the SPAN");
+});
--- a/browser/devtools/markupview/test/helper_outerhtml_test_runner.js
+++ b/browser/devtools/markupview/test/helper_outerhtml_test_runner.js
@@ -39,35 +39,21 @@ function runEditOuterHTMLTests(tests, in
  */
 function* runEditOuterHTMLTest(test, inspector) {
   info("Running an edit outerHTML test on '" + test.selector + "'");
   yield selectNode(test.selector, inspector);
   let oldNodeFront = inspector.selection.nodeFront;
 
   let onUpdated = inspector.once("inspector-updated");
 
-  info("Listening for the markupmutation event");
-  // This event fires once the outerHTML is set, with a target as the parent node and a type of "childList".
-  let mutated = inspector.once("markupmutation");
-  info("Editing the outerHTML");
-  inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront, test.newHTML, test.oldHTML);
-  let mutations = yield mutated;
-  ok(true, "The markupmutation event has fired, mutation done");
-
-  info("Check to make the sure the correct mutation event was fired, and that the parent is selected");
-  let nodeFront = inspector.selection.nodeFront;
-  let mutation = mutations[0];
-  let isFromOuterHTML = mutation.removed.some(n => n === oldNodeFront);
-
-  ok(isFromOuterHTML, "The node is in the 'removed' list of the mutation");
-  is(mutation.type, "childList", "Mutation is a childList after updating outerHTML");
-  is(mutation.target, nodeFront, "Parent node is selected immediately after setting outerHTML");
-
-  // Wait for node to be reselected after outerHTML has been set
-  yield inspector.selection.once("new-node-front");
+  info("Listen for reselectedonremoved and edit the outerHTML");
+  let onReselected = inspector.markup.once("reselectedonremoved");
+  yield inspector.markup.updateNodeOuterHTML(inspector.selection.nodeFront,
+                                             test.newHTML, test.oldHTML);
+  yield onReselected;
 
   // Typically selectedNode will === pageNode, but if a new element has been injected in front
   // of it, this will not be the case.  If this happens.
   let selectedNodeFront = inspector.selection.nodeFront;
   let pageNodeFront = yield inspector.walker.querySelector(inspector.walker.rootNode, test.selector);
   let pageNode = getNode(test.selector);
 
   if (test.validate) {
--- a/browser/devtools/projecteditor/lib/tree.js
+++ b/browser/devtools/projecteditor/lib/tree.js
@@ -64,34 +64,40 @@ var ResourceContainer = Class({
     }, false);
 
     this.children = doc.createElementNS(HTML_NS, "ul");
     this.children.classList.add("children");
 
     this.elt.appendChild(this.children);
 
     this.line.addEventListener("click", (evt) => {
-      if (!this.selected) {
-        this.select();
-        this.expanded = true;
-        evt.stopPropagation();
-      }
+      this.select();
+      this.toggleExpansion();
+      evt.stopPropagation();
     }, false);
     this.expander.addEventListener("click", (evt) => {
-      this.expanded = !this.expanded;
+      this.toggleExpansion();
       this.select();
       evt.stopPropagation();
     }, true);
 
     if (!this.resource.isRoot) {
       this.expanded = false;
     }
     this.update();
   },
 
+  toggleExpansion: function() {
+    if (!this.resource.isRoot) {
+      this.expanded = !this.expanded;
+    } else {
+      this.expanded = true;
+    }
+  },
+
   destroy: function() {
     this.elt.remove();
     this.expander.remove();
     this.highlighter.remove();
     this.children.remove();
     this.label.remove();
     this.elt = this.expander = this.highlighter = this.children = this.label = null;
   },
--- a/browser/devtools/projecteditor/test/browser_projecteditor_tree_selection_01.js
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_tree_selection_01.js
@@ -34,20 +34,26 @@ let test = asyncTest(function*() {
 
 function selectFileFirstLoad(projecteditor, resource) {
   ok (resource && resource.path, "A valid resource has been passed in for selection " + (resource && resource.path));
   projecteditor.projectTree.selectResource(resource);
   let container = projecteditor.projectTree.getViewContainer(resource);
 
   if (resource.isRoot) {
     ok (container.expanded, "The root directory is expanded by default.");
+    container.line.click();
+    ok (container.expanded, "Clicking on the line does not toggles expansion.");
     return;
   }
   if (resource.isDir) {
     ok (!container.expanded, "A directory is not expanded by default.");
+    container.line.click();
+    ok (container.expanded, "Clicking on the line toggles expansion.");
+    container.line.click();
+    ok (!container.expanded, "Clicking on the line toggles expansion.");
     return;
   }
 
   let [editorCreated, editorLoaded, editorActivated] = yield promise.all([
     onceEditorCreated(projecteditor),
     onceEditorLoad(projecteditor),
     onceEditorActivated(projecteditor)
   ]);
--- a/browser/locales/en-US/chrome/browser/loop/loop.properties
+++ b/browser/locales/en-US/chrome/browser/loop/loop.properties
@@ -267,8 +267,13 @@ feedback_back_button=Back
 feedback_window_will_close_in2=This window will close in {{countdown}} second;This window will close in {{countdown}} seconds
 ## LOCALIZATION_NOTE (feedback_rejoin_button): Displayed on the feedback form after
 ## a signed-in to signed-in user call.
 ## https://people.mozilla.org/~dhenein/labs/loop-mvp-spec/#feedback
 feedback_rejoin_button=Rejoin
 ## LOCALIZATION NOTE (feedback_report_user_button): Used to report a user in the case of
 ## an abusive user.
 feedback_report_user_button=Report User
+
+## LOCALIZATION NOTE (rooms_list_current_conversations): We prefer to have no
+## number in the string, but if you need it for your language please use {{num}}.
+rooms_list_current_conversations=Current conversation;Current conversations
+rooms_list_no_current_conversations=No current conversations
--- a/build/moz-automation.mk
+++ b/build/moz-automation.mk
@@ -97,16 +97,20 @@ AUTOMATION_EXTRA_CMDLINE-check = -k
 # We need the log from make upload to grep it for urls in order to set
 # properties.
 AUTOMATION_EXTRA_CMDLINE-upload = 2>&1 | tee $(AUTOMATION_UPLOAD_OUTPUT)
 
 # Note: We have to force -j1 here, at least until bug 1036563 is fixed.
 AUTOMATION_EXTRA_CMDLINE-l10n-check = -j1
 AUTOMATION_EXTRA_CMDLINE-pretty-l10n-check = -j1
 
+# And force -j1 here until bug 1077670 is fixed.
+AUTOMATION_EXTRA_CMDLINE-package-tests = -j1
+AUTOMATION_EXTRA_CMDLINE-pretty-package-tests = -j1
+
 # The commands only run if the corresponding MOZ_AUTOMATION_* variable is
 # enabled. This means, for example, if we enable MOZ_AUTOMATION_UPLOAD, then
 # 'buildsymbols' will only run if MOZ_AUTOMATION_BUILD_SYMBOLS is also set.
 # However, the target automation/buildsymbols will still be executed in this
 # case because it is a prerequisite of automation/upload.
 define automation_commands
 $(call BUILDSTATUS,TIER_START $1)
 @$(MAKE) $1 $(AUTOMATION_EXTRA_CMDLINE-$1)
--- a/configure.in
+++ b/configure.in
@@ -1525,25 +1525,25 @@ if test "$GNU_CXX"; then
     # -Wtrigraphs - catches unlikely use of trigraphs
     # -Wtype-limits - catches overflow bugs, few false positives
     # -Wunused-label - catches unused goto labels
     # -Wwrite-strings - catches non-const char* pointers to string literals
     #
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wall"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wempty-body"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Woverloaded-virtual"
-    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wpointer-arith"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wsign-compare"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Wwrite-strings"
 
     # Treat some warnings as errors:
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=endif-labels"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=int-to-pointer-cast"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=missing-braces"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=parentheses"
+    _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=pointer-arith"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=return-type"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=sequence-point"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=unused-label"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=trigraphs"
     _WARNINGS_CXXFLAGS="${_WARNINGS_CXXFLAGS} -Werror=type-limits"
 
     # Turn off the following warnings that -Wall turns on:
     # -Wno-invalid-offsetof - we use offsetof on non-POD types frequently
--- a/content/base/public/File.h
+++ b/content/base/public/File.h
@@ -136,19 +136,16 @@ public:
   const nsTArray<nsRefPtr<FileImpl>>* GetSubBlobImpls() const;
 
   bool IsSizeUnknown() const;
 
   bool IsDateUnknown() const;
 
   bool IsFile() const;
 
-  void SetLazyData(const nsAString& aName, const nsAString& aContentType,
-                   uint64_t aLength, uint64_t aLastModifiedDate);
-
   already_AddRefed<File>
   CreateSlice(uint64_t aStart, uint64_t aLength, const nsAString& aContentType,
               ErrorResult& aRv);
 
   // WebIDL methods
   nsISupports* GetParentObject() const
   {
     return mParent;
--- a/content/base/src/File.cpp
+++ b/content/base/src/File.cpp
@@ -311,23 +311,16 @@ File::IsDateUnknown() const
 }
 
 bool
 File::IsFile() const
 {
   return mImpl->IsFile();
 }
 
-void
-File::SetLazyData(const nsAString& aName, const nsAString& aContentType,
-                     uint64_t aLength, uint64_t aLastModifiedDate)
-{
-  return mImpl->SetLazyData(aName, aContentType, aLength, aLastModifiedDate);
-}
-
 already_AddRefed<File>
 File::CreateSlice(uint64_t aStart, uint64_t aLength,
                   const nsAString& aContentType,
                   ErrorResult& aRv)
 {
   nsRefPtr<FileImpl> impl = mImpl->CreateSlice(aStart, aLength,
                                                aContentType, aRv);
   if (aRv.Failed()) {
@@ -348,17 +341,17 @@ File::GetName(nsAString& aFileName)
 NS_IMETHODIMP
 File::GetPath(nsAString& aPath)
 {
   return mImpl->GetPath(aPath);
 }
 
 NS_IMETHODIMP
 File::GetLastModifiedDate(JSContext* aCx,
-                             JS::MutableHandle<JS::Value> aDate)
+                          JS::MutableHandle<JS::Value> aDate)
 {
   ErrorResult rv;
   Date value = GetLastModifiedDate(rv);
   if (rv.Failed()) {
     return rv.ErrorCode();
   }
 
   if (!value.ToDateObject(aCx, aDate)) {
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -5978,17 +5978,18 @@ nsContentUtils::CreateArrayBuffer(JSCont
   int32_t dataLen = aData.Length();
   *aResult = JS_NewArrayBuffer(aCx, dataLen);
   if (!*aResult) {
     return NS_ERROR_FAILURE;
   }
 
   if (dataLen > 0) {
     NS_ASSERTION(JS_IsArrayBufferObject(*aResult), "What happened?");
-    memcpy(JS_GetArrayBufferData(*aResult), aData.BeginReading(), dataLen);
+    JS::AutoCheckCannotGC nogc;
+    memcpy(JS_GetArrayBufferData(*aResult, nogc), aData.BeginReading(), dataLen);
   }
 
   return NS_OK;
 }
 
 // Initial implementation: only stores to RAM, not file
 // TODO: bug 704447: large file support
 nsresult
--- a/content/base/src/nsDOMFileReader.cpp
+++ b/content/base/src/nsDOMFileReader.cpp
@@ -289,18 +289,32 @@ nsDOMFileReader::DoOnLoadEnd(nsresult aS
   // Clear out the data if necessary
   if (NS_FAILED(aStatus)) {
     FreeFileData();
     return NS_OK;
   }
 
   nsresult rv = NS_OK;
   switch (mDataFormat) {
-    case FILE_AS_ARRAYBUFFER:
-      break; //Already accumulated mResultArrayBuffer
+    case FILE_AS_ARRAYBUFFER: {
+      AutoJSAPI jsapi;
+      if (NS_WARN_IF(!jsapi.Init(mozilla::DOMEventTargetHelper::GetParentObject()))) {
+        return NS_ERROR_FAILURE;
+      }
+
+      RootResultArrayBuffer();
+      mResultArrayBuffer = JS_NewArrayBufferWithContents(jsapi.cx(), mTotal, mFileData);
+      if (!mResultArrayBuffer) {
+        JS_ClearPendingException(jsapi.cx());
+        rv = NS_ERROR_OUT_OF_MEMORY;
+      } else {
+        mFileData = nullptr; // Transfer ownership
+      }
+      break;
+    }
     case FILE_AS_BINARY:
       break; //Already accumulated mResult
     case FILE_AS_TEXT:
       if (!mFileData) {
         if (mDataLen) {
           rv = NS_ERROR_OUT_OF_MEMORY;
           break;
         }
@@ -337,45 +351,40 @@ nsDOMFileReader::DoReadData(nsIAsyncInpu
     mResult.GetMutableData(&buf, oldLen + aCount, fallible_t());
     NS_ENSURE_TRUE(buf, NS_ERROR_OUT_OF_MEMORY);
 
     uint32_t bytesRead = 0;
     aStream->ReadSegments(ReadFuncBinaryString, buf + oldLen, aCount,
                           &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
-  else if (mDataFormat == FILE_AS_ARRAYBUFFER) {
-    uint32_t bytesRead = 0;
-    aStream->Read((char*) JS_GetArrayBufferData(mResultArrayBuffer) + mDataLen,
-                  aCount, &bytesRead);
-    NS_ASSERTION(bytesRead == aCount, "failed to read data");
-  }
   else {
     //Update memory buffer to reflect the contents of the file
     if (mDataLen + aCount > UINT32_MAX) {
       // PR_Realloc doesn't support over 4GB memory size even if 64-bit OS
       return NS_ERROR_OUT_OF_MEMORY;
     }
-    mFileData = (char *) moz_realloc(mFileData, mDataLen + aCount);
-    NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
+    if (mDataFormat != FILE_AS_ARRAYBUFFER) {
+      mFileData = (char *) moz_realloc(mFileData, mDataLen + aCount);
+      NS_ENSURE_TRUE(mFileData, NS_ERROR_OUT_OF_MEMORY);
+    }
 
     uint32_t bytesRead = 0;
     aStream->Read(mFileData + mDataLen, aCount, &bytesRead);
     NS_ASSERTION(bytesRead == aCount, "failed to read data");
   }
 
   mDataLen += aCount;
   return NS_OK;
 }
 
 // Helper methods
 
 void
-nsDOMFileReader::ReadFileContent(JSContext* aCx,
-                                 File& aFile,
+nsDOMFileReader::ReadFileContent(File& aFile,
                                  const nsAString &aCharset,
                                  eDataFormat aDataFormat,
                                  ErrorResult& aRv)
 {
   //Implicit abort to clear any other activity going on
   Abort();
   mError = nullptr;
   SetDOMStringToNull(mResult);
@@ -438,21 +447,20 @@ nsDOMFileReader::ReadFileContent(JSConte
     return;
   }
 
   //FileReader should be in loading state here
   mReadyState = nsIDOMFileReader::LOADING;
   DispatchProgressEvent(NS_LITERAL_STRING(LOADSTART_STR));
 
   if (mDataFormat == FILE_AS_ARRAYBUFFER) {
-    RootResultArrayBuffer();
-    mResultArrayBuffer = JS_NewArrayBuffer(aCx, mTotal);
-    if (!mResultArrayBuffer) {
-      NS_WARNING("Failed to create JS array buffer");
-      aRv.Throw(NS_ERROR_FAILURE);
+    mFileData = js_pod_malloc<char>(mTotal);
+    if (!mFileData) {
+      NS_WARNING("Preallocation failed for ReadFileData");
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     }
   }
 }
 
 nsresult
 nsDOMFileReader::GetAsText(nsIDOMBlob *aFile,
                            const nsACString &aCharset,
                            const char *aFileData,
--- a/content/base/src/nsDOMFileReader.h
+++ b/content/base/src/nsDOMFileReader.h
@@ -65,27 +65,27 @@ public:
   }
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
   // WebIDL
   static already_AddRefed<nsDOMFileReader>
   Constructor(const GlobalObject& aGlobal, ErrorResult& aRv);
   void ReadAsArrayBuffer(JSContext* aCx, File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(aCx, aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_ARRAYBUFFER, aRv);
   }
 
   void ReadAsText(File& aBlob, const nsAString& aLabel, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, aLabel, FILE_AS_TEXT, aRv);
+    ReadFileContent(aBlob, aLabel, FILE_AS_TEXT, aRv);
   }
 
   void ReadAsDataURL(File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, EmptyString(), FILE_AS_DATAURL, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_DATAURL, aRv);
   }
 
   using FileIOObject::Abort;
 
   // Inherited ReadyState().
 
   void GetResult(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
                  ErrorResult& aRv);
@@ -99,17 +99,17 @@ public:
   using FileIOObject::GetOnabort;
   using FileIOObject::SetOnabort;
   using FileIOObject::GetOnerror;
   using FileIOObject::SetOnerror;
   IMPL_EVENT_HANDLER(loadend)
 
   void ReadAsBinaryString(File& aBlob, ErrorResult& aRv)
   {
-    ReadFileContent(nullptr, aBlob, EmptyString(), FILE_AS_BINARY, aRv);
+    ReadFileContent(aBlob, EmptyString(), FILE_AS_BINARY, aRv);
   }
 
 
   nsresult Init();
 
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(nsDOMFileReader,
                                                          FileIOObject)
   void RootResultArrayBuffer();
@@ -119,17 +119,17 @@ protected:
 
   enum eDataFormat {
     FILE_AS_ARRAYBUFFER,
     FILE_AS_BINARY,
     FILE_AS_TEXT,
     FILE_AS_DATAURL
   };
 
-  void ReadFileContent(JSContext* aCx, File& aBlob,
+  void ReadFileContent(File& aBlob,
                        const nsAString &aCharset, eDataFormat aDataFormat,
                        ErrorResult& aRv);
   nsresult GetAsText(nsIDOMBlob *aFile, const nsACString &aCharset,
                      const char *aFileData, uint32_t aDataLen, nsAString &aResult);
   nsresult GetAsDataURL(nsIDOMBlob *aFile, const char *aFileData, uint32_t aDataLen, nsAString &aResult);
 
   void FreeFileData() {
     moz_free(mFileData);
--- a/content/base/src/nsDocument.cpp
+++ b/content/base/src/nsDocument.cpp
@@ -5843,16 +5843,21 @@ nsDocument::ProcessBaseElementQueue()
 }
 
 // static
 void
 nsDocument::ProcessTopElementQueue(bool aIsBaseQueue)
 {
   MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
 
+  if (sProcessingStack.isNothing()) {
+    // If XPCOM shutdown has reset the processing stack, don't do anything.
+    return;
+  }
+
   nsTArray<CustomElementData*>& stack = *sProcessingStack;
   uint32_t firstQueue = stack.LastIndexOf((CustomElementData*) nullptr);
 
   if (aIsBaseQueue && firstQueue != 0) {
     return;
   }
 
   for (uint32_t i = firstQueue + 1; i < stack.Length(); ++i) {
--- a/content/base/src/nsFrameLoader.cpp
+++ b/content/base/src/nsFrameLoader.cpp
@@ -842,20 +842,21 @@ nsFrameLoader::MarginsChanged(uint32_t a
   if (!mDocShell)
     return;
 
   // Set the margins
   mDocShell->SetMarginWidth(aMarginWidth);
   mDocShell->SetMarginHeight(aMarginHeight);
 
   // Trigger a restyle if there's a prescontext
+  // FIXME: This could do something much less expensive.
   nsRefPtr<nsPresContext> presContext;
   mDocShell->GetPresContext(getter_AddRefs(presContext));
   if (presContext)
-    presContext->RebuildAllStyleData(nsChangeHint(0));
+    presContext->RebuildAllStyleData(nsChangeHint(0), eRestyle_Subtree);
 }
 
 bool
 nsFrameLoader::ShowRemoteFrame(const nsIntSize& size,
                                nsSubDocumentFrame *aFrame)
 {
   NS_ASSERTION(mRemoteFrame, "ShowRemote only makes sense on remote frames.");
 
--- a/content/html/content/test/test_audio_wakelock.html
+++ b/content/html/content/test/test_audio_wakelock.html
@@ -26,38 +26,41 @@ function testAudioPlayPause() {
 
   var content = document.getElementById('content');
 
   var audio = document.createElement('audio');
   audio.src = "wakelock.ogg";
   content.appendChild(audio);
 
   var startDate;
-  audio.addEventListener('playing', function() {
-    startDate = new Date();
-
-    // The next step is to unlock the resource.
-    lockState = false;
-    audio.pause();
-  });
-
   function testAudioPlayListener(topic, state) {
-    is(topic, "cpu", "Audio element locked the target == cpu");
+    is(topic, "cpu", "#1 Audio element locked the target == cpu");
     var locked = state == "locked-foreground" ||
                  state == "locked-background";
 
-    is(locked, lockState, "Audio element locked the cpu - no paused");
+    var s = locked ? "locked" : "unlocked";
+    is(locked, lockState, "#1 Audio element " + s + " the cpu");
     count++;
 
     // count == 1 is when the cpu wakelock is created
     // count == 2 is when the cpu wakelock is released
 
+    if (count == 1) {
+      // The next step is to unlock the resource.
+      lockState = false;
+      audio.pause();
+      startDate = new Date();
+      return;
+    }
+
+    is(count, 2, "The count should be 2 which indicates wakelock release");
+
     if (count == 2) {
       var diffDate = (new Date() - startDate);
-      ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release");
+      ok(diffDate > 200, "#1 There was at least 200 milliseconds between the stop and the wakelock release");
 
       content.removeChild(audio);
       navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
       runTests();
     }
   };
 
   navigator.mozPower.addWakeLockListener(testAudioPlayListener);
@@ -69,40 +72,33 @@ function testAudioPlay() {
   var count = 0;
 
   var content = document.getElementById('content');
 
   var audio = document.createElement('audio');
   audio.src = "wakelock.ogg";
   content.appendChild(audio);
 
-  var startDate;
-  audio.addEventListener('progress', function() {
-    startDate = new Date();
-  });
-
   function testAudioPlayListener(topic, state) {
-    is(topic, "cpu", "Audio element locked the target == cpu");
+    is(topic, "cpu", "#2 Audio element locked the target == cpu");
     var locked = state == "locked-foreground" ||
                  state == "locked-background";
 
-    is(locked, lockState, "Audio element locked the cpu - no paused");
+    var s = locked ? "locked" : "unlocked";
+    is(locked, lockState, "#2 Audio element " + s + " the cpu");
     count++;
 
     // count == 1 is when the cpu wakelock is created: the wakelock must be
     // created when the media element starts playing.
     // count == 2 is when the cpu wakelock is released.
 
     if (count == 1) {
       // The next step is to unlock the resource.
       lockState = false;
     } else if (count == 2) {
-      var diffDate = (new Date() - startDate);
-      ok(diffDate > 200, "There was at least 200 milliseconds between the stop and the wakelock release");
-
       content.removeChild(audio);
       navigator.mozPower.removeWakeLockListener(testAudioPlayListener);
       runTests();
     }
   };
 
   navigator.mozPower.addWakeLockListener(testAudioPlayListener);
   audio.play();
--- a/content/media/RtspMediaResource.cpp
+++ b/content/media/RtspMediaResource.cpp
@@ -48,29 +48,38 @@ namespace mozilla {
 #define BUFFER_SLOT_EMPTY 0
 
 struct BufferSlotData {
   int32_t mLength;
   uint64_t mTime;
   int32_t  mFrameType;
 };
 
+// This constant is used to determine if the buffer usage is over a threshold.
+const float kBufferThresholdPerc = 0.8f;
+// The default value of playout delay duration.
+const uint32_t kPlayoutDelayMs = 3000;
+
+//-----------------------------------------------------------------------------
+// RtspTrackBuffer
+//-----------------------------------------------------------------------------
 class RtspTrackBuffer
 {
 public:
   RtspTrackBuffer(const char *aMonitor, int32_t aTrackIdx, uint32_t aSlotSize)
   : mMonitor(aMonitor)
   , mSlotSize(aSlotSize)
   , mTotalBufferSize(BUFFER_SLOT_NUM * mSlotSize)
   , mFrameType(0)
-  , mIsStarted(false) {
+  , mIsStarted(false)
+  , mDuringPlayoutDelay(false)
+  , mPlayoutDelayMs(kPlayoutDelayMs)
+  , mPlayoutDelayTimer(nullptr) {
     MOZ_COUNT_CTOR(RtspTrackBuffer);
-#ifdef PR_LOGGING
     mTrackIdx = aTrackIdx;
-#endif
     MOZ_ASSERT(mSlotSize < UINT32_MAX / BUFFER_SLOT_NUM);
     mRingBuffer = new uint8_t[mTotalBufferSize];
     Reset();
   };
   ~RtspTrackBuffer() {
     MOZ_COUNT_DTOR(RtspTrackBuffer);
     mRingBuffer = nullptr;
   };
@@ -88,16 +97,17 @@ public:
   void Start() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = true;
     mFrameType = 0;
   }
   void Stop() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = false;
+    StopPlayoutDelay();
   }
 
   // Read the data from mRingBuffer[mConsumerIdx*mSlotSize] into aToBuffer.
   // If the aToBufferSize is smaller than mBufferSlotDataLength[mConsumerIdx],
   // early return and set the aFrameSize to notify the reader the aToBuffer
   // doesn't have enough space. The reader must realloc the aToBuffer if it
   // wishes to read the data.
   nsresult ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
@@ -113,29 +123,55 @@ public:
   // We should call SetFrameType first then reset().
   // If we call reset() first, the queue may still has some "garbage" frame
   // from another thread's |OnMediaDataAvailable| before |SetFrameType|.
   void ResetWithFrameType(uint32_t aFrameType) {
     SetFrameType(aFrameType);
     Reset();
   }
 
+  // When RtspTrackBuffer is in playout delay duration, it should suspend
+  // reading data from the buffer until the playout-delay-ended event occurs,
+  // which wil be trigger by mPlayoutDelayTimer.
+  void StartPlayoutDelay() {
+    mDuringPlayoutDelay = true;
+  }
+  void LockStartPlayoutDelay() {
+    MonitorAutoLock monitor(mMonitor);
+    StartPlayoutDelay();
+  }
+
+  // If the playout delay is stopped, mPlayoutDelayTimer should be canceled.
+  void StopPlayoutDelay() {
+    if (mPlayoutDelayTimer) {
+      mPlayoutDelayTimer->Cancel();
+      mPlayoutDelayTimer = nullptr;
+    }
+    mDuringPlayoutDelay = false;
+  }
+  void LockStopPlayoutDelay() {
+    MonitorAutoLock monitor(mMonitor);
+    StopPlayoutDelay();
+  }
+
+  bool IsBufferOverThreshold();
+  void CreatePlayoutDelayTimer(unsigned long delayMs);
+  static void PlayoutDelayTimerCallback(nsITimer *aTimer, void *aClosure);
+
 private:
   // The FrameType is sync to nsIStreamingProtocolController.h
   void SetFrameType(uint32_t aFrameType) {
     MonitorAutoLock monitor(mMonitor);
     mFrameType = mFrameType | aFrameType;
   }
 
   // A monitor lock to prevent racing condition.
   Monitor mMonitor;
-#ifdef PR_LOGGING
   // Indicate the track number for Rtsp.
   int32_t mTrackIdx;
-#endif
   // mProducerIdx: A slot index that we store data from
   // nsIStreamingProtocolController.
   // mConsumerIdx: A slot index that we read when decoder need(from OMX decoder).
   int32_t mProducerIdx;
   int32_t mConsumerIdx;
 
   // Because each slot's size is fixed, we need an array to record the real
   // data length and data time stamp.
@@ -154,16 +190,23 @@ private:
   uint32_t mTotalBufferSize;
   // A flag that that indicate the incoming data should be dropped or stored.
   // When we are seeking, the incoming data should be dropped.
   // Bit definition in |nsIStreamingProtocolController.h|
   uint32_t mFrameType;
 
   // Set true/false when |Start()/Stop()| is called.
   bool mIsStarted;
+
+  // Indicate the buffer is in playout delay duration or not.
+  bool mDuringPlayoutDelay;
+  // Playout delay duration defined in milliseconds.
+  uint32_t mPlayoutDelayMs;
+  // Timer used to fire playout-delay-ended event.
+  nsCOMPtr<nsITimer> mPlayoutDelayTimer;
 };
 
 nsresult RtspTrackBuffer::ReadBuffer(uint8_t* aToBuffer, uint32_t aToBufferSize,
                                      uint32_t& aReadCount, uint64_t& aFrameTime,
                                      uint32_t& aFrameSize)
 {
   MonitorAutoLock monitor(mMonitor);
   RTSPMLOG("ReadBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d "
@@ -172,19 +215,26 @@ nsresult RtspTrackBuffer::ReadBuffer(uin
            ,mBufferSlotData[mConsumerIdx].mLength);
   // Reader should skip the slots with mLength==BUFFER_SLOT_INVALID.
   // The loop ends when
   // 1. Read data successfully
   // 2. Fail to read data due to aToBuffer's space
   // 3. No data in this buffer
   // 4. mIsStarted is not set
   while (1) {
+    // Do not read from buffer if we are still in the playout delay duration.
+    if (mDuringPlayoutDelay) {
+      monitor.Wait();
+      continue;
+    }
+
     if (mBufferSlotData[mConsumerIdx].mFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       return NS_BASE_STREAM_CLOSED;
     }
+
     if (mBufferSlotData[mConsumerIdx].mLength > 0) {
       // Check the aToBuffer space is enough for data copy.
       if ((int32_t)aToBufferSize < mBufferSlotData[mConsumerIdx].mLength) {
         aFrameSize = mBufferSlotData[mConsumerIdx].mLength;
         break;
       }
       uint32_t slots = (mBufferSlotData[mConsumerIdx].mLength / mSlotSize) + 1;
       // we have data, copy to aToBuffer
@@ -266,16 +316,22 @@ void RtspTrackBuffer::WriteBuffer(const 
   // Checking current buffer frame type.
   // If the MEDIASTREAM_FRAMETYPE_DISCONTINUNITY bit is set, imply the
   // RtspTrackBuffer can't receive data now. So we drop the frame until we
   // receive MEDIASTREAM_FRAMETYPE_DISCONTINUNITY.
   if (mFrameType & MEDIASTREAM_FRAMETYPE_DISCONTINUITY) {
     RTSPMLOG("Return because the mFrameType is set");
     return;
   }
+
+  // Create a timer to delay ReadBuffer() for a duration.
+  if (mDuringPlayoutDelay && !mPlayoutDelayTimer) {
+    CreatePlayoutDelayTimer(mPlayoutDelayMs);
+  }
+
   // The flag is true if the incoming data is larger than one slot size.
   bool isMultipleSlots = false;
   // The flag is true if the incoming data is larger than remainder free slots
   bool returnToHead = false;
   // Calculate how many slots the incoming data needed.
   int32_t slots = 1;
   int32_t i;
   RTSPMLOG("WriteBuffer mTrackIdx %d mProducerIdx %d mConsumerIdx %d",
@@ -309,24 +365,31 @@ void RtspTrackBuffer::WriteBuffer(const 
     }
     mProducerIdx = 0;
   }
 
   if (!(aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM)) {
     memcpy(&(mRingBuffer[mSlotSize * mProducerIdx]), aFromBuffer, aWriteCount);
   }
 
+  // If the buffer is almost full, stop the playout delay to let ReadBuffer()
+  // consume data in the buffer.
+  if (mDuringPlayoutDelay && IsBufferOverThreshold()) {
+    StopPlayoutDelay();
+  }
+
   if (mProducerIdx <= mConsumerIdx && mConsumerIdx < mProducerIdx + slots
       && mBufferSlotData[mConsumerIdx].mLength > 0) {
     // Wrote one or more slots that the decode thread has not yet read.
     RTSPMLOG("overwrite!! %d time %lld"
              ,mTrackIdx,mBufferSlotData[mConsumerIdx].mTime);
     if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       mBufferSlotData[mProducerIdx].mLength = 0;
       mBufferSlotData[mProducerIdx].mTime = 0;
+      StopPlayoutDelay();
     } else {
       mBufferSlotData[mProducerIdx].mLength = aWriteCount;
       mBufferSlotData[mProducerIdx].mTime = aFrameTime;
     }
     mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotDataLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
@@ -337,16 +400,17 @@ void RtspTrackBuffer::WriteBuffer(const 
     // Move the mConsumerIdx forward to ensure that the decoder reads the
     // oldest data available.
     mConsumerIdx = mProducerIdx;
   } else {
     // Normal case, the writer doesn't take over the reader.
     if (aFrameType & MEDIASTREAM_FRAMETYPE_END_OF_STREAM) {
       mBufferSlotData[mProducerIdx].mLength = 0;
       mBufferSlotData[mProducerIdx].mTime = 0;
+      StopPlayoutDelay();
     } else {
       mBufferSlotData[mProducerIdx].mLength = aWriteCount;
       mBufferSlotData[mProducerIdx].mTime = aFrameTime;
     }
     mBufferSlotData[mProducerIdx].mFrameType = aFrameType;
     // Clear the mBufferSlotData[].mLength except the start slot.
     if (isMultipleSlots) {
       for (i = mProducerIdx + 1; i < mProducerIdx + slots; ++i) {
@@ -363,19 +427,68 @@ void RtspTrackBuffer::Reset() {
   MonitorAutoLock monitor(mMonitor);
   mProducerIdx = 0;
   mConsumerIdx = 0;
   for (uint32_t i = 0; i < BUFFER_SLOT_NUM; ++i) {
     mBufferSlotData[i].mLength = BUFFER_SLOT_EMPTY;
     mBufferSlotData[i].mTime = BUFFER_SLOT_EMPTY;
     mBufferSlotData[i].mFrameType = MEDIASTREAM_FRAMETYPE_NORMAL;
   }
+  StopPlayoutDelay();
   mMonitor.NotifyAll();
 }
 
+bool
+RtspTrackBuffer::IsBufferOverThreshold()
+{
+  static int32_t numSlotsThreshold =
+    BUFFER_SLOT_NUM * kBufferThresholdPerc;
+
+  int32_t numSlotsUsed = mProducerIdx - mConsumerIdx;
+  if (numSlotsUsed < 0) {  // wrap-around
+    numSlotsUsed = (BUFFER_SLOT_NUM - mConsumerIdx) + mProducerIdx;
+  }
+  if (numSlotsUsed > numSlotsThreshold) {
+    return true;
+  }
+
+  return false;
+}
+
+void
+RtspTrackBuffer::CreatePlayoutDelayTimer(unsigned long delayMs)
+{
+  if (delayMs <= 0) {
+    return;
+  }
+  mPlayoutDelayTimer = do_CreateInstance("@mozilla.org/timer;1");
+  if (mPlayoutDelayTimer) {
+    mPlayoutDelayTimer->InitWithFuncCallback(PlayoutDelayTimerCallback,
+                                             this, delayMs,
+                                             nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+// static
+void
+RtspTrackBuffer::PlayoutDelayTimerCallback(nsITimer *aTimer,
+                                           void *aClosure)
+{
+  MOZ_ASSERT(aTimer);
+  MOZ_ASSERT(aClosure);
+
+  RtspTrackBuffer *self = static_cast<RtspTrackBuffer*>(aClosure);
+  MonitorAutoLock lock(self->mMonitor);
+  self->StopPlayoutDelay();
+  lock.NotifyAll();
+}
+
+//-----------------------------------------------------------------------------
+// RtspMediaResource
+//-----------------------------------------------------------------------------
 RtspMediaResource::RtspMediaResource(MediaDecoder* aDecoder,
     nsIChannel* aChannel, nsIURI* aURI, const nsACString& aContentType)
   : BaseMediaResource(aDecoder, aChannel, aURI, aContentType)
   , mIsConnected(false)
   , mRealTime(false)
   , mIsSuspend(true)
 {
 #ifndef NECKO_PROTOCOL_rtsp
@@ -753,10 +866,26 @@ nsresult RtspMediaResource::SeekTime(int
   // Clear buffer and raise the frametype flag.
   for(uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
     mTrackBuffer[i]->ResetWithFrameType(MEDIASTREAM_FRAMETYPE_DISCONTINUITY);
   }
 
   return mMediaStreamController->Seek(aOffset);
 }
 
+void
+RtspMediaResource::EnablePlayoutDelay()
+{
+  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
+    mTrackBuffer[i]->LockStartPlayoutDelay();
+  }
+}
+
+void
+RtspMediaResource::DisablePlayoutDelay()
+{
+  for (uint32_t i = 0; i < mTrackBuffer.Length(); ++i) {
+    mTrackBuffer[i]->LockStopPlayoutDelay();
+  }
+}
+
 } // namespace mozilla
 
--- a/content/media/RtspMediaResource.h
+++ b/content/media/RtspMediaResource.h
@@ -2,16 +2,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #if !defined(RtspMediaResource_h_)
 #define RtspMediaResource_h_
 
 #include "MediaResource.h"
+#include "mozilla/Monitor.h"
+#include "nsITimer.h"
 
 namespace mozilla {
 
 class RtspTrackBuffer;
 
 /* RtspMediaResource
  * RtspMediaResource provides an interface to deliver and control RTSP media
  * data to RtspDecoder.
@@ -107,16 +109,22 @@ public:
   //   aFrameSize: actual data size in track.
   nsresult ReadFrameFromTrack(uint8_t* aBuffer, uint32_t aBufferSize,
                               uint32_t aTrackIdx, uint32_t& aBytes,
                               uint64_t& aTime, uint32_t& aFrameSize);
 
   // Seek to the given time offset
   nsresult SeekTime(int64_t aOffset);
 
+  // The idea of playout delay is to hold frames in the playout buffer
+  // (RtspTrackBuffer) for a period of time in order to smooth timing variations
+  // caused by the network.
+  void EnablePlayoutDelay();
+  void DisablePlayoutDelay();
+
   // dummy
   virtual nsresult ReadAt(int64_t aOffset, char* aBuffer,
                           uint32_t aCount, uint32_t* aBytes)  MOZ_OVERRIDE{
     return NS_ERROR_FAILURE;
   }
   // dummy
   virtual void     SetReadMode(MediaCacheStream::ReadMode aMode) MOZ_OVERRIDE {}
   // dummy
--- a/content/media/fmp4/gonk/GonkAudioDecoderManager.cpp
+++ b/content/media/fmp4/gonk/GonkAudioDecoderManager.cpp
@@ -89,43 +89,38 @@ GonkAudioDecoderManager::Init(MediaDataD
   } else {
     ALOG("Failed to input codec specific data!");
     return nullptr;
   }
 }
 
 nsresult
 GonkAudioDecoderManager::CreateAudioData(int64_t aStreamOffset, AudioData **v) {
-
-  void *data;
-  size_t dataOffset;
-  size_t size;
-  int64_t timeUs;
-
   if (!(mAudioBuffer != nullptr && mAudioBuffer->data() != nullptr)) {
     ALOG("Audio Buffer is not valid!");
     return NS_ERROR_UNEXPECTED;
   }
 
+  int64_t timeUs;
   if (!mAudioBuffer->meta_data()->findInt64(kKeyTime, &timeUs)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   if (mAudioBuffer->range_length() == 0) {
     // Some decoders may return spurious empty buffers that we just want to ignore
     // quoted from Android's AwesomePlayer.cpp
     ReleaseAudioBuffer();
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  data = mAudioBuffer->data();
-  dataOffset = mAudioBuffer->range_offset();
-  size = mAudioBuffer->range_length();
+  const uint8_t *data = static_cast<const uint8_t*>(mAudioBuffer->data());
+  size_t dataOffset = mAudioBuffer->range_offset();
+  size_t size = mAudioBuffer->range_length();
 
-  nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[size/2] );
+  nsAutoArrayPtr<AudioDataValue> buffer(new AudioDataValue[size/2]);
   memcpy(buffer.get(), data+dataOffset, size);
   uint32_t frames = size / (2 * mAudioChannels);
 
   CheckedInt64 duration = FramesToUsecs(frames, mAudioRate);
   if (!duration.isValid()) {
     return NS_ERROR_UNEXPECTED;
   }
   *v = new AudioData(aStreamOffset,
@@ -148,17 +143,17 @@ GonkAudioDecoderManager::Output(int64_t 
   err = mDecoder->Output(&mAudioBuffer, READ_OUTPUT_BUFFER_TIMEOUT_US);
 
   switch (err) {
     case OK:
     {
       AudioData* data = nullptr;
       nsresult rv = CreateAudioData(aStreamOffset, &data);
       if (rv == NS_ERROR_NOT_AVAILABLE) {
-	// Decoder outputs a empty video buffer, try again
+        // Decoder outputs an empty video buffer, try again
         return NS_ERROR_NOT_AVAILABLE;
       } else if (rv != NS_OK || data == nullptr) {
         return NS_ERROR_UNEXPECTED;
       }
       aOutData = data;
       return NS_OK;
     }
     case android::INFO_FORMAT_CHANGED:
--- a/content/media/gmp/GMPChild.cpp
+++ b/content/media/gmp/GMPChild.cpp
@@ -11,16 +11,20 @@
 #include "GMPVideoHost.h"
 #include "nsDebugImpl.h"
 #include "nsIFile.h"
 #include "nsXULAppAPI.h"
 #include "gmp-video-decode.h"
 #include "gmp-video-encode.h"
 #include "GMPPlatform.h"
 #include "mozilla/dom/CrashReporterChild.h"
+#ifdef XP_WIN
+#include <fstream>
+#include "nsCRT.h"
+#endif
 
 using mozilla::dom::CrashReporterChild;
 
 #ifdef XP_WIN
 #include <stdlib.h> // for _exit()
 #else
 #include <unistd.h> // for _exit()
 #endif
@@ -46,63 +50,95 @@ GMPChild::GMPChild()
   nsDebugImpl::SetMultiprocessMode("GMP");
 }
 
 GMPChild::~GMPChild()
 {
 }
 
 static bool
-GetPluginFile(const std::string& aPluginPath,
+GetFileBase(const std::string& aPluginPath,
 #if defined(XP_MACOSX)
-              nsCOMPtr<nsIFile>& aLibDirectory,
+            nsCOMPtr<nsIFile>& aLibDirectory,
 #endif
-              nsCOMPtr<nsIFile>& aLibFile)
+            nsCOMPtr<nsIFile>& aFileBase,
+            nsAutoString& aBaseName)
 {
   nsDependentCString pluginPath(aPluginPath.c_str());
 
   nsresult rv = NS_NewLocalFile(NS_ConvertUTF8toUTF16(pluginPath),
-                                true, getter_AddRefs(aLibFile));
+                                true, getter_AddRefs(aFileBase));
   if (NS_FAILED(rv)) {
     return false;
   }
 
 #if defined(XP_MACOSX)
-  if (NS_FAILED(aLibFile->Clone(getter_AddRefs(aLibDirectory)))) {
+  if (NS_FAILED(aFileBase->Clone(getter_AddRefs(aLibDirectory)))) {
     return false;
   }
 #endif
 
   nsCOMPtr<nsIFile> parent;
-  rv = aLibFile->GetParent(getter_AddRefs(parent));
+  rv = aFileBase->GetParent(getter_AddRefs(parent));
   if (NS_FAILED(rv)) {
     return false;
   }
 
   nsAutoString parentLeafName;
   rv = parent->GetLeafName(parentLeafName);
   if (NS_FAILED(rv)) {
     return false;
   }
 
-  nsAutoString baseName(Substring(parentLeafName, 4, parentLeafName.Length() - 1));
+  aBaseName = Substring(parentLeafName,
+                        4,
+                        parentLeafName.Length() - 1);
+  return true;
+}
+
+static bool
+GetPluginFile(const std::string& aPluginPath,
+#if defined(XP_MACOSX)
+              nsCOMPtr<nsIFile>& aLibDirectory,
+#endif
+              nsCOMPtr<nsIFile>& aLibFile)
+{
+  nsAutoString baseName;
+#ifdef XP_MACOSX
+  GetFileBase(aPluginPath, aLibDirectory, aLibFile, baseName);
+#else
+  GetFileBase(aPluginPath, aLibFile, baseName);
+#endif
 
 #if defined(XP_MACOSX)
   nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".dylib");
 #elif defined(OS_POSIX)
   nsAutoString binaryName = NS_LITERAL_STRING("lib") + baseName + NS_LITERAL_STRING(".so");
 #elif defined(XP_WIN)
   nsAutoString binaryName =                            baseName + NS_LITERAL_STRING(".dll");
 #else
 #error not defined
 #endif
   aLibFile->AppendRelativePath(binaryName);
   return true;
 }
 
+#ifdef XP_WIN
+static bool
+GetInfoFile(const std::string& aPluginPath,
+            nsCOMPtr<nsIFile>& aInfoFile)
+{
+  nsAutoString baseName;
+  GetFileBase(aPluginPath, aInfoFile, baseName);
+  nsAutoString infoFileName = baseName + NS_LITERAL_STRING(".info");
+  aInfoFile->AppendRelativePath(infoFileName);
+  return true;
+}
+#endif
+
 #if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
 static bool
 GetPluginPaths(const std::string& aPluginPath,
                nsCString &aPluginDirectoryPath,
                nsCString &aPluginFilePath)
 {
   nsCOMPtr<nsIFile> libDirectory, libFile;
   if (!GetPluginFile(aPluginPath, libDirectory, libFile)) {
@@ -232,23 +268,87 @@ GMPChild::Init(const std::string& aPlugi
   SendPCrashReporterConstructor(CrashReporter::CurrentThreadId());
 #endif
 
 #if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
   mPluginPath = aPluginPath;
   return true;
 #endif
 
+#ifdef XP_WIN
+  PreLoadLibraries(aPluginPath);
+#endif
+
 #if defined(MOZ_SANDBOX) && defined(XP_WIN)
   mozilla::SandboxTarget::Instance()->StartSandbox();
 #endif
 
   return LoadPluginLibrary(aPluginPath);
 }
 
+#ifdef XP_WIN
+// Pre-load DLLs that need to be used by the EME plugin but that can't be
+// loaded after the sandbox has started
+bool
+GMPChild::PreLoadLibraries(const std::string& aPluginPath)
+{
+  // This must be in sorted order and lowercase!
+  static const char* whitelist[] =
+    {
+       "d3d9.dll", // Create an `IDirect3D9` to get adapter information
+       "dxva2.dll", // Get monitor information
+       "msauddecmft.dll", // H.264 decoder
+       "msmpeg2adec.dll", // AAC decoder (on Windows 7)
+       "msmpeg2vdec.dll", // AAC decoder (on Windows 8)
+    };
+  static const int whitelistLen = sizeof(whitelist) / sizeof(whitelist[0]);
+
+  nsCOMPtr<nsIFile> infoFile;
+  GetInfoFile(aPluginPath, infoFile);
+
+  nsString path;
+  infoFile->GetPath(path);
+
+  std::ifstream stream;
+  stream.open(path.get());
+  if (!stream.good()) {
+    NS_WARNING("Failure opening info file for required DLLs");
+    return false;
+  }
+
+  do {
+    std::string line;
+    getline(stream, line);
+    if (stream.fail()) {
+      NS_WARNING("Failure reading info file for required DLLs");
+      return false;
+    }
+    std::transform(line.begin(), line.end(), line.begin(), tolower);
+    static const char* prefix = "libraries:";
+    static const int prefixLen = strlen(prefix);
+    if (0 == line.compare(0, prefixLen, prefix)) {
+      char* lineCopy = strdup(line.c_str() + prefixLen);
+      char* start = lineCopy;
+      while (char* tok = nsCRT::strtok(start, ", ", &start)) {
+        for (int i = 0; i < whitelistLen; i++) {
+          if (0 == strcmp(whitelist[i], tok)) {
+            LoadLibraryA(tok);
+            break;
+          }
+        }
+      }
+      free(lineCopy);
+      break;
+    }
+  } while (!stream.eof());
+
+  return true;
+}
+#endif
+
 bool
 GMPChild::LoadPluginLibrary(const std::string& aPluginPath)
 {
 #if defined(XP_MACOSX) && defined(MOZ_GMP_SANDBOX)
   nsAutoCString nativePath;
   nativePath.Assign(mPluginBinaryPath);
 
   mLib = PR_LoadLibrary(nativePath.get());
--- a/content/media/gmp/GMPChild.h
+++ b/content/media/gmp/GMPChild.h
@@ -29,16 +29,19 @@ public:
   void OnChannelConnected(int32_t aPid);
 #endif
 
   bool Init(const std::string& aPluginPath,
             base::ProcessHandle aParentProcessHandle,
             MessageLoop* aIOLoop,
             IPC::Channel* aChannel);
   bool LoadPluginLibrary(const std::string& aPluginPath);
+#ifdef XP_WIN
+  bool PreLoadLibraries(const std::string& aPluginPath);
+#endif
   MessageLoop* GMPMessageLoop();
 
   // Main thread only.
   GMPTimerChild* GetGMPTimers();
   GMPStorageChild* GetGMPStorage();
 
   // GMPSharedMem
   virtual void CheckThread() MOZ_OVERRIDE;
--- a/content/media/omx/RtspOmxReader.cpp
+++ b/content/media/omx/RtspOmxReader.cpp
@@ -36,16 +36,17 @@ nsresult RtspOmxReader::Seek(int64_t aTi
                              int64_t aEndTime, int64_t aCurrentTime)
 {
   // The seek function of Rtsp is time-based, we call the SeekTime function in
   // RtspMediaResource. The SeekTime function finally send a seek command to
   // Rtsp stream server through network and also clear the buffer data in
   // RtspMediaResource.
   if (mRtspResource) {
     mRtspResource->SeekTime(aTime);
+    mRtspResource->EnablePlayoutDelay();
   }
 
   // Call |MediaOmxReader::Seek| to notify the OMX decoder we are performing a
   // seek operation. The function will clear the |mVideoQueue| and |mAudioQueue|
   // that store the decoded data and also call the |DecodeToTarget| to pass
   // the seek time to OMX a/v decoders.
   return MediaOmxReader::Seek(aTime, aStartTime, aEndTime, aCurrentTime);
 }
@@ -75,9 +76,27 @@ void RtspOmxReader::EnsureActive() {
     }
     mRtspResource->SetSuspend(false);
   }
 
   // Call parent class to set OMXCodec active.
   MediaOmxReader::EnsureActive();
 }
 
+nsresult RtspOmxReader::ReadMetadata(MediaInfo *aInfo, MetadataTags **aTags)
+{
+  // Send a PLAY command to the RTSP server before reading metadata.
+  // Because we might need some decoded samples to ensure we have configuration.
+  mRtspResource->DisablePlayoutDelay();
+  EnsureActive();
+  nsresult rv = MediaOmxReader::ReadMetadata(aInfo, aTags);
+
+  if (rv == NS_OK && !IsWaitingMediaResources()) {
+    mRtspResource->EnablePlayoutDelay();
+  } else if (IsWaitingMediaResources()) {
+    // Send a PAUSE to the RTSP server because the underlying media resource is
+    // not ready.
+    SetIdle();
+  }
+  return rv;
+}
+
 } // namespace mozilla
--- a/content/media/omx/RtspOmxReader.h
+++ b/content/media/omx/RtspOmxReader.h
@@ -60,16 +60,19 @@ public:
   // data so the |GetBuffered| function can retrieve useful time ranges.
   virtual nsresult GetBuffered(mozilla::dom::TimeRanges* aBuffered,
                                int64_t aStartTime) MOZ_FINAL MOZ_OVERRIDE {
     return NS_OK;
   }
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
+  virtual nsresult ReadMetadata(MediaInfo *aInfo, MetadataTags **aTags)
+    MOZ_FINAL MOZ_OVERRIDE;
+
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
   // And MediaDecoderStateMachine holds this RtspOmxReader.
   RtspMediaResource* mRtspResource;
 };
 
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -17,17 +17,17 @@
 # throws an error (and does not cause a crash or hang), just add it to
 # gErrorTests in manifest.js.
 
 # To test for a specific bug in handling a specific resource type, make the
 # test first check canPlayType for the type, and if it's not supported, just
 # do ok(true, "Type not supported") and stop the test.
 
 [DEFAULT]
-skip-if = buildapp == 'mulet'
+skip-if = buildapp == 'mulet' || (os == 'win' && contentSandbox != 'off') # contentSandbox(Bug 1042735)
 support-files =
   320x240.ogv
   320x240.ogv^headers^
   448636.ogv
   448636.ogv^headers^
   VID_0001.ogg
   VID_0001.ogg^headers^
   allowed.sjs
@@ -362,17 +362,16 @@ skip-if = toolkit == 'android' # bug 608
 [test_invalid_reject_play.html]
 [test_invalid_seek.html]
 [test_load.html]
 [test_load_candidates.html]
 [test_load_same_resource.html]
 [test_load_source.html]
 [test_loop.html]
 [test_media_selection.html]
-skip-if = toolkit == 'gonk' && !debug # bug 1021677
 [test_media_sniffer.html]
 [test_mediarecorder_avoid_recursion.html]
 [test_mediarecorder_creation.html]
 [test_mediarecorder_creation_fail.html]
 [test_mediarecorder_getencodeddata.html]
 [test_mediarecorder_record_4ch_audiocontext.html]
 [test_mediarecorder_record_audiocontext.html]
 [test_mediarecorder_record_audiocontext_mlk.html]
@@ -457,17 +456,16 @@ skip-if = true # bug 1021673
 [test_source_write.html]
 [test_standalone.html]
 [test_streams_autoplay.html]
 [test_streams_element_capture.html]
 skip-if = e10s && os == 'win' # Bug 1065881 - Crash on child process shutdown in ShadowLayerForwarder::InWorkerThread
 [test_streams_element_capture_createObjectURL.html]
 [test_streams_element_capture_playback.html]
 [test_streams_element_capture_reset.html]
-skip-if = buildapp == 'b2g' # bug 901102
 [test_streams_gc.html]
 skip-if = buildapp == 'b2g' # bug 1021682
 [test_streams_srcObject.html]
 [test_streams_tracks.html]
 [test_texttrack.html]
 [test_texttrackcue.html]
 [test_texttracklist.html]
 [test_texttrackregion.html]
--- a/content/media/test/test_media_selection.html
+++ b/content/media/test/test_media_selection.html
@@ -4,16 +4,21 @@
   <title>Media test: media selection</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
   <script type="application/javascript" src="manifest.js"></script>
 </head>
 <body>
 <pre id="test">
 <script class="testbody" type="text/javascript">
+//longer timeout for sometimes B2G emulator runs very slowly
+if (SpecialPowers.Services.appinfo.name == "B2G") {
+  SimpleTest.requestLongerTimeout(3);
+}
+
 var manager = new MediaTestManager;
 
 function maketest(attach_media, name, type, check_metadata) {
   return function (token) {
     var e = document.createElement('video');
     e.preload = "metadata";
     manager.started(token);
     var errorRun = false;
--- a/content/media/test/test_playback.html
+++ b/content/media/test/test_playback.html
@@ -16,66 +16,101 @@ if (SpecialPowers.Services.appinfo.name 
 }
 
 var manager = new MediaTestManager;
 
 function startTest(test, token) {
   var v = document.createElement('video');
   v.preload = "metadata";
   v.token = token;
+  v.prevTime = 0;
   manager.started(token);
 
   v.src = test.name;
   v.name = test.name;
+
   var check = function(test, v) { return function() {
-    is(test.name, v.name, "Name should match test.name #1");
+    is(test.name, v.name, test.name + ": Name should match #1");
     checkMetadata(test.name, v, test);
   }}(test, v);
+
   var noLoad = function(test, v) { return function() {
     ok(false, test.name + " should not fire 'load' event");
   }}(test, v);
+
+  // Used to cancel timer callback.
+  var timer = null;
+
+  var cancelTimer = function() {
+    if (timer) {
+      clearTimeout(timer);
+    }
+  }
+
+  var finish = function() {
+    cancelTimer();
+    v.finished = true;
+    v.removeEventListener("timeupdate", timeUpdate, false);
+    removeNodeAndSource(v);
+    manager.finished(v.token);
+  }
+
+  // We should get "ended" and "suspend" events to finish the test.
+  var mayFinish = function() {
+    if (v.seenEnded && v.seenSuspend) {
+      finish();
+    }
+  }
+
+  var onTimeout = function() {
+    ok(v.seenEnded, v.name + " timed out, should get 'ended'");
+    ok(v.seenSuspend, v.name + " timed out, should get 'suspend'");
+    finish();
+  }
+
+  // Check if we time out in waiting for some events.
+  var registerTimer = function() {
+    cancelTimer();
+    timer = setTimeout(onTimeout, 30000);
+  }
+
   var checkEnded = function(test, v) { return function() {
-    if (test.duration) {
-      ok(Math.abs(v.currentTime - test.duration) < 0.1,
-         test.name + " current time at end: " + v.currentTime + " should be: " + test.duration);
-    }
-    is(test.name, v.name, "Name should match test.name #2");
+    is(test.name, v.name, test.name + ": Name should match #2");
+    checkMetadata(test.name, v, test);
     is(v.readyState, v.HAVE_CURRENT_DATA, test.name + " checking readyState");
     ok(v.readyState != v.NETWORK_LOADED, test.name + " shouldn't report NETWORK_LOADED");
     ok(v.ended, test.name + " checking playback has ended");
-    if (v.ended && v.seenSuspend && !v.finished) {
-      v.finished = true;
-      v.removeEventListener("timeupdate", timeUpdate, false);
-      removeNodeAndSource(v);
-      manager.finished(v.token);
+    ok(!v.finished, test.name + " shouldn't be finished");
+    ok(!v.seenEnded, test.name + " shouldn't be ended");
+
+    v.seenEnded = true;
+    registerTimer();
+    mayFinish();
+  }}(test, v);
+
+  var checkSuspended = function(test, v) { return function() {
+    if (v.seenSuspend) {
+      return;
     }
-  }}(test, v);
-  var checkSuspended = function(test, v) { return function() {
-    is(test.name, v.name, "Name should match test.name #3");
-    if (v.seenSuspend)
-      return;
+    is(test.name, v.name, test.name + ": Name should match #3");
 
     v.seenSuspend = true;
-    ok(true, test.name + " got suspend");
-    if (v.ended && !v.finished) {
-      v.finished = true;
-      v.removeEventListener("timeupdate", timeUpdate, false);
-      removeNodeAndSource(v);
-      manager.finished(v.token);
-    }
+    registerTimer();
+    mayFinish();
   }}(test, v);
-  v.prevTime = 0;
+
   var timeUpdate = function(test, v) { return function() {
-    is(test.name, v.name, "Name should match test.name #4");
-    checkMetadata(test.name, v, test);
-    ok(v.prevTime <= v.currentTime,
-       test.name + " time should run forwards: p=" +
-       v.prevTime + " c=" + v.currentTime);
+    if (v.prevTime > v.currentTime) {
+      ok(false, test.name + " time should run forwards: p=" +
+                v.prevTime + " c=" + v.currentTime);
+    }
     v.prevTime = v.currentTime;
+    registerTimer();
   }}(test, v);
+
   v.addEventListener("load", noLoad, false);
   v.addEventListener("loadedmetadata", check, false);
   v.addEventListener("timeupdate", timeUpdate, false);
 
   // We should get "ended" and "suspend" events for every resource
   v.addEventListener("ended", checkEnded, false);
   v.addEventListener("suspend", checkSuspended, false);
 
--- a/content/media/webaudio/AudioBuffer.cpp
+++ b/content/media/webaudio/AudioBuffer.cpp
@@ -110,17 +110,18 @@ AudioBuffer::RestoreJSChannelData(JSCont
       // The following code first zeroes the array and then copies our data
       // into it. We could avoid this with additional JS APIs to construct
       // an array (or ArrayBuffer) containing initial data.
       JS::Rooted<JSObject*> array(aJSContext,
                                   JS_NewFloat32Array(aJSContext, mLength));
       if (!array) {
         return false;
       }
-      memcpy(JS_GetFloat32ArrayData(array), data, sizeof(float)*mLength);
+      JS::AutoCheckCannotGC nogc;
+      mozilla::PodCopy(JS_GetFloat32ArrayData(array, nogc), data, mLength);
       mJSChannels[i] = array;
     }
 
     mSharedChannels = nullptr;
   }
 
   return true;
 }
@@ -141,19 +142,20 @@ AudioBuffer::CopyFromChannel(const Float
   }
 
   if (!mSharedChannels && JS_GetTypedArrayLength(mJSChannels[aChannelNumber]) != mLength) {
     // The array was probably neutered
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
+  JS::AutoCheckCannotGC nogc;
   const float* sourceData = mSharedChannels ?
     mSharedChannels->GetData(aChannelNumber) :
-    JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]);
+    JS_GetFloat32ArrayData(mJSChannels[aChannelNumber], nogc);
   PodMove(aDestination.Data(), sourceData + aStartInChannel, length);
 }
 
 void
 AudioBuffer::CopyToChannel(JSContext* aJSContext, const Float32Array& aSource,
                            uint32_t aChannelNumber, uint32_t aStartInChannel,
                            ErrorResult& aRv)
 {
@@ -174,26 +176,28 @@ AudioBuffer::CopyToChannel(JSContext* aJ
     return;
   }
 
   if (!RestoreJSChannelData(aJSContext)) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
-  PodMove(JS_GetFloat32ArrayData(mJSChannels[aChannelNumber]) + aStartInChannel,
+  JS::AutoCheckCannotGC nogc;
+  PodMove(JS_GetFloat32ArrayData(mJSChannels[aChannelNumber], nogc) + aStartInChannel,
           aSource.Data(), length);
 }
 
 void
 AudioBuffer::SetRawChannelContents(uint32_t aChannel, float* aContents)
 {
   MOZ_ASSERT(!GetWrapperPreserveColor() && !mSharedChannels,
              "The AudioBuffer object should not have been handed to JS or have C++ callers neuter its typed array");
-  PodCopy(JS_GetFloat32ArrayData(mJSChannels[aChannel]), aContents, mLength);
+  JS::AutoCheckCannotGC nogc;
+  PodCopy(JS_GetFloat32ArrayData(mJSChannels[aChannel], nogc), aContents, mLength);
 }
 
 void
 AudioBuffer::GetChannelData(JSContext* aJSContext, uint32_t aChannel,
                             JS::MutableHandle<JSObject*> aRetval,
                             ErrorResult& aRv)
 {
   if (aChannel >= NumberOfChannels()) {
--- a/content/media/webaudio/test/mochitest.ini
+++ b/content/media/webaudio/test/mochitest.ini
@@ -1,10 +1,10 @@
 [DEFAULT]
-skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && (toolkit != 'gonk' || debug)) #b2g-debug,b2g-desktop(bug 916135)
+skip-if = ((buildapp == 'mulet' || buildapp == 'b2g') && (toolkit != 'gonk' || debug)) || (os == 'win' && contentSandbox != 'off') #b2g-debug,b2g-desktop(bug 916135); contentSandbox(Bug 1042735)
 support-files =
   audio-expected.wav
   audio-mono-expected-2.wav
   audio-mono-expected.wav
   audio-quad.wav
   audio.ogv
   audioBufferSourceNodeNeutered_worker.js
   invalid.txt
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -26,39 +26,40 @@ using mozilla::NesteggPacketHolder;
 template <>
 class nsAutoRefTraits<NesteggPacketHolder> :
   public nsPointerRefTraits<NesteggPacketHolder>
 {
 public:
   static void Release(NesteggPacketHolder* aHolder) { delete aHolder; }
 };
 
-namespace mozilla {
-
-using namespace gfx;
-using namespace layers;
-
 // Un-comment to enable logging of seek bisections.
 //#define SEEK_LOGGING
 
 #ifdef PR_LOGGING
 #include "prprf.h"
-extern PRLogModuleInfo* gMediaDecoderLog;
-PRLogModuleInfo* gNesteggLog;
 #define LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
 #ifdef SEEK_LOGGING
 #define SEEK_LOG(type, msg) PR_LOG(gMediaDecoderLog, type, msg)
 #else
 #define SEEK_LOG(type, msg)
 #endif
 #else
 #define LOG(type, msg)
 #define SEEK_LOG(type, msg)
 #endif
 
+namespace mozilla {
+
+using namespace gfx;
+using namespace layers;
+
+extern PRLogModuleInfo* gMediaDecoderLog;
+PRLogModuleInfo* gNesteggLog;
+
 static const unsigned NS_PER_USEC = 1000;
 static const double NS_PER_S = 1e9;
 
 // Functions for reading and seeking using MediaResource required for
 // nestegg_io. The 'user data' passed to these functions is the
 // decoder from which the media resource is obtained.
 static int webm_read(void *aBuffer, size_t aLength, void *aUserData)
 {
--- a/content/media/webspeech/synth/nsSpeechTask.cpp
+++ b/content/media/webspeech/synth/nsSpeechTask.cpp
@@ -157,16 +157,30 @@ nsSpeechTask::Setup(nsISpeechTaskCallbac
   AudioSegment* segment = new AudioSegment();
   mStream->AddTrack(1, aRate, 0, segment);
   mStream->AddAudioOutput(this);
   mStream->SetAudioOutputVolume(this, mVolume);
 
   return NS_OK;
 }
 
+static nsRefPtr<mozilla::SharedBuffer>
+makeSamples(int16_t* aData, uint32_t aDataLen)
+{
+  nsRefPtr<mozilla::SharedBuffer> samples =
+    SharedBuffer::Create(aDataLen * sizeof(int16_t));
+  int16_t* frames = static_cast<int16_t*>(samples->Data());
+
+  for (uint32_t i = 0; i < aDataLen; i++) {
+    frames[i] = aData[i];
+  }
+
+  return samples;
+}
+
 NS_IMETHODIMP
 nsSpeechTask::SendAudio(JS::Handle<JS::Value> aData, JS::Handle<JS::Value> aLandmarks,
                         JSContext* aCx)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
 
   NS_ENSURE_TRUE(mStream, NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_FALSE(mStream->IsDestroyed(), NS_ERROR_NOT_AVAILABLE);
@@ -189,18 +203,23 @@ nsSpeechTask::SendAudio(JS::Handle<JS::V
   } else if (JS_IsArrayObject(aCx, darray)) {
     tsrc = JS_NewInt16ArrayFromArray(aCx, darray);
   }
 
   if (!tsrc) {
     return NS_ERROR_DOM_TYPE_MISMATCH_ERR;
   }
 
-  SendAudioImpl(JS_GetInt16ArrayData(tsrc),
-                JS_GetTypedArrayLength(tsrc));
+  uint32_t dataLen = JS_GetTypedArrayLength(tsrc);
+  nsRefPtr<mozilla::SharedBuffer> samples;
+  {
+    JS::AutoCheckCannotGC nogc;
+    samples = makeSamples(JS_GetInt16ArrayData(tsrc, nogc), dataLen);
+  }
+  SendAudioImpl(samples, dataLen);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsSpeechTask::SendAudioNative(int16_t* aData, uint32_t aDataLen)
 {
   MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
@@ -209,41 +228,34 @@ nsSpeechTask::SendAudioNative(int16_t* a
   NS_ENSURE_FALSE(mStream->IsDestroyed(), NS_ERROR_NOT_AVAILABLE);
   NS_ENSURE_TRUE(mChannels, NS_ERROR_FAILURE);
 
   if (mIndirectAudio) {
     NS_WARNING("Can't call SendAudio from an indirect audio speech service.");
     return NS_ERROR_FAILURE;
   }
 
-  SendAudioImpl(aData, aDataLen);
+  nsRefPtr<mozilla::SharedBuffer> samples = makeSamples(aData, aDataLen);
+  SendAudioImpl(samples, aDataLen);
 
   return NS_OK;
 }
 
 void
-nsSpeechTask::SendAudioImpl(int16_t* aData, uint32_t aDataLen)
+nsSpeechTask::SendAudioImpl(nsRefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen)
 {
   if (aDataLen == 0) {
     mStream->EndAllTrackAndFinish();
     return;
   }
 
-  nsRefPtr<mozilla::SharedBuffer> samples =
-    SharedBuffer::Create(aDataLen * sizeof(int16_t));
-  int16_t* frames = static_cast<int16_t*>(samples->Data());
-
-  for (uint32_t i = 0; i < aDataLen; i++) {
-    frames[i] = aData[i];
-  }
-
   AudioSegment segment;
   nsAutoTArray<const int16_t*, 1> channelData;
-  channelData.AppendElement(frames);
-  segment.AppendFrames(samples.forget(), channelData, aDataLen);
+  channelData.AppendElement(static_cast<int16_t*>(aSamples->Data()));
+  segment.AppendFrames(aSamples.forget(), channelData, aDataLen);
   mStream->AppendToTrack(1, &segment);
   mStream->AdvanceKnownTracksTime(STREAM_TIME_MAX);
 }
 
 NS_IMETHODIMP
 nsSpeechTask::DispatchStart()
 {
   if (!mIndirectAudio) {
--- a/content/media/webspeech/synth/nsSpeechTask.h
+++ b/content/media/webspeech/synth/nsSpeechTask.h
@@ -69,17 +69,17 @@ protected:
 
   float mVolume;
 
   nsString mText;
 
 private:
   void End();
 
-  void SendAudioImpl(int16_t* aData, uint32_t aDataLen);
+  void SendAudioImpl(nsRefPtr<mozilla::SharedBuffer>& aSamples, uint32_t aDataLen);
 
   nsRefPtr<SourceMediaStream> mStream;
 
   nsCOMPtr<nsISpeechTaskCallback> mCallback;
 
   uint32_t mChannels;
 
   nsRefPtr<SpeechSynthesis> mSpeechSynthesis;
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -4372,32 +4372,32 @@ nsDocShell::GetWindow()
 {
   NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr);
   return mScriptGlobal;
 }
 
 NS_IMETHODIMP
 nsDocShell::SetDeviceSizeIsPageSize(bool aValue)
 {
-    if (mDeviceSizeIsPageSize != aValue) {
-      mDeviceSizeIsPageSize = aValue;
-      nsRefPtr<nsPresContext> presContext;
-      GetPresContext(getter_AddRefs(presContext));
-      if (presContext) {
-          presContext->MediaFeatureValuesChanged(presContext->eAlwaysRebuildStyle);
-      }
-    }
-    return NS_OK;
+  if (mDeviceSizeIsPageSize != aValue) {
+    mDeviceSizeIsPageSize = aValue;
+    nsRefPtr<nsPresContext> presContext;
+    GetPresContext(getter_AddRefs(presContext));
+    if (presContext) {
+      presContext->MediaFeatureValuesChanged(nsRestyleHint(0));
+    }
+  }
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocShell::GetDeviceSizeIsPageSize(bool* aValue)
 {
-    *aValue = mDeviceSizeIsPageSize;
-    return NS_OK;
+  *aValue = mDeviceSizeIsPageSize;
+  return NS_OK;
 }
 
 void
 nsDocShell::ClearFrameHistory(nsISHEntry* aEntry)
 {
   nsCOMPtr<nsISHContainer> shcontainer = do_QueryInterface(aEntry);
   nsCOMPtr<nsISHistory> rootSH;
   GetRootSessionHistory(getter_AddRefs(rootSH));
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -162,17 +162,17 @@ nsHistory::Go(int32_t aDelta, ErrorResul
       // trick to work around gecko reflow bugs, and this should have
       // the same effect.
 
       nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
 
       nsIPresShell *shell;
       nsPresContext *pcx;
       if (doc && (shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
-        pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW);
+        pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
       }
 
       return;
     }
   }
 
   nsCOMPtr<nsISHistory> session_history = GetSessionHistory();
   nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(session_history));
--- a/dom/base/nsLocation.cpp
+++ b/dom/base/nsLocation.cpp
@@ -918,17 +918,17 @@ nsLocation::Reload(bool aForceget)
     // page since some sites may use this trick to work around gecko
     // reflow bugs, and this should have the same effect.
 
     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
 
     nsIPresShell *shell;
     nsPresContext *pcx;
     if (doc && (shell = doc->GetShell()) && (pcx = shell->GetPresContext())) {
-      pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW);
+      pcx->RebuildAllStyleData(NS_STYLE_HINT_REFLOW, eRestyle_Subtree);
     }
 
     return NS_OK;
   }
 
   if (webNav) {
     uint32_t reloadFlags = nsIWebNavigation::LOAD_FLAGS_NONE;
 
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -134,17 +134,17 @@ public:
 
 private:
   TypedArray_base(const TypedArray_base&) MOZ_DELETE;
 };
 
 
 template<typename T,
          JSObject* UnwrapArray(JSObject*),
-         T* GetData(JSObject*),
+         T* GetData(JSObject*, const JS::AutoCheckCannotGC&),
          void GetLengthAndData(JSObject*, uint32_t*, T**),
          JSObject* CreateNew(JSContext*, uint32_t)>
 struct TypedArray : public TypedArray_base<T, UnwrapArray, GetLengthAndData> {
 private:
   typedef TypedArray_base<T, UnwrapArray, GetLengthAndData> Base;
 
 public:
   TypedArray()
@@ -176,17 +176,18 @@ public:
 private:
   static inline JSObject*
   CreateCommon(JSContext* cx, uint32_t length, const T* data) {
     JSObject* obj = CreateNew(cx, length);
     if (!obj) {
       return nullptr;
     }
     if (data) {
-      T* buf = static_cast<T*>(GetData(obj));
+      JS::AutoCheckCannotGC nogc;
+      T* buf = static_cast<T*>(GetData(obj, nogc));
       memcpy(buf, data, length*sizeof(T));
     }
     return obj;
   }
 
   TypedArray(const TypedArray&) MOZ_DELETE;
 };
 
--- a/dom/browser-element/mochitest/browserElement_CopyPaste.js
+++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js
@@ -87,59 +87,49 @@ function dispatchTest(e) {
       pasteData = "from parent ";
       iframe.src = "data:text/html,<html><body>" +
                    "<input type='text' id='text' value='" + defaultData + "'>" +
                    "</body>" +
                    "</html>";
       stateMeaning = " (test: <input type=text>)";
       focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();";
       break;
-    case 2: // test for input password
-      defaultData = "Test for selection change event";
-      pasteData = "from parent ";
-      iframe.src = "data:text/html,<html><body>" +
-                   "<input type='password' id='text' value='" + defaultData + "'>" +
-                   "</body>" +
-                   "</html>";
-      stateMeaning = " (test: <input type=password>)";
-      focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();";
-      break;
-    case 3: // test for input number
+    case 2: // test for input number
       defaultData = "12345";
       pasteData = "67890";
       iframe.src = "data:text/html,<html><body>" +
                    "<input type='number' id='text' value='" + defaultData + "'>" +
                    "</body>" +
                    "</html>";
       stateMeaning = " (test: <input type=number>)";
       focusScript = "var elt=content.document.getElementById('text');elt.focus();elt.select();";
       break;
-    case 4: // test for div contenteditable
+    case 3: // test for div contenteditable
       defaultData = "Test for selection change event";
       pasteData = "from parent ";
       iframe.src = "data:text/html,<html><body>" +
                    "<div contenteditable='true' id='text'>" + defaultData + "</div>" +
                    "</body>" +
                    "</html>";
       stateMeaning = " (test: content editable div)";
       focusScript = "var elt=content.document.getElementById('text');elt.focus();";
       break;
-    case 5: // test for normal div
+    case 4: // test for normal div
       SimpleTest.finish();
       return;
       defaultData = "Test for selection change event";
       pasteData = "from parent ";
       iframe.src = "data:text/html,<html><body>" +
                    "<div id='text'>" + defaultData + "</div>" +
                    "</body>" +
                    "</html>";
       stateMeaning = " (test: normal div)";
       focusScript = "var elt=content.document.getElementById('text');elt.focus();";
       break;
-    case 6: // test for normal div with designMode:on
+    case 5: // test for normal div with designMode:on
       defaultData = "Test for selection change event";
       pasteData = "from parent ";
       iframe.src = "data:text/html,<html><body id='text'>" +
                    defaultData +
                    "</body>" +
                    "<script>document.designMode='on';</script>" +
                    "</html>";
       stateMeaning = " (test: normal div with designMode:on)";
@@ -187,43 +177,35 @@ function testCopy1(e) {
     nextTest(true);
   }
 
   let fail = function() {
     nextTest(false);
   }
 
   let compareData = defaultData;
-  if (state == 2) {
-    // In password case, we just check length of text at clipboard is equal
-    // to length of defaultData
-    compareData = function(clipboardText) {
-      return clipboardText.length == defaultData.length;
-    };
-  }
-
   SimpleTest.waitForClipboard(compareData, setup, success, fail);
 }
 
 function testPaste1(e) {
   // Next test paste command, first we copy to global clipboard in parent side.
   // Then paste it to child side.
   copyToClipboard(pasteData);
 
   doCommand("paste");
   SimpleTest.executeSoon(function() { testPaste2(e); });
 }
 
 function testPaste2(e) {
   mm.addMessageListener('content-text', function messageforpaste(msg) {
     mm.removeMessageListener('content-text', messageforpaste);
-    if (state == 5) {
+    if (state == 4) {
       // normal div cannot paste, so the content remain unchange
       ok(SpecialPowers.wrap(msg).json === defaultData, "paste command works" + stateMeaning);
-    } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) {
+    } else if (state == 3 && browserElementTestHelpers.getOOPByDefaultPref()) {
       // Something weird when we doCommand with content editable element in OOP. Mark this case as todo
       todo(false, "paste command works" + stateMeaning);
     } else {
       ok(SpecialPowers.wrap(msg).json === pasteData, "paste command works" + stateMeaning);
     }
     SimpleTest.executeSoon(function() { testCut1(e); });
   });
 
@@ -234,17 +216,17 @@ function testCut1(e) {
   // Clean clipboard first
   copyToClipboard("");
   let setup = function() {
     doCommand("selectall");
     doCommand("cut");
   };
 
   let nextTest = function(success) {
-    if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) {
+    if (state == 3 && browserElementTestHelpers.getOOPByDefaultPref()) {
       // Something weird when we doCommand with content editable element in OOP.
       todo(false, "cut function works" + stateMeaning);
     } else {
       ok(success, "cut function works" + stateMeaning);
     }
     SimpleTest.executeSoon(function() { testCut2(e); });
   };
 
@@ -252,38 +234,32 @@ function testCut1(e) {
     nextTest(true);
   }
 
   let fail = function() {
     nextTest(false);
   }
 
   let compareData = pasteData;
-  if (state == 2) {
-    // In password case, we just check length of text at clipboard is equal
-    // to length of pasteData
-    compareData = function(clipboardText) {
-      return clipboardText.length == pasteData.length;
-    };
-  } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) {
+  if (state == 3 && browserElementTestHelpers.getOOPByDefaultPref()) {
     // Something weird when we doCommand with content editable element in OOP.
     // Always true in this case
     compareData = function() { return true; }
   }
 
   SimpleTest.waitForClipboard(compareData, setup, success, fail);
 }
 
 function testCut2(e) {
   mm.addMessageListener('content-text', function messageforcut(msg) {
     mm.removeMessageListener('content-text', messageforcut);
     // normal div cannot cut
-    if (state == 5) {
+    if (state == 4) {
       ok(SpecialPowers.wrap(msg).json !== "", "cut command works" + stateMeaning);
-    } else if (state == 4 && browserElementTestHelpers.getOOPByDefaultPref()) {
+    } else if (state == 3 && browserElementTestHelpers.getOOPByDefaultPref()) {
       // Something weird when we doCommand with content editable element in OOP. Mark this case as todo
       todo(false, "cut command works" + stateMeaning);
     } else {
       ok(SpecialPowers.wrap(msg).json === "", "cut command works" + stateMeaning);
     }
 
     state++;
     dispatchTest(e);
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -4510,28 +4510,34 @@ CanvasRenderingContext2D::GetImageDataAr
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
   if (mZero) {
     *aRetval = darray;
     return NS_OK;
   }
 
-  uint8_t* data = JS_GetUint8ClampedArrayData(darray);
-
   IntRect dstWriteRect = srcReadRect;
   dstWriteRect.MoveBy(-aX, -aY);
 
-  uint8_t* src = data;
-  uint32_t srcStride = aWidth * 4;
+  uint8_t* src;
+  uint32_t srcStride;
+
   if (readback) {
     srcStride = readback->Stride();
     src = readback->GetData() + srcReadRect.y * srcStride + srcReadRect.x * 4;
   }
 
+  JS::AutoCheckCannotGC nogc;
+  uint8_t* data = JS_GetUint8ClampedArrayData(darray, nogc);
+  if (!readback) {
+    src = data;
+    srcStride = aWidth * 4;
+  }
+
   // NOTE! dst is the same as src, and this relies on reading
   // from src and advancing that ptr before writing to dst.
   // NOTE! I'm not sure that it is, I think this comment might have been
   // inherited from Thebes canvas and is no longer true
   uint8_t* dst = data + dstWriteRect.y * (aWidth * 4) + dstWriteRect.x * 4;
 
   if (mOpaque) {
     for (int32_t j = 0; j < dstWriteRect.height; ++j) {
--- a/dom/fmradio/FMRadio.cpp
+++ b/dom/fmradio/FMRadio.cpp
@@ -338,17 +338,18 @@ void
 FMRadio::GetRdsgroup(JSContext* cx, JS::MutableHandle<JSObject*> retval)
 {
   uint64_t group;
   if (!IFMRadioService::Singleton()->GetRdsgroup(group)) {
     return;
   }
 
   JSObject *rdsgroup = Uint16Array::Create(cx, this, 4);
-  uint16_t *data = JS_GetUint16ArrayData(rdsgroup);
+  JS::AutoCheckCannotGC nogc;
+  uint16_t *data = JS_GetUint16ArrayData(rdsgroup, nogc);
   data[3] = group & 0xFFFF;
   group >>= 16;
   data[2] = group & 0xFFFF;
   group >>= 16;
   data[1] = group & 0xFFFF;
   group >>= 16;
   data[0] = group & 0xFFFF;
 
--- a/dom/indexedDB/test/unit/xpcshell-shared.ini
+++ b/dom/indexedDB/test/unit/xpcshell-shared.ini
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 [DEFAULT]
-skip-if = toolkit == 'android' || toolkit == 'gonk'
+skip-if = toolkit == 'gonk'
 
 [test_add_put.js]
 [test_add_twice_failure.js]
 [test_advance.js]
 [test_autoIncrement.js]
 [test_autoIncrement_indexes.js]
 [test_blocked_order.js]
 [test_clear.js]
@@ -20,16 +20,17 @@ skip-if = toolkit == 'android' || toolki
 [test_cursor_mutation.js]
 [test_cursor_update_updates_indexes.js]
 [test_cursors.js]
 [test_deleteDatabase.js]
 [test_deleteDatabase_interactions.js]
 [test_event_source.js]
 [test_getAll.js]
 [test_globalObjects_other.js]
+skip-if = toolkit == 'android' # bug 1079278
 [test_globalObjects_xpc.js]
 [test_global_data.js]
 [test_index_empty_keyPath.js]
 [test_index_getAll.js]
 [test_index_getAllObjects.js]
 [test_index_object_cursors.js]
 [test_index_update_delete.js]
 [test_indexes.js]
--- a/dom/inputmethod/forms.js
+++ b/dom/inputmethod/forms.js
@@ -580,21 +580,18 @@ let FormAssistant = {
           });
         }
         break;
       }
 
       case "Forms:ReplaceSurroundingText": {
         CompositionManager.endComposition('');
 
-        let selectionRange = getSelectionRange(target);
         if (!replaceSurroundingText(target,
                                     json.text,
-                                    selectionRange[0],
-                                    selectionRange[1],
                                     json.offset,
                                     json.length)) {
           if (json.requestId) {
             sendAsyncMessage("Forms:ReplaceSurroundingText:Result:Error", {
               requestId: json.requestId,
               error: "failed"
             });
           }
@@ -1134,41 +1131,60 @@ function getPlaintextEditor(element) {
     }
   }
   if (editor) {
     editor.QueryInterface(Ci.nsIPlaintextEditor);
   }
   return editor;
 }
 
-function replaceSurroundingText(element, text, selectionStart, selectionEnd,
-                                offset, length) {
+function replaceSurroundingText(element, text, offset, length) {
   let editor = FormAssistant.editor;
   if (!editor) {
     return false;
   }
 
   // Check the parameters.
-  let start = selectionStart + offset;
-  if (start < 0) {
-    start = 0;
-  }
   if (length < 0) {
     length = 0;
   }
-  let end = start + length;
 
-  if (selectionStart != start || selectionEnd != end) {
-    // Change selection range before replacing.
-    if (!setSelectionRange(element, start, end)) {
-      return false;
+  // Change selection range before replacing. For content editable element,
+  // searching the node for setting selection range is not needed when the
+  // selection is collapsed within a text node.
+  let fastPathHit = false;
+  if (!isPlainTextField(element)) {
+    let sel = element.ownerDocument.defaultView.getSelection();
+    let node = sel.anchorNode;
+    if (sel.isCollapsed && node && node.nodeType == 3 /* TEXT_NODE */) {
+      let start = sel.anchorOffset + offset;
+      let end = start + length;
+      // Fallback to setSelectionRange() if the replacement span multiple nodes.
+      if (start >= 0 && end <= node.textContent.length) {
+        fastPathHit = true;
+        sel.collapse(node, start);
+        sel.extend(node, end);
+      }
+    }
+  }
+  if (!fastPathHit) {
+    let range = getSelectionRange(element);
+    let start = range[0] + offset;
+    if (start < 0) {
+      start = 0;
+    }
+    let end = start + length;
+    if (start != range[0] || end != range[1]) {
+      if (!setSelectionRange(element, start, end)) {
+        return false;
+      }
     }
   }
 
-  if (start != end) {
+  if (length) {
     // Delete the selected text.
     editor.deleteSelection(Ci.nsIEditor.ePrevious, Ci.nsIEditor.eStrip);
   }
 
   if (text) {
     // We don't use CR but LF
     // see https://bugzilla.mozilla.org/show_bug.cgi?id=902847
     text = text.replace(/\r/g, '\n');
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/file_test_sms_app_1066515.html
@@ -0,0 +1,14 @@
+<!DOCTYPE HTML>
+<html>
+<body>
+  <div id="messages-input" x-inputmode="-moz-sms" contenteditable="true"
+    autofocus="autofocus">fxos<br>hello <b>world</b></div>
+  <script type="application/javascript;version=1.7">
+    let input = document.getElementById('messages-input');
+    input.focus();
+  </script>
+</body>
+</html>
+ </div>
+</body>
+</html>
--- a/dom/inputmethod/mochitest/inputmethod_common.js
+++ b/dom/inputmethod/mochitest/inputmethod_common.js
@@ -26,10 +26,11 @@ function inputmethod_setup(callback) {
     ];
     SpecialPowers.pushPrefEnv({set: prefs}, function() {
       SimpleTest.waitForFocus(callback);
     });
   });
 }
 
 function inputmethod_cleanup() {
+  SpecialPowers.wrap(navigator.mozInputMethod).setActive(false);
   SimpleTest.finish();
 }
--- a/dom/inputmethod/mochitest/mochitest.ini
+++ b/dom/inputmethod/mochitest/mochitest.ini
@@ -3,20 +3,22 @@
 skip-if = (toolkit == 'android' || toolkit == 'gonk') || e10s
 support-files =
   inputmethod_common.js
   file_inputmethod.html
   file_inputmethod_1043828.html
   file_test_app.html
   file_test_sendkey_cancel.html
   file_test_sms_app.html
+  file_test_sms_app_1066515.html
 
 [test_basic.html]
 [test_bug944397.html]
 [test_bug949059.html]
 [test_bug953044.html]
 [test_bug960946.html]
 [test_bug978918.html]
 [test_bug1026997.html]
 [test_bug1043828.html]
+[test_bug1066515.html]
 [test_delete_focused_element.html]
 [test_sendkey_cancel.html]
 [test_two_inputs.html]
new file mode 100644
--- /dev/null
+++ b/dom/inputmethod/mochitest/test_bug1066515.html
@@ -0,0 +1,93 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1066515
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 1066515</title>
+  <script type="application/javascript;version=1.7" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript;version=1.7" src="inputmethod_common.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1066515">Mozilla Bug 1066515</a>
+<p id="display"></p>
+<pre id="test">
+<script class="testbody" type="application/javascript;version=1.7">
+
+// The input context.
+var gContext = null;
+
+inputmethod_setup(function() {
+  runTest();
+});
+
+function runTest() {
+  let im = navigator.mozInputMethod;
+
+  im.oninputcontextchange = function() {
+    ok(true, 'inputcontextchange event was fired.');
+    im.oninputcontextchange = null;
+
+    gContext = im.inputcontext;
+    if (!gContext) {
+      ok(false, 'Should have a non-null inputcontext.');
+      inputmethod_cleanup();
+      return;
+    }
+
+    test_replaceSurroundingTextWithinTextNode();
+  };
+
+  // Set current page as an input method.
+  SpecialPowers.wrap(im).setActive(true);
+
+  let iframe = document.createElement('iframe');
+  iframe.src = 'file_test_sms_app_1066515.html';
+  iframe.setAttribute('mozbrowser', true);
+  document.body.appendChild(iframe);
+}
+
+function test_replaceSurroundingTextWithinTextNode() {
+  // Set cursor position after 'f'.
+  gContext.setSelectionRange(1, 0);
+
+  // Replace 'fxos' to 'Hitooo' which the range is within current text node.
+  gContext.replaceSurroundingText('Hitooo', -1, 4).then(function() {
+    gContext.getText().then(function(text) {
+      is(text, 'Hitooo\nhello world', 'replaceSurroundingText successfully.');
+      test_replaceSurroundingTextSpanMultipleNodes();
+    }, function(e) {
+      ok(false, 'getText failed: ' + e.name);
+      inputmethod_cleanup();
+    });
+  }, function(e) {
+    ok(false, 'replaceSurroundingText failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+function test_replaceSurroundingTextSpanMultipleNodes() {
+  // Set cursor position to the beginning.
+  gContext.setSelectionRange(0, 0);
+
+  // Replace whole content editable element to 'abc'.
+  gContext.replaceSurroundingText('abc', 0, 100).then(function() {
+    gContext.getText().then(function(text) {
+      is(text, 'abc', 'replaceSurroundingText successfully.');
+      inputmethod_cleanup();
+    }, function(e) {
+      ok(false, 'getText failed: ' + e.name);
+      inputmethod_cleanup();
+    });
+  }, function(e) {
+    ok(false, 'replaceSurroundingText failed: ' + e.name);
+    inputmethod_cleanup();
+  });
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/mathml/nsMathMLElement.cpp
+++ b/dom/mathml/nsMathMLElement.cpp
@@ -114,17 +114,18 @@ nsMathMLElement::BindToTree(nsIDocument*
       doc->
         EnsureOnDemandBuiltInUASheet(nsLayoutStylesheetCache::MathMLSheet());
 
       // Rebuild style data for the presshell, because style system
       // optimizations may have taken place assuming MathML was disabled.
       // (See nsRuleNode::CheckSpecifiedProperties.)
       nsCOMPtr<nsIPresShell> shell = doc->GetShell();
       if (shell) {
-        shell->GetPresContext()->PostRebuildAllStyleDataEvent(nsChangeHint(0));
+        shell->GetPresContext()->
+          PostRebuildAllStyleDataEvent(nsChangeHint(0), eRestyle_Subtree);
       }
     }
   }
 
   return rv;
 }
 
 void
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
+skip-if = (os == 'win' && contentSandbox != 'off') # contentSandbox(Bug 1042735)
 support-files =
   head.js
   constraints.js
   mediaStreamPlayback.js
   pc.js
   templates.js
   NetworkPreparationChromeScript.js
   blacksilence.js
--- a/dom/network/TCPSocketChild.cpp
+++ b/dom/network/TCPSocketChild.cpp
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include <algorithm>
 #include "TCPSocketChild.h"
 #include "mozilla/unused.h"
+#include "mozilla/UniquePtr.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/dom/PBrowserChild.h"
 #include "mozilla/dom/TabChild.h"
 #include "nsIDOMTCPSocket.h"
 #include "nsJSUtils.h"
 #include "nsContentUtils.h"
 #include "jsapi.h"
 #include "jsfriendapi.h"
@@ -22,24 +23,27 @@ namespace IPC {
 bool
 DeserializeArrayBuffer(JS::Handle<JSObject*> aObj,
                        const InfallibleTArray<uint8_t>& aBuffer,
                        JS::MutableHandle<JS::Value> aVal)
 {
   mozilla::AutoSafeJSContext cx;
   JSAutoCompartment ac(cx, aObj);
 
-  JS::Rooted<JSObject*> obj(cx, JS_NewArrayBuffer(cx, aBuffer.Length()));
+  mozilla::UniquePtr<uint8_t[], JS::FreePolicy> data(js_pod_malloc<uint8_t>(aBuffer.Length()));
+  if (!data)
+      return false;
+  memcpy(data.get(), aBuffer.Elements(), aBuffer.Length());
+
+  JSObject* obj = JS_NewArrayBufferWithContents(cx, aBuffer.Length(), data.get());
   if (!obj)
-    return false;
-  uint8_t* data = JS_GetArrayBufferData(obj);
-  if (!data)
-    return false;
-  memcpy(data, aBuffer.Elements(), aBuffer.Length());
-  aVal.set(OBJECT_TO_JSVAL(obj));
+      return false;
+  data.release();
+
+  aVal.setObject(*obj);
   return true;
 }
 
 } // namespace IPC
 
 namespace mozilla {
 namespace dom {
 
@@ -219,23 +223,26 @@ TCPSocketChild::SendSend(JS::Handle<JS::
     SendData(str, aTrackingNumber);
   } else {
     NS_ENSURE_TRUE(aData.isObject(), NS_ERROR_FAILURE);
     JS::Rooted<JSObject*> obj(aCx, &aData.toObject());
     NS_ENSURE_TRUE(JS_IsArrayBufferObject(obj), NS_ERROR_FAILURE);
     uint32_t buflen = JS_GetArrayBufferByteLength(obj);
     aByteOffset = std::min(buflen, aByteOffset);
     uint32_t nbytes = std::min(buflen - aByteOffset, aByteLength);
-    uint8_t* data = JS_GetArrayBufferData(obj);
-    if (!data) {
-      return NS_ERROR_OUT_OF_MEMORY;
-    }
     FallibleTArray<uint8_t> fallibleArr;
-    if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
-      return NS_ERROR_OUT_OF_MEMORY;
+    {
+        JS::AutoCheckCannotGC nogc;
+        uint8_t* data = JS_GetArrayBufferData(obj, nogc);
+        if (!data) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
+        if (!fallibleArr.InsertElementsAt(0, data + aByteOffset, nbytes)) {
+            return NS_ERROR_OUT_OF_MEMORY;
+        }
     }
     InfallibleTArray<uint8_t> arr;
     arr.SwapElements(fallibleArr);
     SendData(arr, aTrackingNumber);
   }
   return NS_OK;
 }
 
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -280,27 +280,37 @@ TCPSocketParent::SendEvent(const nsAStri
     data = SendableData(str);
 
   } else if (aDataVal.isUndefined() || aDataVal.isNull()) {
     data = mozilla::void_t();
 
   } else if (aDataVal.isObject()) {
     JS::Rooted<JSObject *> obj(aCx, &aDataVal.toObject());
     if (JS_IsArrayBufferObject(obj)) {
-      uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
-      uint8_t* buffer = JS_GetArrayBufferData(obj);
-      if (!buffer) {
-        FireInteralError(this, __LINE__);
-        return NS_ERROR_OUT_OF_MEMORY;
+      FallibleTArray<uint8_t> fallibleArr;
+      uint32_t errLine = 0;
+      do {
+          JS::AutoCheckCannotGC nogc;
+          uint32_t nbytes = JS_GetArrayBufferByteLength(obj);
+          uint8_t* buffer = JS_GetArrayBufferData(obj, nogc);
+          if (!buffer) {
+              errLine = __LINE__;
+              break;
+          }
+          if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
+              errLine = __LINE__;
+              break;
+          }
+      } while (false);
+
+      if (errLine) {
+          FireInteralError(this, errLine);
+          return NS_ERROR_OUT_OF_MEMORY;
       }
-      FallibleTArray<uint8_t> fallibleArr;
-      if (!fallibleArr.InsertElementsAt(0, buffer, nbytes)) {
-        FireInteralError(this, __LINE__);
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+
       InfallibleTArray<uint8_t> arr;
       arr.SwapElements(fallibleArr);
       data = SendableData(arr);
 
     } else {
       nsAutoJSString name;
 
       JS::Rooted<JS::Value> val(aCx);
--- a/dom/workers/FileReaderSync.cpp
+++ b/dom/workers/FileReaderSync.cpp
@@ -56,47 +56,45 @@ FileReaderSync::ReadAsArrayBuffer(JSCont
 {
   uint64_t blobSize;
   nsresult rv = aBlob.GetSize(&blobSize);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
-  JS::Rooted<JSObject*> jsArrayBuffer(aCx, JS_NewArrayBuffer(aCx, blobSize));
-  if (!jsArrayBuffer) {
-    // XXXkhuey we need a way to indicate to the bindings that the call failed
-    // but there's already a pending exception that we should not clobber.
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return;
-  }
-
-  uint32_t bufferLength = JS_GetArrayBufferByteLength(jsArrayBuffer);
-  uint8_t* arrayBuffer = JS_GetStableArrayBufferData(aCx, jsArrayBuffer);
-  if (!arrayBuffer) {
+  UniquePtr<char[], JS::FreePolicy> bufferData(js_pod_malloc<char>(blobSize));
+  if (!bufferData) {
     aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
   nsCOMPtr<nsIInputStream> stream;
   rv = aBlob.GetInternalStream(getter_AddRefs(stream));
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   uint32_t numRead;
-  rv = stream->Read((char*)arrayBuffer, bufferLength, &numRead);
+  rv = stream->Read(bufferData.get(), blobSize, &numRead);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
-  NS_ASSERTION(numRead == bufferLength, "failed to read data");
+  NS_ASSERTION(numRead == blobSize, "failed to read data");
 
-  aRetval.set(jsArrayBuffer);
+  JSObject* arrayBuffer = JS_NewArrayBufferWithContents(aCx, blobSize, bufferData.get());
+  if (!arrayBuffer) {
+    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+    return;
+  }
+  bufferData.release();
+
+  aRetval.set(arrayBuffer);
 }
 
 void
 FileReaderSync::ReadAsBinaryString(File& aBlob,
                                    nsAString& aResult,
                                    ErrorResult& aRv)
 {
   nsCOMPtr<nsIInputStream> stream;
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -838,27 +838,38 @@ CreateJSContextForWorker(WorkerPrivate* 
 
 #ifdef JS_GC_ZEAL
   JS_SetGCZeal(workerCx, settings.gcZeal, settings.gcZealFrequency);
 #endif
 
   return workerCx;
 }
 
+static bool
+PreserveWrapper(JSContext *cx, JSObject *obj)
+{
+    MOZ_ASSERT(cx);
+    MOZ_ASSERT(obj);
+    MOZ_ASSERT(mozilla::dom::IsDOMObject(obj));
+
+    return mozilla::dom::TryPreserveWrapper(obj);
+}
+
 class WorkerJSRuntime : public mozilla::CycleCollectedJSRuntime
 {
 public:
   // The heap size passed here doesn't matter, we will change it later in the
   // call to JS_SetGCParameter inside CreateJSContextForWorker.
   WorkerJSRuntime(JSRuntime* aParentRuntime, WorkerPrivate* aWorkerPrivate)
     : CycleCollectedJSRuntime(aParentRuntime,
                               WORKER_DEFAULT_RUNTIME_HEAPSIZE,
                               WORKER_DEFAULT_NURSERY_SIZE),
     mWorkerPrivate(aWorkerPrivate)
   {
+    js::SetPreserveWrapperCallback(Runtime(), PreserveWrapper);
     JS_InitDestroyPrincipalsCallback(Runtime(), DestroyWorkerPrincipals);
   }
 
   ~WorkerJSRuntime()
   {
     auto rtPrivate = static_cast<WorkerThreadRuntimePrivate*>(JS_GetRuntimePrivate(Runtime()));
     delete rtPrivate;
     JS_SetRuntimePrivate(Runtime(), nullptr);
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/943516.html
@@ -0,0 +1,10 @@
+<!--
+Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/
+-->
+<!DOCTYPE html>
+<script>
+// Using a DOM bindings object as a weak map key should not crash when attempting to
+// call the preserve wrapper callback.
+new Worker("data:text/javascript;charset=UTF-8,(new WeakMap()).set(self, 0);")
+</script>
new file mode 100644
--- /dev/null
+++ b/dom/workers/test/crashtests.list
@@ -0,0 +1,1 @@
+load 943516.html
--- a/dom/workers/test/jsm_url_worker.js
+++ b/dom/workers/test/jsm_url_worker.js
@@ -62,21 +62,22 @@ onmessage = function(event) {
   var url = null;
   try {
     url = URL.createObjectURL(blob);
     status = true;
   } catch(e) {
   }
 
   postMessage({type: 'status', status: status, msg: 'Blob URL2:' + url});
-  postMessage({type: 'url', url: url});
 
   status = false;
   try {
     URL.createObjectURL(new Object());
   } catch(e) {
     status = true;
   }
 
   postMessage({type: 'status', status: status, msg: 'Exception wanted' });
 
+  postMessage({type: 'url', url: url});
+
   postMessage({type: 'finish' });
 }
--- a/editor/libeditor/nsPlaintextEditor.cpp
+++ b/editor/libeditor/nsPlaintextEditor.cpp
@@ -1158,16 +1158,19 @@ nsPlaintextEditor::Redo(uint32_t aCount)
 
 bool
 nsPlaintextEditor::CanCutOrCopy()
 {
   nsCOMPtr<nsISelection> selection;
   if (NS_FAILED(GetSelection(getter_AddRefs(selection))))
     return false;
 
+  if (IsPasswordEditor())
+    return false;
+
   return !selection->Collapsed();
 }
 
 bool
 nsPlaintextEditor::FireClipboardEvent(int32_t aType, int32_t aSelectionType)
 {
   if (aType == NS_PASTE)
     ForceCompositionEnd();
--- a/editor/libeditor/tests/mochitest.ini
+++ b/editor/libeditor/tests/mochitest.ini
@@ -133,16 +133,17 @@ skip-if = toolkit == 'android' || e10s
 [test_bug832025.html]
 [test_bug857487.html]
 [test_bug966155.html]
 skip-if = os != "win"
 [test_bug966552.html]
 skip-if = os != "win"
 [test_bug998188.html]
 [test_bug1026397.html]
+[test_bug1067255.html]
 skip-if = e10s
 [test_CF_HTML_clipboard.html]
 [test_contenteditable_focus.html]
 [test_dom_input_event_on_htmleditor.html]
 skip-if = toolkit == 'android' # bug 1054087
 [test_dom_input_event_on_texteditor.html]
 [test_keypress_untrusted_event.html]
 [test_root_element_replacement.html]
new file mode 100644
--- /dev/null
+++ b/editor/libeditor/tests/test_bug1067255.html
@@ -0,0 +1,52 @@
+<!DOCTYPE HTML>
+<!-- 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/. -->
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1067255
+-->
+
+<head>
+  <title>Test for Bug 1067255</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="text/javascript" src="/tests/SimpleTest/EventUtils.js"></script>
+</head>
+
+<body onload="doTest();">
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1067255">Mozilla Bug 1067255</a>
+
+  <pre id="test">
+    <script type="application/javascript">
+      /** Test for Bug 1067255 **/
+      SimpleTest.waitForExplicitFinish();
+
+      function doTest() {
+        var text = $("text-field");
+        var password = $("password-field");
+
+        var editor1 = SpecialPowers.wrap(text).editor;
+        var editor2 = SpecialPowers.wrap(password).editor;
+
+        text.focus();
+        text.select();
+
+        ok(editor1.canCopy(), "can copy, text");
+        ok(editor1.canCut(), "can cut, text");
+
+        password.focus();
+        password.select();
+
+        ok(!editor2.canCopy(), "can copy, password");
+        ok(!editor2.canCut(), "can cut, password");
+
+        SimpleTest.finish();
+      }
+   </script>
+  </pre>
+
+  <input type="text" value="Gonzo says hi" id="text-field" />
+  <input type="password" value="Jan also" id="password-field" />
+</body>
+</html>
--- a/gfx/2d/Logging.h
+++ b/gfx/2d/Logging.h
@@ -106,17 +106,17 @@ public:
 };
 
 MOZ_BEGIN_ENUM_CLASS(LogOptions, int)
   NoNewline = 0x01
 MOZ_END_ENUM_CLASS(LogOptions)
 
 template<typename T>
 struct Hexa {
-  Hexa(T aVal) : mVal(aVal) {}
+  explicit Hexa(T aVal) : mVal(aVal) {}
   T mVal;
 };
 template<typename T>
 Hexa<T> hexa(T val) { return Hexa<T>(val); }
 
 template<int L, typename Logger = BasicLogger>
 class Log
 {
--- a/gfx/layers/GrallocImages.cpp
+++ b/gfx/layers/GrallocImages.cpp
@@ -325,21 +325,21 @@ ConvertOmxYUVFormatToRGB565(android::sp<
     if (!ycbcrData.mYChannel) {
       ycbcrData.mYChannel     = buffer;
       ycbcrData.mYSkip        = 0;
       ycbcrData.mYStride      = aBuffer->getStride();
       ycbcrData.mYSize        = aSurface->GetSize();
       ycbcrData.mCbSkip       = 0;
       ycbcrData.mCbCrSize     = aSurface->GetSize() / 2;
       ycbcrData.mPicSize      = aSurface->GetSize();
-      ycbcrData.mCrChannel    = buffer + ycbcrData.mYStride * ycbcrData.mYSize.height;
+      ycbcrData.mCrChannel    = buffer + ycbcrData.mYStride * aBuffer->getHeight();
       ycbcrData.mCrSkip       = 0;
       // Align to 16 bytes boundary
-      ycbcrData.mCbCrStride   = ((ycbcrData.mYStride / 2) + 15) & ~0x0F;
-      ycbcrData.mCbChannel    = ycbcrData.mCrChannel + (ycbcrData.mCbCrStride * ycbcrData.mCbCrSize.height);
+      ycbcrData.mCbCrStride   = ALIGN(ycbcrData.mYStride / 2, 16);
+      ycbcrData.mCbChannel    = ycbcrData.mCrChannel + (ycbcrData.mCbCrStride * aBuffer->getHeight() / 2);
     }
     gfx::ConvertYCbCrToRGB(ycbcrData,
                            aSurface->GetFormat(),
                            aSurface->GetSize(),
                            aMappedSurface->mData,
                            aMappedSurface->mStride);
     return OK;
   }
--- a/gfx/layers/client/TiledContentClient.cpp
+++ b/gfx/layers/client/TiledContentClient.cpp
@@ -507,17 +507,17 @@ TileClient::~TileClient()
 TileClient::TileClient(const TileClient& o)
 {
   mBackBuffer.Set(this, o.mBackBuffer);
   mBackBufferOnWhite = o.mBackBufferOnWhite;
   mFrontBuffer = o.mFrontBuffer;
   mFrontBufferOnWhite = o.mFrontBufferOnWhite;
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
-  mCompositableClient = nullptr;
+  mCompositableClient = o.mCompositableClient;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
   mManager = o.mManager;
   mInvalidFront = o.mInvalidFront;
   mInvalidBack = o.mInvalidBack;
 }
 
@@ -526,17 +526,17 @@ TileClient::operator=(const TileClient& 
 {
   if (this == &o) return *this;
   mBackBuffer.Set(this, o.mBackBuffer);
   mBackBufferOnWhite = o.mBackBufferOnWhite;
   mFrontBuffer = o.mFrontBuffer;
   mFrontBufferOnWhite = o.mFrontBufferOnWhite;
   mBackLock = o.mBackLock;
   mFrontLock = o.mFrontLock;
-  mCompositableClient = nullptr;
+  mCompositableClient = o.mCompositableClient;
 #ifdef GFX_TILEDLAYER_DEBUG_OVERLAY
   mLastUpdate = o.mLastUpdate;
 #endif
   mManager = o.mManager;
   mInvalidFront = o.mInvalidFront;
   mInvalidBack = o.mInvalidBack;
   return *this;
 }
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -689,17 +689,17 @@ ApplyAsyncTransformToScrollbarForContent
     return;
   }
 
   const FrameMetrics& metrics = aContent.Metrics();
   AsyncPanZoomController* apzc = aContent.GetApzc();
 
   Matrix4x4 asyncTransform = apzc->GetCurrentAsyncTransform();
   Matrix4x4 nontransientTransform = apzc->GetNontransientAsyncTransform();
-  Matrix4x4 transientTransform = asyncTransform * nontransientTransform.Inverse();
+  Matrix4x4 transientTransform = nontransientTransform.Inverse() * asyncTransform;
 
   // |transientTransform| represents the amount by which we have scrolled and
   // zoomed since the last paint. Because the scrollbar was sized and positioned based
   // on the painted content, we need to adjust it based on transientTransform so that
   // it reflects what the user is actually seeing now.
   // - The scroll thumb needs to be scaled in the direction of scrolling by the inverse
   //   of the transientTransform scale (representing the zoom). This is because zooming
   //   in decreases the fraction of the whole scrollable rect that is in view.
@@ -707,33 +707,47 @@ ApplyAsyncTransformToScrollbarForContent
   //   translation (representing the scroll). This is because scrolling down, which
   //   translates the layer content up, should result in moving the scroll thumb down.
   //   The amount of the translation to the scroll thumb should be such that the ratio
   //   of the translation to the size of the scroll port is the same as the ratio
   //   of the scroll amount to the size of the scrollable rect.
   Matrix4x4 scrollbarTransform;
   if (aScrollbar->GetScrollbarDirection() == Layer::VERTICAL) {
     float scale = metrics.CalculateCompositedSizeInCssPixels().height / metrics.mScrollableRect.height;
+    if (aScrollbarIsDescendant) {
+      // In cases where the scrollbar is a descendant of the content, the
+      // scrollbar gets painted at the same resolution as the content. Since the
+      // coordinate space we apply this transform in includes the resolution, we
+      // need to adjust for it as well here. Note that in another
+      // aScrollbarIsDescendant hunk below we unapply the entire async
+      // transform, which includes the nontransientasync transform and would
+      // normally account for the resolution.
+      scale *= metrics.mResolution.scale;
+    }
     scrollbarTransform.PostScale(1.f, 1.f / transientTransform._22, 1.f);
     scrollbarTransform.PostTranslate(0, -transientTransform._42 * scale, 0);
   }
   if (aScrollbar->GetScrollbarDirection() == Layer::HORIZONTAL) {
     float scale = metrics.CalculateCompositedSizeInCssPixels().width / metrics.mScrollableRect.width;
+    if (aScrollbarIsDescendant) {
+      scale *= metrics.mResolution.scale;
+    }
     scrollbarTransform.PostScale(1.f / transientTransform._11, 1.f, 1.f);
     scrollbarTransform.PostTranslate(-transientTransform._41 * scale, 0, 0);
   }
 
   Matrix4x4 transform = scrollbarTransform * aScrollbar->GetTransform();
 
   if (aScrollbarIsDescendant) {
     // If the scrollbar layer is a child of the content it is a scrollbar for, then we
-    // need to do an extra untransform to cancel out the transient async transform on
-    // the content. This is needed because otherwise that transient async transform is
-    // part of the effective transform of this scrollbar, and the scrollbar will jitter
-    // as the content scrolls.
+    // need to do an extra untransform to cancel out the async transform on
+    // the content. This is needed because layout positions and sizes the
+    // scrollbar on the assumption that there is no async transform, and without
+    // this code the scrollbar will end up in the wrong place.
+    //
     // Since the async transform is applied on top of the content's regular
     // transform, we need to make sure to unapply the async transform in the
     // same coordinate space. This requires applying the content transform and
     // then unapplying it after unapplying the async transform.
     Matrix4x4 asyncUntransform = (asyncTransform * apzc->GetOverscrollTransform()).Inverse();
     Matrix4x4 contentTransform = aContent.GetTransform();
     Matrix4x4 contentUntransform = contentTransform.Inverse();
 
--- a/gfx/layers/opengl/TextureHostOGL.cpp
+++ b/gfx/layers/opengl/TextureHostOGL.cpp
@@ -242,17 +242,17 @@ TextureSharedDataGonkOGL::GetAndResetGLT
   mBoundEGLImage = EGL_NO_IMAGE;
   return texture;
 }
 
 void
 TextureSharedDataGonkOGL::DeleteTextureIfPresent()
 {
   if (mTexture) {
-    MOZ_ASSERT(gl());
+    MOZ_ASSERT(mCompositor);
     if (gl() && gl()->MakeCurrent()) {
       gl()->fDeleteTextures(1, &mTexture);
     }
     mTexture = 0;
     mBoundEGLImage = EGL_NO_IMAGE;
   }
 }
 
--- a/gfx/ots/README.mozilla
+++ b/gfx/ots/README.mozilla
@@ -1,11 +1,12 @@
 This is the Sanitiser for OpenType project, from http://code.google.com/p/ots/.
 
 Our reference repository is https://github.com/khaledhosny/ots/.
 
-Current revision: 5c25bdac8f02080f49fa416ea997ed77e3be0d30
+Current revision: c24a839b1c66c4de09e58fabaacb82bf3bd692a4
 
 Upstream files included: LICENSE, src/, include/
 
 Additional files: README.mozilla, src/moz.build
 
 Additional patch: ots-visibility.patch (bug 711079).
+Additional patch: ots-brotli-path.patch (bug 1064737).
--- a/gfx/ots/include/opentype-sanitiser.h
+++ b/gfx/ots/include/opentype-sanitiser.h
@@ -222,25 +222,31 @@ class OTS_API OTSContext {
     //     sanitisied output will be written to this. In the even of a failure,
     //     partial output may have been written.
     //   input: the OpenType file
     //   length: the size, in bytes, of |input|
     //   context: optional context that holds various OTS settings like user callbacks
     bool Process(OTSStream *output, const uint8_t *input, size_t length);
 
     // This function will be called when OTS is reporting an error.
-    virtual void Message(const char *format, ...) MSGFUNC_FMT_ATTR {}
+    //   level: the severity of the generated message:
+    //     0: error messages in case OTS fails to sanitize the font.
+    //     1: warning messages about issue OTS fixed in the sanitized font.
+    virtual void Message(int level, const char *format, ...) MSGFUNC_FMT_ATTR {}
 
     // This function will be called when OTS needs to decide what to do for a
     // font table.
     //   tag: table tag as an integer in big-endian byte order, independent of
     //   platform endianness
     virtual TableAction GetTableAction(uint32_t tag) { return ots::TABLE_ACTION_DEFAULT; }
 };
 
+// For backward compatibility - remove once Chrome switches over to the new API.
+bool Process(OTSStream *output, const uint8_t *input, size_t length);
+
 // Force to disable debug output even when the library is compiled with
 // -DOTS_DEBUG.
 void DisableDebugOutput();
 
 // Enable WOFF2 support(experimental).
 void OTS_API EnableWOFF2();
 
 }  // namespace ots
new file mode 100644
--- /dev/null
+++ b/gfx/ots/ots-brotli-path.patch
@@ -0,0 +1,22 @@
+diff --git a/gfx/ots/src/woff2.cc b/gfx/ots/src/woff2.cc
+--- a/gfx/ots/src/woff2.cc
++++ b/gfx/ots/src/woff2.cc
+@@ -6,17 +6,17 @@
+ // Condensed file format.
+ 
+ #include <cassert>
+ #include <cstdlib>
+ #include <vector>
+ 
+ #include <zlib.h>
+ 
+-#include "third_party/brotli/src/brotli/dec/decode.h"
++#include "decode.h"
+ 
+ #include "opentype-sanitiser.h"
+ #include "ots-memory-stream.h"
+ #include "ots.h"
+ #include "woff2.h"
+ 
+ namespace {
+ 
--- a/gfx/ots/ots-visibility.patch
+++ b/gfx/ots/ots-visibility.patch
@@ -32,17 +32,17 @@ diff --git a/gfx/ots/include/opentype-sa
  #if defined(_WIN32)
  #include <stdlib.h>
  typedef signed char int8_t;
  typedef unsigned char uint8_t;
  typedef short int16_t;
  typedef unsigned short uint16_t;
  typedef int int32_t;
  typedef unsigned int uint32_t;
-@@ -187,17 +187,17 @@ class OTSStream {
+@@ -187,17 +207,17 @@ class OTSStream {
  
  enum TableAction {
    TABLE_ACTION_DEFAULT,  // Use OTS's default action for that table
    TABLE_ACTION_SANITIZE, // Sanitize the table, potentially droping it
    TABLE_ACTION_PASSTHRU, // Serialize the table unchanged
    TABLE_ACTION_DROP      // Drop the table
  };
  
@@ -51,8 +51,23 @@ diff --git a/gfx/ots/include/opentype-sa
    public:
      OTSContext() {}
      ~OTSContext() {}
  
      // Process a given OpenType file and write out a sanitised version
      //   output: a pointer to an object implementing the OTSStream interface. The
      //     sanitisied output will be written to this. In the even of a failure,
      //     partial output may have been written.
+@@ -222,13 +242,13 @@ class OTSContext {
+ // For backward compatibility - remove once Chrome switches over to the new API.
+ bool Process(OTSStream *output, const uint8_t *input, size_t length);
+ 
+ // Force to disable debug output even when the library is compiled with
+ // -DOTS_DEBUG.
+ void DisableDebugOutput();
+ 
+ // Enable WOFF2 support(experimental).
+-void EnableWOFF2();
++void OTS_API EnableWOFF2();
+ 
+ }  // namespace ots
+ 
+ #endif  // OPENTYPE_SANITISER_H_
--- a/gfx/ots/src/cff.cc
+++ b/gfx/ots/src/cff.cc
@@ -457,31 +457,31 @@ bool ParsePrivateDictData(
     }
     operands.clear();
   }
 
   return true;
 }
 
 bool ParseDictData(const uint8_t *data, size_t table_length,
-                   const ots::CFFIndex &index, size_t glyphs,
+                   const ots::CFFIndex &index, uint16_t glyphs,
                    size_t sid_max, DICT_DATA_TYPE type,
                    ots::OpenTypeCFF *out_cff) {
   for (unsigned i = 1; i < index.offsets.size(); ++i) {
     if (type == DICT_DATA_TOPLEVEL) {
       out_cff->char_strings_array.push_back(new ots::CFFIndex);
     }
     size_t dict_length = index.offsets[i] - index.offsets[i - 1];
     ots::Buffer table(data + index.offsets[i - 1], dict_length);
 
     std::vector<std::pair<uint32_t, DICT_OPERAND_TYPE> > operands;
 
     FONT_FORMAT font_format = FORMAT_UNKNOWN;
     bool have_ros = false;
-    size_t charstring_glyphs = 0;
+    uint16_t charstring_glyphs = 0;
     size_t charset_offset = 0;
 
     while (table.offset() < dict_length) {
       if (!ParseDictDataReadNext(&table, &operands)) {
         return OTS_FAILURE();
       }
       if (operands.empty()) {
         return OTS_FAILURE();
@@ -695,17 +695,17 @@ bool ParseDictData(const uint8_t *data, 
           // parse FDSelect data structure
           ots::Buffer cff_table(data, table_length);
           cff_table.set_offset(operands.back().first);
           uint8_t format = 0;
           if (!cff_table.ReadU8(&format)) {
             return OTS_FAILURE();
           }
           if (format == 0) {
-            for (size_t j = 0; j < glyphs; ++j) {
+            for (uint16_t j = 0; j < glyphs; ++j) {
               uint8_t fd_index = 0;
               if (!cff_table.ReadU8(&fd_index)) {
                 return OTS_FAILURE();
               }
               (out_cff->fd_select)[j] = fd_index;
             }
           } else if (format == 3) {
             uint16_t n_ranges = 0;
@@ -839,17 +839,17 @@ bool ParseDictData(const uint8_t *data, 
       ots::Buffer cff_table(data, table_length);
       cff_table.set_offset(charset_offset);
       uint8_t format = 0;
       if (!cff_table.ReadU8(&format)) {
         return OTS_FAILURE();
       }
       switch (format) {
         case 0:
-          for (unsigned j = 1 /* .notdef is omitted */; j < glyphs; ++j) {
+          for (uint16_t j = 1 /* .notdef is omitted */; j < glyphs; ++j) {
             uint16_t sid = 0;
             if (!cff_table.ReadU16(&sid)) {
               return OTS_FAILURE();
             }
             if (!have_ros && (sid > sid_max)) {
               return OTS_FAILURE();
             }
             // TODO(yusukes): check CIDs when have_ros is true.
@@ -962,17 +962,17 @@ bool ots_cff_parse(OpenTypeFile *file, c
   CFFIndex string_index;
   if (!ParseIndex(&table, &string_index)) {
     return OTS_FAILURE();
   }
   if (string_index.count >= 65000 - kNStdString) {
     return OTS_FAILURE();
   }
 
-  const size_t num_glyphs = file->maxp->num_glyphs;
+  const uint16_t num_glyphs = file->maxp->num_glyphs;
   const size_t sid_max = string_index.count + kNStdString;
   // string_index.count == 0 is allowed.
 
   // parse "9. Top DICT Data"
   if (!ParseDictData(data, length, top_dict_index,
                      num_glyphs, sid_max,
                      DICT_DATA_TOPLEVEL, file->cff)) {
     return OTS_FAILURE();
@@ -991,17 +991,18 @@ bool ots_cff_parse(OpenTypeFile *file, c
   for (iter = file->cff->fd_select.begin(); iter != end; ++iter) {
     if (iter->second >= file->cff->font_dict_length) {
       return OTS_FAILURE();
     }
   }
 
   // Check if all charstrings (font hinting code for each glyph) are valid.
   for (size_t i = 0; i < file->cff->char_strings_array.size(); ++i) {
-    if (!ValidateType2CharStringIndex(*(file->cff->char_strings_array.at(i)),
+    if (!ValidateType2CharStringIndex(file,
+                                      *(file->cff->char_strings_array.at(i)),
                                       global_subrs_index,
                                       file->cff->fd_select,
                                       file->cff->local_subrs_per_font,
                                       file->cff->local_subrs,
                                       &table)) {
       return OTS_FAILURE_MSG("Failed validating charstring set %d", (int) i);
     }
   }
--- a/gfx/ots/src/cff_type2_charstring.cc
+++ b/gfx/ots/src/cff_type2_charstring.cc
@@ -9,31 +9,34 @@
 
 #include <climits>
 #include <cstdio>
 #include <cstring>
 #include <stack>
 #include <string>
 #include <utility>
 
+#define TABLE_NAME "CFF"
+
 namespace {
 
 // Type 2 Charstring Implementation Limits. See Appendix. B in Adobe Technical
 // Note #5177.
 const int32_t kMaxSubrsCount = 65536;
 const size_t kMaxCharStringLength = 65535;
 const size_t kMaxArgumentStack = 48;
 const size_t kMaxNumberOfStemHints = 96;
 const size_t kMaxSubrNesting = 10;
 
 // |dummy_result| should be a huge positive integer so callsubr and callgsubr
 // will fail with the dummy value.
 const int32_t dummy_result = INT_MAX;
 
-bool ExecuteType2CharString(size_t call_depth,
+bool ExecuteType2CharString(ots::OpenTypeFile *file,
+                            size_t call_depth,
                             const ots::CFFIndex& global_subrs_index,
                             const ots::CFFIndex& local_subrs_index,
                             ots::Buffer *cff_table,
                             ots::Buffer *char_string,
                             std::stack<int32_t> *argument_stack,
                             bool *out_found_endchar,
                             bool *out_found_width,
                             size_t *in_out_num_stems);
@@ -217,17 +220,18 @@ bool ReadNextNumberFromType2CharString(o
 
   return true;
 }
 
 // Executes |op| and updates |argument_stack|. Returns true if the execution
 // succeeds. If the |op| is kCallSubr or kCallGSubr, the function recursively
 // calls ExecuteType2CharString() function. The arguments other than |op| and
 // |argument_stack| are passed for that reason.
-bool ExecuteType2CharStringOperator(int32_t op,
+bool ExecuteType2CharStringOperator(ots::OpenTypeFile *file,
+                                    int32_t op,
                                     size_t call_depth,
                                     const ots::CFFIndex& global_subrs_index,
                                     const ots::CFFIndex& local_subrs_index,
                                     ots::Buffer *cff_table,
                                     ots::Buffer *char_string,
                                     std::stack<int32_t> *argument_stack,
                                     bool *out_found_endchar,
                                     bool *in_out_found_width,
@@ -281,17 +285,18 @@ bool ExecuteType2CharStringOperator(int3
     }
     const size_t offset = subrs_index.offsets[subr_number];
     cff_table->set_offset(offset);
     if (!cff_table->Skip(length)) {
       return OTS_FAILURE();
     }
     ots::Buffer char_string_to_jump(cff_table->buffer() + offset, length);
 
-    return ExecuteType2CharString(call_depth + 1,
+    return ExecuteType2CharString(file,
+                                  call_depth + 1,
                                   global_subrs_index,
                                   local_subrs_index,
                                   cff_table,
                                   &char_string_to_jump,
                                   argument_stack,
                                   out_found_endchar,
                                   in_out_found_width,
                                   in_out_num_stems);
@@ -703,34 +708,34 @@ bool ExecuteType2CharStringOperator(int3
     if (stack_size != 11) {
       return OTS_FAILURE();
     }
     while (!argument_stack->empty())
       argument_stack->pop();
     return true;
   }
 
-  //OTS_WARNING("Undefined operator: %d (0x%x)", op, op);
-  return OTS_FAILURE();
+  return OTS_FAILURE_MSG("Undefined operator: %d (0x%x)", op, op);
 }
 
 // Executes |char_string| and updates |argument_stack|.
 //
 // call_depth: The current call depth. Initial value is zero.
 // global_subrs_index: Global subroutines.
 // local_subrs_index: Local subroutines for the current glyph.
 // cff_table: A whole CFF table which contains all global and local subroutines.
 // char_string: A charstring we'll execute. |char_string| can be a main routine
 //              in CharString INDEX, or a subroutine in GlobalSubr/LocalSubr.
 // argument_stack: The stack which an operator in |char_string| operates.
 // out_found_endchar: true is set if |char_string| contains 'endchar'.
 // in_out_found_width: true is set if |char_string| contains 'width' byte (which
 //                     is 0 or 1 byte.)
 // in_out_num_stems: total number of hstems and vstems processed so far.
-bool ExecuteType2CharString(size_t call_depth,
+bool ExecuteType2CharString(ots::OpenTypeFile *file,
+                            size_t call_depth,
                             const ots::CFFIndex& global_subrs_index,
                             const ots::CFFIndex& local_subrs_index,
                             ots::Buffer *cff_table,
                             ots::Buffer *char_string,
                             std::stack<int32_t> *argument_stack,
                             bool *out_found_endchar,
                             bool *in_out_found_width,
                             size_t *in_out_num_stems) {
@@ -769,17 +774,18 @@ bool ExecuteType2CharString(size_t call_
       argument_stack->push(operator_or_operand);
       if (argument_stack->size() > kMaxArgumentStack) {
         return OTS_FAILURE();
       }
       continue;
     }
 
     // An operator is found. Execute it.
-    if (!ExecuteType2CharStringOperator(operator_or_operand,
+    if (!ExecuteType2CharStringOperator(file,
+                                        operator_or_operand,
                                         call_depth,
                                         global_subrs_index,
                                         local_subrs_index,
                                         cff_table,
                                         char_string,
                                         argument_stack,
                                         out_found_endchar,
                                         in_out_found_width,
@@ -834,44 +840,47 @@ bool SelectLocalSubr(const std::map<uint
   return true;
 }
 
 }  // namespace
 
 namespace ots {
 
 bool ValidateType2CharStringIndex(
+    ots::OpenTypeFile *file,
     const CFFIndex& char_strings_index,
     const CFFIndex& global_subrs_index,
     const std::map<uint16_t, uint8_t> &fd_select,
     const std::vector<CFFIndex *> &local_subrs_per_font,
     const CFFIndex *local_subrs,
     Buffer* cff_table) {
-  if (char_strings_index.offsets.size() == 0) {
+  const uint16_t num_offsets =
+      static_cast<uint16_t>(char_strings_index.offsets.size());
+  if (num_offsets != char_strings_index.offsets.size() || num_offsets == 0) {
     return OTS_FAILURE();  // no charstring.
   }
 
   // For each glyph, validate the corresponding charstring.
-  for (unsigned i = 1; i < char_strings_index.offsets.size(); ++i) {
+  for (uint16_t i = 1; i < num_offsets; ++i) {
     // Prepare a Buffer object, |char_string|, which contains the charstring
     // for the |i|-th glyph.
     const size_t length =
       char_strings_index.offsets[i] - char_strings_index.offsets[i - 1];
     if (length > kMaxCharStringLength) {
       return OTS_FAILURE();
     }
     const size_t offset = char_strings_index.offsets[i - 1];
     cff_table->set_offset(offset);
     if (!cff_table->Skip(length)) {
       return OTS_FAILURE();
     }
     Buffer char_string(cff_table->buffer() + offset, length);
 
     // Get a local subrs for the glyph.
-    const unsigned glyph_index = i - 1;  // index in the map is 0-origin.
+    const uint16_t glyph_index = i - 1;  // index in the map is 0-origin.
     const CFFIndex *local_subrs_to_use = NULL;
     if (!SelectLocalSubr(fd_select,
                          local_subrs_per_font,
                          local_subrs,
                          glyph_index,
                          &local_subrs_to_use)) {
       return OTS_FAILURE();
     }
@@ -881,22 +890,25 @@ bool ValidateType2CharStringIndex(
       local_subrs_to_use = &default_empty_subrs;
     }
 
     // Check a charstring for the |i|-th glyph.
     std::stack<int32_t> argument_stack;
     bool found_endchar = false;
     bool found_width = false;
     size_t num_stems = 0;
-    if (!ExecuteType2CharString(0 /* initial call_depth is zero */,
+    if (!ExecuteType2CharString(file,
+                                0 /* initial call_depth is zero */,
                                 global_subrs_index, *local_subrs_to_use,
                                 cff_table, &char_string, &argument_stack,
                                 &found_endchar, &found_width, &num_stems)) {
       return OTS_FAILURE();
     }
     if (!found_endchar) {
       return OTS_FAILURE();
     }
   }
   return true;
 }
 
 }  // namespace ots
+
+#undef TABLE_NAME
--- a/gfx/ots/src/cff_type2_charstring.h
+++ b/gfx/ots/src/cff_type2_charstring.h
@@ -30,16 +30,17 @@ namespace ots {
 //                      in |char_strings_index|.
 //  fd_select: A map from glyph # to font #.
 //  local_subrs_per_font: A list of Local Subrs associated with FDArrays. Can be
 //                        empty.
 //  local_subrs: A Local Subrs associated with Top DICT. Can be NULL.
 //  cff_table: A buffer which contains actual byte code of charstring, global
 //             subroutines and local subroutines.
 bool ValidateType2CharStringIndex(
+    OpenTypeFile *file,
     const CFFIndex &char_strings_index,
     const CFFIndex &global_subrs_index,
     const std::map<uint16_t, uint8_t> &fd_select,
     const std::vector<CFFIndex *> &local_subrs_per_font,
     const CFFIndex *local_subrs,
     Buffer *cff_table);
 
 // The list of Operators. See Appendix. A in Adobe Technical Note #5177.
--- a/gfx/ots/src/cmap.cc
+++ b/gfx/ots/src/cmap.cc
@@ -213,17 +213,17 @@ bool ParseFormat4(ots::OpenTypeFile *fil
                            ranges[segcount - 1].start_range, ranges[segcount - 1].end_range);
   }
 
   // A format 4 CMAP subtable is complex. To be safe we simulate a lookup of
   // each code-point defined in the table and make sure that they are all valid
   // glyphs and that we don't access anything out-of-bounds.
   for (unsigned i = 0; i < segcount; ++i) {
     for (unsigned cp = ranges[i].start_range; cp <= ranges[i].end_range; ++cp) {
-      const uint16_t code_point = cp;
+      const uint16_t code_point = static_cast<uint16_t>(cp);
       if (ranges[i].id_range_offset == 0) {
         // this is explictly allowed to overflow in the spec
         const uint16_t glyph = code_point + ranges[i].id_delta;
         if (glyph >= num_glyphs) {
           return OTS_FAILURE_MSG("Range glyph reference too high (%d > %d)", glyph, num_glyphs - 1);
         }
       } else {
         const uint16_t range_delta = code_point - ranges[i].start_range;
@@ -861,23 +861,23 @@ bool ots_cmap_serialise(OTSStream *out, 
   const bool have_0514 = file->cmap->subtable_0_5_14.size() != 0;
   const bool have_100 = file->cmap->subtable_1_0_0.size() != 0;
   const bool have_304 = file->cmap->subtable_3_0_4_data != NULL;
   // MS Symbol and MS Unicode tables should not co-exist.
   // See the comment above in 0-0-4 parser.
   const bool have_314 = (!have_304) && file->cmap->subtable_3_1_4_data;
   const bool have_31012 = file->cmap->subtable_3_10_12.size() != 0;
   const bool have_31013 = file->cmap->subtable_3_10_13.size() != 0;
-  const unsigned num_subtables = static_cast<unsigned>(have_034) +
-                                 static_cast<unsigned>(have_0514) +
-                                 static_cast<unsigned>(have_100) +
-                                 static_cast<unsigned>(have_304) +
-                                 static_cast<unsigned>(have_314) +
-                                 static_cast<unsigned>(have_31012) +
-                                 static_cast<unsigned>(have_31013);
+  const uint16_t num_subtables = static_cast<uint16_t>(have_034) +
+                                 static_cast<uint16_t>(have_0514) +
+                                 static_cast<uint16_t>(have_100) +
+                                 static_cast<uint16_t>(have_304) +
+                                 static_cast<uint16_t>(have_314) +
+                                 static_cast<uint16_t>(have_31012) +
+                                 static_cast<uint16_t>(have_31013);
   const off_t table_start = out->Tell();
 
   // Some fonts don't have 3-0-4 MS Symbol nor 3-1-4 Unicode BMP tables
   // (e.g., old fonts for Mac). We don't support them.
   if (!have_304 && !have_314 && !have_034 && !have_31012 && !have_31013) {
     return OTS_FAILURE_MSG("no supported subtables were found");
   }
 
@@ -1001,18 +1001,18 @@ bool ots_cmap_serialise(OTSStream *out, 
   }
 
   const off_t offset_31013 = out->Tell();
   if (have_31013) {
     std::vector<OpenTypeCMAPSubtableRange> &groups
         = file->cmap->subtable_3_10_13;
     const unsigned num_groups = groups.size();
     if (!out->WriteU16(13) ||
-        !out->WriteU32(0) ||
-        !out->WriteU32(num_groups * 12 + 14) ||
+        !out->WriteU16(0) ||
+        !out->WriteU32(num_groups * 12 + 16) ||
         !out->WriteU32(0) ||
         !out->WriteU32(num_groups)) {
       return OTS_FAILURE();
     }
 
     for (unsigned i = 0; i < num_groups; ++i) {
       if (!out->WriteU32(groups[i].start_range) ||
           !out->WriteU32(groups[i].end_range) ||
--- a/gfx/ots/src/gasp.cc
+++ b/gfx/ots/src/gasp.cc
@@ -82,22 +82,24 @@ bool ots_gasp_parse(OpenTypeFile *file, 
 
 bool ots_gasp_should_serialise(OpenTypeFile *file) {
   return file->gasp != NULL;
 }
 
 bool ots_gasp_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeGASP *gasp = file->gasp;
 
-  if (!out->WriteU16(gasp->version) ||
-      !out->WriteU16(gasp->gasp_ranges.size())) {
+  const uint16_t num_ranges = static_cast<uint16_t>(gasp->gasp_ranges.size());
+  if (num_ranges != gasp->gasp_ranges.size() ||
+      !out->WriteU16(gasp->version) ||
+      !out->WriteU16(num_ranges)) {
     return OTS_FAILURE_MSG("failed to write gasp header");
   }
 
-  for (unsigned i = 0; i < gasp->gasp_ranges.size(); ++i) {
+  for (uint16_t i = 0; i < num_ranges; ++i) {
     if (!out->WriteU16(gasp->gasp_ranges[i].first) ||
         !out->WriteU16(gasp->gasp_ranges[i].second)) {
       return OTS_FAILURE_MSG("Failed to write gasp subtable %d", i);
     }
   }
 
   return true;
 }
--- a/gfx/ots/src/hdmx.cc
+++ b/gfx/ots/src/hdmx.cc
@@ -102,23 +102,26 @@ bool ots_hdmx_should_serialise(OpenTypeF
   if (!file->hdmx) return false;
   if (!file->glyf) return false;  // this table is not for CFF fonts.
   return true;
 }
 
 bool ots_hdmx_serialise(OTSStream *out, OpenTypeFile *file) {
   OpenTypeHDMX * const hdmx = file->hdmx;
 
-  if (!out->WriteU16(hdmx->version) ||
-      !out->WriteS16(hdmx->records.size()) ||
+  const int16_t num_recs = static_cast<int16_t>(hdmx->records.size());
+  if (hdmx->records.size() >
+          static_cast<size_t>(std::numeric_limits<int16_t>::max()) ||
+      !out->WriteU16(hdmx->version) ||
+      !out->WriteS16(num_recs) ||
       !out->WriteS32(hdmx->size_device_record)) {
     return OTS_FAILURE_MSG("Failed to write hdmx header");
   }
 
-  for (unsigned i = 0; i < hdmx->records.size(); ++i) {
+  for (int16_t i = 0; i < num_recs; ++i) {
     const OpenTypeHDMXDeviceRecord& rec = hdmx->records[i];
     if (!out->Write(&rec.pixel_size, 1) ||
         !out->Write(&rec.max_width, 1) ||
         !out->Write(&rec.widths[0], rec.widths.size())) {
       return OTS_FAILURE_MSG("Failed to write hdmx record %d", i);
     }
     if ((hdmx->pad_len > 0) &&
         !out->Write((const uint8_t *)"\x00\x00\x00", hdmx->pad_len)) {
--- a/gfx/ots/src/kern.cc
+++ b/gfx/ots/src/kern.cc
@@ -108,18 +108,18 @@ bool ots_kern_parse(OpenTypeFile *file, 
     const uint16_t expected_search_range = (1u << max_pow2) * kFormat0PairSize;
     if (subtable.search_range != expected_search_range) {
       OTS_WARNING("bad search range");
       subtable.search_range = expected_search_range;
     }
     if (subtable.entry_selector != max_pow2) {
       return OTS_FAILURE_MSG("Bad subtable %d entry selector %d", i, subtable.entry_selector);
     }
-    const uint32_t expected_range_shift
-        = kFormat0PairSize * num_pairs - subtable.search_range;
+    const uint16_t expected_range_shift =
+        kFormat0PairSize * num_pairs - subtable.search_range;
     if (subtable.range_shift != expected_range_shift) {
       OTS_WARNING("bad range shift");
       subtable.range_shift = expected_range_shift;
     }
 
     // Read kerning pairs.
     subtable.pairs.reserve(num_pairs);
     uint32_t last_pair = 0;
@@ -156,27 +156,31 @@ bool ots_kern_parse(OpenTypeFile *file, 
 bool ots_kern_should_serialise(OpenTypeFile *file) {
   if (!file->glyf) return false;  // this table is not for CFF fonts.
   return file->kern != NULL;
 }
 
 bool ots_kern_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeKERN *kern = file->kern;
 
-  if (!out->WriteU16(kern->version) ||
-      !out->WriteU16(kern->subtables.size())) {
+  const uint16_t num_subtables = static_cast<uint16_t>(kern->subtables.size());
+  if (num_subtables != kern->subtables.size() ||
+      !out->WriteU16(kern->version) ||
+      !out->WriteU16(num_subtables)) {
     return OTS_FAILURE_MSG("Can't write kern table header");
   }
 
-  for (unsigned i = 0; i < kern->subtables.size(); ++i) {
-    const uint16_t length = 14 + (6 * kern->subtables[i].pairs.size());
-    if (!out->WriteU16(kern->subtables[i].version) ||
-        !out->WriteU16(length) ||
+  for (uint16_t i = 0; i < num_subtables; ++i) {
+    const size_t length = 14 + (6 * kern->subtables[i].pairs.size());
+    if (length > std::numeric_limits<uint16_t>::max() ||
+        !out->WriteU16(kern->subtables[i].version) ||
+        !out->WriteU16(static_cast<uint16_t>(length)) ||
         !out->WriteU16(kern->subtables[i].coverage) ||
-        !out->WriteU16(kern->subtables[i].pairs.size()) ||
+        !out->WriteU16(
+            static_cast<uint16_t>(kern->subtables[i].pairs.size())) ||
         !out->WriteU16(kern->subtables[i].search_range) ||
         !out->WriteU16(kern->subtables[i].entry_selector) ||
         !out->WriteU16(kern->subtables[i].range_shift)) {
       return OTS_FAILURE_MSG("Failed to write kern subtable %d", i);
     }
     for (unsigned j = 0; j < kern->subtables[i].pairs.size(); ++j) {
       if (!out->WriteU16(kern->subtables[i].pairs[j].left) ||
           !out->WriteU16(kern->subtables[i].pairs[j].right) ||
--- a/gfx/ots/src/loca.cc
+++ b/gfx/ots/src/loca.cc
@@ -73,17 +73,19 @@ bool ots_loca_serialise(OTSStream *out, 
   const OpenTypeHEAD *head = file->head;
 
   if (!head) {
     return OTS_FAILURE_MSG("Missing head table in font needed by loca");
   }
 
   if (head->index_to_loc_format == 0) {
     for (unsigned i = 0; i < loca->offsets.size(); ++i) {
-      if (!out->WriteU16(loca->offsets[i] >> 1)) {
+      const uint16_t offset = static_cast<uint16_t>(loca->offsets[i] >> 1);
+      if ((offset != (loca->offsets[i] >> 1)) ||
+          !out->WriteU16(offset)) {
         return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
       }
     }
   } else {
     for (unsigned i = 0; i < loca->offsets.size(); ++i) {
       if (!out->WriteU32(loca->offsets[i])) {
         return OTS_FAILURE_MSG("Failed to write glyph offset for glyph %d", i);
       }
--- a/gfx/ots/src/ltsh.cc
+++ b/gfx/ots/src/ltsh.cc
@@ -62,21 +62,23 @@ bool ots_ltsh_parse(OpenTypeFile *file, 
 bool ots_ltsh_should_serialise(OpenTypeFile *file) {
   if (!file->glyf) return false;  // this table is not for CFF fonts.
   return file->ltsh != NULL;
 }
 
 bool ots_ltsh_serialise(OTSStream *out, OpenTypeFile *file) {
   const OpenTypeLTSH *ltsh = file->ltsh;
 
-  if (!out->WriteU16(ltsh->version) ||
-      !out->WriteU16(ltsh->ypels.size())) {
+  const uint16_t num_ypels = static_cast<uint16_t>(ltsh->ypels.size());
+  if (num_ypels != ltsh->ypels.size() ||
+      !out->WriteU16(ltsh->version) ||
+      !out->WriteU16(num_ypels)) {
     return OTS_FAILURE_MSG("Failed to write pels size");
   }
-  for (unsigned i = 0; i < ltsh->ypels.size(); ++i) {
+  for (uint16_t i = 0; i < num_ypels; ++i) {
     if (!out->Write(&(ltsh->ypels[i]), 1)) {
       return OTS_FAILURE_MSG("Failed to write pixel size for glyph %d", i);
     }
   }
 
   return true;
 }
 
--- a/gfx/ots/src/metrics.cc
+++ b/gfx/ots/src/metrics.cc
@@ -121,46 +121,52 @@ bool ParseMetricsTable(const ots::OpenTy
   metrics->entries.reserve(num_metrics);
   for (unsigned i = 0; i < num_metrics; ++i) {
     uint16_t adv = 0;
     int16_t sb = 0;
     if (!table->ReadU16(&adv) || !table->ReadS16(&sb)) {
       return OTS_FAILURE_MSG("Failed to read metric %d", i);
     }
 
+    // This check is bogus, see https://github.com/khaledhosny/ots/issues/36
+#if 0
     // Since so many fonts don't have proper value on |adv| and |sb|,
     // we should not call ots_failure() here. For example, about 20% of fonts
     // in http://www.princexml.com/fonts/ (200+ fonts) fails these tests.
     if (adv > header->adv_width_max) {
       OTS_WARNING("bad adv: %u > %u", adv, header->adv_width_max);
       adv = header->adv_width_max;
     }
 
     if (sb < header->min_sb1) {
       OTS_WARNING("bad sb: %d < %d", sb, header->min_sb1);
       sb = header->min_sb1;
     }
+#endif
 
     metrics->entries.push_back(std::make_pair(adv, sb));
   }
 
   metrics->sbs.reserve(num_sbs);
   for (unsigned i = 0; i < num_sbs; ++i) {
     int16_t sb;
     if (!table->ReadS16(&sb)) {
       // Some Japanese fonts (e.g., mona.ttf) fail this test.
       return OTS_FAILURE_MSG("Failed to read side bearing %d", i + num_metrics);
     }
 
+    // This check is bogus, see https://github.com/khaledhosny/ots/issues/36
+#if 0
     if (sb < header->min_sb1) {
       // The same as above. Three fonts in http://www.fontsquirrel.com/fontface
       // (e.g., Notice2Std.otf) have weird lsb values.
       OTS_WARNING("bad lsb: %d < %d", sb, header->min_sb1);
       sb = header->min_sb1;
     }
+#endif
 
     metrics->sbs.push_back(sb);
   }
 
   return true;
 }
 
 bool SerialiseMetricsTable(const ots::OpenTypeFile *file,
--- a/gfx/ots/src/name.cc
+++ b/gfx/ots/src/name.cc
@@ -195,17 +195,17 @@ bool ots_name_parse(OpenTypeFile* file, 
   // check existence of required name strings (synthesize if necessary)
   //  [0 - copyright - skip]
   //   1 - family
   //   2 - subfamily
   //  [3 - unique ID - skip]
   //   4 - full name
   //   5 - version
   //   6 - postscript name
-  static const unsigned kStdNameCount = 7;
+  static const uint16_t kStdNameCount = 7;
   static const char* kStdNames[kStdNameCount] = {
     NULL,
     "OTS derived font",
     "Unspecified",
     NULL,
     "OTS derived font",
     "1.000",
     "OTS-derived-font"
@@ -232,17 +232,17 @@ bool ots_name_parse(OpenTypeFile* file, 
       continue;
     }
     if (name_iter->platform_id == 3) {
       win_name[id] = true;
       continue;
     }
   }
 
-  for (unsigned i = 0; i < kStdNameCount; ++i) {
+  for (uint16_t i = 0; i < kStdNameCount; ++i) {
     if (kStdNames[i] == NULL) {
       continue;
     }
     if (!mac_name[i]) {
       NameRecord rec(1 /* platform_id */, 0 /* encoding_id */,
                      0 /* language_id */ , i /* name_id */);
       rec.text.assign(kStdNames[i]);
       name->names.push_back(rec);
@@ -266,59 +266,63 @@ bool ots_name_parse(OpenTypeFile* file, 
 
 bool ots_name_should_serialise(OpenTypeFile* file) {
   return file->name != NULL;
 }
 
 bool ots_name_serialise(OTSStream* out, OpenTypeFile* file) {
   const OpenTypeNAME* name = file->name;
 
-  uint16_t name_count = name->names.size();
-  uint16_t lang_tag_count = name->lang_tags.size();
+  uint16_t name_count = static_cast<uint16_t>(name->names.size());
+  uint16_t lang_tag_count = static_cast<uint16_t>(name->lang_tags.size());
   uint16_t format = 0;
   size_t string_offset = 6 + name_count * 12;
 
   if (name->lang_tags.size() > 0) {
     // lang tags require a format-1 name table
     format = 1;
     string_offset += 2 + lang_tag_count * 4;
   }
   if (string_offset > 0xffff) {
     return OTS_FAILURE_MSG("Bad string offset %ld", string_offset);
   }
   if (!out->WriteU16(format) ||
       !out->WriteU16(name_count) ||
-      !out->WriteU16(string_offset)) {
+      !out->WriteU16(static_cast<uint16_t>(string_offset))) {
     return OTS_FAILURE_MSG("Failed to write name header");
   }
 
   std::string string_data;
   for (std::vector<NameRecord>::const_iterator name_iter = name->names.begin();
        name_iter != name->names.end(); name_iter++) {
     const NameRecord& rec = *name_iter;
-    if (!out->WriteU16(rec.platform_id) ||
+    if (string_data.size() + rec.text.size() >
+            std::numeric_limits<uint16_t>::max() ||
+        !out->WriteU16(rec.platform_id) ||
         !out->WriteU16(rec.encoding_id) ||
         !out->WriteU16(rec.language_id) ||
         !out->WriteU16(rec.name_id) ||
-        !out->WriteU16(rec.text.size()) ||
-        !out->WriteU16(string_data.size()) ) {
+        !out->WriteU16(static_cast<uint16_t>(rec.text.size())) ||
+        !out->WriteU16(static_cast<uint16_t>(string_data.size())) ) {
       return OTS_FAILURE_MSG("Faile to write name entry");
     }
     string_data.append(rec.text);
   }
 
   if (format == 1) {
     if (!out->WriteU16(lang_tag_count)) {
       return OTS_FAILURE_MSG("Faile to write language tag count");
     }
     for (std::vector<std::string>::const_iterator tag_iter =
              name->lang_tags.begin();
          tag_iter != name->lang_tags.end(); tag_iter++) {
-      if (!out->WriteU16(tag_iter->size()) ||
-          !out->WriteU16(string_data.size())) {
+      if (string_data.size() + tag_iter->size() >
+              std::numeric_limits<uint16_t>::max() ||
+          !out->WriteU16(static_cast<uint16_t>(tag_iter->size())) ||
+          !out->WriteU16(static_cast<uint16_t>(string_data.size()))) {
         return OTS_FAILURE_MSG("Failed to write string");
       }
       string_data.append(*tag_iter);
     }
   }
 
   if (!out->Write(string_data.data(), string_data.size())) {
     return OTS_FAILURE_MSG("Faile to write string data");
--- a/gfx/ots/src/ots.cc
+++ b/gfx/ots/src/ots.cc
@@ -206,18 +206,18 @@ bool ProcessTTF(ots::OpenTypeFile *heade
   // entry_selector is Log2(maximum power of 2 <= numTables)
   if (header->entry_selector != max_pow2) {
     return OTS_FAILURE_MSG_HDR("incorrect entrySelector for table directory");
   }
 
   // range_shift is NumTables x 16-searchRange. We know that 16*num_tables
   // doesn't over flow because we range checked it above. Also, we know that
   // it's > header->search_range by construction of search_range.
-  const uint32_t expected_range_shift
-      = 16 * header->num_tables - header->search_range;
+  const uint16_t expected_range_shift =
+      16 * header->num_tables - header->search_range;
   if (header->range_shift != expected_range_shift) {
     OTS_FAILURE_MSG_HDR("bad range shift");
     header->range_shift = expected_range_shift;  // the same as above.
   }
 
   // Next up is the list of tables.
   std::vector<OpenTypeTable> tables;
 
@@ -606,17 +606,17 @@ bool ProcessGeneric(ots::OpenTypeFile *h
   } else {
     if (!header->glyf || !header->loca) {
       // No TrueType glyph found.
       // Note: bitmap-only fonts are not supported.
       return OTS_FAILURE_MSG_HDR("neither PS nor TT glyphs present");
     }
   }
 
-  unsigned num_output_tables = 0;
+  uint16_t num_output_tables = 0;
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) {
       break;
     }
 
     if (table_parsers[i].should_serialise(header)) {
       num_output_tables++;
     }
@@ -625,17 +625,17 @@ bool ProcessGeneric(ots::OpenTypeFile *h
   for (std::map<uint32_t, OpenTypeTable>::const_iterator it = table_map.begin();
        it != table_map.end(); ++it) {
     ots::TableAction action = GetTableAction(header, it->first);
     if (action == ots::TABLE_ACTION_PASSTHRU) {
       num_output_tables++;
     }
   }
 
-  unsigned max_pow2 = 0;
+  uint16_t max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= num_output_tables) {
     max_pow2++;
   }
   const uint16_t output_search_range = (1u << max_pow2) << 4;
 
   // most of the errors here are highly unlikely - they'd only occur if the
   // output stream returns a failure, e.g. lack of space to write
   output->ResetChecksum();
@@ -826,16 +826,22 @@ bool OTSContext::Process(OTSStream *outp
 
   for (unsigned i = 0; ; ++i) {
     if (table_parsers[i].parse == NULL) break;
     table_parsers[i].free(&header);
   }
   return result;
 }
 
+// For backward compatibility
+bool Process(OTSStream *output, const uint8_t *data, size_t length) {
+  static OTSContext context;
+  return context.Process(output, data, length);
+}
+
 #if !defined(_MSC_VER) && defined(OTS_DEBUG)
 bool Failure(const char *f, int l, const char *fn) {
   if (g_debug_output) {
     std::fprintf(stderr, "ERROR at %s:%d (%s)\n", f, l, fn);
     std::fflush(stderr);
   }
   return false;
 }
--- a/gfx/ots/src/ots.h
+++ b/gfx/ots/src/ots.h
@@ -29,38 +29,41 @@ namespace ots {
 bool Failure(const char *f, int l, const char *fn);
 #endif
 
 // All OTS_FAILURE_* macros ultimately evaluate to 'false', just like the original
 // message-less OTS_FAILURE(), so that the current parser will return 'false' as
 // its result (indicating a failure).
 
 #if defined(_MSC_VER) || !defined(OTS_DEBUG)
-#define OTS_MESSAGE_(otf_,...) \
-  (otf_)->context->Message(__VA_ARGS__)
+#define OTS_MESSAGE_(level,otf_,...) \
+  (otf_)->context->Message(level,__VA_ARGS__)
 #else
-#define OTS_MESSAGE_(otf_,...) \
+#define OTS_MESSAGE_(level,otf_,...) \
   OTS_FAILURE(), \
-  (otf_)->context->Message(__VA_ARGS__)
+  (otf_)->context->Message(level,__VA_ARGS__)
 #endif
 
 // Generate a simple message
 #define OTS_FAILURE_MSG_(otf_,...) \
-  (OTS_MESSAGE_(otf_,__VA_ARGS__), false)
+  (OTS_MESSAGE_(0,otf_,__VA_ARGS__), false)
+
+#define OTS_WARNING_MSG_(otf_,...) \
+  OTS_MESSAGE_(1,otf_,__VA_ARGS__)
 
 // Generate a message with an associated table tag
 #define OTS_FAILURE_MSG_TAG_(otf_,msg_,tag_) \
-  (OTS_MESSAGE_(otf_,"%4.4s: %s", tag_, msg_), false)
+  (OTS_MESSAGE_(0,otf_,"%4.4s: %s", tag_, msg_), false)
 
-// Convenience macro for use in files that only handle a single table tag,
+// Convenience macros for use in files that only handle a single table tag,
 // defined as TABLE_NAME at the top of the file; the 'file' variable is
 // expected to be the current OpenTypeFile pointer.
 #define OTS_FAILURE_MSG(...) OTS_FAILURE_MSG_(file, TABLE_NAME ": " __VA_ARGS__)
 
-#define OTS_WARNING OTS_FAILURE_MSG
+#define OTS_WARNING(...) OTS_WARNING_MSG_(file, TABLE_NAME ": " __VA_ARGS__)
 
 // -----------------------------------------------------------------------------
 // Buffer helper class
 //
 // This class perform some trival buffer operations while checking for
 // out-of-bounds errors. As a family they return false if anything is amiss,
 // updating the current offset otherwise.
 // -----------------------------------------------------------------------------
--- a/gfx/ots/src/post.cc
+++ b/gfx/ots/src/post.cc
@@ -143,31 +143,35 @@ bool ots_post_serialise(OTSStream *out, 
       !out->WriteU32(0)) {
     return OTS_FAILURE_MSG("Failed to write post header");
   }
 
   if (post->version != 0x00020000) {
     return true;  // v1.0 and v3.0 does not have glyph names.
   }
 
-  if (!out->WriteU16(post->glyph_name_index.size())) {
+  const uint16_t num_indexes =
+      static_cast<uint16_t>(post->glyph_name_index.size());
+  if (num_indexes != post->glyph_name_index.size() ||
+      !out->WriteU16(num_indexes)) {
     return OTS_FAILURE_MSG("Failed to write number of indices");
   }
 
-  for (unsigned i = 0; i < post->glyph_name_index.size(); ++i) {
+  for (uint16_t i = 0; i < num_indexes; ++i) {
     if (!out->WriteU16(post->glyph_name_index[i])) {
       return OTS_FAILURE_MSG("Failed to write name index %d", i);
     }
   }
 
   // Now we just have to write out the strings in the correct order
   for (unsigned i = 0; i < post->names.size(); ++i) {
     const std::string& s = post->names[i];
-    const uint8_t string_length = s.size();
-    if (!out->Write(&string_length, 1)) {
+    const uint8_t string_length = static_cast<uint8_t>(s.size());
+    if (string_length != s.size() ||
+        !out->Write(&string_length, 1)) {
       return OTS_FAILURE_MSG("Failed to write string %d", i);
     }
     // Some ttf fonts (e.g., frank.ttf on Windows Vista) have zero-length name.
     // We allow them.
     if (string_length > 0 && !out->Write(s.data(), string_length)) {
       return OTS_FAILURE_MSG("Failed to write string length for string %d", i);
     }
   }
--- a/gfx/ots/src/vorg.cc
+++ b/gfx/ots/src/vorg.cc
@@ -70,25 +70,27 @@ bool ots_vorg_parse(OpenTypeFile *file, 
 
 bool ots_vorg_should_serialise(OpenTypeFile *file) {
   if (!file->cff) return false;  // this table is not for fonts with TT glyphs.
   return file->vorg != NULL;
 }
 
 bool ots_vorg_serialise(OTSStream *out, OpenTypeFile *file) {
   OpenTypeVORG * const vorg = file->vorg;
-
-  if (!out->WriteU16(vorg->major_version) ||
+  
+  const uint16_t num_metrics = static_cast<uint16_t>(vorg->metrics.size());
+  if (num_metrics != vorg->metrics.size() ||
+      !out->WriteU16(vorg->major_version) ||
       !out->WriteU16(vorg->minor_version) ||
       !out->WriteS16(vorg->default_vert_origin_y) ||
-      !out->WriteU16(vorg->metrics.size())) {
+      !out->WriteU16(num_metrics)) {
     return OTS_FAILURE_MSG("Failed to write table header");
   }
 
-  for (unsigned i = 0; i < vorg->metrics.size(); ++i) {
+  for (uint16_t i = 0; i < num_metrics; ++i) {
     const OpenTypeVORGMetrics& rec = vorg->metrics[i];
     if (!out->WriteU16(rec.glyph_index) ||
         !out->WriteS16(rec.vert_origin_y)) {
       return OTS_FAILURE_MSG("Failed to write record %d", i);
     }
   }
 
   return true;
--- a/gfx/ots/src/woff2.cc
+++ b/gfx/ots/src/woff2.cc
@@ -16,22 +16,22 @@
 #include "opentype-sanitiser.h"
 #include "ots-memory-stream.h"
 #include "ots.h"
 #include "woff2.h"
 
 namespace {
 
 // simple glyph flags
-const int kGlyfOnCurve = 1 << 0;
-const int kGlyfXShort = 1 << 1;
-const int kGlyfYShort = 1 << 2;
-const int kGlyfRepeat = 1 << 3;
-const int kGlyfThisXIsSame = 1 << 4;
-const int kGlyfThisYIsSame = 1 << 5;
+const uint8_t kGlyfOnCurve = 1 << 0;
+const uint8_t kGlyfXShort = 1 << 1;
+const uint8_t kGlyfYShort = 1 << 2;
+const uint8_t kGlyfRepeat = 1 << 3;
+const uint8_t kGlyfThisXIsSame = 1 << 4;
+const uint8_t kGlyfThisYIsSame = 1 << 5;
 
 // composite glyph flags
 const int FLAG_ARG_1_AND_2_ARE_WORDS = 1 << 0;
 const int FLAG_WE_HAVE_A_SCALE = 1 << 3;
 const int FLAG_MORE_COMPONENTS = 1 << 5;
 const int FLAG_WE_HAVE_AN_X_AND_Y_SCALE = 1 << 6;
 const int FLAG_WE_HAVE_A_TWO_BY_TWO = 1 << 7;
 const int FLAG_WE_HAVE_INSTRUCTIONS = 1 << 8;
@@ -117,18 +117,18 @@ const uint32_t kKnownTags[] = {
   TAG('S', 'i', 'l', 'f'),  // 58
   TAG('G', 'l', 'a', 't'),  // 59
   TAG('G', 'l', 'o', 'c'),  // 60
   TAG('F', 'e', 'a', 't'),  // 61
   TAG('S', 'i', 'l', 'l'),  // 62
 };
 
 struct Point {
-  int x;
-  int y;
+  int16_t x;
+  int16_t y;
   bool on_curve;
 };
 
 struct Table {
   uint32_t tag;
   uint32_t flags;
   uint32_t src_offset;
   uint32_t src_length;
@@ -144,21 +144,21 @@ struct Table {
         src_offset(0),
         src_length(0),
         transform_length(0),
         dst_offset(0),
         dst_length(0) {}
 };
 
 // Based on section 6.1.1 of MicroType Express draft spec
-bool Read255UShort(ots::Buffer* buf, unsigned int* value) {
-  static const int kWordCode = 253;
-  static const int kOneMoreByteCode2 = 254;
-  static const int kOneMoreByteCode1 = 255;
-  static const int kLowestUCode = 253;
+bool Read255UShort(ots::Buffer* buf, uint16_t* value) {
+  static const uint8_t kWordCode = 253;
+  static const uint8_t kOneMoreByteCode2 = 254;
+  static const uint8_t kOneMoreByteCode1 = 255;
+  static const uint8_t kLowestUCode = 253;
   uint8_t code = 0;
   if (!buf->ReadU8(&code)) {
     return OTS_FAILURE();
   }
   if (code == kWordCode) {
     uint16_t result = 0;
     if (!buf->ReadU16(&result)) {
       return OTS_FAILURE();
@@ -206,25 +206,25 @@ bool ReadBase128(ots::Buffer* buf, uint3
   return OTS_FAILURE();
 }
 
 // Caller must ensure that buffer overrun won't happen.
 // TODO(ksakamaoto): Consider creating 'writer' version of the Buffer class
 // and use it across the code.
 size_t StoreU32(uint8_t* dst, size_t offset, uint32_t x) {
   dst[offset] = x >> 24;
-  dst[offset + 1] = x >> 16;
-  dst[offset + 2] = x >> 8;
-  dst[offset + 3] = x;
+  dst[offset + 1] = (x >> 16) & 0xff;
+  dst[offset + 2] = (x >> 8) & 0xff;
+  dst[offset + 3] = x & 0xff;
   return offset + 4;
 }
 
-size_t Store16(uint8_t* dst, size_t offset, int x) {
+size_t StoreU16(uint8_t* dst, size_t offset, uint16_t x) {
   dst[offset] = x >> 8;
-  dst[offset + 1] = x;
+  dst[offset + 1] = x & 0xff;
   return offset + 2;
 }
 
 int WithSign(int flag, int baseval) {
   assert(0 <= baseval && baseval < 65536);
   return (flag & 1) ? baseval : -baseval;
 }
 
@@ -285,43 +285,43 @@ bool TripletDecode(const uint8_t* flags_
           (in[triplet_index + 2] << 8) + in[triplet_index + 3]);
     }
     triplet_index += n_data_bytes;
     // Possible overflow but coordinate values are not security sensitive
     x += dx;
     y += dy;
     result->push_back(Point());
     Point& back = result->back();
-    back.x = x;
-    back.y = y;
+    back.x = static_cast<int16_t>(x);
+    back.y = static_cast<int16_t>(y);
     back.on_curve = on_curve;
   }
   *in_bytes_consumed = triplet_index;
   return true;
 }
 
 // This function stores just the point data. On entry, dst points to the
 // beginning of a simple glyph. Returns true on success.
 bool StorePoints(const std::vector<Point>& points,
     unsigned int n_contours, unsigned int instruction_length,
     uint8_t* dst, size_t dst_size, size_t* glyph_size) {
   // I believe that n_contours < 65536, in which case this is safe. However, a
   // comment and/or an assert would be good.
   unsigned int flag_offset = kEndPtsOfContoursOffset + 2 * n_contours + 2 +
     instruction_length;
-  int last_flag = -1;
-  int repeat_count = 0;
+  uint8_t last_flag = 0xff;
+  uint8_t repeat_count = 0;
   int last_x = 0;
   int last_y = 0;
   unsigned int x_bytes = 0;
   unsigned int y_bytes = 0;
 
   for (size_t i = 0; i < points.size(); ++i) {
     const Point& point = points.at(i);
-    int flag = point.on_curve ? kGlyfOnCurve : 0;
+    uint8_t flag = point.on_curve ? kGlyfOnCurve : 0;
     int dx = point.x - last_x;
     int dy = point.y - last_y;
     if (dx == 0) {
       flag |= kGlyfThisXIsSame;
     } else if (dx > -256 && dx < 256) {
       flag |= kGlyfXShort | (dx > 0 ? kGlyfThisXIsSame : 0);
       x_bytes += 1;
     } else {
@@ -374,57 +374,57 @@ bool StorePoints(const std::vector<Point
   int y_offset = flag_offset + x_bytes;
   last_x = 0;
   last_y = 0;
   for (size_t i = 0; i < points.size(); ++i) {
     int dx = points.at(i).x - last_x;
     if (dx == 0) {
       // pass
     } else if (dx > -256 && dx < 256) {
-      dst[x_offset++] = std::abs(dx);
+      dst[x_offset++] = static_cast<uint8_t>(std::abs(dx));
     } else {
       // will always fit for valid input, but overflow is harmless
-      x_offset = Store16(dst, x_offset, dx);
+      x_offset = StoreU16(dst, x_offset, static_cast<uint16_t>(dx));
     }
     last_x += dx;
     int dy = points.at(i).y - last_y;
     if (dy == 0) {
       // pass
     } else if (dy > -256 && dy < 256) {
-      dst[y_offset++] = std::abs(dy);
+      dst[y_offset++] = static_cast<uint8_t>(std::abs(dy));
     } else {
-      y_offset = Store16(dst, y_offset, dy);
+      y_offset = StoreU16(dst, y_offset, static_cast<uint16_t>(dy));
     }
     last_y += dy;
   }
   *glyph_size = y_offset;
   return true;
 }
 
 // Compute the bounding box of the coordinates, and store into a glyf buffer.
 // A precondition is that there are at least 10 bytes available.
 void ComputeBbox(const std::vector<Point>& points, uint8_t* dst) {
-  int x_min = 0;
-  int y_min = 0;
-  int x_max = 0;
-  int y_max = 0;
+  int16_t x_min = 0;
+  int16_t y_min = 0;
+  int16_t x_max = 0;
+  int16_t y_max = 0;
 
   for (size_t i = 0; i < points.size(); ++i) {
-    int x = points.at(i).x;
-    int y = points.at(i).y;
+    int16_t x = points.at(i).x;
+    int16_t y = points.at(i).y;
     if (i == 0 || x < x_min) x_min = x;
     if (i == 0 || x > x_max) x_max = x;
     if (i == 0 || y < y_min) y_min = y;
     if (i == 0 || y > y_max) y_max = y;
   }
   size_t offset = 2;
-  offset = Store16(dst, offset, x_min);
-  offset = Store16(dst, offset, y_min);
-  offset = Store16(dst, offset, x_max);
-  offset = Store16(dst, offset, y_max);
+  offset = StoreU16(dst, offset, x_min);
+  offset = StoreU16(dst, offset, y_min);
+  offset = StoreU16(dst, offset, x_max);
+  offset = StoreU16(dst, offset, y_max);
 }
 
 // Process entire bbox stream. This is done as a separate pass to allow for
 // composite bbox computations (an optional more aggressive transform).
 bool ProcessBboxStream(ots::Buffer* bbox_stream, unsigned int n_glyphs,
     const std::vector<uint32_t>& loca_values, uint8_t* glyf_buf,
     size_t glyf_buf_length) {
   const uint8_t* buf = bbox_stream->buffer();
@@ -481,17 +481,17 @@ bool ProcessComposite(ots::Buffer* compo
     if (!composite_stream->Skip(arg_size)) {
       return OTS_FAILURE();
     }
   }
   size_t composite_glyph_size = composite_stream->offset() - start_offset;
   if (composite_glyph_size + kCompositeGlyphBegin > dst_size) {
     return OTS_FAILURE();
   }
-  Store16(dst, 0, 0xffff);  // nContours = -1 for composite glyph
+  StoreU16(dst, 0, 0xffff);  // nContours = -1 for composite glyph
   std::memcpy(dst + kCompositeGlyphBegin,
       composite_stream->buffer() + start_offset,
       composite_glyph_size);
   *glyph_size = kCompositeGlyphBegin + composite_glyph_size;
   *have_instructions = we_have_instructions;
   return true;
 }
 
@@ -508,17 +508,17 @@ bool StoreLoca(const std::vector<uint32_
     return OTS_FAILURE();
   }
   size_t offset = 0;
   for (size_t i = 0; i < loca_values.size(); ++i) {
     uint32_t value = loca_values.at(i);
     if (index_format) {
       offset = StoreU32(dst, offset, value);
     } else {
-      offset = Store16(dst, offset, value >> 1);
+      offset = StoreU16(dst, offset, static_cast<uint16_t>(value >> 1));
     }
   }
   return true;
 }
 
 // Reconstruct entire glyf table based on transformed original
 bool ReconstructGlyf(const uint8_t* data, size_t data_size,
     uint8_t* dst, size_t dst_size,
@@ -559,67 +559,67 @@ bool ReconstructGlyf(const uint8_t* data
   ots::Buffer glyph_stream(substreams.at(3).first, substreams.at(3).second);
   ots::Buffer composite_stream(substreams.at(4).first, substreams.at(4).second);
   ots::Buffer bbox_stream(substreams.at(5).first, substreams.at(5).second);
   ots::Buffer instruction_stream(substreams.at(6).first,
                                  substreams.at(6).second);
 
   std::vector<uint32_t> loca_values;
   loca_values.reserve(num_glyphs + 1);
-  std::vector<unsigned int> n_points_vec;
+  std::vector<uint16_t> n_points_vec;
   std::vector<Point> points;
   uint32_t loca_offset = 0;
   for (unsigned int i = 0; i < num_glyphs; ++i) {
     size_t glyph_size = 0;
     uint16_t n_contours = 0;
     if (!n_contour_stream.ReadU16(&n_contours)) {
       return OTS_FAILURE();
     }
     uint8_t* glyf_dst = dst + loca_offset;
     size_t glyf_dst_size = dst_size - loca_offset;
     if (n_contours == 0xffff) {
       // composite glyph
       bool have_instructions = false;
-      unsigned int instruction_size = 0;
+      uint16_t instruction_size = 0;
       if (!ProcessComposite(&composite_stream, glyf_dst, glyf_dst_size,
             &glyph_size, &have_instructions)) {
         return OTS_FAILURE();
       }
       if (have_instructions) {
         if (!Read255UShort(&glyph_stream, &instruction_size)) {
           return OTS_FAILURE();
         }
         // No integer overflow here (instruction_size < 2^16).
-        if (instruction_size + 2 > glyf_dst_size - glyph_size) {
+        if (instruction_size + 2U > glyf_dst_size - glyph_size) {
           return OTS_FAILURE();
         }
-        Store16(glyf_dst, glyph_size, instruction_size);
+        StoreU16(glyf_dst, glyph_size, instruction_size);
         if (!instruction_stream.Read(glyf_dst + glyph_size + 2,
               instruction_size)) {
           return OTS_FAILURE();
         }
         glyph_size += instruction_size + 2;
       }
     } else if (n_contours > 0) {
       // simple glyph
       n_points_vec.clear();
       points.clear();
-      unsigned int total_n_points = 0;
-      unsigned int n_points_contour;
-      for (unsigned int j = 0; j < n_contours; ++j) {
+      uint32_t total_n_points = 0;
+      uint16_t n_points_contour;
+      for (uint32_t j = 0; j < n_contours; ++j) {
         if (!Read255UShort(&n_points_stream, &n_points_contour)) {
           return OTS_FAILURE();
         }
         n_points_vec.push_back(n_points_contour);
         if (total_n_points + n_points_contour < total_n_points) {
           return OTS_FAILURE();
         }
         total_n_points += n_points_contour;
       }
-      unsigned int flag_size = total_n_points;
+      uint32_t flag_size = total_n_points;
       if (flag_size > flag_stream.length() - flag_stream.offset()) {
         return OTS_FAILURE();
       }
       const uint8_t* flags_buf = flag_stream.buffer() + flag_stream.offset();
       const uint8_t* triplet_buf = glyph_stream.buffer() +
         glyph_stream.offset();
       size_t triplet_size = glyph_stream.length() - glyph_stream.offset();
       size_t triplet_bytes_consumed = 0;
@@ -627,44 +627,44 @@ bool ReconstructGlyf(const uint8_t* data
             &points, &triplet_bytes_consumed)) {
         return OTS_FAILURE();
       }
       const uint32_t header_and_endpts_contours_size =
           kEndPtsOfContoursOffset + 2 * n_contours;
       if (glyf_dst_size < header_and_endpts_contours_size) {
         return OTS_FAILURE();
       }
-      Store16(glyf_dst, 0, n_contours);
+      StoreU16(glyf_dst, 0, n_contours);
       ComputeBbox(points, glyf_dst);
       size_t endpts_offset = kEndPtsOfContoursOffset;
       int end_point = -1;
       for (unsigned int contour_ix = 0; contour_ix < n_contours; ++contour_ix) {
         end_point += n_points_vec.at(contour_ix);
         if (end_point >= 65536) {
           return OTS_FAILURE();
         }
-        endpts_offset = Store16(glyf_dst, endpts_offset, end_point);
+        endpts_offset = StoreU16(glyf_dst, endpts_offset, static_cast<uint16_t>(end_point));
       }
       if (!flag_stream.Skip(flag_size)) {
         return OTS_FAILURE();
       }
       if (!glyph_stream.Skip(triplet_bytes_consumed)) {
         return OTS_FAILURE();
       }
-      unsigned int instruction_size;
+      uint16_t instruction_size;
       if (!Read255UShort(&glyph_stream, &instruction_size)) {
         return OTS_FAILURE();
       }
       // No integer overflow here (instruction_size < 2^16).
       if (glyf_dst_size - header_and_endpts_contours_size <
-          instruction_size + 2) {
+          instruction_size + 2U) {
         return OTS_FAILURE();
       }
       uint8_t* instruction_dst = glyf_dst + header_and_endpts_contours_size;
-      Store16(instruction_dst, 0, instruction_size);
+      StoreU16(instruction_dst, 0, instruction_size);
       if (!instruction_stream.Read(instruction_dst + 2, instruction_size)) {
         return OTS_FAILURE();
       }
       if (!StorePoints(points, n_contours, instruction_size,
             glyf_dst, glyf_dst_size, &glyph_size)) {
         return OTS_FAILURE();
       }
     } else {
@@ -910,24 +910,24 @@ bool ConvertWOFF2ToTTF(uint8_t* result, 
     return OTS_FAILURE();
   }
   uint64_t src_offset = file.offset();
   uint64_t dst_offset = kSfntHeaderSize +
       kSfntEntrySize * static_cast<uint64_t>(num_tables);
   uint64_t uncompressed_sum = 0;
   for (uint16_t i = 0; i < num_tables; ++i) {
     Table* table = &tables.at(i);
-    table->src_offset = src_offset;
+    table->src_offset = static_cast<uint32_t>(src_offset);
     table->src_length = (i == 0 ? compressed_length : 0);
     src_offset += table->src_length;
     if (src_offset > std::numeric_limits<uint32_t>::max()) {
       return OTS_FAILURE();
     }
     src_offset = ots::Round4(src_offset);
-    table->dst_offset = dst_offset;
+    table->dst_offset = static_cast<uint32_t>(dst_offset);
     dst_offset += table->dst_length;
     if (dst_offset > std::numeric_limits<uint32_t>::max()) {
       return OTS_FAILURE();
     }
     dst_offset = ots::Round4(dst_offset);
     if ((table->flags & kCompressionTypeMask) != kCompressionTypeNone) {
       uncompressed_sum += table->src_length;
       if (uncompressed_sum > std::numeric_limits<uint32_t>::max()) {
@@ -946,25 +946,25 @@ bool ConvertWOFF2ToTTF(uint8_t* result, 
   const uint32_t sfnt_header_and_table_directory_size = 12 + 16 * num_tables;
   if (sfnt_header_and_table_directory_size > result_length) {
     return OTS_FAILURE();
   }
 
   // Start building the font
   size_t offset = 0;
   offset = StoreU32(result, offset, flavor);
-  offset = Store16(result, offset, num_tables);
-  unsigned max_pow2 = 0;
+  offset = StoreU16(result, offset, num_tables);
+  uint8_t max_pow2 = 0;
   while (1u << (max_pow2 + 1) <= num_tables) {
     max_pow2++;
   }
   const uint16_t output_search_range = (1u << max_pow2) << 4;
-  offset = Store16(result, offset, output_search_range);
-  offset = Store16(result, offset, max_pow2);
-  offset = Store16(result, offset, (num_tables << 4) - output_search_range);
+  offset = StoreU16(result, offset, output_search_range);
+  offset = StoreU16(result, offset, max_pow2);
+  offset = StoreU16(result, offset, (num_tables << 4) - output_search_range);
   for (uint16_t i = 0; i < num_tables; ++i) {
     const Table* table = &tables.at(i);
     offset = StoreU32(result, offset, table->tag);
     offset = StoreU32(result, offset, 0);  // checksum, to fill in later
     offset = StoreU32(result, offset, table->dst_offset);
     offset = StoreU32(result, offset, table->dst_length);
   }
   std::vector<uint8_t> uncompressed_buf;
@@ -996,18 +996,19 @@ bool ConvertWOFF2ToTTF(uint8_t* result, 
         if (total_size > std::numeric_limits<uint32_t>::max()) {
           return OTS_FAILURE();
         }
       }
       // Enforce same 30M limit on uncompressed tables as OTS
       if (total_size > 30 * 1024 * 1024) {
         return OTS_FAILURE();
       }
-      uncompressed_buf.resize(total_size);
-      if (!Woff2Uncompress(&uncompressed_buf[0], total_size,
+      const size_t total_size_size_t = static_cast<size_t>(total_size);
+      uncompressed_buf.resize(total_size_size_t);
+      if (!Woff2Uncompress(&uncompressed_buf[0], total_size_size_t,
           src_buf, compressed_length, compression_type)) {
         return OTS_FAILURE();
       }
       transform_buf = &uncompressed_buf[0];
       continue_valid = true;
     } else {
       return OTS_FAILURE();
     }
--- a/gfx/ots/sync.sh
+++ b/gfx/ots/sync.sh
@@ -7,22 +7,25 @@ if [ $# = 0 ] ; then
     exit 1
 fi
 
 echo "Updating LICENSE..."
 cp $1/LICENSE .
 
 echo "Updating src..."
 cd src
-ls --ignore moz.build | xargs rm -rf
+ls | fgrep -v moz.build | xargs rm -rf
 cp -r $1/src/* .
 cd ..
 
 echo "Updating include..."
 rm -rf include/
 cp -r $1/include .
 
 echo "Updating README.mozilla..."
 REVISION=`cd $1; git log | head -1 | sed "s/commit //"`
-sed "s/\(Current revision: \).*/\1$REVISION/" -i README.mozilla
+sed -e "s/\(Current revision: \).*/\1$REVISION/" -i "" README.mozilla
 
 echo "Applying ots-visibility.patch..."
 patch -p3 < ots-visibility.patch
+
+echo "Applying ots-brotli-path.patch..."
+patch -p3 < ots-brotli-path.patch
--- a/gfx/thebes/gfxFontUtils.cpp
+++ b/gfx/thebes/gfxFontUtils.cpp
@@ -701,26 +701,26 @@ gfxFontUtils::MapCharToGlyphFormat12(con
     return 0;
 }
 
 namespace {
 
 struct Format14CmapWrapper
 {
     const Format14Cmap& mCmap14;
-    Format14CmapWrapper(const Format14Cmap& cmap14) : mCmap14(cmap14) {}
+    explicit Format14CmapWrapper(const Format14Cmap& cmap14) : mCmap14(cmap14) {}
     uint32_t operator[](size_t index) const {
         return mCmap14.varSelectorRecords[index].varSelector;
     }
 };
 
 struct NonDefUVSTableWrapper
 {
     const NonDefUVSTable& mTable;
-    NonDefUVSTableWrapper(const NonDefUVSTable& table) : mTable(table) {}
+    explicit NonDefUVSTableWrapper(const NonDefUVSTable& table) : mTable(table) {}
     uint32_t operator[](size_t index) const {
         return mTable.uvsMappings[index].unicodeValue;
     }
 };
 
 } // namespace
 
 uint16_t
@@ -1332,17 +1332,17 @@ const char* gfxFontUtils::gMSFontNameCha
     /* [9] reserved */                          nullptr      ,
     /*[10] ENCODING_ID_MICROSOFT_UNICODEFULL */ ""
 };
 
 struct MacCharsetMappingComparator
 {
     typedef gfxFontUtils::MacFontNameCharsetMapping MacFontNameCharsetMapping;
     const MacFontNameCharsetMapping& mSearchValue;
-    MacCharsetMappingComparator(const MacFontNameCharsetMapping& aSearchValue)
+    explicit MacCharsetMappingComparator(const MacFontNameCharsetMapping& aSearchValue)
       : mSearchValue(aSearchValue) {}
     int operator()(const MacFontNameCharsetMapping& aEntry) const {
         if (mSearchValue < aEntry) {
             return -1;
         }
         if (aEntry < mSearchValue) {
             return 1;
         }
--- a/gfx/thebes/gfxMathTable.cpp
+++ b/gfx/thebes/gfxMathTable.cpp
@@ -356,27 +356,27 @@ gfxMathTable::GetGlyphAssembly(uint32_t 
   return reinterpret_cast<const GlyphAssembly*>(start);
 }
 
 namespace {
 
 struct GlyphArrayWrapper
 {
   const GlyphID* const mGlyphArray;
-  GlyphArrayWrapper(const GlyphID* const aGlyphArray) : mGlyphArray(aGlyphArray)
+  explicit GlyphArrayWrapper(const GlyphID* const aGlyphArray) : mGlyphArray(aGlyphArray)
   {}
   uint16_t operator[](size_t index) const {
     return mGlyphArray[index];
   }
 };
 
 struct RangeRecordComparator
 {
   const uint32_t mGlyph;
-  RangeRecordComparator(uint32_t aGlyph) : mGlyph(aGlyph) {}
+  explicit RangeRecordComparator(uint32_t aGlyph) : mGlyph(aGlyph) {}
   int operator()(const RangeRecord& aRecord) const {
     if (mGlyph < static_cast<uint16_t>(aRecord.mStart)) {
       return -1;
     }
     if (mGlyph > static_cast<uint16_t>(aRecord.mEnd)) {
       return 1;
     }
     return 0;
--- a/gfx/thebes/gfxSkipChars.cpp
+++ b/gfx/thebes/gfxSkipChars.cpp
@@ -4,17 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfxSkipChars.h"
 #include "mozilla/BinarySearch.h"
 
 struct SkippedRangeStartComparator
 {
     const uint32_t mOffset;
-    SkippedRangeStartComparator(const uint32_t aOffset) : mOffset(aOffset) {}
+    explicit SkippedRangeStartComparator(const uint32_t aOffset) : mOffset(aOffset) {}
     int operator()(const gfxSkipChars::SkippedRange& aRange) const {
         return (mOffset < aRange.Start()) ? -1 : 1;
     }
 };
 
 void
 gfxSkipCharsIterator::SetOriginalOffset(int32_t aOffset)
 {
@@ -64,17 +64,17 @@ gfxSkipCharsIterator::SetOriginalOffset(
     }
 
     mSkippedStringOffset = aOffset - r.NextDelta();
 }
 
 struct SkippedRangeOffsetComparator
 {
     const uint32_t mOffset;
-    SkippedRangeOffsetComparator(const uint32_t aOffset) : mOffset(aOffset) {}
+    explicit SkippedRangeOffsetComparator(const uint32_t aOffset) : mOffset(aOffset) {}
     int operator()(const gfxSkipChars::SkippedRange& aRange) const {
         return (mOffset < aRange.SkippedOffset()) ? -1 : 1;
     }
 };
 
 void
 gfxSkipCharsIterator::SetSkippedOffset(uint32_t aOffset)
 {
--- a/gfx/thebes/gfxUserFontSet.cpp
+++ b/gfx/thebes/gfxUserFontSet.cpp
@@ -189,17 +189,18 @@ public:
             aTag == TRUETYPE_TAG('S', 'V', 'G', ' ') ||
             aTag == TRUETYPE_TAG('C', 'O', 'L', 'R') ||
             aTag == TRUETYPE_TAG('C', 'P', 'A', 'L')) {
             return ots::TABLE_ACTION_PASSTHRU;
         }
         return ots::TABLE_ACTION_DEFAULT;
     }
 
-    virtual void Message(const char* format, ...) MSGFUNC_FMT_ATTR MOZ_OVERRIDE {
+    virtual void Message(int level, const char* format,
+                         ...) MSGFUNC_FMT_ATTR MOZ_OVERRIDE {
         va_list va;
         va_start(va, format);
 
         // buf should be more than adequate for any message OTS generates,
         // so we don't worry about checking the result of vsnprintf()
         char buf[512];
         (void)vsnprintf(buf, sizeof(buf), format, va);
 
--- a/ipc/ril/Ril.cpp
+++ b/ipc/ril/Ril.cpp
@@ -87,51 +87,55 @@ PostToRIL(JSContext *aCx,
     if (args.length() != 2) {
         JS_ReportError(aCx, "Expecting two arguments with the RIL message");
         return false;
     }
 
     int clientId = args[0].toInt32();
     JS::Value v = args[1];
 
-    JSAutoByteString abs;
-    void *data;
-    size_t size;
+    UnixSocketRawData* raw = nullptr;
+
     if (v.isString()) {
+        JSAutoByteString abs;
         JS::Rooted<JSString*> str(aCx, v.toString());
         if (!abs.encodeUtf8(aCx, str)) {
             return false;
         }
 
-        data = abs.ptr();
-        size = abs.length();
+        raw = new UnixSocketRawData(abs.ptr(), abs.length());
     } else if (!v.isPrimitive()) {
         JSObject *obj = v.toObjectOrNull();
         if (!JS_IsTypedArrayObject(obj)) {
             JS_ReportError(aCx, "Object passed in wasn't a typed array");
             return false;
         }
 
         uint32_t type = JS_GetArrayBufferViewType(obj);
         if (type != js::Scalar::Int8 &&
             type != js::Scalar::Uint8 &&
             type != js::Scalar::Uint8Clamped) {
             JS_ReportError(aCx, "Typed array data is not octets");
             return false;
         }
 
-        size = JS_GetTypedArrayByteLength(obj);
-        data = JS_GetArrayBufferViewData(obj);
+        JS::AutoCheckCannotGC nogc;
+        size_t size = JS_GetTypedArrayByteLength(obj);
+        void *data = JS_GetArrayBufferViewData(obj, nogc);
+        raw = new UnixSocketRawData(data, size);
     } else {
         JS_ReportError(aCx,
                        "Incorrect argument. Expecting a string or a typed array");
         return false;
     }
 
-    UnixSocketRawData* raw = new UnixSocketRawData(data, size);
+    if (!raw) {
+        JS_ReportError(aCx, "Unable to post to RIL");
+        return false;
+    }
 
     nsRefPtr<SendRilSocketDataTask> task =
         new SendRilSocketDataTask(clientId, raw);
     NS_DispatchToMainThread(task);
     return true;
 }
 
 bool
@@ -184,18 +188,21 @@ DispatchRILEvent::RunTask(JSContext *aCx
 {
     JS::Rooted<JSObject*> obj(aCx, JS::CurrentGlobalOrNull(aCx));
 
     JS::Rooted<JSObject*> array(aCx,
                                 JS_NewUint8Array(aCx, mMessage->GetSize()));
     if (!array) {
         return false;
     }
-    memcpy(JS_GetArrayBufferViewData(array),
-           mMessage->GetData(), mMessage->GetSize());
+    {
+        JS::AutoCheckCannotGC nogc;
+        memcpy(JS_GetArrayBufferViewData(array, nogc),
+               mMessage->GetData(), mMessage->GetSize());
+    }
 
     JS::AutoValueArray<2> args(aCx);
     args[0].setNumber((uint32_t)mClientId);
     args[1].setObject(*array);
 
     JS::Rooted<JS::Value> rval(aCx);
     return JS_CallFunctionName(aCx, obj, "onRILMessage", args, &rval);
 }
--- a/js/src/asmjs/AsmJSModule.cpp
+++ b/js/src/asmjs/AsmJSModule.cpp
@@ -13,18 +13,16 @@
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "asmjs/AsmJSModule.h"
 
-#include <errno.h>
-
 #ifndef XP_WIN
 # include <sys/mman.h>
 #endif
 
 #include "mozilla/BinarySearch.h"
 #include "mozilla/Compression.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TaggedAnonymousMemory.h"
@@ -52,47 +50,27 @@ using namespace jit;
 using namespace frontend;
 using mozilla::BinarySearch;
 using mozilla::PodCopy;
 using mozilla::PodEqual;
 using mozilla::Compression::LZ4;
 using mozilla::Swap;
 
 static uint8_t *
-AllocateExecutableMemory(ExclusiveContext *cx, size_t totalBytes)
-{
-    MOZ_ASSERT(totalBytes % AsmJSPageSize == 0);
-
-#ifdef XP_WIN
-    void *p = VirtualAlloc(nullptr, totalBytes, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
-    if (!p) {
-        js_ReportOutOfMemory(cx);
-        return nullptr;
-    }
-#else  // assume Unix
-    void *p = MozTaggedAnonymousMmap(nullptr, totalBytes,
-                                     PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANON,
-                                     -1, 0, "asm-js-code");
-    if (p == MAP_FAILED) {
-        js_ReportOutOfMemory(cx);
-        return nullptr;
-    }
-#endif
-
-    return (uint8_t *)p;
-}
-
-static void
-DeallocateExecutableMemory(uint8_t *code, size_t totalBytes)
+AllocateExecutableMemory(ExclusiveContext *cx, size_t bytes)
 {
 #ifdef XP_WIN
-    JS_ALWAYS_TRUE(VirtualFree(code, 0, MEM_RELEASE));
+    unsigned permissions = PAGE_EXECUTE_READWRITE;
 #else
-    JS_ALWAYS_TRUE(munmap(code, totalBytes) == 0 || errno == ENOMEM);
+    unsigned permissions = PROT_READ | PROT_WRITE | PROT_EXEC;
 #endif
+    void *p = AllocateExecutableMemory(nullptr, bytes, permissions, "asm-js-code", AsmJSPageSize);
+    if (!p)
+        js_ReportOutOfMemory(cx);
+    return (uint8_t *)p;
 }
 
 AsmJSModule::AsmJSModule(ScriptSource *scriptSource, uint32_t srcStart, uint32_t srcBodyStart,
                          bool strict, bool canUseSignalHandlers)
   : srcStart_(srcStart),
     srcBodyStart_(srcBodyStart),
     scriptSource_(scriptSource),
     globalArgumentName_(nullptr),
@@ -124,17 +102,17 @@ AsmJSModule::~AsmJSModule()
             AsmJSModule::ExitDatum &exitDatum = exitIndexToGlobalDatum(i);
             if (!exitDatum.ionScript)
                 continue;
 
             jit::DependentAsmJSModuleExit exit(this, i);
             exitDatum.ionScript->removeDependentAsmJSModule(exit);
         }
 
-        DeallocateExecutableMemory(code_, pod.totalBytes_);
+        DeallocateExecutableMemory(code_, pod.totalBytes_, AsmJSPageSize);
     }
 
     for (size_t i = 0; i < numFunctionCounts(); i++)
         js_delete(functionCounts(i));
 }
 
 void
 AsmJSModule::trace(JSTracer *trc)
--- a/js/src/ctypes/CTypes.cpp
+++ b/js/src/ctypes/CTypes.cpp
@@ -2456,33 +2456,44 @@ ImplicitConvert(JSContext* cx,
         (*char16Buffer)[sourceLength] = 0;
         break;
       }
       default:
         return TypeError(cx, "string pointer", val);
       }
       break;
     } else if (val.isObject() && JS_IsArrayBufferObject(valObj)) {
-      // Convert ArrayBuffer to pointer without any copy.
-      // Just as with C arrays, we make no effort to
-      // keep the ArrayBuffer alive.
-      void* p = JS_GetStableArrayBufferData(cx, valObj);
-      if (!p)
-          return false;
-      *static_cast<void**>(buffer) = p;
+      // Convert ArrayBuffer to pointer without any copy. Just as with C
+      // arrays, we make no effort to keep the ArrayBuffer alive. This
+      // functionality will be removed for all but arguments in bug 1080262.
+      void* ptr;
+      {
+          JS::AutoCheckCannotGC nogc;
+          ptr = JS_GetArrayBufferData(valObj, nogc);
+      }
+      if (!ptr) {
+        return TypeError(cx, "arraybuffer pointer", val);
+      }
+      *static_cast<void**>(buffer) = ptr;
       break;
     } if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
+      // Same as ArrayBuffer, above, though note that this will take the offset
+      // of the view into account.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
         return TypeError(cx, "typed array with the appropriate type", val);
       }
-
-      // Convert TypedArray to pointer without any copy.
-      // Just as with C arrays, we make no effort to
-      // keep the TypedArray alive.
-      *static_cast<void**>(buffer) = JS_GetArrayBufferViewData(valObj);
+      void* ptr;
+      {
+          JS::AutoCheckCannotGC nogc;
+          ptr = JS_GetArrayBufferViewData(valObj, nogc);
+      }
+      if (!ptr) {
+        return TypeError(cx, "typed array pointer", val);
+      }
+      *static_cast<void**>(buffer) = ptr;
       break;
     }
     return TypeError(cx, "pointer", val);
   }
   case TYPE_array: {
     RootedObject baseType(cx, ArrayType::GetBaseType(targetType));
     size_t targetLength = ArrayType::GetLength(targetType);
 
@@ -2578,33 +2589,35 @@ ImplicitConvert(JSContext* cx,
       // copy the array.
       uint32_t sourceLength = JS_GetArrayBufferByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
         JS_ReportError(cx, "ArrayType length does not match source ArrayBuffer length");
         return false;
       }
-      memcpy(buffer, JS_GetArrayBufferData(valObj), sourceLength);
+      JS::AutoCheckCannotGC nogc;
+      memcpy(buffer, JS_GetArrayBufferData(valObj, nogc), sourceLength);
       break;
     } else if (val.isObject() && JS_IsTypedArrayObject(valObj)) {
       // Check that array is consistent with type, then
       // copy the array.
       if(!CanConvertTypedArrayItemTo(baseType, valObj, cx)) {
         return TypeError(cx, "typed array with the appropriate type", val);
       }
 
       uint32_t sourceLength = JS_GetTypedArrayByteLength(valObj);
       size_t elementSize = CType::GetSize(baseType);
       size_t arraySize = elementSize * targetLength;
       if (arraySize != size_t(sourceLength)) {
         JS_ReportError(cx, "typed array length does not match source TypedArray length");
         return false;
       }
-      memcpy(buffer, JS_GetArrayBufferViewData(valObj), sourceLength);
+      JS::AutoCheckCannotGC nogc;
+      memcpy(buffer, JS_GetArrayBufferViewData(valObj, nogc), sourceLength);
       break;
     } else {
       // Don't implicitly convert to string. Users can implicitly convert
       // with `String(x)` or `""+x`.
       return TypeError(cx, "array", val);
     }
     break;
   }
--- a/js/src/devtools/rootAnalysis/annotations.js
+++ b/js/src/devtools/rootAnalysis/annotations.js
@@ -72,16 +72,17 @@ var ignoreCallees = {
     "js::Class.trace" : true,
     "js::Class.finalize" : true,
     "JSRuntime.destroyPrincipals" : true,
     "icu_50::UObject.__deleting_dtor" : true, // destructors in ICU code can't cause GC
     "mozilla::CycleCollectedJSRuntime.DescribeCustomObjects" : true, // During tracing, cannot GC.
     "mozilla::CycleCollectedJSRuntime.NoteCustomGCThingXPCOMChildren" : true, // During tracing, cannot GC.
     "PLDHashTableOps.hashKey" : true,
     "z_stream_s.zfree" : true,
+    "GrGLInterface.fCallback" : true,
 };
 
 function fieldCallCannotGC(csu, fullfield)
 {
     if (csu in ignoreClasses)
         return true;
     if (fullfield in ignoreCallees)
         return true;
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -1732,16 +1732,18 @@ bool TokenStream::getStringOrTemplateTok
                     reportError(JSMSG_UNTERMINATED_STRING);
                     return false;
                 }
                 if (c == '\r') {
                     c = '\n';
                     if (userbuf.peekRawChar() == '\n')
                         skipChars(1);
                 }
+                updateLineInfoForEOL();
+                updateFlagsForEOL();
             } else if (qc == '`' && c == '$') {
                 if ((nc = getCharIgnoreEOL()) == '{')
                     break;
                 ungetCharIgnoreEOL(nc);
             }
         }
         if (!tokenbuf.append(c))
             return false;
--- a/js/src/jit-test/tests/gc/bug-1053676.js
+++ b/js/src/jit-test/tests/gc/bug-1053676.js
@@ -1,9 +1,11 @@
 // |jit-test| ion-eager;
+if (typeof Symbol !== "function") quit(0);
+
 var x
 (function() {
     x
 }());
 verifyprebarriers();
 x = x * 0
 x = Symbol();
 gc();
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/ion/bug1076283.js
@@ -0,0 +1,10 @@
+function f() {
+    assertEq(typeof this, "object");
+}
+this.f();
+function gg() {
+    for (var j = 0; j < 3; ++j) {
+        f();
+    }
+};
+gg();
--- a/js/src/jit-test/tests/ion/dce-with-rinstructions.js
+++ b/js/src/jit-test/tests/ion/dce-with-rinstructions.js
@@ -924,18 +924,22 @@ function rregexp_m_literal_replace(i) {
 
     if (uceFault_regexp_m_literal_replace(i) || uceFault_regexp_m_literal_replace(i))
         assertEq(res, "abc\nabc");
     return i;
 }
 
 var uceFault_typeof = eval(uneval(uceFault).replace('uceFault', 'uceFault_typeof'))
 function rtypeof(i) {
-    var inputs = [ {}, [], 1, true, Symbol(), undefined, function(){}, null ];
-    var types = [ "object", "object", "number", "boolean", "symbol", "undefined", "function", "object"];
+    var inputs = [ {}, [], 1, true, undefined, function(){}, null ];
+    var types = [ "object", "object", "number", "boolean", "undefined", "function", "object"];
+    if (typeof Symbol === "function") {
+      inputs.push(Symbol());
+      types.push("symbol");
+    }
     var x = typeof (inputs[i % inputs.length]);
     var y = types[i % types.length];
 
     if (uceFault_typeof(i) || uceFault_typeof(i)) {
         assertEq(x, y);
     }
 
     return i;
--- a/js/src/jit/AliasAnalysis.cpp
+++ b/js/src/jit/AliasAnalysis.cpp
@@ -22,36 +22,36 @@ using mozilla::Array;
 namespace js {
 namespace jit {
 
 class LoopAliasInfo : public TempObject
 {
   private:
     LoopAliasInfo *outer_;
     MBasicBlock *loopHeader_;
-    MDefinitionVector invariantLoads_;
+    MInstructionVector invariantLoads_;
 
   public:
     LoopAliasInfo(TempAllocator &alloc, LoopAliasInfo *outer, MBasicBlock *loopHeader)
       : outer_(outer), loopHeader_(loopHeader), invariantLoads_(alloc)
     { }
 
     MBasicBlock *loopHeader() const {
         return loopHeader_;
     }
     LoopAliasInfo *outer() const {
         return outer_;
     }
-    bool addInvariantLoad(MDefinition *ins) {
+    bool addInvariantLoad(MInstruction *ins) {
         return invariantLoads_.append(ins);
     }
-    const MDefinitionVector& invariantLoads() const {
+    const MInstructionVector& invariantLoads() const {
         return invariantLoads_;
     }
-    MDefinition *firstInstruction() const {
+    MInstruction *firstInstruction() const {
         return *loopHeader_->begin();
     }
 };
 
 } // namespace jit
 } // namespace js
 
 namespace {
@@ -118,30 +118,30 @@ BlockMightReach(MBasicBlock *src, MBasic
           default:
             return true;
         }
     }
     return false;
 }
 
 static void
-IonSpewDependency(MDefinition *load, MDefinition *store, const char *verb, const char *reason)
+IonSpewDependency(MInstruction *load, MInstruction *store, const char *verb, const char *reason)
 {
     if (!JitSpewEnabled(JitSpew_Alias))
         return;
 
     fprintf(JitSpewFile, "Load ");
     load->printName(JitSpewFile);
     fprintf(JitSpewFile, " %s on store ", verb);
     store->printName(JitSpewFile);
     fprintf(JitSpewFile, " (%s)\n", reason);
 }
 
 static void
-IonSpewAliasInfo(const char *pre, MDefinition *ins, const char *post)
+IonSpewAliasInfo(const char *pre, MInstruction *ins, const char *post)
 {
     if (!JitSpewEnabled(JitSpew_Alias))
         return;
 
     fprintf(JitSpewFile, "%s ", pre);
     ins->printName(JitSpewFile);
     fprintf(JitSpewFile, " %s\n", post);
 }
@@ -158,22 +158,22 @@ IonSpewAliasInfo(const char *pre, MDefin
 // having an implicit dependency on the last instruction of the loop header, so that
 // it's never moved before the loop header.
 //
 // The algorithm depends on the invariant that both control instructions and effectful
 // instructions (stores) are never hoisted.
 bool
 AliasAnalysis::analyze()
 {
-    Vector<MDefinitionVector, AliasSet::NumCategories, IonAllocPolicy> stores(alloc());
+    Vector<MInstructionVector, AliasSet::NumCategories, IonAllocPolicy> stores(alloc());
 
     // Initialize to the first instruction.
-    MDefinition *firstIns = *graph_.entryBlock()->begin();
+    MInstruction *firstIns = *graph_.entryBlock()->begin();
     for (unsigned i = 0; i < AliasSet::NumCategories; i++) {
-        MDefinitionVector defs(alloc());
+        MInstructionVector defs(alloc());
         if (!defs.append(firstIns))
             return false;
         if (!stores.append(Move(defs)))
             return false;
     }
 
     // Type analysis may have inserted new instructions. Since this pass depends
     // on the instruction number ordering, all instructions are renumbered.
@@ -183,17 +183,23 @@ AliasAnalysis::analyze()
         if (mir->shouldCancel("Alias Analysis (main loop)"))
             return false;
 
         if (block->isLoopHeader()) {
             JitSpew(JitSpew_Alias, "Processing loop header %d", block->id());
             loop_ = new(alloc()) LoopAliasInfo(alloc(), loop_, *block);
         }
 
-        for (MDefinitionIterator def(*block); def; def++) {
+        for (MPhiIterator def(block->phisBegin()), end(block->phisEnd()); def != end; ++def)
+            def->setId(newId++);
+
+        for (MInstructionIterator def(block->begin()), end(block->begin(block->lastIns()));
+             def != end;
+             ++def)
+        {
             def->setId(newId++);
 
             AliasSet set = def->getAliasSet();
             if (set.isNone())
                 continue;
 
             if (set.isStore()) {
                 for (AliasSetIterator iter(set); iter; iter++) {
@@ -203,22 +209,22 @@ AliasAnalysis::analyze()
 
                 if (JitSpewEnabled(JitSpew_Alias)) {
                     fprintf(JitSpewFile, "Processing store ");
                     def->printName(JitSpewFile);
                     fprintf(JitSpewFile, " (flags %x)\n", set.flags());
                 }
             } else {
                 // Find the most recent store on which this instruction depends.
-                MDefinition *lastStore = firstIns;
+                MInstruction *lastStore = firstIns;
 
                 for (AliasSetIterator iter(set); iter; iter++) {
-                    MDefinitionVector &aliasedStores = stores[*iter];
+                    MInstructionVector &aliasedStores = stores[*iter];
                     for (int i = aliasedStores.length() - 1; i >= 0; i--) {
-                        MDefinition *store = aliasedStores[i];
+                        MInstruction *store = aliasedStores[i];
                         if (def->mightAlias(store) && BlockMightReach(store->block(), *block)) {
                             if (lastStore->id() < store->id())
                                 lastStore = store;
                             break;
                         }
                     }
                 }
 
@@ -240,28 +246,28 @@ AliasAnalysis::analyze()
 
         if (block->isLoopBackedge()) {
             MOZ_ASSERT(loop_->loopHeader() == block->loopHeaderOfBackedge());
             JitSpew(JitSpew_Alias, "Processing loop backedge %d (header %d)", block->id(),
                     loop_->loopHeader()->id());
             LoopAliasInfo *outerLoop = loop_->outer();
             MInstruction *firstLoopIns = *loop_->loopHeader()->begin();
 
-            const MDefinitionVector &invariant = loop_->invariantLoads();
+            const MInstructionVector &invariant = loop_->invariantLoads();
 
             for (unsigned i = 0; i < invariant.length(); i++) {
-                MDefinition *ins = invariant[i];
+                MInstruction *ins = invariant[i];
                 AliasSet set = ins->getAliasSet();
                 MOZ_ASSERT(set.isLoad());
 
                 bool hasAlias = false;
                 for (AliasSetIterator iter(set); iter; iter++) {
-                    MDefinitionVector &aliasedStores = stores[*iter];
+                    MInstructionVector &aliasedStores = stores[*iter];
                     for (int i = aliasedStores.length() - 1;; i--) {
-                        MDefinition *store = aliasedStores[i];
+                        MInstruction *store = aliasedStores[i];
                         if (store->id() < firstLoopIns->id())
                             break;
                         if (ins->mightAlias(store)) {
                             hasAlias = true;
                             IonSpewDependency(ins, store, "aliases", "store in loop body");
                             break;
                         }
                     }
--- a/js/src/jit/ExecutableAllocator.h
+++ b/js/src/jit/ExecutableAllocator.h
@@ -184,25 +184,30 @@ class ExecutableAllocator {
     DestroyCallback destroyCallback;
 
 public:
     ExecutableAllocator()
       : destroyCallback(NULL)
     {
         if (!pageSize) {
             pageSize = determinePageSize();
-            /*
-             * On Windows, VirtualAlloc effectively allocates in 64K chunks.
-             * (Technically, it allocates in page chunks, but the starting
-             * address is always a multiple of 64K, so each allocation uses up
-             * 64K of address space.)  So a size less than that would be
-             * pointless.  But it turns out that 64KB is a reasonable size for
-             * all platforms.  (This assumes 4KB pages.)
-             */
+            // On Windows, VirtualAlloc effectively allocates in 64K chunks.
+            // (Technically, it allocates in page chunks, but the starting
+            // address is always a multiple of 64K, so each allocation uses up
+            // 64K of address space.)  So a size less than that would be
+            // pointless.  But it turns out that 64KB is a reasonable size for
+            // all platforms.  (This assumes 4KB pages.) On 64-bit windows,
+            // AllocateExecutableMemory prepends an extra page for structured
+            // exception handling data (see comments in function) onto whatever
+            // is passed in, so subtract one page here.
+#if defined(JS_CPU_X64) && defined(XP_WIN)
+            largeAllocSize = pageSize * 15;
+#else
             largeAllocSize = pageSize * 16;
+#endif
         }
 
         MOZ_ASSERT(m_smallPools.empty());
     }
 
     ~ExecutableAllocator()
     {
         for (size_t i = 0; i < m_smallPools.length(); i++)
@@ -467,12 +472,19 @@ private:
     // its reference is removed from m_pools.
     typedef js::HashSet<ExecutablePool *, js::DefaultHasher<ExecutablePool *>, js::SystemAllocPolicy>
             ExecPoolHashSet;
     ExecPoolHashSet m_pools;    // All pools, just for stats purposes.
 
     static size_t determinePageSize();
 };
 
+extern void *
+AllocateExecutableMemory(void *addr, size_t bytes, unsigned permissions, const char *tag,
+                         size_t pageSize);
+
+extern void
+DeallocateExecutableMemory(void *addr, size_t bytes, size_t pageSize);
+
 } // namespace jit
 } // namespace js
 
 #endif /* jit_ExecutableAllocator_h */
--- a/js/src/jit/ExecutableAllocatorPosix.cpp
+++ b/js/src/jit/ExecutableAllocatorPosix.cpp
@@ -21,42 +21,58 @@
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/TaggedAnonymousMemory.h"
 
+#include <errno.h>
 #include <sys/mman.h>
 #include <unistd.h>
 
 #include "jit/ExecutableAllocator.h"
 #include "js/Utility.h"
 
 using namespace js::jit;
 
 size_t ExecutableAllocator::determinePageSize()
 {
     return getpagesize();
 }
 
+void *
+js::jit::AllocateExecutableMemory(void *addr, size_t bytes, unsigned permissions, const char *tag,
+                                  size_t pageSize)
+{
+    MOZ_ASSERT(bytes % pageSize == 0);
+    void *p = MozTaggedAnonymousMmap(addr, bytes, permissions, MAP_PRIVATE | MAP_ANON, -1, 0, tag);
+    return p == MAP_FAILED ? nullptr : p;
+}
+
+void
+js::jit::DeallocateExecutableMemory(void *addr, size_t bytes, size_t pageSize)
+{
+    MOZ_ASSERT(bytes % pageSize == 0);
+    mozilla::DebugOnly<int> result = munmap(addr, bytes);
+    MOZ_ASSERT(!result || errno == ENOMEM);
+}
+
 ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
-    void *allocation = MozTaggedAnonymousMmap(NULL, n, INITIAL_PROTECTION_FLAGS, MAP_PRIVATE | MAP_ANON, -1, 0, "js-jit-code");
-    if (allocation == MAP_FAILED)
-        allocation = NULL;
+    void *allocation = AllocateExecutableMemory(nullptr, n, INITIAL_PROTECTION_FLAGS,
+                                                "js-jit-code", pageSize);
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
-    mozilla::DebugOnly<int> result = munmap(alloc.pages, alloc.size);
-    MOZ_ASSERT(!result);
+    DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
 }
 
 #if WTF_ENABLE_ASSEMBLER_WX_EXCLUSIVE
 void ExecutableAllocator::reprotectRegion(void* start, size_t size, ProtectionSetting setting)
 {
     if (!pageSize)
         intializePageSize();
 
--- a/js/src/jit/ExecutableAllocatorWin.cpp
+++ b/js/src/jit/ExecutableAllocatorWin.cpp
@@ -20,22 +20,22 @@
  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  */
 
 #include "mozilla/WindowsVersion.h"
 
+#include "jsfriendapi.h"
 #include "jsmath.h"
+#include "jswin.h"
 
 #include "jit/ExecutableAllocator.h"
 
-#include "jswin.h"
-
 using namespace js::jit;
 
 uint64_t ExecutableAllocator::rngSeed;
 
 size_t ExecutableAllocator::determinePageSize()
 {
     SYSTEM_INFO system_info;
     GetSystemInfo(&system_info);
@@ -80,37 +80,180 @@ static bool
 RandomizeIsBroken()
 {
     // Use the compiler's intrinsic guards for |static type value = expr| to avoid some potential
     // races if runtimes are created from multiple threads.
     static int result = RandomizeIsBrokenImpl();
     return !!result;
 }
 
+#ifdef JS_CPU_X64
+static js::JitExceptionHandler sJitExceptionHandler;
+
+JS_FRIEND_API(void)
+js::SetJitExceptionHandler(JitExceptionHandler handler)
+{
+    MOZ_ASSERT(!sJitExceptionHandler);
+    sJitExceptionHandler = handler;
+}
+
+// From documentation for UNWIND_INFO on
+// http://msdn.microsoft.com/en-us/library/ddssxxy8.aspx
+struct UnwindInfo
+{
+    uint8_t version : 3;
+    uint8_t flags : 5;
+    uint8_t sizeOfPrologue;
+    uint8_t countOfUnwindCodes;
+    uint8_t frameRegister : 4;
+    uint8_t frameOffset : 4;
+    ULONG exceptionHandler;
+};
+
+static const unsigned ThunkLength = 12;
+
+struct ExceptionHandlerRecord
+{
+    RUNTIME_FUNCTION runtimeFunction;
+    UnwindInfo unwindInfo;
+    uint8_t thunk[ThunkLength];
+};
+
+// This function must match the function pointer type PEXCEPTION_HANDLER
+// mentioned in:
+//   http://msdn.microsoft.com/en-us/library/ssa62fwe.aspx.
+// This type is rather elusive in documentation; Wine is the best I've found:
+//   http://source.winehq.org/source/include/winnt.h
+static DWORD
+ExceptionHandler(PEXCEPTION_RECORD exceptionRecord, _EXCEPTION_REGISTRATION_RECORD *,
+                 PCONTEXT context, _EXCEPTION_REGISTRATION_RECORD **)
+{
+    return sJitExceptionHandler(exceptionRecord, context);
+}
+
+// For an explanation of the problem being solved here, see
+// SetJitExceptionFilter in jsfriendapi.h.
+static bool
+RegisterExecutableMemory(void *p, size_t bytes, size_t pageSize)
+{
+    ExceptionHandlerRecord *r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+
+    // All these fields are specified to be offsets from the base of the
+    // executable code (which is 'p'), even if they have 'Address' in their
+    // names. In particular, exceptionHandler is a ULONG offset which is a
+    // 32-bit integer. Since 'p' can be farther than INT32_MAX away from
+    // sJitExceptionHandler, we must generate a little thunk inside the
+    // record. The record is put on its own page so that we can take away write
+    // access to protect against accidental clobbering.
+
+    r->runtimeFunction.BeginAddress = pageSize;
+    r->runtimeFunction.EndAddress = (DWORD)bytes;
+    r->runtimeFunction.UnwindData = offsetof(ExceptionHandlerRecord, unwindInfo);
+
+    r->unwindInfo.version = 1;
+    r->unwindInfo.flags = UNW_FLAG_EHANDLER;
+    r->unwindInfo.sizeOfPrologue = 0;
+    r->unwindInfo.countOfUnwindCodes = 0;
+    r->unwindInfo.frameRegister = 0;
+    r->unwindInfo.frameOffset = 0;
+    r->unwindInfo.exceptionHandler = offsetof(ExceptionHandlerRecord, thunk);
+
+    // mov imm64, rax
+    r->thunk[0]  = 0x48;
+    r->thunk[1]  = 0xb8;
+    void *handler = &ExceptionHandler;
+    memcpy(&r->thunk[2], &handler, 8);
+
+    // jmp rax
+    r->thunk[10] = 0xff;
+    r->thunk[11] = 0xe0;
+
+    DWORD oldProtect;
+    if (!VirtualProtect(p, pageSize, PAGE_EXECUTE_READ, &oldProtect))
+        return false;
+
+    return RtlAddFunctionTable(&r->runtimeFunction, 1, reinterpret_cast<DWORD64>(p));
+}
+
+static void
+UnregisterExecutableMemory(void *p, size_t bytes, size_t pageSize)
+{
+    ExceptionHandlerRecord *r = reinterpret_cast<ExceptionHandlerRecord*>(p);
+    RtlDeleteFunctionTable(&r->runtimeFunction);
+}
+#endif
+
+void *
+js::jit::AllocateExecutableMemory(void *addr, size_t bytes, unsigned permissions, const char *tag,
+                                  size_t pageSize)
+{
+    MOZ_ASSERT(bytes % pageSize == 0);
+    MOZ_ASSERT(permissions == PAGE_EXECUTE_READWRITE);
+
+#ifdef JS_CPU_X64
+    if (sJitExceptionHandler)
+        bytes += pageSize;
+#endif
+
+    void *p = VirtualAlloc(addr, bytes, MEM_COMMIT | MEM_RESERVE, permissions);
+    if (!p)
+        return nullptr;
+
+#ifdef JS_CPU_X64
+    if (sJitExceptionHandler) {
+        if (!RegisterExecutableMemory(p, bytes, pageSize)) {
+            VirtualFree(p, 0, MEM_RELEASE);
+            return nullptr;
+        }
+
+        p = (uint8_t*)p + pageSize;
+    }
+#endif
+
+    return p;
+}
+
+void
+js::jit::DeallocateExecutableMemory(void *addr, size_t bytes, size_t pageSize)
+{
+    MOZ_ASSERT(bytes % pageSize == 0);
+
+#ifdef JS_CPU_X64
+    if (sJitExceptionHandler) {
+        addr = (uint8_t*)addr - pageSize;
+        UnregisterExecutableMemory(addr, bytes, pageSize);
+    }
+#endif
+
+    VirtualFree(addr, 0, MEM_RELEASE);
+}
+
 ExecutablePool::Allocation ExecutableAllocator::systemAlloc(size_t n)
 {
     void *allocation = NULL;
     // Randomization disabled to avoid a performance fault on x64 builds.
     // See bug 728623.
 #ifndef JS_CPU_X64
     if (!RandomizeIsBroken()) {
         void *randomAddress = computeRandomAllocationAddress();
-        allocation = VirtualAlloc(randomAddress, n, MEM_COMMIT | MEM_RESERVE,
-                                  PAGE_EXECUTE_READWRITE);
+        allocation = AllocateExecutableMemory(randomAddress, n, PAGE_EXECUTE_READWRITE,
+                                              "js-jit-code", pageSize);
     }
 #endif
-    if (!allocation)
-        allocation = VirtualAlloc(0, n, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
+    if (!allocation) {
+        allocation = AllocateExecutableMemory(nullptr, n, PAGE_EXECUTE_READWRITE,
+                                              "js-jit-code", pageSize);
+    }
     ExecutablePool::Allocation alloc = { reinterpret_cast<char*>(allocation), n };
     return alloc;
 }
 
 void ExecutableAllocator::systemRelease(const ExecutablePool::Allocation& alloc)
 {
-    VirtualFree(alloc.pages, 0, MEM_RELEASE);
+    DeallocateExecutableMemory(alloc.pages, alloc.size, pageSize);
 }
 
 void
 ExecutablePool::toggleAllCodeAsAccessible(bool accessible)
 {
     char* begin = m_allocation.pages;
     size_t size = m_freePtr - begin;
 
--- a/js/src/jit/FixedList.h
+++ b/js/src/jit/FixedList.h
@@ -73,14 +73,21 @@ class FixedList
     T &operator[](size_t index) {
         MOZ_ASSERT(index < length_);
         return list_[index];
     }
     const T &operator [](size_t index) const {
         MOZ_ASSERT(index < length_);
         return list_[index];
     }
+
+    T *begin() {
+        return list_;
+    }
+    T *end() {
+        return list_ + length_;
+    }
 };
 
 } // namespace jit
 } // namespace js
 
 #endif /* jit_FixedList_h */
--- a/js/src/jit/InlineList.h
+++ b/js/src/jit/InlineList.h
@@ -62,32 +62,23 @@ class InlineForwardList : protected Inli
 
   public:
     iterator begin() const {
         return iterator(this);
     }
     iterator end() const {
         return iterator(nullptr);
     }
-    iterator removeAt(iterator &where) {
+    void removeAt(iterator where) {
         iterator iter(where);
         iter++;
-        iter.prev = where.prev;
 #ifdef DEBUG
         iter.modifyCount_++;
 #endif
-
-        // Once the element 'where' points at has been removed, it is no longer
-        // safe to do any operations that would touch 'iter', as the element
-        // may be added to another list, etc. This nullptr ensures that any
-        // improper uses of this function will fail quickly and loudly.
         removeAfter(where.prev, where.iter);
-        where.prev = where.iter = nullptr;
-
-        return iter;
     }
     void pushFront(Node *t) {
         insertAfter(this, t);
     }
     void pushBack(Node *t) {
         MOZ_ASSERT(t->next == nullptr);
 #ifdef DEBUG
         modifyCount_++;
@@ -215,17 +206,36 @@ class InlineListNode : public InlineForw
   public:
     InlineListNode() : InlineForwardListNode<T>(nullptr), prev(nullptr)
     { }
     InlineListNode(InlineListNode<T> *n, InlineListNode<T> *p)
       : InlineForwardListNode<T>(n),
         prev(p)
     { }
 
+    // Move constructor. Nodes may be moved without being removed from their
+    // containing lists. For example, this allows list nodes to be safely
+    // stored in a resizable Vector -- when the Vector resizes, the new storage
+    // is initialized by this move constructor. |other| is a reference to the
+    // old node which the |this| node here is replacing.
+    InlineListNode(InlineListNode<T> &&other)
+      : InlineForwardListNode<T>(other.next)
+    {
+        InlineListNode<T> *newNext = static_cast<InlineListNode<T> *>(other.next);
+        InlineListNode<T> *newPrev = other.prev;
+        prev = newPrev;
+
+        // Update the pointers in the adjacent nodes to point to this node's new
+        // location.
+        newNext->prev = this;
+        newPrev->next = this;
+    }
+
     InlineListNode(const InlineListNode<T> &) MOZ_DELETE;
+    void operator=(const InlineListNode<T> &) MOZ_DELETE;
 
   protected:
     friend class InlineList<T>;
     friend class InlineListIterator<T>;
     friend class InlineListReverseIterator<T>;
 
     InlineListNode<T> *prev;
 };
@@ -257,30 +267,16 @@ class InlineList : protected InlineListN
         return reverse_iterator(this->prev);
     }
     reverse_iterator rbegin(Node *t) const {
         return reverse_iterator(t);
     }
     reverse_iterator rend() const {
         return reverse_iterator(this);
     }
-    template <typename itertype>
-    itertype removeAt(itertype &where) {
-        itertype iter(where);
-        iter++;
-
-        // Once the element 'where' points at has been removed, it is no longer
-        // safe to do any operations that would touch 'iter', as the element
-        // may be added to another list, etc. This nullptr ensures that any
-        // improper uses of this function will fail quickly and loudly.
-        remove(where.iter);
-        where.iter = nullptr;
-
-        return iter;
-    }
     void pushFront(Node *t) {
         insertAfter(this, t);
     }
     void pushFrontUnchecked(Node *t) {
         insertAfterUnchecked(this, t);
     }
     void pushBack(Node *t) {
         insertBefore(this, t);
@@ -332,16 +328,28 @@ class InlineList : protected InlineListN
     void remove(Node *t) {
         Node *tNext = static_cast<Node *>(t->next);
         Node *tPrev = t->prev;
         tPrev->next = tNext;
         tNext->prev = tPrev;
         t->next = nullptr;
         t->prev = nullptr;
     }
+    // Remove |old| from the list and insert |now| in its place.
+    void replace(Node *old, Node *now) {
+        MOZ_ASSERT(now->next == nullptr && now->prev == nullptr);
+        Node *listNext = static_cast<Node *>(old->next);
+        Node *listPrev = old->prev;
+        listPrev->next = now;
+        listNext->prev = now;
+        now->next = listNext;
+        now->prev = listPrev;
+        old->next = nullptr;
+        old->prev = nullptr;
+    }
     void clear() {
         this->next = this->prev = this;
     }
     bool empty() const {
         return begin() == end();
     }
     void takeElements(InlineList &l) {
         MOZ_ASSERT(&l != this, "cannot takeElements from this");
--- a/js/src/jit/IonAnalysis.cpp
+++ b/js/src/jit/IonAnalysis.cpp
@@ -244,18 +244,17 @@ MaybeFoldConditionBlock(MIRGraph &graph,
         return;
 
     if (trueResult != trueBranch->peek(-1) || falseResult != falseBranch->peek(-1))
         return;
 
     // OK, we found the desired pattern, now transform the graph.
 
     // Remove the phi and its inputs from testBlock.
-    MPhiIterator phis = testBlock->phisBegin();
-    testBlock->discardPhiAt(phis);
+    testBlock->discardPhi(*testBlock->phisBegin());
     trueBranch->pop();
     falseBranch->pop();
 
     // If either trueBranch or falseBranch just computes a constant for the
     // test, determine the block that branch will end up jumping to and eliminate
     // the branch. Otherwise, change the end of the block to a test that jumps
     // directly to successors of testBlock, rather than to testBlock itself.
 
@@ -358,18 +357,17 @@ MaybeFoldAndOrBlock(MIRGraph &graph, MBa
         return;
 
     if (branchResult != branchBlock->peek(-1) || initialResult != initialBlock->peek(-1))
         return;
 
     // OK, we found the desired pattern, now transform the graph.
 
     // Remove the phi and its inputs from testBlock.
-    MPhiIterator phis = testBlock->phisBegin();
-    testBlock->discardPhiAt(phis);
+    testBlock->discardPhi(*testBlock->phisBegin());
     branchBlock->pop();
     initialBlock->pop();
 
     // Change the end of the initial and branch blocks to a test that jumps
     // directly to successors of testBlock, rather than to testBlock itself.
 
     UpdateTestSuccessors(graph.alloc(), initialBlock, initialResult,
                          branchIsTrue ? branchBlock : finalTest->ifTrue(),
@@ -546,29 +544,27 @@ jit::EliminateDeadCode(MIRGenerator *mir
 {
     // Traverse in postorder so that we hit uses before definitions.
     // Traverse instruction list backwards for the same reason.
     for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
         if (mir->shouldCancel("Eliminate Dead Code (main loop)"))
             return false;
 
         // Remove unused instructions.
-        for (MInstructionReverseIterator inst = block->rbegin(); inst != block->rend(); ) {
+        for (MInstructionReverseIterator iter = block->rbegin(); iter != block->rend(); ) {
+            MInstruction *inst = *iter++;
             if (!inst->isEffectful() && !inst->resumePoint() &&
                 !inst->hasUses() && !inst->isGuard() &&
                 !inst->isControlInstruction())
             {
-                inst = block->discardAt(inst);
+                block->discard(inst);
             } else if (!inst->isRecoveredOnBailout() && !inst->isGuard() &&
                        !inst->hasLiveDefUses() && inst->canRecoverOnBailout())
             {
                 inst->setRecoveredOnBailout();
-                inst++;
-            } else {
-                inst++;
             }
         }
     }
 
     return true;
 }
 
 static inline bool
@@ -654,34 +650,35 @@ jit::EliminatePhis(MIRGenerator *mir, MI
     // Add all observable phis to a worklist. We use the "in worklist" bit to
     // mean "this phi is live".
     for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
         if (mir->shouldCancel("Eliminate Phis (populate loop)"))
             return false;
 
         MPhiIterator iter = block->phisBegin();
         while (iter != block->phisEnd()) {
+            MPhi *phi = *iter++;
+
             // Flag all as unused, only observable phis would be marked as used
             // when processed by the work list.
-            iter->setUnused();
+            phi->setUnused();
 
             // If the phi is redundant, remove it here.
-            if (MDefinition *redundant = IsPhiRedundant(*iter)) {
-                iter->justReplaceAllUsesWith(redundant);
-                iter = block->discardPhiAt(iter);
+            if (MDefinition *redundant = IsPhiRedundant(phi)) {
+                phi->justReplaceAllUsesWith(redundant);
+                block->discardPhi(phi);
                 continue;
             }
 
             // Enqueue observable Phis.
-            if (IsPhiObservable(*iter, observe)) {
-                iter->setInWorklist();
-                if (!worklist.append(*iter))
+            if (IsPhiObservable(phi, observe)) {
+                phi->setInWorklist();
+                if (!worklist.append(phi))
                     return false;
             }
-            iter++;
         }
     }
 
     // Iteratively mark all phis reachable from live phis.
     while (!worklist.empty()) {
         if (mir->shouldCancel("Eliminate Phis (worklist)"))
             return false;
 
@@ -719,20 +716,19 @@ jit::EliminatePhis(MIRGenerator *mir, MI
                 return false;
         }
     }
 
     // Sweep dead phis.
     for (PostorderIterator block = graph.poBegin(); block != graph.poEnd(); block++) {
         MPhiIterator iter = block->phisBegin();
         while (iter != block->phisEnd()) {
-            if (iter->isUnused())
-                iter = block->discardPhiAt(iter);
-            else
-                iter++;
+            MPhi *phi = *iter++;
+            if (phi->isUnused())
+                block->discardPhi(phi);
         }
     }
 
     return true;
 }
 
 namespace {
 
@@ -1117,28 +1113,28 @@ TypeAnalyzer::insertConversions()
 {
     // Instructions are processed in reverse postorder: all uses are defs are
     // seen before uses. This ensures that output adjustment (which may rewrite
     // inputs of uses) does not conflict with input adjustment.
     for (ReversePostorderIterator block(graph.rpoBegin()); block != graph.rpoEnd(); block++) {
         if (mir->shouldCancel("Insert Conversions"))
             return false;
 
-        for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd();) {
+        for (MPhiIterator iter(block->phisBegin()), end(block->phisEnd()); iter != end; ) {
+            MPhi *phi = *iter++;
             if (phi->type() == MIRType_Undefined ||
                 phi->type() == MIRType_Null ||
                 phi->type() == MIRType_MagicOptimizedArguments ||
                 phi->type() == MIRType_MagicOptimizedOut ||
                 phi->type() == MIRType_MagicUninitializedLexical)
             {
-                replaceRedundantPhi(*phi);
-                phi = block->discardPhiAt(phi);
+                replaceRedundantPhi(phi);
+                block->discardPhi(phi);
             } else {
-                adjustPhiInputs(*phi);
-                phi++;
+                adjustPhiInputs(phi);
             }
         }
         for (MInstructionIterator iter(block->begin()); iter != block->end(); iter++) {
             if (!adjustInputs(*iter))
                 return false;
         }
     }
     return true;
@@ -1843,16 +1839,17 @@ jit::AssertBasicGraphCoherency(MIRGraph 
                 MOZ_ASSERT(iter->getUseFor(i)->hasProducer());
                 MOZ_ASSERT(CheckOperandImpliesUse(*iter, iter->getOperand(i)));
             }
         }
         for (MPhiIterator phi(block->phisBegin()); phi != block->phisEnd(); phi++) {
             MOZ_ASSERT(phi->numOperands() == block->numPredecessors());
             MOZ_ASSERT(!phi->isRecoveredOnBailout());
             MOZ_ASSERT(phi->type() != MIRType_None);
+            MOZ_ASSERT(phi->dependency() == nullptr);
         }
         for (MDefinitionIterator iter(*block); iter; iter++) {
             MOZ_ASSERT(iter->block() == *block);
             MOZ_ASSERT_IF(iter->hasUses(), iter->type() != MIRType_None);
             MOZ_ASSERT(!iter->isDiscarded());
             MOZ_ASSERT_IF(iter->isStart(),
                           *block == graph.entryBlock() || *block == graph.osrBlock());
             MOZ_ASSERT_IF(iter->isParameter(),
@@ -1976,16 +1973,30 @@ jit::AssertGraphCoherency(MIRGraph &grap
 #ifdef DEBUG
     if (!js_JitOptions.checkGraphConsistency)
         return;
     AssertBasicGraphCoherency(graph);
     AssertReversePostorder(graph);
 #endif
 }
 
+static void
+AssertResumePointDominatedByOperands(MResumePoint *resume)
+{
+#ifdef DEBUG
+    for (size_t i = 0, e = resume->numOperands(); i < e; ++i) {
+        MDefinition *op = resume->getOperand(i);
+        if (op->type() == MIRType_MagicOptimizedArguments)
+            continue;
+        MOZ_ASSERT(op->block()->dominates(resume->block()),
+                   "Resume point is not dominated by its operands");
+    }
+#endif
+}
+
 void
 jit::AssertExtendedGraphCoherency(MIRGraph &graph)
 {
     // Checks the basic GraphCoherency but also other conditions that
     // do not hold immediately (such as the fact that critical edges
     // are split)
 
 #ifdef DEBUG
@@ -2060,17 +2071,25 @@ jit::AssertExtendedGraphCoherency(MIRGra
                     MInstructionIterator opIter = block->begin(op->toInstruction());
                     do {
                         ++opIter;
                         MOZ_ASSERT(opIter != block->end(),
                                    "Operand in same block as instruction does not precede");
                     } while (*opIter != ins);
                 }
             }
+            if (MResumePoint *resume = ins->resumePoint())
+                AssertResumePointDominatedByOperands(resume);
         }
+
+        // Verify that the block resume points are dominated by their operands.
+        if (MResumePoint *resume = block->entryResumePoint())
+            AssertResumePointDominatedByOperands(resume);
+        if (MResumePoint *resume = block->outerResumePoint())
+            AssertResumePointDominatedByOperands(resume);
     }
 #endif
 }
 
 
 struct BoundsCheckInfo
 {
     MBoundsCheck *check;
@@ -2432,36 +2451,36 @@ jit::EliminateRedundantChecks(MIRGraph &
 
         // Add all immediate dominators to the front of the worklist.
         if (!worklist.append(block->immediatelyDominatedBlocksBegin(),
                              block->immediatelyDominatedBlocksEnd())) {
             return false;
         }
 
         for (MDefinitionIterator iter(block); iter; ) {
+            MDefinition *def = *iter++;
+
             bool eliminated = false;
 
-            if (iter->isBoundsCheck()) {
-                if (!TryEliminateBoundsCheck(checks, index, iter->toBoundsCheck(), &eliminated))
+            if (def->isBoundsCheck()) {
+                if (!TryEliminateBoundsCheck(checks, index, def->toBoundsCheck(), &eliminated))
                     return false;
-            } else if (iter->isTypeBarrier()) {
-                if (!TryEliminateTypeBarrier(iter->toTypeBarrier(), &eliminated))
+            } else if (def->isTypeBarrier()) {
+                if (!TryEliminateTypeBarrier(def->toTypeBarrier(), &eliminated))
                     return false;
             } else {
                 // Now that code motion passes have finished, replace
                 // instructions which pass through one of their operands
                 // (and perform additional checks) with that operand.
-                if (MDefinition *passthrough = PassthroughOperand(*iter))
-                    iter->replaceAllUsesWith(passthrough);
+                if (MDefinition *passthrough = PassthroughOperand(def))
+                    def->replaceAllUsesWith(passthrough);
             }
 
             if (eliminated)
-                iter = block->discardDefAt(iter);
-            else
-                iter++;
+                block->discardDef(def);
         }
         index++;
     }
 
     MOZ_ASSERT(index == graph.numBlocks());
     return true;
 }
 
@@ -3190,22 +3209,22 @@ jit::MarkLoopBlocks(MIRGraph &graph, MBa
     // entry.
     MBasicBlock *backedge = header->backedge();
     backedge->mark();
     size_t numMarked = 1;
     for (PostorderIterator i = graph.poBegin(backedge); ; ++i) {
         MOZ_ASSERT(i != graph.poEnd(),
                    "Reached the end of the graph while searching for the loop header");
         MBasicBlock *block = *i;
+        // If we've reached the loop header, we're done.
+        if (block == header)
+            break;
         // A block not marked by the time we reach it is not in the loop.
         if (!block->isMarked())
             continue;
-        // If we've reached the loop header, we're done.
-        if (block == header)
-            break;
         // This block is in the loop; trace to its predecessors.
         for (size_t p = 0, e = block->numPredecessors(); p != e; ++p) {
             MBasicBlock *pred = block->getPredecessor(p);
             if (pred->isMarked())
                 continue;
 
             // Blocks dominated by the OSR entry are not part of the loop
             // (unless they aren't reachable from the normal entry).
@@ -3238,17 +3257,25 @@ jit::MarkLoopBlocks(MIRGraph &graph, MBa
                     if (backedge->id() > block->id()) {
                         i = graph.poBegin(innerBackedge);
                         --i;
                     }
                 }
             }
         }
     }
-    MOZ_ASSERT(header->isMarked(), "Loop header should be part of the loop");
+
+    // If there's no path connecting the header to the backedge, then this isn't
+    // actually a loop. This can happen when the code starts with a loop but GVN
+    // folds some branches away.
+    if (!header->isMarked()) {
+        jit::UnmarkLoopBlocks(graph, header);
+        return 0;
+    }
+
     return numMarked;
 }
 
 // Unmark all the blocks that are in the loop with the given header.
 void
 jit::UnmarkLoopBlocks(MIRGraph &graph, MBasicBlock *header)
 {
     MBasicBlock *backedge = header->backedge();
@@ -3323,15 +3350,19 @@ jit::MakeLoopsContiguous(MIRGraph &graph
         MBasicBlock *header = *i;
         if (!header->isLoopHeader())
             continue;
 
         // Mark all blocks that are actually part of the loop.
         bool canOsr;
         size_t numMarked = MarkLoopBlocks(graph, header, &canOsr);
 
+        // If the loop isn't a loop, don't try to optimize it.
+        if (numMarked == 0)
+            continue;
+
         // Move all blocks between header and backedge that aren't marked to
         // the end of the loop, making the loop itself contiguous.
         MakeLoopContiguous(graph, header, numMarked);
     }
 
     return true;
 }
--- a/js/src/jit/IonBuilder.cpp
+++ b/js/src/jit/IonBuilder.cpp
@@ -621,16 +621,24 @@ bool
 IonBuilder::init()
 {
     if (!types::TypeScript::FreezeTypeSets(constraints(), script(),
                                            &thisTypes, &argTypes, &typeArray))
     {
         return false;
     }
 
+    if (inlineCallInfo_) {
+        // If we're inlining, the actual this/argument types are not necessarily
+        // a subset of the script's observed types. |argTypes| is never accessed
+        // for inlined scripts, so we just null it.
+        thisTypes = inlineCallInfo_->thisArg()->resultTypeSet();
+        argTypes = nullptr;
+    }
+
     if (!analysis().init(alloc(), gsn))
         return false;
 
     // The baseline script normally has the bytecode type map, but compute
     // it ourselves if we do not have a baseline script.
     if (script()->hasBaselineScript()) {
         bytecodeTypeMap = script()->baselineScript()->bytecodeTypeMap();
     } else {
@@ -802,21 +810,21 @@ IonBuilder::processIterators()
 
     return true;
 }
 
 bool
 IonBuilder::buildInline(IonBuilder *callerBuilder, MResumePoint *callerResumePoint,
                         CallInfo &callInfo)
 {
+    inlineCallInfo_ = &callInfo;
+
     if (!init())
         return false;
 
-    inlineCallInfo_ = &callInfo;
-
     JitSpew(JitSpew_IonScripts, "Inlining script %s:%d (%p)",
             script()->filename(), script()->lineno(), (void *)script());
 
     callerBuilder_ = callerBuilder;
     callerResumePoint_ = callerResumePoint;
 
     if (callerBuilder->failedBoundsCheck_)
         failedBoundsCheck_ = true;
@@ -5721,17 +5729,17 @@ IonBuilder::jsop_eval(uint32_t argc)
 
         if (!info().funMaybeLazy())
             return abort("Direct eval in global code");
 
         // The 'this' value for the outer and eval scripts must be the
         // same. This is not guaranteed if a primitive string/number/etc.
         // is passed through to the eval invoke as the primitive may be
         // boxed into different objects if accessed via 'this'.
-        MIRType type = thisTypes->getKnownMIRType();
+        MIRType type = thisTypes ? thisTypes->getKnownMIRType() : MIRType_Value;
         if (type != MIRType_Object && type != MIRType_Null && type != MIRType_Undefined)
             return abort("Direct eval from script with maybe-primitive 'this'");
 
         CallInfo callInfo(alloc(), /* constructing = */ false);
         if (!callInfo.init(current, argc))
             return false;
         callInfo.setImplicitlyUsedUnchecked();
 
@@ -10395,18 +10403,18 @@ IonBuilder::jsop_this()
     }
 
     if (script()->strict() || info().funMaybeLazy()->isSelfHostedBuiltin()) {
         // No need to wrap primitive |this| in strict mode or self-hosted code.
         current->pushSlot(info().thisSlot());
         return true;
     }
 
-    if (thisTypes->getKnownMIRType() == MIRType_Object ||
-        (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject()))
+    if (thisTypes && (thisTypes->getKnownMIRType() == MIRType_Object ||
+        (thisTypes->empty() && baselineFrame_ && baselineFrame_->thisType.isSomeObject())))
     {
         // This is safe, because if the entry type of |this| is an object, it
         // will necessarily be an object throughout the entire function. OSR
         // can introduce a phi, but this phi will be specialized.
         current->pushSlot(info().thisSlot());
         return true;
     }
 
--- a/js/src/jit/LICM.cpp
+++ b/js/src/jit/LICM.cpp
@@ -118,17 +118,17 @@ IsHoistableIgnoringDependency(MInstructi
            !HasOperandInLoop(ins, hasCalls);
 }
 
 // Test whether the given instruction has a memory dependency inside the loop.
 static bool
 HasDependencyInLoop(MInstruction *ins, MBasicBlock *header)
 {
     // Don't hoist if this instruction depends on a store inside the loop.
-    if (MDefinition *dep = ins->dependency())
+    if (MInstruction *dep = ins->dependency())
         return !IsBeforeLoop(dep, header);
     return false;
 }
 
 // Test whether the given instruction is hoistable.
 static bool
 IsHoistable(MInstruction *ins, MBasicBlock *header, bool hasCalls)
 {
@@ -242,17 +242,22 @@ jit::LICM(MIRGenerator *mir, MIRGraph &g
     // Iterate in RPO to visit outer loops before inner loops. We'd hoist the
     // same things either way, but outer first means we do a little less work.
     for (auto i(graph.rpoBegin()), e(graph.rpoEnd()); i != e; ++i) {
         MBasicBlock *header = *i;
         if (!header->isLoopHeader())
             continue;
 
         bool canOsr;
-        MarkLoopBlocks(graph, header, &canOsr);
+        size_t numBlocks = MarkLoopBlocks(graph, header, &canOsr);
+
+        if (numBlocks == 0) {
+            JitSpew(JitSpew_LICM, "  Loop with header block%u isn't actually a loop", header->id());
+            continue;
+        }
 
         // Hoisting out of a loop that has an entry from the OSR block in
         // addition to its normal entry is tricky. In theory we could clone
         // the instruction and insert phis.
         if (!canOsr)
             VisitLoop(graph, header);
         else
             JitSpew(JitSpew_LICM, "  Skipping loop with header block%u due to OSR", header->id());
--- a/js/src/jit/LinearScan.cpp
+++ b/js/src/jit/LinearScan.cpp
@@ -99,43 +99,39 @@ LinearScanAllocator::allocateRegisters()
                 current->end().bits(), current->requirement()->priority());
 
         // Shift active intervals to the inactive or handled sets as appropriate
         if (position != prevPosition) {
             MOZ_ASSERT(position > prevPosition);
             prevPosition = position;
 
             for (IntervalIterator i(active.begin()); i != active.end(); ) {
-                LiveInterval *it = *i;
+                LiveInterval *it = *i++;
                 MOZ_ASSERT(it->numRanges() > 0);
 
                 if (it->end() <= position) {
-                    i = active.removeAt(i);
+                    active.remove(it);
                     finishInterval(it);
                 } else if (!it->covers(position)) {
-                    i = active.removeAt(i);
+                    active.remove(it);
                     inactive.pushBack(it);
-                } else {
-                    i++;
                 }
             }
 
             // Shift inactive intervals to the active or handled sets as appropriate
             for (IntervalIterator i(inactive.begin()); i != inactive.end(); ) {
-                LiveInterval *it = *i;
+                LiveInterval *it = *i++;
                 MOZ_ASSERT(it->numRanges() > 0);
 
                 if (it->end() <= position) {
-                    i = inactive.removeAt(i);
+                    inactive.remove(it);
                     finishInterval(it);