Basic implementation of @apply

This patch adds an initial implementation of @apply. Aside from setting
custom properties from an @apply block, all the other features of @apply
should be implemented.

The implementation strategy is similar to CSS custom properties, in that
we have a special CSSPropertyID value (CSSPropertyApplyAtRule) and store
the rules in StylePropertySets alongside regular declarations.

CSSVariableData is now able to cache a StylePropertySet object so we can
avoid having to re-parse the custom property sets across multiple style
recalcs. Note that this only helps for custom properties with no var()
references or @apply rules, as otherwise we'd create a new object every
style recalc.

The CSSOM interface for @apply implemented here differs from the current
spec draft. Instead of exposing @apply rules by changing CSSStyleRule to
inherit CSSGroupingRule, we instead treat them as regular declarations.
This has some weirdness in that CSSStyleDeclaration.item(i) can return a
value "@apply", which getPropertyValue won't accept. We need to at some
point work out the best behaviors for interfacing with the CSSOM.

As discussed with shans@, we allow @apply rules inside custom properties
and have them resolved when we resolve variables. They behave similarly
to var() references, and also contribute to cycle detection.

https://groups.google.com/a/chromium.org/forum/?fromgroups#!topic/blink-dev/Wc71ungGdn4
https://tabatkins.github.io/specs/css-apply-rule/

BUG=586974

Review URL: https://codereview.chromium.org/1645433002

