Chromium Code Reviews| Index: client/html/src/DocumentFragmentWrappingImplementation.dart |
| diff --git a/client/html/src/DocumentFragmentWrappingImplementation.dart b/client/html/src/DocumentFragmentWrappingImplementation.dart |
| index 902879adc4f84aa2df365627d5c2c845420fd8ad..329a5ab4d350e867dc97900f4820957708aecd00 100644 |
| --- a/client/html/src/DocumentFragmentWrappingImplementation.dart |
| +++ b/client/html/src/DocumentFragmentWrappingImplementation.dart |
| @@ -2,19 +2,369 @@ |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| +class FilteredElementList implements ElementList { |
| + final Node _node; |
| + final NodeList _childNodes; |
| + |
| + FilteredElementList(Node node): _childNodes = node.nodes, _node = node; |
| + |
| + // We can't memoize this, since it's possible that children will be messed |
| + // with externally to this class. |
| + // |
| + // TODO(nweiz): Do we really need to copy the list to make the types work out? |
| + List<Element> get _filtered() => |
| + new List.from(_childNodes.filter((n) => n is Element)); |
| + |
| + // Don't use _filtered.first so we can short-circuit once we find an element. |
| + Element get first() { |
| + for (var node in _childNodes) { |
| + if (node is Element) { |
| + return node; |
| + } |
| + } |
| + } |
|
Jacob
2011/10/19 23:36:03
return null explicitly
nweiz
2011/10/19 23:42:09
Done.
|
| + |
| + void forEach(void f(Element element)) { |
| + _filtered.forEach(f); |
| + } |
| + |
| + void operator []=(int index, Element value) { |
| + this[index].replaceWith(value); |
| + } |
| + |
| + void set length(int newLength) { |
| + if (newLength >= length) { |
|
Jacob
2011/10/19 23:36:03
length is somewhat costly to compute so cache it a
nweiz
2011/10/19 23:42:09
Done.
|
| + return; |
| + } else if (newLength < 0) { |
| + throw const IllegalArgumentException("Invalid list length"); |
| + } |
| + |
| + removeRange(newLength - 1, length - newLength); |
| + } |
| + |
| + void add(Element value) { |
| + _childNodes.add(value); |
| + } |
| + |
| + void addAll(Collection<Element> collection) { |
| + collection.forEach(add); |
| + } |
| + |
| + void addLast(Element value) { |
| + add(value); |
| + } |
| + |
| + void sort(int compare(Element a, Element b)) { |
| + throw const UnsupportedOperationException('TODO(jacobr): should we impl?'); |
| + } |
| + |
| + void copyFrom(List<Object> src, int srcStart, int dstStart, int count) { |
| + throw const NotImplementedException(); |
| + } |
| + |
| + void setRange(int start, int length, List from, [int startFrom = 0]) { |
| + throw const NotImplementedException(); |
| + } |
| + |
| + void removeRange(int start, int length) { |
| + _filtered.getRange(start, length).forEach((el) => el.remove()); |
| + } |
| + |
| + void insertRange(int start, int length, [initialValue = null]) { |
| + throw const NotImplementedException(); |
| + } |
| + |
| + void clear() { |
| + // Currently, ElementList#clear clears even non-element nodes, so we follow |
| + // that behavior. |
| + _childNodes.clear(); |
| + } |
| + |
| + Element removeLast() { |
| + var last = this.last(); |
| + if (last != null) { |
| + last.remove(); |
| + } |
| + return last; |
| + } |
| + |
| + Collection<Element> filter(bool f(Element element)) => _filtered.filter(f); |
| + bool every(bool f(Element element)) => _filtered.every(f); |
| + bool some(bool f(Element element)) => _filtered.some(f); |
| + bool isEmpty() => _filtered.isEmpty(); |
| + int get length() => _filtered.length; |
| + Element operator [](int index) => _filtered[index]; |
| + Iterator<Element> iterator() => _filtered.iterator(); |
| + List<Element> getRange(int start, int length) => |
| + _filtered.getRange(start, length); |
| + int indexOf(Element element, int startIndex) => |
| + _filtered.indexOf(element, startIndex); |
| + int lastIndexOf(Element element, int startIndex) => |
| + _filtered.lastIndexOf(element, startIndex); |
| + Element last() => _filtered.last(); |
| +} |
| + |
| +class EmptyStyleDeclaration implements CSSStyleDeclaration { |
| + String get cssText() => ""; |
| + int get length() => 0; |
| + CSSRule get parentRule() => null; |
| + CSSValue getPropertyCSSValue(String propertyName) => null; |
| + String getPropertyPriority(String propertyName) => ""; |
| + String getPropertyShorthand(String propertyName) => null; |
| + String getPropertyValue(String propertyName) => null; |
| + bool isPropertyImplicit(String propertyName) => false; |
| + String item(int index) => ""; |
| + |
| + void set cssText(String value) { |
| + throw new UnsupportedOperationException( |
| + "Can't modify a frozen style declaration."); |
| + } |
| + |
| + String removeProperty(String propertyName) { |
| + throw new UnsupportedOperationException( |
| + "Can't modify a frozen style declaration."); |
| + } |
| + |
| + void setProperty(String propertyName, String value, [String priority]) { |
| + throw new UnsupportedOperationException( |
| + "Can't modify a frozen style declaration."); |
| + } |
| +} |
| + |
| +class EmptyClientRect implements ClientRect { |
| + num get bottom() => 0; |
| + num get top() => 0; |
| + num get left() => 0; |
| + num get right() => 0; |
| + num get height() => 0; |
| + num get width() => 0; |
| +} |
| + |
| class DocumentFragmentWrappingImplementation extends NodeWrappingImplementation implements DocumentFragment { |
| + ElementList _elements; |
| + ElementEvents _on; |
|
Jacob
2011/10/19 23:36:03
there's already an _on property declared by the No
nweiz
2011/10/19 23:42:09
Done.
|
| + |
| DocumentFragmentWrappingImplementation._wrap(ptr) : super._wrap(ptr) {} |
| + factory DocumentFragmentWrappingImplementation.html(String html) { |
| + var fragment = new DocumentFragment(); |
| + fragment.innerHTML = html; |
| + return fragment; |
| + } |
| + |
| factory DocumentFragmentWrappingImplementation() { |
|
Jacob
2011/10/19 23:36:03
nit: move this factory before the .html factory in
nweiz
2011/10/19 23:42:09
Done.
|
| return new DocumentFragmentWrappingImplementation._wrap( |
| dom.document.createDocumentFragment()); |
| } |
| - Element query(String selectors) { |
| - return LevelDom.wrapElement(_ptr.querySelector(selectors)); |
| + ElementList get elements() { |
| + if (_elements == null) { |
| + _elements = new FilteredElementList(this); |
| + } |
| + return _elements; |
| } |
| - ElementList queryAll(String selectors) { |
| - return LevelDom.wrapElementList(_ptr.querySelectorAll(selectors)); |
| + // TODO: The type of value should be Collection<Element>. See http://b/5392897 |
| + void set elements(value) { |
| + // Copy list first since we don't want liveness during iteration. |
| + List copy = new List.from(value); |
| + final elements = this.elements; |
| + elements.clear(); |
| + elements.addAll(copy); |
| } |
| -} |
| + |
| + String get innerHTML() { |
| + var e = new Element.tag("div"); |
| + e.nodes.add(this.clone(true)); |
| + return e.innerHTML; |
| + } |
| + |
| + String get outerHTML() => innerHTML; |
| + |
| + void set innerHTML(String value) { |
| + this.nodes.clear(); |
| + |
| + var e = new Element.tag("div"); |
| + e.innerHTML = value; |
| + |
| + // Copy list first since we don't want liveness during iteration. |
| + List nodes = new List.from(e.nodes); |
| + this.nodes.addAll(nodes); |
| + } |
| + |
| + Node _insertAdjacentNode(String where, Node node) { |
| + switch (where.toLowerCase()) { |
| + case "beforebegin": return null; |
| + case "afterend": return null; |
| + case "afterbegin": |
| + this.insertBefore(node, nodes.first); |
| + return node; |
| + case "beforeend": |
| + this.nodes.add(node); |
| + return node; |
| + default: |
| + throw new IllegalArgumentException("Invalid position ${where}"); |
| + } |
| + } |
| + |
| + Element insertAdjacentElement([String where = null, Element element = null]) |
| + => this._insertAdjacentNode(where, element); |
| + |
| + void insertAdjacentText([String where = null, String text = null]) { |
| + this._insertAdjacentNode(where, new Text(text)); |
| + } |
| + |
| + void insertAdjacentHTML( |
| + [String position_OR_where = null, String text = null]) { |
| + this._insertAdjacentNode( |
| + position_OR_where, new DocumentFragment.html(text)); |
| + } |
| + |
| + ElementEvents get on() { |
| + if (_on === null) { |
| + _on = new ElementEventsImplementation._wrap(_ptr); |
| + } |
| + return _on; |
| + } |
| + |
| + Element query(String selectors) => |
| + LevelDom.wrapElement(_ptr.querySelector(selectors)); |
| + |
| + ElementList queryAll(String selectors) => |
| + LevelDom.wrapElementList(_ptr.querySelectorAll(selectors)); |
| + |
| + // If we can come up with a semi-reasonable default value for an Element |
| + // getter, we'll use it. In general, these return the same values as an |
| + // element that has no parent. |
| + int get clientHeight() => 0; |
| + int get clientWidth() => 0; |
| + int get offsetHeight() => 0; |
| + int get offsetWidth() => 0; |
| + int get scrollHeight() => 0; |
| + int get scrollWidth() => 0; |
| + int get clientLeft() => 0; |
| + int get clientTop() => 0; |
| + int get offsetLeft() => 0; |
| + int get offsetTop() => 0; |
| + int get scrollLeft() => 0; |
| + int get scrollTop() => 0; |
| + String get contentEditable() => "false"; |
| + bool get isContentEditable() => false; |
| + bool get draggable() => false; |
| + bool get hidden() => false; |
| + bool get spellcheck() => false; |
| + int get tabIndex() => -1; |
| + String get id() => ""; |
| + String get title() => ""; |
| + String get tagName() => ""; |
| + String get webkitdropzone() => ""; |
| + Element get firstElementChild() => elements.first(); |
| + Element get lastElementChild() => elements.last; |
| + Element get nextElementSibling() => null; |
| + Element get previousElementSibling() => null; |
| + Element get offsetParent() => null; |
| + Element get parent() => null; |
| + Map<String, String> get attributes() => const {}; |
| + // Issue 174: this should be a const set. |
| + Set<String> get classes() => new Set<String>(); |
| + Map<String, String> get dataAttributes() => const {}; |
| + CSSStyleDeclaration get style() => new EmptyStyleDeclaration(); |
| + ClientRect getBoundingClientRect() => new EmptyClientRect(); |
| + List<ClientRect> getClientRects() => const []; |
| + bool matchesSelector([String selectors]) => false; |
| + |
| + // Imperative Element methods are made into no-ops, as they are on parentless |
| + // elements. |
| + void blur() {} |
| + void focus() {} |
| + void scrollByLines([int lines]) {} |
| + void scrollByPages([int pages]) {} |
| + void scrollIntoView([bool centerIfNeeded]) {} |
| + |
| + // Setters throw errors rather than being no-ops because we aren't going to |
| + // retain the values that were set, and erroring out seems clearer. |
| + void set attributes(Map<String, String> value) { |
| + throw new UnsupportedOperationException( |
| + "Attributes can't be set for document fragments."); |
| + } |
| + |
| + void set classes(Collection<String> value) { |
| + throw new UnsupportedOperationException( |
| + "Classes can't be set for document fragments."); |
| + } |
| + |
| + void set dataAttributes(Map<String, String> value) { |
| + throw new UnsupportedOperationException( |
| + "Data attributes can't be set for document fragments."); |
| + } |
| + |
| + void set contentEditable(String value) { |
| + throw new UnsupportedOperationException( |
| + "Content editable can't be set for document fragments."); |
| + } |
| + |
| + String get dir() { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support text direction."); |
| + } |
| + |
| + void set dir(String value) { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support text direction."); |
| + } |
| + |
| + void set draggable(bool value) { |
| + throw new UnsupportedOperationException( |
| + "Draggable can't be set for document fragments."); |
| + } |
| + |
| + void set hidden(bool value) { |
| + throw new UnsupportedOperationException( |
| + "Hidden can't be set for document fragments."); |
| + } |
| + |
| + void set id(String value) { |
| + throw new UnsupportedOperationException( |
| + "ID can't be set for document fragments."); |
| + } |
| + |
| + String get lang() { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support language."); |
| + } |
| + |
| + void set lang(String value) { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support language."); |
| + } |
| + |
| + void set scrollLeft(int value) { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support scrolling."); |
| + } |
| + |
| + void set scrollTop(int value) { |
| + throw new UnsupportedOperationException( |
| + "Document fragments don't support scrolling."); |
| + } |
| + |
| + void set spellcheck(bool value) { |
| + throw new UnsupportedOperationException( |
| + "Spellcheck can't be set for document fragments."); |
| + } |
| + |
| + void set tabIndex(int value) { |
| + throw new UnsupportedOperationException( |
| + "Tab index can't be set for document fragments."); |
| + } |
| + |
| + void set title(String value) { |
| + throw new UnsupportedOperationException( |
| + "Title can't be set for document fragments."); |
| + } |
| + |
| + void set webkitdropzone(String value) { |
| + throw new UnsupportedOperationException( |
| + "WebKit drop zone can't be set for document fragments."); |
| + } |
| +} |