Title: [206686] trunk
Revision
206686
Author
[email protected]
Date
2016-09-30 15:45:17 -0700 (Fri, 30 Sep 2016)

Log Message

[Modern Media Controls] layout nodes
https://bugs.webkit.org/show_bug.cgi?id=162799
<rdar://problem/28569301>

Patch by Antoine Quint <[email protected]> on 2016-09-30
Reviewed by Dean Jackson.

Source/WebCore:

Modern media controls will be using a tree of LayoutNode objects that commit to the DOM
in coordinated `requestAnimationFrame()` calls to ensure all layouts are done in an efficient
and coordinated manner. As a preamble, we introduced the `scheduler` singleton in
https://webkit.org/b/162726 which is in charge of scheduling callbacks.

A LayoutNode is created by providing an Element to its constructor, or an HTML string. Not
providing a parameter creates a simple <div>.

When we set a property on a LayoutNode, we call `markDirtyProperty(propertyName)` which keeps
track of dirty properties in the `_dirtyProperties` set. When this set is non-empty, the node
is marked as dirty and registered in the global `dirtyNodes` map, asking the shared scheduler
that a layout is needed. When the layout is performed, all nodes in the `dirtyNodes` map are
processed such that `commitProperty(propertyName)` is called to commit dirty properties for
a given node to the DOM, and `layout()` is called to allow subclasses of LayoutNode to conduct
custom layout logic that goes beyond committing a given property.

Another reason why a node may be marked as dirty is when a DOM hierarchy change is needed. A
host of DOM-like methods are exposed to allow flexible manipulations of nodes, with an extra
`children` property which allows wholesale change of a node's subtree with a single array
property assignment. Changes to the DOM hierarchy are performed in the same scheduler callback
as style properties.

Nodes can be marked for layout explicitly with by setting the `needsLayout` property.

Tests: media/modern-media-controls/layout-node/addChild.html
       media/modern-media-controls/layout-node/children.html
       media/modern-media-controls/layout-node/constructor.html
       media/modern-media-controls/layout-node/height.html
       media/modern-media-controls/layout-node/insertAfter.html
       media/modern-media-controls/layout-node/insertBefore.html
       media/modern-media-controls/layout-node/parent.html
       media/modern-media-controls/layout-node/remove.html
       media/modern-media-controls/layout-node/removeChild.html
       media/modern-media-controls/layout-node/subclassing.html
       media/modern-media-controls/layout-node/visible.html
       media/modern-media-controls/layout-node/width.html
       media/modern-media-controls/layout-node/x.html
       media/modern-media-controls/layout-node/y.html

* Modules/modern-media-controls/controls/layout-node.js: Added.
(LayoutNode):
(LayoutNode.prototype.get x):
(LayoutNode.prototype.set x):
(LayoutNode.prototype.get y):
(LayoutNode.prototype.set y):
(LayoutNode.prototype.get width):
(LayoutNode.prototype.set width):
(LayoutNode.prototype.get height):
(LayoutNode.prototype.set height):
(LayoutNode.prototype.get visible):
(LayoutNode.prototype.set visible):
(LayoutNode.prototype.get needsLayout):
(LayoutNode.prototype.set needsLayout):
(LayoutNode.prototype.get parent):
(LayoutNode.prototype.get children):
(LayoutNode.prototype.set children):
(LayoutNode.prototype.addChild):
(LayoutNode.prototype.insertBefore):
(LayoutNode.prototype.insertAfter):
(LayoutNode.prototype.removeChild):
(LayoutNode.prototype.remove):
(LayoutNode.prototype.markDirtyProperty):
(LayoutNode.prototype.commitProperty):
(LayoutNode.prototype.layout):
(LayoutNode.prototype._markNodeManipulation):
(LayoutNode.prototype._updateDirtyState):
(LayoutNode.prototype._updateChildren):
(performScheduledLayout):
(elementFromString):

LayoutTests:

Testing all public properties and methods of the LayoutNode class.

* media/modern-media-controls/layout-node/addChild-expected.txt: Added.
* media/modern-media-controls/layout-node/addChild.html: Added.
* media/modern-media-controls/layout-node/children-expected.txt: Added.
* media/modern-media-controls/layout-node/children.html: Added.
* media/modern-media-controls/layout-node/constructor-expected.txt: Added.
* media/modern-media-controls/layout-node/constructor.html: Added.
* media/modern-media-controls/layout-node/height-expected.txt: Added.
* media/modern-media-controls/layout-node/height.html: Added.
* media/modern-media-controls/layout-node/insertAfter-expected.txt: Added.
* media/modern-media-controls/layout-node/insertAfter.html: Added.
* media/modern-media-controls/layout-node/insertBefore-expected.txt: Added.
* media/modern-media-controls/layout-node/insertBefore.html: Added.
* media/modern-media-controls/layout-node/parent-expected.txt: Added.
* media/modern-media-controls/layout-node/parent.html: Added.
* media/modern-media-controls/layout-node/remove-expected.txt: Added.
* media/modern-media-controls/layout-node/remove.html: Added.
* media/modern-media-controls/layout-node/removeChild-expected.txt: Added.
* media/modern-media-controls/layout-node/removeChild.html: Added.
* media/modern-media-controls/layout-node/subclassing-expected.txt: Added.
* media/modern-media-controls/layout-node/subclassing.html: Added.
* media/modern-media-controls/layout-node/visible-expected.txt: Added.
* media/modern-media-controls/layout-node/visible.html: Added.
* media/modern-media-controls/layout-node/width-expected.txt: Added.
* media/modern-media-controls/layout-node/width.html: Added.
* media/modern-media-controls/layout-node/x-expected.txt: Added.
* media/modern-media-controls/layout-node/x.html: Added.
* media/modern-media-controls/layout-node/y-expected.txt: Added.
* media/modern-media-controls/layout-node/y.html: Added.

