Merge inbound to mozila-central. a=merge
authorMargareta Eliza Balazs <ebalazs@mozilla.com>
Fri, 06 Jul 2018 12:47:20 +0300
changeset 425319 fa376bf17cc95539f5e37186977d760296fb5093
parent 425287 80d8f267abd8429bd213e109aa2c0724320e1110 (current diff)
parent 425318 1f347e524288a7d5944be68351d5011b0c55ca0d (diff)
child 425320 4a12e4b0eed6b38bfe71c2776e707d2f77edb492
child 425377 2bce5c230cac53c0889a85adb2eeb1623194c2e6
push id66025
push userebalazs@mozilla.com
push dateFri, 06 Jul 2018 09:54:42 +0000
treeherderautoland@4a12e4b0eed6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone63.0a1
first release with
nightly linux32
fa376bf17cc9 / 63.0a1 / 20180706100210 / files
nightly linux64
fa376bf17cc9 / 63.0a1 / 20180706100210 / files
nightly mac
fa376bf17cc9 / 63.0a1 / 20180706100210 / files
nightly win32
fa376bf17cc9 / 63.0a1 / 20180706100210 / files
nightly win64
fa376bf17cc9 / 63.0a1 / 20180706100210 / 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 mozila-central. a=merge
browser/base/content/test/performance/browser_startup_content.js
--- a/accessible/generic/HyperTextAccessible.cpp
+++ b/accessible/generic/HyperTextAccessible.cpp
@@ -1647,17 +1647,17 @@ HyperTextAccessible::SelectionBoundsAt(i
 bool
 HyperTextAccessible::SetSelectionBoundsAt(int32_t aSelectionNum,
                                           int32_t aStartOffset,
                                           int32_t aEndOffset)
 {
   index_t startOffset = ConvertMagicOffset(aStartOffset);
   index_t endOffset = ConvertMagicOffset(aEndOffset);
   if (!startOffset.IsValid() || !endOffset.IsValid() ||
-      startOffset > endOffset || endOffset > CharacterCount()) {
+      std::max(startOffset, endOffset) > CharacterCount()) {
     NS_ERROR("Wrong in offset");
     return false;
   }
 
   dom::Selection* domSel = DOMSelection();
   if (!domSel)
     return false;
 
@@ -1666,31 +1666,37 @@ HyperTextAccessible::SetSelectionBoundsA
   if (aSelectionNum == static_cast<int32_t>(rangeCount))
     range = new nsRange(mContent);
   else
     range = domSel->GetRangeAt(aSelectionNum);
 
   if (!range)
     return false;
 
-  if (!OffsetsToDOMRange(startOffset, endOffset, range))
+  if (!OffsetsToDOMRange(std::min(startOffset, endOffset),
+                         std::max(startOffset, endOffset), range))
     return false;
 
-  // If new range was created then add it, otherwise notify selection listeners
-  // that existing selection range was changed.
-  if (aSelectionNum == static_cast<int32_t>(rangeCount)) {
-    IgnoredErrorResult err;
-    domSel->AddRange(*range, err);
-    return !err.Failed();
+  // If this is not a new range, notify selection listeners that the existing
+  // selection range has changed. Otherwise, just add the new range.
+  if (aSelectionNum != static_cast<int32_t>(rangeCount)) {
+    domSel->RemoveRange(*range, IgnoreErrors());
   }
 
-  domSel->RemoveRange(*range, IgnoreErrors());
   IgnoredErrorResult err;
   domSel->AddRange(*range, err);
-  return !err.Failed();
+
+  if (!err.Failed()) {
+    // Changing the direction of the selection assures that the caret
+    // will be at the logical end of the selection.
+    domSel->SetDirection(startOffset < endOffset ? eDirNext : eDirPrevious);
+    return true;
+  }
+
+  return false;
 }
 
 bool
 HyperTextAccessible::RemoveFromSelection(int32_t aSelectionNum)
 {
   dom::Selection* domSel = DOMSelection();
   if (!domSel)
     return false;
--- a/accessible/interfaces/nsIAccessibleText.idl
+++ b/accessible/interfaces/nsIAccessibleText.idl
@@ -144,17 +144,19 @@ interface nsIAccessibleText : nsISupport
   long getOffsetAtPoint (in long x, in long y,
                          in unsigned long coordType);
 
   void getSelectionBounds (in long selectionNum,
                            out long startOffset,
                            out long endOffset);
 
   /**
-   * Set the bounds for the given selection range
+   * Set the bounds for the given selection range.
+   * A reverse range where the start offset is larger than the end offset is
+   * acceptable. The caretOffset will be set to the endOffset argument.
    */
   void setSelectionBounds (in long selectionNum,
                            in long startOffset,
                            in long endOffset);
 
   void addSelection (in long startOffset, in long endOffset);
 
   void removeSelection (in long selectionNum);
--- a/accessible/tests/mochitest/textselection/test_general.html
+++ b/accessible/tests/mochitest/textselection/test_general.html
@@ -13,83 +13,103 @@
           src="../common.js"></script>
   <script type="application/javascript"
           src="../events.js"></script>
 
   <script type="application/javascript">
     /**
      * Invokers
      */
-    function addSelection(aID, aStartOffset, aEndOffset) {
+    function addSelections(aID, aSelections) {
       this.hyperTextNode = getNode(aID);
       this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
+      this.initialSelectionCount = this.hyperText.selectionCount;
 
+      // Multiple selection changes will be coalesced, so just listen for one.
       this.eventSeq = [
         new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
       ];
 
       this.invoke = function addSelection_invoke() {
-        this.hyperText.addSelection(aStartOffset, aEndOffset);
+        for (let [startOffset, endOffset] of aSelections) {
+          this.hyperText.addSelection(startOffset, endOffset);
+        }
       };
 
       this.finalCheck = function addSelection_finalCheck() {
-        is(this.hyperText.selectionCount, 1,
+        is(this.hyperText.selectionCount,
+           aSelections.length + this.initialSelectionCount,
            "addSelection: Wrong selection count for " + aID);
-        var startOffset = {}, endOffset = {};
-        this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+
+        for (let i in aSelections) {
+          let [expectedStart, expectedEnd] = aSelections[i];
+          let startOffset = {}, endOffset = {};
+          this.hyperText.getSelectionBounds(this.initialSelectionCount + i,
+            startOffset, endOffset);
 
-        is(startOffset.value, aStartOffset,
-           "addSelection: Wrong start offset for " + aID);
-        is(endOffset.value, aEndOffset,
-           "addSelection: Wrong end offset for " + aID);
+          is(startOffset.value, Math.min(expectedStart, expectedEnd),
+             "addSelection: Wrong start offset for " + aID);
+          is(endOffset.value, Math.max(expectedStart, expectedEnd),
+             "addSelection: Wrong end offset for " + aID);
+
+          if (i == this.hyperText.selectionCount - 1) {
+            is(this.hyperText.caretOffset, expectedEnd,
+               "addSelection: caretOffset not at selection end for " + aID);
+          }
+        }
       };
 
       this.getID = function addSelection_getID() {
         return "nsIAccessibleText::addSelection test for " + aID;
       };
     }
 
-    function changeSelection(aID, aStartOffset, aEndOffset) {
+    function changeSelection(aID, aIndex, aSelection) {
+      let [startOffset, endOffset] = aSelection;
       this.hyperTextNode = getNode(aID);
       this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
 
       this.eventSeq = [
         new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, aID)
       ];
 
       this.invoke = function changeSelection_invoke() {
-        this.hyperText.setSelectionBounds(0, aStartOffset, aEndOffset);
+        this.hyperText.setSelectionBounds(aIndex, startOffset, endOffset);
       };
 
       this.finalCheck = function changeSelection_finalCheck() {
-        is(this.hyperText.selectionCount, 1,
-           "setSelectionBounds: Wrong selection count for " + aID);
-        var startOffset = {}, endOffset = {};
-        this.hyperText.getSelectionBounds(0, startOffset, endOffset);
+        var start = {}, end = {};
+        this.hyperText.getSelectionBounds(aIndex, start, end);
 
-        is(startOffset.value, aStartOffset,
+        is(start.value, Math.min(startOffset, endOffset),
            "setSelectionBounds: Wrong start offset for " + aID);
-        is(endOffset.value, aEndOffset,
+        is(end.value, Math.max(startOffset, endOffset),
            "setSelectionBounds: Wrong end offset for " + aID);
+
+        is(this.hyperText.caretOffset, endOffset,
+           "setSelectionBounds: caretOffset not at selection end for " + aID);
       };
 
       this.getID = function changeSelection_getID() {
         return "nsIAccessibleText::setSelectionBounds test for " + aID;
       };
     }
 
-    function removeSelection(aID) {
+    function removeSelections(aID) {
       this.hyperText = getAccessible(aID, [ nsIAccessibleText ]);
 
       this.eventSeq = [
         new invokerChecker(EVENT_TEXT_SELECTION_CHANGED, document)
       ];
 
       this.invoke = function removeSelection_invoke() {
-        this.hyperText.removeSelection(0);
+        let selectionCount = this.hyperText.selectionCount;
+        for (let i = 0; i < selectionCount; i++) {
+          this.hyperText.removeSelection(0);
+        }
       };
 
       this.finalCheck = function removeSelection_finalCheck() {
         is(this.hyperText.selectionCount, 0,
            "removeSelection: Wrong selection count for " + aID);
       };
 
       this.getID = function removeSelection_getID() {
@@ -149,25 +169,32 @@
      */
 
     // gA11yEventDumpToConsole = true; // debug stuff
 
     var gQueue = null;
     function doTests() {
       gQueue = new eventQueue();
 
-      gQueue.push(new addSelection("paragraph", 1, 3));
-      gQueue.push(new changeSelection("paragraph", 2, 4));
-      gQueue.push(new removeSelection("paragraph"));
+      gQueue.push(new addSelections("paragraph", [[1, 3], [6, 10]]));
+      gQueue.push(new changeSelection("paragraph", 0, [2, 4]));
+      gQueue.push(new removeSelections("paragraph"));
+
+      // reverse selection
+      gQueue.push(new addSelections("paragraph", [[1, 3], [10, 6]]));
+      gQueue.push(new removeSelections("paragraph"));
 
       gQueue.push(new synthFocus("textbox", onfocusEventSeq("textbox")));
-      gQueue.push(new changeSelection("textbox", 1, 3));
+      gQueue.push(new changeSelection("textbox", 0, [1, 3]));
+
+      // reverse selection
+      gQueue.push(new changeSelection("textbox", 0, [3, 1]));
 
       gQueue.push(new synthFocus("textarea", onfocusEventSeq("textarea")));
-      gQueue.push(new changeSelection("textarea", 1, 3));
+      gQueue.push(new changeSelection("textarea", 0, [1, 3]));
 
       gQueue.push(new changeDOMSelection("c1", "c1_span1", 0, "c1_span2", 0,
                                          [["c1", 2, 2]]));
       gQueue.push(new changeDOMSelection("c2", "c2", 0, "c2_div2", 1,
                                          [["c2", 0, 3], ["c2_div2", 0, 2]]));
       gQueue.invoke(); // Will call SimpleTest.finish();
     }
 
@@ -188,16 +215,16 @@
      title="no text selection changed event when selection is removed">
     Bug 688124
   </a>
   <p id="display"></p>
   <div id="content" style="display: none"></div>
   <pre id="test">
   </pre>
 
-  <p id="paragraph">hello</p>
+  <p id="paragraph">hello world</p>
   <input id="textbox" value="hello"/>
   <textarea id="textarea">hello</textarea>
   <div id="c1">hi<span id="c1_span1"></span><span id="c1_span2"></span>hi</div>
   <div id="c2">hi<div id="c2_div2">hi</div></div>
 
 </body>
 </html>
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -58,16 +58,19 @@ static const RedirEntry kRedirMap[] = {
   { "tabcrashed", "chrome://browser/content/aboutTabCrashed.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
   { "feeds", "chrome://browser/content/feeds/subscribe.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::ALLOW_SCRIPT |
     nsIAboutModule::HIDE_FROM_ABOUTABOUT },
+  { "policies", "chrome://browser/content/policies/aboutPolicies.xhtml",
+    nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
+    nsIAboutModule::ALLOW_SCRIPT },
   { "privatebrowsing", "chrome://browser/content/aboutPrivateBrowsing.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::URI_MUST_LOAD_IN_CHILD |
     nsIAboutModule::ALLOW_SCRIPT },
   { "rights",
     "chrome://global/content/aboutRights.xhtml",
     nsIAboutModule::URI_SAFE_FOR_UNTRUSTED_CONTENT |
     nsIAboutModule::MAKE_LINKABLE |
--- a/browser/components/build/nsModule.cpp
+++ b/browser/components/build/nsModule.cpp
@@ -97,16 +97,17 @@ static const mozilla::Module::ContractID
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "home", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "newtab", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "library", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "preferences", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "downloads", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "reader", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "restartrequired", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
     { NS_ABOUT_MODULE_CONTRACTID_PREFIX "welcome", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
+    { NS_ABOUT_MODULE_CONTRACTID_PREFIX "policies", &kNS_BROWSER_ABOUT_REDIRECTOR_CID },
 #if defined(XP_WIN)
     { NS_IEHISTORYENUMERATOR_CONTRACTID, &kNS_WINIEHISTORYENUMERATOR_CID },
 #elif defined(XP_MACOSX)
     { NS_SHELLSERVICE_CONTRACTID, &kNS_SHELLSERVICE_CID },
 #endif
     { nullptr }
 };
 
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.css
@@ -0,0 +1,3 @@
+/* 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/. */
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.js
@@ -0,0 +1,4 @@
+/* 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/. */
+
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/content/aboutPolicies.xhtml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+# 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/.
+-->
+<!DOCTYPE html>
+
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+  	<link rel="stylesheet" href="chrome://browser/content/policies/aboutPolicies.css" type="text/css" />
+    <script type="application/javascript" src="chrome://browser/content/policies/aboutPolicies.js" />
+  </head>
+
+  <body>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/components/enterprisepolicies/jar.mn
@@ -0,0 +1,8 @@
+# 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/.
+
+browser.jar:
+    content/browser/policies/aboutPolicies.css              (content/aboutPolicies.css)
+    content/browser/policies/aboutPolicies.xhtml            (content/aboutPolicies.xhtml)
+    content/browser/policies/aboutPolicies.js               (content/aboutPolicies.js)
--- a/browser/components/enterprisepolicies/moz.build
+++ b/browser/components/enterprisepolicies/moz.build
@@ -27,8 +27,10 @@ EXTRA_JS_MODULES.policies += [
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
     EXTRA_JS_MODULES.policies += [
         'WindowsGPOParser.jsm',
 ]
 
 FINAL_LIBRARY = 'browsercomps'
+
+JAR_MANIFESTS += ['jar.mn']
--- a/browser/components/extensions/parent/ext-browser.js
+++ b/browser/components/extensions/parent/ext-browser.js
@@ -29,17 +29,17 @@ let windowTracker;
 const getSender = (extension, target, sender) => {
   let tabId;
   if ("tabId" in sender) {
     // The message came from a privileged extension page running in a tab. In
     // that case, it should include a tabId property (which is filled in by the
     // page-open listener below).
     tabId = sender.tabId;
     delete sender.tabId;
-  } else if (ExtensionCommon.instanceOf(target, "XULElement") ||
+  } else if (ExtensionCommon.instanceOf(target, "XULFrameElement") ||
              ExtensionCommon.instanceOf(target, "HTMLIFrameElement")) {
     tabId = tabTracker.getBrowserData(target).tabId;
   }
 
   if (tabId) {
     let tab = extension.tabManager.get(tabId, null);
     if (tab) {
       sender.tab = tab.convert();
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -749,20 +749,16 @@ FragmentOrElement::nsDOMSlots::SizeOfInc
   // - mBindingParent: because it is some ancestor element.
   return n;
 }
 
 FragmentOrElement::nsExtendedDOMSlots::nsExtendedDOMSlots() = default;
 
 FragmentOrElement::nsExtendedDOMSlots::~nsExtendedDOMSlots()
 {
-  RefPtr<nsFrameLoader> frameLoader = do_QueryObject(mFrameLoaderOrOpener);
-  if (frameLoader) {
-    frameLoader->Destroy();
-  }
 }
 
 void
 FragmentOrElement::nsExtendedDOMSlots::UnlinkExtendedSlots()
 {
   nsIContent::nsExtendedContentSlots::UnlinkExtendedSlots();
 
   // Don't clear mXBLBinding, it'll be done in
@@ -770,21 +766,16 @@ FragmentOrElement::nsExtendedDOMSlots::U
   mSMILOverrideStyle = nullptr;
   mControllers = nullptr;
   mLabelsList = nullptr;
   mShadowRoot = nullptr;
   if (mCustomElementData) {
     mCustomElementData->Unlink();
     mCustomElementData = nullptr;
   }
-  RefPtr<nsFrameLoader> frameLoader = do_QueryObject(mFrameLoaderOrOpener);
-  if (frameLoader) {
-    frameLoader->Destroy();
-  }
-  mFrameLoaderOrOpener = nullptr;
 }
 
 void
 FragmentOrElement::nsExtendedDOMSlots::TraverseExtendedSlots(nsCycleCollectionTraversalCallback& aCb)
 {
   nsIContent::nsExtendedContentSlots::TraverseExtendedSlots(aCb);
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mSMILOverrideStyle");
@@ -801,19 +792,16 @@ FragmentOrElement::nsExtendedDOMSlots::T
 
   NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mXBLBinding");
   aCb.NoteNativeChild(mXBLBinding,
                      NS_CYCLE_COLLECTION_PARTICIPANT(nsXBLBinding));
 
   if (mCustomElementData) {
     mCustomElementData->Traverse(aCb);
   }
-
-  NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(aCb, "mExtendedSlots->mFrameLoaderOrOpener");
-  aCb.NoteXPCOMChild(mFrameLoaderOrOpener);
 }
 
 FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
   : nsIContent(aNodeInfo)
 {
 }
 
 FragmentOrElement::FragmentOrElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
--- a/dom/base/FragmentOrElement.h
+++ b/dom/base/FragmentOrElement.h
@@ -210,22 +210,16 @@ public:
      * XBL binding installed on the element.
      */
     RefPtr<nsXBLBinding> mXBLBinding;
 
     /**
      * Web components custom element data.
      */
     RefPtr<CustomElementData> mCustomElementData;
-
-    /**
-     * For XUL to hold either frameloader or opener.
-     */
-    nsCOMPtr<nsISupports> mFrameLoaderOrOpener;
-
   };
 
   class nsDOMSlots : public nsIContent::nsContentSlots
   {
   public:
     nsDOMSlots();
     ~nsDOMSlots();
 
--- a/dom/base/nsObjectLoadingContent.h
+++ b/dom/base/nsObjectLoadingContent.h
@@ -26,24 +26,24 @@
 #include "nsIFrameLoaderOwner.h"
 
 class nsAsyncInstantiateEvent;
 class nsStopPluginRunnable;
 class AutoSetInstantiatingToFalse;
 class nsIPrincipal;
 class nsFrameLoader;
 class nsPluginFrame;
-class nsXULElement;
 class nsPluginInstanceOwner;
 
 namespace mozilla {
 namespace dom {
 template<typename T> class Sequence;
 struct MozPluginParameter;
 class HTMLIFrameElement;
+class XULFrameElement;
 } // namespace dom
 } // namespace mozilla
 
 class nsObjectLoadingContent : public nsImageLoadingContent
                              , public nsIStreamListener
                              , public nsIFrameLoaderOwner
                              , public nsIObjectLoadingContent
                              , public nsIChannelEventSink
@@ -234,17 +234,17 @@ class nsObjectLoadingContent : public ns
     {
       aRv = SkipFakePlugins();
     }
     void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
                           mozilla::ErrorResult& aRv)
     {
       aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     }
-    void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+    void SwapFrameLoaders(mozilla::dom::XULFrameElement& aOtherLoaderOwner,
                           mozilla::ErrorResult& aRv)
     {
       aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     }
     void LegacyCall(JSContext* aCx, JS::Handle<JS::Value> aThisVal,
                     const mozilla::dom::Sequence<JS::Value>& aArguments,
                     JS::MutableHandle<JS::Value> aRetval,
                     mozilla::ErrorResult& aRv);
--- a/dom/base/test/chrome/file_bug549682.xul
+++ b/dom/base/test/chrome/file_bug549682.xul
@@ -138,17 +138,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function run() {
     var localmm = document.getElementById('ifr').messageManager;
 
     var wn = document.getElementById('ifr').contentWindow
       .getInterface(Ci.nsIWebNavigation);
     ok(wn, "Should have webnavigation");
-    var cfmm = wn.getInterface(Ci.nsIContentFrameMessageManager);
+    var cfmm = wn.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIContentFrameMessageManager);
     ok(cfmm, "Should have content messageManager");
 
     var didGetSyncMessage = false;
     function syncContinueTestFn() {
       didGetSyncMessage = true;
     }
     localmm.addMessageListener("syncContinueTest", syncContinueTestFn);
     cfmm.sendSyncMessage("syncContinueTest", {});
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -43,16 +43,17 @@
 #include "mozilla/dom/DOMException.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/HTMLObjectElement.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/HTMLEmbedElement.h"
 #include "mozilla/dom/HTMLElementBinding.h"
 #include "mozilla/dom/HTMLEmbedElementBinding.h"
 #include "mozilla/dom/XULElementBinding.h"
+#include "mozilla/dom/XULFrameElementBinding.h"
 #include "mozilla/dom/XULPopupElementBinding.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ResolveSystemBinding.h"
 #include "mozilla/dom/WebIDLGlobalNameHash.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/WorkerScope.h"
 #include "mozilla/dom/XrayExpandoClass.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
@@ -3863,16 +3864,20 @@ HTMLConstructor(JSContext* aCx, unsigned
       // function should be the localname's element interface.
       cb = sConstructorGetterCallback[tag];
     } else { // kNameSpaceID_XUL
       if (definition->mLocalName == nsGkAtoms::menupopup ||
           definition->mLocalName == nsGkAtoms::popup ||
           definition->mLocalName == nsGkAtoms::panel ||
           definition->mLocalName == nsGkAtoms::tooltip) {
         cb = XULPopupElement_Binding::GetConstructorObject;
+      } else if (definition->mLocalName == nsGkAtoms::iframe ||
+                 definition->mLocalName == nsGkAtoms::browser ||
+                 definition->mLocalName == nsGkAtoms::editor) {
+        cb = XULFrameElement_Binding::GetConstructorObject;
       } else {
         cb = XULElement_Binding::GetConstructorObject;
       }
     }
 
     if (!cb) {
       return ThrowErrorMessage(aCx, MSG_ILLEGAL_CONSTRUCTOR);
     }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1829,16 +1829,17 @@ addExternalIface('nsIPrintSettings', nat
                  notflattened=True)
 addExternalIface('nsISelectionListener', nativeType='nsISelectionListener')
 addExternalIface('nsIStreamListener', nativeType='nsIStreamListener', notflattened=True)
 addExternalIface('nsITransportProvider', nativeType='nsITransportProvider')
 addExternalIface('nsITreeSelection', nativeType='nsITreeSelection',
                  notflattened=True)
 addExternalIface('nsISupports', nativeType='nsISupports')
 addExternalIface('nsIDocShell', nativeType='nsIDocShell', notflattened=True)
+addExternalIface('nsIWebNavigation', nativeType='nsIWebNavigation', notflattened=True)
 addExternalIface('nsIEditor', nativeType='nsIEditor', notflattened=True)
 addExternalIface('nsIVariant', nativeType='nsIVariant', notflattened=True)
 addExternalIface('nsIWebBrowserPersistDocumentReceiver',
                  nativeType='nsIWebBrowserPersistDocumentReceiver',
                  headerFile='nsIWebBrowserPersistDocument.h',
                  notflattened=True)
 addExternalIface('nsIWebProgressListener', nativeType='nsIWebProgressListener',
                  notflattened=True)
new file mode 100644
--- /dev/null
+++ b/dom/chrome-webidl/XULFrameElement.webidl
@@ -0,0 +1,19 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 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/. */
+
+interface nsIDocShell;
+interface nsIWebNavigation;
+
+[HTMLConstructor, Func="IsChromeOrXBL"]
+interface XULFrameElement : XULElement
+{
+  readonly attribute nsIDocShell? docShell;
+  readonly attribute nsIWebNavigation? webNavigation;
+
+  readonly attribute WindowProxy? contentWindow;
+  readonly attribute Document? contentDocument; 
+};
+
+XULFrameElement implements MozFrameLoaderOwner;
--- a/dom/chrome-webidl/moz.build
+++ b/dom/chrome-webidl/moz.build
@@ -40,15 +40,16 @@ WEBIDL_FILES = [
     'MozStorageAsyncStatementParams.webidl',
     'MozStorageStatementParams.webidl',
     'MozStorageStatementRow.webidl',
     'PrecompiledScript.webidl',
     'PromiseDebugging.webidl',
     'StructuredCloneHolder.webidl',
     'WebExtensionContentScript.webidl',
     'WebExtensionPolicy.webidl',
+    'XULFrameElement.webidl'
 ]
 
 if CONFIG['MOZ_PLACES']:
     WEBIDL_FILES += [
         'PlacesEvent.webidl',
         'PlacesObservers.webidl',
     ]
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1893,17 +1893,18 @@ EventStateManager::GenerateDragGesture(n
       // reentering GenerateDragGesture inside DOM event processing.
       StopTrackingDragGesture();
 
       if (!targetContent)
         return;
 
       // Use our targetContent, now that we've determined it, as the
       // parent object of the DataTransfer.
-      dataTransfer->SetParentObject(targetContent);
+      nsCOMPtr<nsIContent> parentContent = targetContent->FindFirstNonChromeOnlyAccessContent();
+      dataTransfer->SetParentObject(parentContent);
 
       sLastDragOverFrame = nullptr;
       nsCOMPtr<nsIWidget> widget = mCurrentTarget->GetNearestWidget();
 
       // get the widget from the target frame
       WidgetDragEvent startEvent(aEvent->IsTrusted(), eDragStart, widget);
       FillInEventFromGestureDown(&startEvent);
 
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -3,31 +3,31 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsGenericHTMLFrameElement.h"
 
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/XULFrameElement.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ErrorResult.h"
 #include "GeckoProfiler.h"
 #include "nsAttrValueInlines.h"
 #include "nsContentUtils.h"
 #include "nsIDocShell.h"
 #include "nsIFrame.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermissionManager.h"
 #include "nsIPresShell.h"
 #include "nsIScrollable.h"
 #include "nsPresContext.h"
 #include "nsServiceManagerUtils.h"
 #include "nsSubDocumentFrame.h"
-#include "nsXULElement.h"
 #include "nsAttrValueOrString.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsGenericHTMLFrameElement)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGenericHTMLFrameElement,
@@ -189,17 +189,17 @@ nsGenericHTMLFrameElement::SwapFrameLoad
     // nothing to do
     return;
   }
 
   aOtherLoaderOwner.SwapFrameLoaders(this, rv);
 }
 
 void
-nsGenericHTMLFrameElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+nsGenericHTMLFrameElement::SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
                                             ErrorResult& rv)
 {
   aOtherLoaderOwner.SwapFrameLoaders(this, rv);
 }
 
 void
 nsGenericHTMLFrameElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
                                             mozilla::ErrorResult& rv)
--- a/dom/html/nsGenericHTMLFrameElement.h
+++ b/dom/html/nsGenericHTMLFrameElement.h
@@ -12,17 +12,21 @@
 #include "mozilla/dom/nsBrowserElement.h"
 
 #include "nsFrameLoader.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIDOMEventListener.h"
 #include "nsIFrameLoaderOwner.h"
 #include "nsIMozBrowserFrame.h"
 
-class nsXULElement;
+namespace mozilla {
+namespace dom {
+class XULFrameElement;
+}
+}
 
 #define NS_GENERICHTMLFRAMEELEMENT_IID \
 { 0x8190db72, 0xdab0, 0x4d72, \
   { 0x94, 0x26, 0x87, 0x5f, 0x5a, 0x8a, 0x2a, 0xe5 } }
 
 /**
  * A helper class for frame elements
  */
@@ -68,17 +72,17 @@ public:
   virtual nsIMozBrowserFrame* GetAsMozBrowserFrame() override { return this; }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsGenericHTMLFrameElement,
                                            nsGenericHTMLElement)
 
   void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
                         mozilla::ErrorResult& aError);
 
-  void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
+  void SwapFrameLoaders(mozilla::dom::XULFrameElement& aOtherLoaderOwner,
                         mozilla::ErrorResult& aError);
 
   void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
                         mozilla::ErrorResult& rv);
 
   void PresetOpenerWindow(mozIDOMWindowProxy* aOpenerWindow,
                           mozilla::ErrorResult& aRv);
 
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -1248,16 +1248,18 @@ var interfaceNamesInGlobalScope =
     {name: "XSLTProcessor", insecureContext: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULCommandEvent", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULDocument", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULElement", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
+    {name: "XULFrameElement", insecureContext: true, xbl: true},
+// IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "XULPopupElement", insecureContext: true, xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
   ];
 // IMPORTANT: Do not change the list above without review from a DOM peer!
 
 function createInterfaceMap(isXBLScope) {
   var interfaceMap = {};
 
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozFrameLoaderOwner.webidl
@@ -0,0 +1,15 @@
+// And the things from nsIFrameLoaderOwner
+[NoInterfaceObject]
+interface MozFrameLoaderOwner {
+  [ChromeOnly]
+  readonly attribute FrameLoader? frameLoader;
+
+  [ChromeOnly, Throws]
+  void presetOpenerWindow(WindowProxy? window);
+
+  [ChromeOnly, Throws]
+  void swapFrameLoaders(XULFrameElement aOtherLoaderOwner);
+
+  [ChromeOnly, Throws]
+  void swapFrameLoaders(HTMLIFrameElement aOtherLoaderOwner);
+};
--- a/dom/webidl/XULElement.webidl
+++ b/dom/webidl/XULElement.webidl
@@ -90,28 +90,11 @@ interface XULElement : Element {
   [Throws]
   NodeList            getElementsByAttributeNS(DOMString namespaceURI,
                                                DOMString name,
                                                DOMString value);
   [Constant]
   readonly attribute CSSStyleDeclaration style;
 };
 
-// And the things from nsIFrameLoaderOwner
-[NoInterfaceObject]
-interface MozFrameLoaderOwner {
-  [ChromeOnly]
-  readonly attribute FrameLoader? frameLoader;
-
-  [ChromeOnly, Throws]
-  void presetOpenerWindow(WindowProxy? window);
-
-  [ChromeOnly, Throws]
-  void swapFrameLoaders(XULElement aOtherLoaderOwner);
-
-  [ChromeOnly, Throws]
-  void swapFrameLoaders(HTMLIFrameElement aOtherLoaderOwner);
-};
-
 XULElement implements GlobalEventHandlers;
 XULElement implements TouchEventHandlers;
-XULElement implements MozFrameLoaderOwner;
 XULElement implements OnErrorEventHandlerForNodes;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -672,16 +672,17 @@ WEBIDL_FILES = [
     'MIDIOptions.webidl',
     'MIDIOutput.webidl',
     'MIDIOutputMap.webidl',
     'MIDIPort.webidl',
     'MimeType.webidl',
     'MimeTypeArray.webidl',
     'MouseEvent.webidl',
     'MouseScrollEvent.webidl',
+    'MozFrameLoaderOwner.webidl',
     'MutationEvent.webidl',
     'MutationObserver.webidl',
     'NamedNodeMap.webidl',
     'NativeOSFileInternals.webidl',
     'Navigator.webidl',
     'NetDashboard.webidl',
     'NetworkInformation.webidl',
     'NetworkOptions.webidl',
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULFrameElement.cpp
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsCOMPtr.h"
+#include "nsIContent.h"
+#include "nsFrameLoader.h"
+#include "mozilla/AsyncEventDispatcher.h"
+#include "mozilla/dom/HTMLIFrameElement.h"
+#include "mozilla/dom/XULFrameElement.h"
+#include "mozilla/dom/XULFrameElementBinding.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(XULFrameElement)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(XULFrameElement,
+                                                  nsXULElement)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameLoader);
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpener);
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(XULFrameElement,
+                                                nsXULElement)
+  if (tmp->mFrameLoader) {
+    tmp->mFrameLoader->Destroy();
+  }
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameLoader, mOpener)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_ISUPPORTS_CYCLE_COLLECTION_INHERITED(XULFrameElement,
+                                             nsXULElement,
+                                             nsIFrameLoaderOwner)
+
+JSObject*
+XULFrameElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return XULFrameElement_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+nsIDocShell*
+XULFrameElement::GetDocShell()
+{
+  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  return frameLoader ? frameLoader->GetDocShell(IgnoreErrors()) : nullptr;
+}
+
+already_AddRefed<nsIWebNavigation>
+XULFrameElement::GetWebNavigation()
+{
+  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+  nsCOMPtr<nsIWebNavigation> webnav = do_QueryInterface(docShell);
+  return webnav.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+XULFrameElement::GetContentWindow()
+{
+  nsCOMPtr<nsIDocShell> docShell = GetDocShell();
+  if (docShell) {
+    nsCOMPtr<nsPIDOMWindowOuter> win = docShell->GetWindow();
+    return win.forget();
+  }
+
+  return nullptr;
+}
+
+nsIDocument*
+XULFrameElement::GetContentDocument()
+{
+  nsCOMPtr<nsPIDOMWindowOuter> win = GetContentWindow();
+  return win ? win->GetDoc() : nullptr;
+}
+
+void
+XULFrameElement::LoadSrc()
+{
+  if (!IsInUncomposedDoc() ||
+      !OwnerDoc()->GetRootElement() ||
+      OwnerDoc()->GetRootElement()->
+        NodeInfo()->Equals(nsGkAtoms::overlay, kNameSpaceID_XUL)) {
+      return;
+  }
+  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  if (!frameLoader) {
+    // Check if we have an opener we need to be setting
+    nsCOMPtr<nsPIDOMWindowOuter> opener = mOpener;
+    if (!opener) {
+      // If we are a primary xul-browser, we want to take the opener property!
+      nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
+      if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
+                      nsGkAtoms::_true, eIgnoreCase) && window) {
+        opener = window->TakeOpenerForInitialContentBrowser();
+      }
+    }
+    mOpener = nullptr;
+
+    // false as the last parameter so that xul:iframe/browser/editor
+    // session history handling works like dynamic html:iframes.
+    // Usually xul elements are used in chrome, which doesn't have
+    // session history at all.
+    mFrameLoader = nsFrameLoader::Create(this, opener, false);
+    if (NS_WARN_IF(!mFrameLoader)) {
+      return;
+    }
+
+    (new AsyncEventDispatcher(this,
+                              NS_LITERAL_STRING("XULFrameLoaderCreated"),
+                              CanBubble::eYes))->RunDOMEventWhenSafe();
+  }
+
+  mFrameLoader->LoadFrame(false);
+}
+
+void
+XULFrameElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
+                                  ErrorResult& rv)
+{
+  aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void
+XULFrameElement::SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
+                                  ErrorResult& rv)
+{
+  if (&aOtherLoaderOwner == this) {
+    // nothing to do
+    return;
+  }
+
+  aOtherLoaderOwner.SwapFrameLoaders(this, rv);
+}
+
+void
+XULFrameElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+                                  mozilla::ErrorResult& rv)
+{
+  RefPtr<nsFrameLoader> loader = GetFrameLoader();
+  RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader();
+  if (!loader || !otherLoader) {
+    rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
+    return;
+  }
+
+  rv = loader->SwapWithOtherLoader(otherLoader, this, aOtherLoaderOwner);
+}
+
+nsresult
+XULFrameElement::BindToTree(nsIDocument* aDocument,
+                            nsIContent* aParent,
+                            nsIContent* aBindingParent,
+                            bool aCompileEventHandlers)
+{
+  nsresult rv = nsXULElement::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (aDocument) {
+    NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
+                 "Missing a script blocker!");
+    // We're in a document now.  Kick off the frame load.
+    LoadSrc();
+  }
+
+  return NS_OK;
+}
+
+void
+XULFrameElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  if (frameLoader) {
+    frameLoader->Destroy();
+  }
+  mFrameLoader = nullptr;
+
+  nsXULElement::UnbindFromTree(aDeep, aNullParent);
+}
+
+void
+XULFrameElement::DestroyContent()
+{
+  RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
+  if (frameLoader) {
+    frameLoader->Destroy();
+  }
+  mFrameLoader = nullptr;
+
+  nsXULElement::DestroyContent();
+}
+
+nsresult
+XULFrameElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+                              const nsAttrValue* aValue,
+                              const nsAttrValue* aOldValue,
+                              nsIPrincipal* aSubjectPrincipal,
+                              bool aNotify)
+{
+  if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::src && aValue) {
+    LoadSrc();
+  }
+
+  return nsXULElement::AfterSetAttr(aNamespaceID, aName,
+                                    aValue, aOldValue, aSubjectPrincipal, aNotify);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/xul/XULFrameElement.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef XULFrameElement_h__
+#define XULFrameElement_h__
+
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "js/TypeDecls.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+#include "nsString.h"
+#include "nsXULElement.h"
+
+class nsIWebNavigation;
+class nsFrameLoader;
+
+namespace mozilla {
+namespace dom {
+
+class XULFrameElement final : public nsXULElement,
+                              public nsIFrameLoaderOwner
+{
+public:
+  explicit XULFrameElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
+    : nsXULElement(aNodeInfo)
+  {
+  }
+
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULFrameElement, nsXULElement)
+
+  // XULFrameElement.webidl
+  nsIDocShell* GetDocShell();
+  already_AddRefed<nsIWebNavigation> GetWebNavigation();
+  already_AddRefed<nsPIDOMWindowOuter> GetContentWindow();
+  nsIDocument* GetContentDocument();
+
+  // nsIFrameLoaderOwner / MozFrameLoaderOwner
+  NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() override
+  {
+    return do_AddRef(mFrameLoader);
+  }
+
+  NS_IMETHOD_(void) InternalSetFrameLoader(nsFrameLoader* aFrameLoader) override
+  {
+    mFrameLoader = aFrameLoader;
+  }
+
+  void PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
+  {
+    mOpener = do_QueryInterface(aWindow);
+  }
+
+  void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
+                        mozilla::ErrorResult& rv);
+  void SwapFrameLoaders(XULFrameElement& aOtherLoaderOwner,
+                        mozilla::ErrorResult& rv);
+  void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
+                        mozilla::ErrorResult& rv);
+
+  // nsIContent
+  virtual nsresult BindToTree(nsIDocument* aDocument, nsIContent* aParent,
+                              nsIContent* aBindingParent,
+                              bool aCompileEventHandlers) override;
+  virtual void UnbindFromTree(bool aDeep, bool aNullParent) override;
+  virtual void DestroyContent() override;
+
+
+  virtual nsresult AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
+                                const nsAttrValue* aValue,
+                                const nsAttrValue* aOldValue,
+                                nsIPrincipal* aSubjectPrincipal,
+                                bool aNotify) override;
+
+protected:
+  virtual ~XULFrameElement()
+  {
+  }
+
+  RefPtr<nsFrameLoader> mFrameLoader;
+  nsCOMPtr<nsPIDOMWindowOuter> mOpener;
+
+  JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void LoadSrc();
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // XULFrameElement_h
--- a/dom/xul/moz.build
+++ b/dom/xul/moz.build
@@ -19,29 +19,31 @@ if CONFIG['MOZ_XUL']:
         'nsIXULOverlayProvider.idl',
     ]
 
     EXPORTS += [
         'nsXULElement.h',
     ]
 
     EXPORTS.mozilla.dom += [
+        'XULFrameElement.h',
         'XULPopupElement.h',
     ]
 
     UNIFIED_SOURCES += [
         'nsXULCommandDispatcher.cpp',
         'nsXULContentSink.cpp',
         'nsXULContentUtils.cpp',
         'nsXULElement.cpp',
         'nsXULPopupListener.cpp',
         'nsXULPrototypeCache.cpp',
         'nsXULPrototypeDocument.cpp',
         'nsXULSortService.cpp',
         'XULDocument.cpp',
+        'XULFrameElement.cpp',
         'XULPopupElement.cpp',
     ]
 
 XPIDL_SOURCES += [
     'nsIController.idl',
     'nsIControllers.idl',
     'nsIXULSortService.idl',
 ]
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -8,17 +8,16 @@
 #include "nsError.h"
 #include "nsDOMString.h"
 #include "nsAtom.h"
 #include "nsIBaseWindow.h"
 #include "nsIDOMEventListener.h"
 #include "nsIDOMXULCommandDispatcher.h"
 #include "nsIDOMXULSelectCntrlItemEl.h"
 #include "nsIDocument.h"
