Bug 1473180 part 3 - Use the new algorithm for setting property. r?emilio draft
authorXidorn Quan <me@upsuper.org>
Mon, 16 Jul 2018 09:00:19 +1000
changeset 820138 08a40cf9746a83fceb124dd148d02ccb0d2e4864
parent 819500 0c8cbf0f371c2598d67f92d36972534cf77c5e43
child 820139 05ce3e0531dcbe9da89d1a2ae4efe629cddc73d0
push id116733
push userxquan@mozilla.com
push dateWed, 18 Jul 2018 23:58:21 +0000
reviewersemilio
bugs1473180
milestone63.0a1
Bug 1473180 part 3 - Use the new algorithm for setting property. r?emilio MozReview-Commit-ID: HQsVwWAGPBL
modules/libpref/init/StaticPrefList.h
servo/ports/geckolib/glue.rs
testing/web-platform/meta/MANIFEST.json
testing/web-platform/meta/css/cssom/cssstyledeclaration-mutationrecord-001.html.ini
testing/web-platform/meta/css/cssom/cssstyledeclaration-setter-order.html.ini
testing/web-platform/tests/css/cssom/cssstyledeclaration-mutationrecord-001.html
testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-declarations.html
testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-logical.html
testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-order.html
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -328,34 +328,16 @@ VARCACHE_PREF(
 #endif
 VARCACHE_PREF(
   "layout.css.frames-timing.enabled",
    layout_css_frames_timing_enabled,
   bool, PREF_VALUE
 )
 #undef PREF_VALUE
 
-// When the pref is true, CSSStyleDeclaration.setProperty always appends
-// new declarations (and discards old ones if they exist), otherwise, it
-// will update in-place when given property exists in the block, and
-// avoid updating at all when the existing property declaration is
-// identical to the new one.
-// See bug 1415330, bug 1460295, and bug 1461285 for some background.
-#ifdef RELEASE_OR_BETA
-# define PREF_VALUE false
-#else
-# define PREF_VALUE true
-#endif
-VARCACHE_PREF(
-  "layout.css.property-append-only",
-   layout_css_property_append_only,
-   bool, PREF_VALUE
-)
-#undef PREF_VALUE
-
 // Should the :visited selector ever match (otherwise :link matches instead)?
 VARCACHE_PREF(
   "layout.css.visited_links_enabled",
    layout_css_visited_links_enabled,
   bool, true
 )
 
 // Pref to control whether @-moz-document rules are enabled in content pages.
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -3579,41 +3579,28 @@ fn set_property(
         reporter.as_ref().map(|r| r as &ParseErrorReporter),
     );
 
     if result.is_err() {
         return false;
     }
 
     let importance = if is_important { Importance::Important } else { Importance::Normal };
-    let append_only = unsafe {
-        structs::StaticPrefs_sVarCache_layout_css_property_append_only
-    };
-    let mode = if append_only {
-        DeclarationPushMode::Append
-    } else {
-        let will_change = read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
-            decls.will_change_in_update_mode(&source_declarations, importance)
-        });
-        if !will_change {
-            return false;
-        }
-        DeclarationPushMode::Update
-    };
+    let mut updates = Default::default();
+    let will_change = read_locked_arc(declarations, |decls: &PropertyDeclarationBlock| {
+        decls.prepare_for_update(&source_declarations, importance, &mut updates)
+    });
+    if !will_change {
+        return false;
+    }
 
     before_change_closure.invoke();
-
-    let result = write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
-        decls.extend(
-            source_declarations.drain(),
-            importance,
-            mode,
-        )
+    write_locked_arc(declarations, |decls: &mut PropertyDeclarationBlock| {
+        decls.update(source_declarations.drain(), importance, &mut updates)
     });
-    debug_assert!(result);
     true
 }
 
 #[no_mangle]
 pub unsafe extern "C" fn Servo_DeclarationBlock_SetProperty(
     declarations: RawServoDeclarationBlockBorrowed,
     property: *const nsACString,
     value: *const nsACString,
--- a/testing/web-platform/meta/MANIFEST.json
+++ b/testing/web-platform/meta/MANIFEST.json
@@ -327112,19 +327112,25 @@
     ]
    ],
    "css/cssom/cssstyledeclaration-mutationrecord-004.html": [
     [
      "/css/cssom/cssstyledeclaration-mutationrecord-004.html",
      {}
     ]
    ],
