Merge mozilla-central to mozilla-inbound on a CLOSED TREE
authordvarga <dvarga@mozilla.com>
Thu, 16 Aug 2018 01:22:36 +0300
changeset 431813 baba90c7c28f86218bbf7f0fc8ae5c3831f33b34
parent 431812 083414a62ea15be84270efc2bc86bb358a3b22f1 (current diff)
parent 431644 73efbce701a9880e35a5139dc67757fa68ad892a (diff)
child 431814 186ad2d171c1f2348703b119ad5983515ead58a7
push id34451
push userebalazs@mozilla.com
push dateThu, 16 Aug 2018 09:25:15 +0000
treeherdermozilla-central@161817e6d127 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound on a CLOSED TREE
--- a/browser/base/content/browser-siteIdentity.js
+++ b/browser/base/content/browser-siteIdentity.js
@@ -1010,17 +1010,19 @@ var gIdentityHandler = {
 
     let nameLabel = document.createXULElement("label");
     nameLabel.setAttribute("flex", "1");
     nameLabel.setAttribute("class", "identity-popup-permission-label");
     nameLabel.textContent = SitePermissions.getPermissionLabel(aPermission.id);
     let nameLabelId = "identity-popup-permission-label-" + aPermission.id;
     nameLabel.setAttribute("id", nameLabelId);
 
-    let isPolicyPermission = aPermission.scope == SitePermissions.SCOPE_POLICY;
+    let isPolicyPermission = [
+      SitePermissions.SCOPE_POLICY, SitePermissions.SCOPE_GLOBAL
+    ].includes(aPermission.scope);
 
     if (aPermission.id == "popup" && !isPolicyPermission) {
       let menulist = document.createXULElement("menulist");
       let menupopup = document.createXULElement("menupopup");
       let block = document.createXULElement("vbox");
       block.setAttribute("id", "identity-popup-popup-container");
       menulist.setAttribute("sizetopopup", "none");
       menulist.setAttribute("class", "identity-popup-popup-menulist");
@@ -1078,18 +1080,18 @@ var gIdentityHandler = {
     stateLabel.textContent = SitePermissions.getCurrentStateLabel(state, aPermission.id, scope);
 
     container.appendChild(img);
     container.appendChild(nameLabel);
     container.appendChild(stateLabel);
     container.setAttribute("aria-labelledby", nameLabelId + " " + stateLabelId);
 
     /* We return the permission item here without a remove button if the permission is a
-       SCOPE_POLICY permission. Policy permissions cannot be removed/changed for the duration
-       of the browser session. */
+       SCOPE_POLICY or SCOPE_GLOBAL permission. Policy permissions cannot be
+       removed/changed for the duration of the browser session. */
     if (isPolicyPermission) {
       return container;
     }
 
     let button = document.createXULElement("button");
     button.setAttribute("class", "identity-popup-permission-remove-button");
     let tooltiptext = gNavigatorBundle.getString("permissions.remove.tooltip");
     button.setAttribute("tooltiptext", tooltiptext);
--- a/browser/base/content/pageinfo/permissions.js
+++ b/browser/base/content/pageinfo/permissions.js
@@ -92,17 +92,17 @@ function initRow(aPartId) {
   if (state != defaultState) {
     checkbox.checked = false;
     command.removeAttribute("disabled");
   } else {
     checkbox.checked = true;
     command.setAttribute("disabled", "true");
   }
 
-  if (scope == SitePermissions.SCOPE_POLICY) {
+  if ([SitePermissions.SCOPE_POLICY, SitePermissions.SCOPE_GLOBAL].includes(scope)) {
     checkbox.setAttribute("disabled", "true");
     command.setAttribute("disabled", "true");
   }
 
   setRadioState(aPartId, state);
 }
 
 function createRow(aPartId) {
--- a/browser/base/content/test/permissions/browser.ini
+++ b/browser/base/content/test/permissions/browser.ini
@@ -5,11 +5,15 @@ support-files=
 
 [browser_canvas_fingerprinting_resistance.js]
 [browser_permissions.js]
 [browser_reservedkey.js]
 [browser_temporary_permissions.js]
 support-files =
   temporary_permissions_subframe.html
   ../webrtc/get_user_media.html
+[browser_autoplay_blocked.js]
+support-files =
+  browser_autoplay_blocked.html
+  ../general/audio.ogg
 [browser_temporary_permissions_expiry.js]
 [browser_temporary_permissions_navigation.js]
 [browser_temporary_permissions_tabs.js]
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.html
@@ -0,0 +1,14 @@
+<!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 dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+  </head>
+  <body>
+    <audio autoplay="autoplay" >
+      <source src="audio.ogg" />
+    </audio>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/permissions/browser_autoplay_blocked.js
@@ -0,0 +1,57 @@
+/*
+ * Test that a blocked request to autoplay media is shown to the user
+ */
+
+const AUTOPLAY_PAGE  = getRootDirectory(gTestPath).replace("chrome://mochitests/content", "https://example.com") + "browser_autoplay_blocked.html";
+
+function openIdentityPopup() {
+  let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popupshown");
+  gIdentityHandler._identityBox.click();
+  return promise;
+}
+
+function closeIdentityPopup() {
+  let promise = BrowserTestUtils.waitForEvent(gIdentityHandler._identityPopup, "popuphidden");
+  gIdentityHandler._identityPopup.hidePopup();
+  return promise;
+}
+
+function autoplayBlockedIcon() {
+  return document.querySelector("#blocked-permissions-container " +
+                                ".blocked-permission-icon.autoplay-media-icon");
+}
+
+add_task(async function testMainViewVisible() {
+
+  Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.ALLOWED);
+
+  await BrowserTestUtils.withNewTab(AUTOPLAY_PAGE, async function() {
+    let permissionsList = document.getElementById("identity-popup-permission-list");
+    let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+    ok(BrowserTestUtils.is_hidden(autoplayBlockedIcon()), "Blocked icon not shown");
+
+    await openIdentityPopup();
+    ok(!BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is empty");
+    await closeIdentityPopup();
+  });
+
+  Services.prefs.setIntPref("media.autoplay.default", Ci.nsIAutoplay.BLOCKED);
+
+  await BrowserTestUtils.withNewTab(AUTOPLAY_PAGE, async function() {
+    let permissionsList = document.getElementById("identity-popup-permission-list");
+    let emptyLabel = permissionsList.nextSibling.nextSibling;
+
+    ok(!BrowserTestUtils.is_hidden(autoplayBlockedIcon()), "Blocked icon is shown");
+
+    await openIdentityPopup();
+    ok(BrowserTestUtils.is_hidden(emptyLabel), "List of permissions is not empty");
+    let labelText = SitePermissions.getPermissionLabel("autoplay-media");
+    let labels = permissionsList.querySelectorAll(".identity-popup-permission-label");
+    is(labels.length, 1, "One permission visible in main view");
+    is(labels[0].textContent, labelText, "Correct value");
+    await closeIdentityPopup();
+  });
+
+  Services.prefs.clearUserPref("media.autoplay.default");
+});
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -274,16 +274,29 @@ var PermissionPromptPrototype = {
       // If we're reading and setting permissions, then we need
       // to check to see if we already have a permission setting
       // for this particular principal.
       let {state} = SitePermissions.get(requestingURI,
                                         this.permissionKey,
                                         this.browser);
 
       if (state == SitePermissions.BLOCK) {
+        // If the request is blocked by a global setting then we record
+        // a flag that lasts for the duration of the current page load
+        // to notify the user that the permission has been blocked.
+        // Currently only applies to autoplay-media
+        if (state == SitePermissions.getDefault(this.permissionKey) &&
+            SitePermissions.showGloballyBlocked(this.permissionKey)) {
+          SitePermissions.set(this.principal.URI,
+                              this.permissionKey,
+                              state,
+                              SitePermissions.SCOPE_GLOBAL,
+                              this.browser);
+        }
+
         this.cancel();
         return;
       }
 
       if (state == SitePermissions.ALLOW) {
         this.allow();
         return;
       }
--- a/browser/modules/SitePermissions.jsm
+++ b/browser/modules/SitePermissions.jsm
@@ -124,16 +124,81 @@ const TemporaryBlockedPermissions = {
   copy(browser, newBrowser) {
     let entry = this._stateByBrowser.get(browser);
     if (entry) {
       this._stateByBrowser.set(newBrowser, entry);
     }
   },
 };
 
+// This hold a flag per browser to indicate whether we should show the
+// user a notification as a permission has been requested that has been
+// blocked globally. We only want to notify the user in the case that
+// they actually requested the permission within the current page load
+// so will clear the flag on navigation.
+const GloballyBlockedPermissions = {
+
+  _stateByBrowser: new WeakMap(),
+
+  set(browser, id) {
+    if (!this._stateByBrowser.has(browser)) {
+      this._stateByBrowser.set(browser, {});
+    }
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (!entry[prePath]) {
+      entry[prePath] = {};
+    }
+
+    entry[prePath][id] = true;
+
+    // Listen to any top level navigations, once we see one clear the flag
+    // and remove the listener.
+    browser.addProgressListener({
+      QueryInterface: ChromeUtils.generateQI([Ci.nsIWebProgressListener,
+                                              Ci.nsISupportsWeakReference]),
+      onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
+        if (aWebProgress.isTopLevel) {
+          GloballyBlockedPermissions.remove(browser, id);
+          browser.removeProgressListener(this);
+        }
+      },
+    });
+  },
+
+  // Removes a permission with the specified id for the specified browser.
+  remove(browser, id) {
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (entry && entry[prePath]) {
+      delete entry[prePath][id];
+    }
+  },
+
+  // Gets all permissions for the specified browser.
+  // Note that only permissions that apply to the current URI
+  // of the passed browser element will be returned.
+  getAll(browser) {
+    let permissions = [];
+    let entry = this._stateByBrowser.get(browser);
+    let prePath = browser.currentURI.prePath;
+    if (entry && entry[prePath]) {
+      let timeStamps = entry[prePath];
+      for (let id of Object.keys(timeStamps)) {
+        permissions.push({
+          id,
+          state: SitePermissions.BLOCK,
+          scope: SitePermissions.SCOPE_GLOBAL
+        });
+      }
+    }
+    return permissions;
+  },
+};
+
 /**
  * A module to manage permanent and temporary permissions
  * by URI and browser.
  *
  * Some methods have the side effect of dispatching a "PermissionStateChange"
  * event on changes to temporary permissions, as mentioned in the respective docs.
  */
 var SitePermissions = {
@@ -148,16 +213,17 @@ var SitePermissions = {
   PROMPT_HIDE: Ci.nsIObjectLoadingContent.PLUGIN_PERMISSION_PROMPT_ACTION_QUIET,
 
   // Permission scopes.
   SCOPE_REQUEST: "{SitePermissions.SCOPE_REQUEST}",
   SCOPE_TEMPORARY: "{SitePermissions.SCOPE_TEMPORARY}",
   SCOPE_SESSION: "{SitePermissions.SCOPE_SESSION}",
   SCOPE_PERSISTENT: "{SitePermissions.SCOPE_PERSISTENT}",
   SCOPE_POLICY: "{SitePermissions.SCOPE_POLICY}",
+  SCOPE_GLOBAL: "{SitePermissions.SCOPE_GLOBAL}",
 
   _defaultPrefBranch: Services.prefs.getBranch("permissions.default."),
 
   /**
    * Gets all custom permissions for a given URI.
    * Install addon permission is excluded, check bug 1303108.
    *
    * @return {Array} a list of objects with the keys:
@@ -226,16 +292,20 @@ var SitePermissions = {
   getAllForBrowser(browser) {
     let permissions = {};
 
     for (let permission of TemporaryBlockedPermissions.getAll(browser)) {
       permission.scope = this.SCOPE_TEMPORARY;
       permissions[permission.id] = permission;
     }
 
+    for (let permission of GloballyBlockedPermissions.getAll(browser)) {
+      permissions[permission.id] = permission;
+    }
+
     for (let permission of this.getAllByURI(browser.currentURI)) {
       permissions[permission.id] = permission;
     }
 
     return Object.values(permissions);
   },
 
   /**
@@ -326,16 +396,33 @@ var SitePermissions = {
         gPermissionObject[permissionID].getDefault)
       return gPermissionObject[permissionID].getDefault();
 
     // Otherwise try to get the default preference for that permission.
     return this._defaultPrefBranch.getIntPref(permissionID, this.UNKNOWN);
   },
 
   /**
+   * Return whether the browser should notify the user if a permission was
+   * globally blocked due to a preference.
+   *
+   * @param {string} permissionID
+   *        The ID to get the state for.
+   *
+   * @return boolean Whether to show notification for globally blocked permissions.
+   */
+  showGloballyBlocked(permissionID) {
+    if (permissionID in gPermissionObject &&
+        gPermissionObject[permissionID].showGloballyBlocked)
+      return gPermissionObject[permissionID].showGloballyBlocked;
+
+    return false;
+  },
+
+  /**
    * Returns the state and scope of a particular permission for a given URI.
    *
    * This method will NOT dispatch a "PermissionStateChange" event on the specified
    * browser if a temporary permission was removed because it has expired.
    *
    * @param {nsIURI} uri
    *        The URI to check.
    * @param {String} permissionID
@@ -399,16 +486,23 @@ var SitePermissions = {
    *        The state of the permission.
    * @param {SitePermissions scope} scope (optional)
    *        The scope of the permission. Defaults to SCOPE_PERSISTENT.
    * @param {Browser} browser (optional)
    *        The browser object to set temporary permissions on.
    *        This needs to be provided if the scope is SCOPE_TEMPORARY!
    */
   set(uri, permissionID, state, scope = this.SCOPE_PERSISTENT, browser = null) {
+
+    if (scope == this.SCOPE_GLOBAL && state == this.BLOCK) {
+      GloballyBlockedPermissions.set(browser, permissionID);
+      browser.dispatchEvent(new browser.ownerGlobal.CustomEvent("PermissionStateChange"));
+      return;
+    }
+
     if (state == this.UNKNOWN || state == this.getDefault(permissionID)) {
       // Because they are controlled by two prefs with many states that do not
       // correspond to the classical ALLOW/DENY/PROMPT model, we want to always
       // allow the user to add exceptions to their cookie rules without removing them.
       if (permissionID != "cookie") {
         this.remove(uri, permissionID, browser);
         return;
       }
@@ -602,23 +696,24 @@ var gPermissionObject = {
    *    Array of permission states to be exposed to the user.
    *    Defaults to ALLOW, BLOCK and the default state (see getDefault).
    *    The PROMPT_HIDE state is deliberately excluded from "plugin:flash" since we
    *    don't want to expose a "Hide Prompt" button to the user through pageinfo.
    */
 
   "autoplay-media": {
     exactHostMatch: true,
+    showGloballyBlocked: true,
     getDefault() {
       let state = Services.prefs.getIntPref("media.autoplay.default",
                                             Ci.nsIAutoplay.PROMPT);
-      if (state == Ci.nsIAutoplay.ALLOW) {
+      if (state == Ci.nsIAutoplay.ALLOWED) {
         return SitePermissions.ALLOW;
-      } if (state == Ci.nsIAutoplay.BLOCK) {
-        return SitePermissions.DENY;
+      } if (state == Ci.nsIAutoplay.BLOCKED) {
+        return SitePermissions.BLOCK;
       }
       return SitePermissions.UNKNOWN;
     },
     labelID: "autoplay-media"
   },
 
   "image": {
     states: [ SitePermissions.ALLOW, SitePermissions.BLOCK ],
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -2005,17 +2005,17 @@ HTMLMediaElement::Load()
        "ownerDoc=%p (%s) ownerDocUserActivated=%d "
        "muted=%d volume=%f",
        this,
        !!mSrcAttrStream,
        HasAttr(kNameSpaceID_None, nsGkAtoms::src),
        HasSourceChildren(this),
        EventStateManager::IsHandlingUserInput(),
        HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay),
-       AutoplayPolicy::IsAllowedToPlay(*this) == nsIAutoplay::ALLOWED,
+       AutoplayPolicy::IsAllowedToPlay(*this),
        OwnerDoc(),
        DocumentOrigin(OwnerDoc()).get(),
        OwnerDoc() ? OwnerDoc()->HasBeenUserGestureActivated() : 0,
        mMuted,
        mVolume));
 
   if (mIsRunningLoadMethod) {
     return;
@@ -2524,26 +2524,26 @@ HTMLMediaElement::ResumeLoad(PreloadActi
       LoadFromSourceChildren();
     }
   }
 }
 
 bool
 HTMLMediaElement::AllowedToPlay() const
 {
-  return AutoplayPolicy::IsAllowedToPlay(*this) == nsIAutoplay::ALLOWED;
+  return AutoplayPolicy::IsAllowedToPlay(*this);
 }
 
 void
 HTMLMediaElement::UpdatePreloadAction()
 {
   PreloadAction nextAction = PRELOAD_UNDEFINED;
   // If autoplay is set, or we're playing, we should always preload data,
   // as we'll need it to play.
-  if ((AutoplayPolicy::IsAllowedToPlay(*this) == nsIAutoplay::ALLOWED &&
+  if ((AutoplayPolicy::IsAllowedToPlay(*this) &&
        HasAttr(kNameSpaceID_None, nsGkAtoms::autoplay)) ||
       !mPaused) {
     nextAction = HTMLMediaElement::PRELOAD_ENOUGH;
   } else {
     // Find the appropriate preload action by looking at the attribute.
     const nsAttrValue* val =
       mAttrs.GetAttr(nsGkAtoms::preload, kNameSpaceID_None);
     // MSE doesn't work if preload is none, so it ignores the pref when src is
@@ -3066,17 +3066,17 @@ HTMLMediaElement::SetMutedInternal(uint3
 }
 
 void
 HTMLMediaElement::PauseIfShouldNotBePlaying()
 {
   if (GetPaused()) {
     return;
   }
-  if (AutoplayPolicy::IsAllowedToPlay(*this) != nsIAutoplay::ALLOWED) {
+  if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
     AUTOPLAY_LOG("pause because not allowed to play, element=%p", this);
     ErrorResult rv;
     Pause(rv);
     OwnerDoc()->SetDocTreeHadPlayRevoked();
   }
 }
 
 void
@@ -4098,37 +4098,24 @@ HTMLMediaElement::Play(ErrorResult& aRv)
       DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
     }
     return promise.forget();
   }
 
   UpdateHadAudibleAutoplayState();
 
   const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
-  switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
-    case nsIAutoplay::ALLOWED: {
-      mPendingPlayPromises.AppendElement(promise);
-      PlayInternal(handlingUserInput);
-      UpdateCustomPolicyAfterPlayed();
-      break;
-    }
-    case nsIAutoplay::BLOCKED: {
-      AUTOPLAY_LOG("%p play blocked.", this);
-      promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
-      if (StaticPrefs::MediaBlockEventEnabled()) {
-        DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
-      }
-      break;
-    }
-    case nsIAutoplay::PROMPT: {
-      // Prompt the user for permission to play.
-      mPendingPlayPromises.AppendElement(promise);
-      EnsureAutoplayRequested(handlingUserInput);
-      break;
-    }
+  if (AutoplayPolicy::IsAllowedToPlay(*this)) {
+    mPendingPlayPromises.AppendElement(promise);
+    PlayInternal(handlingUserInput);
+    UpdateCustomPolicyAfterPlayed();
+  } else {
+    // Prompt the user for permission to play.
+    mPendingPlayPromises.AppendElement(promise);
+    EnsureAutoplayRequested(handlingUserInput);
   }
   return promise.forget();
 }
 
 void
 HTMLMediaElement::EnsureAutoplayRequested(bool aHandlingUserInput)
 {
   if (mAutoplayPermissionRequest.Exists()) {
@@ -6146,18 +6133,17 @@ HTMLMediaElement::ChangeReadyState(nsMed
     DispatchAsyncEvent(NS_LITERAL_STRING("loadeddata"));
     mLoadedDataFired = true;
   }
 
   if (oldState < HAVE_FUTURE_DATA && mReadyState >= HAVE_FUTURE_DATA) {
     DispatchAsyncEvent(NS_LITERAL_STRING("canplay"));
     if (!mPaused) {
       if (mDecoder && !mPausedForInactiveDocumentOrChannel) {
-        MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this) ==
-                   nsIAutoplay::ALLOWED);
+        MOZ_ASSERT(AutoplayPolicy::IsAllowedToPlay(*this));
         mDecoder->Play();
       }
       NotifyAboutPlaying();
     }
   }
 
   CheckAutoplayDataReady();
 
@@ -6259,24 +6245,19 @@ HTMLMediaElement::CanActivateAutoplay()
 void
 HTMLMediaElement::CheckAutoplayDataReady()
 {
   if (!CanActivateAutoplay()) {
     return;
   }
 
   UpdateHadAudibleAutoplayState();
-  switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
-    case nsIAutoplay::BLOCKED:
-      return;
-    case nsIAutoplay::PROMPT:
-      EnsureAutoplayRequested(false);
-      return;
-    case nsIAutoplay::ALLOWED:
-      break;
+  if (!AutoplayPolicy::IsAllowedToPlay(*this)) {
+    EnsureAutoplayRequested(false);
+    return;
   }
 
   mPaused = false;
   // We changed mPaused which can affect AddRemoveSelfReference
   AddRemoveSelfReference();
   UpdateSrcMediaStreamPlaying();
   UpdateAudioChannelPlayingState();
 
--- a/dom/media/AutoplayPolicy.cpp
+++ b/dom/media/AutoplayPolicy.cpp
@@ -151,35 +151,39 @@ IsMediaElementAllowedToPlay(const HTMLMe
 }
 
 /* static */ bool
 AutoplayPolicy::WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement)
 {
   return IsMediaElementAllowedToPlay(aElement);
 }
 
-/* static */ uint32_t
+/* static */ bool
 AutoplayPolicy::IsAllowedToPlay(const HTMLMediaElement& aElement)
 {
   const uint32_t autoplayDefault = DefaultAutoplayBehaviour();
   // TODO : this old way would be removed when user-gestures-needed becomes
   // as a default option to block autoplay.
   if (!Preferences::GetBool("media.autoplay.enabled.user-gestures-needed", false)) {
     // If element is blessed, it would always be allowed to play().
     return (autoplayDefault == nsIAutoplay::ALLOWED ||
             aElement.IsBlessed() ||
-            EventStateManager::IsHandlingUserInput())
-              ? nsIAutoplay::ALLOWED : nsIAutoplay::BLOCKED;
+            EventStateManager::IsHandlingUserInput());
   }
 
-  const uint32_t result = IsMediaElementAllowedToPlay(aElement) ?
-    nsIAutoplay::ALLOWED : autoplayDefault;
+  if (IsMediaElementAllowedToPlay(aElement)) {
+    return true;
+  }
+
+  const bool result = IsMediaElementAllowedToPlay(aElement) ||
+    autoplayDefault == nsIAutoplay::ALLOWED;
 
   AUTOPLAY_LOG("IsAllowedToPlay, mediaElement=%p, isAllowToPlay=%s",
                 &aElement, AllowAutoplayToStr(result));
+
   return result;
 }
 
 /* static */ bool
 AutoplayPolicy::IsAudioContextAllowedToPlay(NotNull<AudioContext*> aContext)
 {
   if (!Preferences::GetBool("media.autoplay.block-webaudio", false)) {
     return true;
--- a/dom/media/AutoplayPolicy.h
+++ b/dom/media/AutoplayPolicy.h
@@ -31,17 +31,17 @@ class AudioContext;
  *    We restrict user gestures to "mouse click", "keyboard press" and "touch".
  * 2) Muted media content or video without audio content.
  * 3) Document's origin has the "autoplay-media" permission.
  */
 class AutoplayPolicy
 {
 public:
   // Returns whether a given media element is allowed to play.
-  static uint32_t IsAllowedToPlay(const HTMLMediaElement& aElement);
+  static bool IsAllowedToPlay(const HTMLMediaElement& aElement);
 
   // Returns true if a given media element would be allowed to play
   // if block autoplay was enabled. If this returns false, it means we would
   // either block or ask for permission.
   // Note: this is for telemetry purposes, and doesn't check the prefs
   // which enable/disable block autoplay. Do not use for blocking logic!
   static bool WouldBeAllowedToPlayIfAutoplayDisabled(const HTMLMediaElement& aElement);
 
--- a/layout/base/GeometryUtils.cpp
+++ b/layout/base/GeometryUtils.cpp
@@ -9,17 +9,16 @@
 #include "mozilla/dom/CharacterData.h"
 #include "mozilla/dom/DOMPointBinding.h"
 #include "mozilla/dom/GeometryUtilsBinding.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Text.h"
 #include "mozilla/dom/DOMPoint.h"
 #include "mozilla/dom/DOMQuad.h"
 #include "mozilla/dom/DOMRect.h"
-#include "nsContentUtils.h"
 #include "nsIFrame.h"
 #include "nsCSSFrameConstructor.h"
 #include "nsLayoutUtils.h"
 #include "nsSVGUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
--- a/layout/base/nsFrameManager.cpp
+++ b/layout/base/nsFrameManager.cpp
@@ -16,17 +16,16 @@
 #include "nsGkAtoms.h"
 #include "nsILayoutHistoryState.h"
 #include "mozilla/PresState.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/UndisplayedNode.h"
 #include "nsIDocument.h"
 
-#include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsAutoPtr.h"
 #include "nsAbsoluteContainingBlock.h"
 #include "ChildIterator.h"
 
 #include "GeckoProfiler.h"
 #include "nsIStatefulFrame.h"
 #include "nsContainerFrame.h"
--- a/layout/forms/nsMeterFrame.cpp
+++ b/layout/forms/nsMeterFrame.cpp
@@ -9,17 +9,16 @@
 #include "nsIContent.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsNameSpaceManager.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "nsNodeInfoManager.h"
 #include "nsContentCreatorFunctions.h"
-#include "nsContentUtils.h"
 #include "nsCheckboxRadioFrame.h"
 #include "nsFontMetrics.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLMeterElement.h"
 #include "nsCSSPseudoElements.h"
 #include "nsStyleConsts.h"
 #include <algorithm>
 
--- a/layout/forms/nsProgressFrame.cpp
+++ b/layout/forms/nsProgressFrame.cpp
@@ -9,17 +9,16 @@
 #include "nsIContent.h"
 #include "nsPresContext.h"
 #include "nsGkAtoms.h"
 #include "nsNameSpaceManager.h"
 #include "nsIDocument.h"
 #include "nsIPresShell.h"
 #include "nsNodeInfoManager.h"
 #include "nsContentCreatorFunctions.h"
-#include "nsContentUtils.h"
 #include "nsCheckboxRadioFrame.h"
 #include "nsFontMetrics.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/HTMLProgressElement.h"
 #include "nsCSSPseudoElements.h"
 #include "nsStyleConsts.h"
 #include <algorithm>
 
--- a/layout/forms/nsRangeFrame.cpp
+++ b/layout/forms/nsRangeFrame.cpp
@@ -6,17 +6,16 @@
 
 #include "nsRangeFrame.h"
 
 #include "mozilla/EventStates.h"
 #include "mozilla/TouchEvents.h"
 
 #include "gfxContext.h"
 #include "nsContentCreatorFunctions.h"
-#include "nsContentUtils.h"
 #include "nsCSSPseudoElements.h"
 #include "nsCSSRendering.h"
 #include "nsCheckboxRadioFrame.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsNameSpaceManager.h"
 #include "nsIPresShell.h"
 #include "nsGkAtoms.h"
--- a/layout/mathml/nsMathMLOperators.cpp
+++ b/layout/mathml/nsMathMLOperators.cpp
@@ -7,17 +7,16 @@
 #include "nsMathMLOperators.h"
 #include "nsCOMPtr.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsTArray.h"
 
 #include "nsIPersistentProperties2.h"
 #include "nsISimpleEnumerator.h"
-#include "nsContentUtils.h"
 #include "nsCRT.h"
 
 // operator dictionary entry
 struct OperatorData {
   OperatorData(void)
     : mFlags(0),
       mLeadingSpace(0.0f),
       mTrailingSpace(0.0f)
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -68,17 +68,16 @@
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/ServoBindings.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/Unused.h"
 #include "mozilla/ViewportFrame.h"
 #include "mozilla/gfx/gfxVars.h"
 #include "ActiveLayerTracker.h"
-#include "nsContentUtils.h"
 #include "nsPrintfCString.h"
 #include "UnitTransforms.h"
 #include "LayersLogging.h"
 #include "FrameLayerBuilder.h"
 #include "mozilla/EventStateManager.h"
 #include "nsCaret.h"
 #include "nsDOMTokenList.h"
 #include "nsCSSProps.h"
--- a/layout/style/StyleAnimationValue.cpp
+++ b/layout/style/StyleAnimationValue.cpp
@@ -14,17 +14,16 @@
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/Tuple.h"
 #include "mozilla/UniquePtr.h"
 #include "nsAutoPtr.h"
 #include "nsCOMArray.h"
 #include "nsString.h"
 #include "mozilla/ComputedStyle.h"
 #include "nsComputedDOMStyle.h"
-#include "nsContentUtils.h"
 #include "nsCSSPseudoElements.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/Likely.h"
 #include "mozilla/ServoBindings.h" // RawServoDeclarationBlock
 #include "mozilla/ServoCSSParser.h"
 #include "gfxMatrix.h"
 #include "gfxQuaternion.h"
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -16,17 +16,16 @@
 #include "mozilla/ServoStyleSet.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/AnimationEffect.h"
 #include "mozilla/dom/DocumentTimeline.h"
 #include "mozilla/dom/KeyframeEffect.h"
 
 #include "nsPresContext.h"
 #include "nsStyleChangeList.h"
-#include "nsContentUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsIFrame.h"
 #include "nsIDocument.h"
 #include "nsDOMMutationObserver.h"
 #include "nsIPresShell.h"
 #include "nsIPresShellInlines.h"
 #include "nsRFPService.h"
 #include <algorithm> // std::stable_sort
--- a/layout/style/nsDOMCSSAttrDeclaration.cpp
+++ b/layout/style/nsDOMCSSAttrDeclaration.cpp
@@ -8,17 +8,16 @@
 
 #include "nsDOMCSSAttrDeclaration.h"
 
 #include "mozilla/DeclarationBlock.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/InternalMutationEvent.h"
 #include "mozAutoDocUpdate.h"
-#include "nsContentUtils.h"
 #include "nsIDocument.h"
 #include "nsIURI.h"
 #include "nsNodeUtils.h"
 #include "nsSMILCSSValueType.h"
 #include "nsWrapperCacheInlines.h"
 #include "nsIFrame.h"
 #include "ActiveLayerTracker.h"
 
--- a/layout/style/nsDOMCSSDeclaration.cpp
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -13,17 +13,16 @@
 #include "mozilla/css/Rule.h"
 #include "mozilla/dom/CSS2PropertiesBinding.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "nsCSSProps.h"
 #include "nsCOMPtr.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIURI.h"
 #include "mozilla/dom/BindingUtils.h"
-#include "nsContentUtils.h"
 #include "nsQueryObject.h"
 #include "mozilla/layers/ScrollLinkedEffectDetector.h"
 
 using namespace mozilla;
 
 nsDOMCSSDeclaration::~nsDOMCSSDeclaration() = default;
 
 /* virtual */ JSObject*
--- a/layout/style/nsFontFaceLoader.cpp
+++ b/layout/style/nsFontFaceLoader.cpp
@@ -7,17 +7,16 @@
 /* code for loading in @font-face defined font data */
 
 #include "mozilla/IntegerPrintfMacros.h"
 #include "mozilla/Logging.h"
 
 #include "nsFontFaceLoader.h"
 
 #include "nsError.h"
-#include "nsContentUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/StaticPrefs.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/Unused.h"
 #include "FontFaceSet.h"
 #include "nsPresContext.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptSecurityManager.h"
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -6,17 +6,16 @@
 
 /* Code to start and animate CSS transitions. */
 
 #include "nsTransitionManager.h"
 #include "nsAnimationManager.h"
 #include "mozilla/dom/CSSTransitionBinding.h"
 
 #include "nsIContent.h"
-#include "nsContentUtils.h"
 #include "mozilla/ComputedStyle.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/TimeStamp.h"
 #include "nsRefreshDriver.h"
 #include "nsCSSPropertyIDSet.h"
 #include "mozilla/AnimationEventDispatcher.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/EffectSet.h"
--- a/layout/svg/SVGFEImageFrame.cpp
+++ b/layout/svg/SVGFEImageFrame.cpp
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Keep in (case-insensitive) order:
 #include "nsContainerFrame.h"
-#include "nsContentUtils.h"
 #include "nsFrame.h"
 #include "nsGkAtoms.h"
 #include "nsLiteralString.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGFilters.h"
 #include "mozilla/dom/SVGFEImageElement.h"
 #include "mozilla/dom/MutationEventBinding.h"
 
--- a/layout/svg/SVGImageContext.cpp
+++ b/layout/svg/SVGImageContext.cpp
@@ -6,17 +6,16 @@
 
 
 // Main header first:
 #include "SVGImageContext.h"
 
 // Keep others in (case-insensitive) order:
 #include "gfxUtils.h"
 #include "mozilla/Preferences.h"
-#include "nsContentUtils.h"
 #include "nsIFrame.h"
 #include "nsPresContext.h"
 #include "nsStyleStruct.h"
 
 namespace mozilla {
 
 /* static */ void
 SVGImageContext::MaybeStoreContextPaint(Maybe<SVGImageContext>& aContext,
--- a/layout/svg/nsSVGImageFrame.cpp
+++ b/layout/svg/nsSVGImageFrame.cpp
@@ -17,17 +17,16 @@
 #include "imgINotificationObserver.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGUtils.h"
 #include "SVGContentUtils.h"
 #include "SVGGeometryFrame.h"
 #include "SVGImageContext.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/dom/SVGImageElement.h"
-#include "nsContentUtils.h"
 #include "nsIReflowCallback.h"
 #include "mozilla/Unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::image;
 
--- a/layout/svg/nsSVGImageFrame.h
+++ b/layout/svg/nsSVGImageFrame.h
@@ -17,17 +17,16 @@
 #include "nsLayoutUtils.h"
 #include "imgINotificationObserver.h"
 #include "SVGObserverUtils.h"
 #include "nsSVGUtils.h"
 #include "SVGContentUtils.h"
 #include "SVGGeometryFrame.h"
 #include "SVGImageContext.h"
 #include "mozilla/dom/SVGImageElement.h"
-#include "nsContentUtils.h"
 #include "nsIReflowCallback.h"
 #include "mozilla/Unused.h"
 
 class nsSVGImageFrame;
 
 class nsSVGImageListener final : public imgINotificationObserver
 {
 public:
--- a/layout/svg/nsSVGPatternFrame.cpp
+++ b/layout/svg/nsSVGPatternFrame.cpp
@@ -10,17 +10,16 @@
 // Keep others in (case-insensitive) order:
 #include "AutoReferenceChainGuard.h"
 #include "gfx2DGlue.h"
 #include "gfxContext.h"
 #include "gfxMatrix.h"
 #include "gfxPattern.h"
 #include "gfxPlatform.h"
 #include "mozilla/gfx/2D.h"
-#include "nsContentUtils.h"
 #include "nsGkAtoms.h"
 #include "nsSVGDisplayableFrame.h"
 #include "mozilla/ComputedStyle.h"
 #include "SVGObserverUtils.h"
 #include "SVGGeometryFrame.h"
 #include "mozilla/dom/SVGPatternElement.h"
 #include "mozilla/dom/SVGUnitTypesBinding.h"
 #include "nsSVGUtils.h"
--- a/layout/svg/nsSVGUseFrame.cpp
+++ b/layout/svg/nsSVGUseFrame.cpp
@@ -1,16 +1,15 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsSVGUseFrame.h"
-#include "nsContentUtils.h"
 
 #include "mozilla/dom/SVGUseElement.h"
 #include "SVGObserverUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 //----------------------------------------------------------------------
--- a/layout/xul/nsDocElementBoxFrame.cpp
+++ b/layout/xul/nsDocElementBoxFrame.cpp
@@ -13,17 +13,16 @@
 #include "nsIPresShell.h"
 #include "nsBoxFrame.h"
 #include "nsStackLayout.h"
 #include "nsIAnonymousContentCreator.h"
 #include "mozilla/dom/NodeInfo.h"
 #include "nsIServiceManager.h"
 #include "nsNodeInfoManager.h"
 #include "nsContentCreatorFunctions.h"
-#include "nsContentUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/FromParser.h"
 
 //#define DEBUG_REFLOW
 
 using namespace mozilla::dom;
 
 class nsDocElementBoxFrame final : public nsBoxFrame
--- a/layout/xul/nsMenuBarFrame.cpp
+++ b/layout/xul/nsMenuBarFrame.cpp
@@ -19,17 +19,16 @@
 #include "nsUnicharUtils.h"
 #include "nsPIDOMWindow.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsCSSFrameConstructor.h"
 #ifdef XP_WIN
 #include "nsISound.h"
 #include "nsWidgetsCID.h"
 #endif
-#include "nsContentUtils.h"
 #include "nsUTF8Utils.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/Event.h"
 #include "mozilla/dom/KeyboardEvent.h"
 
 using namespace mozilla;
 using mozilla::dom::KeyboardEvent;
 
--- a/layout/xul/tree/TreeBoxObject.cpp
+++ b/layout/xul/tree/TreeBoxObject.cpp
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TreeBoxObject.h"
 #include "nsCOMPtr.h"
 #include "nsXULElement.h"
 #include "nsTreeContentView.h"
 #include "nsITreeSelection.h"
 #include "ChildIterator.h"
-#include "nsContentUtils.h"
 #include "nsError.h"
 #include "nsTreeBodyFrame.h"
 #include "mozilla/dom/TreeBoxObjectBinding.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/ToJSValue.h"
 
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -2,16 +2,17 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.GeckoProfileDirectories.NoMozillaDirectoryException;
+import org.mozilla.gecko.GeckoScreenOrientation.ScreenOrientation;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
 import org.mozilla.gecko.health.HealthRecorder;
 import org.mozilla.gecko.health.SessionInformation;
 import org.mozilla.gecko.health.StubbedHealthRecorder;
 import org.mozilla.gecko.home.HomeConfig.PanelType;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
@@ -35,17 +36,16 @@ import org.mozilla.gecko.util.EventCallb
 import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.PrefUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.ViewUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.AnchoredPopup;
-import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
 import android.annotation.TargetApi;
 import android.app.Activity;
@@ -108,16 +108,17 @@ import static org.mozilla.gecko.Tabs.INV
 import static org.mozilla.gecko.mma.MmaDelegate.DOWNLOAD_MEDIA_SAVED_IMAGE;
 import static org.mozilla.gecko.mma.MmaDelegate.READER_AVAILABLE;
 
 public abstract class GeckoApp extends GeckoActivity
                                implements AnchoredPopup.OnVisibilityChangeListener,
                                           BundleEventListener,
                                           GeckoMenu.Callback,
                                           GeckoMenu.MenuPresenter,
+                                          GeckoScreenOrientation.OrientationChangeListener,
                                           GeckoSession.ContentDelegate,
                                           ScreenOrientationDelegate,
                                           Tabs.OnTabsChangedListener,
                                           ViewTreeObserver.OnGlobalLayoutListener {
 
     private static final String LOGTAG = "GeckoApp";
     private static final long ONE_DAY_MS = TimeUnit.MILLISECONDS.convert(1, TimeUnit.DAYS);
 
@@ -977,16 +978,17 @@ public abstract class GeckoApp extends G
         earlyStartJavaSampler(intent);
 
         // Workaround for <http://code.google.com/p/android/issues/detail?id=20915>.
         try {
             Class.forName("android.os.AsyncTask");
         } catch (ClassNotFoundException e) { }
 
         GeckoAppShell.setScreenOrientationDelegate(this);
+        GeckoScreenOrientation.getInstance().addListener(this);
 
         // Tell Stumbler to register a local broadcast listener to listen for preference intents.
         // We do this via intents since we can't easily access Stumbler directly,
         // as it might be compiled outside of Fennec.
         final Intent stumblerIntent = new Intent(getApplicationContext(), SafeReceiver.class);
         stumblerIntent.setAction(INTENT_REGISTER_STUMBLER_LISTENER);
         getApplicationContext().sendBroadcast(stumblerIntent);
 
@@ -1060,18 +1062,16 @@ public abstract class GeckoApp extends G
             }
         } else if (savedInstanceState != null) {
             // Bug 896992 - This intent has already been handled; reset the intent.
             setIntent(new Intent(Intent.ACTION_MAIN));
         }
 
         super.onCreate(savedInstanceState);
 
-        GeckoScreenOrientation.getInstance().update(getResources().getConfiguration().orientation);
-
         setContentView(getLayout());
 
         // Set up Gecko layout.
         mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
         mLayerView = (GeckoView) findViewById(R.id.layer_view);
 
@@ -1925,21 +1925,16 @@ public abstract class GeckoApp extends G
         if (mIsAbortingAppLaunch) {
             return;
         }
 
         foregrounded = true;
 
         GeckoAppShell.setScreenOrientationDelegate(this);
 
-        int newOrientation = getResources().getConfiguration().orientation;
-        if (GeckoScreenOrientation.getInstance().update(newOrientation)) {
-            refreshChrome();
-        }
-
         // We use two times: a pseudo-unique wall-clock time to identify the
         // current session across power cycles, and the elapsed realtime to
         // track the duration of the session.
         final long now = System.currentTimeMillis();
         final long realTime = android.os.SystemClock.elapsedRealtime();
 
         ThreadUtils.postToBackgroundThread(new Runnable() {
             @Override
@@ -2129,16 +2124,18 @@ public abstract class GeckoApp extends G
             });
         }
 
         super.onDestroy();
 
         Tabs.unregisterOnTabsChangedListener(this);
         Tabs.getInstance().detachFromContext();
 
+        GeckoScreenOrientation.getInstance().removeListener(this);
+
         if (mShutdownOnDestroy) {
             GeckoApplication.shutdown(!mRestartOnShutdown ? null : new Intent(
                     Intent.ACTION_MAIN, /* uri */ null, getApplicationContext(), getClass()));
         }
     }
 
     public void showSDKVersionError() {
         final String message = getString(R.string.unsupported_sdk_version,
@@ -2173,29 +2170,16 @@ public abstract class GeckoApp extends G
         Log.d(LOGTAG, "onConfigurationChanged: " + newConfig.locale);
 
         final LocaleManager localeManager = BrowserLocaleManager.getInstance();
         final Locale changed = localeManager.onSystemConfigurationChanged(this, getResources(), newConfig, mLastLocale);
         if (changed != null) {
             onLocaleChanged(Locales.getLanguageTag(changed));
         }
 
-        // onConfigurationChanged is not called for 180 degree orientation changes,
-        // we will miss such rotations and the screen orientation will not be
-        // updated.
-        if (GeckoScreenOrientation.getInstance().update(newConfig.orientation)) {
-            if (mFormAssistPopup != null)
-                mFormAssistPopup.hide();
-            refreshChrome();
-        }
-
-        if (mPromptService != null) {
-            mPromptService.changePromptOrientation(newConfig.orientation);
-        }
-
         super.onConfigurationChanged(newConfig);
     }
 
     public String getContentProcessName() {
         return AppConstants.MOZ_CHILD_PROCESS_NAME;
     }
 
     public void addEnvToIntent(Intent intent) {
@@ -2523,9 +2507,17 @@ public abstract class GeckoApp extends G
         // We want to support the Screen Orientation API, and it always makes sense to lock the
         // orientation of a browser Activity, so we support locking.
         if (getRequestedOrientation() == requestedActivityInfoOrientation) {
             return false;
         }
         setRequestedOrientation(requestedActivityInfoOrientation);
         return true;
     }
+
+    @Override
+    public void onScreenOrientationChanged(ScreenOrientation newOrientation) {
+        if (mFormAssistPopup != null) {
+            mFormAssistPopup.hide();
+        }
+        refreshChrome();
+    }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsActivity.java
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.customtabs;
 
 import android.app.PendingIntent;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
 import android.content.pm.ResolveInfo;
-import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.net.Uri;
 import android.os.Bundle;
 import android.provider.Browser;
 import android.support.annotation.ColorInt;
 import android.support.annotation.NonNull;
@@ -34,18 +33,16 @@ import android.widget.ProgressBar;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.Clipboard;
 import org.mozilla.gecko.DoorHangerPopup;
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.FormAssistPopup;
 import org.mozilla.gecko.GeckoApplication;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SnackbarBuilder;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.menu.GeckoMenu;
 import org.mozilla.gecko.menu.GeckoMenuInflater;
 import org.mozilla.gecko.mozglue.SafeIntent;
 import org.mozilla.gecko.permissions.Permissions;
@@ -54,19 +51,17 @@ import org.mozilla.gecko.text.TextSelect
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.IntentUtils;
 import org.mozilla.gecko.util.PackageUtil;
 import org.mozilla.gecko.webapps.WebApps;
 import org.mozilla.gecko.widget.ActionModePresenter;
 import org.mozilla.gecko.widget.GeckoPopupMenu;
-import org.mozilla.geckoview.GeckoResponse;
 import org.mozilla.geckoview.GeckoResult;
-import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 import java.util.List;
 
 public class CustomTabsActivity extends AppCompatActivity
                                 implements ActionModePresenter,
@@ -186,25 +181,16 @@ public class CustomTabsActivity extends 
     }
 
     @Override
     public void onRequestPermissionsResult(final int requestCode, final String[] permissions,
                                            final int[] grantResults) {
         Permissions.onRequestPermissionsResult(this, permissions, grantResults);
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        if (mPromptService != null) {
-            mPromptService.changePromptOrientation(newConfig.orientation);
-        }
-    }
-
     private void sendTelemetry() {
         final SafeIntent startIntent = new SafeIntent(getIntent());
 
         Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab");
         if (IntentUtil.hasToolbarColor(startIntent)) {
             Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.INTENT, "customtab-hasToolbarColor");
         }
         if (IntentUtil.hasActionButton(startIntent)) {
--- a/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/prompts/PromptService.java
@@ -1,58 +1,62 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko.prompts;
 
 import org.mozilla.gecko.EventDispatcher;
+import org.mozilla.gecko.GeckoScreenOrientation;
+import org.mozilla.gecko.GeckoScreenOrientation.ScreenOrientation;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 
 import android.content.Context;
 
-public class PromptService implements BundleEventListener {
+public class PromptService implements BundleEventListener,
+                                      GeckoScreenOrientation.OrientationChangeListener {
     private static final String LOGTAG = "GeckoPromptService";
 
     private final Context context;
     private final EventDispatcher dispatcher;
     private Prompt currentPrompt;
-    private int currentOrientation;
 
     public PromptService(final Context context, final EventDispatcher dispatcher) {
         this.context = context;
-        this.currentOrientation = context.getResources().getConfiguration().orientation;
+        GeckoScreenOrientation.getInstance().addListener(this);
         this.dispatcher = dispatcher;
         this.dispatcher.registerUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
     }
 
     public void destroy() {
         dispatcher.unregisterUiThreadListener(this,
             "Prompt:Show",
             "Prompt:ShowTop");
+        GeckoScreenOrientation.getInstance().removeListener(this);
     }
 
     // BundleEventListener implementation
     @Override
     public void handleMessage(final String event, final GeckoBundle message,
                               final EventCallback callback) {
         currentPrompt = new Prompt(context, new Prompt.PromptCallback() {
             @Override
             public void onPromptFinished(final GeckoBundle result) {
                 callback.sendSuccess(result);
                 currentPrompt = null;
             }
         });
         currentPrompt.show(message);
     }
 
-    public void changePromptOrientation(int newOrientation) {
-        if (currentPrompt != null && currentOrientation != newOrientation) {
+    // OrientationChangeListener implementation
+    @Override
+    public void onScreenOrientationChanged(ScreenOrientation newOrientation) {
+        if (currentPrompt != null) {
             currentPrompt.resetLayout();
         }
-        currentOrientation = newOrientation;
     }
 }
--- a/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
+++ b/mobile/android/base/java/org/mozilla/gecko/webapps/WebAppActivity.java
@@ -4,17 +4,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.webapps;
 
 import android.annotation.TargetApi;
 import android.app.ActivityManager;
 import android.content.ActivityNotFoundException;
 import android.content.Intent;
-import android.content.res.Configuration;
 import android.graphics.Bitmap;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
 import android.support.customtabs.CustomTabsIntent;
 import android.support.v7.app.AppCompatActivity;
 import android.support.v7.view.ActionMode;
 import android.util.Log;
@@ -24,29 +23,25 @@ import android.widget.Toast;
 
 import org.mozilla.gecko.ActivityHandlerHelper;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserApp;
 import org.mozilla.gecko.DoorHangerPopup;
 import org.mozilla.gecko.FormAssistPopup;
 import org.mozilla.gecko.GeckoApplication;
 import org.mozilla.gecko.GeckoScreenOrientation;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.preferences.GeckoPreferences;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.customtabs.CustomTabsActivity;
 import org.mozilla.gecko.permissions.Permissions;
 import org.mozilla.gecko.prompts.PromptService;
 import org.mozilla.gecko.text.TextSelection;
 import org.mozilla.gecko.util.ActivityUtils;
 import org.mozilla.gecko.util.ColorUtil;
 import org.mozilla.gecko.widget.ActionModePresenter;
-import org.mozilla.geckoview.GeckoResponse;
 import org.mozilla.geckoview.GeckoResult;
-import org.mozilla.geckoview.GeckoRuntime;
 import org.mozilla.geckoview.GeckoSession;
 import org.mozilla.geckoview.GeckoSessionSettings;
 import org.mozilla.geckoview.GeckoView;
 
 public class WebAppActivity extends AppCompatActivity
                             implements ActionModePresenter,
                                        GeckoSession.ContentDelegate,
                                        GeckoSession.NavigationDelegate {
@@ -173,25 +168,16 @@ public class WebAppActivity extends AppC
             return;
         }
 
         updateFromManifest();
 
         mGeckoSession.loadUri(mManifest.getStartUri().toString());
     }
 
-    @Override
-    public void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-
-        if (mPromptService != null) {
-            mPromptService.changePromptOrientation(newConfig.orientation);
-        }
-    }
-
     private void fallbackToFennec(String message) {
         if (message != null) {
             Toast.makeText(this, message, Toast.LENGTH_LONG).show();
         }
 
         try {
             Intent intent = new Intent(this, BrowserApp.class);
             intent.setAction(Intent.ACTION_VIEW);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoAppShell.java
@@ -23,49 +23,37 @@ import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.StringTokenizer;
 import java.util.TreeMap;
 
 import org.mozilla.gecko.annotation.JNITarget;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.annotation.WrapForJNI;
-import org.mozilla.gecko.permissions.Permissions;
-import org.mozilla.gecko.process.GeckoProcessManager;
-import org.mozilla.gecko.SysInfo;
 import org.mozilla.gecko.util.BitmapUtils;
 import org.mozilla.gecko.util.HardwareCodecCapabilityUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.IOUtils;
 import org.mozilla.gecko.util.ProxySelector;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.geckoview.BuildConfig;
 
-import android.Manifest;
 import android.annotation.SuppressLint;
-import android.app.Activity;
 import android.app.ActivityManager;
 import android.content.Context;
 import android.content.Intent;
 import android.content.SharedPreferences;
 import android.content.pm.ActivityInfo;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
-import android.content.pm.Signature;
 import android.content.res.TypedArray;
 import android.graphics.Bitmap;
 import android.graphics.ImageFormat;
 import android.graphics.PixelFormat;
 import android.graphics.Rect;
-import android.graphics.RectF;
-import android.graphics.SurfaceTexture;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.hardware.Camera;
 import android.hardware.Sensor;
 import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.location.Criteria;
@@ -73,43 +61,32 @@ import android.location.Location;
 import android.location.LocationListener;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Bundle;
-import android.os.Environment;
 import android.os.LocaleList;
 import android.os.Looper;
-import android.os.ParcelFileDescriptor;
 import android.os.PowerManager;
 import android.os.StrictMode;
-import android.os.SystemClock;
 import android.os.Vibrator;
 import android.provider.Settings;
-import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.v4.util.SimpleArrayMap;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
-import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.ContextThemeWrapper;
 import android.view.Display;
 import android.view.HapticFeedbackConstants;
-import android.view.Surface;
-import android.view.SurfaceView;
-import android.view.TextureView;
-import android.view.View;
 import android.view.WindowManager;
-import android.view.inputmethod.InputMethodManager;
 import android.webkit.MimeTypeMap;
-import android.widget.AbsoluteLayout;
 
 public class GeckoAppShell
 {
     private static final String LOGTAG = "GeckoAppShell";
 
     // We have static members only.
     private GeckoAppShell() { }
 
@@ -256,18 +233,16 @@ public class GeckoAppShell
 
     // helper methods
     @WrapForJNI
     /* package */ static native void reportJavaCrash(Throwable exc, String stackTrace);
 
     @WrapForJNI(dispatchTo = "gecko")
     public static native void notifyUriVisited(String uri);
 
-    private static Rect sScreenSize;
-
     @WrapForJNI(stubName = "NotifyObservers", dispatchTo = "gecko")
     private static native void nativeNotifyObservers(String topic, String data);
 
     @RobocopTarget
     public static void notifyObservers(final String topic, final String data) {
         notifyObservers(topic, data, GeckoThread.State.RUNNING);
     }
 
@@ -1843,29 +1818,22 @@ public class GeckoAppShell
             return 2;
         } else if (pm.hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN)) {
             // 1 finger
             return 1;
         }
         return 0;
     }
 
-    public static synchronized void resetScreenSize() {
-        sScreenSize = null;
-    }
-
     @WrapForJNI(calledFrom = "gecko")
-    private static synchronized Rect getScreenSize() {
-        if (sScreenSize == null) {
-            final WindowManager wm = (WindowManager)
-                    getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
-            final Display disp = wm.getDefaultDisplay();
-            sScreenSize = new Rect(0, 0, disp.getWidth(), disp.getHeight());
-        }
-        return sScreenSize;
+    private static Rect getScreenSize() {
+        final WindowManager wm = (WindowManager)
+                getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
+        final Display disp = wm.getDefaultDisplay();
+        return new Rect(0, 0, disp.getWidth(), disp.getHeight());
     }
 
     @WrapForJNI(calledFrom = "any")
     public static int getAudioOutputFramesPerBuffer() {
         final int DEFAULT = 512;
 
         if (SysInfo.getVersion() < 17) {
             return DEFAULT;
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/GeckoScreenOrientation.java
@@ -8,17 +8,19 @@ package org.mozilla.gecko;
 import android.content.Context;
 import android.content.pm.ActivityInfo;
 import android.content.res.Configuration;
 import android.util.Log;
 import android.view.Surface;
 import android.view.WindowManager;
 
 import org.mozilla.gecko.annotation.WrapForJNI;
+import org.mozilla.gecko.util.ThreadUtils;
 
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 
 /*
  * Updates, locks and unlocks the screen orientation.
  *
  * Note: Replaces the OnOrientationChangeListener to avoid redundant rotation
  * event handling.
@@ -59,27 +61,50 @@ public class GeckoScreenOrientation {
     private static GeckoScreenOrientation sInstance;
     // Default rotation, used when device rotation is unknown.
     private static final int DEFAULT_ROTATION = Surface.ROTATION_0;
     // Last updated screen orientation.
     private ScreenOrientation mScreenOrientation;
     // Whether the update should notify Gecko about screen orientation changes.
     private boolean mShouldNotify = true;
 
+    public interface OrientationChangeListener {
+        void onScreenOrientationChanged(ScreenOrientation newOrientation);
+    }
+
+    private final List<OrientationChangeListener> mListeners;
+
     public static GeckoScreenOrientation getInstance() {
         if (sInstance == null) {
             sInstance = new GeckoScreenOrientation();
         }
         return sInstance;
     }
 
     private GeckoScreenOrientation() {
+        mListeners = new ArrayList<>();
         update();
     }
 
+    /**
+     * Add a listener that will be notified when the screen orientation has changed.
+     */
+    public void addListener(OrientationChangeListener aListener) {
+        ThreadUtils.assertOnUiThread();
+        mListeners.add(aListener);
+    }
+
+    /**
+     * Remove a OrientationChangeListener again.
+     */
+    public void removeListener(OrientationChangeListener aListener) {
+        ThreadUtils.assertOnUiThread();
+        mListeners.remove(aListener);
+    }
+
     /*
      * Enable Gecko screen orientation events on update.
      */
     public void enableNotifications() {
         update();
         mShouldNotify = true;
     }
 
@@ -124,42 +149,60 @@ public class GeckoScreenOrientation {
     /*
      * Update screen orientation given the screen orientation.
      *
      * @param aScreenOrientation
      *        Gecko screen orientation based on android orientation and rotation.
      *
      * @return Whether the screen orientation has changed.
      */
-    public boolean update(ScreenOrientation aScreenOrientation) {
+    public synchronized boolean update(ScreenOrientation aScreenOrientation) {
         if (mScreenOrientation == aScreenOrientation) {
             return false;
         }
         mScreenOrientation = aScreenOrientation;
         Log.d(LOGTAG, "updating to new orientation " + mScreenOrientation);
+        notifyListeners(mScreenOrientation);
         if (mShouldNotify) {
             // Gecko expects a definite screen orientation, so we default to the
             // primary orientations.
             if (aScreenOrientation == ScreenOrientation.PORTRAIT) {
                 aScreenOrientation = ScreenOrientation.PORTRAIT_PRIMARY;
             } else if (aScreenOrientation == ScreenOrientation.LANDSCAPE) {
                 aScreenOrientation = ScreenOrientation.LANDSCAPE_PRIMARY;
             }
 
             if (GeckoThread.isRunning()) {
                 onOrientationChange(aScreenOrientation.value, getAngle());
             } else {
                 GeckoThread.queueNativeCall(GeckoScreenOrientation.class, "onOrientationChange",
                                             aScreenOrientation.value, getAngle());
             }
         }
-        GeckoAppShell.resetScreenSize();
+        ScreenManagerHelper.refreshScreenInfo();
         return true;
     }
 
+    private void notifyListeners(final ScreenOrientation newOrientation) {
+        final Runnable notifier = new Runnable() {
+            @Override
+            public void run() {
+                for (OrientationChangeListener listener : mListeners) {
+                    listener.onScreenOrientationChanged(newOrientation);
+                }
+            }
+        };
+
+        if (ThreadUtils.isOnUiThread()) {
+            notifier.run();
+        } else {
+            ThreadUtils.postToUiThread(notifier);
+        }
+    }
+
     /*
      * @return The Android orientation (Configuration.orientation).
      */
     public int getAndroidOrientation() {
         return screenOrientationToAndroidOrientation(getScreenOrientation());
     }
 
     /*
@@ -188,38 +231,42 @@ public class GeckoScreenOrientation {
      *        rotation.
      *
      * @return Whether the locking was successful.
      */
     public boolean lock(ScreenOrientation aScreenOrientation) {
         Log.d(LOGTAG, "locking to " + aScreenOrientation);
         final ScreenOrientationDelegate delegate = GeckoAppShell.getScreenOrientationDelegate();
         final int activityInfoOrientation = screenOrientationToActivityInfoOrientation(aScreenOrientation);
-        if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
-            update(aScreenOrientation);
-            return true;
-        } else {
-            return false;
+        synchronized (this) {
+            if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
+                update(aScreenOrientation);
+                return true;
+            } else {
+                return false;
+            }
         }
     }
 
     /**
      * Unlock and update screen orientation.
      *
      * @return Whether the unlocking was successful.
      */
     public boolean unlock() {
         Log.d(LOGTAG, "unlocking");
         final ScreenOrientationDelegate delegate = GeckoAppShell.getScreenOrientationDelegate();
         final int activityInfoOrientation = screenOrientationToActivityInfoOrientation(ScreenOrientation.DEFAULT);
-        if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
-            update();
-            return true;
-        } else {
-            return false;
+        synchronized (this) {
+            if (delegate.setRequestedOrientationForCurrentActivity(activityInfoOrientation)) {
+                update();
+                return true;
+            } else {
+                return false;
+            }
         }
     }
 
     /*
      * Combine the Android orientation and rotation to the Gecko orientation.
      *
      * @param aAndroidOrientation
      *        Android orientation from Configuration.orientation.
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ScreenManagerHelper.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/ScreenManagerHelper.java
@@ -13,16 +13,30 @@ class ScreenManagerHelper {
     /**
      * The following display types use the same definition in nsIScreen.idl
      */
     final static int DISPLAY_PRIMARY  = 0; // primary screen
     final static int DISPLAY_EXTERNAL = 1; // wired displays, such as HDMI, DisplayPort, etc.
     final static int DISPLAY_VIRTUAL  = 2; // wireless displays, such as Chromecast, WiFi-Display, etc.
 
     /**
+     * Trigger a refresh of the cached screen information held by Gecko.
+     */
+    public static void refreshScreenInfo() {
+        // Screen data is initialised automatically on startup, so no need to queue the call if
+        // Gecko isn't running yet.
+        if (GeckoThread.isRunning()) {
+            nativeRefreshScreenInfo();
+        }
+    }
+
+    @WrapForJNI(stubName = "RefreshScreenInfo", dispatchTo = "gecko")
+    private native static void nativeRefreshScreenInfo();
+
+    /**
      * Add a new nsScreen when a new display in Android is available.
      *
      * @param displayType the display type of the nsScreen would be added
      * @param width       the width of the new nsScreen
      * @param height      the height of the new nsScreen
      * @param density     the density of the new nsScreen
      *
      * @return            return the ID of the added nsScreen
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoRuntime.java
@@ -1,27 +1,26 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * vim: ts=4 sw=4 expandtab:
  * 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/. */
 
 package org.mozilla.geckoview;
 
-import android.content.SharedPreferences;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.content.Context;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.util.Log;
 
 import org.mozilla.gecko.EventDispatcher;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.GeckoScreenOrientation;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import java.io.File;
@@ -253,16 +252,32 @@ public final class GeckoRuntime implemen
      * internal data.
      *
      * @return Profile directory
      */
     @NonNull public File getProfileDir() {
         return GeckoThread.getActiveProfile().getDir();
     }
 
+    /**
+     * Notify Gecko that the screen orientation has changed.
+     */
+    public void orientationChanged() {
+        GeckoScreenOrientation.getInstance().update();
+    }
+
+    /**
+     * Notify Gecko that the screen orientation has changed.
+     * @param newOrientation The new screen orientation, as retrieved e.g. from the current
+     *                       {@link android.content.res.Configuration}.
+     */
+    public void orientationChanged(int newOrientation) {
+        GeckoScreenOrientation.getInstance().update(newOrientation);
+    }
+
     @Override // Parcelable
     public int describeContents() {
         return 0;
     }
 
     @Override // Parcelable
     public void writeToParcel(Parcel out, int flags) {
         out.writeParcelable(mSettings, flags);
--- a/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/GeckoView.java
@@ -11,30 +11,30 @@ import org.mozilla.gecko.EventDispatcher
 import org.mozilla.gecko.gfx.DynamicToolbarAnimator;
 import org.mozilla.gecko.gfx.PanZoomController;
 import org.mozilla.gecko.gfx.GeckoDisplay;
 import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.util.ActivityUtils;
 
 import android.app.Activity;
 import android.content.Context;
+import android.content.res.Configuration;
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.os.Build;
 import android.os.Handler;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.support.annotation.Nullable;
 import android.support.annotation.NonNull;
 import android.util.AttributeSet;
 import android.util.DisplayMetrics;
 import android.util.TypedValue;
-import android.view.InputDevice;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.SurfaceHolder;
 import android.view.SurfaceView;
 import android.view.View;
 import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
@@ -337,16 +337,17 @@ public class GeckoView extends FrameLayo
     public void onAttachedToWindow() {
         if (mSession == null) {
             setSession(new GeckoSession(), GeckoRuntime.getDefault(getContext()));
         }
 
         if (!mSession.isOpen()) {
             mSession.open(mRuntime);
         }
+        mRuntime.orientationChanged();
 
         super.onAttachedToWindow();
     }
 
     @Override
     public void onDetachedFromWindow() {
         super.onDetachedFromWindow();
 
@@ -356,16 +357,28 @@ public class GeckoView extends FrameLayo
 
         // If we saved state earlier, we don't want to close the window.
         if (!mStateSaved && mSession.isOpen()) {
             mSession.close();
         }
     }
 
     @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+
+        if (mRuntime != null) {
+            // onConfigurationChanged is not called for 180 degree orientation changes,
+            // we will miss such rotations and the screen orientation will not be
+            // updated.
+            mRuntime.orientationChanged(newConfig.orientation);
+        }
+    }
+
+    @Override
     public boolean gatherTransparentRegion(final Region region) {
         // For detecting changes in SurfaceView layout, we take a shortcut here and
         // override gatherTransparentRegion, instead of registering a layout listener,
         // which is more expensive.
         if (mSurfaceView != null) {
             mDisplay.onGlobalLayout();
         }
         return super.gatherTransparentRegion(region);
@@ -388,17 +401,17 @@ public class GeckoView extends FrameLayo
 
         final SavedState ss = (SavedState) state;
         super.onRestoreInstanceState(ss.getSuperState());
 
         if (mSession == null && ss.session != null) {
             setSession(ss.session, ss.session.getRuntime());
         } else if (ss.session != null) {
             mSession.transferFrom(ss.session);
-            mRuntime = ss.session.getRuntime();
+            mRuntime = mSession.getRuntime();
         }
     }
 
     @Override
     public void onFocusChanged(boolean gainFocus, int direction, Rect previouslyFocusedRect) {
         super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
 
         if (!gainFocus || mIsResettingFocus) {
--- a/testing/mozbase/mozprocess/mozprocess/processhandler.py
+++ b/testing/mozbase/mozprocess/mozprocess/processhandler.py
@@ -8,17 +8,20 @@ import errno
 import os
 import signal
 import subprocess
 import sys
 import threading
 import time
 import traceback
 
-from Queue import Queue, Empty
+try:
+    from queue import Queue, Empty
+except ImportError:
+    from Queue import Queue, Empty
 from datetime import datetime
 
 
 __all__ = ['ProcessHandlerMixin', 'ProcessHandler', 'LogOutput',
            'StoreOutput', 'StreamOutput']
 
 # Set the MOZPROCESS_DEBUG environment variable to 1 to see some debugging output
 MOZPROCESS_DEBUG = os.getenv("MOZPROCESS_DEBUG")
@@ -119,24 +122,24 @@ class ProcessHandlerMixin(object):
                 raise
 
         def debug(self, msg):
             if not MOZPROCESS_DEBUG:
                 return
             thread = threading.current_thread().name
             print("DBG::MOZPROC PID:{} ({}) | {}".format(self.pid, thread, msg))
 
-        def __del__(self, _maxint=sys.maxint):
+        def __del__(self, _maxint=sys.maxsize):
             if isWin:
                 handle = getattr(self, '_handle', None)
                 if handle:
                     if hasattr(self, '_internal_poll'):
                         self._internal_poll(_deadstate=_maxint)
                     else:
-                        self.poll(_deadstate=sys.maxint)
+                        self.poll(_deadstate=sys.maxsize)
                 if handle or self._job or self._io_port:
                     self._cleanup()
             else:
                 subprocess.Popen.__del__(self)
 
         def kill(self, sig=None):
             if isWin:
                 try:
@@ -1064,17 +1067,17 @@ class StoreOutput(object):
 class StreamOutput(object):
     """pass output to a stream and flush"""
 
     def __init__(self, stream):
         self.stream = stream
 
     def __call__(self, line):
         try:
-            self.stream.write(line + '\n')
+            self.stream.write(line.decode() + '\n')
         except UnicodeDecodeError:
             # TODO: Workaround for bug #991866 to make sure we can display when
             # when normal UTF-8 display is failing
             self.stream.write(line.decode('iso8859-1') + '\n')
         self.stream.flush()
 
 
 class LogOutput(StreamOutput):
--- a/testing/mozbase/mozprocess/setup.py
+++ b/testing/mozbase/mozprocess/setup.py
@@ -1,28 +1,29 @@
 # 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/.
 
 from __future__ import absolute_import
 
 from setuptools import setup
 
-PACKAGE_VERSION = '0.26'
+PACKAGE_VERSION = '1.0.0'
 
 setup(name='mozprocess',
       version=PACKAGE_VERSION,
       description="Mozilla-authored process handling",
       long_description="see https://firefox-source-docs.mozilla.org/mozbase/index.html",
       classifiers=['Environment :: Console',
                    'Intended Audience :: Developers',
                    'License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)',
                    'Natural Language :: English',
                    'Operating System :: OS Independent',
-                   'Programming Language :: Python',
+                   'Programming Language :: Python :: 2.7',
+                   'Programming Language :: Python :: 3.5'
                    'Topic :: Software Development :: Libraries :: Python Modules',
                    ],
       keywords='mozilla',
       author='Mozilla Automation and Tools team',
       author_email='tools@lists.mozilla.org',
       url='https://wiki.mozilla.org/Auto-tools/Projects/Mozbase',
       license='MPL 2.0',
       packages=['mozprocess'],
--- a/testing/mozbase/mozprocess/tests/manifest.ini
+++ b/testing/mozbase/mozprocess/tests/manifest.ini
@@ -1,10 +1,9 @@
 [DEFAULT]
 subsuite = mozbase, os == "linux"
-skip-if = python == 3
 [test_kill.py]
 [test_misc.py]
 [test_poll.py]
 [test_wait.py]
 [test_output.py]
 [test_params.py]
 [test_process_reader.py]
--- a/testing/mozbase/mozprocess/tests/proclaunch.py
+++ b/testing/mozbase/mozprocess/tests/proclaunch.py
@@ -1,15 +1,18 @@
 #!/usr/bin/env python
 
 from __future__ import absolute_import, print_function
 
 import argparse
 import collections
-import ConfigParser
+try:
+    import configparser as ConfigParser
+except ImportError:
+    import ConfigParser
 import multiprocessing
 import time
 
 ProcessNode = collections.namedtuple('ProcessNode', ['maxtime', 'children'])
 
 
 class ProcessLauncher(object):
 
--- a/testing/mozbase/mozprocess/tests/test_output.py
+++ b/testing/mozbase/mozprocess/tests/test_output.py
@@ -1,19 +1,19 @@
 #!/usr/bin/env python
 
 from __future__ import absolute_import
 
-import io
 import os
 
 import mozunit
 
 import proctest
 from mozprocess import processhandler
+import six
 
 here = os.path.dirname(os.path.abspath(__file__))
 
 
 class ProcTestOutput(proctest.ProcTest):
     """ Class to test operations related to output handling """
 
     def test_process_output_twice(self):
@@ -44,34 +44,32 @@ class ProcTestOutput(proctest.ProcTest):
         self.determine_status(p, False, ())
 
     def test_stream_process_output(self):
         """
         Process output stream does not buffer
         """
         expected = '\n'.join([str(n) for n in range(0, 10)])
 
-        stream = io.BytesIO()
-        buf = io.BufferedRandom(stream)
+        stream = six.StringIO()
 
         p = processhandler.ProcessHandler([self.python,
                                            os.path.join("scripts", "proccountfive.py")],
                                           cwd=here,
-                                          stream=buf)
+                                          stream=stream)
 
         p.run()
         p.wait()
         for i in range(5, 10):
             stream.write(str(i) + '\n')
 
-        buf.flush()
         self.assertEquals(stream.getvalue().strip(), expected)
 
         # make sure mozprocess doesn't close the stream
         # since mozprocess didn't create it
-        self.assertFalse(buf.closed)
-        buf.close()
+        self.assertFalse(stream.closed)
+        stream.close()
 
         self.determine_status(p, False, ())
 
 
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozprocess/tests/test_process_reader.py
+++ b/testing/mozbase/mozprocess/tests/test_process_reader.py
@@ -28,38 +28,38 @@ class TestProcessReader(unittest.TestCas
         def on_timeout():
             self.timeout = True
         self.reader = ProcessReader(stdout_callback=self.out,
                                     stderr_callback=self.err,
                                     finished_callback=on_finished,
                                     timeout_callback=on_timeout)
 
     def test_stdout_callback(self):
-        proc = run_python('print 1; print 2')
+        proc = run_python('print(1); print(2)')
         self.reader.start(proc)
         self.reader.thread.join()
 
-        self.assertEqual(self.out.output, ['1', '2'])
+        self.assertEqual(self.out.output, [b'1', b'2'])
         self.assertEqual(self.err.output, [])
 
     def test_stderr_callback(self):
         proc = run_python('import sys; sys.stderr.write("hello world\\n")')
         self.reader.start(proc)
         self.reader.thread.join()
 
         self.assertEqual(self.out.output, [])
-        self.assertEqual(self.err.output, ['hello world'])
+        self.assertEqual(self.err.output, [b'hello world'])
 
     def test_stdout_and_stderr_callbacks(self):
-        proc = run_python('import sys; sys.stderr.write("hello world\\n"); print 1; print 2')
+        proc = run_python('import sys; sys.stderr.write("hello world\\n"); print(1); print(2)')
         self.reader.start(proc)
         self.reader.thread.join()
 
-        self.assertEqual(self.out.output, ['1', '2'])
-        self.assertEqual(self.err.output, ['hello world'])
+        self.assertEqual(self.out.output, [b'1', b'2'])
+        self.assertEqual(self.err.output, [b'hello world'])
 
     def test_finished_callback(self):
         self.assertFalse(self.finished)
         proc = run_python('')
         self.reader.start(proc)
         self.reader.thread.join()
         self.assertTrue(self.finished)
 
@@ -80,28 +80,28 @@ class TestProcessReader(unittest.TestCas
         self.reader.thread.join()
         self.assertTrue(self.timeout)
         self.assertFalse(self.finished)
 
     def test_read_without_eol(self):
         proc = run_python('import sys; sys.stdout.write("1")')
         self.reader.start(proc)
         self.reader.thread.join()
-        self.assertEqual(self.out.output, ['1'])
+        self.assertEqual(self.out.output, [b'1'])
 
     def test_read_with_strange_eol(self):
         proc = run_python('import sys; sys.stdout.write("1\\r\\r\\r\\n")')
         self.reader.start(proc)
         self.reader.thread.join()
-        self.assertEqual(self.out.output, ['1'])
+        self.assertEqual(self.out.output, [b'1'])
 
     def test_mixed_stdout_stderr(self):
-        proc = run_python('import sys; sys.stderr.write("hello world\\n"); print 1; print 2',
+        proc = run_python('import sys; sys.stderr.write("hello world\\n"); print(1); print(2)',
                           stderr=subprocess.STDOUT)
         self.reader.start(proc)
         self.reader.thread.join()
 
-        self.assertEqual(sorted(self.out.output), sorted(['1', '2', 'hello world']))
+        self.assertEqual(sorted(self.out.output), sorted([b'1', b'2', b'hello world']))
         self.assertEqual(self.err.output, [])
 
 
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozrunner/setup.py
+++ b/testing/mozbase/mozrunner/setup.py
@@ -11,17 +11,17 @@ PACKAGE_VERSION = '7.0.1'
 
 desc = """Reliable start/stop/configuration of Mozilla Applications (Firefox, Thunderbird, etc.)"""
 
 deps = [
     'mozdevice>=1.*',
     'mozfile==1.*',
     'mozinfo>=0.7,<2',
     'mozlog==3.*',
-    'mozprocess>=0.23,<1',
+    'mozprocess>=0.23,<2',
     'mozprofile>=1.1.0,<2',
     'six>=1.10.0,<2',
 ]
 
 EXTRAS_REQUIRE = {'crash': ['mozcrash >= 1.0']}
 
 
 setup(name=PACKAGE_NAME,
--- a/testing/talos/talos/output.py
+++ b/testing/talos/talos/output.py
@@ -115,16 +115,22 @@ class Output(object):
                         subtest_index += 1
 
                         if test.test_config.get('lower_is_better') is not None:
                             subtest['lowerIsBetter'] = \
                                 test.test_config['lower_is_better']
                         if test.test_config.get('alert_threshold') is not None:
                             subtest['alertThreshold'] = \
                                 test.test_config['alert_threshold']
+                        if test.test_config.get('subtest_alerts') is not None:
+                            subtest['shouldAlert'] = \
+                                test.test_config['subtest_alerts']
+                        if test.test_config.get('alert_threshold') is not None:
+                            subtest['alertThreshold'] = \
+                                test.test_config['alert_threshold']
                         if test.test_config.get('unit'):
                             subtest['unit'] = test.test_config['unit']
 
                 # if there is more than one subtest, calculate a summary result
                 if len(subtests) > 1:
                     suite['value'] = self.construct_results(
                         vals, testname=test.name())
                 if test.test_config.get('lower_is_better') is not None:
--- a/testing/talos/talos/test.py
+++ b/testing/talos/talos/test.py
@@ -32,16 +32,18 @@ class Test(object):
     __test__ = False  # not pytest
 
     cycles = None  # number of cycles
     keys = []
     desktop = True
     filters = filter.ignore_first.prepare(1) + filter.median.prepare()
     lower_is_better = True
     alert_threshold = 2.0
+    perfherder_framework = 'talos'
+    subtest_alerts = False
 
     @classmethod
     def name(cls):
         return cls.__name__
 
     @classmethod
     def description(cls):
         if cls.__doc__ is None:
@@ -240,25 +242,27 @@ class tresize(TsBase):
 
 class PageloaderTest(Test):
     """abstract base class for a Talos Pageloader test"""
     extensions = ['${talos}/pageloader']
     tpmanifest = None  # test manifest
     tpcycles = 1  # number of time to run each page
     cycles = None
     timeout = None
+
     keys = ['tpmanifest', 'tpcycles', 'tppagecycles', 'tprender', 'tpchrome',
             'tpmozafterpaint', 'fnbpaint', 'tphero', 'tploadnocache', 'firstpaint',
             'userready', 'testeventmap', 'base_vs_ref', 'mainthread', 'resolution',
             'cycles', 'gecko_profile', 'gecko_profile_interval', 'gecko_profile_entries',
             'tptimeout', 'win_counters', 'w7_counters', 'linux_counters', 'mac_counters',
             'tpscrolltest', 'xperf_counters', 'timeout', 'responsiveness',
             'profile_path', 'xperf_providers', 'xperf_user_providers', 'xperf_stackwalk',
             'format_pagename', 'filters', 'preferences', 'extensions', 'setup', 'cleanup',
-            'lower_is_better', 'alert_threshold', 'unit', 'webextensions', 'profile']
+            'lower_is_better', 'alert_threshold', 'unit', 'webextensions', 'profile',
+            'subtest_alerts', 'perfherder_framework']
 
 
 class QuantumPageloadTest(PageloaderTest):
     """
     Base class for a Quantum Pageload test
     """
     tpcycles = 1
     tppagecycles = 25
@@ -425,16 +429,18 @@ class damp(PageloaderTest):
     tpmozafterpaint = False
     gecko_profile_interval = 10
     gecko_profile_entries = 1000000
     win_counters = w7_counters = linux_counters = mac_counters = None
     filters = filter.ignore_first.prepare(1) + filter.median.prepare()
     preferences = {'devtools.memory.enabled': True,
                    'addon.test.damp.webserver': '${webserver}'}
     unit = 'ms'
+    subtest_alerts = True
+    perfherder_framework = 'devtools'
 
 
 @register_test()
 class glterrain(PageloaderTest):
     """
     Simple rotating WebGL scene with moving light source over a
     textured terrain.
     Measures average frame intervals.
--- a/testing/talos/talos/ttest.py
+++ b/testing/talos/talos/ttest.py
@@ -141,16 +141,21 @@ class TTest(object):
         for i in range(test_config['cycles']):
             time.sleep(0.25)
             LOG.info('Running cycle %d/%d for %s test...'
                      % (i+1, test_config['cycles'], test_config['name']))
 
             # remove the browser  error file
             mozfile.remove(browser_config['error_filename'])
 
+            # individual tests can have different frameworks
+            # TODO: ensure that we don't run >1 test with custom frameworks
+            if test_config.get('perfherder_framework', None) is not None:
+                test_results.framework = test_config['perfherder_framework']
+
             # reinstall any file whose stability we need to ensure across
             # the cycles
             if test_config.get('reinstall', ''):
                 for keep in test_config['reinstall']:
                     origin = os.path.join(test_config['profile_path'],
                                           keep)
                     dest = os.path.join(setup.profile_dir, keep)
                     LOG.debug('Reinstalling %s on top of %s'
--- a/testing/tps/setup.py
+++ b/testing/tps/setup.py
@@ -8,17 +8,17 @@ import sys
 
 version = '0.6'
 
 deps = ['httplib2 == 0.9.2',
         'mozfile == 1.2',
         'mozhttpd == 0.7',
         'mozinfo >= 0.10',
         'mozinstall == 1.16',
-        'mozprocess == 0.26',
+        'mozprocess == 1.0.0',
         'mozprofile == 1.1.0',
         'mozrunner == 7.0.1',
         'mozversion == 1.5',
        ]
 
 # we only support python 2.6+ right now
 assert sys.version_info[0] == 2
 assert sys.version_info[1] >= 6
--- a/toolkit/components/url-classifier/SafeBrowsing.jsm
+++ b/toolkit/components/url-classifier/SafeBrowsing.jsm
@@ -405,98 +405,76 @@ var SafeBrowsing = {
         "blockedEnabled:", this.blockedEnabled,
         "trackingAnnotations", this.trackingAnnotations,
         "flashBlockEnabled", this.flashBlockEnabled,
         "flashInfobarListEnabled:", this.flashInfobarListEnabled);
 
     let listManager = Cc["@mozilla.org/url-classifier/listmanager;1"].
                       getService(Ci.nsIUrlListManager);
 
+    listManager.disableAllUpdates();
+
     for (let i = 0; i < this.phishingLists.length; ++i) {
       if (this.phishingEnabled) {
         listManager.enableUpdate(this.phishingLists[i]);
-      } else {
-        listManager.disableUpdate(this.phishingLists[i]);
       }
     }
     for (let i = 0; i < this.malwareLists.length; ++i) {
       if (this.malwareEnabled) {
         listManager.enableUpdate(this.malwareLists[i]);
-      } else {
-        listManager.disableUpdate(this.malwareLists[i]);
       }
     }
     for (let i = 0; i < this.downloadBlockLists.length; ++i) {
       if (this.malwareEnabled && this.downloadsEnabled) {
         listManager.enableUpdate(this.downloadBlockLists[i]);
-      } else {
-        listManager.disableUpdate(this.downloadBlockLists[i]);
       }
     }
     for (let i = 0; i < this.downloadAllowLists.length; ++i) {
       if (this.malwareEnabled && this.downloadsEnabled) {
         listManager.enableUpdate(this.downloadAllowLists[i]);
-      } else {
-        listManager.disableUpdate(this.downloadAllowLists[i]);
       }
     }
     for (let i = 0; i < this.passwordAllowLists.length; ++i) {
       if (this.passwordsEnabled) {
         listManager.enableUpdate(this.passwordAllowLists[i]);
-      } else {
-        listManager.disableUpdate(this.passwordAllowLists[i]);
       }
     }
     for (let i = 0; i < this.trackingAnnotationLists.length; ++i) {
       if (this.trackingAnnotations) {
         listManager.enableUpdate(this.trackingAnnotationLists[i]);
-      } else {
-        listManager.disableUpdate(this.trackingAnnotationLists[i]);
       }
     }
     for (let i = 0; i < this.trackingAnnotationWhitelists.length; ++i) {
       if (this.trackingAnnotations) {
         listManager.enableUpdate(this.trackingAnnotationWhitelists[i]);
-      } else {
-        listManager.disableUpdate(this.trackingAnnotationWhitelists[i]);
       }
     }
     for (let i = 0; i < this.trackingProtectionLists.length; ++i) {
       if (this.trackingEnabled) {
         listManager.enableUpdate(this.trackingProtectionLists[i]);
-      } else {
-        listManager.disableUpdate(this.trackingProtectionLists[i]);
       }
     }
     for (let i = 0; i < this.trackingProtectionWhitelists.length; ++i) {
       if (this.trackingEnabled) {
         listManager.enableUpdate(this.trackingProtectionWhitelists[i]);
-      } else {
-        listManager.disableUpdate(this.trackingProtectionWhitelists[i]);
       }
     }
     for (let i = 0; i < this.blockedLists.length; ++i) {
       if (this.blockedEnabled) {
         listManager.enableUpdate(this.blockedLists[i]);
-      } else {
-        listManager.disableUpdate(this.blockedLists[i]);
       }
     }
     for (let i = 0; i < this.flashLists.length; ++i) {
       if (this.flashBlockEnabled) {
         listManager.enableUpdate(this.flashLists[i]);
-      } else {
-        listManager.disableUpdate(this.flashLists[i]);
       }
     }
     for (let i = 0; i < this.flashInfobarLists.length; ++i) {
       if (this.flashInfobarListEnabled) {
         listManager.enableUpdate(this.flashInfobarLists[i]);
-      } else {
-        listManager.disableUpdate(this.flashInfobarLists[i]);
       }
     }
     listManager.maybeToggleUpdateChecking();
   },
 
 
   addMozEntries() {
     // Add test entries to the DB.
--- a/toolkit/components/url-classifier/nsIUrlListManager.idl
+++ b/toolkit/components/url-classifier/nsIUrlListManager.idl
@@ -46,17 +46,22 @@ interface nsIUrlListManager : nsISupport
 
     /**
      * Turn on update checking for a table. I.e., during the next server
      * check, download updates for this table.
      */
     void enableUpdate(in ACString tableName);
 
     /**
-     * Turn off update checking for a table.
+     * Turn off update checking for all tables.
+     */
+    void disableAllUpdates();
+
+    /**
+     * Turn off update checking for a single table. Only used in tests.
      */
     void disableUpdate(in ACString tableName);
 
     /**
      * Toggle update checking, if necessary.
      */
     void maybeToggleUpdateChecking();
 
--- a/toolkit/components/url-classifier/nsUrlClassifierListManager.js
+++ b/toolkit/components/url-classifier/nsUrlClassifierListManager.js
@@ -163,18 +163,17 @@ PROT_ListManager.prototype.getGethashUrl
 PROT_ListManager.prototype.getUpdateUrl = function(tableName) {
   if (this.tablesData[tableName] && this.tablesData[tableName].updateUrl) {
     return this.tablesData[tableName].updateUrl;
   }
   return "";
 };
 
 /**
- * Enable updates for some tables
- * @param tables - an array of table names that need updating
+ * Enable updates for a single table.
  */
 PROT_ListManager.prototype.enableUpdate = function(tableName) {
   var table = this.tablesData[tableName];
   if (table) {
     log("Enabling table updates for " + tableName);
     this.needsUpdate_[table.updateUrl][tableName] = true;
   }
 };
@@ -189,18 +188,27 @@ PROT_ListManager.prototype.updatesNeeded
     if (this.needsUpdate_[updateUrl][tableName]) {
       updatesNeeded = true;
     }
   }
   return updatesNeeded;
 };
 
 /**
- * Disables updates for some tables
- * @param tables - an array of table names that no longer need updating
+ * Disable updates for all tables.
+ */
+PROT_ListManager.prototype.disableAllUpdates = function() {
+  for (const tableName of Object.keys(this.tablesData)) {
+    this.disableUpdate(tableName);
+  }
+};
+
+/**
+ * Disables updates for a single table. Avoid this internal function
+ * and use disableAllUpdates() instead.
  */
 PROT_ListManager.prototype.disableUpdate = function(tableName) {
   var table = this.tablesData[tableName];
   if (table) {
     log("Disabling table updates for " + tableName);
     this.needsUpdate_[table.updateUrl][tableName] = false;
     if (!this.updatesNeeded_(table.updateUrl) &&
         this.updateCheckers_[table.updateUrl]) {
--- a/widget/android/ScreenHelperAndroid.cpp
+++ b/widget/android/ScreenHelperAndroid.cpp
@@ -21,16 +21,20 @@ using namespace mozilla::widget;
 static ScreenHelperAndroid* gHelper = nullptr;
 
 class ScreenHelperAndroid::ScreenHelperSupport final
     : public ScreenManagerHelper::Natives<ScreenHelperSupport>
 {
 public:
     typedef ScreenManagerHelper::Natives<ScreenHelperSupport> Base;
 
+    static void RefreshScreenInfo() {
+      gHelper->Refresh();
+    }
+
     static int32_t AddDisplay(int32_t aDisplayType, int32_t aWidth, int32_t aHeight, float aDensity) {
         static Atomic<uint32_t> nextId;
 
         uint32_t screenId = ++nextId;
         NS_DispatchToMainThread(NS_NewRunnableFunction(
             "ScreenHelperAndroid::ScreenHelperSupport::AddDisplay",
             [aDisplayType, aWidth, aHeight, aDensity, screenId] {
                 MOZ_ASSERT(NS_IsMainThread());