Bug 1581537 - Avoid several browser language leaks r=smaug
authorAlex Catarineu <acat@torproject.org>
Mon, 04 Nov 2019 16:56:27 +0000
changeset 500409 0ed7aa23fde578923a1d7b26bb9d4aade4efae79
parent 500408 5967bf6335748dbdeca580449b34c20a45d8a64a
child 500410 e76c8ca10ca21ab957a6c4a9dfdbb14ab1f1047f
push id114164
push useraiakab@mozilla.com
push dateTue, 05 Nov 2019 10:06:15 +0000
treeherdermozilla-inbound@4d585c7edc76 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1581537
milestone72.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 1581537 - Avoid several browser language leaks r=smaug Spoof dom/dom.properties, layout/xmlparser.properties, layout/MediaDocument.properties to en-US if needed. Differential Revision: https://phabricator.services.mozilla.com/D46034
browser/base/content/test/static/browser_misused_characters_in_strings.js
browser/installer/package-manifest.in
dom/base/Document.cpp
dom/base/Document.h
dom/base/nsContentUtils.cpp
dom/base/nsContentUtils.h
dom/html/HTMLInputElement.cpp
dom/html/HTMLSelectElement.cpp
dom/html/HTMLTextAreaElement.cpp
dom/html/ImageDocument.cpp
dom/html/MediaDocument.cpp
dom/html/MediaDocument.h
dom/html/input/CheckableInputTypes.cpp
dom/html/input/DateTimeInputTypes.cpp
dom/html/input/FileInputType.cpp
dom/html/input/InputType.cpp
dom/html/input/NumericInputTypes.cpp
dom/html/input/SingleLineTextInputTypes.cpp
dom/locales/moz.build
layout/base/nsCSSFrameConstructor.cpp
layout/forms/nsFileControlFrame.cpp
layout/forms/nsGfxButtonControlFrame.cpp
layout/generic/DetailsFrame.cpp
mobile/android/installer/package-manifest.in
parser/htmlparser/nsExpatDriver.cpp
parser/htmlparser/nsParserMsgUtils.h
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -94,16 +94,22 @@ let gWhitelist = [
     key: "CommandNotInChrome",
     type: "double-quote",
   },
   {
     file: "dom.properties",
     key: "PatternAttributeCompileFailure",
     type: "single-quote",
   },
+  // dom.properties is packaged twice so we need to have two exceptions for this string.
+  {
+    file: "dom.properties",
+    key: "PatternAttributeCompileFailure",
+    type: "single-quote",
+  },
   {
     file: "netError.dtd",
     key: "inadequateSecurityError.longDesc",
     type: "single-quote",
   },
   {
     file: "netErrorApp.dtd",
     key: "securityOverride.warningContent",
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -321,16 +321,19 @@
 @RESPATH@/res/grabber.gif
 #ifdef XP_MACOSX
 @RESPATH@/res/cursors/*
 #endif
 @RESPATH@/res/fonts/*
 @RESPATH@/res/dtd/*
 @RESPATH@/res/language.properties
 @RESPATH@/res/locale/layout/HtmlForm.properties
+@RESPATH@/res/locale/layout/MediaDocument.properties
+@RESPATH@/res/locale/layout/xmlparser.properties
+@RESPATH@/res/locale/dom/dom.properties
 #ifdef XP_MACOSX
 @RESPATH@/res/MainMenu.nib/
 #endif
 
 ; Content-accessible resources.
 @RESPATH@/contentaccessible/*
 
 ; svg
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -3648,17 +3648,17 @@ bool Document::DocumentSupportsL10n(JSCo
   nsCOMPtr<nsIPrincipal> callerPrincipal =
       nsContentUtils::SubjectPrincipal(aCx);
   nsGlobalWindowInner* win = xpc::WindowOrNull(aObject);
   return nsContentUtils::PrincipalAllowsL10n(
       callerPrincipal, win ? win->GetDocumentURI() : nullptr);
 }
 
 void Document::LocalizationLinkAdded(Element* aLinkElement) {
-  if (!nsContentUtils::PrincipalAllowsL10n(NodePrincipal(), GetDocumentURI())) {
+  if (!AllowsL10n()) {
     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) {
@@ -3686,17 +3686,17 @@ void Document::LocalizationLinkAdded(Ele
       BlockOnload();
     }
 
     mPendingInitialTranslation = true;
   }
 }
 
 void Document::LocalizationLinkRemoved(Element* aLinkElement) {
-  if (!nsContentUtils::PrincipalAllowsL10n(NodePrincipal(), GetDocumentURI())) {
+  if (!AllowsL10n()) {
     return;
   }
 
   nsAutoString href;
   aLinkElement->GetAttr(kNameSpaceID_None, nsGkAtoms::href, href);
   if (mDocumentL10n) {
     AutoTArray<nsString, 1> resourceIds;
     resourceIds.AppendElement(href);
@@ -3752,16 +3752,20 @@ void Document::InitialDocumentTranslatio
   mL10nProtoElements.Clear();
 
   nsXULPrototypeDocument* proto = GetPrototype();
   if (proto) {
     proto->SetIsL10nCached();
   }
 }
 
+bool Document::AllowsL10n() const {
+  return nsContentUtils::PrincipalAllowsL10n(NodePrincipal(), GetDocumentURI());
+}
+
 bool Document::IsWebAnimationsEnabled(JSContext* aCx, JSObject* /*unused*/) {
   MOZ_ASSERT(NS_IsMainThread());
 
   return nsContentUtils::IsSystemCaller(aCx) ||
          StaticPrefs::dom_animations_api_core_enabled();
 }
 
 bool Document::IsWebAnimationsEnabled(CallerType aCallerType) {
--- a/dom/base/Document.h
+++ b/dom/base/Document.h
@@ -4033,16 +4033,21 @@ class Document : public nsINode,
   /**
    * This method is called when the initial translation
    * of the document is completed.
    *
    * It unblocks the load event if translation was blocking it.
    */
   void InitialDocumentTranslationCompleted();
 
+  /**
+   * Returns whether the document allows localization.
+   */
+  bool AllowsL10n() const;
+
  protected:
   RefPtr<DocumentL10n> mDocumentL10n;
 
   /**
    * Return true when you want a document without explicitly specified viewport
    * dimensions/scale to be treated as if "width=device-width" had in fact been
    * specified.
    */
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -3565,18 +3565,18 @@ static const char* gPropertiesFiles[nsCo
     "chrome://global/locale/dom/dom.properties",
     "chrome://global/locale/layout/htmlparser.properties",
     "chrome://global/locale/svg/svg.properties",
     "chrome://branding/locale/brand.properties",
     "chrome://global/locale/commonDialogs.properties",
     "chrome://global/locale/mathml/mathml.properties",
     "chrome://global/locale/security/security.properties",
     "chrome://necko/locale/necko.properties",
-    "chrome://global/locale/layout/HtmlForm.properties",
-    "resource://gre/res/locale/layout/HtmlForm.properties"};
+    "resource://gre/res/locale/layout/HtmlForm.properties",
+    "resource://gre/res/locale/dom/dom.properties"};
 
 /* static */
 nsresult nsContentUtils::EnsureStringBundle(PropertiesFile aFile) {
   if (!sStringBundles[aFile]) {
     if (!sStringBundleService) {
       nsresult rv =
           CallGetService(NS_STRINGBUNDLE_CONTRACTID, &sStringBundleService);
       NS_ENSURE_SUCCESS(rv, rv);
@@ -3615,47 +3615,76 @@ void nsContentUtils::AsyncPrecreateStrin
                                  nsIStringBundle* bundle = sStringBundles[file];
                                  bundle->AsyncPreload();
                                }),
         EventQueuePriority::Idle);
     Unused << NS_WARN_IF(NS_FAILED(rv));
   }
 }
 
