Bug 1539159 - Prevent modification of UA style sheets. r=emilio
authorCameron McCormack <cam@mcc.id.au>
Mon, 29 Apr 2019 05:34:06 +0000
changeset 530528 ee37c856a47e46f3f548cef19d64b581119188fb
parent 530527 3bc847f39bd7ba6359082cddeed7c0fe2bc625cc
child 530529 e75bfefbe44260b79d057374184329bb19068a4f
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersemilio
bugs1539159
milestone68.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 1539159 - Prevent modification of UA style sheets. r=emilio Differential Revision: https://phabricator.services.mozilla.com/D25661
dom/tests/mochitest/chrome/test_parsingMode.html
layout/inspector/tests/chrome/chrome.ini
layout/inspector/tests/chrome/test_ua_rule_modification.html
layout/style/CSSCounterStyleRule.cpp
layout/style/CSSFontFaceRule.cpp
layout/style/CSSFontFeatureValuesRule.cpp
layout/style/CSSKeyframeRule.cpp
layout/style/CSSKeyframesRule.cpp
layout/style/CSSKeyframesRule.h
layout/style/CSSMediaRule.cpp
layout/style/CSSMozDocumentRule.cpp
layout/style/CSSPageRule.cpp
layout/style/CSSStyleRule.cpp
layout/style/CSSSupportsRule.cpp
layout/style/DeclarationBlock.cpp
layout/style/DeclarationBlock.h
layout/style/FontFace.cpp
layout/style/GroupRule.cpp
layout/style/MediaList.cpp
layout/style/MediaList.h
layout/style/Rule.cpp
layout/style/Rule.h
layout/style/ServoCSSRuleList.cpp
layout/style/ServoCSSRuleList.h
layout/style/StyleSheet.cpp
layout/style/StyleSheet.h
layout/style/nsDOMCSSDeclaration.cpp
layout/style/nsICSSDeclaration.cpp
layout/style/nsICSSDeclaration.h
--- a/dom/tests/mochitest/chrome/test_parsingMode.html
+++ b/dom/tests/mochitest/chrome/test_parsingMode.html
@@ -1,16 +1,19 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <title>CSSStyleSheet parsingMode test - bug 1230491</title>
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
 <script type="application/javascript">
   SimpleTest.waitForExplicitFinish();
+  function sheetText(sheet) {
+    return [...sheet.cssRules].map(r => r.cssText).join();
+  }
   function run() {
     const sss = Cc["@mozilla.org/content/style-sheet-service;1"]
       .getService(Ci.nsIStyleSheetService);
     const utils = window.windowUtils;
 
     const userUrl = encodeURI("data:text/css,body { color: seagreen; -moz-window-transform: none }");
     utils.loadSheetUsingURIString(userUrl, sss.USER_SHEET);
 
@@ -37,21 +40,24 @@
         continue;
       } else {
         // Ignore sheets we don't care about.
         continue;
       }
 
       // Check that re-parsing preserves the mode.
       let mode = sheet.parsingMode;
-      try {
-        InspectorUtils.parseStyleSheet(sheet, "body { color: chartreuse; }");
-        isnot(sheet.parsingMode, "agent", "Agent sheets cannot be reparsed");
-      } catch (ex) {
-        is(sheet.parsingMode, "agent", "Agent sheets cannot be reparsed");
+      let text = sheetText(sheet);
+      InspectorUtils.parseStyleSheet(sheet, "body { color: chartreuse; }");
+      if (mode == "agent") {
+        is(sheetText(sheet), text,
+           "Reparsing should not have changed a UA sheet");
+      } else {
+        isnot(sheetText(sheet), text,
+           "Reparsing should have changed a non-UA sheet");
       }
       is(sheet.parsingMode, mode,
          "check that re-parsing preserved mode " + mode);
     }
 
     ok(results[sss.AGENT_SHEET] && results[sss.USER_SHEET] &&
       results[sss.AUTHOR_SHEET],
       "all sheets seen");
--- a/layout/inspector/tests/chrome/chrome.ini
+++ b/layout/inspector/tests/chrome/chrome.ini
@@ -24,9 +24,10 @@ support-files =
 support-files =
   test_fontFeaturesAPI.css
   DejaVuSans.ttf
 [test_fontVariationsAPI.xul]
 skip-if = ((os == 'win' && bits!=64) || (os=='linux' && bits==32) || os == 'mac') # bug 1456855, bug 1456856
 support-files =
   test_fontVariationsAPI.css
 [test_fontFaceGeneric.xul]
+[test_ua_rule_modification.html]
 [test_ua_sheet_disable.html]