Cr-Commit-Position: refs/heads/master@{#378108}
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-basic.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-basic.html
new file mode 100644
index 0000000..ae40fec
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-basic.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --foo: {
+        background-color: green;
+        width: 100px;
+        height: 200px;
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Basic usage of @apply with non-shorthand properties");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-multiple-rules.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-multiple-rules.html
new file mode 100644
index 0000000..928147c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-multiple-rules.html
@@ -0,0 +1,29 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+div {
+    background-color: red;
+}
+.e {
+    --foo: {
+        background-color: green;
+        width: 10px;
+        height: 20px;
+    };
+    @apply --foo;
+    height: 200px;
+}
+#e {
+    width: 100px;
+}
+</style>
+<div class=e id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Declarations from @apply cascade correctly across multiple rules");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2-expected.txt b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2-expected.txt
new file mode 100644
index 0000000..8ac6345e
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Custom property sets can be extended with the (self-referential) cascade trick assert_equals: expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2.html
new file mode 100644
index 0000000..fdb5972
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick-2.html
@@ -0,0 +1,38 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e1 {
+    --props: {
+        width: 100px;
+        background-color: red;
+    }
+}
+#e2 {
+    --tmp: {
+        --props: {
+            width: 0px;
+            @apply --props;
+            background-color: green;
+            height: 200px;
+        }
+    };
+    @apply --tmp;
+}
+#e3 {
+    @apply --props;
+}
+</style>
+<div id=e1>
+  <div id=e2>
+    <div id=e3>
+    </div>
+  </div>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e3).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e3).width, "100px");
+    assert_equals(getComputedStyle(e3).height, "200px");
+}, "Custom property sets can be extended with the (self-referential) cascade trick");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick.html
new file mode 100644
index 0000000..c7006fe
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade-trick.html
@@ -0,0 +1,39 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e1 {
+    --props: {
+        width: 100px;
+        background-color: red;
+    }
+}
+#e2 {
+    --props-copy: var(--props);
+}
+#e3 {
+    --props: {
+        @apply --props-copy;
+        background-color: green;
+        height: 200px;
+    }
+}
+#e4 {
+    @apply --props;
+}
+</style>
+<div id=e1>
+  <div id=e2>
+    <div id=e3>
+      <div id=e4>
+      </div>
+    </div>
+  </div>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e4).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e4).width, "100px");
+    assert_equals(getComputedStyle(e4).height, "200px");
+}, "Custom property sets can be extended with the cascade trick");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade.html
new file mode 100644
index 0000000..47c43c2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cascade.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --foo: {
+        background-color: green;
+        width: 10px;
+        height: 200px;
+    };
+    background-color: red;
+    @apply --foo;
+    width: 100px;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Declarations from @apply cascade correctly within a single rule");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cssom-apis.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cssom-apis.html
new file mode 100644
index 0000000..f916c31
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-cssom-apis.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+div {
+    width: 100px;
+    @apply --foo;
+    height: 100px;
+}
+</style>
+<script>
+var rule = document.styleSheets[0].cssRules[0];
+var style = rule.style;
+
+test(function(){
+    var expected = "width: 100px; @apply --foo; height: 100px;"
+    assert_equals(rule.cssText, "div { " + expected + " }");
+    assert_equals(style.cssText, expected);
+}, "@apply should serialize correctly in cssText");
+
+test(function(){
+    assert_equals(style.item(0), "width");
+    assert_equals(style.item(1), "@apply");
+    assert_equals(style.item(2), "height");
+}, "CSSStyleDeclaration.item should work with @apply");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-inline-style.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-inline-style.html
new file mode 100644
index 0000000..01a55c64
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-inline-style.html
@@ -0,0 +1,21 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+div {
+    width: 100px;
+    height: 100px;
+    --foo: {
+        background-color: green;
+    }
+}
+</style>
+<div id=e1 style="@apply --foo"></div>
+<div id=e2 style="@apply --foo; height: 100px"></div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e1).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e2).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e2).height, "100px");
+}, "@apply should work when specified inside inline styles");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property-expected.txt b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property-expected.txt
new file mode 100644
index 0000000..d3f896d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL Basic usage of @apply with non-shorthand properties assert_equals: expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property.html
new file mode 100644
index 0000000..db6df30
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-in-regular-property.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    background-color: green;
+    width: 100px;
+    height: 100px;
+
+    --red: { red };
+    background-color: @apply --red;
+
+    --border: 5px solid;
+    border: var(--border) @apply --red;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).borderColor, "rgb(0, 0, 0)");
+}, "Basic usage of @apply with non-shorthand properties");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-inherited-variable.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-inherited-variable.html
new file mode 100644
index 0000000..f1770a8
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-inherited-variable.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#parent {
+    --foo: {
+        background-color: green;
+        width: 100px;
+        height: 200px;
+    };
+}
+#child {
+    @apply --foo;
+}
+</style>
+<div id=parent>
+    <div id=child>
+    </div>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(child).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(child).width, "100px");
+    assert_equals(getComputedStyle(child).height, "200px");
+}, "@apply when the variable is inherited");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-cycles.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-cycles.html
new file mode 100644
index 0000000..ad2b516
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-cycles.html
@@ -0,0 +1,32 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 100px;
+    background-color: green;
+    --a1: { @apply --a2; background-color: red; };
+    --a2: { @apply --a1; background-color: red; };
+    @apply --a1;
+    @apply --a2;
+    --b1: { @apply --b2; background-color: red; };
+    --b2: var(--b3);
+    --b3: { var(--b1, background-color: red); };
+    @apply --b1;
+    @apply --b2;
+    @apply --b3;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).getPropertyValue("--a1"), "");
+    assert_equals(getComputedStyle(e).getPropertyValue("--a2"), "");
+    assert_equals(getComputedStyle(e).getPropertyValue("--b1"), "");
+    assert_equals(getComputedStyle(e).getPropertyValue("--b2"), "");
+    assert_equals(getComputedStyle(e).getPropertyValue("--b3"), "");
+}, "Variable cycle detection works with @apply");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-nesting.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-nesting.html
new file mode 100644
index 0000000..e0a472f
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-nesting.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 100px;
+    background-color: red;
+    --apply1: @apply;
+    --apply2: --red;
+    --red: { background-color: red; };
+    --foo: {
+        background-color: green;
+        var(--apply1) var(--apply2);
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).getPropertyValue("--foo"), " { background-color: green;  @apply  --red; }");
+}, "@apply inside custom properties don't get expanded if the @apply rule was " +
+   "constructed from variable references");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-syntax.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-syntax.html
new file mode 100644
index 0000000..1cbf1e4
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-invalid-syntax.html
@@ -0,0 +1,47 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --green: {
+        background-color: green;
+    };
+    @apply --green;
+
+    @apply --unknown;
+    --red: {
+        background-color: red;
+    };
+    @apply--red;
+    @apply red;
+    @apply(--red);
+    @apply --red !important;
+    @apply --red { }
+    @apply { background-color: red; }
+    --redvar: --red;
+    @apply var(--redvar);
+
+    --red2: { background-color: red; } bla ;
+    @apply --red2;
+
+    width: 100px;
+    height: 100px;
+
+    animation: anim 0s both;
+}
+
+@apply --red;
+
+@keyframes anim {
+    100% {
+        @apply --red;
+    }
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+}, "Tests various invalid @apply rules get rejected");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-nested.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-nested.html
new file mode 100644
index 0000000..afad3a2
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-nested.html
@@ -0,0 +1,27 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 200px;
+    --bar: {
+        background-color: green;
+    };
+    --foo: {
+        background-color: red;
+        @apply --bar;
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+    assert_equals(getComputedStyle(e).getPropertyValue("--foo"), " { background-color: red;  background-color: green;  }");
+}, "@apply rules can be used inside variables");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property-expected.txt b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property-expected.txt
new file mode 100644
index 0000000..58fff47
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL @apply can override custom properties assert_equals: expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property.html
new file mode 100644
index 0000000..646c0ff
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-custom-property.html
@@ -0,0 +1,30 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+
+    --foo: {
+        --before: green;
+        --after: 0px;
+    };
+
+    --before: red;
+    @apply --foo;
+    --after: 200px;
+
+    height: var(--after);
+    background-color: var(--before);
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "@apply can override custom properties");
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self-expected.txt b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self-expected.txt
new file mode 100644
index 0000000..319717c
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self-expected.txt
@@ -0,0 +1,4 @@
+This is a testharness.js-based test.
+FAIL @apply can override the custom property it applies assert_equals: expected "rgb(0, 128, 0)" but got "rgba(0, 0, 0, 0)"
+Harness: the test ran to completion.
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self.html
new file mode 100644
index 0000000..8d57092
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-override-self.html
@@ -0,0 +1,25 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 100px;
+
+    --foo: {
+        --foo: green;
+    };
+
+    @apply --foo;
+    background-color: var(--foo);
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).getPropertyValue("--foo"), " green");
+}, "@apply can override the custom property it applies");
+</script>
+
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-recursive.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-recursive.html
new file mode 100644
index 0000000..41ab99aa
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-recursive.html
@@ -0,0 +1,23 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 200px;
+    background-color: green;
+    --foo: {
+        @apply --foo;
+        background-color: red;
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).getPropertyValue("--foo"), "");
+}, "Recursive @apply is invalid");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-shorthands.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-shorthands.html
new file mode 100644
index 0000000..9054dd3
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-shorthands.html
@@ -0,0 +1,20 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --foo: {
+        border: 10px solid green;
+        font: 20px serif;
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).border, "10px solid rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).font, "normal normal normal normal 20px / normal serif");
+}, "Basic usage of @apply with shorthand properties");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-variable-references.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-variable-references.html
new file mode 100644
index 0000000..a134b8d
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-variable-references.html
@@ -0,0 +1,24 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --green: green;
+    --width: 100px;
+    --foo: {
+        background-color: var(--green, red);
+        width: var(--width);
+        height: var(--unknown, 200px);
+    };
+    @apply --foo;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Tests that variables can be referenced inside @apply rules");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-whitespace.html b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-whitespace.html
new file mode 100644
index 0000000..c839dd61
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/at-apply-whitespace.html
@@ -0,0 +1,22 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --background-color:{ background-color: green; };
+    --width:  {width:100px}  ;
+    --height:{height: 200px} ;
+    @apply --background-color ;
+    @apply/**/--width;
+    @apply --height;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Whitespace is optional in @apply and custom property sets");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/custom-property-set-syntax.html b/third_party/WebKit/LayoutTests/fast/css/atapply/custom-property-set-syntax.html
new file mode 100644
index 0000000..3513db5
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/custom-property-set-syntax.html
@@ -0,0 +1,17 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    width: 100px;
+    height: 200px;
+    @apply --foo;
+    --foo: { ... ; background-color: green;
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+}, "Closing } can be omitted in custom property sets");
+</script>
diff --git a/third_party/WebKit/LayoutTests/fast/css/atapply/multiple-at-apply-rules.html b/third_party/WebKit/LayoutTests/fast/css/atapply/multiple-at-apply-rules.html
new file mode 100644
index 0000000..91e7b34
--- /dev/null
+++ b/third_party/WebKit/LayoutTests/fast/css/atapply/multiple-at-apply-rules.html
@@ -0,0 +1,26 @@
+<!doctype html>
+<script src="../../../resources/testharness.js"></script>
+<script src="../../../resources/testharnessreport.js"></script>
+<style>
+#e {
+    --foo: {
+        background-color: red;
+        width: 100px;
+        height: 200px;
+    };
+    --bar: {
+        background-color: green;
+    };
+    @apply --foo;
+    @apply --bar;
+}
+</style>
+<div id=e>
+</div>
+<script>
+test(function(){
+    assert_equals(getComputedStyle(e).backgroundColor, "rgb(0, 128, 0)");
+    assert_equals(getComputedStyle(e).width, "100px");
+    assert_equals(getComputedStyle(e).height, "200px");
+}, "Multiple @apply rules can be used simultaneously");
+</script>
diff --git a/third_party/WebKit/Source/build/scripts/css_properties.py b/third_party/WebKit/Source/build/scripts/css_properties.py
index a79fa7016..1d461f7 100755
--- a/third_party/WebKit/Source/build/scripts/css_properties.py
+++ b/third_party/WebKit/Source/build/scripts/css_properties.py
@@ -52,8 +52,10 @@
         self._aliases = [property for property in properties if property['alias_for']]
         properties = [property for property in properties if not property['alias_for']]
 