-static bool SpoofLocaleEnglish() {
+/* static */
+bool nsContentUtils::SpoofLocaleEnglish() {
   // 0 - will prompt
   // 1 - don't spoof
   // 2 - spoof
   return StaticPrefs::privacy_spoof_english() == 2;
 }
 
+static nsContentUtils::PropertiesFile GetMaybeSpoofedPropertiesFile(
+    nsContentUtils::PropertiesFile aFile, const char* aKey,
+    Document* aDocument) {
+  // When we spoof English, use en-US properties in strings that are accessible
+  // by content.
+  bool spoofLocale = nsContentUtils::SpoofLocaleEnglish() &&
+                     (!aDocument || !aDocument->AllowsL10n());
+  if (spoofLocale) {
+    switch (aFile) {
+      case nsContentUtils::eFORMS_PROPERTIES:
+        return nsContentUtils::eFORMS_PROPERTIES_en_US;
+      case nsContentUtils::eDOM_PROPERTIES:
+        return nsContentUtils::eDOM_PROPERTIES_en_US;
+      default:
+        break;
+    }
+  }
+  return aFile;
+}
+
+/* static */
+nsresult nsContentUtils::GetMaybeLocalizedString(PropertiesFile aFile,
+                                                 const char* aKey,
+                                                 Document* aDocument,
+                                                 nsAString& aResult) {
+  return GetLocalizedString(
+      GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aResult);
+}
+
 /* static */
 nsresult nsContentUtils::GetLocalizedString(PropertiesFile aFile,
                                             const char* aKey,
                                             nsAString& aResult) {
-  // When we spoof English, use en-US default strings in HTML forms.
-  if (aFile == eFORMS_PROPERTIES_MAYBESPOOF && SpoofLocaleEnglish()) {
-    aFile = eFORMS_PROPERTIES_en_US;
-  }
-
   nsresult rv = EnsureStringBundle(aFile);
   NS_ENSURE_SUCCESS(rv, rv);
   nsIStringBundle* bundle = sStringBundles[aFile];
   return bundle->GetStringFromName(aKey, aResult);
 }
 
 /* static */
+nsresult nsContentUtils::FormatMaybeLocalizedString(
+    PropertiesFile aFile, const char* aKey, Document* aDocument,
+    const nsTArray<nsString>& aParams, nsAString& aResult) {
+  return FormatLocalizedString(
+      GetMaybeSpoofedPropertiesFile(aFile, aKey, aDocument), aKey, aParams,
+      aResult);
+}
+
+/* static */
 nsresult nsContentUtils::FormatLocalizedString(
     PropertiesFile aFile, const char* aKey, const nsTArray<nsString>& aParams,
     nsAString& aResult) {
-  // When we spoof English, use en-US default strings in HTML forms.
-  if (aFile == eFORMS_PROPERTIES_MAYBESPOOF && SpoofLocaleEnglish()) {
-    aFile = eFORMS_PROPERTIES_en_US;
-  }
-
   nsresult rv = EnsureStringBundle(aFile);
   NS_ENSURE_SUCCESS(rv, rv);
   nsIStringBundle* bundle = sStringBundles[aFile];
 
   if (aParams.IsEmpty()) {
     return bundle->GetStringFromName(aKey, aResult);
   }
 
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -1093,38 +1093,49 @@ class nsContentUtils {
     eDOM_PROPERTIES,
     eHTMLPARSER_PROPERTIES,
     eSVG_PROPERTIES,
     eBRAND_PROPERTIES,
     eCOMMON_DIALOG_PROPERTIES,
     eMATHML_PROPERTIES,
     eSECURITY_PROPERTIES,
     eNECKO_PROPERTIES,
-    eFORMS_PROPERTIES_MAYBESPOOF,
     eFORMS_PROPERTIES_en_US,
+    eDOM_PROPERTIES_en_US,
     PropertiesFile_COUNT
   };
   static nsresult ReportToConsole(
       uint32_t aErrorFlags, const nsACString& aCategory,
       const Document* aDocument, PropertiesFile aFile, const char* aMessageName,
       const nsTArray<nsString>& aParams = nsTArray<nsString>(),
       nsIURI* aURI = nullptr, const nsString& aSourceLine = EmptyString(),
       uint32_t aLineNumber = 0, uint32_t aColumnNumber = 0);
 
   static void ReportEmptyGetElementByIdArg(const Document* aDoc);
 
   static void LogMessageToConsole(const char* aMsg);
 
+  static bool SpoofLocaleEnglish();
+
   /**
    * Get the localized string named |aKey| in properties file |aFile|.
    */
   static nsresult GetLocalizedString(PropertiesFile aFile, const char* aKey,
                                      nsAString& aResult);
 
   /**
+   * Same as GetLocalizedString, except that it might use en-US locale depending
+   * on SpoofLocaleEnglish() and whether the document is a built-in browser
+   * page.
+   */
+  static nsresult GetMaybeLocalizedString(PropertiesFile aFile,
+                                          const char* aKey, Document* aDocument,
+                                          nsAString& aResult);
+
+  /**
    * A helper function that parses a sandbox attribute (of an <iframe> or a CSP
    * directive) and converts it to the set of flags used internally.
    *
    * @param aSandboxAttr  the sandbox attribute
    * @return              the set of flags (SANDBOXED_NONE if aSandboxAttr is
    *                      null)
    */
   static uint32_t ParseSandboxAttributeToFlags(const nsAttrValue* aSandboxAttr);
@@ -1199,26 +1210,53 @@ class nsContentUtils {
     static_assert(sizeof...(aParams) != 0, "Use GetLocalizedString()");
     AutoTArray<nsString, sizeof...(aParams)> params = {
         aParams...,
     };
     return FormatLocalizedString(aFile, aKey, params, aResult);
   }
 
   /**
+   * Same as FormatLocalizedString template version, except that it might use
+   * en-US locale depending on SpoofLocaleEnglish() and whether the document is
+   * a built-in browser page.
+   */
+  template <typename... T>
+  static nsresult FormatMaybeLocalizedString(nsAString& aResult,
+                                             PropertiesFile aFile,
+                                             const char* aKey,
+                                             Document* aDocument,
+                                             const T&... aParams) {
+    static_assert(sizeof...(aParams) != 0, "Use GetMaybeLocalizedString()");
+    AutoTArray<nsString, sizeof...(aParams)> params = {
+        aParams...,
+    };
+    return FormatMaybeLocalizedString(aFile, aKey, aDocument, params, aResult);
+  }
+
+  /**
    * Fill (with the parameters given) the localized string named |aKey| in
    * properties file |aFile| consuming an nsTArray of nsString parameters rather
    * than a char16_t** for the sake of avoiding use-after-free errors involving
    * temporaries.
    */
   static nsresult FormatLocalizedString(PropertiesFile aFile, const char* aKey,
                                         const nsTArray<nsString>& aParamArray,
                                         nsAString& aResult);
 
   /**
+   * Same as FormatLocalizedString, except that it might use en-US locale
+   * depending on SpoofLocaleEnglish() and whether the document is a built-in
+   * browser page.
+   */
+  static nsresult FormatMaybeLocalizedString(
+      PropertiesFile aFile, const char* aKey, Document* aDocument,
+      const nsTArray<nsString>& aParamArray, nsAString& aResult);
+
+  /**
    * Returns true if aDocument is a chrome document
    */
   static bool IsChromeDoc(const Document* aDocument) {
     return aDocument && aDocument->NodePrincipal() == sSystemPrincipal;
   }
 
   /**
    * Returns true if aDocument is in a docshell whose parent is the same type
--- a/dom/html/HTMLInputElement.cpp
+++ b/dom/html/HTMLInputElement.cpp
@@ -737,25 +737,26 @@ nsresult HTMLInputElement::InitFilePicke
   if (IsPopupBlocked()) {
     return NS_OK;
   }
 
   // Get Loc title
   nsAutoString title;
   nsAutoString okButtonLabel;
   if (aType == FILE_PICKER_DIRECTORY) {
-    nsContentUtils::GetLocalizedString(
-        nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "DirectoryUpload", title);
-
-    nsContentUtils::GetLocalizedString(
-        nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF,
-        "DirectoryPickerOkButtonLabel", okButtonLabel);
+    nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                            "DirectoryUpload", OwnerDoc(),
+                                            title);
+
+    nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                            "DirectoryPickerOkButtonLabel",
+                                            OwnerDoc(), okButtonLabel);
   } else {
-    nsContentUtils::GetLocalizedString(
-        nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "FileUpload", title);
+    nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                            "FileUpload", OwnerDoc(), title);
   }
 
   nsCOMPtr<nsIFilePicker> filePicker =
       do_CreateInstance("@mozilla.org/filepicker;1");
   if (!filePicker) return NS_ERROR_FAILURE;
 
   int16_t mode;
 
@@ -2361,33 +2362,34 @@ void HTMLInputElement::GetDisplayFileNam
   }
 
   nsAutoString value;
 
   if (mFileData->mFilesOrDirectories.IsEmpty()) {
     if ((StaticPrefs::dom_input_dirpicker() && Allowdirs()) ||
         (StaticPrefs::dom_webkitBlink_dirPicker_enabled() &&
          HasAttr(kNameSpaceID_None, nsGkAtoms::webkitdirectory))) {
-      nsContentUtils::GetLocalizedString(
-          nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "NoDirSelected", value);
+      nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                              "NoDirSelected", OwnerDoc(),
+                                              value);
     } else if (HasAttr(kNameSpaceID_None, nsGkAtoms::multiple)) {
-      nsContentUtils::GetLocalizedString(
-          nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "NoFilesSelected",
-          value);
+      nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                              "NoFilesSelected", OwnerDoc(),
+                                              value);
     } else {
-      nsContentUtils::GetLocalizedString(
-          nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "NoFileSelected",
-          value);
+      nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                              "NoFileSelected", OwnerDoc(),
+                                              value);
     }
   } else {
     nsString count;
     count.AppendInt(int(mFileData->mFilesOrDirectories.Length()));
 
-    nsContentUtils::FormatLocalizedString(
-        value, nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "XFilesSelected",
+    nsContentUtils::FormatMaybeLocalizedString(
+        value, nsContentUtils::eFORMS_PROPERTIES, "XFilesSelected", OwnerDoc(),
         count);
   }
 
   aValue = value;
 }
 
 const nsTArray<OwningFileOrDirectory>&
 HTMLInputElement::GetFilesOrDirectoriesInternal() const {
@@ -5814,18 +5816,18 @@ HTMLInputElement::SubmitNamesValues(HTML
   // Get the value
   nsAutoString value;
   GetValue(value, CallerType::System);
 
   if (mType == NS_FORM_INPUT_SUBMIT && value.IsEmpty() &&
       !HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
     // Get our default value, which is the same as our default label
     nsAutoString defaultValue;
-    nsContentUtils::GetLocalizedString(
-        nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "Submit", defaultValue);
+    nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                            "Submit", OwnerDoc(), defaultValue);
     value = defaultValue;
   }
 
   return aFormSubmission->AddNameValuePair(name, value);
 }
 
 static nsTArray<FileContentData> SaveFileContentData(
     const nsTArray<OwningFileOrDirectory>& aArray) {
--- a/dom/html/HTMLSelectElement.cpp
+++ b/dom/html/HTMLSelectElement.cpp
@@ -1515,19 +1515,19 @@ void HTMLSelectElement::UpdateValueMissi
   SetValidityState(VALIDITY_STATE_VALUE_MISSING, IsValueMissing());
 }
 
 nsresult HTMLSelectElement::GetValidationMessage(nsAString& aValidationMessage,
                                                  ValidityStateType aType) {
   switch (aType) {
     case VALIDITY_STATE_VALUE_MISSING: {
       nsAutoString message;
-      nsresult rv = nsContentUtils::GetLocalizedString(
+      nsresult rv = nsContentUtils::GetMaybeLocalizedString(
           nsContentUtils::eDOM_PROPERTIES, "FormValidationSelectMissing",
-          message);
+          OwnerDoc(), message);
       aValidationMessage = message;
       return rv;
     }
     default: {
       return nsIConstraintValidation::GetValidationMessage(aValidationMessage,
                                                            aType);
     }
   }
--- a/dom/html/HTMLTextAreaElement.cpp
+++ b/dom/html/HTMLTextAreaElement.cpp
@@ -1042,41 +1042,42 @@ nsresult HTMLTextAreaElement::GetValidat
       int32_t maxLength = MaxLength();
       int32_t textLength = GetTextLength();
       nsAutoString strMaxLength;
       nsAutoString strTextLength;
 
       strMaxLength.AppendInt(maxLength);
       strTextLength.AppendInt(textLength);
 
-      rv = nsContentUtils::FormatLocalizedString(
+      rv = nsContentUtils::FormatMaybeLocalizedString(
           message, nsContentUtils::eDOM_PROPERTIES, "FormValidationTextTooLong",
-          strMaxLength, strTextLength);
+          OwnerDoc(), strMaxLength, strTextLength);
       aValidationMessage = message;
     } break;
     case VALIDITY_STATE_TOO_SHORT: {
       nsAutoString message;
       int32_t minLength = MinLength();
       int32_t textLength = GetTextLength();
       nsAutoString strMinLength;
       nsAutoString strTextLength;
 
       strMinLength.AppendInt(minLength);
       strTextLength.AppendInt(textLength);
 
-      rv = nsContentUtils::FormatLocalizedString(
+      rv = nsContentUtils::FormatMaybeLocalizedString(
           message, nsContentUtils::eDOM_PROPERTIES,
-          "FormValidationTextTooShort", strMinLength, strTextLength);
+          "FormValidationTextTooShort", OwnerDoc(), strMinLength,
+          strTextLength);
       aValidationMessage = message;
     } break;
     case VALIDITY_STATE_VALUE_MISSING: {
       nsAutoString message;
-      rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                              "FormValidationValueMissing",
-                                              message);
+      rv = nsContentUtils::GetMaybeLocalizedString(
+          nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing",
+          OwnerDoc(), message);
       aValidationMessage = message;
     } break;
     default:
       rv = nsIConstraintValidation::GetValidationMessage(aValidationMessage,
                                                          aType);
   }
 
   return rv;
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -572,23 +572,23 @@ nsresult ImageDocument::OnSizeAvailable(
   return NS_OK;
 }
 
 nsresult ImageDocument::OnLoadComplete(imgIRequest* aRequest,
                                        nsresult aStatus) {
   UpdateTitleAndCharset();
 
   // mImageContent can be null if the document is already destroyed
-  if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
+  if (NS_FAILED(aStatus) && mImageContent) {
     nsAutoCString src;
     mDocumentURI->GetSpec(src);
     AutoTArray<nsString, 1> formatString;
     CopyUTF8toUTF16(src, *formatString.AppendElement());
     nsAutoString errorMsg;
-    mStringBundle->FormatStringFromName("InvalidImage", formatString, errorMsg);
+    FormatStringFromName("InvalidImage", formatString, errorMsg);
 
     mImageContent->SetAttr(kNameSpaceID_None, nsGkAtoms::alt, errorMsg, false);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
@@ -781,17 +781,17 @@ void ImageDocument::UpdateTitleAndCharse
     }
   }
 
   nsAutoString status;
   if (mImageIsResized) {
     AutoTArray<nsString, 1> formatString;
     formatString.AppendElement()->AppendInt(NSToCoordFloor(GetRatio() * 100));
 
-    mStringBundle->FormatStringFromName("ScaledImage", formatString, status);
+    FormatStringFromName("ScaledImage", formatString, status);
   }
 
   static const char* const formatNames[4] = {
       "ImageTitleWithNeitherDimensionsNorFile",
       "ImageTitleWithoutDimensions",
       "ImageTitleWithDimensions2",
       "ImageTitleWithDimensions2AndFile",
   };
--- a/dom/html/MediaDocument.cpp
+++ b/dom/html/MediaDocument.cpp
@@ -116,24 +116,16 @@ const char* const MediaDocument::sFormat
 MediaDocument::MediaDocument()
     : nsHTMLDocument(), mDidInitialDocumentSetup(false) {}
 MediaDocument::~MediaDocument() {}
 
 nsresult MediaDocument::Init() {
   nsresult rv = nsHTMLDocument::Init();
   NS_ENSURE_SUCCESS(rv, rv);
 
-  // Create a bundle for the localization
-  nsCOMPtr<nsIStringBundleService> stringService =
-      mozilla::services::GetStringBundleService();
-  if (stringService) {
-    stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI,
-                                getter_AddRefs(mStringBundle));
-  }
-
   mIsSyntheticDocument = true;
 
   return NS_OK;
 }
 
 nsresult MediaDocument::StartDocumentLoad(const char* aCommand,
                                           nsIChannel* aChannel,
                                           nsILoadGroup* aLoadGroup,
@@ -322,68 +314,92 @@ nsresult MediaDocument::LinkScript(const
                   NS_LITERAL_STRING("text/javascript"), true);
 
   script->SetAttr(kNameSpaceID_None, nsGkAtoms::src, aScript, true);
 
   Element* head = GetHeadElement();
   return head->AppendChildTo(script, false);
 }
 
+void MediaDocument::FormatStringFromName(const char* aName,
+                                         const nsTArray<nsString>& aParams,
+                                         nsAString& aResult) {
+  bool spoofLocale = nsContentUtils::SpoofLocaleEnglish() && !AllowsL10n();
+  if (!spoofLocale) {
+    if (!mStringBundle) {
+      nsCOMPtr<nsIStringBundleService> stringService =
+          mozilla::services::GetStringBundleService();
+      if (stringService) {
+        stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI,
+                                    getter_AddRefs(mStringBundle));
+      }
+    }
+    if (mStringBundle) {
+      mStringBundle->FormatStringFromName(aName, aParams, aResult);
+    }
+  } else {
+    if (!mStringBundleEnglish) {
+      nsCOMPtr<nsIStringBundleService> stringService =
+          mozilla::services::GetStringBundleService();
+      if (stringService) {
+        stringService->CreateBundle(NSMEDIADOCUMENT_PROPERTIES_URI_en_US,
+                                    getter_AddRefs(mStringBundleEnglish));
+      }
+    }
+    if (mStringBundleEnglish) {
+      mStringBundleEnglish->FormatStringFromName(aName, aParams, aResult);
+    }
+  }
+}
+
 void MediaDocument::UpdateTitleAndCharset(const nsACString& aTypeStr,
                                           nsIChannel* aChannel,
                                           const char* const* aFormatNames,
                                           int32_t aWidth, int32_t aHeight,
                                           const nsAString& aStatus) {
   nsAutoString fileStr;
   GetFileName(fileStr, aChannel);
 
   NS_ConvertASCIItoUTF16 typeStr(aTypeStr);
   nsAutoString title;
 
-  if (mStringBundle) {
-    // if we got a valid size (not all media have a size)
-    if (aWidth != 0 && aHeight != 0) {
-      nsAutoString widthStr;
-      nsAutoString heightStr;
-      widthStr.AppendInt(aWidth);
-      heightStr.AppendInt(aHeight);
-      // If we got a filename, display it
-      if (!fileStr.IsEmpty()) {
-        AutoTArray<nsString, 4> formatStrings = {fileStr, typeStr, widthStr,
-                                                 heightStr};
-        mStringBundle->FormatStringFromName(aFormatNames[eWithDimAndFile],
-                                            formatStrings, title);
-      } else {
-        AutoTArray<nsString, 3> formatStrings = {typeStr, widthStr, heightStr};
-        mStringBundle->FormatStringFromName(aFormatNames[eWithDim],
-                                            formatStrings, title);
-      }
+  // if we got a valid size (not all media have a size)
+  if (aWidth != 0 && aHeight != 0) {
+    nsAutoString widthStr;
+    nsAutoString heightStr;
+    widthStr.AppendInt(aWidth);
+    heightStr.AppendInt(aHeight);
+    // If we got a filename, display it
+    if (!fileStr.IsEmpty()) {
+      AutoTArray<nsString, 4> formatStrings = {fileStr, typeStr, widthStr,
+                                               heightStr};
+      FormatStringFromName(aFormatNames[eWithDimAndFile], formatStrings, title);
     } else {
-      // If we got a filename, display it
-      if (!fileStr.IsEmpty()) {
-        AutoTArray<nsString, 2> formatStrings = {fileStr, typeStr};
-        mStringBundle->FormatStringFromName(aFormatNames[eWithFile],
-                                            formatStrings, title);
-      } else {
-        AutoTArray<nsString, 1> formatStrings = {typeStr};
-        mStringBundle->FormatStringFromName(aFormatNames[eWithNoInfo],
-                                            formatStrings, title);
-      }
+      AutoTArray<nsString, 3> formatStrings = {typeStr, widthStr, heightStr};
+      FormatStringFromName(aFormatNames[eWithDim], formatStrings, title);
+    }
+  } else {
+    // If we got a filename, display it
+    if (!fileStr.IsEmpty()) {
+      AutoTArray<nsString, 2> formatStrings = {fileStr, typeStr};
+      FormatStringFromName(aFormatNames[eWithFile], formatStrings, title);
+    } else {
+      AutoTArray<nsString, 1> formatStrings = {typeStr};
+      FormatStringFromName(aFormatNames[eWithNoInfo], formatStrings, title);
     }
   }
 
   // set it on the document
   if (aStatus.IsEmpty()) {
     IgnoredErrorResult ignored;
     SetTitle(title, ignored);
   } else {
     nsAutoString titleWithStatus;
     AutoTArray<nsString, 2> formatStrings;
     formatStrings.AppendElement(title);
     formatStrings.AppendElement(aStatus);
-    mStringBundle->FormatStringFromName("TitleWithStatus", formatStrings,
-                                        titleWithStatus);
+    FormatStringFromName("TitleWithStatus", formatStrings, titleWithStatus);
     SetTitle(titleWithStatus, IgnoreErrors());
   }
 }
 
 }  // namespace dom
 }  // namespace mozilla
--- a/dom/html/MediaDocument.h
+++ b/dom/html/MediaDocument.h
@@ -11,16 +11,19 @@
 #include "nsHTMLDocument.h"
 #include "nsGenericHTMLElement.h"
 #include "nsIStringBundle.h"
 #include "nsIThreadRetargetableStreamListener.h"
 
 #define NSMEDIADOCUMENT_PROPERTIES_URI \
   "chrome://global/locale/layout/MediaDocument.properties"
 
+#define NSMEDIADOCUMENT_PROPERTIES_URI_en_US \
+  "resource://gre/res/locale/layout/MediaDocument.properties"
+
 namespace mozilla {
 namespace dom {
 
 class MediaDocument : public nsHTMLDocument {
  public:
   MediaDocument();
   virtual ~MediaDocument();
 
@@ -55,16 +58,20 @@ class MediaDocument : public nsHTMLDocum
   friend class MediaDocumentStreamListener;
   virtual nsresult StartLayout();
 
   void GetFileName(nsAString& aResult, nsIChannel* aChannel);
 
   nsresult LinkStylesheet(const nsAString& aStylesheet);
   nsresult LinkScript(const nsAString& aScript);
 
+  void FormatStringFromName(const char* aName,
+                            const nsTArray<nsString>& aParams,
+                            nsAString& aResult);
+
   // |aFormatNames[]| needs to have four elements in the following order:
   // a format name with neither dimension nor file, a format name with
   // filename but w/o dimension, a format name with dimension but w/o filename,
   // a format name with both of them.  For instance, it can have
   // "ImageTitleWithNeitherDimensionsNorFile", "ImageTitleWithoutDimensions",
   // "ImageTitleWithDimesions2",  "ImageTitleWithDimensions2AndFile".
   //
   // Also see MediaDocument.properties if you want to define format names
@@ -72,16 +79,17 @@ class MediaDocument : public nsHTMLDocum
   // but could be in other units for other 'media', in which case you have to
   // define format names accordingly.
   void UpdateTitleAndCharset(const nsACString& aTypeStr, nsIChannel* aChannel,
                              const char* const* aFormatNames = sFormatNames,
                              int32_t aWidth = 0, int32_t aHeight = 0,
                              const nsAString& aStatus = EmptyString());
 
   nsCOMPtr<nsIStringBundle> mStringBundle;
+  nsCOMPtr<nsIStringBundle> mStringBundleEnglish;
   static const char* const sFormatNames[4];
 
  private:
   enum { eWithNoInfo, eWithFile, eWithDim, eWithDimAndFile };
 
   // A boolean that indicates whether we did our initial document setup.  This
   // will be false initially, become true when we finish setting up the document
   // during initial load and stay true thereafter.
--- a/dom/html/input/CheckableInputTypes.cpp
+++ b/dom/html/input/CheckableInputTypes.cpp
@@ -18,19 +18,20 @@ bool CheckboxInputType::IsValueMissing()
   if (!IsMutable()) {
     return false;
   }
 
   return !mInputElement->Checked();
 }
 
 nsresult CheckboxInputType::GetValueMissingMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                            "FormValidationCheckboxMissing",
-                                            aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationCheckboxMissing",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 /* input type=radio */
 
 nsresult RadioInputType::GetValueMissingMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationRadioMissing", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationRadioMissing",
+      mInputElement->OwnerDoc(), aMessage);
 }
