| 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 1698edcd7f7dec3834be8dd2f1275440bd9d5f09..a18094f0b0f6a27f0768c763005cb2b19e74ee0a 100644
|
| --- a/tools/dom/templates/html/impl/impl_Element.darttemplate
|
| +++ b/tools/dom/templates/html/impl/impl_Element.darttemplate
|
| @@ -309,20 +309,34 @@ $(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);
|
| +
|
| + if (fragment._firstElementChild != fragment._lastElementChild) {
|
| + throw new StateError("More than one element root");
|
| + }
|
| + return fragment._firstElementChild;
|
| + }
|
|
|
| /**
|
| * Creates the HTML element specified by the tag name.
|
| @@ -1171,136 +1185,135 @@ $endif
|
| return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
|
| }
|
|
|
| -$if DART2JS
|
| - @JSName('innerHTML')
|
| - @DomName('HTMLElement.innerHTML')
|
| - String get innerHtml => JS('String', '#.innerHTML', this);
|
| -
|
| - void set innerHtml(String value) {
|
| - JS('', '#.innerHTML = #', this, value);
|
| - // Polyfill relies on mutation observers for upgrading, but we want it
|
| - // immediate.
|
| - Platform.upgradeCustomElements(this);
|
| - }
|
| -$endif
|
| -
|
| -$!MEMBERS
|
| -}
|
| + static HtmlDocument _parseDocument;
|
| + static NodeValidatorBuilder _defaultValidator;
|
| + static _ValidatingTreeSanitizer _defaultSanitizer;
|
|
|
| -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);
|
| + /**
|
| + * 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) {
|
| + if (_defaultValidator == null) {
|
| + _defaultValidator = new NodeValidatorBuilder.common();
|
| + }
|
| + validator = _defaultValidator;
|
| + }
|
| + if (_defaultSanitizer == null) {
|
| + _defaultSanitizer = new _ValidatingTreeSanitizer(validator);
|
| + } else {
|
| + _defaultSanitizer.validator = validator;
|
| }
|
| - parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
|
| - if (parentTag == null) parentTag = 'div';
|
| + treeSanitizer = _defaultSanitizer;
|
| + } else if (validator != null) {
|
| + throw new ArgumentError(
|
| + 'validator can only be passed if treeSanitizer is null');
|
| }
|
|
|
| - final temp = new Element.tag(parentTag);
|
| - temp.innerHtml = html;
|
| -
|
| - 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 (this is BodyElement) {
|
| + contextElement = _parseDocument.body;
|
| } else {
|
| - _singleNode(temp.children);
|
| + contextElement = _parseDocument.$dom_createElement(tagName);
|
| + _parseDocument.body.append(contextElement);
|
| }
|
| - element.remove();
|
| - return element;
|
| + var fragment;
|
| + if (Range.supportsCreateContextualFragment) {
|
| + var range = _parseDocument.$dom_createRange();
|
| + range.selectNodeContents(contextElement);
|
| + fragment = range.createContextualFragment(html);
|
| + } else {
|
| + contextElement._innerHtml = html;
|
| +
|
| + fragment = _parseDocument.createDocumentFragment();
|
| + while (contextElement.firstChild != null) {
|
| + fragment.append(contextElement.firstChild);
|
| + }
|
| + }
|
| + if (contextElement != _parseDocument.body) {
|
| + contextElement.remove();
|
| + }
|
| +
|
| + treeSanitizer.sanitizeTree(fragment);
|
| + return fragment;
|
| }
|
|
|
| /**
|
| - * 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>.
|
| + * Parses the HTML fragment and sets it as the contents of this element.
|
| + *
|
| + * This uses the default sanitization behavior to sanitize the HTML fragment,
|
| + * use [setInnerHtml] to override the default behavior.
|
| */
|
| - 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;
|
| + void set innerHtml(String html) {
|
| + this.setInnerHtml(html);
|
| }
|
|
|
| - 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());
|
| + /**
|
| + * Parses the HTML fragment and sets it as the contents of this element.
|
| + * This ensures that the generated content 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 resulting tree is guaranteed to only contain nodes and attributes which
|
| + * are allowed by the provided validator.
|
| + *
|
| + * See also:
|
| + *
|
| + * * [NodeValidator]
|
| + * * [NodeTreeSanitizer]
|
| + */
|
| + void setInnerHtml(String html,
|
| + {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
|
| + text = null;
|
| + append(createFragment(
|
| + html, validator: validator, treeSanitizer: treeSanitizer));
|
| }
|
| + String get innerHtml => _innerHtml;
|
|
|
| - 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');
|
| + /**
|
| + * For use while transitioning to the safe [innerHtml] or [setInnerHtml].
|
| + * Unsafe because it opens the app to cross-site scripting vulnerabilities.
|
| + */
|
| + @deprecated
|
| + void set unsafeInnerHtml(String html) {
|
| + _innerHtml = html;
|
| }
|
|
|
| +$!MEMBERS
|
| +}
|
| +
|
| +
|
| +class _ElementFactoryProvider {
|
| +
|
| @DomName('Document.createElement')
|
| $if DART2JS
|
| // Optimization to improve performance until the dart2js compiler inlines this
|
|
|