-        # We currently assign 0 to CSSPropertyInvalid, and 1 to CSSPropertyVariable
-        self._first_enum_value = 2
+        # 0: CSSPropertyInvalid
+        # 1: CSSPropertyApplyAtRule
+        # 2: CSSPropertyVariable
+        self._first_enum_value = 3
 
         # StylePropertyMetadata additionally assumes there are under 1024 properties.
         assert self._first_enum_value + len(properties) < 512, 'Property aliasing expects there are under 512 properties.'
diff --git a/third_party/WebKit/Source/build/scripts/make_css_property_names.py b/third_party/WebKit/Source/build/scripts/make_css_property_names.py
index 5bdaa1c..2360d0c 100755
--- a/third_party/WebKit/Source/build/scripts/make_css_property_names.py
+++ b/third_party/WebKit/Source/build/scripts/make_css_property_names.py
@@ -28,7 +28,9 @@
 
 enum CSSPropertyID {
     CSSPropertyInvalid = 0,
-    CSSPropertyVariable = 1,
+    // This isn't a property, but we need to know the position of @apply rules in style rules
+    CSSPropertyApplyAtRule = 1,
+    CSSPropertyVariable = 2,
 %(property_enums)s
 };
 
@@ -45,7 +47,7 @@
 
 inline CSSPropertyID convertToCSSPropertyID(int value)
 {
-    ASSERT((value >= firstCSSProperty && value <= lastCSSProperty) || value == CSSPropertyInvalid || value == CSSPropertyVariable);
+    ASSERT(value >= CSSPropertyInvalid && value <= lastCSSProperty);
     return static_cast<CSSPropertyID>(value);
 }
 
diff --git a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
index 97f0506..fae3302 100644
--- a/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
+++ b/third_party/WebKit/Source/build/scripts/templates/CSSPropertyMetadata.cpp.tmpl
@@ -28,28 +28,28 @@
 }
 {% endfor %}
 
