Merge inbound to mozilla-central. a=merge
authorBogdan Tara <btara@mozilla.com>
Sun, 21 Oct 2018 00:56:04 +0300
changeset 442259 3872ebc4799bf8e606006f36444e021dddad0825
parent 442258 9eefd490403607a910d9805665b8f2a6d3a32c49 (current diff)
parent 442247 aa67b80229d655bacc8842d8cb18a7c8bf110c20 (diff)
child 442260 1d34fad96d5d182db213456aec5dea1cdad30cd5
child 442263 ab280e276dcec0ed5aec2a61753722929d25b86c
child 442273 c9d8f0d61fa151779f3e129c98550d94c5e8656e
push id109123
push userbtara@mozilla.com
push dateSat, 20 Oct 2018 21:59:56 +0000
treeherdermozilla-inbound@1d34fad96d5d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.0a1
first release with
nightly linux32
3872ebc4799b / 64.0a1 / 20181020220116 / files
nightly linux64
3872ebc4799b / 64.0a1 / 20181020220116 / files
nightly mac
3872ebc4799b / 64.0a1 / 20181020220116 / files
nightly win32
3872ebc4799b / 64.0a1 / 20181020220116 / files
nightly win64
3872ebc4799b / 64.0a1 / 20181020220116 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge
mobile/android/extensions/webcompat/bootstrap.js
mobile/android/extensions/webcompat/content/data/ua_overrides.jsm
mobile/android/extensions/webcompat/content/lib/ua_overrider.jsm
mobile/android/extensions/webcompat/install.rdf.in
mobile/android/extensions/webcompat/jar.mn
mobile/android/extensions/webcompat/test/.eslintrc.js
mobile/android/extensions/webcompat/test/browser.ini
mobile/android/extensions/webcompat/test/browser_check_installed.js
mobile/android/extensions/webcompat/test/browser_overrider.js
mobile/android/extensions/webcompat/webextension/background.js
mobile/android/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
mobile/android/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
mobile/android/extensions/webcompat/webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
mobile/android/extensions/webcompat/webextension/manifest.json
--- a/accessible/base/NotificationController.cpp
+++ b/accessible/base/NotificationController.cpp
@@ -412,37 +412,49 @@ void
 NotificationController::ScheduleChildDocBinding(DocAccessible* aDocument)
 {
   // Schedule child document binding to the tree.
   mHangingChildDocuments.AppendElement(aDocument);
   ScheduleProcessing();
 }
 
 void
