<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[206686] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/206686">206686</a></dd>
<dt>Author</dt> <dd>commit-queue@webkit.org</dd>
<dt>Date</dt> <dd>2016-09-30 15:45:17 -0700 (Fri, 30 Sep 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>[Modern Media Controls] layout nodes
https://bugs.webkit.org/show_bug.cgi?id=162799
&lt;rdar://problem/28569301&gt;

Patch by Antoine Quint &lt;graouts@apple.com&gt; 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 &lt;div&gt;.

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.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebCoreChangeLog">trunk/Source/WebCore/ChangeLog</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li>trunk/LayoutTests/media/modern-media-controls/layout-node/</li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeaddChildexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/addChild-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeaddChildhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/addChild.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodechildrenexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/children-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodechildrenhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/children.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeconstructorexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/constructor-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeconstructorhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/constructor.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeheightexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/height-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeheighthtml">trunk/LayoutTests/media/modern-media-controls/layout-node/height.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertAfterexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertAfterhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertBeforeexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertBeforehtml">trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeparentexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/parent-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeparenthtml">trunk/LayoutTests/media/modern-media-controls/layout-node/parent.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/remove-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnoderemovehtml">trunk/LayoutTests/media/modern-media-controls/layout-node/remove.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveChildexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveChildhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodesubclassingexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodesubclassinghtml">trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodevisibleexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/visible-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodevisiblehtml">trunk/LayoutTests/media/modern-media-controls/layout-node/visible.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodewidthexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/width-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodewidthhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/width.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodexexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/x-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodexhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/x.html</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeyexpectedtxt">trunk/LayoutTests/media/modern-media-controls/layout-node/y-expected.txt</a></li>
<li><a href="#trunkLayoutTestsmediamodernmediacontrolslayoutnodeyhtml">trunk/LayoutTests/media/modern-media-controls/layout-node/y.html</a></li>
<li><a href="#trunkSourceWebCoreModulesmodernmediacontrolscontrolslayoutnodejs">trunk/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (206685 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-09-30 22:43:40 UTC (rev 206685)
+++ trunk/LayoutTests/ChangeLog        2016-09-30 22:45:17 UTC (rev 206686)
</span><span class="lines">@@ -1,3 +1,42 @@
</span><ins>+2016-09-30  Antoine Quint  &lt;graouts@apple.com&gt;
+
+        [Modern Media Controls] layout nodes
+        https://bugs.webkit.org/show_bug.cgi?id=162799
+        &lt;rdar://problem/28569301&gt;
+
+        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.
+
</ins><span class="cx"> 2016-09-30  Ryan Haddad  &lt;ryanhaddad@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Marking http/tests/media/hls/hls-video-resize.html as flaky on mac-wk1.
</span></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeaddChildexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/addChild-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,23 @@
</span><ins>+Testing the LayoutNode.addChild(child[, index]) method.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeaddChildhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/addChild.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,41 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.addChild(child[, index])&lt;/code&gt; method.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+
+debug(&quot;node.addChild(a)&quot;);
+const retVal = node.addChild(a);
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;retVal === a&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.addChild(b, 0)&quot;);
+node.addChild(b, 0);
+shouldBeTrue(&quot;node.children.length === 2&quot;);
+shouldBeTrue(&quot;node.children[0] === b&quot;);
+shouldBeTrue(&quot;node.children[1] === a&quot;);
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    shouldBeTrue(&quot;node.element.childElementCount === 2&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild === b.element&quot;);
+    shouldBeTrue(&quot;node.element.lastElementChild === a.element&quot;);
+
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodechildrenexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/children-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,41 @@
</span><ins>+Testing the LayoutNode.children property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodechildrenhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/children.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,67 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.children&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Check default state&quot;);
+shouldBeTrue(&quot;Array.isArray(node.children)&quot;);
+shouldBeTrue(&quot;node.children.length === 0&quot;);
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+const children = [a, b, c];
+node.children = children;
+
+debug(&quot;&quot;);
+debug(&quot;Set children to [a, b, c]&quot;);
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === b&quot;);
+shouldBeTrue(&quot;node.children[2] === c&quot;);
+shouldBeTrue(&quot;node.children !== children&quot;);
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBeTrue(&quot;node.element.childElementCount === 3&quot;);
+        shouldBeTrue(&quot;node.element.firstElementChild === a.element&quot;);
+        shouldBeTrue(&quot;node.element.firstElementChild.nextElementSibling === b.element&quot;);
+        shouldBeTrue(&quot;node.element.lastElementChild === c.element&quot;);
+        debug(&quot;&quot;);
+        debug(&quot;Set children to [b, a]&quot;);
+        node.children = [b, a];
+        shouldBeTrue(&quot;node.children.length === 2&quot;);
+        shouldBeTrue(&quot;node.children[0] === b&quot;);
+        shouldBeTrue(&quot;node.children[1] === a&quot;);
+        break;
+    case 2:
+        shouldBeTrue(&quot;node.element.childElementCount === 2&quot;);
+        shouldBeTrue(&quot;node.element.firstElementChild === b.element&quot;);
+        shouldBeTrue(&quot;node.element.lastElementChild === a.element&quot;);
+        debug(&quot;&quot;);
+        debug(&quot;Set children to []&quot;);
+        node.children = [];
+        shouldBeTrue(&quot;node.children.length === 0&quot;);
+        break;
+    case 3:
+        shouldBeTrue(&quot;node.element.childElementCount === 0&quot;);
+        finishJSTest();
+        break;
+    }
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeconstructorexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/constructor-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,19 @@
</span><ins>+Testing the LayoutNode various constructor parameters.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+No parameter
+PASS nodeWithNoParameter.element.localName is &quot;div&quot;
+
+Element parameter
+PASS nodeWithElementParameter.element === element is true
+
+HTML string parameter
+PASS nodeWithStringParameter.element.localName is &quot;span&quot;
+PASS nodeWithStringParameter.element.textContent is &quot;hello world&quot;
+PASS nodeWithStringParameter.element.firstElementChild.localName is &quot;strong&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeconstructorhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/constructor.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,26 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode&lt;/code&gt; various constructor parameters.&quot;);
+
+debug(&quot;No parameter&quot;);
+const nodeWithNoParameter = new LayoutNode;
+shouldBeEqualToString(&quot;nodeWithNoParameter.element.localName&quot;, &quot;div&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;Element parameter&quot;);
+const element = document.createElement(&quot;h1&quot;);
+const nodeWithElementParameter = new LayoutNode(element);
+shouldBeTrue(&quot;nodeWithElementParameter.element === element&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;HTML string parameter&quot;);
+const nodeWithStringParameter = new LayoutNode(`&lt;span&gt;hello &lt;strong&gt;world&lt;/strong&gt;&lt;/span&gt;`);
+shouldBeEqualToString(&quot;nodeWithStringParameter.element.localName&quot;, &quot;span&quot;);
+shouldBeEqualToString(&quot;nodeWithStringParameter.element.textContent&quot;, &quot;hello world&quot;);
+shouldBeEqualToString(&quot;nodeWithStringParameter.element.firstElementChild.localName&quot;, &quot;strong&quot;);
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeheightexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/height-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,22 @@
</span><ins>+Testing the LayoutNode.height property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+Checking default value
+PASS node.height is 0
+PASS node.element.style.height is &quot;&quot;
+
+node.height = 10
+PASS node.height is 10
+PASS node.element.style.height is &quot;&quot;
+
+node.height = 20
+
+Layout was performed
+PASS node.height is 20
+PASS node.element.style.height is &quot;20px&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeheighthtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/height.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.height&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Checking default value&quot;);
+shouldBe(&quot;node.height&quot;, &quot;0&quot;);
+shouldBeEqualToString(&quot;node.element.style.height&quot;, &quot;&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.height = 10&quot;);
+node.height = 10;
+shouldBe(&quot;node.height&quot;, &quot;10&quot;);
+shouldBeEqualToString(&quot;node.element.style.height&quot;, &quot;&quot;);
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug(&quot;&quot;);
+debug(&quot;node.height = 20&quot;);
+node.height = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+    shouldBe(&quot;node.height&quot;, &quot;20&quot;);
+    shouldBeEqualToString(&quot;node.element.style.height&quot;, &quot;20px&quot;);
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertAfterexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,36 @@
</span><ins>+Testing the LayoutNode.insertAfter(newSibling, referenceSibling) method.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertAfterhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertAfter.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,59 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.insertAfter(newSibling, referenceSibling)&lt;/code&gt; method.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug(&quot;node.insertAfter(a)&quot;);
+const retVal = node.insertAfter(a);
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;retVal === a&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertAfter(c, a)&quot;);
+node.insertAfter(c, a);
+shouldBeTrue(&quot;node.children.length === 2&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertAfter(b, a)&quot;);
+node.insertAfter(b, a);
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === b&quot;);
+shouldBeTrue(&quot;node.children[2] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertAfter(a, c)&quot;);
+node.insertAfter(a, c);
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === b&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+shouldBeTrue(&quot;node.children[2] === a&quot;);
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    shouldBeTrue(&quot;node.element.childElementCount === 3&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild === b.element&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild.nextElementSibling === c.element&quot;);
+    shouldBeTrue(&quot;node.element.lastElementChild === a.element&quot;);
+
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertBeforeexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,36 @@
</span><ins>+Testing the LayoutNode.insertBefore(newSibling, referenceSibling) method.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeinsertBeforehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/insertBefore.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,59 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.insertBefore(newSibling, referenceSibling)&lt;/code&gt; method.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug(&quot;node.insertBefore(c)&quot;);
+const retVal = node.insertBefore(c);
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === c&quot;);
+shouldBeTrue(&quot;retVal === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertBefore(a, c)&quot;);
+node.insertBefore(a, c);
+shouldBeTrue(&quot;node.children.length === 2&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertBefore(b, c)&quot;);
+node.insertBefore(b, c);
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === b&quot;);
+shouldBeTrue(&quot;node.children[2] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertBefore(a, c)&quot;);
+node.insertBefore(a, c);
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === b&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+shouldBeTrue(&quot;node.children[2] === a&quot;);
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    shouldBeTrue(&quot;node.element.childElementCount === 3&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild === b.element&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild.nextElementSibling === c.element&quot;);
+    shouldBeTrue(&quot;node.element.lastElementChild === a.element&quot;);
+
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeparentexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/parent-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,40 @@
</span><ins>+Testing the LayoutNode.parent property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeparenthtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/parent.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,62 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.parent&lt;/code&gt; property.&quot;);
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+const d = new LayoutNode;
+
+debug(&quot;Checking nodes have a null parent by default&quot;);
+shouldBeTrue(&quot;a.parent === null&quot;);
+shouldBeTrue(&quot;b.parent === null&quot;);
+shouldBeTrue(&quot;c.parent === null&quot;);
+shouldBeTrue(&quot;d.parent === null&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.children = [a, b, c]&quot;);
+node.children = [a, b, c];
+shouldBeTrue(&quot;a.parent === node&quot;);
+shouldBeTrue(&quot;b.parent === node&quot;);
+shouldBeTrue(&quot;c.parent === node&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;a.remove()&quot;);
+a.remove();
+shouldBeTrue(&quot;a.parent === null&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.removeChild(b)&quot;);
+node.removeChild(b);
+shouldBeTrue(&quot;b.parent === null&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.addChild(a)&quot;);
+node.addChild(a);
+shouldBeTrue(&quot;a.parent === node&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertBefore(b, c)&quot;);
+node.insertBefore(b, c);
+shouldBeTrue(&quot;b.parent === node&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.insertAfter(d, c)&quot;);
+node.insertAfter(d, c);
+shouldBeTrue(&quot;d.parent === node&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.children = []&quot;);
+node.children = [];
+shouldBeTrue(&quot;a.parent === null&quot;);
+shouldBeTrue(&quot;b.parent === null&quot;);
+shouldBeTrue(&quot;c.parent === null&quot;);
+shouldBeTrue(&quot;d.parent === null&quot;);
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/remove-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+Testing the LayoutNode.remove() method.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnoderemovehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/remove.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,49 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.remove()&lt;/code&gt; method.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug(&quot;node.children = [a, b, c]&quot;);
+node.children = [a, b, c];
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === b&quot;);
+shouldBeTrue(&quot;node.children[2] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;b.remove()&quot;);
+const retVal = b.remove();
+shouldBeTrue(&quot;node.children.length === 2&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+shouldBeTrue(&quot;retVal === b&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;a.remove()&quot;);
+a.remove();
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === c&quot;);
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    shouldBeTrue(&quot;node.element.childElementCount === 1&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild === c.element&quot;);
+
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveChildexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,32 @@
</span><ins>+Testing the LayoutNode.removeChild(child) method.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnoderemoveChildhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/removeChild.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,55 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.removeChild(child)&lt;/code&gt; method.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+const a = new LayoutNode;
+const b = new LayoutNode;
+const c = new LayoutNode;
+
+debug(&quot;node.children = [a, b, c]&quot;);
+node.children = [a, b, c];
+shouldBeTrue(&quot;node.children.length === 3&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === b&quot;);
+shouldBeTrue(&quot;node.children[2] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.removeChild(b)&quot;);
+const retVal = node.removeChild(b);
+shouldBeTrue(&quot;node.children.length === 2&quot;);
+shouldBeTrue(&quot;node.children[0] === a&quot;);
+shouldBeTrue(&quot;node.children[1] === c&quot;);
+shouldBeTrue(&quot;retVal === b&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.removeChild(a)&quot;);
+node.removeChild(a);
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === c&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.removeChild(a)&quot;);
+node.removeChild(a);
+shouldBeTrue(&quot;node.children.length === 1&quot;);
+shouldBeTrue(&quot;node.children[0] === c&quot;);
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    shouldBeTrue(&quot;node.element.childElementCount === 1&quot;);
+    shouldBeTrue(&quot;node.element.firstElementChild === c.element&quot;);
+
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodesubclassingexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,28 @@
</span><ins>+Subclassing LayoutNode by exposing a new custom property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+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 &quot;0.5&quot;
+
+node.needsLayout = true
+
+Layout will be performed
+OpacityNode.layout() was called
+
+Layout was performed
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodesubclassinghtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/subclassing.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,79 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Subclassing &lt;code&gt;LayoutNode&lt;/code&gt; by exposing a new custom property.&quot;);
+
+window.jsTestIsAsync = true;
+
+class OpacityNode extends LayoutNode
+{
+
+    constructor(stringOrElement)
+    {
+        super(stringOrElement);
+
+        this._opacity = 1;
+    }
+
+    set opacity(opacity)
+    {
+        this._opacity = opacity;
+        this.markDirtyProperty(&quot;opacity&quot;);
+    }
+
+    commitProperty(propertyName)
+    {
+        debug(`OpacityNode.commitProperty() was called with propertyName = ${propertyName}`);
+        if (propertyName === &quot;opacity&quot;)
+            this.element.style.opacity = this._opacity;
+        else
+            super.commitProperty(propertyName);    
+    }
+
+    layout()
+    {
+        debug(&quot;OpacityNode.layout() was called&quot;);
+        super.layout();
+    }
+
+}
+
+const node = new OpacityNode;
+
+debug(&quot;Check the node is not dirty by default&quot;);
+shouldBeFalse(&quot;node.needsLayout&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.opacity = 0.5&quot;);
+node.opacity = 0.5;
+shouldBeTrue(&quot;node.needsLayout&quot;);
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBeEqualToString(&quot;node.element.style.opacity&quot;, &quot;0.5&quot;);
+        debug(&quot;&quot;);
+        debug(&quot;node.needsLayout = true&quot;);
+        node.needsLayout = true;
+        break;
+    case 2:
+        finishJSTest();
+        break;
+    }
+};
+
+scheduler.frameWillFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout will be performed&quot;);
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodevisibleexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/visible-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,26 @@
</span><ins>+Testing the LayoutNode.visible property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+Checking default value
+PASS node.visible is true
+PASS node.element.style.display is &quot;&quot;
+
+node.visible = false
+PASS node.visible is false
+PASS node.element.style.display is &quot;&quot;
+
+Layout was performed
+PASS node.visible is false
+PASS node.element.style.display is &quot;none&quot;
+
+node.visible = true
+
+Layout was performed
+PASS node.visible is true
+PASS node.element.style.display is &quot;inherit&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodevisiblehtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/visible.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,45 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.visible&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Checking default value&quot;);
+shouldBe(&quot;node.visible&quot;, &quot;true&quot;);
+shouldBeEqualToString(&quot;node.element.style.display&quot;, &quot;&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.visible = false&quot;);
+node.visible = false;
+shouldBe(&quot;node.visible&quot;, &quot;false&quot;);
+shouldBeEqualToString(&quot;node.element.style.display&quot;, &quot;&quot;);
+
+let numberOfFrames = 0;
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+
+    switch (++numberOfFrames) {
+    case 1:
+        shouldBe(&quot;node.visible&quot;, &quot;false&quot;);
+        shouldBeEqualToString(&quot;node.element.style.display&quot;, &quot;none&quot;);
+        debug(&quot;&quot;);
+        debug(&quot;node.visible = true&quot;);
+        node.visible = true;
+        break;
+    case 2:
+        shouldBe(&quot;node.visible&quot;, &quot;true&quot;);
+        shouldBeEqualToString(&quot;node.element.style.display&quot;, &quot;inherit&quot;);
+        finishJSTest();
+        break;
+    }
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodewidthexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/width-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,22 @@
</span><ins>+Testing the LayoutNode.width property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+Checking default value
+PASS node.width is 0
+PASS node.element.style.width is &quot;&quot;
+
+node.width = 10
+PASS node.width is 10
+PASS node.element.style.width is &quot;&quot;
+
+node.width = 20
+
+Layout was performed
+PASS node.width is 20
+PASS node.element.style.width is &quot;20px&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodewidthhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/width.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.width&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Checking default value&quot;);
+shouldBe(&quot;node.width&quot;, &quot;0&quot;);
+shouldBeEqualToString(&quot;node.element.style.width&quot;, &quot;&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.width = 10&quot;);
+node.width = 10;
+shouldBe(&quot;node.width&quot;, &quot;10&quot;);
+shouldBeEqualToString(&quot;node.element.style.width&quot;, &quot;&quot;);
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug(&quot;&quot;);
+debug(&quot;node.width = 20&quot;);
+node.width = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+    shouldBe(&quot;node.width&quot;, &quot;20&quot;);
+    shouldBeEqualToString(&quot;node.element.style.width&quot;, &quot;20px&quot;);
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodexexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/x-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,22 @@
</span><ins>+Testing the LayoutNode.x property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+Checking default value
+PASS node.x is 0
+PASS node.element.style.left is &quot;&quot;
+
+node.x = 10
+PASS node.x is 10
+PASS node.element.style.left is &quot;&quot;
+
+node.x = 20
+
+Layout was performed
+PASS node.x is 20
+PASS node.element.style.left is &quot;20px&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodexhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/x.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.x&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Checking default value&quot;);
+shouldBe(&quot;node.x&quot;, &quot;0&quot;);
+shouldBeEqualToString(&quot;node.element.style.left&quot;, &quot;&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.x = 10&quot;);
+node.x = 10;
+shouldBe(&quot;node.x&quot;, &quot;10&quot;);
+shouldBeEqualToString(&quot;node.element.style.left&quot;, &quot;&quot;);
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug(&quot;&quot;);
+debug(&quot;node.x = 20&quot;);
+node.x = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+    shouldBe(&quot;node.x&quot;, &quot;20&quot;);
+    shouldBeEqualToString(&quot;node.element.style.left&quot;, &quot;20px&quot;);
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeyexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/y-expected.txt (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,22 @@
</span><ins>+Testing the LayoutNode.y property.
+
+On success, you will see a series of &quot;PASS&quot; messages, followed by &quot;TEST COMPLETE&quot;.
+
+
+Checking default value
+PASS node.y is 0
+PASS node.element.style.top is &quot;&quot;
+
+node.y = 10
+PASS node.y is 10
+PASS node.element.style.top is &quot;&quot;
+
+node.y = 20
+
+Layout was performed
+PASS node.y is 20
+PASS node.element.style.top is &quot;20px&quot;
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
</ins></span></pre></div>
<a id="trunkLayoutTestsmediamodernmediacontrolslayoutnodeyhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/media/modern-media-controls/layout-node/y.html (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,37 @@
</span><ins>+&lt;script src=&quot;../../../resources/js-test-pre.js&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/scheduler.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script src=&quot;../../../../Source/WebCore/Modules/modern-media-controls/controls/layout-node.js&quot; type=&quot;text/javascript&quot;&gt;&lt;/script&gt;
+&lt;script type=&quot;text/javascript&quot;&gt;
+
+description(&quot;Testing the &lt;code&gt;LayoutNode.y&lt;/code&gt; property.&quot;);
+
+window.jsTestIsAsync = true;
+
+const node = new LayoutNode;
+
+debug(&quot;Checking default value&quot;);
+shouldBe(&quot;node.y&quot;, &quot;0&quot;);
+shouldBeEqualToString(&quot;node.element.style.top&quot;, &quot;&quot;);
+
+debug(&quot;&quot;);
+debug(&quot;node.y = 10&quot;);
+node.y = 10;
+shouldBe(&quot;node.y&quot;, &quot;10&quot;);
+shouldBeEqualToString(&quot;node.element.style.top&quot;, &quot;&quot;);
+
+// Set the value to another one so we can check it's the one committed to the DOM.
+debug(&quot;&quot;);
+debug(&quot;node.y = 20&quot;);
+node.y = 20;
+
+scheduler.frameDidFire = function()
+{
+    debug(&quot;&quot;);
+    debug(&quot;Layout was performed&quot;);
+    shouldBe(&quot;node.y&quot;, &quot;20&quot;);
+    shouldBeEqualToString(&quot;node.element.style.top&quot;, &quot;20px&quot;);
+    finishJSTest();
+};
+
+&lt;/script&gt;
+&lt;script src=&quot;../../../resources/js-test-post.js&quot;&gt;&lt;/script&gt;
</ins></span></pre></div>
<a id="trunkSourceWebCoreChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebCore/ChangeLog (206685 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,3 +1,81 @@
</span><ins>+2016-09-30  Antoine Quint  &lt;graouts@apple.com&gt;
+
+        [Modern Media Controls] layout nodes
+        https://bugs.webkit.org/show_bug.cgi?id=162799
+        &lt;rdar://problem/28569301&gt;
+
+        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 &lt;div&gt;.
+
+        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):
+
</ins><span class="cx"> 2016-09-30  Said Abou-Hallawa  &lt;sabouhallawa@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         The dragged image should be the current frame only of the animated image
</span></span></pre></div>
<a id="trunkSourceWebCoreModulesmodernmediacontrolscontrolslayoutnodejs"></a>
<div class="addfile"><h4>Added: trunk/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js (0 => 206686)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -0,0 +1,270 @@
</span><ins>+
+const dirtyNodes = new Set;
+const nodesRequiringChildrenUpdate = new Set;
+
+class LayoutNode
+{
+
+    constructor(stringOrElement)
+    {
+
+        if (!stringOrElement)
+            this.element = document.createElement(&quot;div&quot;);
+        else if (stringOrElement instanceof Element)
+            this.element = stringOrElement;
+        else if (typeof stringOrElement === &quot;string&quot; || 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(&quot;x&quot;);
+    }
+
+    get y()
+    {
+        return this._y;
+    }
+
+    set y(y)
+    {
+        this._y = y;
+        this.markDirtyProperty(&quot;y&quot;);
+    }
+
+    get width()
+    {
+        return this._width;
+    }
+
+    set width(width)
+    {
+        this._width = width;
+        this.markDirtyProperty(&quot;width&quot;);
+    }
+
+    get height()
+    {
+        return this._height;
+    }
+
+    set height(height)
+    {
+        this._height = height;
+        this.markDirtyProperty(&quot;height&quot;);
+    }
+
+    get visible()
+    {
+        return this._visible;
+    }
+
+    set visible(flag)
+    {
+        this._visible = flag;
+        this.markDirtyProperty(&quot;visible&quot;);
+    }
+
+    get needsLayout()
+    {
+        return this._needsLayout || this._pendingDOMManipulation !== LayoutNode.DOMManipulation.None || this._dirtyProperties.size &gt; 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 &lt; 0 || index &gt; 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 &quot;x&quot;:
+            style.left = `${this._x}px`;
+            break;
+        case &quot;y&quot;:
+            style.top = `${this._y}px`;
+            break;
+        case &quot;width&quot;:
+            style.width = `${this._width}px`;
+            break;
+        case &quot;height&quot;:
+            style.height = `${this._height}px`;
+            break;
+        case &quot;visible&quot;:
+            style.display = this._visible ? &quot;inherit&quot; : &quot;none&quot;;
+            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 &gt;= 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 =&gt; node.layout());
+    dirtyNodes.clear();
+
+    nodesRequiringChildrenUpdate.forEach(node =&gt; node._updateChildren());
+    nodesRequiringChildrenUpdate.clear();
+}
+
+function elementFromString(elementString) {
+    const element = document.createElement(&quot;div&quot;);
+    element.innerHTML = elementString;
+    return element.firstElementChild;
+}
</ins></span></pre>
</div>
</div>

</body>
</html>