-#include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
 #include "mozilla/DeclarationBlock.h"
 #include "nsFocusManager.h"
 #include "nsHTMLStyleSheet.h"
 #include "nsNameSpaceManager.h"
@@ -69,21 +68,21 @@
 #include "nsNodeInfoManager.h"
 #include "nsXBLBinding.h"
 #include "nsXULTooltipListener.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozAutoDocUpdate.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsICSSDeclaration.h"
 #include "nsLayoutUtils.h"
+#include "XULFrameElement.h"
 #include "XULPopupElement.h"
 
 #include "mozilla/dom/XULElementBinding.h"
 #include "mozilla/dom/BoxObject.h"
-#include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/dom/MouseEventBinding.h"
 #include "mozilla/dom/MutationEventBinding.h"
 #include "mozilla/dom/XULCommandEvent.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
@@ -92,43 +91,16 @@ uint32_t             nsXULPrototypeAttri
 uint32_t             nsXULPrototypeAttribute::gNumCacheTests;
 uint32_t             nsXULPrototypeAttribute::gNumCacheHits;
 uint32_t             nsXULPrototypeAttribute::gNumCacheSets;
 uint32_t             nsXULPrototypeAttribute::gNumCacheFills;
 #endif
 
 #define NS_DISPATCH_XUL_COMMAND     (1 << 0)
 
