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 217 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
228 * | 228 * |
229 * Important: the contents of [html] should not contain any user-supplied | 229 * Important: the contents of [html] should not contain any user-supplied |
230 * data. Without strict data validation it is impossible to prevent script | 230 * data. Without strict data validation it is impossible to prevent script |
231 * injection exploits. | 231 * injection exploits. |
232 * | 232 * |
233 * It is instead recommended that elements be constructed via [Element.tag] | 233 * It is instead recommended that elements be constructed via [Element.tag] |
234 * and text be added via [text]. | 234 * and text be added via [text]. |
235 * | 235 * |
236 * var element = new Element.html('<div class="foo">content</div>'); | 236 * var element = new Element.html('<div class="foo">content</div>'); |
237 */ | 237 */ |
238 factory $CLASSNAME.html(String html) => | 238 factory Element.html(String html, |
239 _$(CLASSNAME)FactoryProvider.createElement_html(html); | 239 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| 240 var fragment = document.body.createFragment(html, validator: validator, |
| 241 treeSanitizer: treeSanitizer); |
| 242 return fragment.nodes.where((e) => e is Element).single; |
| 243 } |
240 | 244 |
241 /** | 245 /** |
242 * Creates the HTML element specified by the tag name. | 246 * Creates the HTML element specified by the tag name. |
243 * | 247 * |
244 * This is similar to [Document.createElement]. | 248 * This is similar to [Document.createElement]. |
245 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then | 249 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then |
246 * this will create an [UnknownElement]. | 250 * this will create an [UnknownElement]. |
247 * | 251 * |
248 * var divElement = new Element.tag('div'); | 252 * var divElement = new Element.tag('div'); |
249 * print(divElement is DivElement); // 'true' | 253 * print(divElement is DivElement); // 'true' |
(...skipping 667 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
917 @Experimental | 921 @Experimental |
918 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; | 922 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; |
919 | 923 |
920 void _ensureTemplate() { | 924 void _ensureTemplate() { |
921 if (!isTemplate) { | 925 if (!isTemplate) { |
922 throw new UnsupportedError('$this is not a template.'); | 926 throw new UnsupportedError('$this is not a template.'); |
923 } | 927 } |
924 TemplateElement.decorate(this); | 928 TemplateElement.decorate(this); |
925 } | 929 } |
926 | 930 |
| 931 /** |
| 932 * Create a DocumentFragment from the HTML fragment and ensure that it follows |
| 933 * the sanitization rules specified by the validator or treeSanitizer. |
| 934 * |
| 935 * If the default validation behavior is too restrictive then a new |
| 936 * NodeValidator should be created, either extending or wrapping a default |
| 937 * validator and overriding the validation APIs. |
| 938 * |
| 939 * The treeSanitizer is used to walk the generated node tree and sanitize it. |
| 940 * A custom treeSanitizer can also be provided to perform special validation |
| 941 * rules but since the API is more complex to implement this is discouraged. |
| 942 * |
| 943 * The returned tree is guaranteed to only contain nodes and attributes which |
| 944 * are allowed by the provided validator. |
| 945 * |
| 946 * See also: |
| 947 * |
| 948 * * [NodeValidator] |
| 949 * * [NodeTreeSanitizer] |
| 950 */ |
| 951 DocumentFragment createFragment(String html, |
| 952 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| 953 |
| 954 if (treeSanitizer == null) { |
| 955 if (validator == null) { |
| 956 validator = new NodeValidatorBuilder.common(); |
| 957 } |
| 958 treeSanitizer = new NodeTreeSanitizer(validator); |
| 959 } |
| 960 |
| 961 return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer); |
| 962 } |
| 963 |
| 964 void set innerHtml(String html) { |
| 965 this.setInnerHtml(html); |
| 966 } |
| 967 |
| 968 void setInnerHtml(String html, |
| 969 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { |
| 970 text = null; |
| 971 append(createFragment( |
| 972 html, validator: validator, treeSanitizer: treeSanitizer)); |
| 973 } |
| 974 |
| 975 String get innerHtml => deprecatedInnerHtml; |
| 976 |
927 $!MEMBERS | 977 $!MEMBERS |
928 } | 978 } |
929 | 979 |
930 | 980 |
931 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | |
932 class _ElementFactoryProvider { | 981 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 | 982 |
948 @DomName('Document.createElement') | 983 static DocumentFragment _parseHtml(Element context, String html, |
949 static Element createElement_html(String html) { | 984 NodeTreeSanitizer treeSanitizer) { |
950 // TODO(jacobr): this method can be made more robust and performant. | 985 |
951 // 1) Cache the dummy parent elements required to use innerHTML rather than | 986 var doc = document.implementation.createHtmlDocument(''); |
952 // creating them every call. | 987 var contextElement; |
953 // 2) Verify that the html does not contain leading or trailing text nodes. | 988 if (context == null || context is BodyElement) { |
954 // 3) Verify that the html does not contain both <head> and <body> tags. | 989 contextElement = doc.body; |
955 // 4) Detatch the created element from its dummy parent. | 990 } else { |
956 String parentTag = 'div'; | 991 contextElement = doc.$dom_createElement(context.tagName); |
957 String tag; | 992 doc.body.append(contextElement); |
958 final match = _START_TAG_REGEXP.firstMatch(html); | |
959 if (match != null) { | |
960 tag = match.group(1).toLowerCase(); | |
961 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { | |
962 return _createTableForIE(html, tag); | |
963 } | |
964 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | |
965 if (parentTag == null) parentTag = 'div'; | |
966 } | 993 } |
967 | 994 |
968 final temp = new Element.tag(parentTag); | 995 if (Range.supportsCreateContextualFragment) { |
969 temp.innerHtml = html; | 996 var range = doc.$dom_createRange(); |
| 997 range.selectNodeContents(contextElement); |
| 998 var fragment = range.createContextualFragment(html); |
970 | 999 |
971 Element element; | 1000 treeSanitizer.sanitizeTree(fragment); |
972 if (temp.children.length == 1) { | 1001 return fragment; |
973 element = temp.children[0]; | |
974 } else if (parentTag == 'html' && temp.children.length == 2) { | |
975 // In html5 the root <html> tag will always have a <body> and a <head>, | |
976 // even though the inner html only contains one of them. | |
977 element = temp.children[tag == 'head' ? 0 : 1]; | |
978 } else { | 1002 } else { |
979 _singleNode(temp.children); | 1003 contextElement.deprecatedInnerHtml = html; |
| 1004 |
| 1005 treeSanitizer.sanitizeTree(contextElement); |
| 1006 |
| 1007 var fragment = new DocumentFragment(); |
| 1008 while (contextElement.$dom_firstChild != null) { |
| 1009 fragment.append(contextElement.$dom_firstChild); |
| 1010 } |
| 1011 return fragment; |
980 } | 1012 } |
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 } | 1013 } |
1044 | 1014 |
1045 @DomName('Document.createElement') | 1015 @DomName('Document.createElement') |
1046 $if DART2JS | 1016 $if DART2JS |
1047 // Optimization to improve performance until the dart2js compiler inlines this | 1017 // Optimization to improve performance until the dart2js compiler inlines this |
1048 // method. | 1018 // method. |
1049 static dynamic createElement_tag(String tag) => | 1019 static dynamic createElement_tag(String tag) => |
1050 // Firefox may return a JS function for some types (Embed, Object). | 1020 // Firefox may return a JS function for some types (Embed, Object). |
1051 JS('Element|=Object', 'document.createElement(#)', tag); | 1021 JS('Element|=Object', 'document.createElement(#)', tag); |
1052 $else | 1022 $else |
(...skipping 11 matching lines...) Expand all Loading... |
1064 const ScrollAlignment._internal(this._value); | 1034 const ScrollAlignment._internal(this._value); |
1065 toString() => 'ScrollAlignment.$_value'; | 1035 toString() => 'ScrollAlignment.$_value'; |
1066 | 1036 |
1067 /// Attempt to align the element to the top of the scrollable area. | 1037 /// Attempt to align the element to the top of the scrollable area. |
1068 static const TOP = const ScrollAlignment._internal('TOP'); | 1038 static const TOP = const ScrollAlignment._internal('TOP'); |
1069 /// Attempt to center the element in the scrollable area. | 1039 /// Attempt to center the element in the scrollable area. |
1070 static const CENTER = const ScrollAlignment._internal('CENTER'); | 1040 static const CENTER = const ScrollAlignment._internal('CENTER'); |
1071 /// Attempt to align the element to the bottom of the scrollable area. | 1041 /// Attempt to align the element to the bottom of the scrollable area. |
1072 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); | 1042 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); |
1073 } | 1043 } |
OLD | NEW |