--- a/dom/html/input/DateTimeInputTypes.cpp
+++ b/dom/html/input/DateTimeInputTypes.cpp
@@ -119,28 +119,29 @@ bool DateTimeInputTypeBase::HasBadInput(
 
   return value.IsEmpty();
 }
 
 nsresult DateTimeInputTypeBase::GetRangeOverflowMessage(nsAString& aMessage) {
   nsAutoString maxStr;
   mInputElement->GetAttr(kNameSpaceID_None, nsGkAtoms::max, maxStr);
 
-  return nsContentUtils::FormatLocalizedString(
+  return nsContentUtils::FormatMaybeLocalizedString(
       aMessage, nsContentUtils::eDOM_PROPERTIES,
-      "FormValidationDateTimeRangeOverflow", maxStr);
+      "FormValidationDateTimeRangeOverflow", mInputElement->OwnerDoc(), maxStr);
 }
 
 nsresult DateTimeInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
   nsAutoString minStr;
   mInputElement->GetAttr(kNameSpaceID_None, nsGkAtoms::min, minStr);
 
-  return nsContentUtils::FormatLocalizedString(
+  return nsContentUtils::FormatMaybeLocalizedString(
       aMessage, nsContentUtils::eDOM_PROPERTIES,
-      "FormValidationDateTimeRangeUnderflow", minStr);
+      "FormValidationDateTimeRangeUnderflow", mInputElement->OwnerDoc(),
+      minStr);
 }
 
 nsresult DateTimeInputTypeBase::MinMaxStepAttrChanged() {
   if (Element* dateTimeBoxElement = mInputElement->GetDateTimeBoxElement()) {
     AsyncEventDispatcher* dispatcher = new AsyncEventDispatcher(
         dateTimeBoxElement, NS_LITERAL_STRING("MozNotifyMinMaxStepAttrChanged"),
         CanBubble::eNo, ChromeOnlyDispatch::eNo);
     dispatcher->RunDOMEventWhenSafe();
@@ -174,18 +175,19 @@ bool DateTimeInputTypeBase::GetTimeFromM
 
 // input type=date
 
 nsresult DateInputType::GetBadInputMessage(nsAString& aMessage) {
   if (!StaticPrefs::dom_forms_datetime()) {
     return NS_ERROR_UNEXPECTED;
   }
 
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDate", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidDate",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 bool DateInputType::ConvertStringToNumber(
     nsAString& aValue, mozilla::Decimal& aResultValue) const {
   uint32_t year, month, day;
   if (!ParseDate(aValue, &year, &month, &day)) {
     return false;
   }
--- a/dom/html/input/FileInputType.cpp
+++ b/dom/html/input/FileInputType.cpp
@@ -16,11 +16,12 @@ bool FileInputType::IsValueMissing() con
   if (!IsMutable()) {
     return false;
   }
 
   return mInputElement->GetFilesOrDirectoriesInternal().IsEmpty();
 }
 
 nsresult FileInputType::GetValueMissingMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationFileMissing", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationFileMissing",
+      mInputElement->OwnerDoc(), aMessage);
 }
--- a/dom/html/input/InputType.cpp
+++ b/dom/html/input/InputType.cpp
@@ -160,36 +160,37 @@ nsresult InputType::GetValidationMessage
       int32_t textLength =
           mInputElement->InputTextLength(mozilla::dom::CallerType::System);
       nsAutoString strMaxLength;
       nsAutoString strTextLength;
 
       strMaxLength.AppendInt(maxLength);
       strTextLength.AppendInt(textLength);
 
-      rv = nsContentUtils::FormatLocalizedString(
+      rv = nsContentUtils::FormatMaybeLocalizedString(
           message, nsContentUtils::eDOM_PROPERTIES, "FormValidationTextTooLong",
-          strMaxLength, strTextLength);
+          mInputElement->OwnerDoc(), strMaxLength, strTextLength);
       aValidationMessage = message;
       break;
     }
     case nsIConstraintValidation::VALIDITY_STATE_TOO_SHORT: {
       nsAutoString message;
       int32_t minLength = mInputElement->MinLength();
       int32_t textLength =
           mInputElement->InputTextLength(mozilla::dom::CallerType::System);
       nsAutoString strMinLength;
       nsAutoString strTextLength;
 
       strMinLength.AppendInt(minLength);
       strTextLength.AppendInt(textLength);
 
-      rv = nsContentUtils::FormatLocalizedString(
+      rv = nsContentUtils::FormatMaybeLocalizedString(
           message, nsContentUtils::eDOM_PROPERTIES,
-          "FormValidationTextTooShort", strMinLength, strTextLength);
+          "FormValidationTextTooShort", mInputElement->OwnerDoc(), strMinLength,
+          strTextLength);
 
       aValidationMessage = message;
       break;
     }
     case nsIConstraintValidation::VALIDITY_STATE_VALUE_MISSING: {
       nsAutoString message;
       rv = GetValueMissingMessage(message);
       if (NS_FAILED(rv)) {
@@ -209,28 +210,29 @@ nsresult InputType::GetValidationMessage
       aValidationMessage = message;
       break;
     }
     case nsIConstraintValidation::VALIDITY_STATE_PATTERN_MISMATCH: {
       nsAutoString message;
       nsAutoString title;
       mInputElement->GetAttr(kNameSpaceID_None, nsGkAtoms::title, title);
       if (title.IsEmpty()) {
-        rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                                "FormValidationPatternMismatch",
-                                                message);
+        rv = nsContentUtils::GetMaybeLocalizedString(
+            nsContentUtils::eDOM_PROPERTIES, "FormValidationPatternMismatch",
+            mInputElement->OwnerDoc(), message);
       } else {
         if (title.Length() >
             nsIConstraintValidation::sContentSpecifiedMaxLengthMessage) {
           title.Truncate(
               nsIConstraintValidation::sContentSpecifiedMaxLengthMessage);
         }
-        rv = nsContentUtils::FormatLocalizedString(
+        rv = nsContentUtils::FormatMaybeLocalizedString(
             message, nsContentUtils::eDOM_PROPERTIES,
-            "FormValidationPatternMismatchWithTitle", title);
+            "FormValidationPatternMismatchWithTitle", mInputElement->OwnerDoc(),
+            title);
       }
       aValidationMessage = message;
       break;
     }
     case nsIConstraintValidation::VALIDITY_STATE_RANGE_OVERFLOW: {
       nsAutoString message;
       rv = GetRangeOverflowMessage(message);
       if (NS_FAILED(rv)) {
@@ -269,31 +271,34 @@ nsresult InputType::GetValidationMessage
       mozilla::Decimal maximum = mInputElement->GetMaximum();
 
       if (maximum.isNaN() || valueHigh <= maximum) {
         nsAutoString valueLowStr, valueHighStr;
         ConvertNumberToString(valueLow, valueLowStr);
         ConvertNumberToString(valueHigh, valueHighStr);
 
         if (valueLowStr.Equals(valueHighStr)) {
-          rv = nsContentUtils::FormatLocalizedString(
+          rv = nsContentUtils::FormatMaybeLocalizedString(
               message, nsContentUtils::eDOM_PROPERTIES,
-              "FormValidationStepMismatchOneValue", valueLowStr);
+              "FormValidationStepMismatchOneValue", mInputElement->OwnerDoc(),
+              valueLowStr);
         } else {
-          rv = nsContentUtils::FormatLocalizedString(
+          rv = nsContentUtils::FormatMaybeLocalizedString(
               message, nsContentUtils::eDOM_PROPERTIES,
-              "FormValidationStepMismatch", valueLowStr, valueHighStr);
+              "FormValidationStepMismatch", mInputElement->OwnerDoc(),
+              valueLowStr, valueHighStr);
         }
       } else {
         nsAutoString valueLowStr;
         ConvertNumberToString(valueLow, valueLowStr);
 
-        rv = nsContentUtils::FormatLocalizedString(
+        rv = nsContentUtils::FormatMaybeLocalizedString(
             message, nsContentUtils::eDOM_PROPERTIES,
-            "FormValidationStepMismatchOneValue", valueLowStr);
+            "FormValidationStepMismatchOneValue", mInputElement->OwnerDoc(),
+            valueLowStr);
       }
 
       aValidationMessage = message;
       break;
     }
     case nsIConstraintValidation::VALIDITY_STATE_BAD_INPUT: {
       nsAutoString message;
       rv = GetBadInputMessage(message);
@@ -307,18 +312,19 @@ nsresult InputType::GetValidationMessage
     default:
       return NS_ERROR_UNEXPECTED;
   }
 
   return rv;
 }
 
 nsresult InputType::GetValueMissingMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationValueMissing",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 nsresult InputType::GetTypeMismatchMessage(nsAString& aMessage) {
   return NS_ERROR_UNEXPECTED;
 }
 
 nsresult InputType::GetRangeOverflowMessage(nsAString& aMessage) {
   return NS_ERROR_UNEXPECTED;
--- a/dom/html/input/NumericInputTypes.cpp
+++ b/dom/html/input/NumericInputTypes.cpp
@@ -66,35 +66,35 @@ nsresult NumericInputTypeBase::GetRangeO
 
   nsAutoString maxStr;
   char buf[32];
   mozilla::DebugOnly<bool> ok =
       maximum.toString(buf, mozilla::ArrayLength(buf));
   maxStr.AssignASCII(buf);
   MOZ_ASSERT(ok, "buf not big enough");
 
-  return nsContentUtils::FormatLocalizedString(
+  return nsContentUtils::FormatMaybeLocalizedString(
       aMessage, nsContentUtils::eDOM_PROPERTIES,
-      "FormValidationNumberRangeOverflow", maxStr);
+      "FormValidationNumberRangeOverflow", mInputElement->OwnerDoc(), maxStr);
 }
 
 nsresult NumericInputTypeBase::GetRangeUnderflowMessage(nsAString& aMessage) {
   mozilla::Decimal minimum = mInputElement->GetMinimum();
   MOZ_ASSERT(!minimum.isNaN());
 
   nsAutoString minStr;
   char buf[32];
   mozilla::DebugOnly<bool> ok =
       minimum.toString(buf, mozilla::ArrayLength(buf));
   minStr.AssignASCII(buf);
   MOZ_ASSERT(ok, "buf not big enough");
 
-  return nsContentUtils::FormatLocalizedString(
+  return nsContentUtils::FormatMaybeLocalizedString(
       aMessage, nsContentUtils::eDOM_PROPERTIES,
-      "FormValidationNumberRangeUnderflow", minStr);
+      "FormValidationNumberRangeUnderflow", mInputElement->OwnerDoc(), minStr);
 }
 
 bool NumericInputTypeBase::ConvertStringToNumber(
     nsAString& aValue, mozilla::Decimal& aResultValue) const {
   aResultValue = mozilla::dom::HTMLInputElement::StringToDecimal(aValue);
   if (!aResultValue.isFinite()) {
     return false;
   }
@@ -143,25 +143,25 @@ bool NumberInputType::HasBadInput() cons
   if (numberControlFrame && !numberControlFrame->AnonTextControlIsEmpty()) {
     // The input the user entered failed to parse as a number.
     return true;
   }
   return false;
 }
 
 nsresult NumberInputType::GetValueMissingMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                            "FormValidationBadInputNumber",
