Merge mozilla-central to autoland. a=merge CLOSED TREE
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Wed, 16 May 2018 13:06:18 +0300
changeset 472694 1a3c751f67b60c37c827d719a505edcca8144fde
parent 472693 be23dc007b537d742e1721ac66e4f4aef1b7aa63 (current diff)
parent 472684 3c9d69736f4a421218e5eb01b6571d535d38318a (diff)
child 472695 54538505ee0a40b4eac7ca35cfbfe870118089b2
push id9374
push userjlund@mozilla.com
push dateMon, 18 Jun 2018 21:43:20 +0000
treeherdermozilla-beta@160e085dfb0b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone62.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 autoland. a=merge CLOSED TREE
taskcluster/ci/beetmover-cdns/kind.yml
taskcluster/taskgraph/transforms/beetmover_cdns.py
toolkit/mozapps/extensions/test/xpcshell/data/test_proxy/bootstrap.js
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -714,18 +714,16 @@ pref("plugins.show_infobar", false);
 // Should dismissing the hidden plugin infobar suppress it permanently?
 pref("plugins.remember_infobar_dismissal", true);
 
 pref("plugin.default.state", 1);
 
 // Plugins bundled in XPIs are enabled by default.
 pref("plugin.defaultXpi.state", 2);
 
-// Java is Click-to-Activate by default on all channels.
-pref("plugin.state.java", 1);
 
 // Flash is Click-to-Activate by default on all channels.
 pref("plugin.state.flash", 1);
 
 // Enables the download and use of the flash blocklists.
 pref("plugins.flashBlock.enabled", true);
 
 // Prefer HTML5 video over Flash content, and don't
--- a/browser/base/content/test/tabs/browser_multiselect_tabs_using_Ctrl.js
+++ b/browser/base/content/test/tabs/browser_multiselect_tabs_using_Ctrl.js
@@ -1,33 +1,39 @@
 const PREF_MULTISELECT_TABS = "browser.tabs.multiselect";
 