-class nsXULElementTearoff final : public nsIFrameLoaderOwner
-{
-  ~nsXULElementTearoff() {}
-
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsXULElementTearoff)
-
-  explicit nsXULElementTearoff(nsXULElement* aElement)
-    : mElement(aElement)
-  {
-  }
-
-  NS_FORWARD_NSIFRAMELOADEROWNER(static_cast<nsXULElement*>(mElement.get())->)
-private:
-  RefPtr<nsXULElement> mElement;
-};
-
-NS_IMPL_CYCLE_COLLECTION(nsXULElementTearoff, mElement)
-
-NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULElementTearoff)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULElementTearoff)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULElementTearoff)
-  NS_INTERFACE_MAP_ENTRY(nsIFrameLoaderOwner)
-NS_INTERFACE_MAP_END_AGGREGATED(mElement)
-
 //----------------------------------------------------------------------
 // nsXULElement
 //
 
 nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
     : nsStyledElement(aNodeInfo),
       mBindingParent(nullptr)
 {
@@ -173,16 +145,23 @@ nsXULElement* nsXULElement::Construct(al
   RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
   if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
       nodeInfo->Equals(nsGkAtoms::popup) ||
       nodeInfo->Equals(nsGkAtoms::panel) ||
       nodeInfo->Equals(nsGkAtoms::tooltip)) {
     return NS_NewXULPopupElement(nodeInfo.forget());
   }
 
+  if (nodeInfo->Equals(nsGkAtoms::iframe) ||
+      nodeInfo->Equals(nsGkAtoms::browser) ||
+      nodeInfo->Equals(nsGkAtoms::editor)) {
+    already_AddRefed<mozilla::dom::NodeInfo> frameni = nodeInfo.forget();
+    return new XULFrameElement(frameni);
+  }
+
   return NS_NewBasicXULElement(nodeInfo.forget());
 }
 
 /* static */
 already_AddRefed<nsXULElement>
 nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype,
                                   mozilla::dom::NodeInfo *aNodeInfo,
                                   bool aIsScriptable,
@@ -314,18 +293,16 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_IN
     NS_IMPL_CYCLE_COLLECTION_UNLINK(mBindingParent);
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
 NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
     NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
-    NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFrameLoaderOwner,
-                                   new nsXULElementTearoff(this))
 NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
 
 //----------------------------------------------------------------------
 // nsINode interface
 
 nsresult
 nsXULElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                     bool aPreallocateChildren) const
@@ -778,23 +755,16 @@ nsXULElement::BindToTree(nsIDocument* aD
     }
   }
 #endif
 
   if (doc && NeedTooltipSupport(*this)) {
       AddTooltipSupport();
   }
 
-  if (aDocument) {
-      NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(),
-                   "Missing a script blocker!");
-      // We're in a document now.  Kick off the frame load.
-      LoadSrc();
-  }
-
   return rv;
 }
 
 void
 nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent)
 {
     if (NeedTooltipSupport(*this)) {
         RemoveTooltipSupport();
@@ -809,21 +779,16 @@ nsXULElement::UnbindFromTree(bool aDeep,
     // which owns this content.  That's a cycle, so we break
     // it here.  (It might be better to break this by releasing
     // mDocument in nsGlobalWindow::SetDocShell, but I'm not
     // sure whether that would fix all possible cycles through
     // mControllers.)
     nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     if (slots) {
         slots->mControllers = nullptr;
-        RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
-        if (frameLoader) {
-            frameLoader->Destroy();
-        }
-        slots->mFrameLoaderOrOpener = nullptr;
     }
 
     nsStyledElement::UnbindFromTree(aDeep, aNullParent);
 }
 
 void
 nsXULElement::RemoveChildNode(nsIContent* aKid, bool aNotify)
 {
@@ -1049,20 +1014,16 @@ nsXULElement::AfterSetAttr(int32_t aName
                     if (document->IsXULDocument()) {
                         document->AsXULDocument()->ResetDocumentLWTheme();
                         UpdateBrightTitlebarForeground(document);
                     }
                 } else if (aName == nsGkAtoms::brighttitlebarforeground) {
                     UpdateBrightTitlebarForeground(document);
                 }
             }
-
-            if (aName == nsGkAtoms::src && document) {
-                LoadSrc();
-            }
         } else {
             if (mNodeInfo->Equals(nsGkAtoms::window)) {
                 if (aName == nsGkAtoms::hidechrome) {
                     HideWindowChrome(false);
                 } else if (aName == nsGkAtoms::chromemargin) {
                     ResetChromeMargins();
                 }
             }
@@ -1163,21 +1124,16 @@ nsXULElement::RemoveBroadcaster(const ns
 }
 
 void
 nsXULElement::DestroyContent()
 {
     nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
     if (slots) {
         slots->mControllers = nullptr;
-        RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
-        if (frameLoader) {
-            frameLoader->Destroy();
-        }
-        slots->mFrameLoaderOrOpener = nullptr;
     }
 
     nsStyledElement::DestroyContent();
 }
 
 #ifdef DEBUG
 void
 nsXULElement::List(FILE* out, int32_t aIndent) const
@@ -1352,143 +1308,16 @@ nsXULElement::GetControllers(ErrorResult
 
 already_AddRefed<BoxObject>
 nsXULElement::GetBoxObject(ErrorResult& rv)
 {
     // XXX sXBL/XBL2 issue! Owner or current document?
     return OwnerDoc()->GetBoxObjectFor(this, rv);
 }
 
-void
-nsXULElement::LoadSrc()
-{
-    // Allow frame loader only on objects for which a container box object
-    // can be obtained.
-    if (!IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::editor,
-                            nsGkAtoms::iframe)) {
-        return;
-    }
-    if (!IsInUncomposedDoc() ||
-        !OwnerDoc()->GetRootElement() ||
-        OwnerDoc()->GetRootElement()->
-            NodeInfo()->Equals(nsGkAtoms::overlay, kNameSpaceID_XUL)) {
-        return;
-    }
-    RefPtr<nsFrameLoader> frameLoader = GetFrameLoader();
-    if (!frameLoader) {
-        // Check if we have an opener we need to be setting
-        nsExtendedDOMSlots* slots = ExtendedDOMSlots();
-        nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryInterface(slots->mFrameLoaderOrOpener);
-        if (!opener) {
-            // If we are a primary xul-browser, we want to take the opener property!
-            nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow();
-            if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::primary,
-                            nsGkAtoms::_true, eIgnoreCase) && window) {
-                opener = window->TakeOpenerForInitialContentBrowser();
-            }
-        }
-
-        // false as the last parameter so that xul:iframe/browser/editor
-        // session history handling works like dynamic html:iframes.
-        // Usually xul elements are used in chrome, which doesn't have
-        // session history at all.
-        frameLoader = nsFrameLoader::Create(this, opener, false);
-        slots->mFrameLoaderOrOpener = ToSupports(frameLoader);
-        if (NS_WARN_IF(!frameLoader)) {
-            return;
-        }
-
-        (new AsyncEventDispatcher(this,
-                                  NS_LITERAL_STRING("XULFrameLoaderCreated"),
-                                  CanBubble::eYes))->RunDOMEventWhenSafe();
-    }
-
-    frameLoader->LoadFrame(false);
-}
-
-already_AddRefed<nsFrameLoader>
-nsXULElement::GetFrameLoader()
-{
-    nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
-    if (!slots)
-        return nullptr;
-
-    RefPtr<nsFrameLoader> loader = do_QueryObject(slots->mFrameLoaderOrOpener);
-    return loader.forget();
-}
-
-void
-nsXULElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
-{
-    nsExtendedDOMSlots* slots = ExtendedDOMSlots();
-    MOZ_ASSERT(!slots->mFrameLoaderOrOpener, "A frameLoader or opener is present when calling PresetOpenerWindow");
-
-    slots->mFrameLoaderOrOpener = aWindow;
-}
-
-void
-nsXULElement::InternalSetFrameLoader(nsFrameLoader* aNewFrameLoader)
-{
-    nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
-    MOZ_ASSERT(slots);
-
-    slots->mFrameLoaderOrOpener = ToSupports(aNewFrameLoader);
-}
-
-void
-nsXULElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner,
-                               ErrorResult& rv)
-{
-    if (!GetExistingDOMSlots()) {
-        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-        return;
-    }
-
-    nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(ToSupports(this));
-    aOtherLoaderOwner.SwapFrameLoaders(flo, rv);
-}
-
-void
-nsXULElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
-                               ErrorResult& rv)
-{
-    if (&aOtherLoaderOwner == this) {
-        // nothing to do
-        return;
-    }
-
-    if (!GetExistingDOMSlots()) {
-        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-        return;
-    }
-
-    nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(ToSupports(this));
-    aOtherLoaderOwner.SwapFrameLoaders(flo, rv);
-}
-
-void
-nsXULElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
-                               mozilla::ErrorResult& rv)
-{
-    if (!GetExistingDOMSlots()) {
-        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-        return;
-    }
-
-    RefPtr<nsFrameLoader> loader = GetFrameLoader();
-    RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader();
-    if (!loader || !otherLoader) {
-        rv.Throw(NS_ERROR_NOT_IMPLEMENTED);
-        return;
-    }
-
-    nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(ToSupports(this));
-    rv = loader->SwapWithOtherLoader(otherLoader, flo, aOtherLoaderOwner);
-}
-
 NS_IMETHODIMP
 nsXULElement::GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement)
 {
     for (nsIContent* current = GetParent(); current;
          current = current->GetParent()) {
         if (current->NodeInfo()->Equals(nsGkAtoms::listbox,
                                         kNameSpaceID_XUL)) {
             CallQueryInterface(current, aTreeElement);
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -20,24 +20,22 @@
 #include "nsIControllers.h"
 #include "nsIDOMXULMultSelectCntrlEl.h"
 #include "nsIURI.h"
 #include "nsLayoutCID.h"
 #include "nsAttrAndChildArray.h"
 #include "nsGkAtoms.h"
 #include "nsStringFwd.h"
 #include "nsStyledElement.h"
-#include "nsIFrameLoaderOwner.h"
 #include "mozilla/dom/DOMRect.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/DOMString.h"
 #include "mozilla/dom/FromParser.h"
 
 class nsIDocument;
-class nsFrameLoader;
 class nsXULPrototypeDocument;
 
 class nsIObjectInputStream;
 class nsIObjectOutputStream;
 class nsIOffThreadScriptReceiver;
 class nsXULPrototypeNode;
 typedef nsTArray<RefPtr<nsXULPrototypeNode> > nsPrototypeArray;
 
@@ -393,18 +391,16 @@ public:
     virtual nsChangeHint GetAttributeChangeHint(const nsAtom* aAttribute,
                                                 int32_t aModType) const override;
     NS_IMETHOD_(bool) IsAttributeMapped(const nsAtom* aAttribute) const override;
 
     virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
                            bool aPreallocateChildren) const override;
     virtual mozilla::EventStates IntrinsicState() const override;
 
-    void PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv);
-
     virtual void RecompileScriptEventListeners() override;
 
     // This function should ONLY be used by BindToTree implementations.
     // The function exists solely because XUL elements store the binding
     // parent as a member instead of in the slots, as Element does.
     void SetXULBindingParent(nsIContent* aBindingParent)
     {
       mBindingParent = aBindingParent;
@@ -626,24 +622,16 @@ public:
       GetElementsByAttribute(const nsAString& aAttribute,
                              const nsAString& aValue);
     already_AddRefed<nsINodeList>
       GetElementsByAttributeNS(const nsAString& aNamespaceURI,
                                const nsAString& aAttribute,
                                const nsAString& aValue,
                                mozilla::ErrorResult& rv);
     // Style() inherited from nsStyledElement
-    already_AddRefed<nsFrameLoader> GetFrameLoader();
-    void InternalSetFrameLoader(nsFrameLoader* aNewFrameLoader);
-    void SwapFrameLoaders(mozilla::dom::HTMLIFrameElement& aOtherLoaderOwner,
-                          mozilla::ErrorResult& rv);
-    void SwapFrameLoaders(nsXULElement& aOtherLoaderOwner,
-                          mozilla::ErrorResult& rv);
-    void SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner,
-                          mozilla::ErrorResult& rv);
 
     nsINode* GetScopeChainParent() const override
     {
         // For XUL, the parent is the parent element, if any
         Element* parent = GetParentElement();
         return parent ? parent : nsStyledElement::GetScopeChainParent();
     }
 
@@ -656,18 +644,16 @@ protected:
     // Implementation methods
     nsresult EnsureContentsGenerated(void) const;
 
     // Helper routine that crawls a parent chain looking for a tree element.
     NS_IMETHOD GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement);
 
     nsresult AddPopupListener(nsAtom* aName);
 
-    void LoadSrc();
-
     /**
      * The nearest enclosing content node with a binding
      * that created us.
      */
     nsCOMPtr<nsIContent> mBindingParent;
 
     /**
      * Abandon our prototype linkage, and copy all attributes locally
--- a/js/public/CharacterEncoding.h
+++ b/js/public/CharacterEncoding.h
@@ -321,21 +321,14 @@ LossyUTF8CharsToNewLatin1CharsZ(JSContex
 
 /*
  * Returns true if all characters in the given null-terminated string are
  * ASCII, i.e. < 0x80, false otherwise.
  */
 extern JS_PUBLIC_API(bool)
 StringIsASCII(const char* s);
 
-/*
- * Returns true if the given length-delimited string is a valid UTF-8 string,
- * false otherwise.
- */
-extern JS_PUBLIC_API(bool)
-StringIsUTF8(const uint8_t* s, uint32_t length);
-
 } // namespace JS
 
 inline void JS_free(JS::Latin1CharsZ& ptr) { js_free((void*)ptr.get()); }
 inline void JS_free(JS::UTF8CharsZ& ptr) { js_free((void*)ptr.get()); }
 
 #endif /* js_CharacterEncoding_h */
--- a/js/src/frontend/TokenStream.cpp
+++ b/js/src/frontend/TokenStream.cpp
@@ -10,16 +10,17 @@
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/IntegerTypeTraits.h"
 #include "mozilla/Likely.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/ScopeExit.h"
 #include "mozilla/TextUtils.h"
+#include "mozilla/Utf8.h"
 
 #include <algorithm>
 #include <ctype.h>
 #include <stdarg.h>
 #include <stdio.h>
 #include <string.h>
 #include <utility>
 
@@ -39,16 +40,17 @@
 #include "vm/Realm.h"
 
 using mozilla::ArrayLength;
 using mozilla::AssertedCast;
 using mozilla::IsAscii;
 using mozilla::IsAsciiAlpha;
 using mozilla::IsAsciiDigit;
 using mozilla::MakeScopeExit;
+using mozilla::Utf8Unit;
 
 struct ReservedWordInfo
 {
     const char* chars;         // C string with reserved word text
     js::frontend::TokenKind tokentype;
 };
 
 static const ReservedWordInfo reservedWords[] = {
@@ -2607,16 +2609,17 @@ TokenKindToString(TokenKind tt)
 #undef EMIT_CASE
       case TokenKind::Limit: break;
     }
 
     return "<bad TokenKind>";
 }
 #endif
 
+template class frontend::TokenStreamCharsBase<Utf8Unit>;
 template class frontend::TokenStreamCharsBase<char16_t>;
 
 template class frontend::TokenStreamChars<char16_t, frontend::TokenStreamAnyCharsAccess>;
 template class frontend::TokenStreamSpecific<char16_t, frontend::TokenStreamAnyCharsAccess>;
 
 template class
 frontend::TokenStreamChars<char16_t, frontend::ParserAnyCharsAccess<frontend::GeneralParser<frontend::FullParseHandler, char16_t>>>;
 template class
--- a/js/src/frontend/TokenStream.h
+++ b/js/src/frontend/TokenStream.h
@@ -167,16 +167,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/Casting.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/MemoryChecking.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/TextUtils.h"
 #include "mozilla/TypeTraits.h"
 #include "mozilla/Unused.h"
+#include "mozilla/Utf8.h"
 
 #include <algorithm>
 #include <stdarg.h>
 #include <stddef.h>
 #include <stdio.h>
 
 #include "jspubtd.h"
 
@@ -916,16 +917,22 @@ class TokenStreamAnyChars
 };
 
 constexpr char16_t
 CodeUnitValue(char16_t unit)
 {
     return unit;
 }
 
+constexpr uint8_t
+CodeUnitValue(mozilla::Utf8Unit unit)
+{
+    return unit.toUint8();
+}
+
 // This is the low-level interface to the JS source code buffer.  It just gets
 // raw Unicode code units -- 16-bit char16_t units of source text that are not
 // (always) full code points, and 8-bit units of UTF-8 source text soon.
 // TokenStreams functions are layered on top and do some extra stuff like
 // converting all EOL sequences to '\n', tracking the line number, and setting
 // |flags.isEOF|.  (The "raw" in "raw Unicode code units" refers to the lack of
 // EOL sequence normalization.)
 //
@@ -1216,16 +1223,24 @@ template<>
 inline char16_t
 TokenStreamCharsBase<char16_t>::toCharT(int32_t codeUnitValue)
 {
     MOZ_ASSERT(codeUnitValue != EOF, "EOF is not a CharT");
     return mozilla::AssertedCast<char16_t>(codeUnitValue);
 }
 
 template<>
+inline mozilla::Utf8Unit
+TokenStreamCharsBase<mozilla::Utf8Unit>::toCharT(int32_t value)
+{
+    MOZ_ASSERT(value != EOF, "EOF is not a CharT");
+    return mozilla::Utf8Unit(static_cast<unsigned char>(value));
+}
+
+template<>
 /* static */ MOZ_ALWAYS_INLINE JSAtom*
 TokenStreamCharsBase<char16_t>::atomizeSourceChars(JSContext* cx, const char16_t* chars,
                                                    size_t length)
 {
     return AtomizeChars(cx, chars, length);
 }
 
 template<typename CharT>
--- a/js/src/vm/CharacterEncoding.cpp
+++ b/js/src/vm/CharacterEncoding.cpp
@@ -488,48 +488,8 @@ JS::StringIsASCII(const char* s)
 {
     while (*s) {
         if (*s & 0x80)
             return false;
         s++;
     }
     return true;
 }
-
-bool
-JS::StringIsUTF8(const uint8_t* s, uint32_t length)
-{
-    const uint8_t* limit = s + length;
-    while (s < limit) {
-        uint32_t len;
-        uint32_t min;
-        uint32_t n = *s;
-        if ((n & 0x80) == 0) {
-            len = 1;
-            min = 0;
-        } else if ((n & 0xE0) == 0xC0) {
-            len = 2;
-            min = 0x80;
-            n &= 0x1F;
-        } else if ((n & 0xF0) == 0xE0) {
-            len = 3;
-            min = 0x800;
-            n &= 0x0F;
-        } else if ((n & 0xF8) == 0xF0) {
-            len = 4;
-            min = 0x10000;
-            n &= 0x07;
-        } else {
-            return false;
-        }
-        if (s + len > limit)
-            return false;
-        for (uint32_t i = 1; i < len; i++) {
-            if ((s[i] & 0xC0) != 0x80)
-                return false;
-            n = (n << 6) | (s[i] & 0x3F);
-        }
-        if (n < min || (0xD800 <= n && n < 0xE000) || n >= 0x110000)
-            return false;
-        s += len;
-    }
-    return true;
-}
--- a/js/src/wasm/WasmValidate.cpp
+++ b/js/src/wasm/WasmValidate.cpp
@@ -15,28 +15,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 #include "wasm/WasmValidate.h"
 
 #include "mozilla/CheckedInt.h"
 #include "mozilla/Unused.h"
+#include "mozilla/Utf8.h"
 
 #include "jit/JitOptions.h"
 #include "js/Printf.h"
 #include "vm/JSContext.h"
 #include "vm/Realm.h"
 #include "wasm/WasmOpIter.h"
 
 using namespace js;
 using namespace js::jit;
 using namespace js::wasm;
 
 using mozilla::CheckedInt;
+using mozilla::IsValidUtf8;
 using mozilla::Unused;
 
 // Decoder implementation.
 
 bool
 Decoder::failf(const char* msg, ...)
 {
     va_list ap;
@@ -1282,17 +1284,17 @@ DecodeName(Decoder& d)
 
     if (numBytes > MaxStringBytes)
         return nullptr;
 
     const uint8_t* bytes;
     if (!d.readBytes(numBytes, &bytes))
         return nullptr;
 
-    if (!JS::StringIsUTF8(bytes, numBytes))
+    if (!IsValidUtf8(bytes, numBytes))
         return nullptr;
 
     UniqueChars name(js_pod_malloc<char>(numBytes + 1));
     if (!name)
         return nullptr;
 
     memcpy(name.get(), bytes, numBytes);
     name[numBytes] = '\0';
new file mode 100644
--- /dev/null
+++ b/mfbt/Utf8.cpp
@@ -0,0 +1,79 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Types.h"
+#include "mozilla/Utf8.h"
+
+#include <stddef.h>
+#include <stdint.h>
+
+MFBT_API bool
+mozilla::IsValidUtf8(const void* aCodeUnits, size_t aCount)
+{
+  const auto* s = static_cast<const unsigned char*>(aCodeUnits);
+  const auto* limit = s + aCount;
+
+  while (s < limit) {
+    uint32_t n = *s++;
+
+    // If the first byte is ASCII, it's the only one in the code point.  Have a
+    // fast path that avoids all the rest of the work and looping in that case.
+    if ((n & 0x80) == 0) {
+      continue;
+    }
+
+    // The leading code unit determines the length of the next code point and
+    // the number of bits of the leading code unit that contribute to the code
+    // point's value.
+    uint_fast8_t remaining;
+    uint32_t min;
+    if ((n & 0xE0) == 0xC0) {
+      remaining = 1;
+      min = 0x80;
+      n &= 0x1F;
+    } else if ((n & 0xF0) == 0xE0) {
+      remaining = 2;
+      min = 0x800;
+      n &= 0x0F;
+    } else if ((n & 0xF8) == 0xF0) {
+      remaining = 3;
+      min = 0x10000;
+      n &= 0x07;
+    } else {
+      // UTF-8 used to have a hyper-long encoding form, but it's been removed
+      // for years now.  So in this case, the string is not valid UTF-8.
+      return false;
+    }
+
+    // If the code point would require more code units than remain, the encoding
+    // is invalid.
+    if (s + remaining > limit) {
+      return false;
+    }
+
+    for (uint_fast8_t i = 0; i < remaining; i++) {
+      // Every non-leading code unit in properly encoded UTF-8 has its high bit
+      // set and the next-highest bit unset.
+      if ((s[i] & 0xC0) != 0x80) {
+        return false;
+      }
+
+      // The code point being encoded is the concatenation of all the
+      // unconstrained bits.
+      n = (n << 6) | (s[i] & 0x3F);
+    }
+
+    // Don't consider code points that are overlong, UTF-16 surrogates, or
+    // exceed the maximum code point to be valid.
+    if (n < min || (0xD800 <= n && n < 0xE000) || n >= 0x110000) {
+      return false;
+    }
+
+    s += remaining;
+  }
+
+  return true;
+}
new file mode 100644
--- /dev/null
+++ b/mfbt/Utf8.h
@@ -0,0 +1,210 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * UTF-8-related functionality, including a type-safe structure representing a
+ * UTF-8 code unit.
+ */
+
+#ifndef mozilla_Utf8_h
+#define mozilla_Utf8_h
+
+#include "mozilla/Types.h" // for MFBT_API
+
+#include <limits.h> // for CHAR_BIT
+#include <stddef.h> // for size_t
+#include <stdint.h> // for uint8_t
+
+namespace mozilla {
+
+union Utf8Unit;
+
+static_assert(CHAR_BIT == 8,
+              "Utf8Unit won't work so well with non-octet chars");
+
+/**
+ * A code unit within a UTF-8 encoded string.  (A code unit is the smallest
+ * unit within the Unicode encoding of a string.  For UTF-8 this is an 8-bit
+ * number; for UTF-16 it would be a 16-bit number.)
+ *
+ * This is *not* the same as a single code point: in UTF-8, non-ASCII code
+ * points are constituted by multiple code units.
+ */
+union Utf8Unit
+{
+private:
+  // Utf8Unit is a union wrapping a raw |char|.  The C++ object model and C++
+  // requirements as to how objects may be accessed with respect to their actual
+  // types (almost?) uniquely compel this choice.
+  //
+  // Our requirements for a UTF-8 code unit representation are:
+  //
+  //   1. It must be "compatible" with C++ character/string literals that use
+  //      the UTF-8 encoding.  Given a properly encoded C++ literal, you should
+  //      be able to use |Utf8Unit| and friends to access it; given |Utf8Unit|
+  //      and friends (particularly UnicodeData), you should be able to access
+  //      C++ character types for their contents.
+  //   2. |Utf8Unit| and friends must convert to/from |char| and |char*| only by
+  //      explicit operation.
+  //   3. |Utf8Unit| must participate in overload resolution and template type
+  //      equivalence (that is, given |template<class> class X|, when |X<T>| and
+  //      |X<U>| are the same type) distinctly from the C++ character types.
+  //
+  // And a few nice-to-haves (at least for the moment):
+  //
+  //   4. The representation should use unsigned numbers, to avoid undefined
+  //      behavior that can arise with signed types, and because Unicode code
+  //      points and code units are unsigned.
+  //   5. |Utf8Unit| and friends should be convertible to/from |unsigned char|
+  //      and |unsigned char*|, for APIs that (because of #4 above) use those
+  //      types as the "natural" choice for UTF-8 data.
+  //
+  // #1 requires that |Utf8Unit| "incorporate" a C++ character type: one of
+  // |{,{un,}signed} char|.[0]  |uint8_t| won't work because it might not be a
+  // C++ character type.
+  //
+  // #2 and #3 mean that |Utf8Unit| can't *be* such a type (or a typedef to one:
+  // typedefs don't generate *new* types, just type aliases).  This requires a
+  // compound type.
+  //
+  // The ultimate representation (and character type in it) is constrained by
+  // C++14 [basic.lval]p10 that defines how objects may be accessed, with
+  // respect to the dynamic type in memory and the actual type used to access
+  // them.  It reads:
+  //
+  //     If a program attempts to access the stored value of an object
+  //     through a glvalue of other than one of the following types the
+  //     behavior is undefined:
+  //
+  //       1. the dynamic type of the object,
+  //       2. a cv-qualified version of the dynamic type of the object,
+  //       ...other types irrelevant here...
+  //       3. an aggregate or union type that includes one of the
+  //          aforementioned types among its elements or non-static data
+  //          members (including, recursively, an element or non-static
+  //          data member of a subaggregate or contained union),
+  //       ...more irrelevant types...
+  //       4. a char or unsigned char type.
+  //
+  // Accessing (wrapped) UTF-8 data as |char|/|unsigned char| is allowed no
+  // matter the representation by #4.  (Briefly set aside what values are seen.)
+  // (And #2 allows |const| on either the dynamic type or the accessing type.)
+  // (|signed char| is really only useful for small signed numbers, not
+  // characters, so we ignore it.)
+  //
+  // If we interpret contents as |char|/|unsigned char| contrary to the actual
+  // type stored there, what happens?  C++14 [basic.fundamental]p1 requires
+  // character types be identically aligned/sized; C++14 [basic.fundamental]p3
+  // requires |signed char| and |unsigned char| have the same value
+  // representation.  C++ doesn't require identical bitwise representation, tho.
+  // Practically we could assume it, but this verges on C++ spec bits best not
+  // *relied* on for correctness, if possible.
+  //
+  // So we don't expose |Utf8Unit|'s contents as |unsigned char*|: only |char|
+  // and |char*|.  Instead we safely expose |unsigned char| by fully-defined
+  // *integral conversion* (C++14 [conv.integral]p2).  Integral conversion from
+  // |unsigned char| → |char| has only implementation-defined behavior.  It'd be
+  // better not to depend on that, but given twos-complement won, it should be
+  // okay.  (Also |unsigned char*| is awkward enough to work with for strings
+  // that it probably doesn't appear in string manipulation much anyway, only in
+  // places that should really use |Utf8Unit| directly.)
+  //
+  // The opposite direction -- interpreting |char| or |char*| data through
+  // |Utf8Unit| -- isn't tricky as long as |Utf8Unit| contains a |char| as
+  // decided above, using #3.  An "aggregate or union" will work that contains a
+  // |char|.  Oddly, an aggregate won't work: C++14 [dcl.init.aggr]p1 says
+  // aggregates must have "no private or protected non-static data members", and
+  // we want to keep the inner |char| hidden.  So a |struct| is out, and only
+  // |union| remains.
+  //
+  // (Enums are not "an aggregate or union type", so [maybe surprisingly] we
+  // can't make |Utf8Unit| an enum class with |char| underlying type, because we
+  // are given no license to treat |char| memory as such an |enum|'s memory.)
+  //
+  // Therefore |Utf8Unit| is a union type with a |char| non-static data member.
+  // This satisfies all our requirements.  It also supports the nice-to-haves of
+  // creating a |Utf8Unit| from an |unsigned char|, and being convertible to
+  // |unsigned char|.  It doesn't satisfy the nice-to-haves of using an
+  // |unsigned char| internally, nor of letting us wrap an existing
+  // |unsigned char| or pointer to one.  We probably *could* do these, if we
+  // were willing to rely harder on implementation-defined behaviors, but for
+  // now we privilege C++'s main character type over some conceptual purity.
+  //
+  // 0. There's a proposal for a UTF-8 character type distinct from the existing
+  //    C++ narrow character types:
+  //
+  //      http://open-std.org/JTC1/SC22/WG21/docs/papers/2016/p0482r0.html
+  //
+  //    but it hasn't been standardized (and might never be), and none of the
+  //    compilers we really care about have implemented it.  Maybe someday we
+  //    can change our implementation to it without too much trouble, if we're
+  //    lucky...
+  char mValue;
+
+public:
+  explicit constexpr Utf8Unit(char aUnit)
+    : mValue(aUnit)
+  {}
+
+  explicit constexpr Utf8Unit(unsigned char aUnit)
+    : mValue(static_cast<char>(aUnit))
+  {
+    // Per the above comment, the prior cast is integral conversion with
+    // implementation-defined semantics, and we regretfully but unavoidably
+    // assume the conversion does what we want it to.
+  }
+
+  constexpr bool operator==(const Utf8Unit& aOther) const
+  {
+    return mValue == aOther.mValue;
+  }
+
+  constexpr bool operator!=(const Utf8Unit& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+  /** Convert a UTF-8 code unit to a raw char. */
+  constexpr char toChar() const
+  {
+    // Only a |char| is ever permitted to be written into this location, so this
+    // is both permissible and returns the desired value.
+    return mValue;
+  }
+
+  /** Convert a UTF-8 code unit to a raw unsigned char. */
+  constexpr unsigned char toUnsignedChar() const
+  {
+    // Per the above comment, this is well-defined integral conversion.
+    return static_cast<unsigned char>(mValue);
+  }
+
+  /** Convert a UTF-8 code unit to a uint8_t. */
+  constexpr uint8_t toUint8() const
+  {
+    // Per the above comment, this is well-defined integral conversion.
+    return static_cast<uint8_t>(mValue);
+  }
+
+  // We currently don't expose |&mValue|.  |UnicodeData| sort of does, but
+  // that's a somewhat separate concern, justified in different comments in
+  // that other code.
+};
+
+/**
+ * Returns true if the given length-delimited memory consists of a valid UTF-8
+ * string, false otherwise.
+ *
+ * A valid UTF-8 string contains no overlong-encoded code points (as one would
+ * expect) and contains no code unit sequence encoding a UTF-16 surrogate.  The
+ * string *may* contain U+0000 NULL code points.
+ */
+extern MFBT_API bool
+IsValidUtf8(const void* aCodeUnits, size_t aCount);
+
+} // namespace mozilla
+
+#endif /* mozilla_Utf8_h */
--- a/mfbt/moz.build
+++ b/mfbt/moz.build
@@ -98,16 +98,17 @@ EXPORTS.mozilla = [
     'ToString.h',
     'Tuple.h',
     'TypedEnumBits.h',
     'Types.h',
     'TypeTraits.h',
     'UniquePtr.h',
     'UniquePtrExtensions.h',
     'Unused.h',
+    'Utf8.h',
     'Variant.h',
     'Vector.h',
     'WeakPtr.h',
     'WrappingOperations.h',
     'XorShift128PlusRNG.h',
 ]
 
 EXPORTS["double-conversion"] = [
@@ -141,16 +142,17 @@ UNIFIED_SOURCES += [
     'double-conversion/double-conversion/strtod.cc',
     'FloatingPoint.cpp',
     'HashFunctions.cpp',
     'JSONWriter.cpp',
     'Poison.cpp',
     'SHA1.cpp',
     'TaggedAnonymousMemory.cpp',
     'Unused.cpp',
+    'Utf8.cpp',
 ]
 
 DEFINES['IMPL_MFBT'] = True
 
 SOURCES += [
     'Compression.cpp',
     'decimal/Decimal.cpp',
     'lz4.c',
new file mode 100644
--- /dev/null
+++ b/mfbt/tests/TestUtf8.cpp
@@ -0,0 +1,115 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Utf8.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+
+using mozilla::ArrayLength;
+using mozilla::IsValidUtf8;
+using mozilla::Utf8Unit;
+
+static void
+TestUtf8Unit()
+{
+  Utf8Unit c('A');
+  MOZ_RELEASE_ASSERT(c.toChar() == 'A');
+  MOZ_RELEASE_ASSERT(c == Utf8Unit('A'));
+  MOZ_RELEASE_ASSERT(c != Utf8Unit('B'));
+  MOZ_RELEASE_ASSERT(c.toUint8() == 0x41);
+
+  unsigned char asUnsigned = 'A';
+  MOZ_RELEASE_ASSERT(c.toUnsignedChar() == asUnsigned);
+  MOZ_RELEASE_ASSERT(Utf8Unit('B').toUnsignedChar() != asUnsigned);
+
+  Utf8Unit first('@');
+  Utf8Unit second('#');
+
+  MOZ_RELEASE_ASSERT(first != second);
+
+  first = second;
+  MOZ_RELEASE_ASSERT(first == second);
+}
+
+static void
+TestIsValidUtf8()
+{
+  // Note we include the U+0000 NULL in this one -- and that's fine.
+  static const char asciiBytes[] = u8"How about a nice game of chess?";
+  MOZ_RELEASE_ASSERT(IsValidUtf8(asciiBytes, ArrayLength(asciiBytes)));
+
+  static const char endNonAsciiBytes[] = u8"Life is like a 🌯";
+  MOZ_RELEASE_ASSERT(IsValidUtf8(endNonAsciiBytes, ArrayLength(endNonAsciiBytes) - 1));
+
+  static const unsigned char badLeading[] = { 0x80 };
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(badLeading, ArrayLength(badLeading)));
+
+  // Byte-counts
+
+  // 1
+  static const char oneBytes[] = u8"A"; // U+0041 LATIN CAPITAL LETTER A
+  constexpr size_t oneBytesLen = ArrayLength(oneBytes);
+  static_assert(oneBytesLen == 2, "U+0041 plus nul");
+  MOZ_RELEASE_ASSERT(IsValidUtf8(oneBytes, oneBytesLen));
+
+  // 2
+  static const char twoBytes[] = u8"؆"; // U+0606 ARABIC-INDIC CUBE ROOT
+  constexpr size_t twoBytesLen = ArrayLength(twoBytes);
+  static_assert(twoBytesLen == 3, "U+0606 in two bytes plus nul");
+  MOZ_RELEASE_ASSERT(IsValidUtf8(twoBytes, twoBytesLen));
+
+  // 3
+  static const char threeBytes[] = u8"᨞"; // U+1A1E BUGINESE PALLAWA
+  constexpr size_t threeBytesLen = ArrayLength(threeBytes);
+  static_assert(threeBytesLen == 4, "U+1A1E in three bytes plus nul");
+  MOZ_RELEASE_ASSERT(IsValidUtf8(threeBytes, threeBytesLen));
+
+  // 4
+  static const char fourBytes[] = u8"🁡"; // U+1F061 DOMINO TILE HORIZONTAL-06-06
+  constexpr size_t fourBytesLen = ArrayLength(fourBytes);
+  static_assert(fourBytesLen == 5, "U+1F061 in four bytes plus nul");
+  MOZ_RELEASE_ASSERT(IsValidUtf8(fourBytes, fourBytesLen));
+
+  // Max code point
+  static const char maxCodePoint[] = u8"􏿿"; // U+10FFFF
+  constexpr size_t maxCodePointLen = ArrayLength(maxCodePoint);
+  static_assert(maxCodePointLen == 5, "U+10FFFF in four bytes plus nul");
+  MOZ_RELEASE_ASSERT(IsValidUtf8(maxCodePoint, maxCodePointLen));
+
+  // One past max code point
+  static unsigned const char onePastMaxCodePoint[] = { 0xF4, 0x90, 0x80, 0x80 };
+  constexpr size_t onePastMaxCodePointLen = ArrayLength(onePastMaxCodePoint);
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(onePastMaxCodePoint, onePastMaxCodePointLen));
+
+  // Surrogate-related testing
+
+  static const unsigned char justBeforeSurrogates[] = { 0xED, 0x9F, 0xBF };
+  MOZ_RELEASE_ASSERT(IsValidUtf8(justBeforeSurrogates, ArrayLength(justBeforeSurrogates)));
+
+  static const unsigned char leastSurrogate[] = { 0xED, 0xA0, 0x80 };
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(leastSurrogate, ArrayLength(leastSurrogate)));
+
+  static const unsigned char arbitraryHighSurrogate[] = { 0xED, 0xA2, 0x87 };
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryHighSurrogate, ArrayLength(arbitraryHighSurrogate)));
+
+  static const unsigned char arbitraryLowSurrogate[] = { 0xED, 0xB7, 0xAF };
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(arbitraryLowSurrogate, ArrayLength(arbitraryLowSurrogate)));
+
+  static const unsigned char greatestSurrogate[] = { 0xED, 0xBF, 0xBF };
+  MOZ_RELEASE_ASSERT(!IsValidUtf8(greatestSurrogate, ArrayLength(greatestSurrogate)));
+
+  static const unsigned char justAfterSurrogates[] = { 0xEE, 0x80, 0x80 };
+  MOZ_RELEASE_ASSERT(IsValidUtf8(justAfterSurrogates, ArrayLength(justAfterSurrogates)));
+}
+
+int
+main()
+{
+  TestUtf8Unit();
+  TestIsValidUtf8();
+  return 0;
+}
--- a/mfbt/tests/moz.build
+++ b/mfbt/tests/moz.build
@@ -54,16 +54,17 @@ CppUnitTests([
     'TestSPSCQueue',
     'TestTemplateLib',
     'TestTextUtils',
     'TestThreadSafeWeakPtr',
     'TestTuple',
     'TestTypedEnum',
     'TestTypeTraits',
     'TestUniquePtr',
+    'TestUtf8',
     'TestVariant',
     'TestVector',
     'TestWeakPtr',
     'TestWrappingOperations',
     'TestXorShift128PlusRNG',
 ])
 
 if not CONFIG['MOZ_ASAN']:
--- a/mobile/android/components/extensions/ext-android.js
+++ b/mobile/android/components/extensions/ext-android.js
@@ -5,17 +5,17 @@
 const getSender = (extension, target, sender) => {
   let tabId = -1;
   if ("tabId" in sender) {
     // The message came from a privileged extension page running in a tab. In
     // that case, it should include a tabId property (which is filled in by the
     // page-open listener below).
     tabId = sender.tabId;
     delete sender.tabId;
-  } else if (ChromeUtils.getClassName(target) == "XULElement") {
+  } else if (ChromeUtils.getClassName(target) == "XULFrameElement") {
     tabId = tabTracker.getBrowserData(target).tabId;
   }
 
   if (tabId != null && tabId >= 0) {
     let tab = extension.tabManager.get(tabId, null);
     if (tab) {
       sender.tab = tab.convert();
     }
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SpeechSynthesisService.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/SpeechSynthesisService.java
@@ -61,17 +61,19 @@ public class SpeechSynthesisService  {
                     registerVoice("moz-tts:android:" + localeStr, locale.getDisplayName(), localeStr.replace("_", "-"), !isLocal, defaultLocale == locale);
                 }
                 doneRegisteringVoices();
             }
         });
     }
 
     private static Set<Locale> getAvailableLanguages() {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            // While this method was introduced in 21, it seems that it
+            // has not been implemented in the speech service side until 23.
             return sTTS.getAvailableLanguages();
         }
         Set<Locale> locales = new HashSet<Locale>();
         for (Locale locale : Locale.getAvailableLocales()) {
             if (locale.getVariant().isEmpty() && sTTS.isLanguageAvailable(locale) > 0) {
                 locales.add(locale);
             }
         }
--- a/testing/mozharness/configs/android/android_common.py
+++ b/testing/mozharness/configs/android/android_common.py
@@ -41,17 +41,17 @@ config = {
         'verify-emulator',
         'install',
         'run-tests',
     ],
     "tooltool_cache": "/builds/worker/tooltool_cache",
     "tooltool_servers": ['http://relengapi/tooltool/'],
     "hostutils_manifest_path": "testing/config/tooltool-manifests/linux64/hostutils.manifest",
     "avds_dir": "/builds/worker/workspace/build/.android",
