Chromium Code Reviews| 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 return fragment.nodes.where((e) => e is Element).single; | |
| 
 
Jennifer Messerly
2013/08/17 06:07:07
how's the performance of this? I dunno if it matte
 
blois
2013/08/19 22:02:09
Done.
 
 | |
| 335 } | |
| 326 | 336 | 
| 327 /** | 337 /** | 
| 328 * Creates the HTML element specified by the tag name. | 338 * Creates the HTML element specified by the tag name. | 
| 329 * | 339 * | 
| 330 * This is similar to [Document.createElement]. | 340 * This is similar to [Document.createElement]. | 
| 331 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then | 341 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then | 
| 332 * this will create an [UnknownElement]. | 342 * this will create an [UnknownElement]. | 
| 333 * | 343 * | 
| 334 * var divElement = new Element.tag('div'); | 344 * var divElement = new Element.tag('div'); | 
| 335 * print(divElement is DivElement); // 'true' | 345 * print(divElement is DivElement); // 'true' | 
| (...skipping 827 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 1163 bool foundAsParent = identical(current, parent) || parent.tagName == 'HTML'; | 1173 bool foundAsParent = identical(current, parent) || parent.tagName == 'HTML'; | 
| 1164 if (current == null || identical(current, parent)) { | 1174 if (current == null || identical(current, parent)) { | 
| 1165 if (foundAsParent) return new Point(0, 0); | 1175 if (foundAsParent) return new Point(0, 0); | 
| 1166 throw new ArgumentError("Specified element is not a transitive offset " | 1176 throw new ArgumentError("Specified element is not a transitive offset " | 
| 1167 "parent of this element."); | 1177 "parent of this element."); | 
| 1168 } | 1178 } | 
| 1169 Element parentOffset = current.offsetParent; | 1179 Element parentOffset = current.offsetParent; | 
| 1170 Point p = Element._offsetToHelper(parentOffset, parent); | 1180 Point p = Element._offsetToHelper(parentOffset, parent); | 
| 1171 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); | 1181 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); | 
| 1172 } | 1182 } | 
| 1183 | |
| 1184 /** | |
| 1185 * Create a DocumentFragment from the HTML fragment and ensure that it follows | |
| 1186 * the sanitization rules specified by the validator or treeSanitizer. | |
| 1187 * | |
| 1188 * If the default validation behavior is too restrictive then a new | |
| 1189 * NodeValidator should be created, either extending or wrapping a default | |
| 1190 * validator and overriding the validation APIs. | |
| 1191 * | |
| 1192 * The treeSanitizer is used to walk the generated node tree and sanitize it. | |
| 1193 * A custom treeSanitizer can also be provided to perform special validation | |
| 1194 * rules but since the API is more complex to implement this is discouraged. | |
| 1195 * | |
| 1196 * The returned tree is guaranteed to only contain nodes and attributes which | |
| 1197 * are allowed by the provided validator. | |
| 1198 * | |
| 1199 * See also: | |
| 1200 * | |
| 1201 * * [NodeValidator] | |
| 1202 * * [NodeTreeSanitizer] | |
| 1203 */ | |
| 1204 DocumentFragment createFragment(String html, | |
| 1205 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | |
| 1206 if (treeSanitizer == null) { | |
| 1207 if (validator == null) { | |
| 1208 validator = new NodeValidatorBuilder.common(); | |
| 
 
Jennifer Messerly
2013/08/17 06:07:07
I wonder about performance here, in particular if
 
blois
2013/08/19 22:02:09
Added caching.
 
 | |
| 1209 } | |
| 1210 treeSanitizer = new NodeTreeSanitizer(validator); | |
| 1211 } else if (validator != null) { | |
| 1212 throw new ArgumentError( | |
| 1213 'validator can only be passed if treeSanitizer is null'); | |
| 1214 } | |
| 1215 return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer); | |
| 
 
Jennifer Messerly
2013/08/17 06:07:07
out of curiosity, why is this method on _ElementFa
 
blois
2013/08/19 22:02:09
FactoryProviders are a legacy thing that we've bee
 
Jennifer Messerly
2013/08/26 22:00:31
yay :D
 
 | |
| 1216 } | |
| 1217 void set innerHtml(String html) { | |
| 1218 this.setInnerHtml(html); | |
| 1219 } | |
| 1220 void setInnerHtml(String html, | |
| 1221 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | |
| 1222 text = null; | |
| 1223 append(createFragment( | |
| 1224 html, validator: validator, treeSanitizer: treeSanitizer)); | |
| 1225 } | |
| 1226 String get innerHtml => $dom_innerHtml; | |
| 
 