-                                            aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 nsresult NumberInputType::GetBadInputMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES,
-                                            "FormValidationBadInputNumber",
-                                            aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationBadInputNumber",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 bool NumberInputType::IsMutable() const {
   return !mInputElement->IsDisabled() &&
          !mInputElement->HasAttr(kNameSpaceID_None, nsGkAtoms::readonly);
 }
 
 /* input type=range */
--- a/dom/html/input/SingleLineTextInputTypes.cpp
+++ b/dom/html/input/SingleLineTextInputTypes.cpp
@@ -111,18 +111,19 @@ bool URLInputType::HasTypeMismatch() con
   nsCOMPtr<nsIIOService> ioService = do_GetIOService();
   nsCOMPtr<nsIURI> uri;
 
   return !NS_SUCCEEDED(ioService->NewURI(NS_ConvertUTF16toUTF8(value), nullptr,
                                          nullptr, getter_AddRefs(uri)));
 }
 
 nsresult URLInputType::GetTypeMismatchMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidURL", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidURL",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 /* input type=email */
 
 bool EmailInputType::HasTypeMismatch() const {
   nsAutoString value;
   GetNonFileValueInternal(value);
 
@@ -149,23 +150,25 @@ bool EmailInputType::HasBadInput() const
     if (!PunycodeEncodeEmailAddress(tokenizer.nextToken(), unused, &unused2)) {
       return true;
     }
   }
   return false;
 }
 
 nsresult EmailInputType::GetTypeMismatchMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 nsresult EmailInputType::GetBadInputMessage(nsAString& aMessage) {
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail", aMessage);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eDOM_PROPERTIES, "FormValidationInvalidEmail",
+      mInputElement->OwnerDoc(), aMessage);
 }
 
 /* static */
 bool EmailInputType::IsValidEmailAddressList(const nsAString& aValue) {
   HTMLSplitOnSpacesTokenizer tokenizer(aValue, ',');
 
   while (tokenizer.hasMoreTokens()) {
     if (!IsValidEmailAddress(tokenizer.nextToken())) {
--- a/dom/locales/moz.build
+++ b/dom/locales/moz.build
@@ -57,9 +57,15 @@ with Files("en-US/chrome/xslt/**"):
 
 with Files("en-US/chrome/plugins.properties"):
     BUG_COMPONENT = ("Core", "Plug-ins")
 
 JAR_MANIFESTS += ['jar.mn']
 
 RESOURCE_FILES.locale.layout += [
     'en-US/chrome/layout/HtmlForm.properties',
+    'en-US/chrome/layout/MediaDocument.properties',
+    'en-US/chrome/layout/xmlparser.properties',
 ]
+
+RESOURCE_FILES.locale.dom += [
+    'en-US/chrome/dom/dom.properties',
+]
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1577,18 +1577,18 @@ already_AddRefed<nsIContent> nsCSSFrameC
           nsCOMPtr<nsIContent> content;
           NS_NewAttributeContent(mDocument->NodeInfoManager(),
                                  kNameSpaceID_None, nsGkAtoms::value,
                                  getter_AddRefs(content));
           return content.forget();
         }
 
         nsAutoString temp;
-        nsContentUtils::GetLocalizedString(
-            nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "Submit", temp);
+        nsContentUtils::GetMaybeLocalizedString(
+            nsContentUtils::eFORMS_PROPERTIES, "Submit", mDocument, temp);
         return CreateGenConTextNode(aState, temp, nullptr);
       }
 
       break;
     }
 
     case StyleContentType::Uninitialized:
       MOZ_ASSERT_UNREACHABLE("uninitialized content type");