-// There is one more valid property ID than the total count of CSS properties
-// because of custom properties.
-static const int numValidPropertyIDs = numCSSProperties + 1;
-
 bool CSSPropertyMetadata::isEnabledProperty(CSSPropertyID unresolvedProperty)
 {
     CSSPropertyID property = resolveCSSPropertyID(unresolvedProperty);
-    static BitArray<numValidPropertyIDs>* enabledProperties = 0;
+    static BitArray<numCSSProperties>* enabledProperties = 0;
     if (!enabledProperties) {
-        enabledProperties = new BitArray<numValidPropertyIDs>(true); // All bits sets to 1.
-        static_assert(CSSPropertyVariable == {{first_enum_value - 1}}, "CSSPropertyVariable should directly precede first_enum_value.");
-        if (!RuntimeEnabledFeatures::cssVariablesEnabled())
-            enabledProperties->clear(0);
+        enabledProperties = new BitArray<numCSSProperties>(true); // All bits sets to 1.
         {% for property_id, property in properties.items() if property.runtime_flag %}
         if (!RuntimeEnabledFeatures::{{property.runtime_flag|lower_first}}Enabled())
-            enabledProperties->clear({{property_id}} - {{first_enum_value - 1}});
+            enabledProperties->clear({{property_id}} - {{first_enum_value}});
         {% endfor %}
         {% for property_id, property in properties.items() if property.is_internal %}
-        enabledProperties->clear({{property_id}} - {{first_enum_value - 1}});
+        enabledProperties->clear({{property_id}} - {{first_enum_value}});
         {% endfor %}
     }
-    return enabledProperties->get(property - {{first_enum_value - 1}});
+
+    if (unresolvedProperty >= {{first_enum_value}})
+        return enabledProperties->get(property - {{first_enum_value}});
+
+    if (unresolvedProperty == CSSPropertyVariable)
+        return RuntimeEnabledFeatures::cssVariablesEnabled();
+    ASSERT(unresolvedProperty == CSSPropertyApplyAtRule);
+    return RuntimeEnabledFeatures::cssApplyAtRulesEnabled();
 }
 
 void CSSPropertyMetadata::filterEnabledCSSPropertiesIntoVector(const CSSPropertyID* properties, size_t propertyCount, Vector<CSSPropertyID>& outVector)
diff --git a/third_party/WebKit/Source/core/css/CSSVariableData.cpp b/third_party/WebKit/Source/core/css/CSSVariableData.cpp
index 75134d96..30c6d9c 100644
--- a/third_party/WebKit/Source/core/css/CSSVariableData.cpp
+++ b/third_party/WebKit/Source/core/css/CSSVariableData.cpp
@@ -4,11 +4,22 @@
 
 #include "core/css/CSSVariableData.h"
 
+#include "core/css/parser/CSSParser.h"
 #include "core/css/parser/CSSParserTokenRange.h"
 #include "wtf/text/StringBuilder.h"
 
 namespace blink {
 
+const StylePropertySet* CSSVariableData::propertySet()
+{
+    ASSERT(!m_needsVariableResolution);
+    if (!m_cachedPropertySet) {
+        m_propertySet = CSSParser::parseCustomPropertySet(m_tokens);
+        m_cachedPropertySet = true;
+    }
+    return m_propertySet.get();
+}
+
 template<typename CharacterType> void CSSVariableData::updateTokens(const CSSParserTokenRange& range)
 {
     const CharacterType* currentOffset = m_backingString.getCharacters<CharacterType>();
@@ -55,6 +66,7 @@
 
 CSSVariableData::CSSVariableData(const CSSParserTokenRange& range, bool needsVariableResolution)
     : m_needsVariableResolution(needsVariableResolution)
+    , m_cachedPropertySet(false)
 {
     ASSERT(!range.atEnd());
     consumeAndUpdateTokens(range);
diff --git a/third_party/WebKit/Source/core/css/CSSVariableData.h b/third_party/WebKit/Source/core/css/CSSVariableData.h
index 5e19511..974a9bd 100644
--- a/third_party/WebKit/Source/core/css/CSSVariableData.h
+++ b/third_party/WebKit/Source/core/css/CSSVariableData.h
@@ -5,6 +5,7 @@
 #ifndef CSSVariableData_h
 #define CSSVariableData_h
 
+#include "core/css/StylePropertySet.h"
 #include "core/css/parser/CSSParserToken.h"
 #include "core/css/parser/CSSParserTokenRange.h"
 #include "wtf/RefCounted.h"
@@ -35,6 +36,9 @@
     bool operator==(const CSSVariableData& other) const;
 
     bool needsVariableResolution() const { return m_needsVariableResolution; }
+
+    const StylePropertySet* propertySet();
+
 private:
     CSSVariableData(const CSSParserTokenRange&, bool needsVariableResolution);
 
@@ -46,6 +50,7 @@
         : m_backingString(backingString)
         , m_tokens(resolvedTokens)
         , m_needsVariableResolution(false)
+        , m_cachedPropertySet(false)
     { }
 
     void consumeAndUpdateTokens(const CSSParserTokenRange&);
@@ -54,6 +59,10 @@
     String m_backingString;
     Vector<CSSParserToken> m_tokens;
     const bool m_needsVariableResolution;
+
+    // Parsed representation for @apply
+    bool m_cachedPropertySet;
+    RefPtrWillBePersistent<StylePropertySet> m_propertySet;
 };
 
 } // namespace blink
diff --git a/third_party/WebKit/Source/core/css/PropertySetCSSStyleDeclaration.cpp b/third_party/WebKit/Source/core/css/PropertySetCSSStyleDeclaration.cpp
index 8c444a7..7cf7a0c 100644
--- a/third_party/WebKit/Source/core/css/PropertySetCSSStyleDeclaration.cpp
+++ b/third_party/WebKit/Source/core/css/PropertySetCSSStyleDeclaration.cpp
@@ -155,6 +155,8 @@
     StylePropertySet::PropertyReference property = propertySet().propertyAt(i);
     if (RuntimeEnabledFeatures::cssVariablesEnabled() && property.id() == CSSPropertyVariable)
         return toCSSCustomPropertyDeclaration(property.value())->name();
+    if (property.id() == CSSPropertyApplyAtRule)
+        return "@apply";
     return getPropertyName(property.id());
 }
 
