Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(270)

Side by Side Diff: tools/dom/templates/html/impl/impl_Element.darttemplate

Issue 16374007: First rev of Safe DOM (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Fixing up recent test additions. Created 7 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
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
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
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
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 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698