-   "css/cssom/cssstyledeclaration-setter-order.html": [
-    [
-     "/css/cssom/cssstyledeclaration-setter-order.html",
+   "css/cssom/cssstyledeclaration-setter-declarations.html": [
+    [
+     "/css/cssom/cssstyledeclaration-setter-declarations.html",
+     {}
+    ]
+   ],
+   "css/cssom/cssstyledeclaration-setter-logical.html": [
+    [
+     "/css/cssom/cssstyledeclaration-setter-logical.html",
      {}
     ]
    ],
    "css/cssom/escape.html": [
     [
      "/css/cssom/escape.html",
      {}
     ]
@@ -553794,33 +553800,37 @@
    "aa2adbfcc58f3a844e2e1f2c96e5efed2c81f2c3",
    "testharness"
   ],
   "css/cssom/cssstyledeclaration-mutability.html": [
    "5f29436964d01c57f61d513cee5b83281643ac54",
    "testharness"
   ],
   "css/cssom/cssstyledeclaration-mutationrecord-001.html": [
-   "5d455757e4c80b4781ea4263fa78bced1d6b8632",
+   "0ed8cb2c41f371fdb509731f2ad1cf11e047d46f",
    "testharness"
   ],
   "css/cssom/cssstyledeclaration-mutationrecord-002.html": [
    "f21e4ba8d5195a66a0a5e64c72731bab75a44ea4",
    "testharness"
   ],
   "css/cssom/cssstyledeclaration-mutationrecord-003.html": [
    "a7968f4e7d85d778425f584d2a42bf74b8583bdb",
    "testharness"
   ],
   "css/cssom/cssstyledeclaration-mutationrecord-004.html": [
    "958b71b8f1c58a809590459e6f085f3e1217e9c7",
    "testharness"
   ],
-  "css/cssom/cssstyledeclaration-setter-order.html": [
-   "3e0e768c466011bb3d91b3f0eff55e029a2aec0f",
+  "css/cssom/cssstyledeclaration-setter-declarations.html": [
+   "e530f6b573bfb9774dd732f7289156117fc4bd94",
+   "testharness"
+  ],
+  "css/cssom/cssstyledeclaration-setter-logical.html": [
+   "c454a34b964e2aa05790831cc2de20e169162dd5",
    "testharness"
   ],
   "css/cssom/escape.html": [
    "c9ed57c7ef7a035c25feff4ea60547a57d727f31",
    "testharness"
   ],
   "css/cssom/font-shorthand-serialization.html": [
    "9d0bce1c0d74bf90aca1eb8ee6aa2e2ed2b92b30",
deleted file mode 100644
--- a/testing/web-platform/meta/css/cssom/cssstyledeclaration-mutationrecord-001.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[cssstyledeclaration-mutationrecord-001.html]
-  prefs: [layout.css.property-append-only:true]
deleted file mode 100644
--- a/testing/web-platform/meta/css/cssom/cssstyledeclaration-setter-order.html.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[cssstyledeclaration-setter-order.html]
-  prefs: [layout.css.property-append-only:true]
--- a/testing/web-platform/tests/css/cssom/cssstyledeclaration-mutationrecord-001.html
+++ b/testing/web-platform/tests/css/cssom/cssstyledeclaration-mutationrecord-001.html
@@ -1,20 +1,20 @@
 <!doctype html>
 <meta charset="utf-8">
-<title>CSSOM: CSSStyleDeclaration.setPropertyValue queues a mutation record when not actually mutated</title>
+<title>CSSOM: CSSStyleDeclaration.setPropertyValue queues a mutation record when changed</title>
 <link rel="help" href="https://drafts.csswg.org/cssom/#dom-cssstyledeclaration-setpropertyvalue">
 <link rel="help" href="https://drafts.csswg.org/cssom/#update-style-attribute-for">
 <link rel="author" title="Emilio Cobos Álvarez" href="mailto:emilio@crisal.io">
 <script src=/resources/testharness.js></script>
 <script src=/resources/testharnessreport.js></script>
 <script>
   document.documentElement.style.top = "0px";
 
-  let test = async_test("CSSStyleDeclaration.setPropertyValue queues a mutation record, even if not mutated");
+  let test = async_test("CSSStyleDeclaration.setPropertyValue queues a mutation record when serialization is changed");
   let m = new MutationObserver(function(r) {
     assert_equals(r.length, 1);
     test.done();
   });
 
   m.observe(document.documentElement,  { attributes: true });
-  document.documentElement.style.top = "0px";
+  document.documentElement.style.top = "1px";
 </script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-declarations.html
@@ -0,0 +1,160 @@
+<!DOCTYPE html>
+<title>CSSOM test: declaration block after setting via CSSOM</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#set-a-css-declaration-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<script>
+  function generateCSSDeclBlock(props) {
+    let elem = document.createElement("div");
+    let cssText = props.map(({name, value, priority}) => {
+      let longhand = `${name}: ${value}`;
+      if (priority) {
+        longhand += "!" + priority;
+      }
+      return longhand + ";";
+    }).join(" ");
+    elem.setAttribute("style", cssText);
+    return elem.style;
+  }
+  function compareByName(a, b) {
+    if (a.name < b.name) return -1;
+    if (a.name > b.name) return 1;
+    return 0;
+  }
+  function checkDeclarationsAnyOrder(block, props, msg) {
+    let actual = [];
+    for (let name of block) {
+      let value = block.getPropertyValue(name);
+      let priority = block.getPropertyPriority(name);
+      actual.push({name, value, priority});
+    }
+    actual.sort(compareByName);
+    let expected = Array.from(props);
+    expected.sort(compareByName);
+    assert_object_equals(actual, expected, "Declaration block content should match " + msg);
+  }
+  function longhand(name, value, priority="") {
+    return {name, value, priority};
+  }
+  function* shorthand(name, value, priority="") {
+    for (let subprop of SUBPROPS[name]) {
+      yield longhand(subprop, value, priority);
+    }
+  }
+
+  const SUBPROPS = {
+    "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"],
+    "padding": ["padding-top", "padding-right", "padding-bottom", "padding-left"],
+  };
+
+  test(function() {
+    let expectedDecls = [
+      longhand("top", "1px"),
+      longhand("bottom", "2px"),
+      longhand("left", "3px", "important"),
+      longhand("right", "4px"),
+    ];
+    let block = generateCSSDeclBlock(expectedDecls);
+    checkDeclarationsAnyOrder(block, expectedDecls, "in initial block");
+
+    block.setProperty("top", "5px", "important");
+    expectedDecls[0] = longhand("top", "5px", "important");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property");
+
+    block.setProperty("bottom", "2px");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property with identical value");
+
+    block.setProperty("left", "3px");
+    expectedDecls[2].priority = "";
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property with different priority");
+
+    block.setProperty("float", "none");
+    expectedDecls.push(longhand("float", "none"));
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting non-existing property");
+  }, "setProperty with longhand should update only the declaration being set");
+
+  test(function() {
+    let expectedDecls = [
+      longhand("top", "1px"),
+      longhand("bottom", "2px"),
+      longhand("left", "3px", "important"),
+      longhand("right", "4px"),
+    ];
+    let block = generateCSSDeclBlock(expectedDecls);
+    checkDeclarationsAnyOrder(block, expectedDecls, "in initial block");
+
+    block.top = "5px";
+    expectedDecls[0] = longhand("top", "5px");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property");
+
+    block.bottom = "2px";
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property with identical value");
+
+    block.left = "3px";
+    expectedDecls[2].priority = "";
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting existing property with different priority");
+
+    block.float = "none";
+    expectedDecls.push(longhand("float", "none"));
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting non-existing property");
+  }, "property setter should update only the declaration being set");
+
+  test(function() {
+    let expectedDecls = [
+      ...shorthand("margin", "1px"),
+      longhand("top", "2px"),
+      ...shorthand("padding", "3px", "important"),
+    ];
+    let block = generateCSSDeclBlock(expectedDecls);
+    checkDeclarationsAnyOrder(block, expectedDecls, "in initial block");
+
+    block.setProperty("margin", "4px");
+    for (let i = 0; i < 4; i++) {
+      expectedDecls[i].value = "4px";
+    }
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand");
+
+    block.setProperty("margin", "4px");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand with identical value");
+
+    block.setProperty("padding", "3px");
+    for (let i = 5; i < 9; i++) {
+      expectedDecls[i].priority = "";
+    }
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand with different priority");
+
+    block.setProperty("margin-bottom", "5px", "important");
+    expectedDecls[2] = longhand("margin-bottom", "5px", "important");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting a longhand in an existing shorthand");
+  }, "setProperty with shorthand should update only the declarations being set");
+
+  test(function() {
+    let expectedDecls = [
+      ...shorthand("margin", "1px"),
+      longhand("top", "2px"),
+      ...shorthand("padding", "3px", "important"),
+    ];
+    let block = generateCSSDeclBlock(expectedDecls);
+    checkDeclarationsAnyOrder(block, expectedDecls, "in initial block");
+
+    block.margin = "4px";
+    for (let i = 0; i < 4; i++) {
+      expectedDecls[i].value = "4px";
+    }
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand");
+
+    block.margin = "4px";
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand with identical value");
+
+    block.padding = "3px";
+    for (let i = 5; i < 9; i++) {
+      expectedDecls[i].priority = "";
+    }
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting an existing shorthand with different priority");
+
+    block.marginBottom = "5px";
+    expectedDecls[2] = longhand("margin-bottom", "5px");
+    checkDeclarationsAnyOrder(block, expectedDecls, "after setting a longhand in an existing shorthand");
+  }, "longhand property setter should update only the decoarations being set");
+</script>
new file mode 100644
--- /dev/null
+++ b/testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-logical.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<title>CSSOM test: declaration block after setting via CSSOM</title>
+<link rel="help" href="https://drafts.csswg.org/cssom/#set-a-css-declaration-value">
+<script src="/resources/testharness.js"></script>
+<script src="/resources/testharnessreport.js"></script>
+<div id="log"></div>
+<div id="test"></div>
+<script>
+  test(function() {
+    const PHYSICAL_PROPS = ["padding-top", "padding-right",
+                            "padding-bottom", "padding-left"];
+    const LOGICAL_PROPS = ["padding-block-start", "padding-block-end",
+                           "padding-inline-start", "padding-inline-end"];
+    let elem = document.getElementById("test");
+    let decls = new Map;
+
+    {
+      let css = []
+      let i = 0;
+      for (let name of [...PHYSICAL_PROPS, ...LOGICAL_PROPS]) {
+        let value = `${i++}px`;
+        css.push(`${name}: ${value};`);
+        decls.set(name, value);
+      }
+      elem.setAttribute("style", css.join(" "));
+    }
+
+    let style = elem.style;
+    function indexOfProperty(prop) {
+      return Array.prototype.indexOf.apply(style, [prop]);
+    }
+    function assertPropAfterProps(prop, props, msg) {
+      let index = indexOfProperty(prop);
+      for (let p of props) {
+        assert_less_than(indexOfProperty(p), index, `${prop} should be after ${p} ${msg}`);
+      }
+    }
+    // We are not changing any value in this test, just order.
+    function assertValueUnchanged() {
+      for (let [name, value] of decls.entries()) {
+        assert_equals(style.getPropertyValue(name), value,
+                      `value of ${name} shouldn't be changed`);
+      }
+    }
+
+    assertValueUnchanged();
+    // Check that logical properties are all after physical properties
+    // at the beginning.
+    for (let p of LOGICAL_PROPS) {
+      assertPropAfterProps(p, PHYSICAL_PROPS, "initially");
+    }
+
+    // Try setting a longhand.
+    style.setProperty("padding-top", "0px");
+    assertValueUnchanged();
+    // Check that padding-top is after logical properties, but other
+    // physical properties are still before logical properties.
+    assertPropAfterProps("padding-top", LOGICAL_PROPS, "after setting padding-top");
+    for (let p of LOGICAL_PROPS) {
+      assertPropAfterProps(p, PHYSICAL_PROPS.filter(prop => prop != "padding-top"),
+                           "after setting padding-top");
+    }
+
+    // Try setting a shorthand.
+    style.setProperty("padding", "0px 1px 2px 3px");
+    assertValueUnchanged();
+    // Check that all physical properties are now after logical properties.
+    for (let p of PHYSICAL_PROPS) {
+      assertPropAfterProps(p, LOGICAL_PROPS, "after setting padding shorthand");
+    }
+  }, "newly set declaration should be after all declarations " +
+  "in the same logical property group but have different logical kind");
+</script>
deleted file mode 100644
--- a/testing/web-platform/tests/css/cssom/cssstyledeclaration-setter-order.html
+++ /dev/null
@@ -1,108 +0,0 @@
-<!DOCTYPE html>
-<title>CSSOM test: order of declarations after setting via CSSOM</title>
-<link rel="help" href="https://drafts.csswg.org/cssom/#set-a-css-declaration-value">
-<script src="/resources/testharness.js"></script>
-<script src="/resources/testharnessreport.js"></script>
-<div id="log"></div>
-<script>
-  function generateCSSDeclBlock(props) {
-    let elem = document.createElement("div");
-    let cssText = props.map(([prop, value]) => `${prop}: ${value};`).join(" ");
-    elem.setAttribute("style", cssText);
-    return elem.style;
-  }
-  function checkOrder(block, props, msg) {
-    assert_array_equals(Array.from(block), props, `Property order should match ${msg}`);
-  }
-  function arrayWithItemsAtEnd(array, items) {
-    let result = array.filter(item => !items.includes(item));
-    return result.concat(items);
-  }
-
-  const SUBPROPS = {
-    "margin": ["margin-top", "margin-right", "margin-bottom", "margin-left"],
-    "padding": ["padding-top", "padding-right", "padding-bottom", "padding-left"],
-  };
-
-  test(function() {
-    let block = generateCSSDeclBlock([
-      ["top", "1px"],
-      ["bottom", "2px"],
-      ["left", "3px"],
-      ["right", "4px"],
-    ]);
-    let expectedOrder = ["top", "bottom", "left", "right"];
-    checkOrder(block, expectedOrder, "in initial block");
-
-    block.setProperty("top", "5px");
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["top"]);
-    checkOrder(block, expectedOrder, "after setting existing property");
-
-    block.setProperty("bottom", "2px");
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["bottom"]);
-    checkOrder(block, expectedOrder, "after setting existing property with identical value");
-  }, "setProperty with existing longhand should change order");
-
-  test(function() {
-    let block = generateCSSDeclBlock([
-      ["top", "1px"],
-      ["bottom", "2px"],
-      ["left", "3px"],
-      ["right", "4px"],
-    ]);
-    let expectedOrder = ["top", "bottom", "left", "right"];
-    checkOrder(block, expectedOrder, "in initial block");
-
-    block.top = "5px";
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["top"]);
-    checkOrder(block, expectedOrder, "after setting existing property");
-
-    block.bottom = "2px";
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["bottom"]);
-    checkOrder(block, expectedOrder, "after setting existing property with identical value");
-  }, "invoke property setter with existing longhand should change order");
-
-  test(function() {
-    let block = generateCSSDeclBlock([
-      ["margin", "1px"],
-      ["top", "2px"],
-      ["padding", "3px"],
-    ]);
-    let expectedOrder = SUBPROPS["margin"].concat(["top"]).concat(SUBPROPS["padding"]);
-    checkOrder(block, expectedOrder, "in initial block");
-
-    block.setProperty("margin", "4px");
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["margin"]);
-    checkOrder(block, expectedOrder, "after setting an existing shorthand");
-
-    block.setProperty("padding", "3px");
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["padding"]);
-    checkOrder(block, expectedOrder, "after setting an existing shorthand with identical value");
-
-    block.setProperty("margin-bottom", "5px");
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["margin-bottom"]);
-    checkOrder(block, expectedOrder, "after setting a longhand in an existing shorthand");
-  }, "setProperty with existing shorthand should change order");
-
-  test(function() {
-    let block = generateCSSDeclBlock([
-      ["margin", "1px"],
-      ["top", "2px"],
-      ["padding", "3px"],
-    ]);
-    let expectedOrder = SUBPROPS["margin"].concat(["top"]).concat(SUBPROPS["padding"]);
-    checkOrder(block, expectedOrder, "in initial block");
-
-    block.margin = "4px";
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["margin"]);
-    checkOrder(block, expectedOrder, "after setting an existing shorthand");
-
-    block.padding = "3px";
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, SUBPROPS["padding"]);
-    checkOrder(block, expectedOrder, "after setting an existing shorthand with identical value");
-
-    block.marginBottom = "5px";
-    expectedOrder = arrayWithItemsAtEnd(expectedOrder, ["margin-bottom"]);
-    checkOrder(block, expectedOrder, "after setting a longhand in an existing shorthand");
-  }, "invoke property setter with existing shorthand should change order");
-</script>