diff --git a/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp b/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
index 82dda72..7596baf 100644
--- a/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
+++ b/third_party/WebKit/Source/core/css/StylePropertySerializer.cpp
@@ -192,6 +192,17 @@
     return result.toString();
 }
 
+static String getApplyAtRuleText(const CSSValue* value, bool isNotFirstDecl)
+{
+    StringBuilder result;
+    if (isNotFirstDecl)
+        result.append(' ');
+    result.appendLiteral("@apply ");
+    result.append(toCSSCustomIdentValue(value)->value());
+    result.append(';');
+    return result.toString();
+}
+
 String StylePropertySerializer::getPropertyText(CSSPropertyID propertyID, const String& value, bool isImportant, bool isNotFirstDecl) const
 {
     StringBuilder result;
@@ -367,6 +378,9 @@
         case CSSPropertyAll:
             result.append(getPropertyText(propertyID, property.value()->cssText(), property.isImportant(), numDecls++));
             continue;
+        case CSSPropertyApplyAtRule:
+            result.append(getApplyAtRuleText(property.value(), numDecls++));
+            continue;
         default:
             break;
         }
diff --git a/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.cpp b/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.cpp
index 809d035..881986c 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.cpp
@@ -31,6 +31,8 @@
         return CSSAtRuleViewport;
     if (name.equalIgnoringASCIICase("-webkit-keyframes"))
         return CSSAtRuleWebkitKeyframes;
+    if (name.equalIgnoringASCIICase("apply"))
+        return CSSAtRuleApply;
     return CSSAtRuleInvalid;
 }
 
@@ -72,6 +74,10 @@
         feature = UseCounter::CSSAtRuleWebkitKeyframes;
         break;
 
+    case CSSAtRuleApply:
+        feature = UseCounter::CSSAtRuleApply;
+        break;
+
     case CSSAtRuleInvalid:
         // fallthrough
     default:
diff --git a/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.h b/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.h
index ccd12e4..003b41f 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.h
+++ b/third_party/WebKit/Source/core/css/parser/CSSAtRuleID.h
@@ -24,6 +24,7 @@
     CSSAtRuleViewport = 9,
 
     CSSAtRuleWebkitKeyframes = 10,
+    CSSAtRuleApply = 11,
 };
 
 CSSAtRuleID cssAtRuleID(const CSSParserString&);
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSParser.cpp
index 413bbf7..a6defa4 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSParser.cpp
@@ -89,6 +89,11 @@
     return CSSParserImpl::parseVariableValue(declaration, propertyName, value, important, context);
 }
 
