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

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: Created 7 years, 3 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
335 if (fragment._firstElementChild != fragment._lastElementChild) {
336 throw new StateError("More than one element root");
337 }
338 return fragment._firstElementChild;
339 }
326 340
327 /** 341 /**
328 * Creates the HTML element specified by the tag name. 342 * Creates the HTML element specified by the tag name.
329 * 343 *
330 * This is similar to [Document.createElement]. 344 * This is similar to [Document.createElement].
331 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then 345 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
332 * this will create an [UnknownElement]. 346 * this will create an [UnknownElement].
333 * 347 *
334 * var divElement = new Element.tag('div'); 348 * var divElement = new Element.tag('div');
335 * print(divElement is DivElement); // 'true' 349 * print(divElement is DivElement); // 'true'
(...skipping 828 matching lines...) Expand 10 before | Expand all | Expand 10 after
1164 if (current == null || identical(current, parent)) { 1178 if (current == null || identical(current, parent)) {
1165 if (foundAsParent) return new Point(0, 0); 1179 if (foundAsParent) return new Point(0, 0);
1166 throw new ArgumentError("Specified element is not a transitive offset " 1180 throw new ArgumentError("Specified element is not a transitive offset "
1167 "parent of this element."); 1181 "parent of this element.");
1168 } 1182 }
1169 Element parentOffset = current.offsetParent; 1183 Element parentOffset = current.offsetParent;
1170 Point p = Element._offsetToHelper(parentOffset, parent); 1184 Point p = Element._offsetToHelper(parentOffset, parent);
1171 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop); 1185 return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
1172 } 1186 }
1173 1187
1174 $if DART2JS 1188 static HtmlDocument _parseDocument;
1175 @JSName('innerHTML') 1189 static NodeValidatorBuilder _defaultValidator;
1176 @DomName('HTMLElement.innerHTML') 1190 static _ValidatingTreeSanitizer _defaultSanitizer;
1177 String get innerHtml => JS('String', '#.innerHTML', this);
1178 1191
1179 void set innerHtml(String value) { 1192 /**
1180 JS('', '#.innerHTML = #', this, value); 1193 * Create a DocumentFragment from the HTML fragment and ensure that it follows
1181 // Polyfill relies on mutation observers for upgrading, but we want it 1194 * the sanitization rules specified by the validator or treeSanitizer.
1182 // immediate. 1195 *
1183 Platform.upgradeCustomElements(this); 1196 * If the default validation behavior is too restrictive then a new
1197 * NodeValidator should be created, either extending or wrapping a default
1198 * validator and overriding the validation APIs.
1199 *
1200 * The treeSanitizer is used to walk the generated node tree and sanitize it.
1201 * A custom treeSanitizer can also be provided to perform special validation
1202 * rules but since the API is more complex to implement this is discouraged.
1203 *
1204 * The returned tree is guaranteed to only contain nodes and attributes which
1205 * are allowed by the provided validator.
1206 *
1207 * See also:
1208 *
1209 * * [NodeValidator]
1210 * * [NodeTreeSanitizer]
1211 */
1212 DocumentFragment createFragment(String html,
1213 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
1214 if (treeSanitizer == null) {
1215 if (validator == null) {
1216 if (_defaultValidator == null) {
1217 _defaultValidator = new NodeValidatorBuilder.common();
1218 }
1219 validator = _defaultValidator;
1220 }
1221 if (_defaultSanitizer == null) {
1222 _defaultSanitizer = new _ValidatingTreeSanitizer(validator);
1223 } else {
1224 _defaultSanitizer.validator = validator;
1225 }
1226 treeSanitizer = _defaultSanitizer;
1227 } else if (validator != null) {
1228 throw new ArgumentError(
1229 'validator can only be passed if treeSanitizer is null');
1230 }
1231
1232 if (_parseDocument == null) {
1233 _parseDocument = document.implementation.createHtmlDocument('');
1234 }
1235 var contextElement;
1236 if (this is BodyElement) {
1237 contextElement = _parseDocument.body;
1238 } else {
1239 contextElement = _parseDocument.$dom_createElement(tagName);
1240 _parseDocument.body.append(contextElement);
1241 }
1242 var fragment;
1243 if (Range.supportsCreateContextualFragment) {
1244 var range = _parseDocument.$dom_createRange();
1245 range.selectNodeContents(contextElement);
1246 fragment = range.createContextualFragment(html);
1247 } else {
1248 contextElement._innerHtml = html;
1249
1250 fragment = _parseDocument.createDocumentFragment();
1251 while (contextElement.firstChild != null) {
1252 fragment.append(contextElement.firstChild);
1253 }
1254 }
1255 if (contextElement != _parseDocument.body) {
1256 contextElement.remove();
1257 }
1258
1259 treeSanitizer.sanitizeTree(fragment);
1260 return fragment;
1184 } 1261 }
1185 $endif 1262
1263 /**
1264 * Parses the HTML fragment and sets it as the contents of this element.
1265 *
1266 * This uses the default sanitization behavior to sanitize the HTML fragment,
1267 * use [setInnerHtml] to override the default behavior.
1268 */
1269 void set innerHtml(String html) {
1270 this.setInnerHtml(html);
1271 }
1272
1273 /**
1274 * Parses the HTML fragment and sets it as the contents of this element.
1275 * This ensures that the generated content follows the sanitization rules
1276 * specified by the validator or treeSanitizer.
1277 *
1278 * If the default validation behavior is too restrictive then a new
1279 * NodeValidator should be created, either extending or wrapping a default
1280 * validator and overriding the validation APIs.
1281 *
1282 * The treeSanitizer is used to walk the generated node tree and sanitize it.
1283 * A custom treeSanitizer can also be provided to perform special validation
1284 * rules but since the API is more complex to implement this is discouraged.
1285 *
1286 * The resulting tree is guaranteed to only contain nodes and attributes which
1287 * are allowed by the provided validator.
1288 *
1289 * See also:
1290 *
1291 * * [NodeValidator]
1292 * * [NodeTreeSanitizer]
1293 */
1294 void setInnerHtml(String html,
1295 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
1296 text = null;
1297 append(createFragment(
1298 html, validator: validator, treeSanitizer: treeSanitizer));
1299 }
1300 String get innerHtml => _innerHtml;
1301
1302 /**
1303 * For use while transitioning to the safe [innerHtml] or [setInnerHtml].
1304 * Unsafe because it opens the app to cross-site scripting vulnerabilities.
1305 */
1306 @deprecated
1307 void set unsafeInnerHtml(String html) {
1308 _innerHtml = html;
1309 }
1186 1310
1187 $!MEMBERS 1311 $!MEMBERS
1188 } 1312 }
1189 1313
1190 final _START_TAG_REGEXP = new RegExp('<(\\w+)'); 1314
1191 class _ElementFactoryProvider { 1315 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 1316
1304 @DomName('Document.createElement') 1317 @DomName('Document.createElement')
1305 $if DART2JS 1318 $if DART2JS
1306 // Optimization to improve performance until the dart2js compiler inlines this 1319 // Optimization to improve performance until the dart2js compiler inlines this
1307 // method. 1320 // method.
1308 static dynamic createElement_tag(String tag) => 1321 static dynamic createElement_tag(String tag) =>
1309 // Firefox may return a JS function for some types (Embed, Object). 1322 // Firefox may return a JS function for some types (Embed, Object).
1310 JS('Element|=Object', 'document.createElement(#)', tag); 1323 JS('Element|=Object', 'document.createElement(#)', tag);
1311 $else 1324 $else
1312 static Element createElement_tag(String tag) => 1325 static Element createElement_tag(String tag) =>
(...skipping 10 matching lines...) Expand all
1323 const ScrollAlignment._internal(this._value); 1336 const ScrollAlignment._internal(this._value);
1324 toString() => 'ScrollAlignment.$_value'; 1337 toString() => 'ScrollAlignment.$_value';
1325 1338
1326 /// Attempt to align the element to the top of the scrollable area. 1339 /// Attempt to align the element to the top of the scrollable area.
1327 static const TOP = const ScrollAlignment._internal('TOP'); 1340 static const TOP = const ScrollAlignment._internal('TOP');
1328 /// Attempt to center the element in the scrollable area. 1341 /// Attempt to center the element in the scrollable area.
1329 static const CENTER = const ScrollAlignment._internal('CENTER'); 1342 static const CENTER = const ScrollAlignment._internal('CENTER');
1330 /// Attempt to align the element to the bottom of the scrollable area. 1343 /// Attempt to align the element to the bottom of the scrollable area.
1331 static const BOTTOM = const ScrollAlignment._internal('BOTTOM'); 1344 static const BOTTOM = const ScrollAlignment._internal('BOTTOM');
1332 } 1345 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698