OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |