Bug 467035 - Avoid leaking browser language via DTD r=Gijs,bzbarsky
authorAlex Catarineu <acat@torproject.org>
Mon, 08 Jul 2019 10:47:05 +0000
changeset 481641 9276e88ea3fed58b881ae3eff4580d04e54a6e14
parent 481640 bdec71eff3a6a92dee514cc049f8a3d04609936f
child 481642 a07109e6cefea72b8ae9b884cb21d16eacd78ae6
push id36261
push usernbeleuzu@mozilla.com
push dateTue, 09 Jul 2019 03:44:14 +0000
treeherdermozilla-central@b782ed36b2e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersGijs, bzbarsky
bugs467035
milestone69.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 467035 - Avoid leaking browser language via DTD r=Gijs,bzbarsky Differential Revision: https://phabricator.services.mozilla.com/D34187
browser/base/content/test/static/browser_misused_characters_in_strings.js
browser/components/payments/test/mochitest/formautofill/mochitest.ini
dom/base/DOMParser.cpp
dom/base/DOMParser.h
dom/base/Document.cpp
dom/base/Document.h
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/security/nsContentSecurityManager.cpp
dom/tests/mochitest/bugs/mochitest.ini
dom/tests/mochitest/bugs/test_bug467035.html
dom/webidl/DOMParser.webidl
parser/htmlparser/nsExpatDriver.cpp
testing/marionette/l10n.js
testing/marionette/puppeteer/firefox/firefox_puppeteer/api/l10n.py
toolkit/content/widgets/datetimebox.js
toolkit/content/widgets/pluginProblem.js
toolkit/content/widgets/videocontrols.js
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -267,16 +267,17 @@ add_task(async function checkAllTheDTDs(
 
 add_task(async function checkAllTheFluents() {
   let uris = await getAllTheFiles(".ftl");
   let { FluentResource } = ChromeUtils.import(
     "resource://gre/modules/Fluent.jsm",
     {}
   );
   let domParser = new DOMParser();
+  domParser.forceEnableDTD();
   for (let uri of uris) {
     let rawContents = await fetchFile(uri.spec);
     let resource = FluentResource.fromString(rawContents);
     if (!resource) {
       return;
     }
 
     for (let [key, val] of resource) {
--- a/browser/components/payments/test/mochitest/formautofill/mochitest.ini
+++ b/browser/components/payments/test/mochitest/formautofill/mochitest.ini
@@ -1,9 +1,10 @@
 [DEFAULT]
 # This manifest mostly exists so that the support-files below can be referenced
 # from a relative path of formautofill/* from the tests in the above directory
 # to resemble the layout in the shipped JAR file.
 support-files =
    ../../../../../../browser/extensions/formautofill/content/editCreditCard.xhtml
    ../../../../../../browser/extensions/formautofill/content/editAddress.xhtml
 
+skip-if = true # Bug 1446164
 [test_editCreditCard.html]
--- a/dom/base/DOMParser.cpp
+++ b/dom/base/DOMParser.cpp
@@ -28,17 +28,18 @@ using namespace mozilla;
 using namespace mozilla::dom;
 
 DOMParser::DOMParser(nsIGlobalObject* aOwner, nsIPrincipal* aDocPrincipal,
                      nsIURI* aDocumentURI, nsIURI* aBaseURI)
     : mOwner(aOwner),
       mPrincipal(aDocPrincipal),
       mDocumentURI(aDocumentURI),
       mBaseURI(aBaseURI),
-      mForceEnableXULXBL(false) {
+      mForceEnableXULXBL(false),
+      mForceEnableDTD(false) {
   MOZ_ASSERT(aDocPrincipal);
   MOZ_ASSERT(aDocumentURI);
 }
 
 DOMParser::~DOMParser() {}
 
 // QueryInterface implementation for DOMParser
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DOMParser)
@@ -64,16 +65,20 @@ already_AddRefed<Document> DOMParser::Pa
       return nullptr;
     }
 
     // Keep the XULXBL state in sync with the XML case.
     if (mForceEnableXULXBL) {
       document->ForceEnableXULXBL();
     }
 
+    if (mForceEnableDTD) {
+      document->ForceSkipDTDSecurityChecks();
+    }
+
     nsresult rv = nsContentUtils::ParseDocumentHTML(aStr, document, false);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       aRv.Throw(rv);
       return nullptr;
     }
 
     return document.forget();
   }
@@ -178,16 +183,20 @@ already_AddRefed<Document> DOMParser::Pa
   // Tell the document to start loading
   nsCOMPtr<nsIStreamListener> listener;
 
   // Keep the XULXBL state in sync with the HTML case
   if (mForceEnableXULXBL) {
     document->ForceEnableXULXBL();
   }
 
+  if (mForceEnableDTD) {
+    document->ForceSkipDTDSecurityChecks();
+  }
+
   // Have to pass false for reset here, else the reset will remove
   // our event listener.  Should that listener addition move to later
   // than this call?
   nsresult rv =
       document->StartDocumentLoad(kLoadAsData, parserChannel, nullptr, nullptr,
                                   getter_AddRefs(listener), false);
 
   if (NS_FAILED(rv) || !listener) {
--- a/dom/base/DOMParser.h
+++ b/dom/base/DOMParser.h
@@ -48,17 +48,22 @@ class DOMParser final : public nsISuppor
                                              ErrorResult& aRv);
 
   already_AddRefed<Document> ParseFromStream(nsIInputStream* aStream,
                                              const nsAString& aCharset,
                                              int32_t aContentLength,
                                              SupportedType aType,
                                              ErrorResult& aRv);
 
-  void ForceEnableXULXBL() { mForceEnableXULXBL = true; }
+  void ForceEnableXULXBL() {
+    mForceEnableXULXBL = true;
+    ForceEnableDTD();
+  }
+
+  void ForceEnableDTD() { mForceEnableDTD = true; }
 
   nsIGlobalObject* GetParentObject() const { return mOwner; }
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override {
     return mozilla::dom::DOMParser_Binding::Wrap(aCx, this, aGivenProto);
   }
 
@@ -73,14 +78,15 @@ class DOMParser final : public nsISuppor
                                            ErrorResult& aRv);
 
   nsCOMPtr<nsIGlobalObject> mOwner;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsCOMPtr<nsIURI> mDocumentURI;
   nsCOMPtr<nsIURI> mBaseURI;
 
   bool mForceEnableXULXBL;
+  bool mForceEnableDTD;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -1295,16 +1295,17 @@ Document::Document(const char* aContentT
 #ifdef MOZILLA_INTERNAL_API
       mVisibilityState(dom::VisibilityState::Hidden),
 #else
       mDummy(0),
 #endif
       mType(eUnknown),
       mDefaultElementType(0),
       mAllowXULXBL(eTriUnset),
+      mSkipDTDSecurityChecks(false),
       mBidiOptions(IBMBIDI_DEFAULT_BIDI_OPTIONS),
       mSandboxFlags(0),
       mPartID(0),
       mMarkedCCGeneration(0),
       mPresShell(nullptr),
       mSubtreeModifiedDepth(0),
       mPreloadPictureDepth(0),
       mEventsSuppressed(0),
@@ -2282,48 +2283,16 @@ void Document::Reset(nsIChannel* aChanne
       mDocumentBaseURI = baseURI;
       mChromeXHRDocBaseURI = nullptr;
     }
   }
 
   mChannel = aChannel;
 }
 
-/**
- * Determine whether the principal is allowed access to the localization system.
- * We don't want the web to ever see this but all our UI including in content
- * pages should pass this test.
- */
-bool PrincipalAllowsL10n(nsIPrincipal* principal) {
-  // The system principal is always allowed.
-  if (nsContentUtils::IsSystemPrincipal(principal)) {
-    return true;
-  }
-
-  nsCOMPtr<nsIURI> uri;
-  nsresult rv = principal->GetURI(getter_AddRefs(uri));
-  NS_ENSURE_SUCCESS(rv, false);
-
-  bool hasFlags;
-
-  // Allow access to uris that cannot be loaded by web content.
-  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
-                           &hasFlags);
-  NS_ENSURE_SUCCESS(rv, false);
-  if (hasFlags) {
-    return true;
-  }
-
-  // UI resources also get access.
-  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
-                           &hasFlags);
-  NS_ENSURE_SUCCESS(rv, false);
-  return hasFlags;
-}
-
 void Document::DisconnectNodeTree() {
   // Delete references to sub-documents and kill the subdocument map,
   // if any. This is not strictly needed, but makes the node tree
   // teardown a bit faster.
   delete mSubDocuments;
   mSubDocuments = nullptr;
 
   // Destroy link map now so we don't waste time removing
@@ -3647,21 +3616,21 @@ void Document::InitializeLocalization(ns
   mDocumentL10n = l10n;
 }
 
 DocumentL10n* Document::GetL10n() { return mDocumentL10n; }
 
 bool Document::DocumentSupportsL10n(JSContext* aCx, JSObject* aObject) {
   nsCOMPtr<nsIPrincipal> callerPrincipal =
       nsContentUtils::SubjectPrincipal(aCx);
-  return PrincipalAllowsL10n(callerPrincipal);
+  return nsContentUtils::PrincipalAllowsL10n(callerPrincipal);
 }
 
 void Document::LocalizationLinkAdded(Element* aLinkElement) {
-  if (!PrincipalAllowsL10n(NodePrincipal())) {
+  if (!nsContentUtils::PrincipalAllowsL10n(NodePrincipal())) {
     return;
   }
 
   nsAutoString href;
   aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
   // If the link is added after the DocumentL10n instance
   // has been initialized, just pass the resource ID to it.
   if (mDocumentL10n) {
@@ -3689,17 +3658,17 @@ void Document::LocalizationLinkAdded(Ele
       BlockOnload();
     }
 
     mPendingInitialTranslation = true;
   }
 }
 
 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
-  if (!PrincipalAllowsL10n(NodePrincipal())) {
+  if (!nsContentUtils::PrincipalAllowsL10n(NodePrincipal())) {
     return;
   }
 
   nsAutoString href;
   aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
   if (mDocumentL10n) {
     AutoTArray<nsString, 1> resourceIds;
     resourceIds.AppendElement(href);
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -2992,18 +2992,26 @@ class Document : public nsINode,
    * use non-builtin XBL bindings.
    */
   bool AllowXULXBL() {
     return mAllowXULXBL == eTriTrue
                ? true
                : mAllowXULXBL == eTriFalse ? false : InternalAllowXULXBL();
   }
 
+  /**
+   * Returns true if this document is allowed to load DTDs from UI resources
+   * no matter what.
+   */
+  bool SkipDTDSecurityChecks() { return mSkipDTDSecurityChecks; }
+
   void ForceEnableXULXBL() { mAllowXULXBL = eTriTrue; }
 
+  void ForceSkipDTDSecurityChecks() { mSkipDTDSecurityChecks = true; }
+
   /**
    * Returns the template content owner document that owns the content of
    * HTMLTemplateElement.
    */
   Document* GetTemplateContentsOwner();
 
   /**
    * Returns true if this document is a static clone of a normal document.
@@ -4806,16 +4814,18 @@ class Document : public nsINode,
   Type mType;
 
   uint8_t mDefaultElementType;
 
   enum Tri { eTriUnset = 0, eTriFalse, eTriTrue };
 
   Tri mAllowXULXBL;
 
+  bool mSkipDTDSecurityChecks;
+
   // The document's script global object, the object from which the
   // document can get its script context and scope. This is the
   // *inner* window object.
   nsCOMPtr<nsIScriptGlobalObject> mScriptGlobalObject;
 
   // If mIsStaticDocument is true, mOriginalDocument points to the original
   // document.
   RefPtr<Document> mOriginalDocument;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -1680,16 +1680,44 @@ bool nsContentUtils::OfflineAppAllowed(n
   }
 
   bool allowed;
   nsresult rv = updateService->OfflineAppAllowed(
       aPrincipal, Preferences::GetRootBranch(), &allowed);
   return NS_SUCCEEDED(rv) && allowed;
 }
 
+/* static */
+bool nsContentUtils::PrincipalAllowsL10n(nsIPrincipal* aPrincipal) {
+  // The system principal is always allowed.
+  if (IsSystemPrincipal(aPrincipal)) {
+    return true;
+  }
+
+  nsCOMPtr<nsIURI> uri;
+  nsresult rv = aPrincipal->GetURI(getter_AddRefs(uri));
+  NS_ENSURE_SUCCESS(rv, false);
+
+  bool hasFlags;
+
+  // Allow access to uris that cannot be loaded by web content.
+  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_DANGEROUS_TO_LOAD,
+                           &hasFlags);
+  NS_ENSURE_SUCCESS(rv, false);
+  if (hasFlags) {
+    return true;
+  }
+
+  // UI resources also get access.
+  rv = NS_URIChainHasFlags(uri, nsIProtocolHandler::URI_IS_UI_RESOURCE,
+                           &hasFlags);
+  NS_ENSURE_SUCCESS(rv, false);
+  return hasFlags;
+}
+
 bool nsContentUtils::MaybeAllowOfflineAppByDefault(nsIPrincipal* aPrincipal) {
   if (!Preferences::GetRootBranch()) return false;
 
   nsresult rv;
 
   bool allowedByDefault;
   rv = Preferences::GetRootBranch()->GetBoolPref(
       "offline-apps.allow_by_default", &allowedByDefault);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1972,16 +1972,23 @@ class nsContentUtils {
   static bool OfflineAppAllowed(nsIURI* aURI);
 
   /**
    * Check whether an application should be allowed to use offline APIs.
    */
   static bool OfflineAppAllowed(nsIPrincipal* aPrincipal);
 
   /**
+   * Determine whether the principal is allowed access to the localization
+   * system. We don't want the web to ever see this but all our UI including in
+   * content pages should pass this test.
+   */
+  static bool PrincipalAllowsL10n(nsIPrincipal* aPrincipal);
+
+  /**
    * If offline-apps.allow_by_default is true, we set offline-app permission
    * for the principal and return true.  Otherwise false.
    */
   static bool MaybeAllowOfflineAppByDefault(nsIPrincipal* aPrincipal);
 
   /**
    * Increases the count of blockers preventing scripts from running.
    * NOTE: You might want to use nsAutoScriptBlocker rather than calling
--- a/dom/security/nsContentSecurityManager.cpp
+++ b/dom/security/nsContentSecurityManager.cpp
@@ -323,18 +323,30 @@ static bool IsImageLoadInEditorAppType(n
   if (docShell) {
     appType = docShell->GetAppType();
   }
 
   return appType == nsIDocShell::APP_TYPE_EDITOR;
 }
 
 static nsresult DoCheckLoadURIChecks(nsIURI* aURI, nsILoadInfo* aLoadInfo) {
-  // Bug 1228117: determine the correct security policy for DTD loads
-  if (aLoadInfo->GetExternalContentPolicyType() == nsIContentPolicy::TYPE_DTD) {
+  // In practice, these DTDs are just used for localization, so applying the
+  // same principal check as Fluent.
+  if (aLoadInfo->InternalContentPolicyType() ==
+      nsIContentPolicy::TYPE_INTERNAL_DTD) {
+    return nsContentUtils::PrincipalAllowsL10n(aLoadInfo->TriggeringPrincipal())
+               ? NS_OK
+               : NS_ERROR_DOM_BAD_URI;
+  }
+
+  // This is used in order to allow a privileged DOMParser to parse documents
+  // that need to access localization DTDs. We just allow through
+  // TYPE_INTERNAL_FORCE_ALLOWED_DTD no matter what the triggering principal is.
+  if (aLoadInfo->InternalContentPolicyType() ==
+      nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD) {
     return NS_OK;
   }
 
   if (IsImageLoadInEditorAppType(aLoadInfo)) {
     return NS_OK;
   }
 
   uint32_t flags = nsIScriptSecurityManager::STANDARD;
--- a/dom/tests/mochitest/bugs/mochitest.ini
+++ b/dom/tests/mochitest/bugs/mochitest.ini
@@ -149,10 +149,11 @@ skip-if = toolkit == 'android' #Windows 
 [test_window_bar.html]
 skip-if = toolkit == 'android'
 [test_bug1022869.html]
 [test_bug1112040.html]
 [test_bug1160342_marquee.html]
 [test_bug1171215.html]
 support-files = window_bug1171215.html
 [test_bug1530292.html]
+[test_bug467035.html]
 [test_no_find_showDialog.html]
 skip-if = toolkit == 'android' # Bug 1358633 - window.find doesn't work for Android
new file mode 100644
--- /dev/null
+++ b/dom/tests/mochitest/bugs/test_bug467035.html
@@ -0,0 +1,45 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=467035
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 467035</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+
+  /** Test for Bug 467035 **/
+  SimpleTest.waitForExplicitFinish();
+  addLoadEvent(() => {
+    const s = `<!DOCTYPE html SYSTEM \"chrome://branding/locale/brand.dtd\">
+      <html xmlns=\"http://www.w3.org/1999/xhtml\">
+        <head>
+          <meta charset=\"utf-8\"/>
+          <title>&brandShortName;</title>
+        </head>
+      </html>`;
+
+    const parser = new DOMParser();
+    let doc = parser.parseFromString(s, 'application/xhtml+xml');
+    is(doc.getElementsByTagName('parsererror').length, 1, 'parseFromString cannot access locale DTD');
+
+    SpecialPowers.wrap(parser).forceEnableDTD();
+    doc = parser.parseFromString(s, 'application/xhtml+xml');
+    const isTitleLocalized = doc.getElementsByTagName('parsererror').length === 0 &&
+      typeof doc.title === 'string' &&
+      !!doc.title;
+    ok(isTitleLocalized, 'parseFromString can access locale DTD with forceEnableDTD');
+
+    SimpleTest.finish();
+  });
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=467035">Mozilla Bug 467035</a>
+<p id="display"></p>
+<pre id="test">
+</pre>
+</body>
+</html>
--- a/dom/webidl/DOMParser.webidl
+++ b/dom/webidl/DOMParser.webidl
@@ -31,10 +31,15 @@ interface DOMParser {
   Document parseFromBuffer(Uint8Array buf, SupportedType type);
   [NewObject, Throws, ChromeOnly]
   Document parseFromStream(InputStream stream, DOMString? charset,
                            long contentLength, SupportedType type);
   // Can be used to allow a DOMParser to parse XUL/XBL no matter what
   // principal it's using for the document.
   [ChromeOnly]
   void forceEnableXULXBL();
+
+  // Can be used to allow a DOMParser to load DTDs from URLs that
+  // normally would not be allowed based on the document principal.
+  [Func="IsChromeOrXBLOrUAWidget"]
+    void forceEnableDTD();
 };
 
--- a/parser/htmlparser/nsExpatDriver.cpp
+++ b/parser/htmlparser/nsExpatDriver.cpp
@@ -638,32 +638,36 @@ nsresult nsExpatDriver::OpenInputStreamF
                        nsContentUtils::GetSystemPrincipal(),
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                        nsIContentPolicy::TYPE_DTD);
   } else {
     NS_ASSERTION(
         mSink == nsCOMPtr<nsIExpatSink>(do_QueryInterface(mOriginalSink)),
         "In nsExpatDriver::OpenInputStreamFromExternalDTD: "
         "mOriginalSink not the same object as mSink?");
+    nsContentPolicyType policyType = nsIContentPolicy::TYPE_INTERNAL_DTD;
     nsCOMPtr<nsIPrincipal> loadingPrincipal;
     if (mOriginalSink) {
       nsCOMPtr<Document> doc;
       doc = do_QueryInterface(mOriginalSink->GetTarget());
       if (doc) {
         loadingPrincipal = doc->NodePrincipal();
+        if (doc->SkipDTDSecurityChecks()) {
+          policyType = nsIContentPolicy::TYPE_INTERNAL_FORCE_ALLOWED_DTD;
+        }
       }
     }
     if (!loadingPrincipal) {
       loadingPrincipal =
           mozilla::NullPrincipal::CreateWithoutOriginAttributes();
     }
     rv = NS_NewChannel(getter_AddRefs(channel), uri, loadingPrincipal,
                        nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS |
                            nsILoadInfo::SEC_ALLOW_CHROME,
-                       nsIContentPolicy::TYPE_DTD);
+                       policyType);
   }
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoCString absURL;
   rv = uri->GetSpec(absURL);
   NS_ENSURE_SUCCESS(rv, rv);
   CopyUTF8toUTF16(absURL, aAbsURL);
 
--- a/testing/marionette/l10n.js
+++ b/testing/marionette/l10n.js
@@ -16,17 +16,21 @@
  */
 
 const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 const { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
 
 XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]);
-XPCOMUtils.defineLazyGetter(this, "domParser", () => new DOMParser());
+XPCOMUtils.defineLazyGetter(this, "domParser", () => {
+  const parser = new DOMParser();
+  parser.forceEnableDTD();
+  return parser;
+});
 
 const { NoSuchElementError } = ChromeUtils.import(
   "chrome://marionette/content/error.js"
 );
 
 this.EXPORTED_SYMBOLS = ["l10n"];
 
 /** @namespace */
--- a/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/l10n.py
+++ b/testing/marionette/puppeteer/firefox/firefox_puppeteer/api/l10n.py
@@ -70,16 +70,17 @@ class L10n(BaseLib):
                 <!DOCTYPE elem [%s]>
 
                 <elem id="entity">&%s;</elem>""" % (dtd_refs, entity_id)
 
             with self.marionette.using_context('chrome'):
                 value = self.marionette.execute_script("""
                     Cu.importGlobalProperties(["DOMParser"]);
                     var parser = new DOMParser();
+                    parser.forceEnableDTD();
                     var doc = parser.parseFromString(arguments[0], "text/xml");
                     var node = doc.querySelector("elem[id='entity']");
 
                     return node ? node.textContent : null;
                 """, script_args=[contents])
 
             if not value:
                 raise NoSuchElementException('DTD Entity not found: %s' % entity_id)
--- a/toolkit/content/widgets/datetimebox.js
+++ b/toolkit/content/widgets/datetimebox.js
@@ -138,16 +138,17 @@ this.DateTimeInputBaseImplWidget = class
   }
 
   generateContent() {
     /*
      * Pass the markup through XML parser purely for the reason of loading the localization DTD.
      * Remove it when migrate to Fluent (bug 1504363).
      */
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `<!DOCTYPE bindings [
       <!ENTITY % datetimeboxDTD SYSTEM "chrome://global/locale/datetimebox.dtd">
       %datetimeboxDTD;
       ]>
       <div class="datetimebox" xmlns="http://www.w3.org/1999/xhtml" role="none">
         <link rel="stylesheet" type="text/css" href="chrome://global/content/bindings/datetimebox.css" />
         <div class="datetime-input-box-wrapper" id="input-box-wrapper" role="presentation">
--- a/toolkit/content/widgets/pluginProblem.js
+++ b/toolkit/content/widgets/pluginProblem.js
@@ -12,16 +12,17 @@ this.PluginProblemWidget = class {
     this.shadowRoot = shadowRoot;
     this.element = shadowRoot.host;
     // ownerGlobal is chrome-only, not accessible to UA Widget script here.
     this.window = this.element.ownerDocument.defaultView; // eslint-disable-line mozilla/use-ownerGlobal
   }
 
   onsetup() {
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `
       <!DOCTYPE bindings [
         <!ENTITY % pluginproblemDTD SYSTEM "chrome://pluginproblem/locale/pluginproblem.dtd">
         <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd">
         <!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd" >
         %pluginproblemDTD;
         %globalDTD;
--- a/toolkit/content/widgets/videocontrols.js
+++ b/toolkit/content/widgets/videocontrols.js
@@ -2541,16 +2541,17 @@ this.VideoControlsImplWidget = class {
   }
 
   generateContent() {
     /*
      * Pass the markup through XML parser purely for the reason of loading the localization DTD.
      * Remove it when migrate to Fluent.
      */
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `<!DOCTYPE bindings [
       <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
       %videocontrolsDTD;
       ]>
       <div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
         <link rel="stylesheet" type="text/css" href="chrome://global/skin/media/videocontrols.css" />
         <div id="controlsContainer" class="controlsContainer" role="none">
@@ -2788,16 +2789,17 @@ this.NoControlsMobileImplWidget = class 
   }
 
   generateContent() {
     /*
      * Pass the markup through XML parser purely for the reason of loading the localization DTD.
      * Remove it when migrate to Fluent.
      */
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `<!DOCTYPE bindings [
       <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
       %videocontrolsDTD;
       ]>
       <div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
         <link rel="stylesheet" type="text/css" href="chrome://global/skin/media/videocontrols.css" />
         <div id="controlsContainer" class="controlsContainer" role="none" hidden="true">
@@ -2838,16 +2840,17 @@ this.NoControlsPictureInPictureImplWidge
   destructor() {}
 
   generateContent() {
     /*
      * Pass the markup through XML parser purely for the reason of loading the localization DTD.
      * Remove it when migrate to Fluent.
      */
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `<!DOCTYPE bindings [
       <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
       %videocontrolsDTD;
       ]>
       <div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
         <link rel="stylesheet" type="text/css" href="chrome://global/skin/media/videocontrols.css" />
         <div id="controlsContainer" class="controlsContainer" role="none">
@@ -2975,16 +2978,17 @@ this.NoControlsDesktopImplWidget = class
   }
 
   generateContent() {
     /*
      * Pass the markup through XML parser purely for the reason of loading the localization DTD.
      * Remove it when migrate to Fluent.
      */
     const parser = new this.window.DOMParser();
+    parser.forceEnableDTD();
     let parserDoc = parser.parseFromString(
       `<!DOCTYPE bindings [
       <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
       %videocontrolsDTD;
       ]>
       <div class="videocontrols" xmlns="http://www.w3.org/1999/xhtml" role="none">
         <link rel="stylesheet" type="text/css" href="chrome://global/skin/media/videocontrols.css" />
         <div id="controlsContainer" class="controlsContainer" role="none">