| 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 |