Jennifer Messerly
2013/08/17 06:07:07
on an unrelated note: is all this $dom_ stuff goin
 
blois
2013/08/19 22:02:09
That's the plan- been having hiccups with benchmar
 
 | |
| 1227 | |
| 1173 $!MEMBERS | 1228 $!MEMBERS | 
| 1174 } | 1229 } | 
| 1175 | 1230 | 
| 1176 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); | 1231 | 
| 1177 class _ElementFactoryProvider { | 1232 class _ElementFactoryProvider { | 
| 1178 static const _CUSTOM_PARENT_TAG_MAP = const { | |
| 1179 'body' : 'html', | |
| 1180 'head' : 'html', | |
| 1181 'caption' : 'table', | |
| 1182 'td': 'tr', | |
| 1183 'th': 'tr', | |
| 1184 'colgroup': 'table', | |
| 1185 'col' : 'colgroup', | |
| 1186 'tr' : 'tbody', | |
| 1187 'tbody' : 'table', | |
| 1188 'tfoot' : 'table', | |
| 1189 'thead' : 'table', | |
| 1190 'track' : 'audio', | |
| 1191 }; | |
| 1192 | 1233 | 
| 1193 @DomName('Document.createElement') | 1234 static HtmlDocument _parseDocument; | 
| 1194 static Element createElement_html(String html) { | 1235 | 
| 1195 // TODO(jacobr): this method can be made more robust and performant. | 1236 static DocumentFragment _parseHtml(Element context, String html, | 
| 1196 // 1) Cache the dummy parent elements required to use innerHTML rather than | 1237 NodeTreeSanitizer treeSanitizer) { | 
| 1197 // creating them every call. | 1238 | 
| 1198 // 2) Verify that the html does not contain leading or trailing text nodes. | 1239 if (_parseDocument == null) { | 
| 1199 // 3) Verify that the html does not contain both <head> and <body> tags. | 1240 _parseDocument = document.implementation.createHtmlDocument(''); | 
| 1200 // 4) Detatch the created element from its dummy parent. | 1241 } | 
| 1201 String parentTag = 'div'; | 1242 var contextElement; | 
| 1202 String tag; | 1243 if (context == null || context is BodyElement) { | 
| 
 
Jennifer Messerly
2013/08/17 06:07:07
curious about the special case here for "body". co
 
blois
2013/08/19 22:02:09
It's defaulting to body and just re-using that ele
 
 | |
| 1203 final match = _START_TAG_REGEXP.firstMatch(html); | 1244 contextElement = _parseDocument.body; | 
| 1204 if (match != null) { | 1245 } else { | 
| 1205 tag = match.group(1).toLowerCase(); | 1246 contextElement = _parseDocument.$dom_createElement(context.tagName); | 
| 1206 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { | 1247 _parseDocument.body.append(contextElement); | 
| 1207 return _createTableForIE(html, tag); | 1248 } | 
| 1249 if (Range.supportsCreateContextualFragment) { | |
| 1250 var range = _parseDocument.$dom_createRange(); | |
| 1251 range.selectNodeContents(contextElement); | |
| 1252 var fragment = range.createContextualFragment(html); | |
| 1253 | |
| 1254 if (contextElement != _parseDocument.body) { | |
| 1255 contextElement.remove(); | |
| 1208 } | 1256 } | 
| 1209 parentTag = _CUSTOM_PARENT_TAG_MAP[tag]; | 1257 | 
| 1210 if (parentTag == null) parentTag = 'div'; | 1258 treeSanitizer.sanitizeTree(fragment); | 
| 1259 return fragment; | |
| 1260 } else { | |
| 1261 contextElement.$dom_innerHtml = html; | |
| 1262 | |
| 1263 treeSanitizer.sanitizeTree(contextElement); | |
| 1264 | |
| 1265 var fragment = new DocumentFragment(); | |
| 1266 while (contextElement.firstChild != null) { | |
| 1267 fragment.append(contextElement.firstChild); | |
| 1268 } | |
| 1269 return fragment; | |
| 1211 } | 1270 } | 
| 1212 | |
| 1213 final temp = new Element.tag(parentTag); | |
| 1214 temp.innerHtml = html; | |
| 1215 | |
| 1216 Element element; | |
| 1217 if (temp.children.length == 1) { | |
| 1218 element = temp.children[0]; | |
| 1219 } else if (parentTag == 'html' && temp.children.length == 2) { | |
| 1220 // In html5 the root <html> tag will always have a <body> and a <head>, | |
| 1221 // even though the inner html only contains one of them. | |
| 1222 element = temp.children[tag == 'head' ? 0 : 1]; | |
| 1223 } else { | |
| 1224 _singleNode(temp.children); | |
| 1225 } | |
| 1226 element.remove(); | |
| 1227 return element; | |
| 1228 } | |
| 1229 | |
| 1230 /** | |
| 1231 * IE table elements don't support innerHTML (even in standards mode). | |
| 1232 * Instead we use a div and inject the table element in the innerHtml string. | |
| 1233 * This technique works on other browsers too, but it's probably slower, | |
| 1234 * so we only use it when running on IE. | |
| 1235 * | |
| 1236 * See also innerHTML: | |
| 1237 * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx> | |
| 1238 * and Building Tables Dynamically: | |
| 1239 * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>. | |
| 1240 */ | |
| 1241 static Element _createTableForIE(String html, String tag) { | |
| 1242 var div = new Element.tag('div'); | |
| 1243 div.innerHtml = '<table>$html</table>'; | |
| 1244 var table = _singleNode(div.children); | |
| 1245 Element element; | |
| 1246 switch (tag) { | |
| 1247 case 'td': | |
| 1248 case 'th': | |
| 1249 TableRowElement row = _singleNode(table.rows); | |
| 1250 element = _singleNode(row.cells); | |
| 1251 break; | |
| 1252 case 'tr': | |
| 1253 element = _singleNode(table.rows); | |
| 1254 break; | |
| 1255 case 'tbody': | |
| 1256 element = _singleNode(table.tBodies); | |
| 1257 break; | |
| 1258 case 'thead': | |
| 1259 element = table.tHead; | |
| 1260 break; | |
| 1261 case 'tfoot': | |
| 1262 element = table.tFoot; | |
| 1263 break; | |
| 1264 case 'caption': | |
| 1265 element = table.caption; | |
| 1266 break; | |
| 1267 case 'colgroup': | |
| 1268 element = _getColgroup(table); | |
| 1269 break; | |
| 1270 case 'col': | |
| 1271 element = _singleNode(_getColgroup(table).children); | |
| 1272 break; | |
| 1273 } | |
| 1274 element.remove(); | |
| 1275 return element; | |
| 1276 } | |
| 1277 | |
| 1278 static TableColElement _getColgroup(TableElement table) { | |
| 1279 // TODO(jmesserly): is there a better way to do this? | |
| 1280 return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP') | |
| 1281 .toList()); | |
| 1282 } | |
| 1283 | |
| 1284 static Node _singleNode(List<Node> list) { | |
| 1285 if (list.length == 1) return list[0]; | |
| 1286 throw new ArgumentError('HTML had ${list.length} ' | |
| 1287 'top level elements but 1 expected'); | |
| 1288 } | 1271 } | 
| 1289 | 1272 | 
| 1290 @DomName('Document.createElement') | 1273 @DomName('Document.createElement') | 
| 1291 $if DART2JS | 1274 $if DART2JS | 
| 1292 // Optimization to improve performance until the dart2js compiler inlines this | 1275 // Optimization to improve performance until the dart2js compiler inlines this | 
| 1293 // method. | 1276 // method. | 
| 1294 static dynamic createElement_tag(String tag) => | 1277 static dynamic createElement_tag(String tag) => | 
| 1295 // Firefox may return a JS function for some types (Embed, Object). | 1278 // Firefox may return a JS function for some types (Embed, Object). | 
| 1296 JS('Element|=Object', 'document.createElement(#)', tag); | 1279 JS('Element|=Object', 'document.createElement(#)', tag); | 
| 1297 $else | 1280 $else | 
| (...skipping 11 matching lines...) Expand all Loading... | |
| 1309 const ScrollAlignment._internal(this._value); | 1292 const ScrollAlignment._internal(this._value); | 
| 1310 toString() => 'ScrollAlignment.$_value'; | 1293 toString() => 'ScrollAlignment.$_value'; | 
| 1311 | 1294 | 
| 1312 /// Attempt to align the element to the top of the scrollable area. | 1295 /// Attempt to align the element to the top of the scrollable area. | 
| 1313 static const TOP = const ScrollAlignment._internal('TOP'); | 1296 static const TOP = const ScrollAlignment._internal('TOP'); | 
| 1314 /// Attempt to center the element in the scrollable area. | 1297 /// Attempt to center the element in the scrollable area. | 
| 1315 static const CENTER = const ScrollAlignment._internal('CENTER'); | 1298 static const CENTER = const ScrollAlignment._internal('CENTER'); | 
| 1316 /// Attempt to align the element to the bottom of the scrollable area. | 1299 /// Attempt to align the element to the bottom of the scrollable area. | 
| 1317 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); | 1300 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); | 
| 1318 } | 1301 } | 
| OLD | NEW |