Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(484)

Unified Diff: tools/dom/templates/html/impl/impl_Element.darttemplate

Issue 16374007: First rev of Safe DOM (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixing up recent test additions. Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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')

Powered by Google App Engine
This is Rietveld 408576698