[css-variables] Clone style when there are no variable references

When resolving the CSS Custom Properties inheritance, we recalculate all
the styles of nodes in the hierarchy. Following a similar approach to
the one used for 'independent properties', we can just clone the node's
parent style and propagate just the InheritedVariables data structure.

We'll only recalc the styles if a node has any variable reference,
defined as :var(--*), to a custom property.

Bug: 1056209
Change-Id: I81081e11cd69f06e69a6030b97fabce3a50630b3
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2111172
Commit-Queue: Javier Fernandez <jfernandez@igalia.com>
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/master@{#765278}
diff --git a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
index 6de3f6a3..3f2199b 100644
--- a/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
+++ b/third_party/blink/renderer/build/scripts/core/style/computed_style_fields.py
@@ -180,6 +180,8 @@
         if not self.is_inherited_flag:
             self.is_inherited = kwargs.pop('inherited')
             self.is_independent = kwargs.pop('independent')
+            self.is_semi_independent_variable = kwargs.pop(
+                'semi_independent_variable')
             assert self.is_inherited or not self.is_independent, \
                 'Only inherited fields can be independent'
 
diff --git a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
index 0c50e29..f929043 100755
--- a/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
+++ b/third_party/blink/renderer/build/scripts/core/style/make_computed_style_base.py
@@ -276,6 +276,7 @@
         property_name=property_['name'].original,
         inherited=property_['inherited'],
         independent=property_['independent'],
+        semi_independent_variable=property_['semi_independent_variable'],
         type_name=property_['type_name'],
         wrapper_pointer_name=property_['wrapper_pointer_name'],
         field_template=property_['field_template'],
diff --git a/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl b/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
index eeaaa05..1b01069 100644
--- a/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
+++ b/third_party/blink/renderer/build/scripts/core/style/templates/computed_style_base.h.tmpl
@@ -86,6 +86,7 @@
             |selectattr("is_property")
             |selectattr("is_inherited")
             |selectattr("is_independent")
+            |rejectattr("is_semi_independent_variable")
             |list
           )|indent(8)}}
     );
@@ -97,6 +98,19 @@
             |selectattr("is_property")
             |selectattr("is_inherited")
             |rejectattr("is_independent")
+            |rejectattr("is_semi_independent_variable")
+            |list
+          )|indent(8)}}
+    );
+  }
+
+  inline bool InheritedVariablesEqual(const ComputedStyleBase& o) const {
+    return (
+        {{fieldwise_compare(computed_style, computed_style.all_fields
+            |selectattr("is_property")
+            |selectattr("is_inherited")
+            |rejectattr("is_independent")
+            |selectattr("is_semi_independent_variable")
             |list
           )|indent(8)}}
     );
@@ -129,6 +143,9 @@
 
   void InheritFrom(const ComputedStyleBase& other,
                                       IsAtShadowBoundary isAtShadowBoundary);
+  void InheritCustomPropertiesFrom(const ComputedStyleBase& other) {
+    inherited_variables_data_ = other.inherited_variables_data_;
+  }
 
   void CopyNonInheritedFromCached(
       const ComputedStyleBase& other);
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index 2cb7fec7..cfce5ef 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -110,6 +110,16 @@
       valid_type: "bool",
     },
 
+    // - semi_independent_variable
+    // This property affects to the {Inherited, NonInherited}Variable data fields so that we
+    // can assume that the custom properties might not depend on any other property. We can
+    // handle these properties so that they are excluded from the shared Inherited/NohInherited
+    // logic, like the Equal and inheritance functions.
+    semi_independent_variable: {
+      default: false,
+      valid_type: "bool",
+    },
+
     // - affected_by_all
     // The affected_by_all flag indicates whether a change to the CSS property
     // "all" affects this property.
diff --git a/third_party/blink/renderer/core/css/resolver/style_cascade.cc b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
index 62120d0..906e723 100644
--- a/third_party/blink/renderer/core/css/resolver/style_cascade.cc
+++ b/third_party/blink/renderer/core/css/resolver/style_cascade.cc
@@ -583,6 +583,8 @@
   if (HasFontSizeDependency(To<CustomProperty>(property), data.get()))
     resolver.DetectCycle(GetCSSPropertyFontSize());
 