-NotificationController::ScheduleContentInsertion(Accessible* aContainer,
-                                                 nsIContent* aStartChildNode,
+NotificationController::ScheduleContentInsertion(nsIContent* aStartChildNode,
                                                  nsIContent* aEndChildNode)
 {
-  nsTArray<nsCOMPtr<nsIContent>> list;
+  // The frame constructor guarantees that only ranges with the same parent
+  // arrive here in presence of dynamic changes to the page, see
+  // nsCSSFrameConstructor::IssueSingleInsertNotifications' callers.
+  nsINode* parent = aStartChildNode->GetFlattenedTreeParentNode();
+  if (!parent) {
+    return;
+  }
 
-  bool needsProcessing = false;
-  nsIContent* node = aStartChildNode;
-  while (node != aEndChildNode) {
+  Accessible* container = mDocument->AccessibleOrTrueContainer(parent);
+  if (!container) {
+    return;
+  }
+
+  AutoTArray<nsCOMPtr<nsIContent>, 10> list;
+  for (nsIContent* node = aStartChildNode;
+       node != aEndChildNode;
+       node = node->GetNextSibling()) {
+    MOZ_ASSERT(parent == node->GetFlattenedTreeParentNode());
     // Notification triggers for content insertion even if no content was
     // actually inserted, check if the given content has a frame to discard
     // this case early.
+    //
+    // TODO(emilio): Should this handle display: contents?
     if (node->GetPrimaryFrame()) {
-      if (list.AppendElement(node))
-        needsProcessing = true;
+      list.AppendElement(node);
     }
-    node = node->GetNextSibling();
   }
 
-  if (needsProcessing) {
-    mContentInsertions.LookupOrAdd(aContainer)->AppendElements(list);
+  if (!list.IsEmpty()) {
+    mContentInsertions.LookupOrAdd(container)->AppendElements(list);
     ScheduleProcessing();
   }
 }
 
 void
 NotificationController::ScheduleProcessing()
 {
   // If notification flush isn't planed yet start notification flush
--- a/accessible/base/NotificationController.h
+++ b/accessible/base/NotificationController.h
@@ -190,18 +190,17 @@ public:
 
     mTextHash.PutEntry(aTextNode);
     ScheduleProcessing();
   }
 
   /**
    * Pend accessible tree update for content insertion.
    */
-  void ScheduleContentInsertion(Accessible* aContainer,
-                                nsIContent* aStartChildNode,
+  void ScheduleContentInsertion(nsIContent* aStartChildNode,
                                 nsIContent* aEndChildNode);
 
   /**
    * Pend an accessible subtree relocation.
    */
   void ScheduleRelocation(Accessible* aOwner)
   {
     if (!mRelocations.Contains(aOwner) && mRelocations.AppendElement(aOwner)) {
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -380,20 +380,17 @@ nsAccessibilityService::ListenersChanged
     for (uint32_t i = 0 ; i < changeCount ; i++) {
       nsIDocument* ownerDoc = node->OwnerDoc();
       DocAccessible* document = GetExistingDocAccessible(ownerDoc);
 
       // Create an accessible for a inaccessible element having click event
       // handler.
       if (document && !document->HasAccessible(node) &&
           nsCoreUtils::HasClickListener(node)) {
-        nsIContent* parentEl = node->GetFlattenedTreeParent();
-        if (parentEl) {
-          document->ContentInserted(parentEl, node, node->GetNextSibling());
-        }
+        document->ContentInserted(node, node->GetNextSibling());
         break;
       }
     }
   }
   return NS_OK;
 }
 
 ////////////////////////////////////////////////////////////////////////////////
@@ -606,17 +603,17 @@ nsAccessibilityService::DeckPanelSwitche
     if (logging::IsEnabled(logging::eTree)) {
       logging::MsgBegin("TREE", "deck panel selected");
       logging::Node("container", panelNode);
       logging::Node("content", aDeckNode);
       logging::MsgEnd();
     }
 #endif
 
-    document->ContentInserted(aDeckNode, panelNode, panelNode->GetNextSibling());
+    document->ContentInserted(panelNode, panelNode->GetNextSibling());
   }
 }
 
 void
 nsAccessibilityService::ContentRangeInserted(nsIPresShell* aPresShell,
                                              nsIContent* aStartChild,
                                              nsIContent* aEndChild)
 {
@@ -630,17 +627,17 @@ nsAccessibilityService::ContentRangeInse
       logging::Node("content", child);
     }
     logging::MsgEnd();
     logging::Stack();
   }
 #endif
 
   if (document) {
-    document->ContentInserted(aStartChild->GetParent(), aStartChild, aEndChild);
+    document->ContentInserted(aStartChild, aEndChild);
   }
 }
 
 void
 nsAccessibilityService::ContentRemoved(nsIPresShell* aPresShell,
                                        nsIContent* aChildNode)
 {
   DocAccessible* document = GetDocAccessible(aPresShell);
--- a/accessible/generic/DocAccessible.cpp
+++ b/accessible/generic/DocAccessible.cpp
@@ -772,20 +772,18 @@ DocAccessible::AttributeChanged(dom::Ele
   if (UpdateAccessibleOnAttrChange(aElement, aAttribute))
     return;
 
   // Update the accessible tree on aria-hidden change. Make sure to not create
   // a tree under aria-hidden='true'.
   if (aAttribute == nsGkAtoms::aria_hidden) {
     if (aria::HasDefinedARIAHidden(aElement)) {
       ContentRemoved(aElement);
-    }
-    else {
-      ContentInserted(aElement->GetFlattenedTreeParent(),
-                      aElement, aElement->GetNextSibling());
+    } else {
+      ContentInserted(aElement, aElement->GetNextSibling());
     }
     return;
   }
 
   // Ignore attribute change if the element doesn't have an accessible (at all
   // or still) iff the element is not a root content of this document accessible
   // (which is treated as attribute change on this document accessible).
   // Note: we don't bail if all the content hasn't finished loading because
@@ -1348,33 +1346,26 @@ DocAccessible::UnbindFromDocument(Access
 
   NS_ASSERTION(!aAccessible->IsDefunct(), "Shutdown the shutdown accessible!");
   aAccessible->Shutdown();
 
   mAccessibleCache.Remove(uniqueID);
 }
 
 void
-DocAccessible::ContentInserted(nsIContent* aContainerNode,
-                               nsIContent* aStartChildNode,
+DocAccessible::ContentInserted(nsIContent* aStartChildNode,
                                nsIContent* aEndChildNode)
 {
   // Ignore content insertions until we constructed accessible tree. Otherwise
   // schedule tree update on content insertion after layout.
   if (mNotificationController && HasLoadState(eTreeConstructed)) {
     // Update the whole tree of this document accessible when the container is
     // null (document element is inserted or removed).
-    Accessible* container = aContainerNode ?
-      AccessibleOrTrueContainer(aContainerNode) : this;
-    if (container) {
-      // Ignore notification if the container node is no longer in the DOM tree.
-      mNotificationController->ScheduleContentInsertion(container,
-                                                        aStartChildNode,
-                                                        aEndChildNode);
-    }
+    mNotificationController->ScheduleContentInsertion(aStartChildNode,
+                                                      aEndChildNode);
   }
 }
 
 void
 DocAccessible::RecreateAccessible(nsIContent* aContent)
 {
 #ifdef A11Y_LOG
   if (logging::IsEnabled(logging::eTree)) {
@@ -1383,20 +1374,18 @@ DocAccessible::RecreateAccessible(nsICon
     logging::MsgEnd();
   }
 #endif
 
   // XXX: we shouldn't recreate whole accessible subtree, instead we should
   // subclass hide and show events to handle them separately and implement their
   // coalescence with normal hide and show events. Note, in this case they
   // should be coalesced with normal show/hide events.
-
-  nsIContent* parent = aContent->GetFlattenedTreeParent();
   ContentRemoved(aContent);
-  ContentInserted(parent, aContent, aContent->GetNextSibling());
+  ContentInserted(aContent, aContent->GetNextSibling());
 }
 
 void
 DocAccessible::ProcessInvalidationList()
 {
   // Invalidate children of container accessible for each element in
   // invalidation list. Allow invalidation list insertions while container
   // children are recached.
--- a/accessible/generic/DocAccessible.h
+++ b/accessible/generic/DocAccessible.h
@@ -345,19 +345,17 @@ public:
   /**
    * Remove from document and shutdown the given accessible.
    */
   void UnbindFromDocument(Accessible* aAccessible);
 
   /**
    * Notify the document accessible that content was inserted.
    */
-  void ContentInserted(nsIContent* aContainerNode,
-                       nsIContent* aStartChildNode,
-                       nsIContent* aEndChildNode);
+  void ContentInserted(nsIContent* aStartChildNode, nsIContent* aEndChildNode);
 
   /**
    * Update the tree on content removal.
    */
   void ContentRemoved(Accessible* aAccessible);
   void ContentRemoved(nsIContent* aContentNode);
 
   /**
--- a/accessible/tests/mochitest/treeupdate/test_bug1276857.html
+++ b/accessible/tests/mochitest/treeupdate/test_bug1276857.html
@@ -76,23 +76,27 @@
             ] },
             { TEXT_LEAF: [] }, // More text
           ],
         };
         testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree);
 
         var shadowRoot = iframe.contentDocument.getElementById("c2_c").shadowRoot;
         shadowRoot.firstElementChild.querySelector("span").remove();
+        // bug 1487312
+        shadowRoot.firstElementChild.offsetTop;
+        shadowRoot.appendChild(document.createElement("button"));
       };
 
       this.finalCheck = function runShadowTest_finalCheck() {
         var tree = {
           SECTION: [ // c2
             { TEXT_LEAF: [] }, // Some text
             { TEXT_LEAF: [] }, // More text
+            { PUSHBUTTON: [] }, // The button we appended.
           ],
         };
         testAccessibleTree(iframe.contentDocument.getElementById("c2"), tree);
       };
 
       this.getID = function runShadowTest_getID() {
         return "child DOM node is removed before the layout notifies the a11y about parent removal/show in shadow DOM";
       };
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1514,16 +1514,21 @@ pref("network.cookie.cookieBehavior", 4 
 pref("browser.contentblocking.allowlist.storage.enabled", true);
 
 #ifdef NIGHTLY_BUILD
 pref("browser.contentblocking.global-toggle.enabled", true);
 #else
 pref("browser.contentblocking.global-toggle.enabled", false);
 #endif
 
+#ifdef NIGHTLY_BUILD
+// Enable the Storage Access API in Nightly
+pref("dom.storage_access.enabled", true);
+#endif
+
 // Disable the UI for FastBlock in product.
 pref("browser.contentblocking.fastblock.ui.enabled", false);
 pref("browser.contentblocking.fastblock.control-center.ui.enabled", false);
 
 // Define a set of default features for the Content Blocking UI.
 pref("browser.contentblocking.trackingprotection.ui.enabled", true);
 pref("browser.contentblocking.trackingprotection.control-center.ui.enabled", true);
 pref("browser.contentblocking.rejecttrackers.ui.enabled", true);
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,5 +1,5 @@
 This is the PDF.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 2.0.928
+Current extension version is: 2.0.936
 
-Taken from upstream commit: e41c50c3
+Taken from upstream commit: d2189293
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.928';
-var pdfjsBuild = 'e41c50c3';
+var pdfjsVersion = '2.0.936';
+var pdfjsBuild = 'd2189293';
 var pdfjsSharedUtil = __w_pdfjs_require__(1);
 var pdfjsDisplayAPI = __w_pdfjs_require__(7);
 var pdfjsDisplayTextLayer = __w_pdfjs_require__(19);
 var pdfjsDisplayAnnotationLayer = __w_pdfjs_require__(20);
 var pdfjsDisplayDOMUtils = __w_pdfjs_require__(8);
 var pdfjsDisplaySVG = __w_pdfjs_require__(21);
 let pdfjsDisplayWorkerOptions = __w_pdfjs_require__(13);
 let pdfjsDisplayAPICompatibility = __w_pdfjs_require__(10);
@@ -143,16 +143,18 @@ exports.PDFWorker = pdfjsDisplayAPI.PDFW
 exports.renderTextLayer = pdfjsDisplayTextLayer.renderTextLayer;
 exports.AnnotationLayer = pdfjsDisplayAnnotationLayer.AnnotationLayer;
 exports.createPromiseCapability = pdfjsSharedUtil.createPromiseCapability;
 exports.PasswordResponses = pdfjsSharedUtil.PasswordResponses;
 exports.InvalidPDFException = pdfjsSharedUtil.InvalidPDFException;
 exports.MissingPDFException = pdfjsSharedUtil.MissingPDFException;
 exports.SVGGraphics = pdfjsDisplaySVG.SVGGraphics;
 exports.NativeImageDecoding = pdfjsSharedUtil.NativeImageDecoding;
+exports.CMapCompressionType = pdfjsSharedUtil.CMapCompressionType;
+exports.PermissionFlag = pdfjsSharedUtil.PermissionFlag;
 exports.UnexpectedResponseException = pdfjsSharedUtil.UnexpectedResponseException;
 exports.OPS = pdfjsSharedUtil.OPS;
 exports.VerbosityLevel = pdfjsSharedUtil.VerbosityLevel;
 exports.UNSUPPORTED_FEATURES = pdfjsSharedUtil.UNSUPPORTED_FEATURES;
 exports.createValidAbsoluteUrl = pdfjsSharedUtil.createValidAbsoluteUrl;
 exports.createObjectURL = pdfjsSharedUtil.createObjectURL;
 exports.removeNullCharacters = pdfjsSharedUtil.removeNullCharacters;
 exports.shadow = pdfjsSharedUtil.shadow;
@@ -4219,17 +4221,17 @@ function _fetchDocument(worker, source, 
     return Promise.reject(new Error('Worker was destroyed'));
   }
   if (pdfDataRangeTransport) {
     source.length = pdfDataRangeTransport.length;
     source.initialData = pdfDataRangeTransport.initialData;
   }
   return worker.messageHandler.sendWithPromise('GetDocRequest', {
     docId,
-    apiVersion: '2.0.928',
+    apiVersion: '2.0.936',
     source: {
       data: source.data,
       url: source.url,
       password: source.password,
       disableAutoFetch: source.disableAutoFetch,
       rangeChunkSize: source.rangeChunkSize,
       length: source.length
     },
@@ -4994,20 +4996,18 @@ class WorkerTransport {
         this._fullReader.cancel(reason);
       };
     }, this);
     messageHandler.on('ReaderHeadersReady', function (data) {
       const headersCapability = (0, _util.createPromiseCapability)();
       const fullReader = this._fullReader;
       fullReader.headersReady.then(() => {
         if (!fullReader.isStreamingSupported || !fullReader.isRangeSupported) {
-          if (this._lastProgress) {
-            if (loadingTask.onProgress) {
-              loadingTask.onProgress(this._lastProgress);
-            }
+          if (this._lastProgress && loadingTask.onProgress) {
+            loadingTask.onProgress(this._lastProgress);
           }
           fullReader.onProgress = evt => {
             if (loadingTask.onProgress) {
               loadingTask.onProgress({
                 loaded: evt.loaded,
                 total: evt.total
               });
             }
@@ -5072,16 +5072,22 @@ class WorkerTransport {
     }, this);
     messageHandler.on('UnexpectedResponse', function (exception) {
       loadingTask._capability.reject(new _util.UnexpectedResponseException(exception.message, exception.status));
     }, this);
     messageHandler.on('UnknownError', function (exception) {
       loadingTask._capability.reject(new _util.UnknownErrorException(exception.message, exception.details));
     }, this);
     messageHandler.on('DataLoaded', function (data) {
+      if (loadingTask.onProgress) {
+        loadingTask.onProgress({
+          loaded: data.length,
+          total: data.length
+        });
+      }
       this.downloadInfoCapability.resolve(data);
     }, this);
     messageHandler.on('StartRenderPage', function (data) {
       if (this.destroyed) {
         return;
       }
       const page = this.pageCache[data.pageIndex];
       page._stats.timeEnd('Page Request');
@@ -5550,18 +5556,18 @@ var InternalRenderTask = function Intern
         }
       });
     }
   };
   return InternalRenderTask;
 }();
 var version, build;
 {
-  exports.version = version = '2.0.928';
-  exports.build = build = 'e41c50c3';
+  exports.version = version = '2.0.936';
+  exports.build = build = 'd2189293';
 }
 exports.getDocument = getDocument;
 exports.LoopbackPort = LoopbackPort;
 exports.PDFDataRangeTransport = PDFDataRangeTransport;
 exports.PDFWorker = PDFWorker;
 exports.PDFDocumentProxy = PDFDocumentProxy;
 exports.PDFPageProxy = PDFPageProxy;
 exports.setPDFNetworkStreamFactory = setPDFNetworkStreamFactory;
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -118,18 +118,18 @@ return /******/ (function(modules) { // 
 /************************************************************************/
 /******/ ([
 /* 0 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
 
 
-var pdfjsVersion = '2.0.928';
-var pdfjsBuild = 'e41c50c3';
+var pdfjsVersion = '2.0.936';
+var pdfjsBuild = 'd2189293';
 var pdfjsCoreWorker = __w_pdfjs_require__(1);
 exports.WorkerMessageHandler = pdfjsCoreWorker.WorkerMessageHandler;
 
 /***/ }),
 /* 1 */
 /***/ (function(module, exports, __w_pdfjs_require__) {
 
 "use strict";
@@ -322,17 +322,17 @@ var WorkerMessageHandler = {
     });
   },
   createDocumentHandler(docParams, port) {
     var pdfManager;
     var terminated = false;
     var cancelXHRs = null;
     var WorkerTasks = [];
     let apiVersion = docParams.apiVersion;
-    let workerVersion = '2.0.928';
+    let workerVersion = '2.0.936';
     if (apiVersion !== workerVersion) {
       throw new Error(`The API version "${apiVersion}" does not match ` + `the Worker version "${workerVersion}".`);
     }
     var docId = docParams.docId;
     var docBaseUrl = docParams.docBaseUrl;
     var workerHandlerName = docParams.docId + '_worker';
     var handler = new _message_handler.MessageHandler(workerHandlerName, docId, port);
     handler.postMessageTransfers = docParams.postMessageTransfers;
--- a/browser/extensions/pdfjs/moz.yaml
+++ b/browser/extensions/pdfjs/moz.yaml
@@ -15,15 +15,15 @@ origin:
   description: Portable Document Format (PDF) viewer that is built with HTML5
 
   # Full URL for the package's homepage/etc
   # Usually different from repository url
   url: https://github.com/mozilla/pdf.js
 
   # Human-readable identifier for this version/release
   # Generally "version NNN", "tag SSS", "bookmark SSS"
-  release: version 2.0.928
+  release: version 2.0.936
 
   # The package's license, where possible using the mnemonic from
   # https://spdx.org/licenses/
   # Multiple licenses can be specified (as a YAML list)
   # A "LICENSE" file must exist containing the full license text
   license: Apache-2.0
--- a/dom/html/HTMLIFrameElement.cpp
+++ b/dom/html/HTMLIFrameElement.cpp
@@ -69,17 +69,17 @@ HTMLIFrameElement::BindToTree(nsIDocumen
 {
   nsresult rv = nsGenericHTMLFrameElement::BindToTree(aDocument, aParent,
                                                       aBindingParent);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (StaticPrefs::dom_security_featurePolicy_enabled()) {
-    RefreshFeaturePolicy();
+    RefreshFeaturePolicy(true /* parse the feature policy attribute */);
   }
   return NS_OK;
 }
 
 bool
 HTMLIFrameElement::ParseAttribute(int32_t aNamespaceID,
                                   nsAtom* aAttribute,
                                   const nsAString& aValue,
@@ -185,23 +185,27 @@ HTMLIFrameElement::AfterSetAttr(int32_t 
     if (aName == nsGkAtoms::sandbox) {
       if (mFrameLoader) {
         // If we have an nsFrameLoader, apply the new sandbox flags.
         // Since this is called after the setter, the sandbox flags have
         // alreay been updated.
         mFrameLoader->ApplySandboxFlags(GetSandboxFlags());
       }
     }
-    if ((aName == nsGkAtoms::allow ||
-         aName == nsGkAtoms::src ||
-         aName == nsGkAtoms::srcdoc ||
-         aName == nsGkAtoms::sandbox ||
-         aName == nsGkAtoms::allowpaymentrequest) &&
-        StaticPrefs::dom_security_featurePolicy_enabled()) {
-      RefreshFeaturePolicy();
+
+    if (StaticPrefs::dom_security_featurePolicy_enabled()) {
+      if (aName == nsGkAtoms::allow ||
+          aName == nsGkAtoms::src ||
+          aName == nsGkAtoms::srcdoc ||
+          aName == nsGkAtoms::sandbox) {
+        RefreshFeaturePolicy(true /* parse the feature policy attribute */);
+      } else if (aName == nsGkAtoms::allowfullscreen ||
+                 aName == nsGkAtoms::allowpaymentrequest) {
+        RefreshFeaturePolicy(false /* parse the feature policy attribute */);
+      }
     }
   }
   return nsGenericHTMLFrameElement::AfterSetAttr(aNameSpaceID, aName,
                                                  aValue, aOldValue,
                                                  aMaybeScriptedPrincipal,
                                                  aNotify);
 }
 
@@ -274,43 +278,43 @@ HTMLIFrameElement::GetFeaturePolicyDefau
   if (!principal) {
     principal = NodePrincipal();
   }
 
   return principal.forget();
 }
 
 void
-HTMLIFrameElement::RefreshFeaturePolicy()
+HTMLIFrameElement::RefreshFeaturePolicy(bool aParseAllowAttribute)
 {
   MOZ_ASSERT(StaticPrefs::dom_security_featurePolicy_enabled());
-  mFeaturePolicy->ResetDeclaredPolicy();
+
+  if (aParseAllowAttribute) {
+    mFeaturePolicy->ResetDeclaredPolicy();
 
-  // The origin can change if 'src' and 'srcdoc' attributes change.
-  nsCOMPtr<nsIPrincipal> origin = GetFeaturePolicyDefaultOrigin();
-  MOZ_ASSERT(origin);
-  mFeaturePolicy->SetDefaultOrigin(origin);
+    // The origin can change if 'src' and 'srcdoc' attributes change.
+    nsCOMPtr<nsIPrincipal> origin = GetFeaturePolicyDefaultOrigin();
+    MOZ_ASSERT(origin);
+    mFeaturePolicy->SetDefaultOrigin(origin);
 
-  nsAutoString allow;
-  GetAttr(nsGkAtoms::allow, allow);
+    nsAutoString allow;
+    GetAttr(nsGkAtoms::allow, allow);
 
-  if (!allow.IsEmpty()) {
-    // Set or reset the FeaturePolicy directives.
-    mFeaturePolicy->SetDeclaredPolicy(OwnerDoc(), allow, NodePrincipal(),
-                                      origin);
+    if (!allow.IsEmpty()) {
+      // Set or reset the FeaturePolicy directives.
+      mFeaturePolicy->SetDeclaredPolicy(OwnerDoc(), allow, NodePrincipal(),
+                                        origin);
+    }
+
+    mFeaturePolicy->InheritPolicy(OwnerDoc()->Policy());
   }
 
-  mFeaturePolicy->InheritPolicy(OwnerDoc()->Policy());
-
   if (AllowPaymentRequest()) {
     mFeaturePolicy->MaybeSetAllowedPolicy(NS_LITERAL_STRING("payment"));
   }
 
   if (AllowFullscreen()) {
     mFeaturePolicy->MaybeSetAllowedPolicy(NS_LITERAL_STRING("fullscreen"));
   }
-
-  // TODO: https://wicg.github.io/feature-policy/#process-feature-policy-attributes
-  // requires to check allowusermediarequest
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/html/HTMLIFrameElement.h
+++ b/dom/html/HTMLIFrameElement.h
@@ -218,17 +218,17 @@ protected:
                                           bool aNotify) override;
 
 private:
   static void MapAttributesIntoRule(const nsMappedAttributes* aAttributes,
                                     MappedDeclarations&);
 
   static const DOMTokenListSupportedToken sSupportedSandboxTokens[];
 
-  void RefreshFeaturePolicy();
+  void RefreshFeaturePolicy(bool aParseAllowAttribute);
 
   // If this iframe has a 'srcdoc' attribute, the document's origin will be
   // returned. Otherwise, if this iframe has a 'src' attribute, the origin will
   // be the parsing of its value as URL. If the URL is invalid, or 'src'
   // attribute doesn't exist, the origin will be the document's origin.
   already_AddRefed<nsIPrincipal>
   GetFeaturePolicyDefaultOrigin() const;
 
--- a/js/src/builtin/TestingFunctions.cpp
+++ b/js/src/builtin/TestingFunctions.cpp
@@ -211,16 +211,25 @@ GetBuildConfiguration(JSContext* cx, uns
     value = BooleanValue(true);
 #else
     value = BooleanValue(false);
 #endif
     if (!JS_SetProperty(cx, info, "arm-simulator", value)) {
         return false;
     }
 
+#ifdef JS_CODEGEN_ARM64
+    value = BooleanValue(true);
+#else
+    value = BooleanValue(false);
+#endif
+    if (!JS_SetProperty(cx, info, "arm64", value)) {
+        return false;
+    }
+
 #ifdef JS_SIMULATOR_ARM64
     value = BooleanValue(true);
 #else
     value = BooleanValue(false);
 #endif
     if (!JS_SetProperty(cx, info, "arm64-simulator", value)) {
         return false;
     }
--- a/js/src/jit-test/lib/jitopts.js
+++ b/js/src/jit-test/lib/jitopts.js
@@ -7,17 +7,18 @@ function jitTogglesMatch(opts) {
   var currentOpts = getJitCompilerOptions();
   for (var k in opts) {
     if (k.indexOf(".enable") > 0 && opts[k] != currentOpts[k])
       return false;
   }
 
   // ARM64 does not yet have an Ion code generator, so return false if
   // ion.enable is requested.
-  if (getBuildConfiguration()['arm64-simulator'] && opts['ion.enable'])
+  var conf = getBuildConfiguration();
+  if (conf['arm64'] && opts['ion.enable'])
     return false;
 
   return true;
 }
 
 // Run fn under a particular set of JIT options.
 function withJitOptions(opts, fn) {
   var oldOpts = getJitCompilerOptions();
--- a/js/src/vm/Iteration.cpp
+++ b/js/src/vm/Iteration.cpp
@@ -94,43 +94,38 @@ NativeIterator::trace(JSTracer* trc)
                       // Properties begin life non-null and never *become*
                       // null.  (Deletion-suppression will shift trailing
                       // properties over a deleted property in the properties
                       // array, but it doesn't null them out.)
                       TraceEdge(trc, &prop, "prop");
                   });
 }
 
-typedef HashSet<jsid, DefaultHasher<jsid>> IdSet;
+using IdSet = GCHashSet<jsid, DefaultHasher<jsid>>;
 
 template <bool CheckForDuplicates>
 static inline bool
 Enumerate(JSContext* cx, HandleObject pobj, jsid id,
-          bool enumerable, unsigned flags, Maybe<IdSet>& ht, AutoIdVector* props)
+          bool enumerable, unsigned flags, MutableHandle<IdSet> visited, AutoIdVector* props)
 {
     if (CheckForDuplicates) {
-        if (!ht) {
-            // Most of the time there are only a handful of entries.
-            ht.emplace(cx, 5);
-        }
-
         // If we've already seen this, we definitely won't add it.
-        IdSet::AddPtr p = ht->lookupForAdd(id);
+        IdSet::AddPtr p = visited.lookupForAdd(id);
         if (MOZ_UNLIKELY(!!p)) {
             return true;
         }
 
-        // It's not necessary to add properties to the hash table at the end of
+        // It's not necessary to add properties to the hash set at the end of
         // the prototype chain, but custom enumeration behaviors might return
         // duplicated properties, so always add in such cases.
         if (pobj->is<ProxyObject>() ||
             pobj->staticPrototype() ||
             pobj->getClass()->getNewEnumerate())
         {
-            if (!ht->add(p, id)) {
+            if (!visited.add(p, id)) {
                 return false;
             }
         }
     }
 
     if (!enumerable && !(flags & JSITER_HIDDEN)) {
         return true;
     }
@@ -142,18 +137,18 @@ Enumerate(JSContext* cx, HandleObject po
         return true;
     }
 
     return props->append(id);
 }
 
 template <bool CheckForDuplicates>
 static bool
-EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags, Maybe<IdSet>& ht,
-                         AutoIdVector* props)
+EnumerateExtraProperties(JSContext* cx, HandleObject obj, unsigned flags,
+                         MutableHandle<IdSet> visited, AutoIdVector* props)
 {
     MOZ_ASSERT(obj->getClass()->getNewEnumerate());
 
     AutoIdVector properties(cx);
     bool enumerableOnly = !(flags & JSITER_HIDDEN);
     if (!obj->getClass()->getNewEnumerate()(cx, obj, properties, enumerableOnly)) {
         return false;
     }
@@ -162,17 +157,17 @@ EnumerateExtraProperties(JSContext* cx, 
     for (size_t n = 0; n < properties.length(); n++) {
         id = properties[n];
 
         // The enumerate hook does not indicate whether the properties
         // it returns are enumerable or not. Since we already passed
         // `enumerableOnly` to the hook to filter out non-enumerable
         // properties, it doesn't really matter what we pass here.
         bool enumerable = true;
-        if (!Enumerate<CheckForDuplicates>(cx, obj, id, enumerable, flags, ht, props)) {
+        if (!Enumerate<CheckForDuplicates>(cx, obj, id, enumerable, flags, visited, props)) {
             return false;
         }
     }
 
     return true;
 }
 
 static bool
@@ -182,47 +177,48 @@ SortComparatorIntegerIds(jsid a, jsid b,
     MOZ_ALWAYS_TRUE(IdIsIndex(a, &indexA));
     MOZ_ALWAYS_TRUE(IdIsIndex(b, &indexB));
     *lessOrEqualp = (indexA <= indexB);
     return true;
 }
 
 template <bool CheckForDuplicates>
 static bool
-EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
-                          AutoIdVector* props, Handle<UnboxedPlainObject*> unboxed = nullptr)
+EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags,
+                          MutableHandle<IdSet> visited, AutoIdVector* props,
+                          Handle<UnboxedPlainObject*> unboxed = nullptr)
 {
     bool enumerateSymbols;
     if (flags & JSITER_SYMBOLSONLY) {
         enumerateSymbols = true;
     } else {
         /* Collect any dense elements from this object. */
         size_t firstElemIndex = props->length();
         size_t initlen = pobj->getDenseInitializedLength();
         const Value* vp = pobj->getDenseElements();
         bool hasHoles = false;
         for (size_t i = 0; i < initlen; ++i, ++vp) {
             if (vp->isMagic(JS_ELEMENTS_HOLE)) {
                 hasHoles = true;
             } else {
                 /* Dense arrays never get so large that i would not fit into an integer id. */
                 if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
-                                                   /* enumerable = */ true, flags, ht, props))
+                                                   /* enumerable = */ true, flags, visited, props))
                 {
                     return false;
                 }
             }
         }
 
         /* Collect any typed array or shared typed array elements from this object. */
         if (pobj->is<TypedArrayObject>()) {
             size_t len = pobj->as<TypedArrayObject>().length();
             for (size_t i = 0; i < len; i++) {
                 if (!Enumerate<CheckForDuplicates>(cx, pobj, INT_TO_JSID(i),
-                                                   /* enumerable = */ true, flags, ht, props))
+                                                   /* enumerable = */ true, flags, visited, props))
                 {
                     return false;
                 }
             }
         }
 
         // Collect any sparse elements from this object.
         bool isIndexed = pobj->isIndexed();
@@ -233,18 +229,18 @@ EnumerateNativeProperties(JSContext* cx,
                 firstElemIndex = props->length();
             }
 
             for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
                 Shape& shape = r.front();
                 jsid id = shape.propid();
                 uint32_t dummy;
                 if (IdIsIndex(id, &dummy)) {
-                    if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht,
-                                                       props))
+                    if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags,
+                                                       visited, props))
                     {
                         return false;
                     }
                 }
             }
 
             MOZ_ASSERT(firstElemIndex <= props->length());
 