@@ -7866,18 +7866,19 @@ void nsCSSFrameConstructor::GetAlternate
     // If there's no "alt" attribute, and aContent is an input element, then use
     // the value of the "value" attribute
     if (aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::value, aAltText)) {
       return;
     }
 
     // If there's no "value" attribute either, then use the localized string for
     // "Submit" as the alternate text.
-    nsContentUtils::GetLocalizedString(
-        nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "Submit", aAltText);
+    nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                            "Submit", aElement->OwnerDoc(),
+                                            aAltText);
   }
 }
 
 nsIFrame* nsCSSFrameConstructor::CreateContinuingOuterTableFrame(
     PresShell* aPresShell, nsPresContext* aPresContext, nsIFrame* aFrame,
     nsContainerFrame* aParentFrame, nsIContent* aContent,
     ComputedStyle* aComputedStyle) {
   nsTableWrapperFrame* newFrame =
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -210,18 +210,18 @@ static already_AddRefed<Element> MakeAno
                                                 const nsAString& aAccessKey) {
   RefPtr<Element> button = aDoc->CreateHTMLElement(nsGkAtoms::button);
   // NOTE: SetIsNativeAnonymousRoot() has to be called before setting any
   // attribute.
   button->SetIsNativeAnonymousRoot();
 
   // Set the file picking button text depending on the current locale.
   nsAutoString buttonTxt;
-  nsContentUtils::GetLocalizedString(
-      nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, labelKey, buttonTxt);
+  nsContentUtils::GetMaybeLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                          labelKey, aDoc, buttonTxt);
 
   // Set the browse button text. It's a bit of a pain to do because we want to
   // make sure we are not notifying.
   RefPtr<nsTextNode> textContent =
       new nsTextNode(button->NodeInfo()->NodeInfoManager());
 
   textContent->SetText(buttonTxt, false);
 
