Merge mozilla-central to autoland. a=merge CLOSED TREE
authorBogdan Tara <btara@mozilla.com>
Sun, 21 Oct 2018 00:58:03 +0300
changeset 500760 c9d8f0d61fa151779f3e129c98550d94c5e8656e
parent 500748 2d8a1d405333036b4adce643e4893f2da29957be (current diff)
parent 500759 3872ebc4799bf8e606006f36444e021dddad0825 (diff)
child 500766 80361808cf668648a20a3da2bdafb62ffbe04dd4
push id1864
push userffxbld-merge
push dateMon, 03 Dec 2018 15:51:40 +0000
treeherdermozilla-release@f040763d99ad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone64.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
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]