summaryrefslogtreecommitdiff
path: root/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js
diff options
context:
space:
mode:
Diffstat (limited to 'Source/WebCore/Modules/modern-media-controls/controls/layout-node.js')
-rw-r--r--Source/WebCore/Modules/modern-media-controls/controls/layout-node.js307
1 files changed, 307 insertions, 0 deletions
diff --git a/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js b/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js
new file mode 100644
index 000000000..2a87f27d6
--- /dev/null
+++ b/Source/WebCore/Modules/modern-media-controls/controls/layout-node.js
@@ -0,0 +1,307 @@
+
+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)
+ {
+ if (x === this._x)
+ return;
+
+ this._x = x;
+ this.markDirtyProperty("x");
+ }
+
+ get y()
+ {
+ return this._y;
+ }
+
+ set y(y)
+ {
+ if (y === this._y)
+ return;
+
+ this._y = y;
+ this.markDirtyProperty("y");
+ }
+
+ get width()
+ {
+ return this._width;
+ }
+
+ set width(width)
+ {
+ if (width === this._width)
+ return;
+
+ this._width = width;
+ this.markDirtyProperty("width");
+ }
+
+ get height()
+ {
+ return this._height;
+ }
+
+ set height(height)
+ {
+ if (height === this._height)
+ return;
+
+ this._height = height;
+ this.markDirtyProperty("height");
+ }
+
+ get visible()
+ {
+ return this._visible;
+ }
+
+ set visible(flag)
+ {
+ if (flag === this._visible)
+ return;
+
+ 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 = flag;
+ 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);
+ }
+
+ parentOfType(type)
+ {
+ let node = this;
+ while (node = node._parent) {
+ if (node instanceof type)
+ return node;
+ }
+ return null;
+ }
+
+ 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(this);
+ if (dirtyNodes.size === 0)
+ scheduler.unscheduleLayout(performScheduledLayout);
+ }
+ }
+
+ _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()
+{
+ const previousDirtyNodes = Array.from(dirtyNodes);
+ dirtyNodes.clear();
+ previousDirtyNodes.forEach(node => {
+ node._needsLayout = false;
+ node.layout();
+ });
+
+ nodesRequiringChildrenUpdate.forEach(node => node._updateChildren());
+ nodesRequiringChildrenUpdate.clear();
+}
+
+function elementFromString(elementString)
+{
+ const element = document.createElement("div");
+ element.innerHTML = elementString;
+ return element.firstElementChild;
+}