| 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, |
| 313 * which will be parsed and returned. |
| 314 * |
| 315 * Important: the contents of [html] should not contain any user-supplied |
| 316 * data. Without strict data validation it is impossible to prevent script |
| 317 * injection exploits. |
| 318 * |
| 319 * It is instead recommended that elements be constructed via [Element.tag] |
| 320 * and text be added via [text]. |
| 321 * |
| 312 * var element = new Element.html('<div class="foo">content</div>'); | 322 * var element = new Element.html('<div class="foo">content</div>'); |
| 313 * | |
| 314 * The HTML fragment should contain only one single root element, any | |
| 315 * leading or trailing text nodes will be removed. | |
| 316 * | |
| 317 * The HTML fragment is parsed as if it occurred within the context of a | |
| 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 * | |
| 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 * | |
| 329 */ | 323 */ |
| 330 factory Element.html(String html, | 324 factory $CLASSNAME.html(String html) => |
| 331 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) { | 325 _$(CLASSNAME)FactoryProvider.createElement_html(html); |
| 332 var fragment = document.body.createFragment(html, validator: validator, | |
| 333 treeSanitizer: treeSanitizer); | |
| 334 | |
| 335 return fragment.nodes.where((e) => e is Element).single; | |
| 336 } | |
| 337 | 326 |
| 338 /** | 327 /** |
| 339 * Creates the HTML element specified by the tag name. | 328 * Creates the HTML element specified by the tag name. |
| 340 * | 329 * |
| 341 * This is similar to [Document.createElement]. | 330 * This is similar to [Document.createElement]. |
| 342 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then | 331 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then |
| 343 * this will create an [UnknownElement]. | 332 * this will create an [UnknownElement]. |
| 344 * | 333 * |
| 345 * var divElement = new Element.tag('div'); | 334 * var divElement = new Element.tag('div'); |
| 346 * print(divElement is DivElement); // 'true' | 335 * print(divElement is DivElement); // 'true' |
| (...skipping 828 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1175 if (current == null || identical(current, parent)) { | 1164 if (current == null || identical(current, parent)) { |
| 1176 if (foundAsParent) return new Point(0, 0); | 1165 if (foundAsParent) return new Point(0, 0); |
| 1177 throw new ArgumentError("Specified element is not a transitive offset " | 1166 throw new ArgumentError("Specified element is not a transitive offset " |
| 1178 "parent of this element."); | 1167 "parent of this element."); |
| 1179 } | 1168 } |
| 1180 Element parentOffset = current.offsetParent; | 1169 Element parentOffset = current.offsetParent; |
| 1181 Point p = Element._offsetToHelper(parentOffset, parent); | 1170 Point p = Element._offsetToHelper(parentOffset, parent); |
| 1182 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); | 1171 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); |
| 1183 } | 1172 } |
| 1184 | 1173 |
| 1185 static HtmlDocument _parseDocument; | 1174 $if DART2JS |
| 1186 static NodeValidatorBuilder _defaultValidator; | 1175 @JSName('innerHTML') |
| 1187 static _ValidatingTreeSanitizer _defaultSanitizer; | 1176 @DomName('HTMLElement.innerHTML') |
| 1177 String get innerHtml => JS('String', '#.innerHTML', this); |
| 1188 | 1178 |
| 1189 /** | 1179 void set innerHtml(String value) { |
| 1190 * Create a DocumentFragment from the HTML fragment and ensure that it follows | 1180 JS('', '#.innerHTML = #', this, value); |
| 1191 * the sanitization rules specified by the validator or treeSanitizer. | 1181 // Polyfill relies on mutation observers for upgrading, but we want it |
| 1192 * | 1182 // immediate. |
| 1193 * If the default validation behavior is too restrictive then a new | 1183 Platform.upgradeCustomElements(this); |
| 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; | |
| 1258 } | 1184 } |
| 1259 | 1185 $endif |
| 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 } | |
| 1307 | 1186 |
| 1308 $!MEMBERS | 1187 $!MEMBERS |
| 1309 } | 1188 } |
| 1310 | 1189 |
| 1190 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); |
| 1191 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 }; |
| 1311 | 1206 |
| 1312 class _ElementFactoryProvider { | 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 } |
| 1313 | 1303 |
| 1314 @DomName('Document.createElement') | 1304 @DomName('Document.createElement') |
| 1315 $if DART2JS | 1305 $if DART2JS |
| 1316 // Optimization to improve performance until the dart2js compiler inlines this | 1306 // Optimization to improve performance until the dart2js compiler inlines this |
| 1317 // method. | 1307 // method. |
| 1318 static dynamic createElement_tag(String tag) => | 1308 static dynamic createElement_tag(String tag) => |
| 1319 // Firefox may return a JS function for some types (Embed, Object). | 1309 // Firefox may return a JS function for some types (Embed, Object). |
| 1320 JS('Element|=Object', 'document.createElement(#)', tag); | 1310 JS('Element|=Object', 'document.createElement(#)', tag); |
| 1321 $else | 1311 $else |
| 1322 static Element createElement_tag(String tag) => | 1312 static Element createElement_tag(String tag) => |
| (...skipping 10 matching lines...) Expand all Loading... |
| 1333 const ScrollAlignment._internal(this._value); | 1323 const ScrollAlignment._internal(this._value); |
| 1334 toString() => 'ScrollAlignment.$_value'; | 1324 toString() => 'ScrollAlignment.$_value'; |
| 1335 | 1325 |
| 1336 /// Attempt to align the element to the top of the scrollable area. | 1326 /// Attempt to align the element to the top of the scrollable area. |
| 1337 static const TOP = const ScrollAlignment._internal('TOP'); | 1327 static const TOP = const ScrollAlignment._internal('TOP'); |
| 1338 /// Attempt to center the element in the scrollable area. | 1328 /// Attempt to center the element in the scrollable area. |
| 1339 static const CENTER = const ScrollAlignment._internal('CENTER'); | 1329 static const CENTER = const ScrollAlignment._internal('CENTER'); |
| 1340 /// Attempt to align the element to the bottom of the scrollable area. | 1330 /// Attempt to align the element to the bottom of the scrollable area. |
| 1341 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); | 1331 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); |
| 1342 } | 1332 } |
| OLD | NEW |