@@ -263,17 +259,19 @@ EnumerateNativeProperties(JSContext* cx,
         }
 
         if (unboxed) {
             // If |unboxed| is set then |pobj| is the expando for an unboxed
             // plain object we are enumerating. Add the unboxed properties
             // themselves here since they are all property names that were
             // given to the object before any of the expando's properties.
             MOZ_ASSERT(pobj->is<UnboxedExpandoObject>());
-            if (!EnumerateExtraProperties<CheckForDuplicates>(cx, unboxed, flags, ht, props)) {
+            if (!EnumerateExtraProperties<CheckForDuplicates>(cx, unboxed, flags, visited,
+                                                              props))
+            {
                 return false;
             }
         }
 
         size_t initialLength = props->length();
 
         /* Collect all unique property names from this object's shape. */
         bool symbolsFound = false;
@@ -287,17 +285,19 @@ EnumerateNativeProperties(JSContext* cx,
                 continue;
             }
 
             uint32_t dummy;
             if (isIndexed && IdIsIndex(id, &dummy)) {
                 continue;
             }
 
-            if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht, props)) {
+            if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, visited,
+                                               props))
+            {
                 return false;
             }
         }
         ::Reverse(props->begin() + initialLength, props->end());
 
         enumerateSymbols = symbolsFound && (flags & JSITER_SYMBOLS);
     }
 