+PassRefPtrWillBeRawPtr<ImmutableStylePropertySet> CSSParser::parseCustomPropertySet(CSSParserTokenRange range)
+{
+    return CSSParserImpl::parseCustomPropertySet(range);
+}
+
 bool CSSParser::parseValue(MutableStylePropertySet* declaration, CSSPropertyID unresolvedProperty, const String& string, bool important, const CSSParserContext& context)
 {
     return CSSParserImpl::parseValue(declaration, unresolvedProperty, string, important, context);
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParser.h b/third_party/WebKit/Source/core/css/parser/CSSParser.h
index 01b441f..1809796 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParser.h
+++ b/third_party/WebKit/Source/core/css/parser/CSSParser.h
@@ -14,6 +14,7 @@
 namespace blink {
 
 class CSSParserObserver;
+class CSSParserTokenRange;
 class CSSSelectorList;
 class Element;
 class ImmutableStylePropertySet;
@@ -37,6 +38,7 @@
     static bool parseValue(MutableStylePropertySet*, CSSPropertyID unresolvedProperty, const String&, bool important, StyleSheetContents*);
 
     static bool parseValueForCustomProperty(MutableStylePropertySet*, const AtomicString& propertyName, const String& value, bool important, StyleSheetContents*);
+    static PassRefPtrWillBeRawPtr<ImmutableStylePropertySet> parseCustomPropertySet(CSSParserTokenRange);
 
     // This is for non-shorthands only
     static PassRefPtrWillBeRawPtr<CSSValue> parseSingleValue(CSSPropertyID, const String&, const CSSParserContext& = strictCSSParserContext());
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParserImpl.cpp b/third_party/WebKit/Source/core/css/parser/CSSParserImpl.cpp
index b20276b..aadb57e 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParserImpl.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSParserImpl.cpp
@@ -4,6 +4,7 @@
 
 #include "core/css/parser/CSSParserImpl.h"
 
+#include "core/css/CSSCustomIdentValue.h"
 #include "core/css/CSSCustomPropertyDeclaration.h"
 #include "core/css/CSSKeyframesRule.h"
 #include "core/css/CSSStyleSheet.h"
@@ -75,6 +76,8 @@
             if (seenCustomProperties.contains(name))
                 continue;
             seenCustomProperties.add(name);
+        } else if (property.id() == CSSPropertyApplyAtRule) {
+            // TODO(timloh): Do we need to do anything here?
         } else {
             if (seenProperties.get(propertyIDIndex))
                 continue;
@@ -224,6 +227,28 @@
     return selectorList;
 }
 
+PassRefPtrWillBeRawPtr<ImmutableStylePropertySet> CSSParserImpl::parseCustomPropertySet(CSSParserTokenRange range)
+{
+    range.consumeWhitespace();
+    if (range.peek().type() != LeftBraceToken)
+        return nullptr;
+    CSSParserTokenRange block = range.consumeBlock();
+    range.consumeWhitespace();
+    if (!range.atEnd())
+        return nullptr;
+    CSSParserImpl parser(strictCSSParserContext());
+    parser.consumeDeclarationList(block, StyleRule::Style);
+
+    // Drop nested @apply rules. Seems nicer to do this here instead of making
+    // a different StyleRule type
+    for (size_t i = parser.m_parsedProperties.size(); i--; ) {
+        if (parser.m_parsedProperties[i].id() == CSSPropertyApplyAtRule)
+            parser.m_parsedProperties.remove(i);
+    }
+
+    return createStylePropertySet(parser.m_parsedProperties, HTMLStandardMode);
+}
+
 PassOwnPtr<Vector<double>> CSSParserImpl::parseKeyframeKeyList(const String& keyList)
 {
     return consumeKeyframeKeyList(CSSTokenizer::Scope(keyList).tokenRange());
@@ -349,14 +374,18 @@
             return consumeImportRule(prelude);
         if (allowedRules <= AllowNamespaceRules && id == CSSAtRuleNamespace)
             return consumeNamespaceRule(prelude);
+        if (allowedRules == ApplyRules && id == CSSAtRuleApply) {
+            consumeApplyRule(prelude);
+            return nullptr; // consumeApplyRule just updates m_parsedProperties
+        }
         return nullptr; // Parse error, unrecognised at-rule without block
     }
 
     CSSParserTokenRange block = range.consumeBlock();
     if (allowedRules == KeyframeRules)
         return nullptr; // Parse error, no at-rules supported inside @keyframes
-    if (allowedRules == NoRules)
-        return nullptr; // Parse error, no at-rules supported inside declaration lists
+    if (allowedRules == NoRules || allowedRules == ApplyRules)
+        return nullptr; // Parse error, no at-rules with blocks supported inside declaration lists
 
     ASSERT(allowedRules <= RegularRules);
 
@@ -602,6 +631,19 @@
     return StyleRulePage::create(std::move(selectorList), createStylePropertySet(m_parsedProperties, m_context.mode()));
 }
 
+void CSSParserImpl::consumeApplyRule(CSSParserTokenRange prelude)
+{
+    ASSERT(RuntimeEnabledFeatures::cssApplyAtRulesEnabled());
+
+    prelude.consumeWhitespace();
+    const CSSParserToken& ident = prelude.consumeIncludingWhitespace();
+    if (!prelude.atEnd() || !CSSVariableParser::isValidVariableName(ident))
+        return; // Parse error, expected a single custom property name
+    m_parsedProperties.append(CSSProperty(
+        CSSPropertyApplyAtRule,
+        CSSCustomIdentValue::create(ident.value())));
+}
+
 PassRefPtrWillBeRawPtr<StyleRuleKeyframe> CSSParserImpl::consumeKeyframeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block)
 {
     OwnPtr<Vector<double>> keyList = consumeKeyframeKeyList(prelude);
@@ -684,7 +726,8 @@
             break;
         }
         case AtKeywordToken: {
-            RefPtrWillBeRawPtr<StyleRuleBase> rule = consumeAtRule(range, NoRules);
+            AllowedRulesType allowedRules = ruleType == StyleRule::Style && RuntimeEnabledFeatures::cssApplyAtRulesEnabled() ? ApplyRules : NoRules;
+            RefPtrWillBeRawPtr<StyleRuleBase> rule = consumeAtRule(range, allowedRules);
             ASSERT_UNUSED(rule, !rule);
             break;
         }
diff --git a/third_party/WebKit/Source/core/css/parser/CSSParserImpl.h b/third_party/WebKit/Source/core/css/parser/CSSParserImpl.h
index 882cbb4..c3b696d 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSParserImpl.h
+++ b/third_party/WebKit/Source/core/css/parser/CSSParserImpl.h
@@ -51,6 +51,7 @@
         AllowNamespaceRules,
         RegularRules,
         KeyframeRules,
+        ApplyRules, // For @apply inside style rules
         NoRules, // For parsing at-rules inside declaration lists
     };
 
@@ -62,6 +63,8 @@
     static void parseStyleSheet(const String&, const CSSParserContext&, StyleSheetContents*);
     static CSSSelectorList parsePageSelector(CSSParserTokenRange, StyleSheetContents*);
 
+    static PassRefPtrWillBeRawPtr<ImmutableStylePropertySet> parseCustomPropertySet(CSSParserTokenRange);
+
     static PassOwnPtr<Vector<double>> parseKeyframeKeyList(const String&);
 
     bool supportsDeclaration(CSSParserTokenRange&);