new file mode 100644
--- /dev/null
+++ b/layout/inspector/tests/chrome/test_ua_rule_modification.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<title>Test for bug 1539159</title>
+<script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+<p></p>
+<script>
+function start() {
+  let rules = InspectorUtils.getCSSStyleRules(document.querySelector("p"));
+  ok(rules.length > 0, "Should have found some rules");
+  is(rules[0].type, CSSRule.STYLE_RULE, "Should have found a style rule");
+
+  let selector = rules[0].selectorText;
+  isnot(selector, ".xxxxx", "Rule selector should not be something strange");
+
+  try {
+    rules[0].selectorText = "img";
+  } catch (ex) {
+  }
+  is(rules[0].selectorText, selector, "Selector text should be unchanged");
+
+  SimpleTest.finish();
+}
+
+SimpleTest.waitForExplicitFinish();
+document.addEventListener('DOMContentLoaded', start)
+</script>
--- a/layout/style/CSSCounterStyleRule.cpp
+++ b/layout/style/CSSCounterStyleRule.cpp
@@ -38,31 +38,37 @@ void CSSCounterStyleRule::GetCssText(nsA
 void CSSCounterStyleRule::GetName(nsAString& aName) {
   aName.Truncate();
   nsAtom* name = Servo_CounterStyleRule_GetName(mRawRule);
   nsDependentAtomString nameStr(name);
   nsStyleUtil::AppendEscapedCSSIdent(nameStr, aName);
 }
 
 void CSSCounterStyleRule::SetName(const nsAString& aName) {
+  if (IsReadOnly()) {
+    return;
+  }
   NS_ConvertUTF16toUTF8 name(aName);
   if (Servo_CounterStyleRule_SetName(mRawRule, &name)) {
     if (StyleSheet* sheet = GetStyleSheet()) {
       sheet->RuleChanged(this);
     }
   }
 }
 
 #define CSS_COUNTER_DESC(name_, method_)                            \
   void CSSCounterStyleRule::Get##method_(nsAString& aValue) {       \
     aValue.Truncate();                                              \
     Servo_CounterStyleRule_GetDescriptorCssText(                    \
         mRawRule, eCSSCounterDesc_##method_, &aValue);              \
   }                                                                 \
   void CSSCounterStyleRule::Set##method_(const nsAString& aValue) { \
+    if (IsReadOnly()) {                                             \
+      return;                                                       \
+    }                                                               \
     NS_ConvertUTF16toUTF8 value(aValue);                            \
     if (Servo_CounterStyleRule_SetDescriptor(                       \
             mRawRule, eCSSCounterDesc_##method_, &value)) {         \
       if (StyleSheet* sheet = GetStyleSheet()) {                    \
         sheet->RuleChanged(this);                                   \
       }                                                             \
     }                                                               \
   }
--- a/layout/style/CSSFontFaceRule.cpp
+++ b/layout/style/CSSFontFaceRule.cpp
@@ -44,16 +44,19 @@ void CSSFontFaceRuleDecl::GetPropertyVal
 void CSSFontFaceRuleDecl::GetCssText(nsAString& aCssText) {
   aCssText.Truncate();
   Servo_FontFaceRule_GetDeclCssText(mRawRule, &aCssText);
 }
 
 void CSSFontFaceRuleDecl::SetCssText(const nsAString& aCssText,
                                      nsIPrincipal* aSubjectPrincipal,
                                      ErrorResult& aRv) {
+  if (ContainingRule()->IsReadOnly()) {
+    return;
+  }
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);  // bug 443978
 }
 
 NS_IMETHODIMP
 CSSFontFaceRuleDecl::GetPropertyValue(const nsAString& aPropName,
                                       nsAString& aResult) {
   aResult.Truncate();
   nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
@@ -65,16 +68,20 @@ CSSFontFaceRuleDecl::GetPropertyValue(co
 
 NS_IMETHODIMP
 CSSFontFaceRuleDecl::RemoveProperty(const nsAString& aPropName,
                                     nsAString& aResult) {
   nsCSSFontDesc descID = nsCSSProps::LookupFontDesc(aPropName);
   NS_ASSERTION(descID >= eCSSFontDesc_UNKNOWN && descID < eCSSFontDesc_COUNT,
                "LookupFontDesc returned value out of range");
 
+  if (ContainingRule()->IsReadOnly()) {
+    return NS_OK;
+  }
+
   aResult.Truncate();
   if (descID != eCSSFontDesc_UNKNOWN) {
     GetPropertyValue(descID, aResult);
     Servo_FontFaceRule_ResetDescriptor(mRawRule, descID);
   }
   return NS_OK;
 }
 
@@ -87,16 +94,20 @@ void CSSFontFaceRuleDecl::GetPropertyPri
 NS_IMETHODIMP
 CSSFontFaceRuleDecl::SetProperty(const nsAString& aPropName,
                                  const nsAString& aValue,
                                  const nsAString& aPriority,
                                  nsIPrincipal* aSubjectPrincipal) {
   // FIXME(heycam): If we are changing unicode-range, then a FontFace object
   // representing this rule must have its mUnicodeRange value invalidated.
 
+  if (ContainingRule()->IsReadOnly()) {
+    return NS_OK;
+  }
+
   return NS_ERROR_NOT_IMPLEMENTED;  // bug 443978
 }
 
 uint32_t CSSFontFaceRuleDecl::Length() {
   return Servo_FontFaceRule_Length(mRawRule);
 }
 
 void CSSFontFaceRuleDecl::IndexedGetter(uint32_t aIndex, bool& aFound,
--- a/layout/style/CSSFontFeatureValuesRule.cpp
+++ b/layout/style/CSSFontFeatureValuesRule.cpp
@@ -41,21 +41,29 @@ void CSSFontFeatureValuesRule::GetFontFa
 }
 
 void CSSFontFeatureValuesRule::GetValueText(nsAString& aValueText) {
   Servo_FontFeatureValuesRule_GetValueText(mRawRule, &aValueText);
 }
 
 void CSSFontFeatureValuesRule::SetFontFamily(const nsAString& aFontFamily,
                                              ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 void CSSFontFeatureValuesRule::SetValueText(const nsAString& aValueText,
                                             ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 // If this ever gets its own cycle-collection bits, reevaluate our IsCCLeaf
 // implementation.
 
 bool CSSFontFeatureValuesRule::IsCCLeaf() const { return Rule::IsCCLeaf(); }
 
--- a/layout/style/CSSKeyframeRule.cpp
+++ b/layout/style/CSSKeyframeRule.cpp
@@ -135,16 +135,20 @@ void CSSKeyframeRule::List(FILE* out, in
   }
   Servo_Keyframe_Debug(mRaw, &str);
   fprintf_stderr(out, "%s\n", str.get());
 }
 #endif
 
 template <typename Func>
 void CSSKeyframeRule::UpdateRule(Func aCallback) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   aCallback();
 
   if (StyleSheet* sheet = GetStyleSheet()) {
     sheet->RuleChanged(this);
   }
 }
 
 void CSSKeyframeRule::GetKeyText(nsAString& aKeyText) {
--- a/layout/style/CSSKeyframesRule.cpp
+++ b/layout/style/CSSKeyframesRule.cpp
@@ -62,19 +62,24 @@ class CSSKeyframeList : public dom::CSSR
     if (aIndex >= mRules.Length()) {
       aFound = false;
       return nullptr;
     }
     aFound = true;
     return GetRule(aIndex);
   }
 
-  void AppendRule() { mRules.AppendObject(nullptr); }
+  void AppendRule() {
+    MOZ_ASSERT(!mParentRule->IsReadOnly());
+    mRules.AppendObject(nullptr);
+  }
 
   void RemoveRule(uint32_t aIndex) {
+    MOZ_ASSERT(!mParentRule->IsReadOnly());
+
     if (aIndex >= mRules.Length()) {
       return;
     }
     if (css::Rule* child = mRules[aIndex]) {
       child->DropReferences();
     }
     mRules.RemoveObjectAt(aIndex);
   }
@@ -209,21 +214,27 @@ void CSSKeyframesRule::DropSheetReferenc
 static const uint32_t kRuleNotFound = std::numeric_limits<uint32_t>::max();
 
 uint32_t CSSKeyframesRule::FindRuleIndexForKey(const nsAString& aKey) {
   NS_ConvertUTF16toUTF8 key(aKey);
   return Servo_KeyframesRule_FindRule(mRawRule, &key);
 }
 
 template <typename Func>
-void CSSKeyframesRule::UpdateRule(Func aCallback) {
+nsresult CSSKeyframesRule::UpdateRule(Func aCallback) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   aCallback();
   if (StyleSheet* sheet = GetStyleSheet()) {
     sheet->RuleChanged(this);
   }
+
+  return NS_OK;
 }
 
 void CSSKeyframesRule::GetName(nsAString& aName) const {
   nsAtom* name = Servo_KeyframesRule_GetName(mRawRule);
   aName = nsDependentAtomString(name);
 }
 
 void CSSKeyframesRule::SetName(const nsAString& aName) {
--- a/layout/style/CSSKeyframesRule.h
+++ b/layout/style/CSSKeyframesRule.h
@@ -44,17 +44,17 @@ class CSSKeyframesRule final : public cs
   size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const final;
 
   JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) final;
 
  private:
   uint32_t FindRuleIndexForKey(const nsAString& aKey);
 
   template <typename Func>
-  void UpdateRule(Func aCallback);
+  nsresult UpdateRule(Func aCallback);
 
   virtual ~CSSKeyframesRule();
 
   RefPtr<RawServoKeyframesRule> mRawRule;
   RefPtr<CSSKeyframeList> mKeyframeList;  // lazily constructed
 };
 
 }  // namespace dom
