"use strict"; const DOMException = require("../generated/DOMException"); const idlUtils = require("../generated/utils"); const { attach, detach } = require("../helpers/svg/basic-types"); // https://svgwg.org/svg2-draft/types.html#ListInterfaces // Child classes must implement _reserialize() class List { _initList({ element, attribute, readOnly = false }) { this._element = element; this._attribute = attribute; this._attributeRegistryEntry = element.constructor.attributeRegistry.get(attribute); this._readOnly = readOnly; this._list = []; this._version = -1; } get _needsResync() { return this._version < this._element._version; } _synchronize() { if (!this._needsResync) { return; } let value = []; if (this._element.hasAttributeNS(null, this._attribute)) { value = this._attributeRegistryEntry.getValue(this._element.getAttributeNS(null, this._attribute)); } if (value.length === 0 && this._attributeRegistryEntry.initialValue !== undefined) { value = this._attributeRegistryEntry.getValue(this._attributeRegistryEntry.initialValue); } // TODO: support non-DOMString lists. this._list = value; this._version = this._element._version; } _reserialize() { const elements = this._list; this._element.setAttributeNS(null, this._attribute, this._attributeRegistryEntry.serialize(elements)); // Prevent ping-ponging back and forth between _reserialize() and _synchronize(). this._version = this._element._version; } [idlUtils.supportsPropertyIndex](index) { this._synchronize(); return index >= 0 && index < this.length; } get [idlUtils.supportedPropertyIndices]() { this._synchronize(); return this._list.keys(); } get length() { this._synchronize(); return this._list.length; } get numberOfItems() { this._synchronize(); return this._list.length; } clear() { this._synchronize(); if (this._readOnly) { throw DOMException.create(this._globalObject, [ "Attempting to modify a read-only list", "NoModificationAllowedError" ]); } for (const item of this._list) { detach(item); } this._list.length = 0; this._reserialize(); } initialize(newItem) { this._synchronize(); if (this._readOnly) { throw DOMException.create(this._globalObject, [ "Attempting to modify a read-only list", "NoModificationAllowedError" ]); } for (const item of this._list) { detach(item); } this._list.length = 0; // TODO: clone non-DOMString list elements. attach(newItem, this); this._list.push(newItem); this._reserialize(); } getItem(index) { this._synchronize(); if (index >= this._list.length) { throw DOMException.create(this._globalObject, [ `The index provided (${index}) is greater than or equal to the maximum bound (${this._list.length}).`, "IndexSizeError" ]); } return this._list[index]; } insertItemBefore(newItem, index) { this._synchronize(); if (this._readOnly) { throw DOMException.create(this._globalObject, [ "Attempting to modify a read-only list", "NoModificationAllowedError" ]); } // TODO: clone non-DOMString list elements. if (index > this._list.length) { index = this._list.length; } this._list.splice(index, 0, newItem); attach(newItem, this); this._reserialize(); return newItem; } replaceItem(newItem, index) { this._synchronize(); if (this._readOnly) { throw DOMException.create(this._globalObject, [ "Attempting to modify a read-only list", "NoModificationAllowedError" ]); } if (index >= this._list.length) { throw DOMException.create(this._globalObject, [ `The index provided (${index}) is greater than or equal to the maximum bound (${this._list.length}).`, "IndexSizeError" ]); } // TODO: clone non-DOMString list elements. detach(this._list[index]); this._list[index] = newItem; attach(newItem, this); this._reserialize(); return newItem; } removeItem(index) { this._synchronize(); if (this._readOnly) { throw DOMException.create(this._globalObject, [ "Attempting to modify a read-only list", "NoModificationAllowedError" ]); } if (index >= this._list.length) { throw DOMException.create(this._globalObject, [ `The index provided (${index}) is greater than or equal to the maximum bound (${this._list.length}).`, "IndexSizeError" ]); } const item = this._list[index]; detach(item); this._list.splice(index, 1); this._reserialize(); return item; } appendItem(newItem) { this._synchronize(); // TODO: clone non-DOMString list elements. this._list.push(newItem); attach(newItem, this); this._reserialize(); return newItem; } [idlUtils.indexedSetNew](index, value) { // Note: this will always throw a IndexSizeError. this.replaceItem(value, index); } [idlUtils.indexedSetExisting](index, value) { this.replaceItem(value, index); } } module.exports = List;