Chromium Code Reviews| Index: tools/dom/templates/html/impl/impl_Element.darttemplate |
| diff --git a/tools/dom/templates/html/impl/impl_Element.darttemplate b/tools/dom/templates/html/impl/impl_Element.darttemplate |
| index cbea8c1aed4623ebbe6782df2bbf15f8827e0620..528471bacf24381207c685653ac8c3612a08ab18 100644 |
| --- a/tools/dom/templates/html/impl/impl_Element.darttemplate |
| +++ b/tools/dom/templates/html/impl/impl_Element.darttemplate |
| @@ -309,20 +309,30 @@ $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |
| /** |
| * Creates an HTML element from a valid fragment of HTML. |
| * |
| - * The [html] fragment must represent valid HTML with a single element root, |
| - * which will be parsed and returned. |
| + * var element = new Element.html('<div class="foo">content</div>'); |
| * |
| - * Important: the contents of [html] should not contain any user-supplied |
| - * data. Without strict data validation it is impossible to prevent script |
| - * injection exploits. |
| + * The HTML fragment should contain only one single root element, any |
| + * leading or trailing text nodes will be removed. |
| * |
| - * It is instead recommended that elements be constructed via [Element.tag] |
| - * and text be added via [text]. |
| + * The HTML fragment is parsed as if it occurred within the context of a |
| + * `<body>` tag, this means that special elements such as `<caption>` which |
| + * must be parsed within the scope of a `<table>` element will be dropped. Use |
| + * [createFragment] to parse contextual HTML fragments. |
| + * |
| + * Unless a validator is provided this will perform the default validation |
| + * and remove all scriptable elements and attributes. |
| + * |
| + * See also: |
| + * |
| + * * [NodeValidator] |
| * |
| - * var element = new Element.html('<div class="foo">content</div>'); |
| */ |
| - factory $CLASSNAME.html(String html) => |
| - _$(CLASSNAME)FactoryProvider.createElement_html(html); |
| + factory Element.html(String html, |
| + {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| + var fragment = document.body.createFragment(html, validator: validator, |
| + treeSanitizer: treeSanitizer); |
| + return fragment.nodes.where((e) => e is Element).single; |
|
Jennifer Messerly
2013/08/17 06:07:07
how's the performance of this? I dunno if it matte
blois
2013/08/19 22:02:09
Done.
|
| + } |
| /** |
| * Creates the HTML element specified by the tag name. |
| @@ -1170,121 +1180,94 @@ $endif |
| Point p = Element._offsetToHelper(parentOffset, parent); |
| return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); |
| } |
| + |
| + /** |
| + * Create a DocumentFragment from the HTML fragment and ensure that it follows |
| + * the sanitization rules specified by the validator or treeSanitizer. |
| + * |
| + * If the default validation behavior is too restrictive then a new |
| + * NodeValidator should be created, either extending or wrapping a default |
| + * validator and overriding the validation APIs. |
| + * |
| + * The treeSanitizer is used to walk the generated node tree and sanitize it. |
| + * A custom treeSanitizer can also be provided to perform special validation |
| + * rules but since the API is more complex to implement this is discouraged. |
| + * |
| + * The returned tree is guaranteed to only contain nodes and attributes which |
| + * are allowed by the provided validator. |
| + * |
| + * See also: |
| + * |
| + * * [NodeValidator] |
| + * * [NodeTreeSanitizer] |
| + */ |
| + DocumentFragment createFragment(String html, |
| + {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| + if (treeSanitizer == null) { |
| + if (validator == null) { |
| + validator = new NodeValidatorBuilder.common(); |
|
Jennifer Messerly
2013/08/17 06:07:07
I wonder about performance here, in particular if
blois
2013/08/19 22:02:09
Added caching.
|
| + } |
| + treeSanitizer = new NodeTreeSanitizer(validator); |
| + } else if (validator != null) { |
| + throw new ArgumentError( |
| + 'validator can only be passed if treeSanitizer is null'); |
| + } |
| + return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer); |
|
Jennifer Messerly
2013/08/17 06:07:07
out of curiosity, why is this method on _ElementFa
blois
2013/08/19 22:02:09
FactoryProviders are a legacy thing that we've bee
Jennifer Messerly
2013/08/26 22:00:31
yay :D
|
| + } |
| + void set innerHtml(String html) { |
| + this.setInnerHtml(html); |
| + } |
| + void setInnerHtml(String html, |
| + {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| + text = null; |
| + append(createFragment( |
| + html, validator: validator, treeSanitizer: treeSanitizer)); |
| + } |
| + String get innerHtml => $dom_innerHtml; |
|
Jennifer Messerly
2013/08/17 06:07:07
on an unrelated note: is all this $dom_ stuff goin
blois
2013/08/19 22:02:09
That's the plan- been having hiccups with benchmar
|
| + |
| $!MEMBERS |
| } |
| -final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| + |
| class _ElementFactoryProvider { |
| - static const _CUSTOM_PARENT_TAG_MAP = const { |
| - 'body' : 'html', |
| - 'head' : 'html', |
| - 'caption' : 'table', |
| - 'td': 'tr', |
| - 'th': 'tr', |
| - 'colgroup': 'table', |
| - 'col' : 'colgroup', |
| - 'tr' : 'tbody', |
| - 'tbody' : 'table', |
| - 'tfoot' : 'table', |
| - 'thead' : 'table', |
| - 'track' : 'audio', |
| - }; |
| - @DomName('Document.createElement') |
| - static Element createElement_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 (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { |
| - return _createTableForIE(html, tag); |
| - } |
| - parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |
| - if (parentTag == null) parentTag = 'div'; |
| - } |
| + static HtmlDocument _parseDocument; |
| - final temp = new Element.tag(parentTag); |
| - temp.innerHtml = html; |
| + static DocumentFragment _parseHtml(Element context, String html, |
| + NodeTreeSanitizer treeSanitizer) { |
| - Element element; |
| - if (temp.children.length == 1) { |
| - element = temp.children[0]; |
| - } else if (parentTag == 'html' && temp.children.length == 2) { |
| - // In html5 the root <html> tag will always have a <body> and a <head>, |
| - // even though the inner html only contains one of them. |
| - element = temp.children[tag == 'head' ? 0 : 1]; |
| + if (_parseDocument == null) { |
| + _parseDocument = document.implementation.createHtmlDocument(''); |
| + } |
| + var contextElement; |
| + if (context == null || context is BodyElement) { |
|
Jennifer Messerly
2013/08/17 06:07:07
curious about the special case here for "body". co
blois
2013/08/19 22:02:09
It's defaulting to body and just re-using that ele
|
| + contextElement = _parseDocument.body; |
| } else { |
| - _singleNode(temp.children); |
| + contextElement = _parseDocument.$dom_createElement(context.tagName); |
| + _parseDocument.body.append(contextElement); |
| } |
| - element.remove(); |
| - return element; |
| - } |
| + if (Range.supportsCreateContextualFragment) { |
| + var range = _parseDocument.$dom_createRange(); |
| + range.selectNodeContents(contextElement); |
| + var fragment = range.createContextualFragment(html); |
| - /** |
| - * IE table elements don't support innerHTML (even in standards mode). |
| - * Instead we use a div and inject the table element in the innerHtml string. |
| - * This technique works on other browsers too, but it's probably slower, |
| - * so we only use it when running on IE. |
| - * |
| - * See also innerHTML: |
| - * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx> |
| - * and Building Tables Dynamically: |
| - * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>. |
| - */ |
| - static Element _createTableForIE(String html, String tag) { |
| - var div = new Element.tag('div'); |
| - div.innerHtml = '<table>$html</table>'; |
| - var table = _singleNode(div.children); |
| - Element element; |
| - switch (tag) { |
| - case 'td': |
| - case 'th': |
| - TableRowElement row = _singleNode(table.rows); |
| - element = _singleNode(row.cells); |
| - break; |
| - case 'tr': |
| - element = _singleNode(table.rows); |
| - break; |
| - case 'tbody': |
| - element = _singleNode(table.tBodies); |
| - break; |
| - case 'thead': |
| - element = table.tHead; |
| - break; |
| - case 'tfoot': |
| - element = table.tFoot; |
| - break; |
| - case 'caption': |
| - element = table.caption; |
| - break; |
| - case 'colgroup': |
| - element = _getColgroup(table); |
| - break; |
| - case 'col': |
| - element = _singleNode(_getColgroup(table).children); |
| - break; |
| - } |
| - element.remove(); |
| - return element; |
| - } |
| + if (contextElement != _parseDocument.body) { |
| + contextElement.remove(); |
| + } |
| - static TableColElement _getColgroup(TableElement table) { |
| - // TODO(jmesserly): is there a better way to do this? |
| - return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP') |
| - .toList()); |
| - } |
| + treeSanitizer.sanitizeTree(fragment); |
| + return fragment; |
| + } else { |
| + contextElement.$dom_innerHtml = html; |
| - static Node _singleNode(List<Node> list) { |
| - if (list.length == 1) return list[0]; |
| - throw new ArgumentError('HTML had ${list.length} ' |
| - 'top level elements but 1 expected'); |
| + treeSanitizer.sanitizeTree(contextElement); |
| + |
| + var fragment = new DocumentFragment(); |
| + while (contextElement.firstChild != null) { |
| + fragment.append(contextElement.firstChild); |
| + } |
| + return fragment; |
| + } |
| } |
| @DomName('Document.createElement') |