--- a/layout/style/CSSMediaRule.cpp
+++ b/layout/style/CSSMediaRule.cpp
@@ -69,16 +69,19 @@ void CSSMediaRule::List(FILE* out, int32
 #endif
 
 void CSSMediaRule::GetConditionText(nsAString& aConditionText) {
   Media()->GetMediaText(aConditionText);
 }
 
 void CSSMediaRule::SetConditionText(const nsAString& aConditionText,
                                     ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
   Media()->SetMediaText(aConditionText);
 }
 
 /* virtual */
 void CSSMediaRule::GetCssText(nsAString& aCssText) const {
   Servo_MediaRule_GetCssText(mRawRule, &aCssText);
 }
 
--- a/layout/style/CSSMozDocumentRule.cpp
+++ b/layout/style/CSSMozDocumentRule.cpp
@@ -99,16 +99,20 @@ void CSSMozDocumentRule::List(FILE* out,
 #endif
 
 void CSSMozDocumentRule::GetConditionText(nsAString& aConditionText) {
   Servo_MozDocumentRule_GetConditionText(mRawRule, &aConditionText);
 }
 
 void CSSMozDocumentRule::SetConditionText(const nsAString& aConditionText,
                                           ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 /* virtual */
 void CSSMozDocumentRule::GetCssText(nsAString& aCssText) const {
   Servo_MozDocumentRule_GetCssText(mRawRule, &aCssText);
 }
 
--- a/layout/style/CSSPageRule.cpp
+++ b/layout/style/CSSPageRule.cpp
@@ -50,16 +50,20 @@ DeclarationBlock* CSSPageRuleDeclaration
   return mDecls;
 }
 
 nsresult CSSPageRuleDeclaration::SetCSSDeclaration(
     DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
   MOZ_ASSERT(aDecl, "must be non-null");
   CSSPageRule* rule = Rule();
 
+  if (rule->IsReadOnly()) {
+    return NS_OK;
+  }
+
   if (aDecl != mDecls) {
     mDecls->SetOwningRule(nullptr);
     RefPtr<DeclarationBlock> decls = aDecl;
     Servo_PageRule_SetStyle(rule->Raw(), decls->Raw());
     mDecls = decls.forget();
     mDecls->SetOwningRule(rule);
   }
 
--- a/layout/style/CSSStyleRule.cpp
+++ b/layout/style/CSSStyleRule.cpp
@@ -52,16 +52,21 @@ nsINode* CSSStyleRuleDeclaration::GetPar
 DeclarationBlock* CSSStyleRuleDeclaration::GetOrCreateCSSDeclaration(
     Operation aOperation, DeclarationBlock** aCreated) {
   return mDecls;
 }
 
 nsresult CSSStyleRuleDeclaration::SetCSSDeclaration(
     DeclarationBlock* aDecl, MutationClosureData* aClosureData) {
   CSSStyleRule* rule = Rule();
+
+  if (rule->IsReadOnly()) {
+    return NS_OK;
+  }
+
   if (RefPtr<StyleSheet> sheet = rule->GetStyleSheet()) {
     if (aDecl != mDecls) {
       mDecls->SetOwningRule(nullptr);
       RefPtr<DeclarationBlock> decls = aDecl;
       Servo_StyleRule_SetStyle(rule->Raw(), decls->Raw());
       mDecls = decls.forget();
       mDecls->SetOwningRule(rule);
     }
@@ -153,16 +158,20 @@ nsICSSDeclaration* CSSStyleRule::Style()
 
 /* CSSStyleRule implementation */
 
 void CSSStyleRule::GetSelectorText(nsAString& aSelectorText) {
   Servo_StyleRule_GetSelectorText(mRawRule, &aSelectorText);
 }
 
 void CSSStyleRule::SetSelectorText(const nsAString& aSelectorText) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   if (RefPtr<StyleSheet> sheet = GetStyleSheet()) {
     // StyleRule lives inside of the Inner, it is unsafe to call WillDirty
     // if sheet does not already have a unique Inner.
     sheet->AssertHasUniqueInner();
     sheet->WillDirty();
 
     const RawServoStyleSheetContents* contents = sheet->RawContents();
     if (Servo_StyleRule_SetSelectorText(contents, mRawRule, &aSelectorText)) {
--- a/layout/style/CSSSupportsRule.cpp
+++ b/layout/style/CSSSupportsRule.cpp
@@ -42,16 +42,20 @@ void CSSSupportsRule::List(FILE* out, in
 #endif
 
 void CSSSupportsRule::GetConditionText(nsAString& aConditionText) {
   Servo_SupportsRule_GetConditionText(mRawRule, &aConditionText);
 }
 
 void CSSSupportsRule::SetConditionText(const nsAString& aConditionText,
                                        ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 }
 
 /* virtual */
 void CSSSupportsRule::GetCssText(nsAString& aCssText) const {
   Servo_SupportsRule_GetCssText(mRawRule, &aCssText);
 }
 
--- a/layout/style/DeclarationBlock.cpp
+++ b/layout/style/DeclarationBlock.cpp
@@ -1,16 +1,17 @@
 /* -*- 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/DeclarationBlock.h"
 
+#include "mozilla/css/Rule.h"
 #include "mozilla/ServoBindings.h"
 
 #include "nsCSSProps.h"
 
 namespace mozilla {
 
 /* static */
 already_AddRefed<DeclarationBlock> DeclarationBlock::FromCssText(
@@ -18,9 +19,14 @@ already_AddRefed<DeclarationBlock> Decla
     css::Loader* aLoader) {
   NS_ConvertUTF16toUTF8 value(aCssText);
   RefPtr<RawServoDeclarationBlock> raw =
       Servo_ParseStyleAttribute(&value, aExtraData, aMode, aLoader).Consume();
   RefPtr<DeclarationBlock> decl = new DeclarationBlock(raw.forget());
   return decl.forget();
 }
 
+bool DeclarationBlock::OwnerIsReadOnly() const {
+  css::Rule* rule = GetOwningRule();
+  return rule && rule->IsReadOnly();
+}
+
 }  // namespace mozilla
--- a/layout/style/DeclarationBlock.h
+++ b/layout/style/DeclarationBlock.h
@@ -54,16 +54,17 @@ class DeclarationBlock final {
    */
   bool IsMutable() const { return !mImmutable; }
 
   /**
    * Crash in debug builds if |this| cannot be modified.
    */
   void AssertMutable() const {
     MOZ_ASSERT(IsMutable(), "someone forgot to call EnsureMutable");
+    MOZ_ASSERT(!OwnerIsReadOnly(), "User Agent sheets shouldn't be modified");
   }
 
   /**
    * Mark this declaration as unmodifiable.
    */
   void SetImmutable() { mImmutable = true; }
 
   /**
@@ -80,16 +81,18 @@ class DeclarationBlock final {
    * Mark this declaration as not dirty.
    */
   void UnsetDirty() { mIsDirty = false; }
 
   /**
    * Copy |this|, if necessary to ensure that it can be modified.
    */
   already_AddRefed<DeclarationBlock> EnsureMutable() {
+    MOZ_ASSERT(!OwnerIsReadOnly());
+
     if (!IsDirty()) {
       // In stylo, the old DeclarationBlock is stored in element's rule node
       // tree directly, to avoid new values replacing the DeclarationBlock in
       // the tree directly, we need to copy the old one here if we haven't yet
       // copied. As a result the new value does not replace rule node tree until
       // traversal happens.
       //
       // FIXME(emilio): This is a hack for ::first-line and transitions starting
@@ -131,16 +134,18 @@ class DeclarationBlock final {
     if (!(mContainer.mRaw & 0x1)) {
       return nullptr;
     }
     auto c = mContainer;
     c.mRaw &= ~uintptr_t(1);
     return c.mHTMLCSSStyleSheet;
   }
 
+  bool IsReadOnly() const;
+
   static already_AddRefed<DeclarationBlock> FromCssText(
       const nsAString& aCssText, URLExtraData* aExtraData,
       nsCompatibility aMode, css::Loader* aLoader);
 
   RawServoDeclarationBlock* Raw() const { return mRaw; }
   RawServoDeclarationBlock* const* RefRaw() const {
     static_assert(sizeof(RefPtr<RawServoDeclarationBlock>) ==
                       sizeof(RawServoDeclarationBlock*),
@@ -197,16 +202,19 @@ class DeclarationBlock final {
   bool RemovePropertyByID(nsCSSPropertyID aProperty,
                           DeclarationBlockMutationClosure aClosure = {}) {
     AssertMutable();
     return Servo_DeclarationBlock_RemovePropertyById(mRaw, aProperty, aClosure);
   }
 
  private:
   ~DeclarationBlock() = default;
+
+  bool OwnerIsReadOnly() const;
+
   union {
     // We only ever have one of these since we have an
     // nsHTMLCSSStyleSheet only for style attributes, and style
     // attributes never have an owning rule.
 
     // It's an nsHTMLCSSStyleSheet if the low bit is set.
 
     uintptr_t mRaw;
--- a/layout/style/FontFace.cpp
+++ b/layout/style/FontFace.cpp
@@ -451,16 +451,19 @@ bool FontFace::SetDescriptor(nsCSSFontDe
                              ErrorResult& aRv) {
   // FIXME We probably don't need to distinguish between this anymore
   // since we have common backend now.
   NS_ASSERTION(!HasRule(), "we don't handle rule backed FontFace objects yet");
   if (HasRule()) {
     return false;
   }
 
+  // FIXME(heycam): Should not allow modification of FontFaces that are
+  // CSS-connected and whose rule is read only.
+
   NS_ConvertUTF16toUTF8 value(aValue);
   RefPtr<URLExtraData> url = GetURLExtraData();
   if (!Servo_FontFaceRule_SetDescriptor(GetData(), aFontDesc, &value, url)) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return false;
   }
 
   if (aFontDesc == eCSSFontDesc_UnicodeRange) {
--- a/layout/style/GroupRule.cpp
+++ b/layout/style/GroupRule.cpp
@@ -69,16 +69,20 @@ void GroupRule::DropSheetReference() {
   if (mRuleList) {
     mRuleList->DropSheetReference();
   }
   Rule::DropSheetReference();
 }
 
 uint32_t GroupRule::InsertRule(const nsAString& aRule, uint32_t aIndex,
                                ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return 0;
+  }
+
   StyleSheet* sheet = GetStyleSheet();
   if (NS_WARN_IF(!sheet)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return 0;
   }
 
   uint32_t count = StyleRuleCount();
   if (aIndex > count) {
@@ -92,16 +96,20 @@ uint32_t GroupRule::InsertRule(const nsA
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return 0;
   }
   return aIndex;
 }
 
 void GroupRule::DeleteRule(uint32_t aIndex, ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   StyleSheet* sheet = GetStyleSheet();
   if (NS_WARN_IF(!sheet)) {
     aRv.Throw(NS_ERROR_FAILURE);
     return;
   }
 
   uint32_t count = StyleRuleCount();
   if (aIndex >= count) {
--- a/layout/style/MediaList.cpp
+++ b/layout/style/MediaList.cpp
@@ -36,16 +36,20 @@ JSObject* MediaList::WrapObject(JSContex
 void MediaList::SetStyleSheet(StyleSheet* aSheet) {
   MOZ_ASSERT(aSheet == mStyleSheet || !aSheet || !mStyleSheet,
              "Multiple style sheets competing for one media list");
   mStyleSheet = aSheet;
 }
 
 template <typename Func>
 nsresult MediaList::DoMediaChange(Func aCallback) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   if (mStyleSheet) {
     mStyleSheet->WillDirty();
   }
 
   nsresult rv = aCallback();
   if (NS_FAILED(rv)) {
     return rv;
   }
@@ -80,16 +84,20 @@ void MediaList::GetText(nsAString& aMedi
 /* static */
 already_AddRefed<MediaList> MediaList::Create(const nsAString& aMedia,
                                               CallerType aCallerType) {
   RefPtr<MediaList> mediaList = new MediaList(aMedia, aCallerType);
   return mediaList.forget();
 }
 
 void MediaList::SetText(const nsAString& aMediaText) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   SetTextInternal(aMediaText, CallerType::NonSystem);
 }
 
 void MediaList::GetMediaText(nsAString& aMediaText) { GetText(aMediaText); }
 
 void MediaList::SetTextInternal(const nsAString& aMediaText,
                                 CallerType aCallerType) {
   NS_ConvertUTF16toUTF8 mediaText(aMediaText);
@@ -102,31 +110,33 @@ void MediaList::IndexedGetter(uint32_t a
                               nsAString& aReturn) {
   aFound = Servo_MediaList_GetMediumAt(mRawList, aIndex, &aReturn);
   if (!aFound) {
     SetDOMStringToNull(aReturn);
   }
 }
 
 nsresult MediaList::Delete(const nsAString& aOldMedium) {
+  MOZ_ASSERT(!IsReadOnly());
   NS_ConvertUTF16toUTF8 oldMedium(aOldMedium);
   if (Servo_MediaList_DeleteMedium(mRawList, &oldMedium)) {
     return NS_OK;
   }
   return NS_ERROR_DOM_NOT_FOUND_ERR;
 }
 
 bool MediaList::Matches(const Document& aDocument) const {
   const RawServoStyleSet* rawSet =
       aDocument.StyleSetForPresShellOrMediaQueryEvaluation()->RawSet();
   MOZ_ASSERT(rawSet, "The RawServoStyleSet should be valid!");
   return Servo_MediaList_Matches(mRawList, rawSet);
 }
 
 nsresult MediaList::Append(const nsAString& aNewMedium) {
+  MOZ_ASSERT(!IsReadOnly());
   if (aNewMedium.IsEmpty()) {
     return NS_ERROR_DOM_NOT_FOUND_ERR;
   }
   NS_ConvertUTF16toUTF8 newMedium(aNewMedium);
   Servo_MediaList_AppendMedium(mRawList, &newMedium);
   return NS_OK;
 }
 
@@ -156,10 +166,14 @@ MOZ_DEFINE_MALLOC_ENCLOSING_SIZE_OF(Serv
 size_t MediaList::SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const {
   size_t n = 0;
   n += Servo_MediaList_SizeOfIncludingThis(ServoMediaListMallocSizeOf,
                                            ServoMediaListMallocEnclosingSizeOf,
                                            mRawList);
   return n;
 }
 
+bool MediaList::IsReadOnly() const {
+  return mStyleSheet && mStyleSheet->IsReadOnly();
+}
+
 }  // namespace dom
 }  // namespace mozilla
--- a/layout/style/MediaList.h
+++ b/layout/style/MediaList.h
@@ -72,16 +72,18 @@ class MediaList final : public nsISuppor
 
   nsresult Delete(const nsAString& aOldMedium);
   nsresult Append(const nsAString& aNewMedium);
 
   ~MediaList() {
     MOZ_ASSERT(!mStyleSheet, "Backpointer should have been cleared");
   }
 
+  bool IsReadOnly() const;
+
   // not refcounted; sheet will let us know when it goes away
   // mStyleSheet is the sheet that needs to be dirtied when this
   // medialist changes
   StyleSheet* mStyleSheet = nullptr;
 
  private:
   template <typename Func>
   inline nsresult DoMediaChange(Func aCallback);
--- a/layout/style/Rule.cpp
+++ b/layout/style/Rule.cpp
@@ -70,10 +70,18 @@ void Rule::DropSheetReference() { mSheet
 
 void Rule::SetCssText(const nsAString& aCssText) {
   // We used to throw for some rule types, but not all.  Specifically, we did
   // not throw for StyleRule.  Let's just always not throw.
 }
 
 Rule* Rule::GetParentRule() const { return mParentRule; }
 
+bool Rule::IsReadOnly() const {
+  MOZ_ASSERT(!mSheet || !mParentRule ||
+                 mSheet->IsReadOnly() == mParentRule->IsReadOnly(),
+             "a parent rule should be read only iff the owning sheet is "
+             "read only");
+  return mSheet && mSheet->IsReadOnly();
+}
+
 }  // namespace css
 }  // namespace mozilla
--- a/layout/style/Rule.h
+++ b/layout/style/Rule.h
@@ -84,16 +84,19 @@ class Rule : public nsISupports, public 
   void DropReferences() {
     DropSheetReference();
     DropParentRuleReference();
   }
 
   uint32_t GetLineNumber() const { return mLineNumber; }
   uint32_t GetColumnNumber() const { return mColumnNumber; }
 
+  // Whether this a rule in a read only style sheet.
+  bool IsReadOnly() const;
+
   // This is pure virtual because all of Rule's data members are non-owning and
   // thus measured elsewhere.
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
       MOZ_MUST_OVERRIDE = 0;
 
   // WebIDL interface
   virtual uint16_t Type() const = 0;
   virtual void GetCssText(nsAString& aCssText) const = 0;
--- a/layout/style/ServoCSSRuleList.cpp
+++ b/layout/style/ServoCSSRuleList.cpp
@@ -149,16 +149,21 @@ void ServoCSSRuleList::DropParentRuleRef
   EnumerateInstantiatedRules(
       [](css::Rule* rule) { rule->DropParentRuleReference(); });
 }
 
 nsresult ServoCSSRuleList::InsertRule(const nsAString& aRule, uint32_t aIndex) {
   MOZ_ASSERT(mStyleSheet,
              "Caller must ensure that "
              "the list is not unlinked from stylesheet");
+
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   NS_ConvertUTF16toUTF8 rule(aRule);
   bool nested = !!mParentRule;
   css::Loader* loader = nullptr;
 
   // TODO(emilio, bug 1535456): Should probably always be able to get a handle
   // to some loader if we're parsing an @import rule, but which one?
   //
   // StyleSheet::ReparseSheet just mints a new loader, but that'd be wrong in
@@ -173,16 +178,20 @@ nsresult ServoCSSRuleList::InsertRule(co
   if (NS_FAILED(rv)) {
     return rv;
   }
   mRules.InsertElementAt(aIndex, type);
   return rv;
 }
 
 nsresult ServoCSSRuleList::DeleteRule(uint32_t aIndex) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   nsresult rv = Servo_CssRules_DeleteRule(mRawRules, aIndex);
   if (!NS_FAILED(rv)) {
     uintptr_t rule = mRules[aIndex];
     if (rule > kMaxRuleType) {
       DropRule(already_AddRefed<css::Rule>(CastToPtr(rule)));
     }
     mRules.RemoveElementAt(aIndex);
   }
@@ -198,9 +207,17 @@ uint16_t ServoCSSRuleList::GetDOMCSSRule
 }
 
 ServoCSSRuleList::~ServoCSSRuleList() {
   MOZ_ASSERT(!mStyleSheet, "Backpointer should have been cleared");
   MOZ_ASSERT(!mParentRule, "Backpointer should have been cleared");
   DropAllRules();
 }
 
+bool ServoCSSRuleList::IsReadOnly() const {
+  MOZ_ASSERT(!mStyleSheet || !mParentRule ||
+                 mStyleSheet->IsReadOnly() == mParentRule->IsReadOnly(),
+             "a parent rule should be read only iff the owning sheet is "
+             "read only");
+  return mStyleSheet && mStyleSheet->IsReadOnly();
+}
+
 }  // namespace mozilla
--- a/layout/style/ServoCSSRuleList.h
+++ b/layout/style/ServoCSSRuleList.h
@@ -66,16 +66,18 @@ class ServoCSSRuleList final : public do
     return reinterpret_cast<css::Rule*>(aInt);
   }
 
   template <typename Func>
   void EnumerateInstantiatedRules(Func aCallback);
 
   void DropAllRules();
 
+  bool IsReadOnly() const;
+
   // mStyleSheet may be nullptr when it drops the reference to us.
   StyleSheet* mStyleSheet = nullptr;
   // mParentRule is nullptr if it isn't a nested rule list.
   css::GroupRule* mParentRule = nullptr;
   RefPtr<ServoCssRules> mRawRules;
   // Array stores either a number indicating rule type, or a pointer to
   // css::Rule. If the value is less than kMaxRuleType, the given rule
   // instance has not been constructed, and the value means the type
--- a/layout/style/StyleSheet.cpp
+++ b/layout/style/StyleSheet.cpp
@@ -237,18 +237,17 @@ void StyleSheet::ApplicableStateChanged(
   if (auto* shadow = ShadowRoot::FromNode(node)) {
     shadow->StyleSheetApplicableStateChanged(*this, aApplicable);
   } else {
     node.AsDocument()->SetStyleSheetApplicableState(this, aApplicable);
   }
 }
 
 void StyleSheet::SetDisabled(bool aDisabled) {
-  // Only allow disabling author sheets.
-  if (mParsingMode != css::eAuthorSheetFeatures) {
+  if (IsReadOnly()) {
     return;
   }
 
   if (aDisabled == Disabled()) {
     return;
   }
 
   if (aDisabled) {
@@ -291,19 +290,18 @@ StyleSheetInfo::StyleSheetInfo(CORSMode 
 StyleSheetInfo::StyleSheetInfo(StyleSheetInfo& aCopy, StyleSheet* aPrimarySheet)
     : mSheetURI(aCopy.mSheetURI),
       mOriginalSheetURI(aCopy.mOriginalSheetURI),
       mBaseURI(aCopy.mBaseURI),
       mPrincipal(aCopy.mPrincipal),
       mCORSMode(aCopy.mCORSMode),
       mReferrerPolicy(aCopy.mReferrerPolicy),
       mIntegrity(aCopy.mIntegrity),
-      mFirstChild()  // We don't rebuild the child because we're making a copy
-                     // without children.
-      ,
+      mFirstChild(),  // We don't rebuild the child because we're making a copy
+                      // without children.
       mSourceMapURL(aCopy.mSourceMapURL),
       mSourceMapURLFromComment(aCopy.mSourceMapURLFromComment),
       mSourceURL(aCopy.mSourceURL),
       mContents(Servo_StyleSheet_Clone(aCopy.mContents.get(), aPrimarySheet)
                     .Consume()),
       // Cloning aCopy.mContents will still leave us with some references to
       // data in shared memory (for example, any SelectorList objects will still
       // be shared), so continue to keep it alive.
@@ -407,16 +405,18 @@ void StyleSheet::GetTitle(nsAString& aTi
   if (!mTitle.IsEmpty()) {
     aTitle.Assign(mTitle);
   } else {
     SetDOMStringToNull(aTitle);
   }
 }
 
 void StyleSheet::WillDirty() {
+  MOZ_ASSERT(!IsReadOnly());
+
   if (IsComplete()) {
     EnsureUniqueInner();
   }
 }
 
 void StyleSheet::AddStyleSet(ServoStyleSet* aStyleSet) {
   MOZ_DIAGNOSTIC_ASSERT(!mStyleSets.Contains(aStyleSet),
                         "style set already registered");
@@ -428,16 +428,22 @@ void StyleSheet::DropStyleSet(ServoStyle
   MOZ_DIAGNOSTIC_ASSERT(found, "didn't find style set");
 #ifndef MOZ_DIAGNOSTIC_ASSERT_ENABLED
   Unused << found;
 #endif
 }
 
 void StyleSheet::EnsureUniqueInner() {
   MOZ_ASSERT(mInner->mSheets.Length() != 0, "unexpected number of outers");
+
+  if (IsReadOnly()) {
+    // Sheets that can't be modified don't need a unique inner.
+    return;
+  }
+
   mState |= State::ForcedUniqueInner;
 
   if (HasUniqueInner()) {
     // already unique
     return;
   }
 
   StyleSheetInfo* clone = mInner->CloneFor(this);
@@ -499,24 +505,30 @@ void StyleSheet::SetSourceURL(const nsAS
   mInner->mSourceURL = aSourceURL;
 }
 
 css::Rule* StyleSheet::GetDOMOwnerRule() const { return mOwnerRule; }
 
 uint32_t StyleSheet::InsertRule(const nsAString& aRule, uint32_t aIndex,
                                 nsIPrincipal& aSubjectPrincipal,
                                 ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return 0;
+  }
   if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
     return 0;
   }
   return InsertRuleInternal(aRule, aIndex, aRv);
 }
 
 void StyleSheet::DeleteRule(uint32_t aIndex, nsIPrincipal& aSubjectPrincipal,
                             ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
   if (!AreRulesAvailable(aSubjectPrincipal, aRv)) {
     return;
   }
   return DeleteRuleInternal(aIndex, aRv);
 }
 
 nsresult StyleSheet::DeleteRuleFromGroup(css::GroupRule* aGroup,
                                          uint32_t aIndex) {
@@ -525,16 +537,20 @@ nsresult StyleSheet::DeleteRuleFromGroup
   RefPtr<css::Rule> rule = aGroup->GetStyleRuleAt(aIndex);
   NS_ENSURE_TRUE(rule, NS_ERROR_ILLEGAL_VALUE);
 
   // check that the rule actually belongs to this sheet!
   if (this != rule->GetStyleSheet()) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   WillDirty();
 
   nsresult result = aGroup->DeleteStyleRuleAt(aIndex);
   NS_ENSURE_SUCCESS(result, result);
 
   rule->DropReferences();
 
   RuleRemoved(*rule);
@@ -596,16 +612,20 @@ nsresult StyleSheet::InsertRuleIntoGroup
                                          css::GroupRule* aGroup,
                                          uint32_t aIndex) {
   NS_ASSERTION(IsComplete(), "No inserting into an incomplete sheet!");
   // check that the group actually belongs to this sheet!
   if (this != aGroup->GetStyleSheet()) {
     return NS_ERROR_INVALID_ARG;
   }
 
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   WillDirty();
 
   nsresult result = InsertRuleIntoGroupInternal(aRule, aGroup, aIndex);
   NS_ENSURE_SUCCESS(result, result);
   RuleAdded(*aGroup->GetStyleRuleAt(aIndex));
   return NS_OK;
 }
 
@@ -724,16 +744,17 @@ void StyleSheet::SetAssociatedDocumentOr
 
 void StyleSheet::PrependStyleSheet(StyleSheet* aSheet) {
   WillDirty();
   PrependStyleSheetSilently(aSheet);
 }
 
 void StyleSheet::PrependStyleSheetSilently(StyleSheet* aSheet) {
   MOZ_ASSERT(aSheet);
+  MOZ_ASSERT(!IsReadOnly());
 
   aSheet->mNext = Inner().mFirstChild;
   Inner().mFirstChild = aSheet;
 
   // This is not reference counted. Our parent tells us when
   // it's going away.
   aSheet->mParent = this;
   aSheet->SetAssociatedDocumentOrShadowRoot(mDocumentOrShadowRoot,
@@ -990,18 +1011,18 @@ void StyleSheet::FinishParse() {
 nsresult StyleSheet::ReparseSheet(const nsAString& aInput) {
   if (!IsComplete()) {
     return NS_ERROR_DOM_INVALID_ACCESS_ERR;
   }
 
   // Allowing to modify UA sheets is dangerous (in the sense that C++ code
   // relies on rules in those sheets), plus they're probably going to be shared
   // across processes in which case this is directly a no-go.
-  if (GetOrigin() == StyleOrigin::UserAgent) {
-    return NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR;
+  if (IsReadOnly()) {
+    return NS_OK;
   }
 
   // Hold strong ref to the CSSLoader in case the document update
   // kills the document
   RefPtr<css::Loader> loader;
   if (Document* doc = GetAssociatedDocument()) {
     loader = doc->CSSLoader();
     NS_ASSERTION(loader, "Document with no CSS loader!");
@@ -1128,16 +1149,18 @@ ServoCSSRuleList* StyleSheet::GetCssRule
     MOZ_ASSERT(rawRules);
     mRuleList = new ServoCSSRuleList(rawRules.forget(), this, nullptr);
   }
   return mRuleList;
 }
 
 uint32_t StyleSheet::InsertRuleInternal(const nsAString& aRule, uint32_t aIndex,
                                         ErrorResult& aRv) {
+  MOZ_ASSERT(!IsReadOnly());
+
   // Ensure mRuleList is constructed.
   GetCssRulesInternal();
 
   aRv = mRuleList->InsertRule(aRule, aIndex);
   if (aRv.Failed()) {
     return 0;
   }
 
@@ -1148,16 +1171,18 @@ uint32_t StyleSheet::InsertRuleInternal(
       !RuleHasPendingChildSheet(rule)) {
     RuleAdded(*rule);
   }
 
   return aIndex;
 }
 
 void StyleSheet::DeleteRuleInternal(uint32_t aIndex, ErrorResult& aRv) {
+  MOZ_ASSERT(!IsReadOnly());
+
   // Ensure mRuleList is constructed.
   GetCssRulesInternal();
   if (aIndex >= mRuleList->Length()) {
     aRv.Throw(NS_ERROR_DOM_INDEX_SIZE_ERR);
     return;
   }
 
   // Hold a strong ref to the rule so it doesn't die when we remove it
@@ -1170,16 +1195,18 @@ void StyleSheet::DeleteRuleInternal(uint
   if (!aRv.Failed()) {
     RuleRemoved(*rule);
   }
 }
 
 nsresult StyleSheet::InsertRuleIntoGroupInternal(const nsAString& aRule,
                                                  css::GroupRule* aGroup,
                                                  uint32_t aIndex) {
+  MOZ_ASSERT(!IsReadOnly());
+
   auto rules = static_cast<ServoCSSRuleList*>(aGroup->CssRules());
   MOZ_ASSERT(rules->GetParentRule() == aGroup);
   return rules->InsertRule(aRule, aIndex);
 }
 
 StyleOrigin StyleSheet::GetOrigin() const {
   return Servo_StyleSheet_GetOrigin(Inner().mContents);
 }
@@ -1210,9 +1237,13 @@ const ServoCssRules* StyleSheet::ToShare
   MOZ_ASSERT(GetReferrerPolicy() == net::RP_Unset);
   MOZ_ASSERT(GetCORSMode() == CORS_NONE);
   MOZ_ASSERT(Inner().mIntegrity.IsEmpty());
   MOZ_ASSERT(nsContentUtils::IsSystemPrincipal(Principal()));
 
   return Servo_SharedMemoryBuilder_AddStylesheet(aBuilder, Inner().mContents);
 }
 
+bool StyleSheet::IsReadOnly() const {
+  return IsComplete() && GetOrigin() == StyleOrigin::UserAgent;
+}
+
 }  // namespace mozilla
--- a/layout/style/StyleSheet.h
+++ b/layout/style/StyleSheet.h
@@ -27,17 +27,16 @@ class nsINode;
 class nsIPrincipal;
 struct nsLayoutStylesheetCacheShm;
 struct RawServoSharedMemoryBuilder;
 
 namespace mozilla {
 
 class ServoCSSRuleList;
 class ServoStyleSet;
-enum class OriginFlags : uint8_t;
 
 typedef MozPromise</* Dummy */ bool,
                    /* Dummy */ bool,
                    /* IsExclusive = */ true>
     StyleSheetParsePromise;
 
 namespace css {
 class GroupRule;
@@ -135,17 +134,17 @@ class StyleSheet final : public nsICSSLo
   // nsICSSLoaderObserver interface
   NS_IMETHOD StyleSheetLoaded(StyleSheet* aSheet, bool aWasAlternate,
                               nsresult aStatus) final;
 
   // Internal GetCssRules methods which do not have security check and
   // completeness check.
   ServoCSSRuleList* GetCssRulesInternal();
 
-  // Returns the stylesheet's Servo origin as an OriginFlags value.
+  // Returns the stylesheet's Servo origin as a StyleOrigin value.
   mozilla::StyleOrigin GetOrigin() const;
 
   /**
    * The different changes that a stylesheet may go through.
    *
    * Used by the StyleSets in order to handle more efficiently some kinds of
    * changes.
    */
@@ -384,16 +383,21 @@ class StyleSheet final : public nsICSSLo
   const ServoCssRules* ToShared(RawServoSharedMemoryBuilder* aBuilder);
 
   // Sets the contents of this style sheet to the specified aSharedRules
   // pointer, which must be a pointer somewhere in the aSharedMemory buffer
   // as previously returned by a ToShared() call.
   void SetSharedContents(nsLayoutStylesheetCacheShm* aSharedMemory,
                          const ServoCssRules* aSharedRules);
 
+  // Whether this style sheet should not allow any modifications.
+  //
+  // This is true for any User Agent sheets once they are complete.
+  bool IsReadOnly() const;
+
  private:
   dom::ShadowRoot* GetContainingShadow() const;
 
   StyleSheetInfo& Inner() {
     MOZ_ASSERT(mInner);
     return *mInner;
   }
 
--- a/layout/style/nsDOMCSSDeclaration.cpp
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -45,16 +45,20 @@ nsresult nsDOMCSSDeclaration::GetPropert
     decl->GetPropertyValueByID(aPropID, aValue);
   }
   return NS_OK;
 }
 
 nsresult nsDOMCSSDeclaration::SetPropertyValue(
     const nsCSSPropertyID aPropID, const nsAString& aValue,
     nsIPrincipal* aSubjectPrincipal) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   switch (aPropID) {
     case eCSSProperty_background_position:
     case eCSSProperty_background_position_x:
     case eCSSProperty_background_position_y:
     case eCSSProperty_transform:
     case eCSSProperty_translate:
     case eCSSProperty_rotate:
     case eCSSProperty_scale:
@@ -93,16 +97,20 @@ void nsDOMCSSDeclaration::GetCssText(nsA
   if (decl) {
     decl->ToString(aCssText);
   }
 }
 
 void nsDOMCSSDeclaration::SetCssText(const nsAString& aCssText,
                                      nsIPrincipal* aSubjectPrincipal,
                                      ErrorResult& aRv) {
+  if (IsReadOnly()) {
+    return;
+  }
+
   // We don't need to *do* anything with the old declaration, but we need
   // to ensure that it exists, or else SetCSSDeclaration may crash.
   RefPtr<DeclarationBlock> created;
   DeclarationBlock* olddecl =
       GetOrCreateCSSDeclaration(eOperation_Modify, getter_AddRefs(created));
   if (!olddecl) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
     return;
@@ -174,16 +182,20 @@ void nsDOMCSSDeclaration::GetPropertyPri
   }
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::SetProperty(const nsAString& aPropertyName,
                                  const nsAString& aValue,
                                  const nsAString& aPriority,
                                  nsIPrincipal* aSubjectPrincipal) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   if (aValue.IsEmpty()) {
     // If the new value of the property is an empty string we remove the
     // property.
     // XXX this ignores the priority string, should it?
     return RemovePropertyInternal(aPropertyName);
   }
 
   // In the common (and fast) cases we can use the property id
@@ -207,16 +219,20 @@ nsDOMCSSDeclaration::SetProperty(const n
                                     aSubjectPrincipal);
   }
   return ParsePropertyValue(propID, aValue, important, aSubjectPrincipal);
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::RemoveProperty(const nsAString& aPropertyName,
                                     nsAString& aReturn) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   nsresult rv = GetPropertyValue(aPropertyName, aReturn);
   NS_ENSURE_SUCCESS(rv, rv);
   return RemovePropertyInternal(aPropertyName);
 }
 
 /* static */ nsDOMCSSDeclaration::ParsingEnvironment
 nsDOMCSSDeclaration::GetParsingEnvironmentForRule(const css::Rule* aRule) {
   StyleSheet* sheet = aRule ? aRule->GetStyleSheet() : nullptr;
@@ -274,16 +290,20 @@ nsresult nsDOMCSSDeclaration::ModifyDecl
   return SetCSSDeclaration(decl, aClosureData);
 }
 
 nsresult nsDOMCSSDeclaration::ParsePropertyValue(
     const nsCSSPropertyID aPropID, const nsAString& aPropValue,
     bool aIsImportant, nsIPrincipal* aSubjectPrincipal) {
   AUTO_PROFILER_LABEL_CATEGORY_PAIR(LAYOUT_CSSParsing);
 
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   DeclarationBlockMutationClosure closure = {};
   MutationClosureData closureData;
   GetPropertyChangeClosure(&closure, &closureData);
 
   return ModifyDeclaration(
       aSubjectPrincipal, &closureData,
       [&](DeclarationBlock* decl, ParsingEnvironment& env) {
         NS_ConvertUTF16toUTF8 value(aPropValue);
@@ -293,16 +313,20 @@ nsresult nsDOMCSSDeclaration::ParsePrope
       });
 }
 
 nsresult nsDOMCSSDeclaration::ParseCustomPropertyValue(
     const nsAString& aPropertyName, const nsAString& aPropValue,
     bool aIsImportant, nsIPrincipal* aSubjectPrincipal) {
   MOZ_ASSERT(nsCSSProps::IsCustomPropertyName(aPropertyName));
 
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   DeclarationBlockMutationClosure closure = {};
   MutationClosureData closureData;
   GetPropertyChangeClosure(&closure, &closureData);
 
   return ModifyDeclaration(
       aSubjectPrincipal, &closureData,
       [&](DeclarationBlock* decl, ParsingEnvironment& env) {
         NS_ConvertUTF16toUTF8 property(aPropertyName);
@@ -311,16 +335,20 @@ nsresult nsDOMCSSDeclaration::ParseCusto
             decl->Raw(), &property, &value, aIsImportant, env.mUrlExtraData,
             ParsingMode::Default, env.mCompatMode, env.mLoader, closure);
       });
 }
 
 nsresult nsDOMCSSDeclaration::RemovePropertyInternal(nsCSSPropertyID aPropID) {
   DeclarationBlock* olddecl =
       GetOrCreateCSSDeclaration(eOperation_RemoveProperty, nullptr);
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   if (!olddecl) {
     return NS_OK;  // no decl, so nothing to remove
   }
 
   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
   // Attribute setting code, which leads in turn to BeginUpdate.  We
   // need to start the update now so that the old rule doesn't get used
   // between when we mutate the declaration and when we set the new
@@ -335,16 +363,20 @@ nsresult nsDOMCSSDeclaration::RemoveProp
   if (!decl->RemovePropertyByID(aPropID, closure)) {
     return NS_OK;
   }
   return SetCSSDeclaration(decl, &closureData);
 }
 
 nsresult nsDOMCSSDeclaration::RemovePropertyInternal(
     const nsAString& aPropertyName) {
+  if (IsReadOnly()) {
+    return NS_OK;
+  }
+
   DeclarationBlock* olddecl =
       GetOrCreateCSSDeclaration(eOperation_RemoveProperty, nullptr);
   if (!olddecl) {
     return NS_OK;  // no decl, so nothing to remove
   }
 
   // For nsDOMCSSAttributeDeclaration, SetCSSDeclaration will lead to
   // Attribute setting code, which leads in turn to BeginUpdate.  We
--- a/layout/style/nsICSSDeclaration.cpp
+++ b/layout/style/nsICSSDeclaration.cpp
@@ -15,8 +15,13 @@ using mozilla::dom::DocGroup;
 DocGroup* nsICSSDeclaration::GetDocGroup() {
   nsINode* parentNode = GetParentObject();
   if (!parentNode) {
     return nullptr;
   }
 
   return parentNode->GetDocGroup();
 }
+
+bool nsICSSDeclaration::IsReadOnly() {
+  mozilla::css::Rule* rule = GetParentRule();
+  return rule && rule->IsReadOnly();
+}
--- a/layout/style/nsICSSDeclaration.h
+++ b/layout/style/nsICSSDeclaration.h
@@ -95,16 +95,19 @@ class nsICSSDeclaration : public nsISupp
                    mozilla::ErrorResult& rv) {
     rv = SetProperty(aPropName, aValue, aPriority, aSubjectPrincipal);
   }
   void RemoveProperty(const nsAString& aPropName, nsString& aRetval,
                       mozilla::ErrorResult& rv) {
     rv = RemoveProperty(aPropName, aRetval);
   }
   virtual mozilla::css::Rule* GetParentRule() = 0;
+
+ protected:
+  bool IsReadOnly();
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsICSSDeclaration, NS_ICSSDECLARATION_IID)
 
 #define NS_DECL_NSIDOMCSSSTYLEDECLARATION_HELPER                               \
   void GetCssText(nsAString& aCssText) override;                               \
   void SetCssText(const nsAString& aCssText, nsIPrincipal* aSubjectPrincipal,  \
                   mozilla::ErrorResult& aRv) override;                         \