+  state_.Style()->SetHasVariableDeclaration();
+
   if (resolver.InCycle())
     return CSSInvalidVariableValue::Create();
 
@@ -885,6 +887,7 @@
 void StyleCascade::MarkHasVariableReference(const CSSProperty& property) {
   if (!property.IsInherited())
     state_.Style()->SetHasVariableReferenceFromNonInheritedProperty();
+  state_.Style()->SetHasVariableReference();
 }
 
 const Document& StyleCascade::GetDocument() const {
diff --git a/third_party/blink/renderer/core/dom/element.cc b/third_party/blink/renderer/core/dom/element.cc
index 103e61d..3dcea60 100644
--- a/third_party/blink/renderer/core/dom/element.cc
+++ b/third_party/blink/renderer/core/dom/element.cc
@@ -3166,7 +3166,8 @@
   const ComputedStyle* parent_style = ParentComputedStyle();
   DCHECK(parent_style);
   const ComputedStyle* style = GetComputedStyle();
-  if (!style || style->Animations() || style->Transitions())
+  if (!style || style->Animations() || style->Transitions() ||
+      style->HasVariableReference() || style->HasVariableDeclaration())
     return nullptr;
   scoped_refptr<ComputedStyle> new_style = ComputedStyle::Clone(*style);
   new_style->PropagateIndependentInheritedProperties(*parent_style);
diff --git a/third_party/blink/renderer/core/style/computed_style.cc b/third_party/blink/renderer/core/style/computed_style.cc
index c8dbf5a..431fcbd3 100644
--- a/third_party/blink/renderer/core/style/computed_style.cc
+++ b/third_party/blink/renderer/core/style/computed_style.cc
@@ -102,7 +102,7 @@
   }
 
  private:
-  void* data_refs[7];
+  void* data_refs[8];
   unsigned bitfields[5];
 };
 
@@ -291,8 +291,16 @@
   if (!non_inherited_equal && old_style.HasExplicitlyInheritedProperties()) {
     return Difference::kInherited;
   }