@@ -305,44 +305,44 @@ EnumerateNativeProperties(JSContext* cx,
         // Do a second pass to collect symbols. ES6 draft rev 25 (2014 May 22)
         // 9.1.12 requires that all symbols appear after all strings in the
         // result.
         size_t initialLength = props->length();
         for (Shape::Range<NoGC> r(pobj->lastProperty()); !r.empty(); r.popFront()) {
             Shape& shape = r.front();
             jsid id = shape.propid();
             if (JSID_IS_SYMBOL(id)) {
-                if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags, ht,
-                                                   props))
+                if (!Enumerate<CheckForDuplicates>(cx, pobj, id, shape.enumerable(), flags,
+                                                   visited, props))
                 {
                     return false;
                 }
             }
         }
         ::Reverse(props->begin() + initialLength, props->end());
     }
 
     return true;
 }
 
 static bool
-EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags, Maybe<IdSet>& ht,
-                          AutoIdVector* props, bool checkForDuplicates,
-                          Handle<UnboxedPlainObject*> unboxed = nullptr)
+EnumerateNativeProperties(JSContext* cx, HandleNativeObject pobj, unsigned flags,
+                          MutableHandle<IdSet> visited, AutoIdVector* props,
+                          bool checkForDuplicates, Handle<UnboxedPlainObject*> unboxed = nullptr)
 {
     if (checkForDuplicates) {
-        return EnumerateNativeProperties<true>(cx, pobj, flags, ht, props, unboxed);
+        return EnumerateNativeProperties<true>(cx, pobj, flags, visited, props, unboxed);
     }
-    return EnumerateNativeProperties<false>(cx, pobj, flags, ht, props, unboxed);
+    return EnumerateNativeProperties<false>(cx, pobj, flags, visited, props, unboxed);
 }
 
 template <bool CheckForDuplicates>
 static bool
