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

Side by Side 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: Created 7 years, 6 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 unified diff | Download patch | Annotate | Revision Log
OLDNEW
1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a 2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file. 3 // BSD-style license that can be found in the LICENSE file.
4 4
5 part of $LIBRARYNAME; 5 part of $LIBRARYNAME;
6 6
7 class _ChildrenElementList extends ListBase<Element> { 7 class _ChildrenElementList extends ListBase<Element> {
8 // Raw Element. 8 // Raw Element.
9 final Element _element; 9 final Element _element;
10 final HtmlCollection _childElements; 10 final HtmlCollection _childElements;
(...skipping 205 matching lines...) Expand 10 before | Expand all | Expand 10 after
216 } 216 }
217 217
218 /** 218 /**
219 * An abstract class, which all HTML elements extend. 219 * An abstract class, which all HTML elements extend.
220 */ 220 */
221 $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { 221 $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
222 222
223 /** 223 /**
224 * Creates an HTML element from a valid fragment of HTML. 224 * Creates an HTML element from a valid fragment of HTML.
225 * 225 *
226 * The [html] fragment must represent valid HTML with a single element root, 226 * var element = new Element.html('<div class="foo">content</div>');
227 * which will be parsed and returned.
228 * 227 *
229 * Important: the contents of [html] should not contain any user-supplied 228 * The HTML fragment should contain only one single root element, any
230 * data. Without strict data validation it is impossible to prevent script 229 * leading or trailing text nodes will be removed.
231 * injection exploits.
232 * 230 *
233 * It is instead recommended that elements be constructed via [Element.tag] 231 * The HTML fragment is parsed as if it occurred within the context of a
234 * and text be added via [text]. 232 * `<body>` tag, this means that special elements such as `<caption>` which
233 * must be parsed within the scope of a `<table>` element will be dropped. Use
234 * [createFragment] to parse contextual HTML fragments.
235 * 235 *
236 * var element = new Element.html('<div class="foo">content</div>'); 236 * Unless a validator is provided this will perform the default validation
237 * and remove all scriptable elements and attributes.
238 *
239 * See also:
240 *
241 * * [NodeValidator]
242 *
237 */ 243 */
238 factory $CLASSNAME.html(String html) => 244 factory Element.html(String html,
239 _$(CLASSNAME)FactoryProvider.createElement_html(html); 245 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
246 var fragment = document.body.createFragment(html, validator: validator,
247 treeSanitizer: treeSanitizer);
248 return fragment.nodes.where((e) => e is Element).single;
249 }
240 250
241 /** 251 /**
242 * Creates the HTML element specified by the tag name. 252 * Creates the HTML element specified by the tag name.
243 * 253 *
244 * This is similar to [Document.createElement]. 254 * This is similar to [Document.createElement].
245 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then 255 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
246 * this will create an [UnknownElement]. 256 * this will create an [UnknownElement].
247 * 257 *
248 * var divElement = new Element.tag('div'); 258 * var divElement = new Element.tag('div');
249 * print(divElement is DivElement); // 'true' 259 * print(divElement is DivElement); // 'true'
(...skipping 667 matching lines...) Expand 10 before | Expand all | Expand 10 after
917 @Experimental 927 @Experimental
918 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; 928 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
919 929
920 void _ensureTemplate() { 930 void _ensureTemplate() {
921 if (!isTemplate) { 931 if (!isTemplate) {
922 throw new UnsupportedError('$this is not a template.'); 932 throw new UnsupportedError('$this is not a template.');
923 } 933 }
924 TemplateElement.decorate(this); 934 TemplateElement.decorate(this);
925 } 935 }
926 936
937 /**
938 * Create a DocumentFragment from the HTML fragment and ensure that it follows
939 * the sanitization rules specified by the validator or treeSanitizer.
940 *
941 * If the default validation behavior is too restrictive then a new
942 * NodeValidator should be created, either extending or wrapping a default
943 * validator and overriding the validation APIs.
944 *
945 * The treeSanitizer is used to walk the generated node tree and sanitize it.
946 * A custom treeSanitizer can also be provided to perform special validation
947 * rules but since the API is more complex to implement this is discouraged.
948 *
949 * The returned tree is guaranteed to only contain nodes and attributes which
950 * are allowed by the provided validator.
951 *
952 * See also:
953 *
954 * * [NodeValidator]
955 * * [NodeTreeSanitizer]
956 */
957 DocumentFragment createFragment(String html,
958 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
959
960 if (treeSanitizer == null) {
961 if (validator == null) {
962 validator = new NodeValidatorBuilder.common();
963 }
964 treeSanitizer = new NodeTreeSanitizer(validator);
965 } else if (validator != null) {
966 throw new ArgumentError(
967 'validator can only be passed if treeSanitizer is null');
968 }
969
970 return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer);
971 }
972
973 void set innerHtml(String html) {
974 this.setInnerHtml(html);
975 }
976
977 void setInnerHtml(String html,
978 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
979 text = null;
980 append(createFragment(
981 html, validator: validator, treeSanitizer: treeSanitizer));
982 }
983
984 String get innerHtml => $dom_innerHtml;
985
927 $!MEMBERS 986 $!MEMBERS
928 } 987 }
929 988
930 989
931 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
932 class _ElementFactoryProvider { 990 class _ElementFactoryProvider {
933 static const _CUSTOM_PARENT_TAG_MAP = const {
934 'body' : 'html',
935 'head' : 'html',
936 'caption' : 'table',
937 'td': 'tr',
938 'th': 'tr',
939 'colgroup': 'table',
940 'col' : 'colgroup',
941 'tr' : 'tbody',
942 'tbody' : 'table',
943 'tfoot' : 'table',
944 'thead' : 'table',
945 'track' : 'audio',
946 };
947 991
948 @DomName('Document.createElement') 992 static HtmlDocument _parseDocument;
949 static Element createElement_html(String html) { 993
950 // TODO(jacobr): this method can be made more robust and performant. 994 static DocumentFragment _parseHtml(Element context, String html,
951 // 1) Cache the dummy parent elements required to use innerHTML rather than 995 NodeTreeSanitizer treeSanitizer) {
952 // creating them every call. 996
953 // 2) Verify that the html does not contain leading or trailing text nodes. 997 if (_parseDocument == null) {
954 // 3) Verify that the html does not contain both <head> and <body> tags. 998 _parseDocument = document.implementation.createHtmlDocument('');
955 // 4) Detatch the created element from its dummy parent. 999 }
956 String parentTag = 'div'; 1000 var contextElement;
957 String tag; 1001 if (context == null || context is BodyElement) {
958 final match = _START_TAG_REGEXP.firstMatch(html); 1002 contextElement = _parseDocument.body;
959 if (match != null) { 1003 } else {
960 tag = match.group(1).toLowerCase(); 1004 contextElement = _parseDocument.$dom_createElement(context.tagName);
961 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { 1005 _parseDocument.body.append(contextElement);
962 return _createTableForIE(html, tag);
963 }
964 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
965 if (parentTag == null) parentTag = 'div';
966 } 1006 }
967 1007
968 final temp = new Element.tag(parentTag); 1008 if (Range.supportsCreateContextualFragment) {
969 temp.innerHtml = html; 1009 var range = _parseDocument.$dom_createRange();
1010 range.selectNodeContents(contextElement);
1011 var fragment = range.createContextualFragment(html);
970 1012
971 Element element; 1013 if (contextElement != _parseDocument.body) {
972 if (temp.children.length == 1) { 1014 contextElement.remove();
973 element = temp.children[0]; 1015 }
974 } else if (parentTag == 'html' && temp.children.length == 2) { 1016
975 // In html5 the root <html> tag will always have a <body> and a <head>, 1017 treeSanitizer.sanitizeTree(fragment);
976 // even though the inner html only contains one of them. 1018 return fragment;
977 element = temp.children[tag == 'head' ? 0 : 1];
978 } else { 1019 } else {
979 _singleNode(temp.children); 1020 contextElement.$dom_innerHtml = html;
1021
1022 treeSanitizer.sanitizeTree(contextElement);
1023
1024 var fragment = new DocumentFragment();
1025 while (contextElement.$dom_firstChild != null) {
1026 fragment.append(contextElement.$dom_firstChild);
1027 }
1028 return fragment;
980 } 1029 }
981 element.remove();
982 return element;
983 }
984
985 /**
986 * IE table elements don't support innerHTML (even in standards mode).
987 * Instead we use a div and inject the table element in the innerHtml string.
988 * This technique works on other browsers too, but it's probably slower,
989 * so we only use it when running on IE.
990 *
991 * See also innerHTML:
992 * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx>
993 * and Building Tables Dynamically:
994 * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>.
995 */
996 static Element _createTableForIE(String html, String tag) {
997 var div = new Element.tag('div');
998 div.innerHtml = '<table>$html</table>';
999 var table = _singleNode(div.children);
1000 Element element;
1001 switch (tag) {
1002 case 'td':
1003 case 'th':
1004 TableRowElement row = _singleNode(table.rows);
1005 element = _singleNode(row.cells);
1006 break;
1007 case 'tr':
1008 element = _singleNode(table.rows);
1009 break;
1010 case 'tbody':
1011 element = _singleNode(table.tBodies);
1012 break;
1013 case 'thead':
1014 element = table.tHead;
1015 break;
1016 case 'tfoot':
1017 element = table.tFoot;
1018 break;
1019 case 'caption':
1020 element = table.caption;
1021 break;
1022 case 'colgroup':
1023 element = _getColgroup(table);
1024 break;
1025 case 'col':
1026 element = _singleNode(_getColgroup(table).children);
1027 break;
1028 }
1029 element.remove();
1030 return element;
1031 }
1032
1033 static TableColElement _getColgroup(TableElement table) {
1034 // TODO(jmesserly): is there a better way to do this?
1035 return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP')
1036 .toList());
1037 }
1038
1039 static Node _singleNode(List<Node> list) {
1040 if (list.length == 1) return list[0];
1041 throw new ArgumentError('HTML had ${list.length} '
1042 'top level elements but 1 expected');
1043 } 1030 }
1044 1031
1045 @DomName('Document.createElement') 1032 @DomName('Document.createElement')
1046 $if DART2JS 1033 $if DART2JS
1047 // Optimization to improve performance until the dart2js compiler inlines this 1034 // Optimization to improve performance until the dart2js compiler inlines this
1048 // method. 1035 // method.
1049 static dynamic createElement_tag(String tag) => 1036 static dynamic createElement_tag(String tag) =>
1050 // Firefox may return a JS function for some types (Embed, Object). 1037 // Firefox may return a JS function for some types (Embed, Object).
1051 JS('Element|=Object', 'document.createElement(#)', tag); 1038 JS('Element|=Object', 'document.createElement(#)', tag);
1052 $else 1039 $else
(...skipping 11 matching lines...) Expand all
1064 const ScrollAlignment._internal(this._value); 1051 const ScrollAlignment._internal(this._value);
1065 toString() => 'ScrollAlignment.$_value'; 1052 toString() => 'ScrollAlignment.$_value';
1066 1053
1067 /// Attempt to align the element to the top of the scrollable area. 1054 /// Attempt to align the element to the top of the scrollable area.
1068 static const TOP = const ScrollAlignment._internal('TOP'); 1055 static const TOP = const ScrollAlignment._internal('TOP');
1069 /// Attempt to center the element in the scrollable area. 1056 /// Attempt to center the element in the scrollable area.
1070 static const CENTER = const ScrollAlignment._internal('CENTER'); 1057 static const CENTER = const ScrollAlignment._internal('CENTER');
1071 /// Attempt to align the element to the bottom of the scrollable area. 1058 /// Attempt to align the element to the bottom of the scrollable area.
1072 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); 1059 static const BOTTOM = const ScrollAlignment._internal('BOTTOM');
1073 } 1060 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698