| 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 291 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|   302 } |   302 } | 
|   303  |   303  | 
|   304 /** |   304 /** | 
|   305  * An abstract class, which all HTML elements extend. |   305  * An abstract class, which all HTML elements extend. | 
|   306  */ |   306  */ | 
|   307 $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { |   307 $(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC { | 
|   308  |   308  | 
|   309   /** |   309   /** | 
|   310    * Creates an HTML element from a valid fragment of HTML. |   310    * Creates an HTML element from a valid fragment of HTML. | 
|   311    * |   311    * | 
|   312    * The [html] fragment must represent valid HTML with a single element root, |   312    *     var element = new Element.html('<div class="foo">content</div>'); | 
|   313    * which will be parsed and returned. |  | 
|   314    * |   313    * | 
|   315    * Important: the contents of [html] should not contain any user-supplied |   314    * The HTML fragment should contain only one single root element, any | 
|   316    * data. Without strict data validation it is impossible to prevent script |   315    * leading or trailing text nodes will be removed. | 
|   317    * injection exploits. |  | 
|   318    * |   316    * | 
|   319    * It is instead recommended that elements be constructed via [Element.tag] |   317    * The HTML fragment is parsed as if it occurred within the context of a | 
|   320    * and text be added via [text]. |   318    * `<body>` tag, this means that special elements such as `<caption>` which | 
 |   319    * must be parsed within the scope of a `<table>` element will be dropped. Use | 
 |   320    * [createFragment] to parse contextual HTML fragments. | 
|   321    * |   321    * | 
|   322    *     var element = new Element.html('<div class="foo">content</div>'); |   322    * Unless a validator is provided this will perform the default validation | 
 |   323    * and remove all scriptable elements and attributes. | 
 |   324    * | 
 |   325    * See also: | 
 |   326    * | 
 |   327    * * [NodeValidator] | 
 |   328    * | 
|   323    */ |   329    */ | 
|   324   factory $CLASSNAME.html(String html) => |   330   factory Element.html(String html, | 
|   325       _$(CLASSNAME)FactoryProvider.createElement_html(html); |   331       {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | 
 |   332     var fragment = document.body.createFragment(html, validator: validator, | 
 |   333         treeSanitizer: treeSanitizer); | 
 |   334  | 
 |   335     return fragment.nodes.where((e) => e is Element).single; | 
 |   336   } | 
|   326  |   337  | 
|   327   /** |   338   /** | 
|   328    * Creates the HTML element specified by the tag name. |   339    * Creates the HTML element specified by the tag name. | 
|   329    * |   340    * | 
|   330    * This is similar to [Document.createElement]. |   341    * This is similar to [Document.createElement]. | 
|   331    * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then |   342    * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then | 
|   332    * this will create an [UnknownElement]. |   343    * this will create an [UnknownElement]. | 
|   333    * |   344    * | 
|   334    *     var divElement = new Element.tag('div'); |   345    *     var divElement = new Element.tag('div'); | 
|   335    *     print(divElement is DivElement); // 'true' |   346    *     print(divElement is DivElement); // 'true' | 
| (...skipping 828 matching lines...) Expand 10 before | Expand all | Expand 10 after  Loading... | 
|  1164     if (current == null || identical(current, parent)) { |  1175     if (current == null || identical(current, parent)) { | 
|  1165       if (foundAsParent) return new Point(0, 0); |  1176       if (foundAsParent) return new Point(0, 0); | 
|  1166       throw new ArgumentError("Specified element is not a transitive offset " |  1177       throw new ArgumentError("Specified element is not a transitive offset " | 
|  1167           "parent of this element."); |  1178           "parent of this element."); | 
|  1168     } |  1179     } | 
|  1169     Element parentOffset = current.offsetParent; |  1180     Element parentOffset = current.offsetParent; | 
|  1170     Point p = Element._offsetToHelper(parentOffset, parent); |  1181     Point p = Element._offsetToHelper(parentOffset, parent); | 
|  1171     return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); |  1182     return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); | 
|  1172   } |  1183   } | 
|  1173  |  1184  | 
|  1174 $if DART2JS |  1185   static HtmlDocument _parseDocument; | 
|  1175   @JSName('innerHTML') |  1186   static NodeValidatorBuilder _defaultValidator; | 
|  1176   @DomName('HTMLElement.innerHTML') |  1187   static _ValidatingTreeSanitizer _defaultSanitizer; | 
|  1177   String get innerHtml => JS('String', '#.innerHTML', this); |  | 
|  1178  |  1188  | 
|  1179   void set innerHtml(String value) { |  1189   /** | 
|  1180     JS('', '#.innerHTML = #', this, value); |  1190    * Create a DocumentFragment from the HTML fragment and ensure that it follows | 
|  1181     // Polyfill relies on mutation observers for upgrading, but we want it |  1191    * the sanitization rules specified by the validator or treeSanitizer. | 
|  1182     // immediate. |  1192    * | 
|  1183     Platform.upgradeCustomElements(this); |  1193    * If the default validation behavior is too restrictive then a new | 
 |  1194    * NodeValidator should be created, either extending or wrapping a default | 
 |  1195    * validator and overriding the validation APIs. | 
 |  1196    * | 
 |  1197    * The treeSanitizer is used to walk the generated node tree and sanitize it. | 
 |  1198    * A custom treeSanitizer can also be provided to perform special validation | 
 |  1199    * rules but since the API is more complex to implement this is discouraged. | 
 |  1200    * | 
 |  1201    * The returned tree is guaranteed to only contain nodes and attributes which | 
 |  1202    * are allowed by the provided validator. | 
 |  1203    * | 
 |  1204    * See also: | 
 |  1205    * | 
 |  1206    * * [NodeValidator] | 
 |  1207    * * [NodeTreeSanitizer] | 
 |  1208    */ | 
 |  1209   DocumentFragment createFragment(String html, | 
 |  1210       {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | 
 |  1211     if (treeSanitizer == null) { | 
 |  1212       if (validator == null) { | 
 |  1213         if (_defaultValidator == null) { | 
 |  1214           _defaultValidator = new NodeValidatorBuilder.common(); | 
 |  1215         } | 
 |  1216         validator = _defaultValidator; | 
 |  1217       } | 
 |  1218       if (_defaultSanitizer == null) { | 
 |  1219         _defaultSanitizer = new _ValidatingTreeSanitizer(validator); | 
 |  1220       } else { | 
 |  1221         _defaultSanitizer.validator = validator; | 
 |  1222       } | 
 |  1223       treeSanitizer = _defaultSanitizer; | 
 |  1224     } else if (validator != null) { | 
 |  1225       throw new ArgumentError( | 
 |  1226           'validator can only be passed if treeSanitizer is null'); | 
 |  1227     } | 
 |  1228  | 
 |  1229     if (_parseDocument == null) { | 
 |  1230       _parseDocument = document.implementation.createHtmlDocument(''); | 
 |  1231     } | 
 |  1232     var contextElement; | 
 |  1233     if (this is BodyElement) { | 
 |  1234       contextElement = _parseDocument.body; | 
 |  1235     } else { | 
 |  1236       contextElement = _parseDocument.$dom_createElement(tagName); | 
 |  1237       _parseDocument.body.append(contextElement); | 
 |  1238     } | 
 |  1239     var fragment; | 
 |  1240     if (Range.supportsCreateContextualFragment) { | 
 |  1241       var range = _parseDocument.$dom_createRange(); | 
 |  1242       range.selectNodeContents(contextElement); | 
 |  1243       fragment = range.createContextualFragment(html); | 
 |  1244     } else { | 
 |  1245       contextElement._innerHtml = html; | 
 |  1246  | 
 |  1247       fragment = _parseDocument.createDocumentFragment(); | 
 |  1248       while (contextElement.firstChild != null) { | 
 |  1249         fragment.append(contextElement.firstChild); | 
 |  1250       } | 
 |  1251     } | 
 |  1252     if (contextElement != _parseDocument.body) { | 
 |  1253       contextElement.remove(); | 
 |  1254     } | 
 |  1255  | 
 |  1256     treeSanitizer.sanitizeTree(fragment); | 
 |  1257     return fragment; | 
|  1184   } |  1258   } | 
|  1185 $endif |  1259  | 
 |  1260   /** | 
 |  1261    * Parses the HTML fragment and sets it as the contents of this element. | 
 |  1262    * | 
 |  1263    * This uses the default sanitization behavior to sanitize the HTML fragment, | 
 |  1264    * use [setInnerHtml] to override the default behavior. | 
 |  1265    */ | 
 |  1266   void set innerHtml(String html) { | 
 |  1267     this.setInnerHtml(html); | 
 |  1268   } | 
 |  1269  | 
 |  1270   /** | 
 |  1271    * Parses the HTML fragment and sets it as the contents of this element. | 
 |  1272    * This ensures that the generated content follows the sanitization rules | 
 |  1273    * specified by the validator or treeSanitizer. | 
 |  1274    * | 
 |  1275    * If the default validation behavior is too restrictive then a new | 
 |  1276    * NodeValidator should be created, either extending or wrapping a default | 
 |  1277    * validator and overriding the validation APIs. | 
 |  1278    * | 
 |  1279    * The treeSanitizer is used to walk the generated node tree and sanitize it. | 
 |  1280    * A custom treeSanitizer can also be provided to perform special validation | 
 |  1281    * rules but since the API is more complex to implement this is discouraged. | 
 |  1282    * | 
 |  1283    * The resulting tree is guaranteed to only contain nodes and attributes which | 
 |  1284    * are allowed by the provided validator. | 
 |  1285    * | 
 |  1286    * See also: | 
 |  1287    * | 
 |  1288    * * [NodeValidator] | 
 |  1289    * * [NodeTreeSanitizer] | 
 |  1290    */ | 
 |  1291   void setInnerHtml(String html, | 
 |  1292     {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | 
 |  1293     text = null; | 
 |  1294     append(createFragment( | 
 |  1295         html, validator: validator, treeSanitizer: treeSanitizer)); | 
 |  1296   } | 
 |  1297   String get innerHtml => _innerHtml; | 
 |  1298  | 
 |  1299   /** | 
 |  1300    * For use while transitioning to the safe [innerHtml] or [setInnerHtml]. | 
 |  1301    * Unsafe because it opens the app to cross-site scripting vulnerabilities. | 
 |  1302    */ | 
 |  1303   @deprecated | 
 |  1304   void set unsafeInnerHtml(String html) { | 
 |  1305     _innerHtml = html; | 
 |  1306   } | 
|  1186  |  1307  | 
|  1187 $!MEMBERS |  1308 $!MEMBERS | 
|  1188 } |  1309 } | 
|  1189  |  1310  | 
|  1190 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |  1311  | 
|  1191 class _ElementFactoryProvider { |  1312 class _ElementFactoryProvider { | 
|  1192   static const _CUSTOM_PARENT_TAG_MAP = const { |  | 
|  1193     'body' : 'html', |  | 
|  1194     'head' : 'html', |  | 
|  1195     'caption' : 'table', |  | 
|  1196     'td': 'tr', |  | 
|  1197     'th': 'tr', |  | 
|  1198     'colgroup': 'table', |  | 
|  1199     'col' : 'colgroup', |  | 
|  1200     'tr' : 'tbody', |  | 
|  1201     'tbody' : 'table', |  | 
|  1202     'tfoot' : 'table', |  | 
|  1203     'thead' : 'table', |  | 
|  1204     'track' : 'audio', |  | 
|  1205   }; |  | 
|  1206  |  | 
|  1207   @DomName('Document.createElement') |  | 
|  1208   static Element createElement_html(String html) { |  | 
|  1209     // TODO(jacobr): this method can be made more robust and performant. |  | 
|  1210     // 1) Cache the dummy parent elements required to use innerHTML rather than |  | 
|  1211     //    creating them every call. |  | 
|  1212     // 2) Verify that the html does not contain leading or trailing text nodes. |  | 
|  1213     // 3) Verify that the html does not contain both <head> and <body> tags. |  | 
|  1214     // 4) Detatch the created element from its dummy parent. |  | 
|  1215     String parentTag = 'div'; |  | 
|  1216     String tag; |  | 
|  1217     final match = _START_TAG_REGEXP.firstMatch(html); |  | 
|  1218     if (match != null) { |  | 
|  1219       tag = match.group(1).toLowerCase(); |  | 
|  1220       if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { |  | 
|  1221         return _createTableForIE(html, tag); |  | 
|  1222       } |  | 
|  1223       parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; |  | 
|  1224       if (parentTag == null) parentTag = 'div'; |  | 
|  1225     } |  | 
|  1226  |  | 
|  1227     final temp = new Element.tag(parentTag); |  | 
|  1228     temp.innerHtml = html; |  | 
|  1229  |  | 
|  1230     Element element; |  | 
|  1231     if (temp.children.length == 1) { |  | 
|  1232       element = temp.children[0]; |  | 
|  1233     } else if (parentTag == 'html' && temp.children.length == 2) { |  | 
|  1234       // In html5 the root <html> tag will always have a <body> and a <head>, |  | 
|  1235       // even though the inner html only contains one of them. |  | 
|  1236       element = temp.children[tag == 'head' ? 0 : 1]; |  | 
|  1237     } else { |  | 
|  1238       _singleNode(temp.children); |  | 
|  1239     } |  | 
|  1240     element.remove(); |  | 
|  1241     return element; |  | 
|  1242   } |  | 
|  1243  |  | 
|  1244   /** |  | 
|  1245    * IE table elements don't support innerHTML (even in standards mode). |  | 
|  1246    * Instead we use a div and inject the table element in the innerHtml string. |  | 
|  1247    * This technique works on other browsers too, but it's probably slower, |  | 
|  1248    * so we only use it when running on IE. |  | 
|  1249    * |  | 
|  1250    * See also innerHTML: |  | 
|  1251    * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx> |  | 
|  1252    * and Building Tables Dynamically: |  | 
|  1253    * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>. |  | 
|  1254    */ |  | 
|  1255   static Element _createTableForIE(String html, String tag) { |  | 
|  1256     var div = new Element.tag('div'); |  | 
|  1257     div.innerHtml = '<table>$html</table>'; |  | 
|  1258     var table = _singleNode(div.children); |  | 
|  1259     Element element; |  | 
|  1260     switch (tag) { |  | 
|  1261       case 'td': |  | 
|  1262       case 'th': |  | 
|  1263         TableRowElement row = _singleNode(table.rows); |  | 
|  1264         element = _singleNode(row.cells); |  | 
|  1265         break; |  | 
|  1266       case 'tr': |  | 
|  1267         element = _singleNode(table.rows); |  | 
|  1268         break; |  | 
|  1269       case 'tbody': |  | 
|  1270         element = _singleNode(table.tBodies); |  | 
|  1271         break; |  | 
|  1272       case 'thead': |  | 
|  1273         element = table.tHead; |  | 
|  1274         break; |  | 
|  1275       case 'tfoot': |  | 
|  1276         element = table.tFoot; |  | 
|  1277         break; |  | 
|  1278       case 'caption': |  | 
|  1279         element = table.caption; |  | 
|  1280         break; |  | 
|  1281       case 'colgroup': |  | 
|  1282         element = _getColgroup(table); |  | 
|  1283         break; |  | 
|  1284       case 'col': |  | 
|  1285         element = _singleNode(_getColgroup(table).children); |  | 
|  1286         break; |  | 
|  1287     } |  | 
|  1288     element.remove(); |  | 
|  1289     return element; |  | 
|  1290   } |  | 
|  1291  |  | 
|  1292   static TableColElement _getColgroup(TableElement table) { |  | 
|  1293     // TODO(jmesserly): is there a better way to do this? |  | 
|  1294     return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP') |  | 
|  1295         .toList()); |  | 
|  1296   } |  | 
|  1297  |  | 
|  1298   static Node _singleNode(List<Node> list) { |  | 
|  1299     if (list.length == 1) return list[0]; |  | 
|  1300     throw new ArgumentError('HTML had ${list.length} ' |  | 
|  1301         'top level elements but 1 expected'); |  | 
|  1302   } |  | 
|  1303  |  1313  | 
|  1304   @DomName('Document.createElement') |  1314   @DomName('Document.createElement') | 
|  1305 $if DART2JS |  1315 $if DART2JS | 
|  1306   // Optimization to improve performance until the dart2js compiler inlines this |  1316   // Optimization to improve performance until the dart2js compiler inlines this | 
|  1307   // method. |  1317   // method. | 
|  1308   static dynamic createElement_tag(String tag) => |  1318   static dynamic createElement_tag(String tag) => | 
|  1309       // Firefox may return a JS function for some types (Embed, Object). |  1319       // Firefox may return a JS function for some types (Embed, Object). | 
|  1310       JS('Element|=Object', 'document.createElement(#)', tag); |  1320       JS('Element|=Object', 'document.createElement(#)', tag); | 
|  1311 $else |  1321 $else | 
|  1312   static Element createElement_tag(String tag) => |  1322   static Element createElement_tag(String tag) => | 
| (...skipping 10 matching lines...) Expand all  Loading... | 
|  1323   const ScrollAlignment._internal(this._value); |  1333   const ScrollAlignment._internal(this._value); | 
|  1324   toString() => 'ScrollAlignment.$_value'; |  1334   toString() => 'ScrollAlignment.$_value'; | 
|  1325  |  1335  | 
|  1326   /// Attempt to align the element to the top of the scrollable area. |  1336   /// Attempt to align the element to the top of the scrollable area. | 
|  1327   static const TOP = const ScrollAlignment._internal('TOP'); |  1337   static const TOP = const ScrollAlignment._internal('TOP'); | 
|  1328   /// Attempt to center the element in the scrollable area. |  1338   /// Attempt to center the element in the scrollable area. | 
|  1329   static const CENTER = const ScrollAlignment._internal('CENTER'); |  1339   static const CENTER = const ScrollAlignment._internal('CENTER'); | 
|  1330   /// Attempt to align the element to the bottom of the scrollable area. |  1340   /// Attempt to align the element to the bottom of the scrollable area. | 
|  1331   static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); |  1341   static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); | 
|  1332 } |  1342 } | 
| OLD | NEW |