-    "log_format": "%(levelname)8s - %(message)s",
+    # "log_format": "%(levelname)8s - %(message)s",
     "log_tbpl_level": "info",
     "log_raw_level": "info",
     "minidump_stackwalk_path": "/usr/local/bin/linux64-minidump_stackwalk",
     "marionette_address": "localhost:2828",
     "marionette_test_manifest": "unit-tests.ini",
 
     "suite_definitions": {
         "mochitest": {
--- a/testing/mozharness/configs/unittests/linux_unittest.py
+++ b/testing/mozharness/configs/unittests/linux_unittest.py
@@ -282,10 +282,10 @@ config = {
                              },
     "download_minidump_stackwalk": True,
     "minidump_stackwalk_path": MINIDUMP_STACKWALK_PATH,
     "minidump_tooltool_manifest_path": TOOLTOOL_MANIFEST_PATH,
     "tooltool_cache": "/builds/worker/tooltool-cache",
     "download_nodejs": True,
     "nodejs_path": NODEJS_PATH,
     "nodejs_tooltool_manifest_path": NODEJS_TOOLTOOL_MANIFEST_PATH,
-    "log_format": "%(levelname)8s - %(message)s",
+    # "log_format": "%(levelname)8s - %(message)s",
 }
--- a/toolkit/content/tests/chrome/chrome.ini
+++ b/toolkit/content/tests/chrome/chrome.ini
@@ -106,16 +106,17 @@ skip-if = toolkit == "cocoa"
 [test_custom_element_base.xul]
 [test_deck.xul]
 [test_dialogfocus.xul]
 [test_findbar.xul]
 subsuite = clipboard
 [test_findbar_entireword.xul]
 [test_findbar_events.xul]
 [test_focus_anons.xul]
+[test_frames.xul]
 [test_hiddenitems.xul]
 [test_hiddenpaging.xul]
 [test_keys.xul]
 [test_labelcontrol.xul]
 [test_largemenu.xul]
 skip-if = os == 'linux' && !debug # Bug 1207174
 [test_maximized_persist.xul]
 support-files = window_maximized_persist.xul
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/chrome/test_frames.xul
@@ -0,0 +1,62 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin" type="text/css"?>
+<?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css" type="text/css"?>
+
+<window onload="setTimeout(runTest, 0);"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
+
+        <script><![CDATA[
+SimpleTest.waitForExplicitFinish();
+
+function runTest() {
+  for (let i = 1; i <= 3; i++) {
+    let frame = document.getElementById("frame" + i);
+    ok(frame instanceof XULFrameElement, "XULFrameElement " + i);
+
+    // Check the various fields to ensure that they have the correct type.
+    ok(frame.docShell instanceof Ci.nsIDocShell, "docShell " + i);
+    ok(frame.webNavigation instanceof Ci.nsIWebNavigation, "webNavigation " + i);
+
+    let contentWindow = frame.contentWindow;
+    let contentDocument = frame.contentDocument;
+    ok(contentWindow instanceof Window, "contentWindow " + i);
+    ok(contentDocument instanceof Document, "contentDocument " + i);
+    is(contentDocument.body.id, "thechildbody" + i, "right document body " + i);
+
+    // These fields should all be read-only.
+    frame.docShell = null;
+    ok(frame.docShell instanceof Ci.nsIDocShell, "docShell after set " + i);
+    frame.webNavigation = null;
+    ok(frame.webNavigation instanceof Ci.nsIWebNavigation, "webNavigation after set " + i);
+    frame.contentWindow = window;
+    is(frame.contentWindow, contentWindow, "contentWindow after set " + i);
+    frame.contentDocument = document;
+    is(frame.contentDocument, contentDocument, "contentDocument after set " + i);
+  }
+
+  // A non-frame element should not have these fields.
+  let button = document.getElementById("nonframe");
+  ok(!(button instanceof XULFrameElement), "XULFrameElement non frame");
+  is(button.docShell, undefined, "docShell non frame");
+  is(button.webNavigation, undefined, "webNavigation non frame");
+  is(button.contentWindow, undefined, "contentWindow non frame");
+  is(button.contentDocument, undefined, "contentDocument non frame");
+
+  SimpleTest.finish();
+}
+]]>
+</script>
+
+<iframe id="frame1" src="data:text/html,&lt;body id='thechildbody1'&gt;"/>
+<browser id="frame2" src="data:text/html,&lt;body id='thechildbody2'&gt;"/>
+<editor id="frame3" src="data:text/html,&lt;body id='thechildbody3'&gt;"/>
+<button id="nonframe"/>
+
+<body xmlns="http://www.w3.org/1999/xhtml">
+<div id="content" style="display: none"></div>
+</body>
+
+</window>
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -176,31 +176,16 @@
         <getter><![CDATA[
           return this._sameProcessAsFrameLoader && this._sameProcessAsFrameLoader.get();
         ]]></getter>
         <setter><![CDATA[
           this._sameProcessAsFrameLoader = Cu.getWeakReference(val);
         ]]></setter>
       </property>
 
-      <field name="_docShell">null</field>
-
-      <property name="docShell" readonly="true">
-        <getter><![CDATA[
-          if (this._docShell)
-            return this._docShell;
-
-          let {frameLoader} = this;
-          if (!frameLoader)
-            return null;
-          this._docShell = frameLoader.docShell;
-          return this._docShell;
-        ]]></getter>
-      </property>
-
       <field name="_loadContext">null</field>
 
       <property name="loadContext" readonly="true">
         <getter><![CDATA[
           if (this._loadContext)
             return this._loadContext;
 
           let {frameLoader} = this;
@@ -309,33 +294,16 @@
               return this.frameLoader.messageManager;
             }
             return null;
           ]]>
         </getter>
 
       </property>
 
-      <field name="_webNavigation">null</field>
-
-      <property name="webNavigation"
-                readonly="true">
-        <getter>
-        <![CDATA[
-          if (!this._webNavigation) {
-            if (!this.docShell) {
-              return null;
-            }
-            this._webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
-          }
-          return this._webNavigation;
-        ]]>
-        </getter>
-      </property>
-
       <field name="_webBrowserFind">null</field>
 
       <property name="webBrowserFind"
                 readonly="true">
         <getter>
         <![CDATA[
           if (!this._webBrowserFind)
             this._webBrowserFind = this.docShell.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIWebBrowserFind);
@@ -420,38 +388,28 @@
       </property>
 
       <field name="_lastSearchString">null</field>
 
       <property name="webProgress"
                 readonly="true"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebProgress);"/>
 
-      <field name="_contentWindow">null</field>
-
-      <property name="contentWindow"
-                readonly="true"
-                onget="return this._contentWindow || (this._contentWindow = this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow));"/>
-
       <property name="contentWindowAsCPOW"
                 readonly="true"
                 onget="return this.contentWindow;"/>
 
       <property name="sessionHistory"
                 onget="return this.webNavigation.sessionHistory;"
                 readonly="true"/>
 
       <property name="markupDocumentViewer"
                 onget="return this.docShell.contentViewer;"
                 readonly="true"/>
 
-      <property name="contentDocument"
-                onget="return this.webNavigation.document;"
-                readonly="true"/>
-
       <property name="contentDocumentAsCPOW"
                 onget="return this.contentDocument;"
                 readonly="true"/>
 
       <property name="contentTitle"
                 onget="return this.contentDocument.title;"
                 readonly="true"/>
 
@@ -1368,20 +1326,17 @@
 
           // We need to swap fields that are tied to our docshell or related to
           // the loaded page
           // Fields which are built as a result of notifactions (pageshow/hide,
           // DOMLinkAdded/Removed, onStateChange) should not be swapped here,
           // because these notifications are dispatched again once the docshells
           // are swapped.
           var fieldsToSwap = [
-            "_docShell",
             "_webBrowserFind",
-            "_contentWindow",
-            "_webNavigation"
           ];
 
           if (this.isRemoteBrowser) {
             fieldsToSwap.push(...[
               "_remoteWebNavigation",
               "_remoteWebNavigationImpl",
               "_remoteWebProgressManager",
               "_remoteWebProgress",
--- a/toolkit/content/widgets/editor.xml
+++ b/toolkit/content/widgets/editor.xml
@@ -112,33 +112,19 @@
         </getter>
       </property>
 
       <field name="_lastSearchString">null</field>
 
       <property name="editortype"
                 onget="return this.getAttribute('editortype');"
                 onset="this.setAttribute('editortype', val); return val;"/>
-      <property name="webNavigation"
-                onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
-                readonly="true"/>
-      <property name="contentDocument" readonly="true"
-                onget="return this.webNavigation.document;"/>
-      <property name="docShell" readonly="true">
-        <getter><![CDATA[
-          let {frameLoader} = this;
-          return frameLoader ? frameLoader.docShell : null;
-        ]]></getter>
-      </property>
       <property name="currentURI"
                 readonly="true"
                 onget="return this.webNavigation.currentURI;"/>
-      <property name="contentWindow"
-                readonly="true"
-                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
       <property name="contentWindowAsCPOW"
                 readonly="true"
                 onget="return this.contentWindow;"/>
       <property name="webBrowserFind"
                 readonly="true"
                 onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIWebBrowserFind);"/>
       <property name="markupDocumentViewer"
                 readonly="true"
--- a/toolkit/content/widgets/general.xml
+++ b/toolkit/content/widgets/general.xml
@@ -52,28 +52,9 @@
           ]]>
         </setter>
       </property>
 
       <field name="labelElement"/>
     </implementation>
   </binding>
 
-  <binding id="iframe">
-    <implementation>
-      <property name="docShell" readonly="true">
-        <getter><![CDATA[
-          let {frameLoader} = this;
-          return frameLoader ? frameLoader.docShell : null;
-        ]]></getter>
-      </property>
-      <property name="contentWindow"
-                readonly="true"
-                onget="return this.docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindow);"/>
-      <property name="webNavigation"
-                onget="return this.docShell.QueryInterface(Components.interfaces.nsIWebNavigation);"
-                readonly="true"/>
-      <property name="contentDocument" readonly="true"
-                onget="return this.webNavigation.document;"/>
-    </implementation>
-  </binding>
-
 </bindings>
--- a/toolkit/content/xul.css
+++ b/toolkit/content/xul.css
@@ -171,20 +171,16 @@ browser {
 browser[remote=true]:not(.lightweight) {
   -moz-binding: url("chrome://global/content/bindings/remote-browser.xml#remote-browser");
 }
 
 editor {
   -moz-binding: url("chrome://global/content/bindings/editor.xml#editor");
 }
 
-iframe {
-  -moz-binding: url("chrome://global/content/bindings/general.xml#iframe");
-}
-
 /********** notifications **********/
 
 notificationbox {
   -moz-binding: url("chrome://global/content/bindings/notification.xml#notificationbox");
   -moz-box-orient: vertical;
 }
 
 .notificationbox-stack {
--- a/toolkit/modules/addons/WebRequest.jsm
+++ b/toolkit/modules/addons/WebRequest.jsm
@@ -219,17 +219,17 @@ var ContentPolicyManager = {
   init() {
     Services.ppmm.initialProcessData.webRequestContentPolicies = this.policyData;
 
     Services.ppmm.addMessageListener("WebRequest:ShouldLoad", this);
     Services.mm.addMessageListener("WebRequest:ShouldLoad", this);
   },
 
   receiveMessage(msg) {
-    let browser = ChromeUtils.getClassName(msg.target) == "XULElement" ? msg.target : null;
+    let browser = ChromeUtils.getClassName(msg.target) == "XULFrameElement" ? msg.target : null;
 
     let requestId = `fakeRequest-${++nextFakeRequestId}`;
     for (let id of msg.data.ids) {
       let callback = this.policies.get(id);
       if (!callback) {
         // It's possible that this listener has been removed and the
         // child hasn't learned yet.
         continue;
--- a/toolkit/modules/addons/WebRequestContent.js
+++ b/toolkit/modules/addons/WebRequestContent.js
@@ -123,17 +123,17 @@ var ContentPolicy = {
       return window.QueryInterface(Ci.nsIInterfaceRequestor)
         .getInterface(Ci.nsIDOMWindowUtils)
         .outerWindowID;
     }
 
     let node = loadInfo.loadingContext;
     if (node &&
         (policyType == Ci.nsIContentPolicy.TYPE_SUBDOCUMENT ||
-         (ChromeUtils.getClassName(node) == "XULElement" &&
+        (ChromeUtils.getClassName(node) == "XULFrameElement" &&
           node.localName == "browser"))) {
       // Chrome sets frameId to the ID of the sub-window. But when
       // Firefox loads an iframe, it sets |node| to the <iframe>
       // element, whose window is the parent window. We adopt the
       // Chrome behavior here.
       node = node.contentWindow;
     }