c9c8d17cfd
Review URL: https://chromiumcodereview.appspot.com//10826146 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@10245 260f80e4-7a28-3924-810f-c04153c831b5
805 lines
21 KiB
Plaintext
805 lines
21 KiB
Plaintext
// 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 _ElementImpl _element;
|
|
final _HTMLCollectionImpl _childElements;
|
|
|
|
_ChildrenElementList._wrap(_ElementImpl element)
|
|
: _childElements = element.$dom_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;
|
|
}
|
|
|
|
_ElementImpl get first() {
|
|
return _element.$dom_firstElementChild;
|
|
}
|
|
|
|
void forEach(void f(Element element)) {
|
|
for (_ElementImpl element in _childElements) {
|
|
f(element);
|
|
}
|
|
}
|
|
|
|
ElementList filter(bool f(Element element)) {
|
|
final output = <Element>[];
|
|
forEach((Element element) {
|
|
if (f(element)) {
|
|
output.add(element);
|
|
}
|
|
});
|
|
return new _FrozenElementList._wrap(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;
|
|
}
|
|
|
|
Collection map(f(Element element)) {
|
|
final out = [];
|
|
for (Element el in this) {
|
|
out.add(f(el));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
bool isEmpty() {
|
|
return _element.$dom_firstElementChild == null;
|
|
}
|
|
|
|
int get length() {
|
|
return _childElements.length;
|
|
}
|
|
|
|
_ElementImpl operator [](int index) {
|
|
return _childElements[index];
|
|
}
|
|
|
|
void operator []=(int index, _ElementImpl value) {
|
|
_element.$dom_replaceChild(value, _childElements[index]);
|
|
}
|
|
|
|
void set length(int newLength) {
|
|
// TODO(jacobr): remove children when length is reduced.
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
Element add(_ElementImpl value) {
|
|
_element.$dom_appendChild(value);
|
|
return value;
|
|
}
|
|
|
|
Element addLast(_ElementImpl value) => add(value);
|
|
|
|
Iterator<Element> iterator() => _toList().iterator();
|
|
|
|
void addAll(Collection<Element> collection) {
|
|
for (_ElementImpl element in collection) {
|
|
_element.$dom_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 rangeLength, List from, [int startFrom = 0]) {
|
|
throw const NotImplementedException();
|
|
}
|
|
|
|
void removeRange(int start, int rangeLength) {
|
|
throw const NotImplementedException();
|
|
}
|
|
|
|
void insertRange(int start, int rangeLength, [initialValue = null]) {
|
|
throw const NotImplementedException();
|
|
}
|
|
|
|
List getRange(int start, int rangeLength) =>
|
|
new _FrozenElementList._wrap(_Lists.getRange(this, start, rangeLength,
|
|
<Element>[]));
|
|
|
|
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 result = this.last();
|
|
if (result != null) {
|
|
_element.$dom_removeChild(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Element last() {
|
|
return _element.$dom_lastElementChild;
|
|
}
|
|
}
|
|
|
|
// TODO(jacobr): this is an inefficient implementation but it is hard to see
|
|
// a better option given that we cannot quite force NodeList to be an
|
|
// ElementList as there are valid cases where a NodeList JavaScript object
|
|
// contains Node objects that are not Elements.
|
|
class _FrozenElementList implements ElementList {
|
|
final List<Node> _nodeList;
|
|
|
|
_FrozenElementList._wrap(this._nodeList);
|
|
|
|
Element get first() {
|
|
return _nodeList[0];
|
|
}
|
|
|
|
void forEach(void f(Element element)) {
|
|
for (Element el in this) {
|
|
f(el);
|
|
}
|
|
}
|
|
|
|
Collection map(f(Element element)) {
|
|
final out = [];
|
|
for (Element el in this) {
|
|
out.add(f(el));
|
|
}
|
|
return out;
|
|
}
|
|
|
|
ElementList filter(bool f(Element element)) {
|
|
final out = new _ElementList([]);
|
|
for (Element el in this) {
|
|
if (f(el)) out.add(el);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
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() => _nodeList.isEmpty();
|
|
|
|
int get length() => _nodeList.length;
|
|
|
|
Element operator [](int index) => _nodeList[index];
|
|
|
|
void operator []=(int index, Element value) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void set length(int newLength) {
|
|
_nodeList.length = newLength;
|
|
}
|
|
|
|
void add(Element value) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void addLast(Element value) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
Iterator<Element> iterator() => new _FrozenElementListIterator(this);
|
|
|
|
void addAll(Collection<Element> collection) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void sort(int compare(Element a, Element b)) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void setRange(int start, int rangeLength, List from, [int startFrom = 0]) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void removeRange(int start, int rangeLength) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
void insertRange(int start, int rangeLength, [initialValue = null]) {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
ElementList getRange(int start, int rangeLength) =>
|
|
new _FrozenElementList._wrap(_nodeList.getRange(start, rangeLength));
|
|
|
|
int indexOf(Element element, [int start = 0]) =>
|
|
_nodeList.indexOf(element, start);
|
|
|
|
int lastIndexOf(Element element, [int start = null]) =>
|
|
_nodeList.lastIndexOf(element, start);
|
|
|
|
void clear() {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
Element removeLast() {
|
|
throw const UnsupportedOperationException('');
|
|
}
|
|
|
|
Element last() => _nodeList.last();
|
|
}
|
|
|
|
class _FrozenElementListIterator implements Iterator<Element> {
|
|
final _FrozenElementList _list;
|
|
int _index = 0;
|
|
|
|
_FrozenElementListIterator(this._list);
|
|
|
|
/**
|
|
* Gets the next element in the iteration. Throws a
|
|
* [NoMoreElementsException] if no element is left.
|
|
*/
|
|
Element next() {
|
|
if (!hasNext()) {
|
|
throw const NoMoreElementsException();
|
|
}
|
|
|
|
return _list[_index++];
|
|
}
|
|
|
|
/**
|
|
* Returns whether the [Iterator] has elements left.
|
|
*/
|
|
bool hasNext() => _index < _list.length;
|
|
}
|
|
|
|
class _ElementList extends _ListWrapper<Element> implements ElementList {
|
|
_ElementList(List<Element> list) : super(list);
|
|
|
|
ElementList filter(bool f(Element element)) =>
|
|
new _ElementList(super.filter(f));
|
|
|
|
ElementList getRange(int start, int rangeLength) =>
|
|
new _ElementList(super.getRange(start, rangeLength));
|
|
}
|
|
|
|
class _ElementAttributeMap implements AttributeMap {
|
|
|
|
final _ElementImpl _element;
|
|
|
|
_ElementAttributeMap(this._element);
|
|
|
|
bool containsValue(String value) {
|
|
final attributes = _element.$dom_attributes;
|
|
for (int i = 0, len = attributes.length; i < len; i++) {
|
|
if(value == attributes[i].value) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool containsKey(String key) {
|
|
return _element.$dom_hasAttribute(key);
|
|
}
|
|
|
|
String operator [](String key) {
|
|
return _element.$dom_getAttribute(key);
|
|
}
|
|
|
|
void operator []=(String key, value) {
|
|
_element.$dom_setAttribute(key, '$value');
|
|
}
|
|
|
|
String putIfAbsent(String key, String ifAbsent()) {
|
|
if (!containsKey(key)) {
|
|
this[key] = ifAbsent();
|
|
}
|
|
return this[key];
|
|
}
|
|
|
|
String remove(String key) {
|
|
String value = _element.$dom_getAttribute(key);
|
|
_element.$dom_removeAttribute(key);
|
|
return value;
|
|
}
|
|
|
|
void clear() {
|
|
final attributes = _element.$dom_attributes;
|
|
for (int i = attributes.length - 1; i >= 0; i--) {
|
|
remove(attributes[i].name);
|
|
}
|
|
}
|
|
|
|
void forEach(void f(String key, String value)) {
|
|
final attributes = _element.$dom_attributes;
|
|
for (int i = 0, len = attributes.length; i < len; i++) {
|
|
final item = attributes[i];
|
|
f(item.name, item.value);
|
|
}
|
|
}
|
|
|
|
Collection<String> getKeys() {
|
|
// TODO(jacobr): generate a lazy collection instead.
|
|
final attributes = _element.$dom_attributes;
|
|
final keys = new List<String>(attributes.length);
|
|
for (int i = 0, len = attributes.length; i < len; i++) {
|
|
keys[i] = attributes[i].name;
|
|
}
|
|
return keys;
|
|
}
|
|
|
|
Collection<String> getValues() {
|
|
// TODO(jacobr): generate a lazy collection instead.
|
|
final attributes = _element.$dom_attributes;
|
|
final values = new List<String>(attributes.length);
|
|
for (int i = 0, len = attributes.length; i < len; i++) {
|
|
values[i] = attributes[i].value;
|
|
}
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* The number of {key, value} pairs in the map.
|
|
*/
|
|
int get length() {
|
|
return _element.$dom_attributes.length;
|
|
}
|
|
|
|
/**
|
|
* Returns true if there is no {key, value} pair in the map.
|
|
*/
|
|
bool isEmpty() {
|
|
return length == 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Provides a Map abstraction on top of data-* attributes, similar to the
|
|
* dataSet in the old DOM.
|
|
*/
|
|
class _DataAttributeMap implements AttributeMap {
|
|
|
|
final Map<String, String> $dom_attributes;
|
|
|
|
_DataAttributeMap(this.$dom_attributes);
|
|
|
|
// interface Map
|
|
|
|
// TODO: Use lazy iterator when it is available on Map.
|
|
bool containsValue(String value) => getValues().some((v) => v == value);
|
|
|
|
bool containsKey(String key) => $dom_attributes.containsKey(_attr(key));
|
|
|
|
String operator [](String key) => $dom_attributes[_attr(key)];
|
|
|
|
void operator []=(String key, value) {
|
|
$dom_attributes[_attr(key)] = '$value';
|
|
}
|
|
|
|
String putIfAbsent(String key, String ifAbsent()) =>
|
|
$dom_attributes.putIfAbsent(_attr(key), ifAbsent);
|
|
|
|
String remove(String key) => $dom_attributes.remove(_attr(key));
|
|
|
|
void clear() {
|
|
// Needs to operate on a snapshot since we are mutating the collection.
|
|
for (String key in getKeys()) {
|
|
remove(key);
|
|
}
|
|
}
|
|
|
|
void forEach(void f(String key, String value)) {
|
|
$dom_attributes.forEach((String key, String value) {
|
|
if (_matches(key)) {
|
|
f(_strip(key), value);
|
|
}
|
|
});
|
|
}
|
|
|
|
Collection<String> getKeys() {
|
|
final keys = new List<String>();
|
|
$dom_attributes.forEach((String key, String value) {
|
|
if (_matches(key)) {
|
|
keys.add(_strip(key));
|
|
}
|
|
});
|
|
return keys;
|
|
}
|
|
|
|
Collection<String> getValues() {
|
|
final values = new List<String>();
|
|
$dom_attributes.forEach((String key, String value) {
|
|
if (_matches(key)) {
|
|
values.add(value);
|
|
}
|
|
});
|
|
return values;
|
|
}
|
|
|
|
int get length() => getKeys().length;
|
|
|
|
// TODO: Use lazy iterator when it is available on Map.
|
|
bool isEmpty() => length == 0;
|
|
|
|
// Helpers.
|
|
String _attr(String key) => 'data-$key';
|
|
bool _matches(String key) => key.startsWith('data-');
|
|
String _strip(String key) => key.substring(5);
|
|
}
|
|
|
|
class _CssClassSet implements Set<String> {
|
|
|
|
final _ElementImpl _element;
|
|
|
|
_CssClassSet(this._element);
|
|
|
|
String toString() => _formatSet(_read());
|
|
|
|
// interface Iterable - BEGIN
|
|
Iterator<String> iterator() => _read().iterator();
|
|
// interface Iterable - END
|
|
|
|
// interface Collection - BEGIN
|
|
void forEach(void f(String element)) {
|
|
_read().forEach(f);
|
|
}
|
|
|
|
Collection map(f(String element)) => _read().map(f);
|
|
|
|
Collection<String> filter(bool f(String element)) => _read().filter(f);
|
|
|
|
bool every(bool f(String element)) => _read().every(f);
|
|
|
|
bool some(bool f(String element)) => _read().some(f);
|
|
|
|
bool isEmpty() => _read().isEmpty();
|
|
|
|
int get length() =>_read().length;
|
|
|
|
// interface Collection - END
|
|
|
|
// interface Set - BEGIN
|
|
bool contains(String value) => _read().contains(value);
|
|
|
|
void add(String value) {
|
|
// TODO - figure out if we need to do any validation here
|
|
// or if the browser natively does enough
|
|
_modify((s) => s.add(value));
|
|
}
|
|
|
|
bool remove(String value) {
|
|
Set<String> s = _read();
|
|
bool result = s.remove(value);
|
|
_write(s);
|
|
return result;
|
|
}
|
|
|
|
void addAll(Collection<String> collection) {
|
|
// TODO - see comment above about validation
|
|
_modify((s) => s.addAll(collection));
|
|
}
|
|
|
|
void removeAll(Collection<String> collection) {
|
|
_modify((s) => s.removeAll(collection));
|
|
}
|
|
|
|
bool isSubsetOf(Collection<String> collection) =>
|
|
_read().isSubsetOf(collection);
|
|
|
|
bool containsAll(Collection<String> collection) =>
|
|
_read().containsAll(collection);
|
|
|
|
Set<String> intersection(Collection<String> other) =>
|
|
_read().intersection(other);
|
|
|
|
void clear() {
|
|
_modify((s) => s.clear());
|
|
}
|
|
// interface Set - END
|
|
|
|
/**
|
|
* Helper method used to modify the set of css classes on this element.
|
|
*
|
|
* f - callback with:
|
|
* s - a Set of all the css class name currently on this element.
|
|
*
|
|
* After f returns, the modified set is written to the
|
|
* className property of this element.
|
|
*/
|
|
void _modify( f(Set<String> s)) {
|
|
Set<String> s = _read();
|
|
f(s);
|
|
_write(s);
|
|
}
|
|
|
|
/**
|
|
* Read the class names from the Element class property,
|
|
* and put them into a set (duplicates are discarded).
|
|
*/
|
|
Set<String> _read() {
|
|
// TODO(mattsh) simplify this once split can take regex.
|
|
Set<String> s = new Set<String>();
|
|
for (String name in _classname().split(' ')) {
|
|
String trimmed = name.trim();
|
|
if (!trimmed.isEmpty()) {
|
|
s.add(trimmed);
|
|
}
|
|
}
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* Read the class names as a space-separated string. This is meant to be
|
|
* overridden by subclasses.
|
|
*/
|
|
String _classname() => _element.$dom_className;
|
|
|
|
/**
|
|
* Join all the elements of a set into one string and write
|
|
* back to the element.
|
|
*/
|
|
void _write(Set s) {
|
|
_element.$dom_className = _formatSet(s);
|
|
}
|
|
|
|
String _formatSet(Set<String> s) {
|
|
// TODO(mattsh) should be able to pass Set to String.joins http:/b/5398605
|
|
List list = new List.from(s);
|
|
return Strings.join(list, ' ');
|
|
}
|
|
}
|
|
|
|
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 _ClientRectImpl _boundingClientRect;
|
|
final _ClientRectListImpl _clientRects;
|
|
|
|
_ElementRectImpl(_ElementImpl element) :
|
|
client = new _SimpleClientRect(element.$dom_clientLeft,
|
|
element.$dom_clientTop,
|
|
element.$dom_clientWidth,
|
|
element.$dom_clientHeight),
|
|
offset = new _SimpleClientRect(element.$dom_offsetLeft,
|
|
element.$dom_offsetTop,
|
|
element.$dom_offsetWidth,
|
|
element.$dom_offsetHeight),
|
|
scroll = new _SimpleClientRect(element.$dom_scrollLeft,
|
|
element.$dom_scrollTop,
|
|
element.$dom_scrollWidth,
|
|
element.$dom_scrollHeight),
|
|
_boundingClientRect = element.$dom_getBoundingClientRect(),
|
|
_clientRects = element.$dom_getClientRects();
|
|
|
|
_ClientRectImpl 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;
|
|
}
|
|
}
|
|
|
|
class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
|
|
|
|
/**
|
|
* @domName Element.hasAttribute, Element.getAttribute, Element.setAttribute,
|
|
* Element.removeAttribute
|
|
*/
|
|
_ElementAttributeMap get attributes() => new _ElementAttributeMap(this);
|
|
|
|
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);
|
|
}
|
|
|
|
ElementList get elements() => new _ChildrenElementList._wrap(this);
|
|
|
|
_ElementImpl query(String selectors) => $dom_querySelector(selectors);
|
|
|
|
List<Element> queryAll(String selectors) =>
|
|
new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
|
|
|
|
_CssClassSet get classes() => new _CssClassSet(this);
|
|
|
|
void set classes(Collection<String> value) {
|
|
_CssClassSet classSet = classes;
|
|
classSet.clear();
|
|
classSet.addAll(value);
|
|
}
|
|
|
|
Map<String, String> get dataAttributes() =>
|
|
new _DataAttributeMap(attributes);
|
|
|
|
void set dataAttributes(Map<String, String> value) {
|
|
final dataAttributes = this.dataAttributes;
|
|
dataAttributes.clear();
|
|
for (String key in value.getKeys()) {
|
|
dataAttributes[key] = value[key];
|
|
}
|
|
}
|
|
|
|
Future<ElementRect> get rect() {
|
|
return _createMeasurementFuture(
|
|
() => new _ElementRectImpl(this),
|
|
new Completer<ElementRect>());
|
|
}
|
|
|
|
Future<CSSStyleDeclaration> get computedStyle() {
|
|
// TODO(jacobr): last param should be null, see b/5045788
|
|
return getComputedStyle('');
|
|
}
|
|
|
|
Future<CSSStyleDeclaration> getComputedStyle(String pseudoElement) {
|
|
return _createMeasurementFuture(
|
|
() => _window.$dom_getComputedStyle(this, pseudoElement),
|
|
new Completer<CSSStyleDeclaration>());
|
|
}
|
|
|
|
// Hooks to support custom WebComponents.
|
|
var xtag;
|
|
|
|
$if DARTIUM
|
|
noSuchMethod(String name, List args) {
|
|
if (dynamicUnknownElementDispatcher == null) {
|
|
throw new NoSuchMethodException(this, name, args);
|
|
} else {
|
|
return dynamicUnknownElementDispatcher(this, name, args);
|
|
}
|
|
}
|
|
$else
|
|
// TODO(vsm): Implement noSuchMethod or similar for dart2js.
|
|
$endif
|
|
|
|
$!MEMBERS
|
|
}
|
|
|
|
// Temporary dispatch hook to support WebComponents.
|
|
Function dynamicUnknownElementDispatcher;
|
|
|
|
final _START_TAG_REGEXP = const RegExp('<(\\w+)');
|
|
class _ElementFactoryProvider {
|
|
static final _CUSTOM_PARENT_TAG_MAP = const {
|
|
'body' : 'html',
|
|
'head' : 'html',
|
|
'caption' : 'table',
|
|
'td': 'tr',
|
|
'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];
|
|
}
|
|
}
|
|
final _ElementImpl temp = new Element.tag(parentTag);
|
|
temp.innerHTML = html;
|
|
|
|
Element element;
|
|
if (temp.elements.length == 1) {
|
|
element = temp.elements.first;
|
|
} else if (parentTag == 'html' && temp.elements.length == 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.
|
|
element = temp.elements[tag == 'head' ? 0 : 1];
|
|
} else {
|
|
throw new IllegalArgumentException('HTML had ${temp.elements.length} '
|
|
'top level elements but 1 expected');
|
|
}
|
|
element.remove();
|
|
return element;
|
|
}
|
|
|
|
/** @domName Document.createElement */
|
|
$if DART2JS
|
|
// Optimization to improve performance until the dart2js compiler inlines this
|
|
// method.
|
|
factory Element.tag(String tag) native "return document.createElement(tag)";
|
|
$else
|
|
factory Element.tag(String tag) => _document.$dom_createElement(tag);
|
|
$endif
|
|
}
|