@@ -93,6 +96,8 @@
     PassRefPtrWillBeRawPtr<StyleRuleFontFace> consumeFontFaceRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
     PassRefPtrWillBeRawPtr<StyleRuleKeyframes> consumeKeyframesRule(bool webkitPrefixed, CSSParserTokenRange prelude, CSSParserTokenRange block);
     PassRefPtrWillBeRawPtr<StyleRulePage> consumePageRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
+    // Updates m_parsedProperties
+    void consumeApplyRule(CSSParserTokenRange prelude);
 
     PassRefPtrWillBeRawPtr<StyleRuleKeyframe> consumeKeyframeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
     PassRefPtrWillBeRawPtr<StyleRule> consumeStyleRule(CSSParserTokenRange prelude, CSSParserTokenRange block);
diff --git a/third_party/WebKit/Source/core/css/parser/CSSVariableParser.cpp b/third_party/WebKit/Source/core/css/parser/CSSVariableParser.cpp
index f025509..a54302e 100644
--- a/third_party/WebKit/Source/core/css/parser/CSSVariableParser.cpp
+++ b/third_party/WebKit/Source/core/css/parser/CSSVariableParser.cpp
@@ -47,6 +47,13 @@
 
         const CSSParserToken& token = range.consume();
         switch (token.type()) {
+        case AtKeywordToken: {
+            // This might have false positives if the @apply doesn't actually match
+            // the syntax, but that just means we do extra computation work.
+            if (token.valueEqualsIgnoringASCIICase("apply"))
+                hasReferences = true;
+            break;
+        }
         case DelimiterToken: {
             if (token.delimiter() == '!' && isTopLevelBlock)
                 return false;
diff --git a/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.cpp b/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.cpp
index ace65cc..5496728 100644
--- a/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.cpp
+++ b/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.cpp
@@ -15,6 +15,7 @@
 #include "core/css/parser/CSSParserTokenRange.h"
 #include "core/css/parser/CSSParserValues.h"
 #include "core/css/parser/CSSPropertyParser.h"
+#include "core/css/parser/CSSVariableParser.h"
 #include "core/css/resolver/StyleBuilder.h"
 #include "core/css/resolver/StyleResolverState.h"
 #include "core/style/StyleVariableData.h"
@@ -87,6 +88,39 @@
     return true;
 }
 
+void CSSVariableResolver::resolveApplyAtRule(CSSParserTokenRange& range,
+    Vector<CSSParserToken>& result)
+{
+    ASSERT(range.peek().type() == AtKeywordToken && range.peek().valueEqualsIgnoringASCIICase("apply"));
+    CSSParserTokenRange originalRange = range;
+
+    range.consumeIncludingWhitespace();
+    const CSSParserToken& variableName = range.consumeIncludingWhitespace();
+    if (!CSSVariableParser::isValidVariableName(variableName)
+        || !(range.atEnd() || range.peek().type() == SemicolonToken || range.peek().type() == RightBraceToken)) {
+        range = originalRange;
+        result.append(range.consume());
+        return;
+    }
+    if (range.peek().type() == SemicolonToken)
+        range.consume();
+
+    CSSVariableData* variableData = valueForCustomProperty(variableName.value());
+    if (!variableData)
+        return; // Invalid custom property
+
+    CSSParserTokenRange rule = variableData->tokenRange();
+    rule.consumeWhitespace();
+    if (rule.peek().type() != LeftBraceToken)
+        return;
+    CSSParserTokenRange ruleContents = rule.consumeBlock();
+    rule.consumeWhitespace();
+    if (!rule.atEnd())
+        return;
+
+    result.appendRange(ruleContents.begin(), ruleContents.end());
+}
+
 bool CSSVariableResolver::resolveTokenRange(CSSParserTokenRange range,
     Vector<CSSParserToken>& result)
 {
@@ -94,6 +128,9 @@
     while (!range.atEnd()) {
         if (range.peek().functionId() == CSSValueVar) {
             success &= resolveVariableReference(range.consumeBlock(), result);
+        } else if (range.peek().type() == AtKeywordToken && range.peek().valueEqualsIgnoringASCIICase("apply")
+            && RuntimeEnabledFeatures::cssApplyAtRulesEnabled()) {
+            resolveApplyAtRule(range, result);
         } else {
             result.append(range.consume());
         }
diff --git a/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.h b/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.h
index 81d69bd..c49b97f 100644
--- a/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.h
+++ b/third_party/WebKit/Source/core/css/resolver/CSSVariableResolver.h
@@ -32,12 +32,14 @@
 
     // These return false if we encounter a reference to an invalid variable with no fallback
 
-    // Resolves a range which may contain var() references
+    // Resolves a range which may contain var() references or @apply rules
     bool resolveTokenRange(CSSParserTokenRange, Vector<CSSParserToken>& result);
     // Resolves the fallback (if present) of a var() reference, starting from the comma
     bool resolveFallback(CSSParserTokenRange, Vector<CSSParserToken>& result);
     // Resolves the contents of a var() reference
     bool resolveVariableReference(CSSParserTokenRange, Vector<CSSParserToken>& result);
+    // Consumes and resolves an @apply rule
+    void resolveApplyAtRule(CSSParserTokenRange&, Vector<CSSParserToken>& result);
 
     // These return null if the custom property is invalid
 
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp b/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
index cc13a8e7..fe12341 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
+++ b/third_party/WebKit/Source/core/css/resolver/StyleResolver.cpp
@@ -84,6 +84,7 @@
 #include "core/inspector/InspectorInstrumentation.h"
 #include "core/layout/GeneratedChildren.h"
 #include "core/layout/LayoutView.h"
+#include "core/style/StyleVariableData.h"
 #include "core/svg/SVGDocumentExtensions.h"
 #include "core/svg/SVGElement.h"
 #include "platform/RuntimeEnabledFeatures.h"
@@ -1269,15 +1270,37 @@
 }
 
 template <CSSPropertyPriority priority>
+void StyleResolver::applyPropertiesForApplyAtRule(StyleResolverState& state, const CSSValue* value, bool isImportant, bool inheritedOnly, PropertyWhitelistType propertyWhitelistType)
+{
+    if (priority == ResolveVariables)
+        return;
+    if (!state.style()->variables())
+        return;
+    AtomicString name(toCSSCustomIdentValue(value)->value());
+    CSSVariableData* variableData = state.style()->variables()->getVariable(name);
+    if (!variableData)
+        return;
+    const StylePropertySet* propertySet = variableData->propertySet();
+    if (propertySet)
+        applyProperties<priority>(state, propertySet, isImportant, inheritedOnly, propertyWhitelistType);
+}
+
+template <CSSPropertyPriority priority>
 void StyleResolver::applyProperties(StyleResolverState& state, const StylePropertySet* properties, bool isImportant, bool inheritedOnly, PropertyWhitelistType propertyWhitelistType)
 {
     unsigned propertyCount = properties->propertyCount();
     for (unsigned i = 0; i < propertyCount; ++i) {
         StylePropertySet::PropertyReference current = properties->propertyAt(i);
+        CSSPropertyID property = current.id();
+
+        if (property == CSSPropertyApplyAtRule) {
+            applyPropertiesForApplyAtRule<priority>(state, current.value(), isImportant, inheritedOnly, propertyWhitelistType);
+            continue;
+        }
+
         if (isImportant != current.isImportant())
             continue;
 
-        CSSPropertyID property = current.id();
         if (property == CSSPropertyAll) {
             applyAllProperty<priority>(state, current.value(), inheritedOnly, propertyWhitelistType);
             continue;
diff --git a/third_party/WebKit/Source/core/css/resolver/StyleResolver.h b/third_party/WebKit/Source/core/css/resolver/StyleResolver.h
index 3c6c065..14e13bb 100644
--- a/third_party/WebKit/Source/core/css/resolver/StyleResolver.h
+++ b/third_party/WebKit/Source/core/css/resolver/StyleResolver.h
@@ -211,6 +211,8 @@
     void applyAnimatedProperties(StyleResolverState&, const ActiveInterpolationsMap&);
     template <CSSPropertyPriority priority>
     void applyAllProperty(StyleResolverState&, CSSValue*, bool inheritedOnly, PropertyWhitelistType);
+    template <CSSPropertyPriority priority>
+    void applyPropertiesForApplyAtRule(StyleResolverState&, const CSSValue*, bool isImportant, bool inheritedOnly, PropertyWhitelistType);
 
     bool pseudoStyleForElementInternal(Element&, const PseudoStyleRequest&, const ComputedStyle* parentStyle, StyleResolverState&);
     bool hasAuthorBackground(const StyleResolverState&);
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.cpp b/third_party/WebKit/Source/core/frame/UseCounter.cpp
index 022799f..a10b107 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.cpp
+++ b/third_party/WebKit/Source/core/frame/UseCounter.cpp
@@ -564,6 +564,7 @@
     case CSSPropertyColumnSpan: return 529;
     case CSSPropertyColumnWidth: return 530;
     case CSSPropertyColumns: return 531;
+    case CSSPropertyApplyAtRule: return 532;
 
     // 1. Add new features above this line (don't change the assigned numbers of the existing
     // items).
@@ -580,7 +581,7 @@
     return 0;
 }
 
-static int maximumCSSSampleId() { return 531; }
+static int maximumCSSSampleId() { return 532; }
 
 static EnumerationHistogram& featureObserverHistogram()
 {
diff --git a/third_party/WebKit/Source/core/frame/UseCounter.h b/third_party/WebKit/Source/core/frame/UseCounter.h
index 8ad66002..2d7b5fb6 100644
--- a/third_party/WebKit/Source/core/frame/UseCounter.h
+++ b/third_party/WebKit/Source/core/frame/UseCounter.h
@@ -1086,6 +1086,7 @@
         ApplicationCacheManifestSelectSecureOrigin = 1246,
         ApplicationCacheAPIInsecureOrigin = 1247,
         ApplicationCacheAPISecureOrigin = 1248,
+        CSSAtRuleApply = 1249,
 
         // Add new features immediately above this line. Don't change assigned
         // numbers of any item, and don't reuse removed slots.
diff --git a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
index 6b797b2..77e1b8d 100644
--- a/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
+++ b/third_party/WebKit/Source/platform/RuntimeEnabledFeatures.in
@@ -40,6 +40,7 @@
 CredentialManager status=stable
 CSS3Text status=experimental
 CSS3TextDecorations status=experimental
+CSSApplyAtRules status=experimental, depends_on=CSSVariables
 CSSAdditiveAnimations status=experimental, depends_on=StackedCSSPropertyAnimations
 CSSBackdropFilter status=experimental
 CSSCompositing status=stable