-EnumerateProxyProperties(JSContext* cx, HandleObject pobj, unsigned flags, Maybe<IdSet>& ht,
-                         AutoIdVector* props)
+EnumerateProxyProperties(JSContext* cx, HandleObject pobj, unsigned flags,
+                         MutableHandle<IdSet> visited, AutoIdVector* props)
 {
     MOZ_ASSERT(pobj->is<ProxyObject>());
 
     AutoIdVector proxyProps(cx);
 
     if (flags & JSITER_HIDDEN || flags & JSITER_SYMBOLS) {
         // This gets all property keys, both strings and symbols. The call to
         // Enumerate in the loop below will filter out unwanted keys, per the
@@ -358,33 +358,33 @@ EnumerateProxyProperties(JSContext* cx, 
             // We need to filter, if the caller just wants enumerable symbols.
             if (!(flags & JSITER_HIDDEN)) {
                 if (!Proxy::getOwnPropertyDescriptor(cx, pobj, proxyProps[n], &desc)) {
                     return false;
                 }
                 enumerable = desc.enumerable();
             }
 
-            if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], enumerable, flags, ht,
+            if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], enumerable, flags, visited,
                                                props))
             {
                 return false;
             }
         }
 
         return true;
     }
 
     // Returns enumerable property names (no symbols).
     if (!Proxy::getOwnEnumerablePropertyKeys(cx, pobj, proxyProps)) {
         return false;
     }
 
     for (size_t n = 0, len = proxyProps.length(); n < len; n++) {
-        if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], true, flags, ht, props)) {
+        if (!Enumerate<CheckForDuplicates>(cx, pobj, proxyProps[n], true, flags, visited, props)) {
             return false;
         }
     }
 
     return true;
 }
 
 #ifdef JS_MORE_DETERMINISTIC