-  if (!old_style.IndependentInheritedEqual(new_style))
+  bool variables_independent = RuntimeEnabledFeatures::CSSCascadeEnabled() &&
+                               !old_style.HasVariableReference() &&
+                               !old_style.HasVariableDeclaration();
+  bool inherited_variables_equal = old_style.InheritedVariablesEqual(new_style);
+  if (!inherited_variables_equal && !variables_independent)
+    return Difference::kInherited;
+  if (!old_style.IndependentInheritedEqual(new_style) ||
+      !inherited_variables_equal) {
     return Difference::kIndependentInherited;
+  }
   if (non_inherited_equal) {
     DCHECK(old_style == new_style);
     if (PseudoElementStylesEqual(old_style, new_style))
@@ -312,6 +320,8 @@
 void ComputedStyle::PropagateIndependentInheritedProperties(
     const ComputedStyle& parent_style) {
   ComputedStyleBase::PropagateIndependentInheritedProperties(parent_style);
+  if (!HasVariableReference() && !HasVariableDeclaration())
+    InheritCustomPropertiesFrom(parent_style);
 }
 
 StyleSelfAlignmentData ResolvedSelfAlignment(
diff --git a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5 b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
index 8bbe5782..14e9626 100644
--- a/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
+++ b/third_party/blink/renderer/core/style/computed_style_extra_fields.json5
@@ -176,6 +176,20 @@
       custom_copy: true,
       custom_compare: true,
     },
+    // A property references a variable that needs to be resolved
+    {
+      name: "HasVariableReference",
+      field_template: "monotonic_flag",
+      default_value: "false",
+      custom_compare: true,
+    },
+    // A property which value consists of a custom property declaration.
+    {
+      name: "HasVariableDeclaration",
+      field_template: "monotonic_flag",
+      default_value: "false",
+      custom_compare: true,
+    },
     // Explicitly inherits a non-inherited property
     {
       name: "HasExplicitlyInheritedProperties",
@@ -561,12 +575,13 @@
     {
       name: "InheritedVariables",
       inherited: true,
+      semi_independent_variable: true,
       field_template: "external",
       type_name: "StyleInheritedVariables",
       include_paths: ["third_party/blink/renderer/core/style/style_inherited_variables.h"],
       default_value: "nullptr",
       wrapper_pointer_name: "scoped_refptr",
-      field_group: "*",
+      field_group: "InheritedVariables",
       computed_style_custom_functions: ["getter", "setter"],
     },
     {
diff --git a/third_party/blink/renderer/core/style/computed_style_test.cc b/third_party/blink/renderer/core/style/computed_style_test.cc
index 074d1e4..17f5b22 100644
--- a/third_party/blink/renderer/core/style/computed_style_test.cc
+++ b/third_party/blink/renderer/core/style/computed_style_test.cc
@@ -527,6 +527,134 @@
   EXPECT_FALSE(style1->CustomPropertiesEqual(properties, *style2));
 }
 
+TEST(ComputedStyleTest, CustomPropertiesInheritance_FastPath) {
+  auto dummy = std::make_unique<DummyPageHolder>(IntSize(0, 0));
+  css_test_helpers::RegisterProperty(dummy->GetDocument(), "--x", "<length>",
+                                     "0px", true);
+
+  scoped_refptr<ComputedStyle> old_style = ComputedStyle::Create();
+  scoped_refptr<ComputedStyle> new_style = ComputedStyle::Create();
+
+  using UnitType = CSSPrimitiveValue::UnitType;
+
+  const auto* value1 = CSSNumericLiteralValue::Create(1.0, UnitType::kPixels);
+  const auto* value2 = CSSNumericLiteralValue::Create(2.0, UnitType::kPixels);
+
+  EXPECT_FALSE(old_style->HasVariableDeclaration());
+  EXPECT_FALSE(old_style->HasVariableReference());
+  EXPECT_FALSE(new_style->HasVariableReference());
+  EXPECT_FALSE(new_style->HasVariableDeclaration());
+
+  // Removed variable
+  old_style->SetVariableValue("--x", value1, true);
+  EXPECT_EQ(ComputedStyle::Difference::kIndependentInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // Added a new variable
+  new_style->SetVariableValue("--x", value2, true);
+  EXPECT_EQ(ComputedStyle::Difference::kIndependentInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  // Change value of variable
+  old_style->SetVariableValue("--x", value1, true);
+  new_style->SetVariableValue("--x", value2, true);
+  new_style->SetHasVariableReference();
+  EXPECT_FALSE(new_style->HasVariableDeclaration());
+  EXPECT_TRUE(new_style->HasVariableReference());
+  EXPECT_EQ(ComputedStyle::Difference::kIndependentInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // New styles with variable declaration don't force style recalc
+  old_style->SetVariableValue("--x", value1, true);
+  new_style->SetVariableValue("--x", value2, true);
+  new_style->SetHasVariableDeclaration();
+  EXPECT_TRUE(new_style->HasVariableDeclaration());
+  EXPECT_FALSE(new_style->HasVariableReference());
+  EXPECT_EQ(ComputedStyle::Difference::kIndependentInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // New styles with variable reference don't force style recalc
+  old_style->SetVariableValue("--x", value1, true);
+  new_style->SetVariableValue("--x", value2, true);
+  new_style->SetHasVariableDeclaration();
+  new_style->SetHasVariableReference();
+  EXPECT_TRUE(new_style->HasVariableDeclaration());
+  EXPECT_TRUE(new_style->HasVariableReference());
+  EXPECT_EQ(ComputedStyle::Difference::kIndependentInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+}
+
+TEST(ComputedStyleTest, CustomPropertiesInheritance_StyleRecalc) {
+  auto dummy = std::make_unique<DummyPageHolder>(IntSize(0, 0));
+  css_test_helpers::RegisterProperty(dummy->GetDocument(), "--x", "<length>",
+                                     "0px", true);
+
+  scoped_refptr<ComputedStyle> old_style = ComputedStyle::Create();
+  scoped_refptr<ComputedStyle> new_style = ComputedStyle::Create();
+
+  using UnitType = CSSPrimitiveValue::UnitType;
+
+  const auto* value1 = CSSNumericLiteralValue::Create(1.0, UnitType::kPixels);
+  const auto* value2 = CSSNumericLiteralValue::Create(2.0, UnitType::kPixels);
+
+  EXPECT_FALSE(old_style->HasVariableDeclaration());
+  EXPECT_FALSE(old_style->HasVariableReference());
+  EXPECT_FALSE(new_style->HasVariableReference());
+  EXPECT_FALSE(new_style->HasVariableDeclaration());
+
+  // Removed variable value
+  // Old styles with variable reference force style recalc
+  old_style->SetHasVariableReference();
+  old_style->SetVariableValue("--x", value2, true);
+  EXPECT_TRUE(old_style->HasVariableReference());
+  EXPECT_EQ(ComputedStyle::Difference::kInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // New variable value
+  // Old styles with variable declaration force style recalc
+  old_style->SetHasVariableDeclaration();
+  new_style->SetVariableValue("--x", value2, true);
+  EXPECT_TRUE(old_style->HasVariableDeclaration());
+  EXPECT_EQ(ComputedStyle::Difference::kInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // Change variable value
+  // Old styles with variable declaration force style recalc
+  old_style->SetVariableValue("--x", value1, true);
+  new_style->SetVariableValue("--x", value2, true);
+  old_style->SetHasVariableDeclaration();
+  EXPECT_TRUE(old_style->HasVariableDeclaration());
+  EXPECT_EQ(ComputedStyle::Difference::kInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+
+  old_style = ComputedStyle::Create();
+  new_style = ComputedStyle::Create();
+
+  // Change variable value
+  // Old styles with variable reference force style recalc
+  old_style->SetVariableValue("--x", value1, true);
+  new_style->SetVariableValue("--x", value2, true);
+  old_style->SetHasVariableReference();
+  EXPECT_TRUE(old_style->HasVariableReference());
+  EXPECT_EQ(ComputedStyle::Difference::kInherited,
+            ComputedStyle::ComputeDifference(old_style.get(), new_style.get()));
+}
+
 TEST(ComputedStyleTest, ApplyColorSchemeLightOnDark) {
   ScopedCSSColorSchemeForTest scoped_property_enabled(true);
   ScopedCSSColorSchemeUARenderingForTest scoped_ua_enabled(true);
diff --git a/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-change-style-001.html b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-change-style-001.html
new file mode 100644
index 0000000..320b44d8
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-properties-values-api/registered-property-change-style-001.html
@@ -0,0 +1,50 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Variables Test: Style changes on registered properties using variables</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-variables-1/#using-variables">
+<meta name="assert" content="A change in the custom property declaration must be propagated to all the descendants">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="outer">
+    <div id="inbetween">
+        <div id="inner"></div>
+    </div>
+</div>
+<script>
+    "use strict";
+    test( function () {
+        outer.style.cssText = '';
+        inbetween.style.cssText = '';
+        inner.style.cssText = 'color: var(--color1)';
+        let initialValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(initialValue, "rgb(0, 0, 0)", "Initial value");
+
+        inbetween.style.cssText = 'color: green';
+        let inheritedValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(inheritedValue, "rgb(0, 128, 0)", "Inherited value");
+
+        CSS.registerProperty({name: '--color1', syntax: '<color>', initialValue: 'red', inherits: true});
+        let actualValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(actualValue, "rgb(255, 0, 0)", "Resolved value");
+    }, "New registered property declaration");
+
+    test( function () {
+        outer.style.cssText = '';
+        inbetween.style.cssText = '';
+        inner.style.cssText = 'color: var(--color2)';
+        let initialValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(initialValue, "rgb(0, 0, 0)", "Initial value");
+
+        outer.style.cssText = '--color2: blue';
+        inbetween.style.cssText = 'color: green';
+        let resolvedValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(resolvedValue, "rgb(0, 0, 255)", "Resolved value");
+
+        outer.style.cssText = '';
+        CSS.registerProperty({name: '--color2', syntax: '<color>', initialValue: 'red', inherits: true});
+        let actualValue = getComputedStyle(inner).getPropertyValue('color');
+        assert_equals(actualValue, "rgb(255, 0, 0)", "Resolved value");
+    }, "Registered property overrides a previous declaration ");
+</script>
+
diff --git a/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-001.html b/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-001.html
new file mode 100644
index 0000000..798c772
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-001.html
@@ -0,0 +1,96 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Variables Test: Style changes on properties using variables</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-variables-1/#using-variables">
+<meta name="assert" content="A change in the custom property declaration must be propagated to all the descendants">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+    .inner {
+        color: var(--x);
+        background-color: var(--x);
+        white-space: var(--x);
+    }
+</style>
+<div id="outer">
+    <div id="inbetween">
+        <div id="inner"></div>
+    </div>
+</div>
+<script>
+    "use strict";
+    let colorValues = [
+        { Id: "case1", outer: "red",   inbetween: "",     expected: "rgb(255, 0, 0)" },
+        { Id: "case2", outer: "red",   inbetween: "blue", expected: "rgb(0, 0, 255)" },
+        { Id: "case3", outer: "green", inbetween: "blue", expected: "rgb(0, 0, 255)" },
+        { Id: "case4", outer: "green", inbetween: "",     expected: "rgb(0, 128, 0)" },
+        { Id: "case5", outer: "green", inbetween: "red",  expected: "rgb(255, 0, 0)" },
+        { Id: "case6", outer: ""     , inbetween: "red",  expected: "rgb(255, 0, 0)" },
+        { Id: "case7", outer: "blue" , inbetween: ""   ,  expected: "rgb(0, 0, 255)" },
+    ];
+
+    let whiteSpaceValues = [
+        { Id: "case1", outer: "pre",      inbetween: "",         expected: "pre"      },
+        { Id: "case2", outer: "pre-wrap", inbetween: "",         expected: "pre-wrap" },
+        { Id: "case3", outer: "pre-wrap", inbetween: "nowrap",   expected: "nowrap"   },
+        { Id: "case3", outer: "pre-wrap", inbetween: "",         expected: "pre-wrap" },
+        { Id: "case4", outer: "pre-line", inbetween: "normal",   expected: "normal"   },
+        { Id: "case5", outer: "pre-line", inbetween: "",         expected: "pre-line" },
+        { Id: "case6", outer: "",         inbetween: "pre-wrap", expected: "pre-wrap" },
+        { Id: "case7", outer: "",         inbetween: "",         expected: "normal"   },
+    ];
+
+    let testcases = [
+        { property: "color",            values: colorValues,     },
+        { property: "background-color", values: colorValues,     },
+        { property: "white-space",      values: whiteSpaceValues },
+    ];
+
+    function initializeStyles() {
+        outer.style.cssText = "";
+        inbetween.style.cssText = "";
+        inner.style.cssText = "";
+    }
+
+    testcases.forEach(function (testcase) {
+        test( function () {
+            initializeStyles();
+            inner.style.cssText = testcase.property + ': var(--x)';
+            testcase.values.forEach(function (value) {
+                outer.style.cssText = "--x:" + value.outer;
+                inbetween.style.cssText = "--x:" + value.inbetween;
+                let computedStyle = getComputedStyle(inner);
+                let actualValue = computedStyle.getPropertyValue(testcase.property);
+                assert_equals(actualValue, value.expected, value.Id);
+            });
+        }, "Test declaration changes on '" + testcase.property + "' as variable");
+
+        test( function () {
+            initializeStyles();
+            inbetween.style.cssText = testcase.property + ': inherit';
+            inner.style.cssText = testcase.property + ': inherit';
+            testcase.values.forEach(function (value) {
+                outer.style.cssText = "--x:" + value.outer + "; " + testcase.property + ": " + value.outer;
+                let actualValue = getComputedStyle(inner).getPropertyValue(testcase.property);
+                let expectedValue = getComputedStyle(outer).getPropertyValue(testcase.property);
+                assert_equals(actualValue, expectedValue, value.Id);
+            });
+        }, "Avoid masking differences on '" + testcase.property + "' due to declaration changes");
+
+        test( function () {
+            initializeStyles();
+            inbetween.style.cssText = testcase.property + ': inherit';
+            inner.style.cssText = testcase.property + ': inherit';
+            let value1 = testcase.values[0];
+            let value2 = testcase.values[3];
+            outer.style.cssText = "--x:" + value2.outer + "; " + testcase.property + ": " + value1.outer;
+            let actualValue = getComputedStyle(inner).getPropertyValue(testcase.property);
+            assert_equals(actualValue, value1.expected, value1.Id);
+
+            inner.style.cssText = testcase.property + ': var(--x)';
+            actualValue = getComputedStyle(inner).getPropertyValue(testcase.property);
+            assert_equals(actualValue, value2.expected, value2.Id);
+        }, "Test changing '" + testcase.property + "' value to become a css variable");
+    });
+</script>
diff --git a/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-002.html b/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-002.html
new file mode 100644
index 0000000..9057136
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-variables/css-variable-change-style-002.html
@@ -0,0 +1,56 @@
+<!DOCTYPE html>
+<meta charset="utf-8">
+<title>CSS Variables Test: Style changes on properties using variables</title>
+<link rel="author" title="Javier Fernandez Garcia-Boente" href="mailto:jfernandez@igalia.com">
+<link rel="help" href="http://www.w3.org/TR/css-variables-1/#using-variables">
+<meta name="assert" content="A change in the custom property declaration must be propagated to all the descendants">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<style>
+    .test1 > div > div { color: var(--x); }
+    .test2 > div > div { background-color: var(--x); }
+    .test3 > div > div { white-space: var(--x); }
+</style>
+<div id="outer">
+    <div>
+        <div id="inner1"></div>
+    </div>
+    <div>
+        <div id="inner2"></div>
+    </div>
+    <div>
+        <div id="inner3"></div>
+    </div>
+</div>
+<script>
+    "use strict";
+
+    let colorValues = [
+        { Id: "case1", value: "red",   expected: "rgb(255, 0, 0)" },
+        { Id: "case2", value: "green", expected: "rgb(0, 128, 0)" },
+    ];
+    let whiteSpaceValues = [
+        { Id: "case1", value: "pre-wrap", expected: "pre-wrap" },
+        { Id: "case2", value: "nowrap",   expected: "nowrap" },
+    ];
+    let testcases = [
+        { property: "color",            className: "test1", values: colorValues, },
+        { property: "background-color", className: "test2", values: colorValues, },
+        { property: "white-space",      className: "test3", values: whiteSpaceValues},
+    ];
+
+    testcases.forEach(function (testcase) {
+        test( function () {
+            outer.className = testcase.className;
+            testcase.values.forEach(function (entry) {
+                document.body.style.cssText = "--x: " + entry.value;
+                let actualValue = getComputedStyle(inner1).getPropertyValue(testcase.property);
+                assert_equals(actualValue, entry.expected, entry.Id + "-1");
+                actualValue = getComputedStyle(inner2).getPropertyValue(testcase.property);
+                assert_equals(actualValue, entry.expected, entry.Id + "-2");
+                actualValue = getComputedStyle(inner3).getPropertyValue(testcase.property);
+                assert_equals(actualValue, entry.expected, entry.Id + "-3");
+            });
+        }, "Declaration changes on '" + testcase.property + "' propagate to all variable references");
+    });
+</script>