Index: client/dom/templates/html/frog/impl_Element.darttemplate |
diff --git a/client/dom/templates/html/frog/impl_Element.darttemplate b/client/dom/templates/html/frog/impl_Element.darttemplate |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1345ffc5bb96b74c277543a22b48f86403eab46e |
--- /dev/null |
+++ b/client/dom/templates/html/frog/impl_Element.darttemplate |
@@ -0,0 +1,464 @@ |
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
+// 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. |
+ |
+// TODO(jacobr): use Lists.dart to remove some of the duplicated functionality. |
+class _ChildrenElementList implements ElementList { |
+ // Raw Element. |
+ final _ElementJs _element; |
+ final _HTMLCollectionJs _childElements; |
+ |
+ _ChildrenElementList._wrap(_ElementJs element) |
+ : _childElements = element._children, |
+ _element = element; |
+ |
+ List<Element> _toList() { |
+ final output = new List(_childElements.length); |
+ for (int i = 0, len = _childElements.length; i < len; i++) { |
+ output[i] = _childElements[i]; |
+ } |
+ return output; |
+ } |
+ |
+ _ElementJs get first() { |
+ return _element._firstElementChild; |
+ } |
+ |
+ void forEach(void f(Element element)) { |
+ for (_ElementJs element in _childElements) { |
+ f(element); |
+ } |
+ } |
+ |
+ Collection<Element> filter(bool f(Element element)) { |
+ List<Element> output = <Element>[]; |
+ forEach((Element element) { |
+ if (f(element)) { |
+ output.add(element); |
+ } |
+ }); |
+ return output; |
+ } |
+ |
+ bool every(bool f(Element element)) { |
+ for(Element element in this) { |
+ if (!f(element)) { |
+ return false; |
+ } |
+ }; |
+ return true; |
+ } |
+ |
+ bool some(bool f(Element element)) { |
+ for(Element element in this) { |
+ if (f(element)) { |
+ return true; |
+ } |
+ }; |
+ return false; |
+ } |
+ |
+ bool isEmpty() { |
+ return _element._firstElementChild !== null; |
+ } |
+ |
+ int get length() { |
+ return _childElements.length; |
+ } |
+ |
+ _ElementJs operator [](int index) { |
+ return _childElements[index]; |
+ } |
+ |
+ void operator []=(int index, _ElementJs value) { |
+ _element._replaceChild(value, _childElements.item(index)); |
+ } |
+ |
+ void set length(int newLength) { |
+ // TODO(jacobr): remove children when length is reduced. |
+ throw const UnsupportedOperationException(''); |
+ } |
+ |
+ Element add(_ElementJs value) { |
+ _element._appendChild(value); |
+ return value; |
+ } |
+ |
+ Element addLast(_ElementJs value) => add(value); |
+ |
+ Iterator<Element> iterator() => _toList().iterator(); |
+ |
+ void addAll(Collection<_ElementJs> collection) { |
+ for (_ElementJs element in collection) { |
+ _element._appendChild(element); |
+ } |
+ } |
+ |
+ 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 'Not impl yet. todo(jacobr)'; |
+ } |
+ |
+ void setRange(int start, int length, List from, [int startFrom = 0]) { |
+ throw const NotImplementedException(); |
+ } |
+ |
+ void removeRange(int start, int length) { |
+ throw const NotImplementedException(); |
+ } |
+ |
+ void insertRange(int start, int length, [initialValue = null]) { |
+ throw const NotImplementedException(); |
+ } |
+ |
+ List getRange(int start, int length) { |
+ throw const NotImplementedException(); |
+ } |
+ |
+ int indexOf(Element element, [int start = 0]) { |
+ return _Lists.indexOf(this, element, start, this.length); |
+ } |
+ |
+ int lastIndexOf(Element element, [int start = null]) { |
+ if (start === null) start = length - 1; |
+ return _Lists.lastIndexOf(this, element, start); |
+ } |
+ |
+ void clear() { |
+ // It is unclear if we want to keep non element nodes? |
+ _element.text = ''; |
+ } |
+ |
+ Element removeLast() { |
+ final last = this.last(); |
+ if (last != null) { |
+ _element._removeChild(last); |
+ } |
+ return last; |
+ } |
+ |
+ Element last() { |
+ return _element.lastElementChild; |
+ } |
+} |
+ |
+class ElementAttributeMap implements Map<String, String> { |
+ |
+ final _ElementJs _element; |
+ |
+ ElementAttributeMap._wrap(this._element); |
+ |
+ bool containsValue(String value) { |
+ final attributes = _element.attributes; |
+ for (int i = 0, len = attributes.length; i < len; i++) { |
+ if(value == attributes.item(i).value) { |
+ return true; |
+ } |
+ } |
+ return false; |
+ } |
+ |
+ bool containsKey(String key) { |
+ return _element._hasAttribute(key); |
+ } |
+ |
+ String operator [](String key) { |
+ return _element._getAttribute(key); |
+ } |
+ |
+ void operator []=(String key, String value) { |
+ _element._setAttribute(key, value); |
+ } |
+ |
+ String putIfAbsent(String key, String ifAbsent()) { |
+ if (!containsKey(key)) { |
+ this[key] = ifAbsent(); |
+ } |
+ } |
+ |
+ String remove(String key) { |
+ _element._removeAttribute(key); |
+ } |
+ |
+ void clear() { |
+ final attributes = _element._attributes; |
+ for (int i = attributes.length - 1; i >= 0; i--) { |
+ remove(attributes.item(i).name); |
+ } |
+ } |
+ |
+ void forEach(void f(String key, String value)) { |
+ final attributes = _element.attributes; |
+ for (int i = 0, len = attributes.length; i < len; i++) { |
+ final item = attributes.item(i); |
+ f(item.name, item.value); |
+ } |
+ } |
+ |
+ Collection<String> getKeys() { |
+ // TODO(jacobr): generate a lazy collection instead. |
+ final attributes = _element.attributes; |
+ final keys = new List<String>(attributes.length); |
+ for (int i = 0, len = attributes.length; i < len; i++) { |
+ keys[i] = attributes.item(i).name; |
+ } |
+ return keys; |
+ } |
+ |
+ Collection<String> getValues() { |
+ // TODO(jacobr): generate a lazy collection instead. |
+ final attributes = _element.attributes; |
+ final values = new List<String>(attributes.length); |
+ for (int i = 0, len = attributes.length; i < len; i++) { |
+ values[i] = attributes.item(i).value; |
+ } |
+ return values; |
+ } |
+ |
+ /** |
+ * The number of {key, value} pairs in the map. |
+ */ |
+ int get length() { |
+ return _element._attributes.length; |
+ } |
+ |
+ /** |
+ * Returns true if there is no {key, value} pair in the map. |
+ */ |
+ bool isEmpty() { |
+ return length == 0; |
+ } |
+} |
+ |
+class _SimpleClientRect implements ClientRect { |
+ final num left; |
+ final num top; |
+ final num width; |
+ final num height; |
+ num get right() => left + width; |
+ num get bottom() => top + height; |
+ |
+ const _SimpleClientRect(this.left, this.top, this.width, this.height); |
+ |
+ bool operator ==(ClientRect other) { |
+ return other !== null && left == other.left && top == other.top |
+ && width == other.width && height == other.height; |
+ } |
+ |
+ String toString() => "($left, $top, $width, $height)"; |
+} |
+ |
+// TODO(jacobr): we cannot currently be lazy about calculating the client |
+// rects as we must perform all measurement queries at a safe point to avoid |
+// triggering unneeded layouts. |
+/** |
+ * All your element measurement needs in one place |
+ * @domName none |
+ */ |
+class _ElementRectImpl implements ElementRect { |
+ final ClientRect client; |
+ final ClientRect offset; |
+ final ClientRect scroll; |
+ |
+ // TODO(jacobr): should we move these outside of ElementRect to avoid the |
+ // overhead of computing them every time even though they are rarely used. |
+ final _ClientRectJs _boundingClientRect; |
+ final _ClientRectListJs _clientRects; |
+ |
+ _ElementRectImpl(_ElementJs element) : |
+ client = new _SimpleClientRect(element._clientLeft, |
+ element._clientTop, |
+ element._clientWidth, |
+ element._clientHeight), |
+ offset = new _SimpleClientRect(element._offsetLeft, |
+ element._offsetTop, |
+ element._offsetWidth, |
+ element._offsetHeight), |
+ scroll = new _SimpleClientRect(element._scrollLeft, |
+ element._scrollTop, |
+ element._scrollWidth, |
+ element._scrollHeight), |
+ _boundingClientRect = element.getBoundingClientRect(), |
+ _clientRects = element.getClientRects(); |
+ |
+ _ClientRectJs get bounding() => _boundingClientRect; |
+ |
+ // TODO(jacobr): cleanup. |
+ List<ClientRect> get clientRects() { |
+ final out = new List(_clientRects.length); |
+ for (num i = 0; i < _clientRects.length; i++) { |
+ out[i] = _clientRects.item(i); |
+ } |
+ return out; |
+ } |
+} |
+ |
+final _START_TAG_REGEXP = const RegExp('<(\\w+)'); |
+ |
+class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |
+ |
+ static final _CUSTOM_PARENT_TAG_MAP = const { |
+ 'body' : 'html', |
+ 'head' : 'html', |
+ 'caption' : 'table', |
+ 'td': 'tr', |
+ 'tbody': 'table', |
+ 'colgroup': 'table', |
+ 'col' : 'colgroup', |
+ 'tr' : 'tbody', |
+ 'tbody' : 'table', |
+ 'tfoot' : 'table', |
+ 'thead' : 'table', |
+ 'track' : 'audio', |
+ }; |
+ |
+ /** @domName Document.createElement */ |
+ factory Element.html(String html) { |
+ // TODO(jacobr): this method can be made more robust and performant. |
+ // 1) Cache the dummy parent elements required to use innerHTML rather than |
+ // creating them every call. |
+ // 2) Verify that the html does not contain leading or trailing text nodes. |
+ // 3) Verify that the html does not contain both <head> and <body> tags. |
+ // 4) Detatch the created element from its dummy parent. |
+ String parentTag = 'div'; |
+ String tag; |
+ final match = _START_TAG_REGEXP.firstMatch(html); |
+ if (match !== null) { |
+ tag = match.group(1).toLowerCase(); |
+ if (_CUSTOM_PARENT_TAG_MAP.containsKey(tag)) { |
+ parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
+ } |
+ } |
+ _ElementJs temp = _document._createElement(parentTag); |
+ temp.innerHTML = html; |
+ |
+ if (temp._childElementCount == 1) { |
+ return temp._firstElementChild; |
+ } else if (parentTag == 'html' && temp._childElementCount == 2) { |
+ // Work around for edge case in WebKit and possibly other browsers where |
+ // both body and head elements are created even though the inner html |
+ // only contains a head or body element. |
+ return temp.elements[tag == 'head' ? 0 : 1]; |
+ } else { |
+ throw new IllegalArgumentException('HTML had ${temp._childElementCount} ' + |
+ 'top level elements but 1 expected'); |
+ } |
+ } |
+ |
+ /** @domName Document.createElement */ |
+ factory Element.tag(String tag) { |
+ return _document._createElement(tag); |
+ } |
+ |
+ // TODO(jacobr): caching these may hurt performance. |
+ ElementAttributeMap _elementAttributeMap; |
+ _CssClassSet _cssClassSet; |
+ _DataAttributeMap _dataAttributes; |
+ |
+ // TODO(jacobr): remove these methods and let them be generated automatically |
+ // once dart supports defining fields with the same name in an interface and |
+ // its parent interface. |
+ String get title() native "return this.parentNode.title;"; |
+ void set title(String value) native "this.parentNode.title = value;"; |
+ |
+ /** |
+ * @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute, |
+ * Element.removeAttribute |
+ */ |
+ Map<String, String> get attributes() { |
+ if (_elementAttributeMap === null) { |
+ _elementAttributeMap = new ElementAttributeMap._wrap(this); |
+ } |
+ return _elementAttributeMap; |
+ } |
+ |
+ void set attributes(Map<String, String> value) { |
+ Map<String, String> attributes = this.attributes; |
+ attributes.clear(); |
+ for (String key in value.getKeys()) { |
+ attributes[key] = value[key]; |
+ } |
+ } |
+ |
+ void set elements(Collection<Element> value) { |
+ final elements = this.elements; |
+ elements.clear(); |
+ elements.addAll(value); |
+ } |
+ |
+ /** |
+ * @domName childElementCount, firstElementChild, lastElementChild, |
+ * children, Node.nodes.add |
+ */ |
+ ElementList get elements() => new _ChildrenElementList._wrap(this); |
+ |
+ /** @domName querySelector, Document.getElementById */ |
+ Element query(String selectors) native "return this.querySelector(selectors);"; |
+ |
+ /** |
+ * @domName querySelectorAll, getElementsByClassName, getElementsByTagName, |
+ * getElementsByTagNameNS |
+ */ |
+ ElementList queryAll(String selectors) native "return this.querySelectorAll(selectors);"; |
+ |
+ /** @domName className, classList */ |
+ Set<String> get classes() { |
+ if (_cssClassSet === null) { |
+ _cssClassSet = new _CssClassSet(this); |
+ } |
+ return _cssClassSet; |
+ } |
+ |
+ void set classes(Collection<String> value) { |
+ _CssClassSet classSet = classes; |
+ classSet.clear(); |
+ classSet.addAll(value); |
+ } |
+ |
+ Map<String, String> get dataAttributes() { |
+ if (_dataAttributes === null) { |
+ _dataAttributes = new _DataAttributeMap(attributes); |
+ } |
+ return _dataAttributes; |
+ } |
+ |
+ void set dataAttributes(Map<String, String> value) { |
+ Map<String, String> dataAttributes = this.dataAttributes; |
+ dataAttributes.clear(); |
+ for (String key in value.getKeys()) { |
+ dataAttributes[key] = value[key]; |
+ } |
+ } |
+ |
+ bool matchesSelector(String selectors) native "return this.webkitMatchesSelector(selectors)"; |
+ |
+ /** |
+ * @domName getClientRects, getBoundingClientRect, clientHeight, clientWidth, |
+ * clientTop, clientLeft, offsetHeight, offsetWidth, offsetTop, offsetLeft, |
+ * scrollHeight, scrollWidth, scrollTop, scrollLeft |
+ */ |
+ Future<ElementRect> get rect() { |
+ return _createMeasurementFuture( |
+ () => new _ElementRectImpl(this), |
+ new Completer<ElementRect>()); |
+ } |
+ |
+ /** @domName Window.getComputedStyle */ |
+ Future<CSSStyleDeclaration> get computedStyle() { |
+ // TODO(jacobr): last param should be null, see b/5045788 |
+ return getComputedStyle(''); |
+ } |
+ |
+ /** @domName Window.getComputedStyle */ |
+ Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) { |
+ return _createMeasurementFuture(() => |
+ _window._getComputedStyle(this, pseudoElement), |
+ new Completer<CSSStyleDeclaration>()); |
+ } |
+ |
+ _ElementJs clone(bool deep) native; |
+$!MEMBERS |
+} |