@@ -458,19 +458,17 @@ struct SortComparatorIds
     }
 };
 
 #endif /* JS_MORE_DETERMINISTIC */
 
 static bool
 Snapshot(JSContext* cx, HandleObject pobj_, unsigned flags, AutoIdVector* props)
 {
-    // We initialize |ht| lazily (in Enumerate()) because it ends up unused
-    // anywhere from 67--99.9% of the time.
-    Maybe<IdSet> ht;
+    Rooted<IdSet> visited(cx, IdSet(cx));
     RootedObject pobj(cx, pobj_);
 
     // Don't check for duplicates if we're only interested in own properties.
     // This does the right thing for most objects: native objects don't have
     // duplicate property ids and we allow the [[OwnPropertyKeys]] proxy trap to
     // return duplicates.
     //
     // The only special case is when the object has a newEnumerate hook: it
@@ -478,65 +476,65 @@ Snapshot(JSContext* cx, HandleObject pob
     // handled below.
     bool checkForDuplicates = !(flags & JSITER_OWNONLY);
 
     do {
         if (pobj->getClass()->getNewEnumerate()) {
             if (pobj->is<UnboxedPlainObject>() && pobj->as<UnboxedPlainObject>().maybeExpando()) {
                 // Special case unboxed objects with an expando object.
                 RootedNativeObject expando(cx, pobj->as<UnboxedPlainObject>().maybeExpando());
-                if (!EnumerateNativeProperties(cx, expando, flags, ht, props, checkForDuplicates,
-                                               pobj.as<UnboxedPlainObject>()))
+                if (!EnumerateNativeProperties(cx, expando, flags, &visited, props,
+                                               checkForDuplicates, pobj.as<UnboxedPlainObject>()))
                 {
                     return false;
                 }
             } else {
                 // The newEnumerate hook may return duplicates. Whitelist the
                 // unboxed object hooks because we know they are well-behaved.
                 if (!pobj->is<UnboxedPlainObject>()) {
                     checkForDuplicates = true;
                 }
 
                 if (checkForDuplicates) {
-                    if (!EnumerateExtraProperties<true>(cx, pobj, flags, ht, props)) {
+                    if (!EnumerateExtraProperties<true>(cx, pobj, flags, &visited, props)) {
                         return false;
                     }
                 } else {
-                    if (!EnumerateExtraProperties<false>(cx, pobj, flags, ht, props)) {
+                    if (!EnumerateExtraProperties<false>(cx, pobj, flags, &visited, props)) {
                         return false;
                     }
                 }
 
                 if (pobj->isNative()) {
-                    if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props,
-                                                   checkForDuplicates))
+                    if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, &visited,
+                                                   props, checkForDuplicates))
                     {
                         return false;
                     }
                 }
             }
         } else if (pobj->isNative()) {
             // Give the object a chance to resolve all lazy properties
             if (JSEnumerateOp enumerate = pobj->getClass()->getEnumerate()) {
                 if (!enumerate(cx, pobj.as<NativeObject>())) {
                     return false;
                 }
             }
-            if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, ht, props,
+            if (!EnumerateNativeProperties(cx, pobj.as<NativeObject>(), flags, &visited, props,
                                            checkForDuplicates))
             {
                 return false;
             }
         } else if (pobj->is<ProxyObject>()) {
             if (checkForDuplicates) {
-                if (!EnumerateProxyProperties<true>(cx, pobj, flags, ht, props)) {
+                if (!EnumerateProxyProperties<true>(cx, pobj, flags, &visited, props)) {
                     return false;
                 }
             } else {
-                if (!EnumerateProxyProperties<false>(cx, pobj, flags, ht, props)) {
+                if (!EnumerateProxyProperties<false>(cx, pobj, flags, &visited, props)) {
                     return false;
                 }
             }
         } else {
             MOZ_CRASH("non-native objects must have an enumerate op");
         }
 
         if (flags & JSITER_OWNONLY) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/aboutConfigPrefs.js
@@ -0,0 +1,46 @@
+/* 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/. */
+
+"use strict";
+
+/* global ExtensionAPI, ExtensionCommon */
+
+ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+this.aboutConfigPrefs = class extends ExtensionAPI {
+  getAPI(context) {
+    const EventManager = ExtensionCommon.EventManager;
+    const extensionIDBase = context.extension.id.split("@")[0];
+    const extensionPrefNameBase = `extensions.${extensionIDBase}.`;
+
+    return {
+      aboutConfigPrefs: {
+        onPrefChange: new EventManager({
+          context,
+          name: "aboutConfigPrefs.onUAOverridesPrefChange",
+          register: (fire, name) => {
+            const prefName = `${extensionPrefNameBase}${name}`;
+            const callback = () => {
+              fire.async(name).catch(() => {}); // ignore Message Manager disconnects
+            };
+            Services.prefs.addObserver(prefName, callback);
+            return () => {
+              Services.prefs.removeObserver(prefName, callback);
+            };
+          },
+        }).api(),
+        async getPref(name) {
+          try {
+            return Services.prefs.getBoolPref(`${extensionPrefNameBase}${name}`);
+          } catch (_) {
+            return undefined;
+          }
+        },
+        async setPref(name, value) {
+          Services.prefs.setBoolPref(`${extensionPrefNameBase}${name}`, value);
+        },
+      },
+    };
+  }
+};
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/aboutConfigPrefs.json
@@ -0,0 +1,53 @@
+[
+  {
+    "namespace": "aboutConfigPrefs",
+    "description": "experimental API extension to allow access to about:config preferences",
+    "events": [
+      {
+        "name": "onPrefChange",
+        "type": "function",
+        "parameters": [{
+          "name": "name",
+          "type": "string",
+          "description": "The preference which changed"
+        }],
+        "extraParameters": [{
+          "name": "name",
+          "type": "string",
+          "description": "The preference to monitor"
+        }]
+      }
+    ],
+    "functions": [
+      {
+        "name": "getPref",
+        "type": "function",
+        "description": "Get a preference's value",
+        "parameters": [{
+          "name": "name",
+          "type": "string",
+          "description": "The preference name"
+        }],
+        "async": true
+      },
+      {
+        "name": "setPref",
+        "type": "function",
+        "description": "Set a preference's value",
+        "parameters": [
+          {
+            "name": "name",
+            "type": "string",
+            "description": "The preference name"
+          },
+          {
+            "name": "value",
+            "type": "boolean",
+            "description": "The new value"
+          }
+        ],
+        "async": true
+      }
+    ]
+  }
+]
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/bootstrap.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-const PREF_BRANCH = "extensions.webcompat.";
-const PREF_DEFAULTS = {
-  perform_injections: true,
-  perform_ua_overrides: true,
-};
-
-const INJECTIONS_ENABLE_PREF_NAME = "extensions.webcompat.perform_injections";
-
-const BROWSER_STARTUP_FINISHED_TOPIC = "browser-delayed-startup-finished";
-
-const UA_OVERRIDES_INIT_TOPIC = "useragentoverrides-initialized";
-const UA_ENABLE_PREF_NAME = "extensions.webcompat.perform_ua_overrides";
-
-ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
-ChromeUtils.defineModuleGetter(this, "UAOverrides", "chrome://webcompat/content/data/ua_overrides.jsm");
-
-let overrider;
-let webextensionPort;
-
-function InjectionsEnablePrefObserver() {
-  let isEnabled = Services.prefs.getBoolPref(INJECTIONS_ENABLE_PREF_NAME);
-  webextensionPort.postMessage({
-    type: "injection-pref-changed",
-    prefState: isEnabled,
-  });
-}
-
-function UAEnablePrefObserver() {
-  let isEnabled = Services.prefs.getBoolPref(UA_ENABLE_PREF_NAME);
-  overrider.setShouldOverride(isEnabled);
-}
-
-function setDefaultPrefs() {
-  const branch = Services.prefs.getDefaultBranch(PREF_BRANCH);
-  for (const [key, val] of Object.entries(PREF_DEFAULTS)) {
-    // If someone beat us to setting a default, don't overwrite it.
-    if (branch.getPrefType(key) !== branch.PREF_INVALID) {
-      continue;
-    }
-
-    switch (typeof val) {
-      case "boolean":
-        branch.setBoolPref(key, val);
-        break;
-      case "number":
-        branch.setIntPref(key, val);
-        break;
-      case "string":
-        branch.setCharPref(key, val);
-        break;
-    }
-  }
-}
-
-this.install = function() {};
-this.uninstall = function() {};
-
-this.startup = function({webExtension}) {
-  setDefaultPrefs();
-
-  // Intentionally reset the preference on every browser restart to avoid site
-  // breakage by accidentally toggled preferences or by leaving it off after
-  // debugging a site.
-  Services.prefs.clearUserPref(INJECTIONS_ENABLE_PREF_NAME);
-  Services.prefs.addObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
-
-  Services.prefs.clearUserPref(UA_ENABLE_PREF_NAME);
-  Services.prefs.addObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
-
-  // Listen to the useragentoverrides-initialized notification we get and
-  // initialize our overrider there. This is done to avoid slowing down the
-  // apparent startup process, since we avoid loading anything before the first
-  // window is visible to the user. See bug 1371442 for details.
-  let uaStartupObserver = {
-    observe(aSubject, aTopic, aData) {
-      if (aTopic !== UA_OVERRIDES_INIT_TOPIC) {
-        return;
-      }
-
-      Services.obs.removeObserver(this, UA_OVERRIDES_INIT_TOPIC);
-      overrider = new UAOverrider(UAOverrides);
-      overrider.init();
-    },
-  };
-  Services.obs.addObserver(uaStartupObserver, UA_OVERRIDES_INIT_TOPIC);
-
-  // Observe browser-delayed-startup-finished and only initialize our embedded
-  // WebExtension after that. Otherwise, we'd try to initialize as soon as the
-  // browser starts up, which adds a heavy startup penalty.
-  let appStartupObserver = {
-    observe(aSubject, aTopic, aData) {
-      webExtension.startup().then((api) => {
-        api.browser.runtime.onConnect.addListener((port) => {
-          webextensionPort = port;
-        });
-
-        return Promise.resolve();
-      }).catch((ex) => {
-        console.error(ex);
-      });
-      Services.obs.removeObserver(this, BROWSER_STARTUP_FINISHED_TOPIC);
-    },
-  };
-  Services.obs.addObserver(appStartupObserver, BROWSER_STARTUP_FINISHED_TOPIC);
-};
-
-this.shutdown = function() {
-  Services.prefs.removeObserver(INJECTIONS_ENABLE_PREF_NAME, InjectionsEnablePrefObserver);
-  Services.prefs.removeObserver(UA_ENABLE_PREF_NAME, UAEnablePrefObserver);
-};
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/content/data/ua_overrides.jsm
+++ /dev/null
@@ -1,65 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/**
- * For detailed information on our policies, and a documention on this format
- * and its possibilites, please check the Mozilla-Wiki at
- *
- * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
- */
-const UAOverrides = [
-
-  /*
-   * This is a dummy override that applies a Chrome UA to a dummy site that
-   * blocks all browsers but Chrome.
-   *
-   * This was only put in place to allow QA to test this system addon on an
-   * actual site, since we were not able to find a proper override in time.
-   */
-  {
-    baseDomain: "schub.io",
-    applications: ["firefox", "fennec"],
-    uriMatcher: (uri) => uri.includes("webcompat-addon-testcases.schub.io"),
-    uaTransformer: (originalUA) => {
-      let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
-      return `${prefix} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36`;
-    },
-  },
-
-  /*
-   * Bug 1480710 - m.imgur.com - Build UA override
-   * WebCompat issue #13154 - https://webcompat.com/issues/13154
-   *
-   * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
-   * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
-   * receive the correct CSS and JS files.
-   */
-  {
-    baseDomain: "imgur.com",
-    applications: ["fennec"],
-    uriMatcher: (uri) => uri.includes("m.imgur.com"),
-    uaTransformer: (originalUA) => {
-      let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
-      return prefix + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36";
-    },
-  },
-
-  /*
-   * Bug 755590 - sites.google.com - top bar doesn't show up in Firefox for Android
-   *
-   * Google Sites does show a different top bar template based on the User Agent.
-   * For Fennec, this results in a broken top bar. Appending Chrome and Mobile Safari
-   * identifiers to the UA results in a correct rendering.
-   */
-  {
-    baseDomain: "google.com",
-    applications: ["fennec"],
-    uriMatcher: (uri) => uri.includes("sites.google.com"),
-    uaTransformer: (originalUA) => {
-      return originalUA + " Chrome/68.0.3440.85 Mobile Safari/537.366";
-    },
-  },
-];
-
-var EXPORTED_SYMBOLS = ["UAOverrides"]; /* exported UAOverrides */
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/content/lib/ua_overrider.jsm
+++ /dev/null
@@ -1,126 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-ChromeUtils.defineModuleGetter(this, "Services", "resource://gre/modules/Services.jsm");
-ChromeUtils.defineModuleGetter(this, "UserAgentOverrides", "resource://gre/modules/UserAgentOverrides.jsm");
-
-class UAOverrider {
-  constructor(overrides) {
-    this._overrides = {};
-    this._shouldOverride = true;
-
-    this.initOverrides(overrides);
-  }
-
-  initOverrides(overrides) {
-    // on xpcshell tests, there is no implementation for nsIXULAppInfo, so this
-    // might fail there. To have all of our test cases running at all times,
-    // assume they are on Desktop for now.
-    let currentApplication = "firefox";
-    try {
-      currentApplication = Services.appinfo.name.toLowerCase();
-    } catch (ex) {
-      console.warn("Getting appinfo.name failed, assuming we run on Desktop.", ex);
-    }
-
-    for (let override of overrides) {
-      // Firefox for Desktop is the default application for all overrides.
-      if (!override.applications) {
-        override.applications = ["firefox"];
-      }
-
-      // If the current application is not targeted by the override in question,
-      // we can skip adding the override to our checks entirely.
-      if (!override.applications.includes(currentApplication)) {
-        continue;
-      }
-
-      if (!this._overrides[override.baseDomain]) {
-        this._overrides[override.baseDomain] = [];
-      }
-
-      if (!override.uriMatcher) {
-        override.uriMatcher = () => true;
-      }
-
-      this._overrides[override.baseDomain].push(override);
-    }
-  }
-
-  /**
-   * Used for disabling overrides when the pref has been flipped to false.
-   *
-   * Since we no longer use our own event handlers, we check this bool in our
-   * override callback and simply return early if we are not supposed to do
-   * anything.
-   */
-  setShouldOverride(newState) {
-    this._shouldOverride = newState;
-  }
-
-  init() {
-    UserAgentOverrides.addComplexOverride(this.overrideCallback.bind(this));
-  }
-
-  overrideCallback(channel, defaultUA) {
-    if (!this._shouldOverride) {
-      return false;
-    }
-
-    let uaOverride = this.lookupUAOverride(channel.URI, defaultUA);
-    if (uaOverride) {
-      console.log("The user agent has been overridden for compatibility reasons.");
-      return uaOverride;
-    }
-
-    return false;
-  }
-
-  /**
-   * Try to use the eTLDService to get the base domain (will return example.com
-   * for http://foo.bar.example.com/foo/bar).
-   *
-   * However, the eTLDService is a bit picky and throws whenever we pass a
-   * blank host name or an IP into it, see bug 1337785. Since we do not plan on
-   * override UAs for such cases, we simply catch everything and return false.
-   */
-  getBaseDomainFromURI(uri) {
-    try {
-      return Services.eTLD.getBaseDomain(uri);
-    } catch (_) {
-      return false;
-    }
-  }
-
-  /**
-   * This function returns a User Agent based on the URI passed into. All
-   * override rules are defined in data/ua_overrides.jsm and the required format
-   * is explained there.
-   *
-   * Since it is expected and designed to have more than one override per base
-   * domain, we have to loop over this._overrides[baseDomain], which contains
-   * all available overrides.
-   *
-   * If the uriMatcher function returns true, the uaTransformer function gets
-   * called and its result will be used as the Use Agent for the current
-   * request.
-   *
-   * If there are more than one possible overrides, that is if two or more
-   * uriMatchers would return true, the first one gets applied.
-   */
-  lookupUAOverride(uri, defaultUA) {
-    let baseDomain = this.getBaseDomainFromURI(uri);
-    if (baseDomain && this._overrides[baseDomain]) {
-      for (let uaOverride of this._overrides[baseDomain]) {
-        if (uaOverride.uriMatcher(uri.specIgnoringRef)) {
-          return uaOverride.uaTransformer(defaultUA);
-        }
-      }
-    }
-
-    return false;
-  }
-}
-
-var EXPORTED_SYMBOLS = ["UAOverrider"]; /* exported UAOverrider */
rename from mobile/android/extensions/webcompat/webextension/background.js
rename to mobile/android/extensions/webcompat/injections.js
--- a/mobile/android/extensions/webcompat/webextension/background.js
+++ b/mobile/android/extensions/webcompat/injections.js
@@ -81,14 +81,22 @@ port.onMessage.addListener((message) => 
         registerContentScripts();
       } else {
         unregisterContentScripts();
       }
       break;
   }
 });
 