-async function triggerClickOn(target, options) {
+function triggerClickOn(target, options) {
   let promise = BrowserTestUtils.waitForEvent(target, "click");
   if (AppConstants.platform == "macosx") {
     options = { metaKey: options.ctrlKey };
   }
   EventUtils.synthesizeMouseAtCenter(target, options);
   return promise;
 }
 
 async function addTab() {
-  const tab = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/");
+  const tab = BrowserTestUtils.addTab(gBrowser, "http://mochi.test:8888/", {skipAnimation: true});
   const browser = gBrowser.getBrowserForTab(tab);
   await BrowserTestUtils.browserLoaded(browser);
   return tab;
 }
 
 add_task(async function clickWithoutPrefSet() {
   let tab = await addTab();
   let mSelectedTabs = gBrowser._multiSelectedTabsMap;
 
   isnot(gBrowser.selectedTab, tab, "Tab doesn't have focus");
 
-  await triggerClickOn(tab, { ctrlKey: true });
+  // We make sure that the tab-switch is completely done before executing checks
+  await BrowserTestUtils.switchTab(gBrowser, () => {
+    triggerClickOn(tab, { ctrlKey: true });
+  });
+
+  await TestUtils.waitForCondition(() => gBrowser.selectedTab == tab,
+    "Wait for the selectedTab getter to update");
 
   ok(!tab.multiselected && !mSelectedTabs.has(tab),
     "Multi-select tab doesn't work when multi-select pref is not set");
   is(gBrowser.selectedTab, tab,
     "Tab has focus, selected tab has changed after Ctrl/Cmd + click");
 
   BrowserTestUtils.removeTab(tab);
 });
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1838,17 +1838,17 @@ BrowserGlue.prototype = {
       }
     }
   },
 
   // eslint-disable-next-line complexity
   _migrateUI: function BG__migrateUI() {
     // Use an increasing number to keep track of the current migration state.
     // Completely unrelated to the current Firefox release number.
-    const UI_VERSION = 68;
+    const UI_VERSION = 69;
     const BROWSER_DOCURL = "chrome://browser/content/browser.xul";
 
     let currentUIVersion;
     if (Services.prefs.prefHasUserValue("browser.migration.version")) {
       currentUIVersion = Services.prefs.getIntPref("browser.migration.version");
     } else {
       // This is a new profile, nothing to migrate.
       Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
@@ -2200,16 +2200,27 @@ BrowserGlue.prototype = {
     }
 
     if (currentUIVersion < 68) {
       // Remove blocklists legacy storage, now relying on IndexedDB.
       OS.File.remove(OS.Path.join(OS.Constants.Path.profileDir,
                                   "kinto.sqlite"), {ignoreAbsent: true});
     }
 
+    if (currentUIVersion < 69) {
+      // Clear old social prefs from profile (bug 1460675)
+      let socialPrefs = Services.prefs.getBranch("social.");
+      if (socialPrefs) {
+        let socialPrefsArray = socialPrefs.getChildList("");
+        for (let item of socialPrefsArray) {
+          Services.prefs.clearUserPref("social." + item);
+        }
+      }
+    }
+
     // Update the migration version.
     Services.prefs.setIntPref("browser.migration.version", UI_VERSION);
   },
 
   _checkForDefaultBrowser() {
     // Perform default browser checking.
     if (!ShellService) {
       return;
new file mode 100644
--- /dev/null
+++ b/browser/components/tests/unit/test_browserGlue_migration_social_cleanup.js
@@ -0,0 +1,23 @@
+const UI_VERSION = 69;
+const TOPIC_BROWSERGLUE_TEST = "browser-glue-test";
+const TOPICDATA_BROWSERGLUE_TEST = "force-ui-migration";
+
+var gBrowserGlue = Cc["@mozilla.org/browser/browserglue;1"]
+                     .getService(Ci.nsIObserver);
+
+Services.prefs.setIntPref("browser.migration.version", UI_VERSION - 1);
+
+add_task(async function test_check_cleanup_social_prefs() {
+  Services.prefs.setStringPref("social.manifest.example-com", "example.com");
+
+  // Simulate a migration.
+  gBrowserGlue.observe(null, TOPIC_BROWSERGLUE_TEST, TOPICDATA_BROWSERGLUE_TEST);
+
+  Assert.ok(!Services.prefs.prefHasUserValue("social.manifest.example-com"),
+            "should have cleared old social preference 'social.manifest.example-com'");
+});
+
+registerCleanupFunction(() => {
+  Services.prefs.clearUserPref("browser.migration.version");
+  Services.prefs.clearUserPref("social.manifest.example-com");
+});
--- a/browser/components/tests/unit/xpcshell.ini
+++ b/browser/components/tests/unit/xpcshell.ini
@@ -4,9 +4,10 @@ firefox-appdir = browser
 skip-if = toolkit == 'android'
 support-files =
   distribution.ini
   data/engine-de-DE.xml
 
 [test_browserGlue_migration_loop_cleanup.js]
 [test_distribution.js]
 [test_distribution_cachedexistence.js]
+[test_browserGlue_migration_social_cleanup.js]
 [test_browserGlue_pingcentre.js]
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -610,41 +610,16 @@ Navigator::GetDoNotTrack(nsAString &aRes
 
   if (doNotTrack) {
     aResult.AssignLiteral("1");
   } else {
     aResult.AssignLiteral("unspecified");
   }
 }
 
-bool
-Navigator::JavaEnabled(CallerType aCallerType, ErrorResult& aRv)
-{
-  Telemetry::AutoTimer<Telemetry::CHECK_JAVA_ENABLED> telemetryTimer;
-
-  // Return true if we have a handler for the java mime
-  nsAutoString javaMIME;
-  Preferences::GetString("plugin.java.mime", javaMIME);
-  NS_ENSURE_TRUE(!javaMIME.IsEmpty(), false);
-
-  if (!mMimeTypes) {
-    if (!mWindow) {
-      aRv.Throw(NS_ERROR_UNEXPECTED);
-      return false;
-    }
-    mMimeTypes = new nsMimeTypeArray(mWindow);
-  }
-
-  RefreshMIMEArray();
-
-  nsMimeType *mimeType = mMimeTypes->NamedItem(javaMIME, aCallerType);
-
-  return mimeType && mimeType->GetEnabledPlugin();
-}
-
 uint64_t
 Navigator::HardwareConcurrency()
 {
   workerinternals::RuntimeService* rts =
     workerinternals::RuntimeService::GetOrCreateService();
   if (!rts) {
     return 1;
   }
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -153,17 +153,20 @@ public:
   void GetOscpu(nsAString& aOscpu, CallerType aCallerType,
                 ErrorResult& aRv) const;
   void GetVendorSub(nsAString& aVendorSub);
   void GetVendor(nsAString& aVendor);
   void GetProductSub(nsAString& aProductSub);
   bool CookieEnabled();
   void GetBuildID(nsAString& aBuildID, CallerType aCallerType,
                   ErrorResult& aRv) const;
-  bool JavaEnabled(CallerType aCallerType, ErrorResult& aRv);
+  bool JavaEnabled()
+  {
+    return false;
+  }
   uint64_t HardwareConcurrency();
   bool TaintEnabled()
   {
     return false;
   }
   void AddIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
   void RemoveIdleObserver(MozIdleObserver& aObserver, ErrorResult& aRv);
 
--- a/dom/base/StructuredCloneHolder.h
+++ b/dom/base/StructuredCloneHolder.h
@@ -37,17 +37,20 @@ namespace dom {
 class StructuredCloneHolderBase
 {
 public:
   typedef JS::StructuredCloneScope StructuredCloneScope;
 
   StructuredCloneHolderBase(StructuredCloneScope aScope = StructuredCloneScope::SameProcessSameThread);
   virtual ~StructuredCloneHolderBase();
 
-  StructuredCloneHolderBase(StructuredCloneHolderBase&& aOther) = default;
+  // Note, it is unsafe to Move() a StructuredCloneHolderBase since a raw
+  // this pointer is passed to mBuffer as a callback closure.  That must
+  // be fixed if you want to implement a move constructor here.
+  StructuredCloneHolderBase(StructuredCloneHolderBase&& aOther) = delete;
 
   // These methods should be implemented in order to clone data.
   // Read more documentation in js/public/StructuredClone.h.
 
   virtual JSObject* CustomReadHandler(JSContext* aCx,
                                       JSStructuredCloneReader* aReader,
                                       uint32_t aTag,
                                       uint32_t aIndex) = 0;
@@ -168,17 +171,17 @@ public:
   // data can be read and written. Additional checks about the nature of the
   // objects will be done based on this scope value because not all the
   // objects can be sent between threads or processes.
   explicit StructuredCloneHolder(CloningSupport aSupportsCloning,
                                  TransferringSupport aSupportsTransferring,
                                  StructuredCloneScope aStructuredCloneScope);
   virtual ~StructuredCloneHolder();
 
-  StructuredCloneHolder(StructuredCloneHolder&& aOther) = default;
+  StructuredCloneHolder(StructuredCloneHolder&& aOther) = delete;
 
   // Normally you should just use Write() and Read().
 
   void Write(JSContext* aCx,
              JS::Handle<JS::Value> aValue,
              ErrorResult &aRv);
 
   void Write(JSContext* aCx,
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -41,8 +41,9 @@ DEPRECATED_OPERATION(XMLBaseAttribute)
 DEPRECATED_OPERATION(WindowContentUntrusted)
 DEPRECATED_OPERATION(RegisterProtocolHandlerInsecure)
 DEPRECATED_OPERATION(MixedDisplayObjectSubrequest)
 DEPRECATED_OPERATION(MotionEvent)
 DEPRECATED_OPERATION(OrientationEvent)
 DEPRECATED_OPERATION(ProximityEvent)
 DEPRECATED_OPERATION(AmbientLightEvent)
 DEPRECATED_OPERATION(IDBOpenDBOptions_StorageType)
+DEPRECATED_OPERATION(DOMAttrModifiedEvent)
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -321,16 +321,19 @@ EventListenerManager::AddEventListenerIn
     // For mutation listeners, we need to update the global bit on the DOM window.
     // Otherwise we won't actually fire the mutation event.
     mMayHaveMutationListeners = true;
     // Go from our target to the nearest enclosing DOM window.
     if (nsPIDOMWindowInner* window = GetInnerWindowForTarget()) {
       nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
       if (doc) {
         doc->WarnOnceAbout(nsIDocument::eMutationEvent);
+        if (aEventMessage == eLegacyAttrModified) {
+          doc->WarnOnceAbout(nsIDocument::eDOMAttrModifiedEvent);
+        }
       }
       // If aEventMessage is eLegacySubtreeModified, we need to listen all
       // mutations. nsContentUtils::HasMutationListeners relies on this.
       window->SetMutationListeners(
         (aEventMessage == eLegacySubtreeModified) ?
           kAllMutationBits : MutationBitForEventType(aEventMessage));
     }
   } else if (aTypeAtom == nsGkAtoms::ondeviceorientation) {
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -98,27 +98,27 @@ using namespace mozilla::plugins::parent
 
 // We should make this const...
 static NPNetscapeFuncs sBrowserFuncs = {
   sizeof(sBrowserFuncs),
   (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR,
   _geturl,
   _posturl,
   _requestread,
-  nullptr,
-  nullptr,
-  nullptr,
+  nullptr, // _newstream, unimplemented
+  nullptr, // _write, unimplemented
+  nullptr, // _destroystream, unimplemented
   _status,
   _useragent,
   _memalloc,
   _memfree,
   _memflush,
   _reloadplugins,
-  _getJavaEnv,
-  _getJavaPeer,
+  nullptr, // _getJavaEnv, unimplemented
+  nullptr, // _getJavaPeer, unimplemented
   _geturlnotify,
   _posturlnotify,
   _getvalue,
   _setvalue,
   _invalidaterect,
   _invalidateregion,
   _forceredraw,
   _getstringidentifier,
@@ -1697,24 +1697,16 @@ NPError
 }
 
 NPError
 _requestread(NPStream *pstream, NPByteRange *rangeList)
 {
   return NPERR_STREAM_NOT_SEEKABLE;
 }
 
-// Deprecated, only stubbed out
-void* /* OJI type: JRIEnv* */
-_getJavaEnv()
-{
-  NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaEnv\n"));
-  return nullptr;
-}
-
 const char *
 _useragent(NPP npp)
 {
   if (!NS_IsMainThread()) {
     NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_useragent called from the wrong thread\n"));
     return nullptr;
   }
   NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_UserAgent: npp=%p\n", (void*)npp));
@@ -1738,24 +1730,16 @@ void *
 {
   if (!NS_IsMainThread()) {
     NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL,("NPN_memalloc called from the wrong thread\n"));
   }
   NPN_PLUGIN_LOG(PLUGIN_LOG_NOISY, ("NPN_MemAlloc: size=%d\n", size));
   return moz_xmalloc(size);
 }
 
-// Deprecated, only stubbed out
-void* /* OJI type: jref */
-_getJavaPeer(NPP npp)
-{
-  NPN_PLUGIN_LOG(PLUGIN_LOG_NORMAL, ("NPN_GetJavaPeer: npp=%p\n", (void*)npp));
-  return nullptr;
-}
-
 void
 _pushpopupsenabledstate(NPP npp, NPBool enabled)
 {
   if (!NS_IsMainThread()) {
     NPN_PLUGIN_LOG(PLUGIN_LOG_ALWAYS,("NPN_pushpopupsenabledstate called from the wrong thread\n"));
     return;
   }
   nsNPAPIPluginInstance *inst = npp ? (nsNPAPIPluginInstance *)npp->ndata : nullptr;
--- a/dom/plugins/base/nsNPAPIPlugin.h
+++ b/dom/plugins/base/nsNPAPIPlugin.h
@@ -274,23 +274,16 @@ void
 _forceredraw(NPP npp);
 
 const char*
 _useragent(NPP npp);
 
 void*
 _memalloc (uint32_t size);
 
-// Deprecated entry points for the old Java plugin.
-void* /* OJI type: JRIEnv* */
-_getJavaEnv();
-
-void* /* OJI type: jref */
-_getJavaPeer(NPP npp);
-
 void
 _urlredirectresponse(NPP instance, void* notifyData, NPBool allow);
 
 NPError
 _initasyncsurface(NPP instance, NPSize *size, NPImageFormat format, void *initData, NPAsyncSurface *surface);
 
 NPError
 _finalizeasyncsurface(NPP instance, NPAsyncSurface *surface);
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -54,17 +54,16 @@ nsNPAPIPluginInstance::nsNPAPIPluginInst
   , mUsesDOMForCursor(false)
   , mInPluginInitCall(false)
   , mPlugin(nullptr)
   , mMIMEType(nullptr)
   , mOwner(nullptr)
 #ifdef XP_MACOSX
   , mCurrentPluginEvent(nullptr)
 #endif
-  , mHaveJavaC2PJSObjectQuirk(false)
   , mCachedParamLength(0)
   , mCachedParamNames(nullptr)
   , mCachedParamValues(nullptr)
   , mMuted(false)
 {
   mNPP.pdata = nullptr;
   mNPP.ndata = this;
 
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -293,19 +293,16 @@ private:
   // non-null during a HandleEvent call
   void* mCurrentPluginEvent;
 #endif
 
   // Timestamp for the last time this plugin was stopped.
   // This is only valid when the plugin is actually stopped!
   mozilla::TimeStamp mStopTime;
 
-  // is this instance Java and affected by bug 750480?
-  bool mHaveJavaC2PJSObjectQuirk;
-
   static uint32_t gInUnsafePluginCalls;
 
   // The arrays can only be released when the plugin instance is destroyed,
   // because the plugin, in in-process mode, might keep a reference to them.
   uint32_t mCachedParamLength;
   char **mCachedParamNames;
   char **mCachedParamValues;
 
--- a/dom/plugins/ipc/PluginModuleChild.cpp
+++ b/dom/plugins/ipc/PluginModuleChild.cpp
@@ -870,24 +870,16 @@ static void
 _forceredraw(NPP aNPP);
 
 static const char*
 _useragent(NPP aNPP);
 
 static void*
 _memalloc (uint32_t size);
 
-// Deprecated entry points for the old Java plugin.
-static void* /* OJI type: JRIEnv* */
-_getjavaenv(void);
-
-// Deprecated entry points for the old Java plugin.
-static void* /* OJI type: jref */
-_getjavapeer(NPP aNPP);
-
 static bool
 _invoke(NPP aNPP, NPObject* npobj, NPIdentifier method, const NPVariant *args,
         uint32_t argCount, NPVariant *result);
 
 static bool
 _invokedefault(NPP aNPP, NPObject* npobj, const NPVariant *args,
                uint32_t argCount, NPVariant *result);
 
@@ -973,27 +965,27 @@ static void
 } /* namespace mozilla */
 
 const NPNetscapeFuncs PluginModuleChild::sBrowserFuncs = {
     sizeof(sBrowserFuncs),
     (NP_VERSION_MAJOR << 8) + NP_VERSION_MINOR,
     mozilla::plugins::child::_geturl,
     mozilla::plugins::child::_posturl,
     mozilla::plugins::child::_requestread,
-    nullptr,
-    nullptr,
-    nullptr,
+    nullptr, // _newstream, unimplemented
+    nullptr, // _write, unimplemented
+    nullptr, // _destroystream, unimplemented
     mozilla::plugins::child::_status,
     mozilla::plugins::child::_useragent,
     mozilla::plugins::child::_memalloc,
     mozilla::plugins::child::_memfree,
     mozilla::plugins::child::_memflush,
     mozilla::plugins::child::_reloadplugins,
-    mozilla::plugins::child::_getjavaenv,
-    mozilla::plugins::child::_getjavapeer,
+    nullptr, // _getjavaenv, unimplemented
+    nullptr, // _getjavapeer, unimplemented
     mozilla::plugins::child::_geturlnotify,
     mozilla::plugins::child::_posturlnotify,
     mozilla::plugins::child::_getvalue,
     mozilla::plugins::child::_setvalue,
     mozilla::plugins::child::_invalidaterect,
     mozilla::plugins::child::_invalidateregion,
     mozilla::plugins::child::_forceredraw,
     PluginModuleChild::NPN_GetStringIdentifier,
@@ -1299,31 +1291,16 @@ const char*
 
 void*
 _memalloc(uint32_t aSize)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
     return moz_xmalloc(aSize);
 }
 
-// Deprecated entry points for the old Java plugin.
-void* /* OJI type: JRIEnv* */
-_getjavaenv(void)
-{
-    PLUGIN_LOG_DEBUG_FUNCTION;
-    return 0;
-}
-
-void* /* OJI type: jref */
-_getjavapeer(NPP aNPP)
-{
-    PLUGIN_LOG_DEBUG_FUNCTION;
-    return 0;
-}
-
 bool
 _invoke(NPP aNPP,
         NPObject* aNPObj,
         NPIdentifier aMethod,
         const NPVariant* aArgs,
         uint32_t aArgCount,
         NPVariant* aResult)
 {
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -130,17 +130,16 @@ static bool throwExceptionNextInvoke(NPO
 static bool convertPointX(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool convertPointY(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool streamTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool postFileToURLTest(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool setPluginWantsAllStreams(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool crashPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool crashOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
-static bool getJavaCodebase(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool checkObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool enableFPExceptions(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool hangPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool stallPlugin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getClipboardText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool callOnDestroy(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool reinitWidget(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool crashPluginInNestedLoop(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
@@ -202,17 +201,16 @@ static const NPUTF8* sPluginMethodIdenti
   "convertPointX",
   "convertPointY",
   "streamTest",
   "postFileToURLTest",
   "setPluginWantsAllStreams",
   "crash",
   "crashOnDestroy",
   "getObjectValue",
-  "getJavaCodebase",
   "checkObjectValue",
   "enableFPExceptions",
   "hang",
   "stall",
   "getClipboardText",
   "callOnDestroy",
   "reinitWidget",
   "crashInNestedLoop",
@@ -275,17 +273,16 @@ static const ScriptableFunction sPluginM
   convertPointX,
   convertPointY,
   streamTest,
   postFileToURLTest,
   setPluginWantsAllStreams,
   crashPlugin,
   crashOnDestroy,
   getObjectValue,
-  getJavaCodebase,
   checkObjectValue,
   enableFPExceptions,
   hangPlugin,
   stallPlugin,
   getClipboardText,
   callOnDestroy,
   reinitWidget,
   crashPluginInNestedLoop,
@@ -877,21 +874,16 @@ NPP_New(NPMIMEType pluginType, NPP insta
     // window gets destroyed).
     if (strcmp(argn[i], "cleanupwidget") == 0 &&
         strcmp(argv[i], "false") == 0) {
       instanceData->cleanupWidget = false;
     }
     if (strcmp(argn[i], "bugmode") == 0) {
       instanceData->bugMode = atoi(argv[i]);
     }
-    // Try to emulate java's codebase handling: Use the last seen codebase
-    // value, regardless of whether it is in attributes or params.
-    if (strcasecmp(argn[i], "codebase") == 0) {
-      instanceData->javaCodebase = argv[i];
-    }
 
     // Bug 1307694 - There are two flash parameters that are order dependent for
     // scaling/sizing the plugin. If they ever change from what is expected, it
     // breaks flash on the web. In a test, if the scale tag ever happens
     // with an salign before it, fail the plugin creation.
     if (strcmp(argn[i], "scale") == 0) {
       if (alreadyHasSalign) {
         // If salign came before this parameter, error out now.
@@ -2719,30 +2711,16 @@ void notifyDidPaint(InstanceData* instan
   }
 }
 
 static const NPClass kTestSharedNPClass = {
   NP_CLASS_STRUCT_VERSION,
   // Everything else is nullptr
 };
 
-static bool getJavaCodebase(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
-{
-  if (argCount != 0) {
-    return false;
-  }
-
-  NPP npp = static_cast<TestNPObject*>(npobj)->npp;
-  InstanceData* id = static_cast<InstanceData*>(npp->pdata);
-
-  char *outval = NPN_StrDup(id->javaCodebase.c_str());
-  STRINGZ_TO_NPVARIANT(outval, *result);
-  return true;
-}
-
 static bool getObjectValue(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
 {
   NPP npp = static_cast<TestNPObject*>(npobj)->npp;
 
   NPObject* o = NPN_CreateObject(npp,
                                  const_cast<NPClass*>(&kTestSharedNPClass));
   if (!o)
     return false;
--- a/dom/plugins/test/testplugin/nptest.h
+++ b/dom/plugins/test/testplugin/nptest.h
@@ -136,17 +136,16 @@ typedef struct InstanceData {
   ActivationState focusState;
   int32_t focusEventCount;
   int32_t eventModel;
   bool closeStream;
   std::string lastKeyText;
   bool wantsAllStreams;
   int32_t mouseUpEventCount;
   int32_t bugMode;
-  std::string javaCodebase;
   AsyncDrawing asyncDrawing;
   NPAsyncSurface *frontBuffer;
   NPAsyncSurface *backBuffer;
   std::string lastComposition;
   void* placeholderWnd;
   double cssZoomFactor;
 } InstanceData;
 
--- a/dom/serviceworkers/ServiceWorkerPrivate.cpp
+++ b/dom/serviceworkers/ServiceWorkerPrivate.cpp
@@ -516,45 +516,45 @@ public:
       return NS_ERROR_XPC_JS_THREW_EXCEPTION;
     }
 
     return NS_OK;
   }
 };
 
 class SendMessageEventRunnable final : public ExtendableEventWorkerRunnable
+                                     , public StructuredCloneHolder
 {
-  StructuredCloneHolder mData;
   const ClientInfoAndState mClientInfoAndState;
 
 public:
   SendMessageEventRunnable(WorkerPrivate*  aWorkerPrivate,
                            KeepAliveToken* aKeepAliveToken,
-                           StructuredCloneHolder&& aData,
                            const ClientInfoAndState& aClientInfoAndState)
     : ExtendableEventWorkerRunnable(aWorkerPrivate, aKeepAliveToken)
-    , mData(Move(aData))
+    , StructuredCloneHolder(CloningSupported, TransferringSupported,
+                            JS::StructuredCloneScope::SameProcessDifferentThread)
     , mClientInfoAndState(aClientInfoAndState)
   {
     MOZ_ASSERT(NS_IsMainThread());
   }
 
   bool
   WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
   {
     JS::Rooted<JS::Value> messageData(aCx);
     nsCOMPtr<nsIGlobalObject> sgo = aWorkerPrivate->GlobalScope();
     ErrorResult rv;
-    mData.Read(sgo, aCx, &messageData, rv);
+    Read(sgo, aCx, &messageData, rv);
     if (NS_WARN_IF(rv.Failed())) {
       return true;
     }
 
     Sequence<OwningNonNull<MessagePort>> ports;
-    if (!mData.TakeTransferredPortsAsSequence(ports)) {
+    if (!TakeTransferredPortsAsSequence(ports)) {
       return true;
     }
 
     RootedDictionary<ExtendableMessageEventInit> init(aCx);
 
     init.mBubbles = false;
     init.mCancelable = false;
 
@@ -635,36 +635,30 @@ ServiceWorkerPrivate::SendMessageEvent(i
     if (!JS_DefineElement(cx, array, i, value, JSPROP_ENUMERATE)) {
       return NS_ERROR_OUT_OF_MEMORY;
     }
   }
 
   JS::Rooted<JS::Value> transferable(cx);
   transferable.setObject(*array);
 
-  StructuredCloneHolder holder(StructuredCloneHolder::CloningSupported,
-                               StructuredCloneHolder::TransferringSupported,
-                               JS::StructuredCloneScope::DifferentProcess);
-  holder.Write(cx, messageData, transferable, JS::CloneDataPolicy(), rv);
-  if (rv.Failed()) {
-    return rv.StealNSResult();
-  }
-
-  // Now that the re-packing is complete, send a runnable to the service worker
-  // thread.
-
   rv = SpawnWorkerIfNeeded(MessageEvent);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   RefPtr<KeepAliveToken> token = CreateEventKeepAliveToken();
   RefPtr<SendMessageEventRunnable> runnable =
-    new SendMessageEventRunnable(mWorkerPrivate, token, Move(holder),
-                                 aClientInfoAndState);
+    new SendMessageEventRunnable(mWorkerPrivate, token, aClientInfoAndState);
+
+  runnable->Write(cx, messageData, transferable, JS::CloneDataPolicy(), rv);
+  if (rv.Failed()) {
+    return rv.StealNSResult();
+  }
+
   if (!runnable->Dispatch()) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 namespace {
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -181,17 +181,17 @@ partial interface Navigator {
   // WebKit/Blink supports this (hardcoded "20030107"); Trident/Presto don't
   readonly attribute DOMString productSub;
   // WebKit/Blink/Trident/Presto support this.
   readonly attribute boolean cookieEnabled;
   [Throws, Constant, Cached, NeedsCallerType]
   readonly attribute DOMString buildID;
 
   // WebKit/Blink/Trident/Presto support this.
-  [Throws, NeedsCallerType]
+  [Affects=Nothing, DependsOn=Nothing]
   boolean javaEnabled();
 
   /**
    * Navigator requests to add an idle observer to the existing window.
    */
   [Throws, ChromeOnly]
   void addIdleObserver(MozIdleObserver aIdleObserver);
 
--- a/gfx/layers/composite/LayerManagerComposite.h
+++ b/gfx/layers/composite/LayerManagerComposite.h
@@ -114,16 +114,17 @@ public:
   virtual void BeginTransactionWithDrawTarget(gfx::DrawTarget* aTarget,
                                               const gfx::IntRect& aRect) = 0;
   virtual Compositor* GetCompositor() const = 0;
   virtual TextureSourceProvider* GetTextureSourceProvider() const = 0;
   virtual void EndTransaction(const TimeStamp& aTimeStamp,
                               EndTransactionFlags aFlags = END_DEFAULT) = 0;
   virtual void UpdateRenderBounds(const gfx::IntRect& aRect) {}
   virtual void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) {}
+  virtual void InvalidateAll() = 0;
 
   virtual HostLayerManager* AsHostLayerManager() override {
     return this;
   }
   virtual LayerManagerMLGPU* AsLayerManagerMLGPU() {
     return nullptr;
   }
 
@@ -401,16 +402,20 @@ public:
   }
   virtual LayersBackend GetBackendType() override {
     return mCompositor ? mCompositor->GetBackendType() : LayersBackend::LAYERS_NONE;
   }
   virtual void SetDiagnosticTypes(DiagnosticTypes aDiagnostics) override {
     mCompositor->SetDiagnosticTypes(aDiagnostics);
   }
 
+  virtual void InvalidateAll() override {
+    AddInvalidRegion(nsIntRegion(mRenderBounds));
+  }
+
   void ForcePresent() override { mCompositor->ForcePresent(); }
 
 private:
   /** Region we're clipping our current drawing to. */
   nsIntRegion mClippingRegion;
   gfx::IntRect mRenderBounds;
 
   /** Current root layer. */
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -637,19 +637,18 @@ CompositorBridgeParent::RecvNotifyRegion
     mLayerManager->AddInvalidRegion(aRegion);
   }
   return IPC_OK();
 }
 
 void
 CompositorBridgeParent::Invalidate()
 {
-  if (mLayerManager && mLayerManager->GetRoot()) {
-    mLayerManager->AddInvalidRegion(
-                                    mLayerManager->GetRoot()->GetLocalVisibleRegion().ToUnknownRegion().GetBounds());
+  if (mLayerManager) {
+    mLayerManager->InvalidateAll();
   }
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvStartFrameTimeRecording(const int32_t& aBufferSize, uint32_t* aOutStartIndex)
 {
   if (mLayerManager) {
     *aOutStartIndex = mLayerManager->StartFrameTimeRecording(aBufferSize);
--- a/gfx/layers/mlgpu/LayerManagerMLGPU.h
+++ b/gfx/layers/mlgpu/LayerManagerMLGPU.h
@@ -64,16 +64,20 @@ public:
     return nullptr;
   }
   bool IsCompositingToScreen() const override;
   TextureSourceProvider* GetTextureSourceProvider() const override;
   void ClearCachedResources(Layer* aSubtree = nullptr) override;
   void NotifyShadowTreeTransaction() override;
   void UpdateRenderBounds(const gfx::IntRect& aRect) override;
 
+  void InvalidateAll() override {
+    AddInvalidRegion(nsIntRegion(mRenderBounds));
+  }
+
   LayerManagerMLGPU* AsLayerManagerMLGPU() override {
     return this;
   }
   const char* Name() const override {
     return "";
   }
 
   // This should only be called while a FrameBuilder is live.
--- a/js/src/devtools/automation/autospider.py
+++ b/js/src/devtools/automation/autospider.py
@@ -196,22 +196,16 @@ if opt is None:
     opt = variant.get('debug')
 if opt is not None:
     CONFIGURE_ARGS += (" --enable-debug" if opt else " --disable-debug")
 
 opt = args.jemalloc
 if opt is not None:
     CONFIGURE_ARGS += (" --enable-jemalloc" if opt else " --disable-jemalloc")
 
-# Any jobs that wish to produce additional output can save them into the upload
-# directory if there is such a thing, falling back to OBJDIR.
-env.setdefault('MOZ_UPLOAD_DIR', OBJDIR)
-ensure_dir_exists(env['MOZ_UPLOAD_DIR'], clobber=False, creation_marker_filename=None)
-info("MOZ_UPLOAD_DIR = {}".format(env['MOZ_UPLOAD_DIR']))
-
 # Some of the variants request a particular word size (eg ARM simulators).
 word_bits = variant.get('bits')
 
 # On Linux and Windows, we build 32- and 64-bit versions on a 64 bit
 # host, so the caller has to specify what is desired.
 if word_bits is None and args.platform:
     platform_arch = args.platform.split('-')[0]
     if platform_arch in ('win32', 'linux'):
@@ -309,16 +303,22 @@ def killall():
 
 timer = Timer(args.timeout, killall)
 timer.daemon = True
 timer.start()
 
 ensure_dir_exists(OBJDIR, clobber=not args.dep and not args.nobuild)
 ensure_dir_exists(OUTDIR, clobber=not args.keep)
 
+# Any jobs that wish to produce additional output can save them into the upload
+# directory if there is such a thing, falling back to OBJDIR.
+env.setdefault('MOZ_UPLOAD_DIR', OBJDIR)
+ensure_dir_exists(env['MOZ_UPLOAD_DIR'], clobber=False, creation_marker_filename=None)
+info("MOZ_UPLOAD_DIR = {}".format(env['MOZ_UPLOAD_DIR']))
+
 
 def run_command(command, check=False, **kwargs):
     kwargs.setdefault('cwd', OBJDIR)
     info("in directory {}, running {}".format(kwargs['cwd'], command))
     proc = Popen(command, **kwargs)
     ACTIVE_PROCESSES.add(proc)
     stdout, stderr = None, None
     try:
new file mode 100644
--- /dev/null
+++ b/js/src/tests/non262/literals/numeric/idstart-after-numeric.js
@@ -0,0 +1,15 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/licenses/publicdomain/ */
+
+var BUGNUMBER = '523401';
+var summary = 'numeric literal followed by an identifier';
+
+var array = new Array();
+assertThrowsInstanceOf(() => eval("array[0for]"), SyntaxError);
+assertThrowsInstanceOf(() => eval("array[1yield]"), SyntaxError);
+assertThrowsInstanceOf(() => eval("array[2in []]"), SyntaxError); // "2 in []" is valid.
+reportCompare(array[2 in []], undefined);
+reportCompare(2 in [], false);
+assertThrowsInstanceOf(() => eval("array[3in]"), SyntaxError);
+if (typeof reportCompare === "function")
+    reportCompare(0, 0);
new file mode 100644
new file mode 100644
--- a/js/xpconnect/loader/XPCOMUtils.jsm
+++ b/js/xpconnect/loader/XPCOMUtils.jsm
@@ -552,18 +552,198 @@ var XPCOMUtils = {
    */
   defineConstant: function XPCOMUtils__defineConstant(aObj, aName, aValue) {
     Object.defineProperty(aObj, aName, {
       value: aValue,
       enumerable: true,
       writable: false
     });
   },
+
+  /**
+   * Defines a proxy which acts as a lazy object getter that can be passed
+   * around as a reference, and will only be evaluated when something in
+   * that object gets accessed.
+   *
+   * The evaluation can be triggered by a function call, by getting or
+   * setting a property, calling this as a constructor, or enumerating
+   * the properties of this object (e.g. during an iteration).
+   *
+   * Please note that, even after evaluated, the object given to you
+   * remains being the proxy object (which forwards everything to the
+   * real object). This is important to correctly use these objects
+   * in pairs of add+remove listeners, for example.
+   * If your use case requires access to the direct object, you can
+   * get it through the untrap callback.
+   *
+   * @param aObject
+   *        The object to define the lazy getter on.
+   *
+   *        You can pass null to aObject if you just want to get this
+   *        proxy through the return value.
+   *
+   * @param aName
+   *        The name of the getter to define on aObject.
+   *
+   * @param aInitFuncOrResource
+   *        A function or a module that defines what this object actually
+   *        should be when it gets evaluated. This will only ever be called once.
+   *
+   *        Short-hand: If you pass a string to this parameter, it will be treated
+   *        as the URI of a module to be imported, and aName will be used as
+   *        the symbol to retrieve from the module.
+   *
+   * @param aStubProperties
+   *        In this parameter, you can provide an object which contains
+   *        properties from the original object that, when accessed, will still
+   *        prevent the entire object from being evaluated.
+   *
+   *        These can be copies or simplified versions of the original properties.
+   *
+   *        One example is to provide an alternative QueryInterface implementation
+   *        to avoid the entire object from being evaluated when it's added as an
+   *        observer (as addObserver calls object.QueryInterface(Ci.nsIObserver)).
+   *
+   *        Once the object has been evaluated, the properties from the real
+   *        object will be used instead of the ones provided here.
+   *
+   * @param aUntrapCallback
+   *        A function that gets called once when the object has just been evaluated.
+   *        You can use this to do some work (e.g. setting properties) that you need
+   *        to do on this object but that can wait until it gets evaluated.
+   *
+   *        Another use case for this is to use during code development to log when
+   *        this object gets evaluated, to make sure you're not accidentally triggering
+   *        it earlier than expected.
+   */
+  defineLazyProxy: function XPCOMUtils__defineLazyProxy(aObject, aName, aInitFuncOrResource,
+                                                        aStubProperties, aUntrapCallback) {
+    let initFunc = aInitFuncOrResource;
+
+    if (typeof(aInitFuncOrResource) == "string") {
+      initFunc = function () {
+        let tmp = {};
+        ChromeUtils.import(aInitFuncOrResource, tmp);
+        return tmp[aName];
+      };
+    }
+
+    let handler = new LazyProxyHandler(aName, initFunc,
+                                       aStubProperties, aUntrapCallback);
+
+    /*
+     * We cannot simply create a lazy getter for the underlying
+     * object and pass it as the target of the proxy, because
+     * just passing it in `new Proxy` means it would get
+     * evaluated. Becase of this, a full handler needs to be
+     * implemented (the LazyProxyHandler).
+     *
+     * So, an empty object is used as the target, and the handler
+     * replaces it on every call with the real object.
+     */
+    let proxy = new Proxy({}, handler);
+
+    if (aObject) {
+      Object.defineProperty(aObject, aName, {
+        value: proxy,
+        enumerable: true,
+        writable: true,
+      });
+    }
+
+    return proxy;
+  },
 };
 
+/**
+ * LazyProxyHandler
+ * This class implements the handler used
+ * in the proxy from defineLazyProxy.
+ *
+ * This handler forwards all calls to an underlying object,
+ * stored as `this.realObject`, which is obtained as the returned
+ * value from aInitFunc, which will be called on the first time
+ * time that it needs to be used (with an exception in the get() trap
+ * for the properties provided in the `aStubProperties` parameter).
+ */
+
+class LazyProxyHandler {
+  constructor(aName, aInitFunc, aStubProperties, aUntrapCallback) {
+    this.pending = true;
+    this.name = aName;
+    this.initFuncOrResource = aInitFunc;
+    this.stubProperties = aStubProperties;
+    this.untrapCallback = aUntrapCallback;
+  }
+
+  getObject() {
+    if (this.pending) {
+      this.realObject = this.initFuncOrResource.call(null);
+
+      if (this.untrapCallback) {
+        this.untrapCallback.call(null, this.realObject);
+        this.untrapCallback = null;
+      }
+
+      this.pending = false;
+      this.stubProperties = null;
+    }
+    return this.realObject;
+  }
+
+  getPrototypeOf(target) {
+    return Reflect.getPrototypeOf(this.getObject());
+  }
+
+  setPrototypeOf(target, prototype) {
+    return Reflect.setPrototypeOf(this.getObject(), prototype);
+  }
+
+  isExtensible(target) {
+    return Reflect.isExtensible(this.getObject());
+  }
+
+  preventExtensions(target) {
+    return Reflect.preventExtensions(this.getObject());
+  }
+
+  getOwnPropertyDescriptor(target, prop) {
+    return Reflect.getOwnPropertyDescriptor(this.getObject(), prop);
+  }
+
+  defineProperty(target, prop, descriptor) {
+    return Reflect.defineProperty(this.getObject(), prop, descriptor);
+  }
+
+  has(target, prop) {
+    return Reflect.has(this.getObject(), prop);
+  }
+
+  get(target, prop, receiver) {
+    if (this.pending &&
+        this.stubProperties &&
+        Object.prototype.hasOwnProperty.call(this.stubProperties, prop)) {
+      return this.stubProperties[prop];
+    }
+    return Reflect.get(this.getObject(), prop, receiver);
+  }
+
+  set(target, prop, value, receiver) {
+    return Reflect.set(this.getObject(), prop, value, receiver);
+  }
+
+  deleteProperty(target, prop) {
+    return Reflect.deleteProperty(this.getObject(), prop);
+  }
+
+  ownKeys(target) {
+    return Reflect.ownKeys(this.getObject());
+  }
+}
+
 var XPCU_lazyPreferenceObserverQI = ChromeUtils.generateQI([Ci.nsIObserver, Ci.nsISupportsWeakReference]);
 
 ChromeUtils.defineModuleGetter(this, "Services",
                                "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(XPCOMUtils, "categoryManager",
                                    "@mozilla.org/categorymanager;1",
                                    "nsICategoryManager");
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_lazyproxy.js
@@ -0,0 +1,113 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This file tests the method defineLazyProxy from XPCOMUtils.jsm.
+ */
+
+ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+add_task(function test_lazy_proxy() {
+  let tmp = {};
+  let realObject = {
+    "prop1": "value1",
+    "prop2": "value2",
+  };
+
+  let evaluated = false;
+  let untrapCalled = false;
+
+  let lazyProxy = XPCOMUtils.defineLazyProxy(
+    tmp,
+    "myLazyProxy",
+
+    // Initiliazer function
+    function init() {
+      evaluated = true;
+      return realObject;
+    },
+
+    // Stub properties
+    {
+      "prop1": "stub"
+    },
+
+    // Untrap callback
+    function untrapCallback(obj) {
+      Assert.equal(obj, realObject, "The underlying object can be obtained in the untrap callback");
+      untrapCalled = true;
+    }
+  );
+
+  // Check that the proxy returned and the one
+  // defined in tmp are the same.
+  //
+  // Note: Assert.strictEqual can't be used here
+  // because it wants to stringify the two objects
+  // compared, which defeats the lazy proxy.
+  Assert.ok(lazyProxy === tmp.myLazyProxy, "Return value and object defined are the same");
+
+  Assert.ok(Cu.isProxy(lazyProxy), "Returned value is in fact a proxy");
+
+  // Check that just using the proxy above didn't
+  // trigger the lazy getter evaluation.
+  Assert.ok(!evaluated, "The lazy proxy hasn't been evaluated yet");
+  Assert.ok(!untrapCalled, "The untrap callback hasn't been called yet");
+
+  // Accessing a stubbed property returns the stub
+  // value and doesn't trigger evaluation.
+  Assert.equal(lazyProxy.prop1, "stub", "Accessing a stubbed property returns the stubbed value");
+
+  Assert.ok(!evaluated, "The access to the stubbed property above didn't evaluate the lazy proxy");
+  Assert.ok(!untrapCalled, "The untrap callback hasn't been called yet");
+
+  // Now the access to another property will trigger
+  // the evaluation, as expected.
+  Assert.equal(lazyProxy.prop2, "value2", "Property access is correctly forwarded to the underlying object");
+
+  Assert.ok(evaluated, "Accessing a non-stubbed property triggered the proxy evaluation");
+  Assert.ok(untrapCalled, "The untrap callback was called");
+
+  // The value of prop1 is now the real value and not the stub value.
+  Assert.equal(lazyProxy.prop1, "value1", "The  value of prop1 is now the real value and not the stub one");
+});
+
+add_task(function test_module_version() {
+  // Test that passing a string instead of an initialization function
+  // makes this behave like a lazy module getter.
+  const NET_UTIL_URI = "resource://gre/modules/NetUtil.jsm";
+  let underlyingObject;
+
+  Cu.unload(NET_UTIL_URI);
+
+  let lazyProxy = XPCOMUtils.defineLazyProxy(
+    null,
+    "NetUtil",
+    NET_UTIL_URI,
+    null, /* no stubs */
+    function untrapCallback(object) {
+      underlyingObject = object;
+    }
+  );
+
+  Assert.ok(!Cu.isModuleLoaded(NET_UTIL_URI), "The NetUtil module was not loaded by the lazy proxy definition");
+
+  // Access the object, which will evaluate the proxy.
+  lazyProxy.foo = "bar";
+
+  // Module was loaded.
+  Assert.ok(Cu.isModuleLoaded(NET_UTIL_URI), "The NetUtil module was loaded");
+
+  let { NetUtil } = ChromeUtils.import(NET_UTIL_URI, {});
+
+  // Avoids a gigantic stringification in the logs.
+  Assert.ok(NetUtil === underlyingObject, "The module loaded is the same as the one directly obtained by ChromeUtils.import");
+
+  // Proxy correctly passed the setter to the underlying object.
+  Assert.equal(NetUtil.foo, "bar", "Proxy correctly passed the setter to the underlying object");
+
+  delete lazyProxy.foo;
+
+  // Proxy correctly passed the delete operation to the underlying object.
+  Assert.ok(!NetUtil.hasOwnProperty("foo"), "Proxy correctly passed the delete operation to the underlying object");
+});
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -88,16 +88,17 @@ head = head_ongc.js
 head = head_ongc.js
 [test_onGarbageCollection-05.js]
 head = head_ongc.js
 [test_reflect_parse.js]
 [test_localeCompare.js]
 [test_recursive_import.js]
 [test_xpcomutils.js]
 [test_unload.js]
+[test_lazyproxy.js]
 [test_attributes.js]
 [test_params.js]
 [test_tearoffs.js]
 [test_want_components.js]
 [test_components.js]
 [test_allowedDomains.js]
 [test_allowedDomainsXHR.js]
 [test_nuke_sandbox.js]
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -10192,36 +10192,36 @@ PaintTelemetry::AutoRecordPaint::~AutoRe
   Telemetry::Accumulate(Telemetry::CONTENT_PAINT_TIME, static_cast<uint32_t>(totalMs));
 
   // Helpers for recording large/small paints.
   auto recordLarge = [=](const nsCString& aKey, double aDurationMs) -> void {
     MOZ_ASSERT(aDurationMs <= totalMs);
     uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0);
     Telemetry::Accumulate(Telemetry::CONTENT_LARGE_PAINT_PHASE_WEIGHT, aKey, amount);
   };
-  auto recordSmall = [=](const nsString& aKey, double aDurationMs) -> void {
+  auto recordSmall = [=](const nsCString& aKey, double aDurationMs) -> void {
     MOZ_ASSERT(aDurationMs <= totalMs);
     uint32_t amount = static_cast<int32_t>((aDurationMs / totalMs) * 100.0);
-    Telemetry::ScalarAdd(Telemetry::ScalarID::GFX_SMALL_PAINT_PHASE_WEIGHT, aKey, amount);
+    Telemetry::Accumulate(Telemetry::CONTENT_SMALL_PAINT_PHASE_WEIGHT, aKey, amount);
   };
 
   double dlMs = sMetrics[Metric::DisplayList];
   double flbMs = sMetrics[Metric::Layerization];
   double rMs = sMetrics[Metric::Rasterization];
 
   // If the total time was >= 16ms, then it's likely we missed a frame due to
   // painting. We bucket these metrics separately.
   if (totalMs >= 16.0) {
     recordLarge(NS_LITERAL_CSTRING("dl"), dlMs);
     recordLarge(NS_LITERAL_CSTRING("flb"), flbMs);
     recordLarge(NS_LITERAL_CSTRING("r"), rMs);
   } else {
-    recordSmall(NS_LITERAL_STRING("dl"), dlMs);
-    recordSmall(NS_LITERAL_STRING("flb"), flbMs);
-    recordSmall(NS_LITERAL_STRING("r"), rMs);
+    recordSmall(NS_LITERAL_CSTRING("dl"), dlMs);
+    recordSmall(NS_LITERAL_CSTRING("flb"), flbMs);
+    recordSmall(NS_LITERAL_CSTRING("r"), rMs);
   }
 }
 
 PaintTelemetry::AutoRecord::AutoRecord(Metric aMetric)
  : mMetric(aMetric)
 {
   // Don't double-record anything nested.
   if (sMetricLevel++ > 0) {
--- a/mfbt/Maybe.h
+++ b/mfbt/Maybe.h
@@ -310,21 +310,17 @@ public:
   }
 
   /* Methods that check whether this Maybe contains a value */
   explicit operator bool() const { return isSome(); }
   bool isSome() const { return mIsSome; }
   bool isNothing() const { return !mIsSome; }
 
   /* Returns the contents of this Maybe<T> by value. Unsafe unless |isSome()|. */
-  T value() const
-  {
-    MOZ_ASSERT(mIsSome);
-    return ref();
-  }
+  T value() const;
 
   /*
    * Returns the contents of this Maybe<T> by value. If |isNothing()|, returns
    * the default value provided.
    */
   template<typename V>
   T valueOr(V&& aDefault) const
   {
@@ -343,27 +339,18 @@ public:
   {
     if (isSome()) {
       return ref();
     }
     return aFunc();
   }
 
   /* Returns the contents of this Maybe<T> by pointer. Unsafe unless |isSome()|. */
-  T* ptr()
-  {
-    MOZ_ASSERT(mIsSome);
-    return &ref();
-  }
-
-  const T* ptr() const
-  {
-    MOZ_ASSERT(mIsSome);
-    return &ref();
-  }
+  T* ptr();
+  const T* ptr() const;
 
   /*
    * Returns the contents of this Maybe<T> by pointer. If |isNothing()|,
    * returns the default value provided.
    */
   T* ptrOr(T* aDefault)
   {
     if (isSome()) {
@@ -397,40 +384,22 @@ public:
   const T* ptrOrFrom(F&& aFunc) const
   {
     if (isSome()) {
       return ptr();
     }
     return aFunc();
   }
 
-  T* operator->()
-  {
-    MOZ_ASSERT(mIsSome);
-    return ptr();
-  }
-
-  const T* operator->() const
-  {
-    MOZ_ASSERT(mIsSome);
-    return ptr();
-  }
+  T* operator->();
+  const T* operator->() const;
 
   /* Returns the contents of this Maybe<T> by ref. Unsafe unless |isSome()|. */
-  T& ref()
-  {
-    MOZ_ASSERT(mIsSome);
-    return *static_cast<T*>(data());
-  }
-
-  const T& ref() const
-  {
-    MOZ_ASSERT(mIsSome);
-    return *static_cast<const T*>(data());
-  }
+  T& ref();
+  const T& ref() const;
 
   /*
    * Returns the contents of this Maybe<T> by ref. If |isNothing()|, returns
    * the default value provided.
    */
   T& refOr(T& aDefault)
   {
     if (isSome()) {
@@ -464,27 +433,18 @@ public:
   const T& refOrFrom(F&& aFunc) const
   {
     if (isSome()) {
       return ref();
     }
     return aFunc();
   }
 
-  T& operator*()
-  {
-    MOZ_ASSERT(mIsSome);
-    return ref();
-  }
-
-  const T& operator*() const
-  {
-    MOZ_ASSERT(mIsSome);
-    return ref();
-  }
+  T& operator*();
+  const T& operator*() const;
 
   /* If |isSome()|, runs the provided function or functor on the contents of
    * this Maybe. */
   template<typename Func>
   Maybe& apply(Func aFunc)
   {
     if (isSome()) {
       aFunc(ref());
@@ -539,35 +499,112 @@ public:
     }
   }
 
   /*
    * Constructs a T value in-place in this empty Maybe<T>'s storage. The
    * arguments to |emplace()| are the parameters to T's constructor.
    */
   template<typename... Args>
-  void emplace(Args&&... aArgs)
-  {
-    MOZ_ASSERT(!mIsSome);
-    ::new (KnownNotNull, data()) T(Forward<Args>(aArgs)...);
-    mIsSome = true;
-  }
+  void emplace(Args&&... aArgs);
 
   friend std::ostream&
   operator<<(std::ostream& aStream, const Maybe<T>& aMaybe)
   {
     if (aMaybe) {
       aStream << aMaybe.ref();
     } else {
       aStream << "<Nothing>";
     }
     return aStream;
   }
 };
 
+template<typename T>
+T
+Maybe<T>::value() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return ref();
+}
+
+template<typename T>
+T*
+Maybe<T>::ptr()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return &ref();
+}
+
+template<typename T>
+const T*
+Maybe<T>::ptr() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return &ref();
+}
+
+template<typename T>
+T*
+Maybe<T>::operator->()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return ptr();
+}
+
+template<typename T>
+const T*
+Maybe<T>::operator->() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return ptr();
+}
+
+template<typename T>
+T&
+Maybe<T>::ref()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return *static_cast<T*>(data());
+}
+
+template<typename T>
+const T&
+Maybe<T>::ref() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return *static_cast<const T*>(data());
+}
+
+template<typename T>
+T&
+Maybe<T>::operator*()
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return ref();
+}
+
+template<typename T>
+const T&
+Maybe<T>::operator*() const
+{
+  MOZ_DIAGNOSTIC_ASSERT(mIsSome);
+  return ref();
+}
+
+template<typename T>
+template<typename... Args>
+void
+Maybe<T>::emplace(Args&&... aArgs)
+{
+  MOZ_DIAGNOSTIC_ASSERT(!mIsSome);
+  ::new (KnownNotNull, data()) T(Forward<Args>(aArgs)...);
+  mIsSome = true;
+}
+
 /*
  * Some() creates a Maybe<T> value containing the provided T value. If T has a
  * move constructor, it's used to make this as efficient as possible.
  *
  * Some() selects the type of Maybe it returns by removing any const, volatile,
  * or reference qualifiers from the type of the value you pass to it. This gives
  * it more intuitive behavior when used in expressions, but it also means that
  * if you need to construct a Maybe value that holds a const, volatile, or
--- a/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
+++ b/mobile/android/geckoview/src/androidTest/java/org/mozilla/geckoview/test/NavigationDelegateTest.kt
@@ -391,16 +391,19 @@ class NavigationDelegateTest : BaseSessi
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
                 assertThat("URI should be correct", uri, endsWith(NEW_SESSION_CHILD_HTML_PATH))
             }
         })
     }
 
     @WithDevToolsAPI
     @Test fun onNewSession_calledForTargetBlankLink() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
 
         sessionRule.session.waitUntilCalled(object : Callbacks.NavigationDelegate {
             // We get two onLoadRequest calls for the link click,
             // one when loading the URL and one when opening a new window.
@@ -428,16 +431,19 @@ class NavigationDelegateTest : BaseSessi
             }
         })
 
         return newSession
     }
 
     @WithDevToolsAPI
     @Test fun onNewSession_childShouldLoad() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         val newSession = delegateNewSession()
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
         newSession.waitForPageStop()
 
         newSession.forCallbacksDuringWait(object : Callbacks.ProgressDelegate {
@@ -450,43 +456,52 @@ class NavigationDelegateTest : BaseSessi
             override fun onPageStop(session: GeckoSession, success: Boolean) {
                 assertThat("Load should succeed", success, equalTo(true))
             }
         })
     }
 
     @WithDevToolsAPI
     @Test fun onNewSession_setWindowOpener() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         val newSession = delegateNewSession()
         sessionRule.session.evaluateJS("$('#targetBlankLink').click()")
         newSession.waitForPageStop()
 
         assertThat("window.opener should be set",
                    newSession.evaluateJS("window.opener.location.pathname") as String,
                    equalTo(NEW_SESSION_HTML_PATH))
     }
 
     @WithDevToolsAPI
     @Test fun onNewSession_supportNoOpener() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         val newSession = delegateNewSession()
         sessionRule.session.evaluateJS("$('#noOpenerLink').click()")
         newSession.waitForPageStop()
 
         assertThat("window.opener should not be set",
                    newSession.evaluateJS("window.opener"), nullValue())
     }
 
     @WithDevToolsAPI
     @Test fun onNewSession_notCalledForHandledLoads() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             override fun onLoadRequest(session: GeckoSession, uri: String, where: Int, flags: Int, response: GeckoResponse<Boolean>) {
                 // Pretend we handled the target="_blank" link click.
                 response.respond(uri.endsWith(NEW_SESSION_CHILD_HTML_PATH))
             }
@@ -509,16 +524,19 @@ class NavigationDelegateTest : BaseSessi
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
             }
         })
     }
 
     @WithDevToolsAPI
     @Test(expected = IllegalArgumentException::class)
     fun onNewSession_doesNotAllowOpened() {
+        // Disable popup blocker.
+        sessionRule.setPrefsUntilTestEnd(mapOf("dom.disable_open_during_load" to false))
+
         sessionRule.session.loadTestPath(NEW_SESSION_HTML_PATH)
         sessionRule.session.waitForPageStop()
 
         sessionRule.session.delegateDuringNextWait(object : Callbacks.NavigationDelegate {
             @AssertCalled(count = 1)
             override fun onNewSession(session: GeckoSession, uri: String, response: GeckoResponse<GeckoSession>) {
                 response.respond(sessionRule.createOpenSession())
             }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -3098,21 +3098,16 @@ pref("plugins.load_appdir_plugins", fals
 pref("plugins.click_to_play", false);
 
 // This only supports one hidden ctp plugin, edit nsPluginArray.cpp if adding a second
 pref("plugins.navigator.hidden_ctp_plugin", "");
 
 // The default value for nsIPluginTag.enabledState (STATE_ENABLED = 2)
 pref("plugin.default.state", 2);
 
-// The MIME type that should bind to legacy java-specific invocations like
-// <applet> and <object data="java:foo">. Setting this to a non-java MIME type
-// is undefined behavior.
-pref("plugin.java.mime", "application/x-java-vm");
-
 // How long in minutes we will allow a plugin to work after the user has chosen
 // to allow it "now"
 pref("plugin.sessionPermissionNow.intervalInMinutes", 60);
 // How long in days we will allow a plugin to work after the user has chosen
 // to allow it persistently.
 pref("plugin.persistentPermissionAlways.intervalInDays", 90);
 
 // This pref can take 3 possible string values:
--- a/mozglue/misc/interceptor/MMPolicies.h
+++ b/mozglue/misc/interceptor/MMPolicies.h
@@ -149,16 +149,21 @@ public:
            mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS;
   }
 
   bool FlushInstructionCache() const
   {
     return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
   }
 
+  static DWORD GetTrampWriteProtFlags()
+  {
+    return PAGE_EXECUTE_READWRITE;
+  }
+
 protected:
   uint8_t* GetLocalView() const
   {
     return mBase;
   }
 
   uintptr_t GetRemoteView() const
   {
@@ -365,16 +370,21 @@ public:
            mbi.State == MEM_COMMIT && mbi.Protect != PAGE_NOACCESS;
   }
 
   bool FlushInstructionCache() const
   {
     return !!::FlushInstructionCache(mProcess, nullptr, 0);
   }
 
+  static DWORD GetTrampWriteProtFlags()
+  {
+    return PAGE_READWRITE;
+  }
+
 protected:
   uint8_t* GetLocalView() const
   {
     return mLocalView;
   }
 
   uintptr_t GetRemoteView() const
   {
--- a/mozglue/misc/interceptor/Trampoline.h
+++ b/mozglue/misc/interceptor/Trampoline.h
@@ -25,17 +25,17 @@ public:
     , mPrevLocalProt(0)
     , mLocalBase(aLocalBase)
     , mRemoteBase(aRemoteBase)
     , mOffset(0)
     , mExeOffset(0)
     , mMaxOffset(aChunkSize)
     , mAccumulatedStatus(true)
   {
-    ::VirtualProtect(aLocalBase, aChunkSize, PAGE_EXECUTE_READWRITE,
+    ::VirtualProtect(aLocalBase, aChunkSize, MMPolicy::GetTrampWriteProtFlags(),
                      &mPrevLocalProt);
   }
 
   Trampoline(Trampoline&& aOther)
     : mMMPolicy(aOther.mMMPolicy)
     , mPrevLocalProt(aOther.mPrevLocalProt)
     , mLocalBase(aOther.mLocalBase)
     , mRemoteBase(aOther.mRemoteBase)
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -2701,42 +2701,16 @@ NS_LinkRedirectChannels(uint32_t channel
       do_GetService("@mozilla.org/redirectchannelregistrar;1", &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return registrar->LinkChannels(channelId,
                                  parentChannel,
                                  _result);
 }
 
-#define NS_FAKE_SCHEME "http://"
-#define NS_FAKE_TLD ".invalid"
-nsresult NS_MakeRandomInvalidURLString(nsCString &result)
-{
-  nsresult rv;
-  nsCOMPtr<nsIUUIDGenerator> uuidgen =
-    do_GetService("@mozilla.org/uuid-generator;1", &rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsID idee;
-  rv = uuidgen->GenerateUUIDInPlace(&idee);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  char chars[NSID_LENGTH];
-  idee.ToProvidedString(chars);
-
-  result.AssignLiteral(NS_FAKE_SCHEME);
-  // Strip off the '{' and '}' at the beginning and end of the UUID
-  result.Append(chars + 1, NSID_LENGTH - 3);
-  result.AppendLiteral(NS_FAKE_TLD);
-
-  return NS_OK;
-}
-#undef NS_FAKE_SCHEME
-#undef NS_FAKE_TLD
-
 nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
                                        nsIInputStream **aStream)
 {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
   if (loadInfo && loadInfo->GetSecurityMode() != 0) {
     return aChannel->Open2(aStream);
   }
   return aChannel->Open(aStream);
@@ -2747,74 +2721,16 @@ nsresult NS_MaybeOpenChannelUsingAsyncOp
 {
   nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo();
   if (loadInfo && loadInfo->GetSecurityMode() != 0) {
     return aChannel->AsyncOpen2(aListener);
   }
   return aChannel->AsyncOpen(aListener, nullptr);
 }
 
-nsresult
-NS_CheckIsJavaCompatibleURLString(nsCString &urlString, bool *result)
-{
-  *result = false; // Default to "no"
-
-  nsresult rv = NS_OK;
-  nsCOMPtr<nsIURLParser> urlParser =
-    do_GetService(NS_STDURLPARSER_CONTRACTID, &rv);
-  if (NS_FAILED(rv) || !urlParser)
-    return NS_ERROR_FAILURE;
-
-  bool compatible = true;
-  uint32_t schemePos = 0;
-  int32_t schemeLen = 0;
-  urlParser->ParseURL(urlString.get(), -1, &schemePos, &schemeLen,
-                      nullptr, nullptr, nullptr, nullptr);
-  if (schemeLen != -1) {
-    nsCString scheme;
-    scheme.Assign(urlString.get() + schemePos, schemeLen);
-    // By default Java only understands a small number of URL schemes, and of
-    // these only some can legitimately represent a browser page's "origin"
-    // (and be something we can legitimately expect Java to handle ... or not
-    // to mishandle).
-    //
-    // Besides those listed below, the OJI plugin understands the "jar",
-    // "mailto", "netdoc", "javascript" and "rmi" schemes, and Java Plugin2
-    // also understands the "about" scheme.  We actually pass "about" URLs
-    // to Java ("about:blank" when processing a javascript: URL (one that
-    // calls Java) from the location bar of a blank page, and (in FF4 and up)
-    // "about:home" when processing a javascript: URL from the home page).
-    // And Java doesn't appear to mishandle them (for example it doesn't allow
-    // connections to "about" URLs).  But it doesn't make any sense to do
-    // same-origin checks on "about" URLs, so we don't include them in our
-    // scheme whitelist.
-    //
-    // The OJI plugin doesn't understand "chrome" URLs (only Java Plugin2
-    // does) -- so we mustn't pass them to the OJI plugin.  But we do need to
-    // pass "chrome" URLs to Java Plugin2:  Java Plugin2 grants additional
-    // privileges to chrome "origins", and some extensions take advantage of
-    // this.  For more information see bug 620773.
-    //
-    // As of FF4, we no longer support the OJI plugin.
-    if (PL_strcasecmp(scheme.get(), "http") &&
-        PL_strcasecmp(scheme.get(), "https") &&
-        PL_strcasecmp(scheme.get(), "file") &&
-        PL_strcasecmp(scheme.get(), "ftp") &&
-        PL_strcasecmp(scheme.get(), "gopher") &&
-        PL_strcasecmp(scheme.get(), "chrome"))
-      compatible = false;
-  } else {
-    compatible = false;
-  }
-
-  *result = compatible;
-
-  return NS_OK;
-}
-
 /** Given the first (disposition) token from a Content-Disposition header,
  * tell whether it indicates the content is inline or attachment
  * @param aDispToken the disposition token from the content-disposition header
  */
 uint32_t
 NS_GetContentDispositionFromToken(const nsAString &aDispToken)
 {
   // RFC 2183, section 2.8 says that an unknown disposition
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -845,46 +845,31 @@ bool NS_IsHSTSUpgradeRedirect(nsIChannel
                               nsIChannel *aNewChannel,
                               uint32_t aFlags);
 
 nsresult NS_LinkRedirectChannels(uint32_t channelId,
                                  nsIParentChannel *parentChannel,
                                  nsIChannel **_result);
 
 /**
- * Helper function to create a random URL string that's properly formed
- * but guaranteed to be invalid.
- */
-nsresult NS_MakeRandomInvalidURLString(nsCString &result);
-
-/**
  * Helper function which checks whether the channel can be
  * openend using Open2() or has to fall back to opening
  * the channel using Open().
  */
 nsresult NS_MaybeOpenChannelUsingOpen2(nsIChannel* aChannel,
                                        nsIInputStream **aStream);
 
 /**
  * Helper function which checks whether the channel can be
  * openend using AsyncOpen2() or has to fall back to opening
  * the channel using AsyncOpen().
  */
 nsresult NS_MaybeOpenChannelUsingAsyncOpen2(nsIChannel* aChannel,
                                             nsIStreamListener *aListener);
 
-/**
- * Helper function to determine whether urlString is Java-compatible --
- * whether it can be passed to the Java URL(String) constructor without the
- * latter throwing a MalformedURLException, or without Java otherwise
- * mishandling it.  This function (in effect) implements a scheme whitelist
- * for Java.
- */
-nsresult NS_CheckIsJavaCompatibleURLString(nsCString& urlString, bool *result);
-
 /** Given the first (disposition) token from a Content-Disposition header,
  * tell whether it indicates the content is inline or attachment
  * @param aDispToken the disposition token from the content-disposition header
  */
 uint32_t NS_GetContentDispositionFromToken(const nsAString &aDispToken);
 
 /** Determine the disposition (inline/attachment) of the content based on the
  * Content-Disposition header
--- a/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
+++ b/netwerk/protocol/res/SubstitutingProtocolHandler.cpp
@@ -9,16 +9,17 @@
 #include "mozilla/Unused.h"
 
 #include "SubstitutingProtocolHandler.h"
 #include "nsIChannel.h"
 #include "nsIIOService.h"
 #include "nsIFile.h"
 #include "nsNetCID.h"
 #include "nsNetUtil.h"
+#include "nsReadableUtils.h"
 #include "nsURLHelper.h"
 #include "nsEscape.h"
 
 using mozilla::dom::ContentParent;
 
 namespace mozilla {
 namespace net {
 
@@ -301,18 +302,21 @@ nsresult
 SubstitutingProtocolHandler::SetSubstitution(const nsACString& root, nsIURI *baseURI)
 {
   // Add-ons use this API but they should not be able to make anything
   // content-accessible.
   return SetSubstitutionWithFlags(root, baseURI, 0);
 }
 
 nsresult
-SubstitutingProtocolHandler::SetSubstitutionWithFlags(const nsACString& root, nsIURI *baseURI, uint32_t flags)
+SubstitutingProtocolHandler::SetSubstitutionWithFlags(const nsACString& origRoot, nsIURI *baseURI, uint32_t flags)
 {
+  nsAutoCString root;
+  ToLowerCase(origRoot, root);
+
   if (!baseURI) {
     mSubstitutions.Remove(root);
     NotifyObservers(root, baseURI);
     return SendSubstitution(root, baseURI, flags);
   }
 
   // If baseURI isn't a same-scheme URI, we can set the substitution immediately.
   nsAutoCString scheme;
@@ -344,49 +348,62 @@ SubstitutingProtocolHandler::SetSubstitu
   SubstitutionEntry& entry = mSubstitutions.GetOrInsert(root);
   entry.baseURI = newBaseURI;
   entry.flags = flags;
   NotifyObservers(root, baseURI);
   return SendSubstitution(root, newBaseURI, flags);
 }
 
 nsresult
-SubstitutingProtocolHandler::GetSubstitution(const nsACString& root, nsIURI **result)
+SubstitutingProtocolHandler::GetSubstitution(const nsACString& origRoot, nsIURI **result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
+  nsAutoCString root;
+  ToLowerCase(origRoot, root);
+
   SubstitutionEntry entry;
   if (mSubstitutions.Get(root, &entry)) {
     nsCOMPtr<nsIURI> baseURI = entry.baseURI;
     baseURI.forget(result);
     return NS_OK;
   }
 
   uint32_t flags;
   return GetSubstitutionInternal(root, result, &flags);
 }
 
 nsresult
 SubstitutingProtocolHandler::GetSubstitutionFlags(const nsACString& root, uint32_t* flags)
 {
+#ifdef DEBUG
+  nsAutoCString lcRoot;
+  ToLowerCase(root, lcRoot);
+  MOZ_ASSERT(root.Equals(lcRoot), "GetSubstitutionFlags should never receive mixed-case root name");
+#endif
+
   *flags = 0;
   SubstitutionEntry entry;
   if (mSubstitutions.Get(root, &entry)) {
     *flags = entry.flags;
     return NS_OK;
   }
 
   nsCOMPtr<nsIURI> baseURI;
   return GetSubstitutionInternal(root, getter_AddRefs(baseURI), flags);
 }
 
 nsresult
-SubstitutingProtocolHandler::HasSubstitution(const nsACString& root, bool *result)
+SubstitutingProtocolHandler::HasSubstitution(const nsACString& origRoot, bool *result)
 {
   NS_ENSURE_ARG_POINTER(result);
+
+  nsAutoCString root;
+  ToLowerCase(origRoot, root);
+
   *result = HasSubstitution(root);
   return NS_OK;
 }
 
 nsresult
 SubstitutingProtocolHandler::ResolveURI(nsIURI *uri, nsACString &result)
 {
   nsresult rv;
--- a/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
+++ b/netwerk/protocol/res/nsISubstitutingProtocolHandler.idl
@@ -20,18 +20,18 @@ interface nsISubstitutingProtocolHandler
   const short ALLOW_CONTENT_ACCESS = 1;
 
   /**
    * Sets the substitution for the root key:
    *   resource://root/path ==> baseURI.resolve(path)
    *
    * A null baseURI removes the specified substitution.
    *
-   * A root key should always be lowercase; however, this may not be
-   * enforced.
+   * The root key will be converted to lower-case to conform to
+   * case-insensitive URI hostname matching behavior.
    */
   [must_use] void setSubstitution(in ACString root, in nsIURI baseURI);
 
   /**
    * Same as setSubstitution, but with specific flags.
    */
   [must_use] void setSubstitutionWithFlags(in ACString root, in nsIURI baseURI, in uint32_t flags);
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit/test_substituting_protocol_handler.js
@@ -0,0 +1,41 @@
+"use strict";
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+add_task(async function test_case_insensitive_substitutions() {
+  let resProto = Services.io.getProtocolHandler("resource")
+    .QueryInterface(Ci.nsISubstitutingProtocolHandler);
+
+  let uri = Services.io.newFileURI(do_get_file("data"));
+
+  resProto.setSubstitution("FooBar", uri);
+  resProto.setSubstitutionWithFlags("BarBaz", uri, 0);
+
+  equal(resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+        uri.spec, "Got correct resolved URI for setSubstitution");
+
+  equal(resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+        uri.spec, "Got correct resolved URI for setSubstitutionWithFlags");
+
+  ok(resProto.hasSubstitution("foobar"), "hasSubstitution works with all-lower-case root");
+  ok(resProto.hasSubstitution("FooBar"), "hasSubstitution works with mixed-case root");
+
+  equal(resProto.getSubstitution("foobar").spec, uri.spec,
+        "getSubstitution works with all-lower-case root");
+  equal(resProto.getSubstitution("FooBar").spec, uri.spec,
+        "getSubstitution works with mixed-case root");
+
+  resProto.setSubstitution("foobar", null);
+  resProto.setSubstitution("barbaz", null);
+
+  Assert.throws(() => resProto.resolveURI(Services.io.newURI("resource://foobar/")),
+                e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+                "Correctly unregistered case-insensitive substitution in setSubstitution");
+  Assert.throws(() => resProto.resolveURI(Services.io.newURI("resource://barbaz/")),
+                e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+                "Correctly unregistered case-insensitive substitution in setSubstitutionWithFlags");
+
+  Assert.throws(() => resProto.getSubstitution("foobar"),
+                e => e.result == Cr.NS_ERROR_NOT_AVAILABLE,
+                "foobar substitution has been removed");
+});
--- a/netwerk/test/unit/xpcshell.ini
+++ b/netwerk/test/unit/xpcshell.ini
@@ -416,8 +416,9 @@ skip-if = os == "android"
 [test_header_Server_Timing.js]
 # Test requires http/2, and http/2 server doesn't run on android.
 skip-if = os == "android"
 run-sequentially = node server exceptions dont replay well
 [test_trr.js]
 # http2-using tests require node available
 skip-if = os == "android"
 [test_ioservice.js]
+[test_substituting_protocol_handler.js]
rename from taskcluster/ci/beetmover-cdns/kind.yml
rename to taskcluster/ci/release-beetmover-push-to-release/kind.yml
--- a/taskcluster/ci/beetmover-cdns/kind.yml
+++ b/taskcluster/ci/release-beetmover-push-to-release/kind.yml
@@ -1,36 +1,36 @@
 # 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/.
 
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
-   - taskgraph.transforms.beetmover_cdns:transforms
+   - taskgraph.transforms.beetmover_push_to_release:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
    - release-generate-checksums-beetmover
 
 job-defaults:
    run-on-projects: []
    shipping-phase: push
 
 jobs:
-   fennec-push-to-cdns:
-      name: fennec_push_to_cdns
+   fennec-push-to-release:
+      name: fennec_push_to_release
       product: fennec
       shipping-product: fennec
-      treeherder-platform: Android/opt
+      treeherder-platform: fennec-release/opt
 
-   devedition-push-to-cdns:
-      name: devedition_push_to_cdns
+   devedition-push-to-release:
+      name: devedition_push_to_release
       product: devedition
       shipping-product: devedition
-      treeherder-platform: Linux64-devedition/opt
+      treeherder-platform: devedition-release/opt
 
-   firefox-push-to-cdns:
-      name: firefox_push_to_cdns
+   firefox-push-to-release:
+      name: firefox_push_to_release
       product: firefox
       shipping-product: firefox
-      treeherder-platform: Linux64/opt
+      treeherder-platform: firefox-release/opt
--- a/taskcluster/ci/release-bouncer-check/kind.yml
+++ b/taskcluster/ci/release-bouncer-check/kind.yml
@@ -1,16 +1,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 loader: taskgraph.loader.transform:loader
 
 kind-dependencies:
-    - beetmover-cdns
+    - release-beetmover-push-to-release
 
 transforms:
     - taskgraph.transforms.release_deps:transforms
     - taskgraph.transforms.bouncer_check:transforms
     - taskgraph.transforms.job:transforms
     - taskgraph.transforms.task:transforms
 
 job-defaults:
--- a/taskcluster/ci/release-notify-push/kind.yml
+++ b/taskcluster/ci/release-notify-push/kind.yml
@@ -5,17 +5,17 @@
 loader: taskgraph.loader.transform:loader
 
 transforms:
    - taskgraph.transforms.release_deps:transforms
    - taskgraph.transforms.release_notifications:transforms
    - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-   - beetmover-cdns
+   - release-beetmover-push-to-release
 
 job-defaults:
    name: notify-release-drivers-push
    description: Sends email to release-drivers telling release was pushed.
    run-on-projects: []
    shipping-phase: push
    worker-type: aws-provisioner-v1/gecko-{level}-b-linux
    worker:
--- a/taskcluster/ci/release-version-bump/kind.yml
+++ b/taskcluster/ci/release-version-bump/kind.yml
@@ -5,17 +5,17 @@
 loader: taskgraph.loader.transform:loader
 
 transforms:
     - taskgraph.transforms.release_deps:transforms
     - taskgraph.transforms.release_version_bump:transforms
     - taskgraph.transforms.task:transforms
 
 kind-dependencies:
-    - beetmover-cdns
+    - release-beetmover-push-to-release
 
 job-defaults:
     description: Release Promotion version bump/tag
     run-on-projects: []
     shipping-phase: ship
     worker-type: scriptworker-prov-v1/treescript-v1
     worker:
         implementation: treescript
--- a/taskcluster/docs/kinds.rst
+++ b/taskcluster/docs/kinds.rst
@@ -190,20 +190,21 @@ the language in the final artifact names
 
 beetmover-repackage
 -------------------
 
 Beetmover-repackage is beetmover but for tasks that need an intermediate step
 between signing and packaging, such as OSX. For more details see the definitions
 of the Beetmover kind above and the repackage kind below.
 
-beetmover-cdns
--------------------
+release-beetmover-push-to-release
+---------------------------------
 
-Beetmover-cdns publishes promoted releases to CDNs. This is part of release promotion.
+Beetmover-cdns publishes promoted releases from the candidates directory to the
+release directory. This is part of release promotion.
 
 beetmover-source
 -------------------
 
 Beetmover-source publishes release source. This is part of release promotion.
 
 checksums-signing
 -----------------
--- a/taskcluster/taskgraph/actions/release_promotion.py
+++ b/taskcluster/taskgraph/actions/release_promotion.py
@@ -351,16 +351,19 @@ def release_promotion_action(parameters,
     parameters['build_number'] = int(input['build_number'])
     parameters['next_version'] = next_version
     parameters['release_history'] = release_history
     parameters['release_type'] = promotion_config.get('release_type', '')
     parameters['release_eta'] = input.get('release_eta', '')
     parameters['release_enable_partners'] = release_enable_partners
     parameters['release_partners'] = input.get('release_partners')
     parameters['release_enable_emefree'] = release_enable_emefree
+    # When doing staging releases on try, we still want to re-use tasks from
+    # previous graphs.
+    parameters['optimize_target_tasks'] = True
 
     partner_config = input.get('release_partner_config')
     if not partner_config and (release_enable_emefree or release_enable_partners):
         partner_url_config = get_partner_url_config(
             parameters, graph_config, enable_emefree=release_enable_emefree,
             enable_partners=release_enable_partners
         )
         github_token = get_token(parameters)
rename from taskcluster/taskgraph/transforms/beetmover_cdns.py
rename to taskcluster/taskgraph/transforms/beetmover_push_to_release.py
--- a/taskcluster/taskgraph/transforms/beetmover_cdns.py
+++ b/taskcluster/taskgraph/transforms/beetmover_push_to_release.py
@@ -1,13 +1,13 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 """
-Transform the beetmover-cdns task into a task description.
+Transform the beetmover-push-to-release task into a task description.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.schema import (
      validate_schema, Schema,
 )
@@ -25,17 +25,17 @@ task_description_schema = {str(k): v for
 job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
 
 transforms = TransformSequence()
 
 taskref_or_string = Any(
     basestring,
     {Required('task-reference'): basestring})
 
-beetmover_cdns_description_schema = Schema({
+beetmover_push_to_release_description_schema = Schema({
     Required('name'): basestring,
     Required('product'): basestring,
     Required('treeherder-platform'): basestring,
     Optional('attributes'): {basestring: object},
     Optional('job-from'): task_description_schema['job-from'],
     Optional('run'): {basestring: object},
     Optional('run-on-projects'): task_description_schema['run-on-projects'],
     Optional('dependencies'): {basestring: taskref_or_string},
@@ -47,33 +47,33 @@ beetmover_cdns_description_schema = Sche
 })
 
 
 @transforms.add
 def validate(config, jobs):
     for job in jobs:
         label = job['name']
         validate_schema(
-            beetmover_cdns_description_schema, job,
-            "In cdns-signing ({!r} kind) task for {!r}:".format(config.kind, label))
+            beetmover_push_to_release_description_schema, job,
+            "In beetmover-push-to-release ({!r} kind) task for {!r}:".format(config.kind, label))
         yield job
 
 
 @transforms.add
-def make_beetmover_cdns_description(config, jobs):
+def make_beetmover_push_to_release_description(config, jobs):
     for job in jobs:
         treeherder = job.get('treeherder', {})
         treeherder.setdefault('symbol', 'Rel(BM-C)')
         treeherder.setdefault('tier', 1)
         treeherder.setdefault('kind', 'build')
         treeherder.setdefault('platform', job['treeherder-platform'])
 
         label = job['name']
         description = (
-            "Beetmover push to cdns for '{product}'".format(
+            "Beetmover push to release for '{product}'".format(
                 product=job['product']
             )
         )
 
         bucket_scope = get_beetmover_bucket_scope(config)
         action_scope = get_beetmover_action_scope(config)
 
         task = {
@@ -91,18 +91,18 @@ def make_beetmover_cdns_description(conf
             'routes': job.get('routes', []),
             'extra': job.get('extra', {}),
         }
 
         yield task
 
 
 @transforms.add
-def make_beetmover_cdns_worker(config, jobs):
+def make_beetmover_push_to_release_worker(config, jobs):
     for job in jobs:
         worker = {
-            'implementation': 'beetmover-cdns',
+            'implementation': 'beetmover-push-to-release',
             'product': job['product'],
         }
         job["worker"] = worker
         del(job['product'])
 
         yield job
--- a/taskcluster/taskgraph/transforms/checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/checksums_signing.py
@@ -8,16 +8,17 @@ Transform the checksums signing task int
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
+    add_scope_prefix,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
@@ -93,17 +94,17 @@ def make_checksums_signing_description(c
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                "project:releng:signing:format:gpg"
+                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/release_generate_checksums_signing.py
@@ -8,16 +8,17 @@ Transform the release-generate-checksums
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
+    add_scope_prefix,
 )
 from taskgraph.util.taskcluster import get_artifact_path
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Required, Optional
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
@@ -83,17 +84,17 @@ def make_release_generate_checksums_sign
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                "project:releng:signing:format:gpg"
+                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/transforms/source_checksums_signing.py
+++ b/taskcluster/taskgraph/transforms/source_checksums_signing.py
@@ -8,16 +8,17 @@ Transform the checksums signing task int
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.util.attributes import copy_attributes_from_dependent_job
 from taskgraph.util.schema import validate_schema, Schema
 from taskgraph.util.scriptworker import (
     get_signing_cert_scope,
     get_worker_type_for_scope,
+    add_scope_prefix,
 )
 from taskgraph.transforms.task import task_description_schema
 from voluptuous import Any, Required, Optional
 
 # Voluptuous uses marker objects as dictionary *keys*, but they are not
 # comparable, so we cast all of the keys back to regular strings
 task_description_schema = {str(k): v for k, v in task_description_schema.schema.iteritems()}
 
@@ -83,17 +84,17 @@ def make_checksums_signing_description(c
             'label': label,
             'description': description,
             'worker-type': get_worker_type_for_scope(config, signing_cert_scope),
             'worker': {'implementation': 'scriptworker-signing',
                        'upstream-artifacts': upstream_artifacts,
                        'max-run-time': 3600},
             'scopes': [
                 signing_cert_scope,
-                "project:releng:signing:format:gpg"
+                add_scope_prefix(config, 'signing:format:gpg'),
             ],
             'dependencies': dependencies,
             'attributes': attributes,
             'run-on-projects': dep_job.attributes.get('run_on_projects'),
             'treeherder': treeherder,
         }
 
         yield task
--- a/taskcluster/taskgraph/transforms/task.py
+++ b/taskcluster/taskgraph/transforms/task.py
@@ -460,17 +460,17 @@ task_description_schema = Schema({
 
             # Paths to the artifacts to sign
             Required('paths'): [basestring],
 
             # locale is used to map upload path and allow for duplicate simple names
             Required('locale'): basestring,
         }],
     }, {
-        Required('implementation'): 'beetmover-cdns',
+        Required('implementation'): 'beetmover-push-to-release',
 
         # the maximum time to run, in seconds
         Required('max-run-time'): int,
         Required('product'): basestring,
     }, {
         Required('implementation'): 'balrog',
         Required('balrog-action'): Any(*BALROG_ACTIONS),
         Optional('product'): basestring,
@@ -993,18 +993,18 @@ def build_beetmover_payload(config, task
     if worker.get('locale'):
         task_def['payload']['locale'] = worker['locale']
     if worker.get('partner-public'):
         task_def['payload']['is_partner_repack_public'] = worker['partner-public']
     if release_config:
         task_def['payload'].update(release_config)
 
 
-@payload_builder('beetmover-cdns')
-def build_beetmover_cdns_payload(config, task, task_def):
+@payload_builder('beetmover-push-to-release')
+def build_beetmover_push_to_release_payload(config, task, task_def):
     worker = task['worker']
     release_config = get_release_config(config)
 
     task_def['payload'] = {
         'maxRunTime': worker['max-run-time'],
         'product': worker['product'],
         'version': release_config['version'],
         'build_number': release_config['build_number'],
@@ -1214,17 +1214,17 @@ def set_defaults(config, tasks):
         elif worker['implementation'] == 'generic-worker':
             worker.setdefault('env', {})
             worker.setdefault('os-groups', [])
             worker.setdefault('chain-of-trust', False)
         elif worker['implementation'] == 'scriptworker-signing':
             worker.setdefault('max-run-time', 600)
         elif worker['implementation'] == 'beetmover':
             worker.setdefault('max-run-time', 600)
-        elif worker['implementation'] == 'beetmover-cdns':
+        elif worker['implementation'] == 'beetmover-push-to-release':
             worker.setdefault('max-run-time', 600)
         elif worker['implementation'] == 'push-apk':
             worker.setdefault('commit', False)
 
         yield task
 
 
 @transforms.add
--- a/toolkit/components/telemetry/Histograms.json
+++ b/toolkit/components/telemetry/Histograms.json
@@ -3983,24 +3983,16 @@
     "record_in_processes": ["main", "content"],
     "alert_emails": ["perf-telemetry-alerts@mozilla.com"],
     "expires_in_version": "never",
     "kind": "exponential",
     "high": 3000,
     "n_buckets": 10,
     "description": "Time spent scanning filesystem for plugins (ms)"
   },
-  "CHECK_JAVA_ENABLED": {
-    "record_in_processes": ["main", "content"],
-    "expires_in_version": "default",
-    "kind": "exponential",
-    "high": 3000,
-    "n_buckets": 10,
-    "description": "Time spent checking if Java is enabled (ms)"
-  },
   "PLUGIN_HANG_UI_USER_RESPONSE": {
     "record_in_processes": ["main", "content"],
     "expires_in_version": "never",
     "kind": "enumerated",
     "n_values": 3,
     "description": "User response to Plugin Hang UI"
   },
   "PLUGIN_HANG_UI_DONT_ASK": {
@@ -12982,16 +12974,27 @@
     "bug_numbers": [1309442],
     "expires_in_version": "66",
     "keyed": true,
     "kind": "linear",
     "high": 100,
     "n_buckets": 10,
     "description": "Percentage of time taken by phases in expensive content paints."
   },
+  "CONTENT_SMALL_PAINT_PHASE_WEIGHT": {
+    "record_in_processes": ["main", "content"],
+    "alert_emails": ["mwoodrow@mozilla.com","gfx-telemetry-alerts@mozilla.com"],
+    "bug_numbers": [1430897],
+    "expires_in_version": "66",
+    "keyed": true,
+    "kind": "linear",
+    "high": 100,
+    "n_buckets": 10,
+    "description": "Percentage of time taken by phases in normal content paints."
+  },
   "NARRATE_CONTENT_BY_LANGUAGE_2": {
     "record_in_processes": ["main", "content"],
     "alert_emails": ["eisaacson@mozilla.com"],
     "bug_numbers": [1308030, 1324868],
     "releaseChannelCollection": "opt-out",
     "expires_in_version": "60",
     "kind": "enumerated",
     "keyed": true,
--- a/toolkit/components/telemetry/Scalars.yaml
+++ b/toolkit/components/telemetry/Scalars.yaml
@@ -1313,34 +1313,16 @@ gfx.omtp:
     kind: uint
     expires: "66"
     notification_emails:
       - gfx-telemetry-alerts@mozilla.com
       - rhunt@mozilla.com
     record_in_processes:
       - 'content'
 
-gfx:
-  small_paint_phase_weight:
-    bug_numbers:
-      - 1430897
-    description: >
-      Time that is spent in each phase of painting, as a percentage of
-      paint time. See also CONTENT_LARGE_PAINT_PHASE_WEIGHT in Histograms.json.
-      Keys are r (rasterization), dl (display list construction), and flb
-      (FrameLayerBuilder).
-    keyed: true
-    kind: uint
-    expires: "66"
-    notification_emails:
-      - gfx-telemetry-alerts@mozilla.com
-      - mwoodrow@mozilla.com
-    record_in_processes:
-      - 'content'
-
 # The following section contains the form autofill related scalars.
 formautofill:
   availability:
     bug_numbers:
       - 1386959
     description: A boolean sent once per session to represent whether the formautofill is available in the build
     expires: never
     kind: boolean
--- a/toolkit/components/telemetry/histogram-whitelists.json
+++ b/toolkit/components/telemetry/histogram-whitelists.json
@@ -89,17 +89,16 @@
     "CANVAS_WEBGL_USED",
     "CERT_CHAIN_KEY_SIZE_STATUS",
     "CERT_CHAIN_SHA1_POLICY_STATUS",
     "CHANGES_OF_DETECTED_LANGUAGE",
     "CHANGES_OF_TARGET_LANGUAGE",
     "CHARSET_OVERRIDE_SITUATION",
     "CHARSET_OVERRIDE_USED",
     "CHECK_ADDONS_MODIFIED_MS",
-    "CHECK_JAVA_ENABLED",
     "COMPONENTS_SHIM_ACCESSED_BY_CONTENT",
     "COMPOSITE_FRAME_ROUNDTRIP_TIME",
     "COMPOSITE_TIME",
     "CONTENT_DOCUMENTS_DESTROYED",
     "CRASH_STORE_COMPRESSED_BYTES",
     "DATABASE_LOCKED_EXCEPTION",
     "DATABASE_SUCCESSFUL_UNLOCK",
     "DATA_STORAGE_ENTRIES",
@@ -596,17 +595,16 @@
     "CERT_VALIDATION_HTTP_REQUEST_FAILED_TIME",
     "CERT_VALIDATION_HTTP_REQUEST_RESULT",
     "CERT_VALIDATION_HTTP_REQUEST_SUCCEEDED_TIME",
     "CHANGES_OF_DETECTED_LANGUAGE",
     "CHANGES_OF_TARGET_LANGUAGE",
     "CHARSET_OVERRIDE_SITUATION",
     "CHARSET_OVERRIDE_USED",
     "CHECK_ADDONS_MODIFIED_MS",
-    "CHECK_JAVA_ENABLED",
     "COMPONENTS_SHIM_ACCESSED_BY_CONTENT",
     "COMPOSITE_FRAME_ROUNDTRIP_TIME",
     "COMPOSITE_TIME",
     "CONTENT_DOCUMENTS_DESTROYED",
     "COOKIE_SCHEME_SECURITY",
     "CRASH_STORE_COMPRESSED_BYTES",
     "CYCLE_COLLECTOR",
     "CYCLE_COLLECTOR_ASYNC_SNOW_WHITE_FREEING",
@@ -1430,17 +1428,16 @@
     "OSFILE_WRITEATOMIC_JANK_MS",
     "STARTUP_MEASUREMENT_ERRORS",
     "CERT_CHAIN_KEY_SIZE_STATUS",
     "CHANGES_OF_TARGET_LANGUAGE",
     "FX_NEW_WINDOW_MS",
     "PDF_VIEWER_TIME_TO_VIEW_MS",
     "SSL_OCSP_MAY_FETCH",
     "MOZ_SQLITE_OTHER_READ_B",
-    "CHECK_JAVA_ENABLED",
     "TRANSLATION_OPPORTUNITIES",
     "NEWTAB_PAGE_BLOCKED_SITES_COUNT",
     "FX_SESSION_RESTORE_NUMBER_OF_TABS_RESTORED",
     "WEAVE_START_COUNT",
     "FX_SESSION_RESTORE_RESTORE_WINDOW_MS",
     "HTTP_DISK_CACHE_OVERHEAD",
     "FX_SESSION_RESTORE_CORRUPT_FILE",
     "FX_TAB_CLICK_MS",
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -2466,36 +2466,44 @@ this.XPIDatabaseReconcile = {
     //    directory scan.
     let isNewInstall = !!aNewAddon || !XPIDatabase.rebuildingDatabase;
 
     // If it's a new install and we haven't yet loaded the manifest then it
     // must be something dropped directly into the install location
     let isDetectedInstall = isNewInstall && !aNewAddon;
 
     // Load the manifest if necessary and sanity check the add-on ID
+    let unsigned;
     try {
       if (!aNewAddon) {
         // Load the manifest from the add-on.
         let file = new nsIFile(aAddonState.path);
         aNewAddon = XPIInstall.syncLoadManifestFromFile(file, aInstallLocation);
       }
       // The add-on in the manifest should match the add-on ID.
       if (aNewAddon.id != aId) {
         throw new Error("Invalid addon ID: expected addon ID " + aId +
                         ", found " + aNewAddon.id + " in manifest");
       }
+
+      unsigned = XPIDatabase.mustSign(aNewAddon.type) && !aNewAddon.isCorrectlySigned;
+      if (unsigned) {
+          throw Error(`Extension ${aNewAddon.id} is not correctly signed`);
+      }
     } catch (e) {
       logger.warn("addMetadata: Add-on " + aId + " is invalid", e);
 
       // Remove the invalid add-on from the install location if the install
       // location isn't locked
       if (aInstallLocation.isLinkedAddon(aId))
         logger.warn("Not uninstalling invalid item because it is a proxy file");
       else if (aInstallLocation.locked)
         logger.warn("Could not uninstall invalid item from locked install location");
+      else if (unsigned && !isNewInstall)
+        logger.warn("Not uninstalling existing unsigned add-on");
       else
         aInstallLocation.uninstallAddon(aId);
       return null;
     }
 
     // Update the AddonInternal properties.
     aNewAddon.installDate = aAddonState.mtime;
     aNewAddon.updateDate = aAddonState.mtime;
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_proxy/bootstrap.js
+++ /dev/null
@@ -1,1 +0,0 @@
-ChromeUtils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_cache_certdb.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_cache_certdb.js
@@ -18,12 +18,10 @@ add_task(async function() {
 
   await promiseStartupManager();
 
   // Force a rescan of signatures
   const { XPIProvider } = ChromeUtils.import("resource://gre/modules/addons/XPIProvider.jsm", {});
   await XPIProvider.verifySignatures();
 
   let addon = await AddonManager.getAddonByID(ID);
-  Assert.equal(addon.signedState, AddonManager.SIGNEDSTATE_MISSING);
-  Assert.ok(!addon.isActive);
-  Assert.ok(addon.appDisabled);
+  Assert.equal(addon, null, "Unsigned extensions should not be installed at startup");
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_filepointer.js
@@ -71,16 +71,20 @@ function promiseWriteRelativePointer(aId
   let relTarget = absTarget.getRelativeDescriptor(profileDir);
 
   return OS.File.writeAtomic(path, new TextEncoder().encode(relTarget));
 }
 
 add_task(async function setup() {
   ok(TEST_UNPACKED, "Pointer files only work with unpacked directories");
 
+  // Unpacked extensions are never signed, so this can only run with
+  // signature checks disabled.
+  Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
+
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
 });
 
 // Tests that installing a new add-on by pointer works
 add_task(async function test_new_pointer_install() {
   await promiseWriteInstallRDFForExtension(addon1, sourceDir);
   await promiseWritePointer(addon1.id);
   await promiseStartupManager();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_proxies.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_proxies.js
@@ -68,16 +68,20 @@ function checkAddonsExist() {
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
 
 function run_test() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "2", "2");
 
+  // Unpacked extensions are never signed, so this can only run with
+  // signature checks disabled.
+  Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
+
   add_task(run_proxy_tests);
 
   if (gHaveSymlinks)
     add_task(run_symlink_tests);
 
   run_next_test();
 }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_proxy.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_proxy.js
@@ -3,94 +3,97 @@
  */
 
 const ID = "proxy1@tests.mozilla.org";
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 BootstrapMonitor.init();
 
+const BOOTSTRAP_JS = `ChromeUtils.import("resource://xpcshell-data/BootstrapMonitor.jsm").monitor(this);`;
+
 // Ensure that a proxy file to an add-on with a valid manifest works.
 add_task(async function() {
   Services.prefs.setBoolPref(PREF_XPI_SIGNATURES_REQUIRED, false);
 
   await promiseStartupManager();
 
   let tempdir = gTmpD.clone();
-  await promiseWriteInstallRDFToDir({
+  let unpackedAddon = await promiseWriteInstallRDFToDir({
     id: ID,
     version: "1.0",
     bootstrap: true,
     unpack: true,
     targetApplications: [{
-          id: "xpcshell@tests.mozilla.org",
+      id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
-        }],
+    }],
     name: "Test Bootstrap 1 (proxy)",
-  }, tempdir, ID, "bootstrap.js");
-
-  let unpackedAddon = tempdir.clone();
-  unpackedAddon.append(ID);
-  do_get_file("data/test_proxy/bootstrap.js")
-    .copyTo(unpackedAddon, "bootstrap.js");
+  }, tempdir, ID, {
+    "bootstrap.js": BOOTSTRAP_JS,
+  });
 
   // create proxy file in profile/extensions dir
   let extensionsDir = gProfD.clone();
   extensionsDir.append("extensions");
   let proxyFile = await promiseWriteProxyFileToDir(extensionsDir, unpackedAddon, ID);
 
   await promiseRestartManager();
 
-  BootstrapMonitor.checkAddonInstalled(ID, "1.0");
-  BootstrapMonitor.checkAddonStarted(ID, "1.0");
-
   let addon = await promiseAddonByID(ID);
 
-  Assert.notEqual(addon, null);
-  Assert.equal(addon.version, "1.0");
-  Assert.equal(addon.name, "Test Bootstrap 1 (proxy)");
-  Assert.ok(addon.isCompatible);
-  Assert.ok(!addon.appDisabled);
-  Assert.ok(addon.isActive);
-  Assert.equal(addon.type, "extension");
-  Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
+  if (AppConstants.MOZ_REQUIRE_SIGNING) {
+    BootstrapMonitor.checkAddonNotInstalled(ID, "1.0");
+    BootstrapMonitor.checkAddonNotStarted(ID, "1.0");
+
+    Assert.equal(addon, null);
+  } else {
+    BootstrapMonitor.checkAddonInstalled(ID, "1.0");
+    BootstrapMonitor.checkAddonStarted(ID, "1.0");
 
-  Assert.ok(proxyFile.exists());
+    Assert.notEqual(addon, null);
+    Assert.equal(addon.version, "1.0");
+    Assert.equal(addon.name, "Test Bootstrap 1 (proxy)");
+    Assert.ok(addon.isCompatible);
+    Assert.ok(!addon.appDisabled);
+    Assert.ok(addon.isActive);
+    Assert.equal(addon.type, "extension");
+    Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_UNKNOWN : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
-  addon.uninstall();
+    Assert.ok(proxyFile.exists());
+
+    addon.uninstall();
+  }
   unpackedAddon.remove(true);
 
   await promiseRestartManager();
 });
 
 
 // Ensure that a proxy file to an add-on is not removed even
 // if the manifest file is invalid. See bug 1195353.
 add_task(async function() {
   let tempdir = gTmpD.clone();
 
   // use a mismatched ID to make this install.rdf invalid
-  await promiseWriteInstallRDFToDir({
+  let unpackedAddon = await promiseWriteInstallRDFToDir({
     id: "bad-proxy1@tests.mozilla.org",
     version: "1.0",
     bootstrap: true,
     unpack: true,
     targetApplications: [{
-          id: "xpcshell@tests.mozilla.org",
+      id: "xpcshell@tests.mozilla.org",
       minVersion: "1",
       maxVersion: "1"
-        }],
+    }],
     name: "Test Bootstrap 1 (proxy)",
-  }, tempdir, ID, "bootstrap.js");
-
-  let unpackedAddon = tempdir.clone();
-  unpackedAddon.append(ID);
-  do_get_file("data/test_proxy/bootstrap.js")
-    .copyTo(unpackedAddon, "bootstrap.js");
+  }, tempdir, ID, {
+    "bootstrap.js": BOOTSTRAP_JS,
+  });
 
   // create proxy file in profile/extensions dir
   let extensionsDir = gProfD.clone();
   extensionsDir.append("extensions");
   let proxyFile = await promiseWriteProxyFileToDir(extensionsDir, unpackedAddon, ID);
 
   await promiseRestartManager();
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_signed_verify.js
@@ -85,78 +85,16 @@ function verify_no_change([startFile, st
 
     // Remove the add-on and restart to let it go away
     manuallyUninstall(profileDir, ID);
     await promiseRestartManager();
     await promiseShutdownManager();
   });
 }
 
-function verify_enables([startFile, startState], [endFile, endState]) {
-  add_task(async function() {
-    info("A switch from " + startFile + " to " + endFile + " should enable the add-on.");
-
-    // Install the first add-on
-    await manuallyInstall(do_get_file(DATA + startFile), profileDir, ID);
-    await promiseStartupManager();
-
-    let addon = await promiseAddonByID(ID);
-    Assert.notEqual(addon, null);
-    Assert.ok(!addon.isActive);
-    Assert.equal(addon.pendingOperations, AddonManager.PENDING_NONE);
-    Assert.equal(addon.signedState, startState);
-
-    // Swap in the files from the next add-on
-    manuallyUninstall(profileDir, ID);
-    await manuallyInstall(do_get_file(DATA + endFile), profileDir, ID);
-
-    let needsRestart = hasFlag(addon.operationsRequiringRestart, AddonManager.OP_NEEDS_RESTART_ENABLE);
-    info(needsRestart);
-
-    let events = {};
-    if (!needsRestart) {
-      events[ID] = [
-        ["onPropertyChanged", ["appDisabled"]],
-        ["onEnabling", false],
-        "onEnabled"
-      ];
-    } else {
-      events[ID] = [
-        ["onPropertyChanged", ["appDisabled"]],
-        "onEnabling"
-      ];
-    }
-
-    if (startState != endState)
-      events[ID].unshift(["onPropertyChanged", ["signedState"]]);
-
-    prepare_test(events);
-
-    // Trigger the check
-    let changes = await verifySignatures();
-    Assert.equal(changes.enabled.length, 1);
-    Assert.equal(changes.enabled[0], ID);
-    Assert.equal(changes.disabled.length, 0);
-
-    Assert.ok(!addon.appDisabled);
-    if (needsRestart)
-      Assert.notEqual(addon.pendingOperations, AddonManager.PENDING_NONE);
-    else
-      Assert.ok(addon.isActive);
-    Assert.equal(addon.signedState, endState);
-
-    ensure_test_completed();
-
-    // Remove the add-on and restart to let it go away
-    manuallyUninstall(profileDir, ID);
-    await promiseRestartManager();
-    await promiseShutdownManager();
-  });
-}
-
 function verify_disables([startFile, startState], [endFile, endState]) {
   add_task(async function() {
     info("A switch from " + startFile + " to " + endFile + " should disable the add-on.");
 
     // Install the first add-on
     await manuallyInstall(do_get_file(DATA + startFile), profileDir, ID);
     await promiseStartupManager();
 
@@ -214,25 +152,13 @@ function verify_disables([startFile, sta
 }
 
 for (let start of GOOD) {
   for (let end of BAD) {
     verify_disables(start, end);
   }
 }
 
-for (let start of BAD) {
-  for (let end of GOOD) {
-    verify_enables(start, end);
-  }
-}
-
 for (let start of GOOD) {
   for (let end of GOOD.filter(f => f != start)) {
     verify_no_change(start, end);
   }
 }
-
-for (let start of BAD) {
-  for (let end of BAD.filter(f => f != start)) {
-    verify_no_change(start, end);
-  }
-}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -167,16 +167,24 @@ add_task(async function() {
           }],
           name: "Test Bootstrap 1 (temporary)",
         }),
         "bootstrap.js": bootstrapJS,
       };
 
       let target;
       if (!packed) {
+        // Unpacked extensions don't support signing, which means that
+        // our mock signing service is not able to give them a
+        // privileged signed state, and we can't install them on release
+        // builds.
+        if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+          continue;
+        }
+
         target = tempdir.clone();
         target.append(ID);
 
         await AddonTestUtils.promiseWriteFilesToDir(target.path, files);
       } else {
         target = tempdir.clone();
         target.append(`${ID}.xpi`);
 
@@ -357,16 +365,21 @@ add_task(async function test_samefile() 
   Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   addon.uninstall();
 });
 
 // Install a temporary add-on over the top of an existing add-on.
 // Uninstall it and make sure the existing add-on comes back.
 add_task(async function() {
+  // We can't install unpacked add-ons on release builds. See above.
+  if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+    return;
+  }
+
   await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
 
   BootstrapMonitor.checkAddonInstalled(ID, "1.0");
   BootstrapMonitor.checkAddonStarted(ID, "1.0");
 
   let tempdir = gTmpD.clone();
   await promiseWriteInstallRDFToDir({
     id: ID,
@@ -459,16 +472,21 @@ add_task(async function() {
   BootstrapMonitor.checkAddonNotStarted(ID);
 
   await promiseRestartManager();
 });
 
 // Install a temporary add-on as a version upgrade over the top of an
 // existing temporary add-on.
 add_task(async function() {
+  // We can't install unpacked add-ons on release builds. See above.
+  if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+    return;
+  }
+
   const tempdir = gTmpD.clone();
 
   await promiseWriteInstallRDFToDir(sampleRDFManifest, tempdir, "bootstrap1@tests.mozilla.org", "bootstrap.js");
 
   const unpackedAddon = tempdir.clone();
   unpackedAddon.append(ID);
   do_get_file("data/test_temporary/bootstrap.js")
     .copyTo(unpackedAddon, "bootstrap.js");
@@ -510,16 +528,21 @@ add_task(async function() {
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
 // Install a temporary add-on as a version downgrade over the top of an
 // existing temporary add-on.
 add_task(async function() {
+  // We can't install unpacked add-ons on release builds. See above.
+  if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+    return;
+  }
+
   const tempdir = gTmpD.clone();
 
   await promiseWriteInstallRDFToDir(sampleRDFManifest, tempdir, "bootstrap1@tests.mozilla.org", "bootstrap.js");
 
   const unpackedAddon = tempdir.clone();
   unpackedAddon.append(ID);
   do_get_file("data/test_temporary/bootstrap.js")
     .copyTo(unpackedAddon, "bootstrap.js");
@@ -559,16 +582,21 @@ add_task(async function() {
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
 // Installing a temporary add-on over an existing add-on with the same
 // version number should be installed as an upgrade.
 add_task(async function() {
+  // We can't install unpacked add-ons on release builds. See above.
+  if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+    return;
+  }
+
   const tempdir = gTmpD.clone();
 
   await promiseWriteInstallRDFToDir(sampleRDFManifest, tempdir, "bootstrap1@tests.mozilla.org", "bootstrap.js");
 
   const unpackedAddon = tempdir.clone();
   unpackedAddon.append(ID);
   do_get_file("data/test_temporary/bootstrap.js")
     .copyTo(unpackedAddon, "bootstrap.js");
@@ -616,16 +644,21 @@ add_task(async function() {
 
   unpackedAddon.remove(true);
   await promiseRestartManager();
 });
 
 // Install a temporary add-on over the top of an existing disabled add-on.
 // After restart, the existing add-on should continue to be installed and disabled.
 add_task(async function() {
+  // We can't install unpacked add-ons on release builds. See above.
+  if (!AppConstants.MOZ_ALLOW_LEGACY_EXTENSIONS) {
+    return;
+  }
+
   await promiseInstallAllFiles([do_get_addon("test_bootstrap1_1")], true);
 
   BootstrapMonitor.checkAddonInstalled(ID, "1.0");
   BootstrapMonitor.checkAddonStarted(ID, "1.0");
 
   let addon = await promiseAddonByID(ID);
 
   addon.userDisabled = true;
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-unpack.ini
@@ -5,8 +5,9 @@ firefox-appdir = browser
 skip-if = toolkit == 'android'
 dupe-manifest =
 tags = addons
 
 [test_webextension_paths.js]
 tags = webextensions
 
 [test_filepointer.js]
+skip-if = !allow_legacy_extensions || require_signing