Modified Paths

Added Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (206685 => 206686)


--- trunk/LayoutTests/ChangeLog	2016-09-30 22:43:40 UTC (rev 206685)
+++ trunk/LayoutTests/ChangeLog	2016-09-30 22:45:17 UTC (rev 206686)
@@ -1,3 +1,42 @@
+2016-09-30  Antoine Quint  <[email protected]>
+
+        [Modern Media Controls] layout nodes
+        https://bugs.webkit.org/show_bug.cgi?id=162799
+        <rdar://problem/28569301>
+
+        Reviewed by Dean Jackson.
+
+        Testing all public properties and methods of the LayoutNode class.
+
+        * media/modern-media-controls/layout-node/addChild-expected.txt: Added.
+        * media/modern-media-controls/layout-node/addChild.html: Added.
+        * media/modern-media-controls/layout-node/children-expected.txt: Added.
+        * media/modern-media-controls/layout-node/children.html: Added.
+        * media/modern-media-controls/layout-node/constructor-expected.txt: Added.
+        * media/modern-media-controls/layout-node/constructor.html: Added.
+        * media/modern-media-controls/layout-node/height-expected.txt: Added.
+        * media/modern-media-controls/layout-node/height.html: Added.
+        * media/modern-media-controls/layout-node/insertAfter-expected.txt: Added.
+        * media/modern-media-controls/layout-node/insertAfter.html: Added.
+        * media/modern-media-controls/layout-node/insertBefore-expected.txt: Added.
+        * media/modern-media-controls/layout-node/insertBefore.html: Added.
+        * media/modern-media-controls/layout-node/parent-expected.txt: Added.
+        * media/modern-media-controls/layout-node/parent.html: Added.
+        * media/modern-media-controls/layout-node/remove-expected.txt: Added.
+        * media/modern-media-controls/layout-node/remove.html: Added.
+        * media/modern-media-controls/layout-node/removeChild-expected.txt: Added.
+        * media/modern-media-controls/layout-node/removeChild.html: Added.
+        * media/modern-media-controls/layout-node/subclassing-expected.txt: Added.
+        * media/modern-media-controls/layout-node/subclassing.html: Added.
+        * media/modern-media-controls/layout-node/visible-expected.txt: Added.
+        * media/modern-media-controls/layout-node/visible.html: Added.
+        * media/modern-media-controls/layout-node/width-expected.txt: Added.
+        * media/modern-media-controls/layout-node/width.html: Added.
+        * media/modern-media-controls/layout-node/x-expected.txt: Added.
+        * media/modern-media-controls/layout-node/x.html: Added.
+        * media/modern-media-controls/layout-node/y-expected.txt: Added.
+        * media/modern-media-controls/layout-node/y.html: Added.
+
 2016-09-30  Ryan Haddad  <[email protected]>
 
         Marking http/tests/media/hls/hls-video-resize.html as flaky on mac-wk1.

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/addChild-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/addChild-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/addChild-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,23 @@
+Testing the LayoutNode.addChild(child[, index]) method.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+node.addChild(a)
+PASS node.children.length === 1 is true
+PASS node.children[0] === a is true
+PASS retVal === a is true
+
+node.addChild(b, 0)
+PASS node.children.length === 2 is true
+PASS node.children[0] === b is true
+PASS node.children[1] === a is true
+
+Layout was performed
+PASS node.element.childElementCount === 2 is true
+PASS node.element.firstElementChild === b.element is true
+PASS node.element.lastElementChild === a.element is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/addChild.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/addChild.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/addChild.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,41 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.addChild(child[, index])</code> method.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+
+debug("node.addChild(a)");
+const retVal = node.addChild(a);
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("retVal === a");
+
+debug("");
+debug("node.addChild(b, 0)");
+node.addChild(b, 0);
+shouldBeTrue("node.children.length === 2");
+shouldBeTrue("node.children[0] === b");
+shouldBeTrue("node.children[1] === a");
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    shouldBeTrue("node.element.childElementCount === 2");
+    shouldBeTrue("node.element.firstElementChild === b.element");
+    shouldBeTrue("node.element.lastElementChild === a.element");
+
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/children-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/children-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/children-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,41 @@
+Testing the LayoutNode.children property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Check default state
+PASS Array.isArray(node.children) is true
+PASS node.children.length === 0 is true
+
+Set children to [a, b, c]
+PASS node.children.length === 3 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === b is true
+PASS node.children[2] === c is true
+PASS node.children !== children is true
+
+Layout was performed
+PASS node.element.childElementCount === 3 is true
+PASS node.element.firstElementChild === a.element is true
+PASS node.element.firstElementChild.nextElementSibling === b.element is true
+PASS node.element.lastElementChild === c.element is true
+
+Set children to [b, a]
+PASS node.children.length === 2 is true
+PASS node.children[0] === b is true
+PASS node.children[1] === a is true
+
+Layout was performed
+PASS node.element.childElementCount === 2 is true
+PASS node.element.firstElementChild === b.element is true
+PASS node.element.lastElementChild === a.element is true
+
+Set children to []
+PASS node.children.length === 0 is true
+
+Layout was performed
+PASS node.element.childElementCount === 0 is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/children.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/children.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/children.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,67 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.children</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Check default state");
+shouldBeTrue("Array.isArray(node.children)");
+shouldBeTrue("node.children.length === 0");
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+const children = [a, b, c];
+node.children = children;
+
+debug("");
+debug("Set children to [a, b, c]");
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === b");
+shouldBeTrue("node.children[2] === c");
+shouldBeTrue("node.children !== children");
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBeTrue("node.element.childElementCount === 3");
+        shouldBeTrue("node.element.firstElementChild === a.element");
+        shouldBeTrue("node.element.firstElementChild.nextElementSibling === b.element");
+        shouldBeTrue("node.element.lastElementChild === c.element");
+        debug("");
+        debug("Set children to [b, a]");
+        node.children = [b, a];
+        shouldBeTrue("node.children.length === 2");
+        shouldBeTrue("node.children[0] === b");
+        shouldBeTrue("node.children[1] === a");
+        break;
+    case 2:
+        shouldBeTrue("node.element.childElementCount === 2");
+        shouldBeTrue("node.element.firstElementChild === b.element");
+        shouldBeTrue("node.element.lastElementChild === a.element");
+        debug("");
+        debug("Set children to []");
+        node.children = [];
+        shouldBeTrue("node.children.length === 0");
+        break;
+    case 3:
+        shouldBeTrue("node.element.childElementCount === 0");
+        finishJSTest();
+        break;
+    }
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/constructor-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/constructor-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/constructor-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,19 @@
+Testing the LayoutNode various constructor parameters.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+No parameter
+PASS nodeWithNoParameter.element.localName is "div"
+
+Element parameter
+PASS nodeWithElementParameter.element === element is true
+
+HTML string parameter
+PASS nodeWithStringParameter.element.localName is "span"
+PASS nodeWithStringParameter.element.textContent is "hello world"
+PASS nodeWithStringParameter.element.firstElementChild.localName is "strong"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/constructor.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/constructor.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/constructor.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,26 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode</code> various constructor parameters.");
+
+debug("No parameter");
+const nodeWithNoParameter = new LayoutNode;
+shouldBeEqualToString("nodeWithNoParameter.element.localName", "div");
+
+debug("");
+debug("Element parameter");
+const element = document.createElement("h1");
+const nodeWithElementParameter = new LayoutNode(element);
+shouldBeTrue("nodeWithElementParameter.element === element");
+
+debug("");
+debug("HTML string parameter");
+const nodeWithStringParameter = new LayoutNode(`<span>hello <strong>world</strong></span>`);
+shouldBeEqualToString("nodeWithStringParameter.element.localName", "span");
+shouldBeEqualToString("nodeWithStringParameter.element.textContent", "hello world");
+shouldBeEqualToString("nodeWithStringParameter.element.firstElementChild.localName", "strong");
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/height-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/height-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/height-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,22 @@
+Testing the LayoutNode.height property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking default value
+PASS node.height is 0
+PASS node.element.style.height is ""
+
+node.height = 10
+PASS node.height is 10
+PASS node.element.style.height is ""
+
+node.height = 20
+
+Layout was performed
+PASS node.height is 20
+PASS node.element.style.height is "20px"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/height.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/height.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/height.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,37 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.height</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Checking default value");
+shouldBe("node.height", "0");
+shouldBeEqualToString("node.element.style.height", "");
+
+debug("");
+debug("node.height = 10");
+node.height = 10;
+shouldBe("node.height", "10");
+shouldBeEqualToString("node.element.style.height", "");
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug("");
+debug("node.height = 20");
+node.height = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+    shouldBe("node.height", "20");
+    shouldBeEqualToString("node.element.style.height", "20px");
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,36 @@
+Testing the LayoutNode.insertAfter(newSibling, referenceSibling) method.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+node.insertAfter(a)
+PASS node.children.length === 1 is true
+PASS node.children[0] === a is true
+PASS retVal === a is true
+
+node.insertAfter(c, a)
+PASS node.children.length === 2 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === c is true
+
+node.insertAfter(b, a)
+PASS node.children.length === 3 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === b is true
+PASS node.children[2] === c is true
+
+node.insertAfter(a, c)
+PASS node.children.length === 3 is true
+PASS node.children[0] === b is true
+PASS node.children[1] === c is true
+PASS node.children[2] === a is true
+
+Layout was performed
+PASS node.element.childElementCount === 3 is true
+PASS node.element.firstElementChild === b.element is true
+PASS node.element.firstElementChild.nextElementSibling === c.element is true
+PASS node.element.lastElementChild === a.element is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,59 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.insertAfter(newSibling, referenceSibling)</code> method.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug("node.insertAfter(a)");
+const retVal = node.insertAfter(a);
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("retVal === a");
+
+debug("");
+debug("node.insertAfter(c, a)");
+node.insertAfter(c, a);
+shouldBeTrue("node.children.length === 2");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === c");
+
+debug("");
+debug("node.insertAfter(b, a)");
+node.insertAfter(b, a);
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === b");
+shouldBeTrue("node.children[2] === c");
+
+debug("");
+debug("node.insertAfter(a, c)");
+node.insertAfter(a, c);
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === b");
+shouldBeTrue("node.children[1] === c");
+shouldBeTrue("node.children[2] === a");
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    shouldBeTrue("node.element.childElementCount === 3");
+    shouldBeTrue("node.element.firstElementChild === b.element");
+    shouldBeTrue("node.element.firstElementChild.nextElementSibling === c.element");
+    shouldBeTrue("node.element.lastElementChild === a.element");
+
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,36 @@
+Testing the LayoutNode.insertBefore(newSibling, referenceSibling) method.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+node.insertBefore(c)
+PASS node.children.length === 1 is true
+PASS node.children[0] === c is true
+PASS retVal === c is true
+
+node.insertBefore(a, c)
+PASS node.children.length === 2 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === c is true
+
+node.insertBefore(b, c)
+PASS node.children.length === 3 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === b is true
+PASS node.children[2] === c is true
+
+node.insertBefore(a, c)
+PASS node.children.length === 3 is true
+PASS node.children[0] === b is true
+PASS node.children[1] === c is true
+PASS node.children[2] === a is true
+
+Layout was performed
+PASS node.element.childElementCount === 3 is true
+PASS node.element.firstElementChild === b.element is true
+PASS node.element.firstElementChild.nextElementSibling === c.element is true
+PASS node.element.lastElementChild === a.element is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,59 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.insertBefore(newSibling, referenceSibling)</code> method.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug("node.insertBefore(c)");
+const retVal = node.insertBefore(c);
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === c");
+shouldBeTrue("retVal === c");
+
+debug("");
+debug("node.insertBefore(a, c)");
+node.insertBefore(a, c);
+shouldBeTrue("node.children.length === 2");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === c");
+
+debug("");
+debug("node.insertBefore(b, c)");
+node.insertBefore(b, c);
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === b");
+shouldBeTrue("node.children[2] === c");
+
+debug("");
+debug("node.insertBefore(a, c)");
+node.insertBefore(a, c);
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === b");
+shouldBeTrue("node.children[1] === c");
+shouldBeTrue("node.children[2] === a");
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    shouldBeTrue("node.element.childElementCount === 3");
+    shouldBeTrue("node.element.firstElementChild === b.element");
+    shouldBeTrue("node.element.firstElementChild.nextElementSibling === c.element");
+    shouldBeTrue("node.element.lastElementChild === a.element");
+
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/parent-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/parent-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/parent-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,40 @@
+Testing the LayoutNode.parent property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking nodes have a null parent by default
+PASS a.parent === null is true
+PASS b.parent === null is true
+PASS c.parent === null is true
+PASS d.parent === null is true
+
+node.children = [a, b, c]
+PASS a.parent === node is true
+PASS b.parent === node is true
+PASS c.parent === node is true
+
+a.remove()
+PASS a.parent === null is true
+
+node.removeChild(b)
+PASS b.parent === null is true
+
+node.addChild(a)
+PASS a.parent === node is true
+
+node.insertBefore(b, c)
+PASS b.parent === node is true
+
+node.insertAfter(d, c)
+PASS d.parent === node is true
+
+node.children = []
+PASS a.parent === null is true
+PASS b.parent === null is true
+PASS c.parent === null is true
+PASS d.parent === null is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/parent.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/parent.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/parent.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,62 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.parent</code> property.");
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+const d = new LayoutNode;
+
+debug("Checking nodes have a null parent by default");
+shouldBeTrue("a.parent === null");
+shouldBeTrue("b.parent === null");
+shouldBeTrue("c.parent === null");
+shouldBeTrue("d.parent === null");
+
+debug("");
+debug("node.children = [a, b, c]");
+node.children = [a, b, c];
+shouldBeTrue("a.parent === node");
+shouldBeTrue("b.parent === node");
+shouldBeTrue("c.parent === node");
+
+debug("");
+debug("a.remove()");
+a.remove();
+shouldBeTrue("a.parent === null");
+
+debug("");
+debug("node.removeChild(b)");
+node.removeChild(b);
+shouldBeTrue("b.parent === null");
+
+debug("");
+debug("node.addChild(a)");
+node.addChild(a);
+shouldBeTrue("a.parent === node");
+
+debug("");
+debug("node.insertBefore(b, c)");
+node.insertBefore(b, c);
+shouldBeTrue("b.parent === node");
+
+debug("");
+debug("node.insertAfter(d, c)");
+node.insertAfter(d, c);
+shouldBeTrue("d.parent === node");
+
+debug("");
+debug("node.children = []");
+node.children = [];
+shouldBeTrue("a.parent === null");
+shouldBeTrue("b.parent === null");
+shouldBeTrue("c.parent === null");
+shouldBeTrue("d.parent === null");
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/remove-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/remove-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/remove-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,28 @@
+Testing the LayoutNode.remove() method.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+node.children = [a, b, c]
+PASS node.children.length === 3 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === b is true
+PASS node.children[2] === c is true
+
+b.remove()
+PASS node.children.length === 2 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === c is true
+PASS retVal === b is true
+
+a.remove()
+PASS node.children.length === 1 is true
+PASS node.children[0] === c is true
+
+Layout was performed
+PASS node.element.childElementCount === 1 is true
+PASS node.element.firstElementChild === c.element is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/remove.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/remove.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/remove.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,49 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.remove()</code> method.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug("node.children = [a, b, c]");
+node.children = [a, b, c];
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === b");
+shouldBeTrue("node.children[2] === c");
+
+debug("");
+debug("b.remove()");
+const retVal = b.remove();
+shouldBeTrue("node.children.length === 2");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === c");
+shouldBeTrue("retVal === b");
+
+debug("");
+debug("a.remove()");
+a.remove();
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === c");
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    shouldBeTrue("node.element.childElementCount === 1");
+    shouldBeTrue("node.element.firstElementChild === c.element");
+
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,32 @@
+Testing the LayoutNode.removeChild(child) method.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+node.children = [a, b, c]
+PASS node.children.length === 3 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === b is true
+PASS node.children[2] === c is true
+
+node.removeChild(b)
+PASS node.children.length === 2 is true
+PASS node.children[0] === a is true
+PASS node.children[1] === c is true
+PASS retVal === b is true
+
+node.removeChild(a)
+PASS node.children.length === 1 is true
+PASS node.children[0] === c is true
+
+node.removeChild(a)
+PASS node.children.length === 1 is true
+PASS node.children[0] === c is true
+
+Layout was performed
+PASS node.element.childElementCount === 1 is true
+PASS node.element.firstElementChild === c.element is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,55 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.removeChild(child)</code> method.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug("node.children = [a, b, c]");
+node.children = [a, b, c];
+shouldBeTrue("node.children.length === 3");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === b");
+shouldBeTrue("node.children[2] === c");
+
+debug("");
+debug("node.removeChild(b)");
+const retVal = node.removeChild(b);
+shouldBeTrue("node.children.length === 2");
+shouldBeTrue("node.children[0] === a");
+shouldBeTrue("node.children[1] === c");
+shouldBeTrue("retVal === b");
+
+debug("");
+debug("node.removeChild(a)");
+node.removeChild(a);
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === c");
+
+debug("");
+debug("node.removeChild(a)");
+node.removeChild(a);
+shouldBeTrue("node.children.length === 1");
+shouldBeTrue("node.children[0] === c");
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    shouldBeTrue("node.element.childElementCount === 1");
+    shouldBeTrue("node.element.firstElementChild === c.element");
+
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,28 @@
+Subclassing LayoutNode by exposing a new custom property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Check the node is not dirty by default
+PASS node.needsLayout is false
+
+node.opacity = 0.5
+PASS node.needsLayout is true
+
+Layout will be performed
+OpacityNode.layout() was called
+OpacityNode.commitProperty() was called with propertyName = opacity
+
+Layout was performed
+PASS node.element.style.opacity is "0.5"
+
+node.needsLayout = true
+
+Layout will be performed
+OpacityNode.layout() was called
+
+Layout was performed
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,79 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Subclassing <code>LayoutNode</code> by exposing a new custom property.");
+
+window.jsTestIsAsync = true;
+
+class OpacityNode extends LayoutNode
+{
+
+    constructor(stringOrElement)
+    {
+        super(stringOrElement);
+
+        this._opacity = 1;
+    }
+
+    set opacity(opacity)
+    {
+        this._opacity = opacity;
+        this.markDirtyProperty("opacity");
+    }
+
+    commitProperty(propertyName)
+    {
+        debug(`OpacityNode.commitProperty() was called with propertyName = ${propertyName}`);
+        if (propertyName === "opacity")
+            this.element.style.opacity = this._opacity;
+        else
+            super.commitProperty(propertyName);    
+    }
+
+    layout()
+    {
+        debug("OpacityNode.layout() was called");
+        super.layout();
+    }
+
+}
+
+const node = new OpacityNode;
+
+debug("Check the node is not dirty by default");
+shouldBeFalse("node.needsLayout");
+
+debug("");
+debug("node.opacity = 0.5");
+node.opacity = 0.5;
+shouldBeTrue("node.needsLayout");
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBeEqualToString("node.element.style.opacity", "0.5");
+        debug("");
+        debug("node.needsLayout = true");
+        node.needsLayout = true;
+        break;
+    case 2:
+        finishJSTest();
+        break;
+    }
+};
+
+scheduler.frameWillFire = function()
+{
+    debug("");
+    debug("Layout will be performed");
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/visible-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/visible-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/visible-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,26 @@
+Testing the LayoutNode.visible property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking default value
+PASS node.visible is true
+PASS node.element.style.display is ""
+
+node.visible = false
+PASS node.visible is false
+PASS node.element.style.display is ""
+
+Layout was performed
+PASS node.visible is false
+PASS node.element.style.display is "none"
+
+node.visible = true
+
+Layout was performed
+PASS node.visible is true
+PASS node.element.style.display is "inherit"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/visible.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/visible.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/visible.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,45 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.visible</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Checking default value");
+shouldBe("node.visible", "true");
+shouldBeEqualToString("node.element.style.display", "");
+
+debug("");
+debug("node.visible = false");
+node.visible = false;
+shouldBe("node.visible", "false");
+shouldBeEqualToString("node.element.style.display", "");
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBe("node.visible", "false");
+        shouldBeEqualToString("node.element.style.display", "none");
+        debug("");
+        debug("node.visible = true");
+        node.visible = true;
+        break;
+    case 2:
+        shouldBe("node.visible", "true");
+        shouldBeEqualToString("node.element.style.display", "inherit");
+        finishJSTest();
+        break;
+    }
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/width-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/width-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/width-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,22 @@
+Testing the LayoutNode.width property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking default value
+PASS node.width is 0
+PASS node.element.style.width is ""
+
+node.width = 10
+PASS node.width is 10
+PASS node.element.style.width is ""
+
+node.width = 20
+
+Layout was performed
+PASS node.width is 20
+PASS node.element.style.width is "20px"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/width.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/width.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/width.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,37 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.width</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Checking default value");
+shouldBe("node.width", "0");
+shouldBeEqualToString("node.element.style.width", "");
+
+debug("");
+debug("node.width = 10");
+node.width = 10;
+shouldBe("node.width", "10");
+shouldBeEqualToString("node.element.style.width", "");
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug("");
+debug("node.width = 20");
+node.width = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+    shouldBe("node.width", "20");
+    shouldBeEqualToString("node.element.style.width", "20px");
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/x-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/x-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/x-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,22 @@
+Testing the LayoutNode.x property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking default value
+PASS node.x is 0
+PASS node.element.style.left is ""
+
+node.x = 10
+PASS node.x is 10
+PASS node.element.style.left is ""
+
+node.x = 20
+
+Layout was performed
+PASS node.x is 20
+PASS node.element.style.left is "20px"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/x.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/x.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/x.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,37 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.x</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Checking default value");
+shouldBe("node.x", "0");
+shouldBeEqualToString("node.element.style.left", "");
+
+debug("");
+debug("node.x = 10");
+node.x = 10;
+shouldBe("node.x", "10");
+shouldBeEqualToString("node.element.style.left", "");
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug("");
+debug("node.x = 20");
+node.x = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+    shouldBe("node.x", "20");
+    shouldBeEqualToString("node.element.style.left", "20px");
+    finishJSTest();
+};
+
+</script>
+<script src=""

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/y-expected.txt (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/y-expected.txt	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/y-expected.txt	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,22 @@
+Testing the LayoutNode.y property.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+Checking default value
+PASS node.y is 0
+PASS node.element.style.top is ""
+
+node.y = 10
+PASS node.y is 10
+PASS node.element.style.top is ""
+
+node.y = 20
+
+Layout was performed
+PASS node.y is 20
+PASS node.element.style.top is "20px"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+

Added: trunk/LayoutTests/media/modern-media-controls/layout-node/y.html (0 => 206686)


--- trunk/LayoutTests/media/modern-media-controls/layout-node/y.html	                        (rev 0)
+++ trunk/LayoutTests/media/modern-media-controls/layout-node/y.html	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,37 @@
+<script src=""
+<script src="" type="text/_javascript_"></script>
+<script src="" type="text/_javascript_"></script>
+<script type="text/_javascript_">
+
+description("Testing the <code>LayoutNode.y</code> property.");
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug("Checking default value");
+shouldBe("node.y", "0");
+shouldBeEqualToString("node.element.style.top", "");
+
+debug("");
+debug("node.y = 10");
+node.y = 10;
+shouldBe("node.y", "10");
+shouldBeEqualToString("node.element.style.top", "");
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug("");
+debug("node.y = 20");
+node.y = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug("");
+    debug("Layout was performed");
+    shouldBe("node.y", "20");
+    shouldBeEqualToString("node.element.style.top", "20px");
+    finishJSTest();
+};
+
+</script>
+<script src=""