--- a/layout/forms/nsGfxButtonControlFrame.cpp
+++ b/layout/forms/nsGfxButtonControlFrame.cpp
@@ -83,18 +83,18 @@ nsresult nsGfxButtonControlFrame::GetDef
     prop = "Reset";
   } else if (type == NS_FORM_INPUT_SUBMIT) {
     prop = "Submit";
   } else {
     aString.Truncate();
     return NS_OK;
   }
 
-  return nsContentUtils::GetLocalizedString(
-      nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, prop, aString);
+  return nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eFORMS_PROPERTIES, prop, mContent->OwnerDoc(), aString);
 }
 
 nsresult nsGfxButtonControlFrame::GetLabel(nsString& aLabel) {
   // Get the text from the "value" property on our content if there is
   // one; otherwise set it to a default value (localized).
   dom::HTMLInputElement* elt = dom::HTMLInputElement::FromNode(mContent);
   if (elt && elt->HasAttr(kNameSpaceID_None, nsGkAtoms::value)) {
     elt->GetValue(aLabel, dom::CallerType::System);
--- a/layout/generic/DetailsFrame.cpp
+++ b/layout/generic/DetailsFrame.cpp
@@ -93,19 +93,19 @@ nsresult DetailsFrame::CreateAnonymousCo
   nsNodeInfoManager* nodeInfoManager =
       GetContent()->NodeInfo()->NodeInfoManager();
 
   RefPtr<NodeInfo> nodeInfo = nodeInfoManager->GetNodeInfo(
       nsGkAtoms::summary, nullptr, kNameSpaceID_XHTML, nsINode::ELEMENT_NODE);
   mDefaultSummary = new HTMLSummaryElement(nodeInfo.forget());
 
   nsAutoString defaultSummaryText;
-  nsContentUtils::GetLocalizedString(
-      nsContentUtils::eFORMS_PROPERTIES_MAYBESPOOF, "DefaultSummary",
-      defaultSummaryText);
+  nsContentUtils::GetMaybeLocalizedString(
+      nsContentUtils::eFORMS_PROPERTIES, "DefaultSummary",
+      GetContent()->OwnerDoc(), defaultSummaryText);
   RefPtr<nsTextNode> description = new nsTextNode(nodeInfoManager);
   description->SetText(defaultSummaryText, false);
   mDefaultSummary->AppendChildTo(description, false);
 
   aElements.AppendElement(mDefaultSummary);
 
   return NS_OK;
 }
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -201,16 +201,19 @@
 @BINPATH@/res/table-remove-column.gif
 @BINPATH@/res/table-remove-row-active.gif
 @BINPATH@/res/table-remove-row-hover.gif
 @BINPATH@/res/table-remove-row.gif
 @BINPATH@/res/grabber.gif
 @BINPATH@/res/dtd/*
 @BINPATH@/res/language.properties
 @BINPATH@/res/locale/layout/HtmlForm.properties
+@BINPATH@/res/locale/layout/MediaDocument.properties
+@BINPATH@/res/locale/layout/xmlparser.properties
+@BINPATH@/res/locale/dom/dom.properties
 
 #ifndef MOZ_ANDROID_EXCLUDE_FONTS
 @BINPATH@/res/fonts/*
 #else
 @BINPATH@/res/fonts/*.properties
 #endif
 
 ; Content-accessible resources.
--- a/parser/htmlparser/nsExpatDriver.cpp
+++ b/parser/htmlparser/nsExpatDriver.cpp
@@ -678,22 +678,23 @@ nsresult nsExpatDriver::OpenInputStreamF
   channel->SetContentType(NS_LITERAL_CSTRING("application/xml"));
   return channel->Open(aStream);
 }
 
 static nsresult CreateErrorText(const char16_t* aDescription,
                                 const char16_t* aSourceURL,
                                 const uint32_t aLineNumber,
                                 const uint32_t aColNumber,
-                                nsString& aErrorString) {
+                                nsString& aErrorString, bool spoofEnglish) {
   aErrorString.Truncate();
 
   nsAutoString msg;
   nsresult rv = nsParserMsgUtils::GetLocalizedStringByName(
-      XMLPARSER_PROPERTIES, "XMLParsingError", msg);
+      spoofEnglish ? XMLPARSER_PROPERTIES_en_US : XMLPARSER_PROPERTIES,
+      "XMLParsingError", msg);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // XML Parsing Error: %1$S\nLocation: %2$S\nLine Number %3$u, Column %4$u:
   nsTextFormatter::ssprintf(aErrorString, msg.get(), aDescription, aSourceURL,
                             aLineNumber, aColNumber);
   return NS_OK;
 }
 
@@ -724,18 +725,25 @@ static nsresult AppendErrorPointer(const
 
 nsresult nsExpatDriver::HandleError() {
   int32_t code = XML_GetErrorCode(mExpatParser);
   NS_ASSERTION(code > XML_ERROR_NONE, "unexpected XML error code");
 
   // Map Expat error code to an error string
   // XXX Deal with error returns.
   nsAutoString description;
-  nsParserMsgUtils::GetLocalizedStringByID(XMLPARSER_PROPERTIES, code,
-                                           description);
+  nsCOMPtr<Document> doc;
+  if (mOriginalSink) {
+    doc = do_QueryInterface(mOriginalSink->GetTarget());
+  }
+  bool spoofEnglish =
+      nsContentUtils::SpoofLocaleEnglish() && (!doc || !doc->AllowsL10n());
+  nsParserMsgUtils::GetLocalizedStringByID(
+      spoofEnglish ? XMLPARSER_PROPERTIES_en_US : XMLPARSER_PROPERTIES, code,
+      description);
 
   if (code == XML_ERROR_TAG_MISMATCH) {
     /**
      *  Expat can send the following:
      *    localName
      *    namespaceURI<separator>localName
      *    namespaceURI<separator>localName<separator>prefix
      *
@@ -761,32 +769,33 @@ nsresult nsExpatDriver::HandleError() {
       // We have a prefix.
       tagName.Append(nameEnd + 1, pos - nameEnd - 1);
       tagName.Append(char16_t(':'));
     }
     const char16_t* nameStart = uriEnd ? uriEnd + 1 : mismatch;
     tagName.Append(nameStart, (nameEnd ? nameEnd : pos) - nameStart);
 
     nsAutoString msg;
-    nsParserMsgUtils::GetLocalizedStringByName(XMLPARSER_PROPERTIES, "Expected",
-                                               msg);
+    nsParserMsgUtils::GetLocalizedStringByName(
+        spoofEnglish ? XMLPARSER_PROPERTIES_en_US : XMLPARSER_PROPERTIES,
+        "Expected", msg);
 
     // . Expected: </%S>.
     nsAutoString message;
     nsTextFormatter::ssprintf(message, msg.get(), tagName.get());
     description.Append(message);
   }
 
   // Adjust the column number so that it is one based rather than zero based.
   uint32_t colNumber = XML_GetCurrentColumnNumber(mExpatParser) + 1;
   uint32_t lineNumber = XML_GetCurrentLineNumber(mExpatParser);
 
   nsAutoString errorText;
   CreateErrorText(description.get(), XML_GetBase(mExpatParser), lineNumber,
-                  colNumber, errorText);
+                  colNumber, errorText, spoofEnglish);
 
   nsAutoString sourceText(mLastLine);
   AppendErrorPointer(colNumber, mLastLine.get(), sourceText);
 
   // Try to create and initialize the script error.
   nsCOMPtr<nsIScriptError> serr(do_CreateInstance(NS_SCRIPTERROR_CONTRACTID));
   nsresult rv = NS_ERROR_FAILURE;
   if (serr) {
--- a/parser/htmlparser/nsParserMsgUtils.h
+++ b/parser/htmlparser/nsParserMsgUtils.h
@@ -6,16 +6,19 @@
 #ifndef nsParserMsgUtils_h
 #define nsParserMsgUtils_h
 
 #include "nsString.h"
 
 #define XMLPARSER_PROPERTIES \
   "chrome://global/locale/layout/xmlparser.properties"
 
+#define XMLPARSER_PROPERTIES_en_US \
+  "resource://gre/res/locale/layout/xmlparser.properties"
+
 class nsParserMsgUtils {
   nsParserMsgUtils();   // Currently this is not meant to be created, use the
                         // static methods
   ~nsParserMsgUtils();  // If perf required, change this to cache values etc.
  public:
   static nsresult GetLocalizedStringByName(const char* aPropFileName,
                                            const char* aKey, nsString& aVal);
   static nsresult GetLocalizedStringByID(const char* aPropFileName,