-/**
- * Note that we reset all preferences on extension startup, so the injections will
- * never be disabled when this loads up. Because of that, we can simply register
- * right away.
- */
-registerContentScripts();
+const INJECTION_PREF = "perform_injections";
+function checkInjectionPref() {
+  browser.aboutConfigPrefs.getPref(INJECTION_PREF).then(value => {
+    if (value === undefined) {
+      browser.aboutConfigPrefs.setPref(INJECTION_PREF, true);
+    } else if (value === false) {
+      unregisterContentScripts();
+    } else {
+      registerContentScripts();
+    }
+  });
+}
+browser.aboutConfigPrefs.onPrefChange.addListener(checkInjectionPref, INJECTION_PREF);
+checkInjectionPref();
rename from mobile/android/extensions/webcompat/webextension/injections/css/bug0000000-dummy-css-injection.css
rename to mobile/android/extensions/webcompat/injections/css/bug0000000-dummy-css-injection.css
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug0000000-dummy-js-injection.js
rename to mobile/android/extensions/webcompat/injections/js/bug0000000-dummy-js-injection.js
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
rename to mobile/android/extensions/webcompat/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug1457335-histography.io-ua-change.js
rename to mobile/android/extensions/webcompat/injections/js/bug1457335-histography.io-ua-change.js
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js
rename to mobile/android/extensions/webcompat/injections/js/bug1472075-bankofamerica.com-ua-change.js
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
rename to mobile/android/extensions/webcompat/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js
rename from mobile/android/extensions/webcompat/webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
rename to mobile/android/extensions/webcompat/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/install.rdf.in
+++ /dev/null
@@ -1,41 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-#filter substitution
-
-<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
-     xmlns:em="http://www.mozilla.org/2004/em-rdf#">
-
-  <Description about="urn:mozilla:install-manifest">
-    <em:id>webcompat@mozilla.org</em:id>
-    <em:version>2.0.1</em:version>
-    <em:type>2</em:type>
-    <em:bootstrap>true</em:bootstrap>
-    <em:multiprocessCompatible>true</em:multiprocessCompatible>
-    <em:hasEmbeddedWebExtension>true</em:hasEmbeddedWebExtension>
-
-    <!-- Firefox Desktop -->
-    <em:targetApplication>
-      <Description>
-        <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id>
-        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
-        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Firefox for Android -->
-    <em:targetApplication>
-      <Description>
-        <em:id>{aa3c5121-dab2-40e2-81ca-7ea25febc110}</em:id>
-        <em:minVersion>@MOZ_APP_VERSION@</em:minVersion>
-        <em:maxVersion>@MOZ_APP_MAXVERSION@</em:maxVersion>
-      </Description>
-    </em:targetApplication>
-
-    <!-- Front End MetaData -->
-    <em:name>Web Compat</em:name>
-    <em:description>Urgent post-release fixes for web compatibility.</em:description>
-  </Description>
-</RDF>
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/jar.mn
+++ /dev/null
@@ -1,3 +0,0 @@
-[features/webcompat@mozilla.org] chrome.jar:
-% content webcompat %content/
-  content/ (content/*)
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/manifest.json
@@ -0,0 +1,37 @@
+{
+  "manifest_version": 2,
+  "name": "Web Compat",
+  "description": "Urgent post-release fixes for web compatibility.",
+  "version": "3.0.0",
+
+  "applications": {
+    "gecko": {
+      "id": "webcompat@mozilla.org",
+      "strict_min_version": "59.0b5"
+    }
+  },
+
+  "experiment_apis": {
+    "aboutConfigPrefs": {
+      "schema": "aboutConfigPrefs.json",
+      "parent": {
+        "scopes": ["addon_parent"],
+        "script": "aboutConfigPrefs.js",
+        "paths": [["aboutConfigPrefs"]]
+      }
+    }
+  },
+
+  "permissions": [
+    "webRequest",
+    "webRequestBlocking",
+    "<all_urls>"
+  ],
+
+  "background": {
+    "scripts": [
+      "injections.js",
+      "ua_overrides.js"
+    ]
+  }
+}
--- a/mobile/android/extensions/webcompat/moz.build
+++ b/mobile/android/extensions/webcompat/moz.build
@@ -3,38 +3,30 @@
 # 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/.
 
 DEFINES['MOZ_APP_VERSION'] = CONFIG['MOZ_APP_VERSION']
 DEFINES['MOZ_APP_MAXVERSION'] = CONFIG['MOZ_APP_MAXVERSION']
 
 FINAL_TARGET_FILES.features['webcompat@mozilla.org'] += [
-  'bootstrap.js'
-]
-
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension'] += [
-  'webextension/background.js',
-  'webextension/manifest.json'
-]
-
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['css'] += [
-  'webextension/injections/css/bug0000000-dummy-css-injection.css'
+  'aboutConfigPrefs.js',
+  'aboutConfigPrefs.json',
+  'injections.js',
+  'manifest.json',
+  'ua_overrides.js'
 ]
 
-FINAL_TARGET_FILES.features['webcompat@mozilla.org']['webextension']['injections']['js'] += [
-  'webextension/injections/js/bug0000000-dummy-js-injection.js',
-  'webextension/injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js',
-  'webextension/injections/js/bug1457335-histography.io-ua-change.js',
-  'webextension/injections/js/bug1472075-bankofamerica.com-ua-change.js',
-  'webextension/injections/js/bug1472081-election.gov.np-window.sidebar-shim.js',
-  'webextension/injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js'
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['injections']['css'] += [
+  'injections/css/bug0000000-dummy-css-injection.css'
 ]
 
-FINAL_TARGET_PP_FILES.features['webcompat@mozilla.org'] += [
-  'install.rdf.in'
+FINAL_TARGET_FILES.features['webcompat@mozilla.org']['injections']['js'] += [
+  'injections/js/bug0000000-dummy-js-injection.js',
+  'injections/js/bug1452707-window.controllers-shim-ib.absa.co.za.js',
+  'injections/js/bug1457335-histography.io-ua-change.js',
+  'injections/js/bug1472075-bankofamerica.com-ua-change.js',
+  'injections/js/bug1472081-election.gov.np-window.sidebar-shim.js',
+  'injections/js/bug1482066-portalminasnet.com-window.sidebar-shim.js'
 ]
 
-BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
-JAR_MANIFESTS += ['jar.mn']
-
 with Files('**'):
   BUG_COMPONENT = ('Web Compatibility Tools', 'Go Faster')
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/test/.eslintrc.js
+++ /dev/null
@@ -1,7 +0,0 @@
-"use strict";
-
-module.exports = {
-  "extends": [
-    "plugin:mozilla/browser-test"
-  ]
-};
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/test/browser.ini
+++ /dev/null
@@ -1,4 +0,0 @@
-[DEFAULT]
-
-[browser_check_installed.js]
-[browser_overrider.js]
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/test/browser_check_installed.js
+++ /dev/null
@@ -1,15 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-add_task(async function installed() {
-  let addon = await AddonManager.getAddonByID("webcompat@mozilla.org");
-  isnot(addon, null, "Webcompat addon should exist");
-  is(addon.name, "Web Compat");
-  ok(addon.isCompatible, "Webcompat addon is compatible with Firefox");
-  ok(!addon.appDisabled, "Webcompat addon is not app disabled");
-  ok(addon.isActive, "Webcompat addon is active");
-  is(addon.type, "extension", "Webcompat addon is type extension");
-});
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/test/browser_overrider.js
+++ /dev/null
@@ -1,38 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-"use strict";
-
-ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
-ChromeUtils.defineModuleGetter(this, "UAOverrider", "chrome://webcompat/content/lib/ua_overrider.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "IOService", "@mozilla.org/network/io-service;1", "nsIIOService");
-
-function getnsIURI(uri) {
-  return IOService.newURI(uri, "utf-8");
-}
-
-add_task(function test() {
-  let overrider = new UAOverrider([
-    {
-      baseDomain: "example.org",
-      uaTransformer: () => "Test UA",
-    },
-  ]);
-
-  let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
-  is(finalUA, "Test UA", "Overrides the UA without a matcher function");
-});
-
-add_task(function test() {
-  let overrider = new UAOverrider([
-    {
-      baseDomain: "example.org",
-      uriMatcher: () => false,
-      uaTransformer: () => "Test UA",
-    },
-  ]);
-
-  let finalUA = overrider.lookupUAOverride(getnsIURI("http://www.example.org/foobar/"));
-  isnot(finalUA, "Test UA", "Does not override the UA with the matcher returning false");
-});
new file mode 100644
--- /dev/null
+++ b/mobile/android/extensions/webcompat/ua_overrides.js
@@ -0,0 +1,129 @@
+/**
+ * For detailed information on our policies, and a documention on this format
+ * and its possibilites, please check the Mozilla-Wiki at
+ *
+ * https://wiki.mozilla.org/Compatibility/Go_Faster_Addon/Override_Policies_and_Workflows#User_Agent_overrides
+ */
+const UAOverrides = {
+  universal: [
+    /*
+     * This is a dummy override that applies a Chrome UA to a dummy site that
+     * blocks all browsers but Chrome.
+     *
+     * This was only put in place to allow QA to test this system addon on an
+     * actual site, since we were not able to find a proper override in time.
+     */
+    {
+      matches: ["*://webcompat-addon-testcases.schub.io/*"],
+      uaTransformer: (originalUA) => {
+        let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+        return `${prefix} AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36`;
+      },
+    },
+  ],
+  desktop: [],
+  android: [
+    /*
+     * Bug 1480710 - m.imgur.com - Build UA override
+     * WebCompat issue #13154 - https://webcompat.com/issues/13154
+     *
+     * imgur returns a 404 for requests to CSS and JS file if requested with a Fennec
+     * User Agent. By removing the Fennec identifies and adding Chrome Mobile's, we
+     * receive the correct CSS and JS files.
+     */
+    {
+      matches: ["*://m.imgur.com/*"],
+      uaTransformer: (originalUA) => {
+        let prefix = originalUA.substr(0, originalUA.indexOf(")") + 1);
+        return prefix + " AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.85 Mobile Safari/537.36";
+      },
+    },
+
+    /*
+     * Bug 755590 - sites.google.com - top bar doesn't show up in Firefox for Android
+     *
+     * Google Sites does show a different top bar template based on the User Agent.
+     * For Fennec, this results in a broken top bar. Appending Chrome and Mobile Safari
+     * identifiers to the UA results in a correct rendering.
+     */
+    {
+      matches: ["*://sites.google.com/*"],
+      uaTransformer: (originalUA) => {
+        return originalUA + " Chrome/68.0.3440.85 Mobile Safari/537.366";
+      },
+    },
+
+    /*
+     * Bug 945963 - tieba.baidu.com serves simplified mobile content to Firefox Android
+     * WebCompat issue #18455 - https://webcompat.com/issues/18455
+     *
+     * tieba.baidu.com and tiebac.baidu.com serve a heavily simplified and less functional
+     * mobile experience to Firefox for Android users. Adding the AppleWebKit indicator
+     * to the User Agent gets us the same experience.
+     */
+    {
+      matches: ["*://tieba.baidu.com/*", "*://tiebac.baidu.com/*"],
+      uaTransformer: (originalUA) => {
+        return originalUA + " AppleWebKit/537.36 (KHTML, like Gecko)";
+      },
+    },
+  ],
+};
+
+/* globals browser */
+
+let activeListeners = [];
+function buildAndRegisterListener(matches, transformer) {
+  let listener = (details) => {
+    for (var header of details.requestHeaders) {
+      if (header.name.toLowerCase() === "user-agent") {
+        header.value = transformer(header.value);
+      }
+    }
+    return {requestHeaders: details.requestHeaders};
+  };
+
+  browser.webRequest.onBeforeSendHeaders.addListener(
+    listener,
+    {urls: matches},
+    ["blocking", "requestHeaders"]
+  );
+
+  activeListeners.push(listener);
+}
+
+async function registerUAOverrides() {
+  let platform = "desktop";
+  let platformInfo = await browser.runtime.getPlatformInfo();
+  if (platformInfo.os == "android") {
+    platform = "android";
+  }
+
+  let targetOverrides = UAOverrides.universal.concat(UAOverrides[platform]);
+  targetOverrides.forEach((override) => {
+    buildAndRegisterListener(override.matches, override.uaTransformer);
+  });
+}
+
+function unregisterUAOverrides() {
+  activeListeners.forEach((listener) => {
+    browser.webRequest.onBeforeSendHeaders.removeListener(listener);
+  });
+
+  activeListeners = [];
+}
+
+const OVERRIDE_PREF = "perform_ua_overrides";
+function checkOverridePref() {
+  browser.aboutConfigPrefs.getPref(OVERRIDE_PREF).then(value => {
+    if (value === undefined) {
+      browser.aboutConfigPrefs.setPref(OVERRIDE_PREF, true);
+    } else if (value === false) {
+      unregisterUAOverrides();
+    } else {
+      registerUAOverrides();
+    }
+  });
+}
+browser.aboutConfigPrefs.onPrefChange.addListener(checkOverridePref, OVERRIDE_PREF);
+checkOverridePref();
deleted file mode 100644
--- a/mobile/android/extensions/webcompat/webextension/manifest.json
+++ /dev/null
@@ -1,23 +0,0 @@
-{
-  "manifest_version": 2,
-  "name": "Web Compat",
-  "description": "Urgent post-release fixes for web compatibility.",
-  "version": "2.0",
-
-  "applications": {
-    "gecko": {
-      "id": "webcompat@mozilla.org",
-      "strict_min_version": "59.0b5"
-    }
-  },
-
-  "permissions": [
-    "<all_urls>"
-  ],
-
-  "background": {
-    "scripts": [
-      "background.js"
-    ]
-  }
-}
--- a/mobile/android/tests/browser/robocop/robocop_testharness.js
+++ b/mobile/android/tests/browser/robocop/robocop_testharness.js
@@ -1,13 +1,20 @@
 // -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+if (!("SpecialPowers" in window)) {
+  dump("Robocop robocop_testharness.js found SpecialPowers unavailable: reloading...\n");
+  setTimeout(() => {
+    window.location.reload();
+  }, 1000);
+}
+
 function sendMessageToJava(message) {
   SpecialPowers.Services.androidBridge.dispatch(message.type, message);
 }
 
 function _evalURI(uri, sandbox) {
   // We explicitly allow Cross-Origin requests, since it is useful for
   // testing, but we allow relative URLs by maintaining our baseURI.
   let req = new XMLHttpRequest();
--- a/uriloader/exthandler/tests/mochitest/browser.ini
+++ b/uriloader/exthandler/tests/mochitest/browser.ini
@@ -1,13 +1,13 @@
 [DEFAULT]
 head = head.js
 support-files =
   download_page.html
   download.bin
   protocolHandler.html
 
 [browser_auto_close_window.js]
-skip-if = !e10s # test relies on e10s behavior
+skip-if = true # Bug 1371509
 [browser_download_always_ask_preferred_app.js]
 [browser_download_privatebrowsing.js]
 [browser_remember_download_option.js]
 [browser_web_protocol_handlers.js]