Modified: trunk/Source/WebCore/ChangeLog (206685 => 206686)


--- trunk/Source/WebCore/ChangeLog	2016-09-30 22:43:40 UTC (rev 206685)
+++ trunk/Source/WebCore/ChangeLog	2016-09-30 22:45:17 UTC (rev 206686)
@@ -1,3 +1,81 @@
+2016-09-30  Antoine Quint  <[email protected]>
+
+        [Modern Media Controls] layout nodes
+        https://bugs.webkit.org/show_bug.cgi?id=162799
+        <rdar://problem/28569301>
+
+        Reviewed by Dean Jackson.
+
+        Modern media controls will be using a tree of LayoutNode objects that commit to the DOM
+        in coordinated `requestAnimationFrame()` calls to ensure all layouts are done in an efficient
+        and coordinated manner. As a preamble, we introduced the `scheduler` singleton in
+        https://webkit.org/b/162726 which is in charge of scheduling callbacks.
+
+        A LayoutNode is created by providing an Element to its constructor, or an HTML string. Not
+        providing a parameter creates a simple <div>.
+
+        When we set a property on a LayoutNode, we call `markDirtyProperty(propertyName)` which keeps
+        track of dirty properties in the `_dirtyProperties` set. When this set is non-empty, the node
+        is marked as dirty and registered in the global `dirtyNodes` map, asking the shared scheduler
+        that a layout is needed. When the layout is performed, all nodes in the `dirtyNodes` map are
+        processed such that `commitProperty(propertyName)` is called to commit dirty properties for
+        a given node to the DOM, and `layout()` is called to allow subclasses of LayoutNode to conduct
+        custom layout logic that goes beyond committing a given property.
+
+        Another reason why a node may be marked as dirty is when a DOM hierarchy change is needed. A
+        host of DOM-like methods are exposed to allow flexible manipulations of nodes, with an extra
+        `children` property which allows wholesale change of a node's subtree with a single array
+        property assignment. Changes to the DOM hierarchy are performed in the same scheduler callback
+        as style properties.
+
+        Nodes can be marked for layout explicitly with by setting the `needsLayout` property.
+
+        Tests: media/modern-media-controls/layout-node/addChild.html
+               media/modern-media-controls/layout-node/children.html
+               media/modern-media-controls/layout-node/constructor.html
+               media/modern-media-controls/layout-node/height.html
+               media/modern-media-controls/layout-node/insertAfter.html
+               media/modern-media-controls/layout-node/insertBefore.html
+               media/modern-media-controls/layout-node/parent.html
+               media/modern-media-controls/layout-node/remove.html
+               media/modern-media-controls/layout-node/removeChild.html
+               media/modern-media-controls/layout-node/subclassing.html
+               media/modern-media-controls/layout-node/visible.html
+               media/modern-media-controls/layout-node/width.html
+               media/modern-media-controls/layout-node/x.html
+               media/modern-media-controls/layout-node/y.html
+
+        * Modules/modern-media-controls/controls/layout-node.js: Added.
+        (LayoutNode):
+        (LayoutNode.prototype.get x):
+        (LayoutNode.prototype.set x):
+        (LayoutNode.prototype.get y):
+        (LayoutNode.prototype.set y):
+        (LayoutNode.prototype.get width):
+        (LayoutNode.prototype.set width):
+        (LayoutNode.prototype.get height):
+        (LayoutNode.prototype.set height):
+        (LayoutNode.prototype.get visible):
+        (LayoutNode.prototype.set visible):
+        (LayoutNode.prototype.get needsLayout):
+        (LayoutNode.prototype.set needsLayout):
+        (LayoutNode.prototype.get parent):
+        (LayoutNode.prototype.get children):
+        (LayoutNode.prototype.set children):
+        (LayoutNode.prototype.addChild):
+        (LayoutNode.prototype.insertBefore):
+        (LayoutNode.prototype.insertAfter):
+        (LayoutNode.prototype.removeChild):
+        (LayoutNode.prototype.remove):
+        (LayoutNode.prototype.markDirtyProperty):
+        (LayoutNode.prototype.commitProperty):
+        (LayoutNode.prototype.layout):
+        (LayoutNode.prototype._markNodeManipulation):
+        (LayoutNode.prototype._updateDirtyState):
+        (LayoutNode.prototype._updateChildren):
+        (performScheduledLayout):
+        (elementFromString):
+
 2016-09-30  Said Abou-Hallawa  <[email protected]>
 
         The dragged image should be the current frame only of the animated image

Added: trunk/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js (0 => 206686)


--- trunk/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js	                        (rev 0)
+++ trunk/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js	2016-09-30 22:45:17 UTC (rev 206686)
@@ -0,0 +1,270 @@
+
+const dirtyNodes = new Set;
+const nodesRequiringChildrenUpdate = new Set;
+
+class LayoutNode
+{
+
+    constructor(stringOrElement)
+    {
+
+        if (!stringOrElement)
+            this.element = document.createElement("div");
+        else if (stringOrElement instanceof Element)
+            this.element = stringOrElement;
+        else if (typeof stringOrElement === "string" || stringOrElement instanceof String)
+            this.element = elementFromString(stringOrElement);
+
+        this._parent = null;
+        this._children = [];
+
+        this._x = 0;
+        this._y = 0;
+        this._width = 0;
+        this._height = 0;
+        this._visible = true;
+    
+        this._needsLayout = false;
+        this._dirtyProperties = new Set;
+
+        this._pendingDOMManipulation = LayoutNode.DOMManipulation.None;
+    }
+
+    get x()
+    {
+        return this._x;
+    }
+
+    set x(x)
+    {
+        this._x = x;
+        this.markDirtyProperty("x");
+    }
+
+    get y()
+    {
+        return this._y;
+    }
+
+    set y(y)
+    {
+        this._y = y;
+        this.markDirtyProperty("y");
+    }
+
+    get width()
+    {
+        return this._width;
+    }
+
+    set width(width)
+    {
+        this._width = width;
+        this.markDirtyProperty("width");
+    }
+
+    get height()
+    {
+        return this._height;
+    }
+
+    set height(height)
+    {
+        this._height = height;
+        this.markDirtyProperty("height");
+    }
+
+    get visible()
+    {
+        return this._visible;
+    }
+
+    set visible(flag)
+    {
+        this._visible = flag;
+        this.markDirtyProperty("visible");
+    }
+
+    get needsLayout()
+    {
+        return this._needsLayout || this._pendingDOMManipulation !== LayoutNode.DOMManipulation.None || this._dirtyProperties.size > 0;
+    }
+
+    set needsLayout(flag)
+    {
+        if (this.needsLayout === flag)
+            return;
+
+        this._needsLayout = true;
+        this._updateDirtyState();
+    }
+
+    get parent()
+    {
+        return this._parent;
+    }
+
+    get children()
+    {
+        return this._children;
+    }
+
+    set children(children)
+    {
+        while (this._children.length)
+            this.removeChild(this._children[0]);
+        
+        for (let child of children)
+            this.addChild(child);
+    }
+
+    addChild(child, index)
+    {
+        child.remove();
+
+        if (index === undefined || index < 0 || index > this._children.length)
+            index = this._children.length;
+
+        this._children.splice(index, 0, child);
+        child._parent = this;
+
+        child._markNodeManipulation(LayoutNode.DOMManipulation.Addition);
+
+        return child;
+    }
+
+    insertBefore(newSibling, referenceSibling)
+    {
+        return this.addChild(newSibling, this._children.indexOf(referenceSibling));
+    }
+
+    insertAfter(newSibling, referenceSibling)
+    {
+        const index = this._children.indexOf(referenceSibling);
+        return this.addChild(newSibling, index + 1);
+    }
+
+    removeChild(child)
+    {
+        if (child._parent !== this)
+            return;
+
+        const index = this._children.indexOf(child);
+        if (index === -1)
+            return;
+
+        this._children.splice(index, 1);
+        child._parent = null;
+
+        child._markNodeManipulation(LayoutNode.DOMManipulation.Removal);
+
+        return child;
+    }
+
+    remove()
+    {
+        if (this._parent instanceof LayoutNode)
+            return this._parent.removeChild(this);
+    }
+
+    markDirtyProperty(propertyName) {
+        const hadProperty = this._dirtyProperties.has(propertyName);
+        this._dirtyProperties.add(propertyName);
+
+        if (!hadProperty)
+            this._updateDirtyState();
+    }
+
+    commitProperty(propertyName)
+    {
+        const style = this.element.style;
+
+        switch (propertyName) {
+        case "x":
+            style.left = `${this._x}px`;
+            break;
+        case "y":
+            style.top = `${this._y}px`;
+            break;
+        case "width":
+            style.width = `${this._width}px`;
+            break;
+        case "height":
+            style.height = `${this._height}px`;
+            break;
+        case "visible":
+            style.display = this._visible ? "inherit" : "none";
+            break;
+        }
+    }
+
+    layout()
+    {
+        if (this._pendingDOMManipulation === LayoutNode.DOMManipulation.Removal) {
+            const parent = this.element.parentNode;
+            if (parent)
+                parent.removeChild(this.element);
+        }
+    
+        for (let propertyName of this._dirtyProperties)
+            this.commitProperty(propertyName);
+
+        this._dirtyProperties.clear();
+
+        if (this._pendingDOMManipulation === LayoutNode.DOMManipulation.Addition)
+            nodesRequiringChildrenUpdate.add(this.parent);
+    }
+
+    // Private
+
+    _markNodeManipulation(manipulation) {
+        this._pendingDOMManipulation = manipulation;
+        this._updateDirtyState();
+    }
+
+    _updateDirtyState() {
+        if (this.needsLayout) {
+            dirtyNodes.add(this);
+            scheduler.scheduleLayout(performScheduledLayout);
+        } else
+            dirtyNodes.delete(node);
+    }
+
+    _updateChildren()
+    {
+        let nextChildElement = null;
+        const element = this.element;
+        for (let i = this.children.length - 1; i >= 0; --i) {
+            let child = this.children[i];
+            let childElement = child.element;
+        
+            if (child._pendingDOMManipulation === LayoutNode.DOMManipulation.Addition) {
+                element.insertBefore(childElement, nextChildElement);
+                child._pendingDOMManipulation = LayoutNode.DOMManipulation.None;
+            }
+
+            nextChildElement = childElement;
+        }
+    }
+
+}
+
+LayoutNode.DOMManipulation = {
+    None:     0,
+    Removal:  1,
+    Addition: 2
+};
+
+function performScheduledLayout() {
+    dirtyNodes.forEach(node => node.layout());
+    dirtyNodes.clear();
+
+    nodesRequiringChildrenUpdate.forEach(node => node._updateChildren());
+    nodesRequiringChildrenUpdate.clear();
+}
+
+function elementFromString(elementString) {
+    const element = document.createElement("div");
+    element.innerHTML = elementString;
+    return element.firstElementChild;
+}
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to