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

Side by Side Diff: sdk/lib/html/dart2js/html_dart2js.dart

Issue 16374007: First rev of Safe DOM (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 6 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 /// The Dart HTML library. 1 /// The Dart HTML library.
2 library dart.dom.html; 2 library dart.dom.html;
3 3
4 import 'dart:async'; 4 import 'dart:async';
5 import 'dart:collection'; 5 import 'dart:collection';
6 import 'dart:_collection-dev' hide Symbol; 6 import 'dart:_collection-dev' hide Symbol;
7 import 'dart:html_common'; 7 import 'dart:html_common';
8 import 'dart:indexed_db'; 8 import 'dart:indexed_db';
9 import 'dart:isolate'; 9 import 'dart:isolate';
10 import 'dart:json' as json; 10 import 'dart:json' as json;
(...skipping 6867 matching lines...) Expand 10 before | Expand all | Expand 10 after
6878 } 6878 }
6879 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file 6879 // Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
6880 // for details. All rights reserved. Use of this source code is governed by a 6880 // for details. All rights reserved. Use of this source code is governed by a
6881 // BSD-style license that can be found in the LICENSE file. 6881 // BSD-style license that can be found in the LICENSE file.
6882 6882
6883 6883
6884 @DomName('DocumentFragment') 6884 @DomName('DocumentFragment')
6885 class DocumentFragment extends Node native "DocumentFragment" { 6885 class DocumentFragment extends Node native "DocumentFragment" {
6886 factory DocumentFragment() => document.createDocumentFragment(); 6886 factory DocumentFragment() => document.createDocumentFragment();
6887 6887
6888 factory DocumentFragment.html(String html) { 6888 factory DocumentFragment.html(String html,
6889 final fragment = new DocumentFragment(); 6889 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
6890 fragment.innerHtml = html; 6890
6891 return fragment; 6891 return document.body.createFragment(html,
6892 validator: validator, treeSanitizer: treeSanitizer);
6892 } 6893 }
6893 6894
6894 factory DocumentFragment.svg(String svgContent) { 6895 factory DocumentFragment.svg(String svgContent,
6895 final fragment = new DocumentFragment(); 6896 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
6896 final e = new svg.SvgSvgElement();
6897 e.innerHtml = svgContent;
6898 6897
6899 // Copy list first since we don't want liveness during iteration. 6898 return new svg.SvgSvgElement().createFragment(svgContent,
6900 final List nodes = new List.from(e.nodes); 6899 validator: validator, treeSanitizer: treeSanitizer);
6901 fragment.nodes.addAll(nodes);
6902 return fragment;
6903 } 6900 }
6904 6901
6905 // Native field is used only by Dart code so does not lead to instantiation 6902 // Native field is used only by Dart code so does not lead to instantiation
6906 // of native classes 6903 // of native classes
6907 @Creates('Null') 6904 @Creates('Null')
6908 List<Element> _children; 6905 List<Element> _children;
6909 6906
6910 List<Element> get children { 6907 List<Element> get children {
6911 if (_children == null) { 6908 if (_children == null) {
6912 _children = new FilteredElementList(this); 6909 _children = new FilteredElementList(this);
(...skipping 13 matching lines...) Expand all
6926 6923
6927 List<Element> queryAll(String selectors) => 6924 List<Element> queryAll(String selectors) =>
6928 new _FrozenElementList._wrap($dom_querySelectorAll(selectors)); 6925 new _FrozenElementList._wrap($dom_querySelectorAll(selectors));
6929 6926
6930 String get innerHtml { 6927 String get innerHtml {
6931 final e = new Element.tag("div"); 6928 final e = new Element.tag("div");
6932 e.append(this.clone(true)); 6929 e.append(this.clone(true));
6933 return e.innerHtml; 6930 return e.innerHtml;
6934 } 6931 }
6935 6932
6936 // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
6937 // SVG strings?
6938 void set innerHtml(String value) { 6933 void set innerHtml(String value) {
6934 this.setInnerHtml(value);
6935 }
6936
6937 void setInnerHtml(String html,
6938 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
6939
6939 this.nodes.clear(); 6940 this.nodes.clear();
6940 6941 append(document.body.createFragment(
6941 final e = new Element.tag("div"); 6942 html, validator: validator, treeSanitizer: treeSanitizer));
6942 e.innerHtml = value;
6943
6944 // Copy list first since we don't want liveness during iteration.
6945 List nodes = new List.from(e.nodes, growable: false);
6946 this.nodes.addAll(nodes);
6947 } 6943 }
6948 6944
6949 /** 6945 /**
6950 * Adds the specified text as a text node after the last child of this 6946 * Adds the specified text as a text node after the last child of this
6951 * document fragment. 6947 * document fragment.
6952 */ 6948 */
6953 void appendText(String text) { 6949 void appendText(String text) {
6954 this.append(new Text(text)); 6950 this.append(new Text(text));
6955 } 6951 }
6956 6952
(...skipping 496 matching lines...) Expand 10 before | Expand all | Expand 10 after
7453 } 7449 }
7454 } 7450 }
7455 7451
7456 /** 7452 /**
7457 * An abstract class, which all HTML elements extend. 7453 * An abstract class, which all HTML elements extend.
7458 */ 7454 */
7459 @DomName('Element') 7455 @DomName('Element')
7460 abstract class Element extends Node implements ElementTraversal native "Element" { 7456 abstract class Element extends Node implements ElementTraversal native "Element" {
7461 7457
7462 /** 7458 /**
7463 * Creates an HTML element from a valid fragment of HTML. 7459 * Creates an HTML element from a valid fragment of HTML.
Jennifer Messerly 2013/06/06 05:55:53 is this still true given restrictions around table
blois 2013/06/06 16:59:42 Done.
7464 * 7460 *
7465 * The [html] fragment must represent valid HTML with a single element root, 7461 * The [html] fragment must represent valid HTML with a single element root,
7466 * which will be parsed and returned. 7462 * which will be parsed and returned.
7467 * 7463 *
7468 * Important: the contents of [html] should not contain any user-supplied 7464 * Important: the contents of [html] should not contain any user-supplied
7469 * data. Without strict data validation it is impossible to prevent script 7465 * data. Without strict data validation it is impossible to prevent script
Jennifer Messerly 2013/06/06 05:55:53 update doc comment?
blois 2013/06/06 16:59:42 Done. Doc comments in general for this CL need a b
7470 * injection exploits. 7466 * injection exploits.
7471 * 7467 *
7472 * It is instead recommended that elements be constructed via [Element.tag] 7468 * It is instead recommended that elements be constructed via [Element.tag]
7473 * and text be added via [text]. 7469 * and text be added via [text].
7474 * 7470 *
7475 * var element = new Element.html('<div class="foo">content</div>'); 7471 * var element = new Element.html('<div class="foo">content</div>');
7476 */ 7472 */
7477 factory Element.html(String html) => 7473 factory Element.html(String html,
Jennifer Messerly 2013/06/06 05:55:53 add context argument? that way it can still be use
blois 2013/06/06 16:59:42 I was intending that table.createFragment(...) wou
7478 _ElementFactoryProvider.createElement_html(html); 7474 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
7475 var fragment = document.body.createFragment(html, validator: validator,
7476 treeSanitizer: treeSanitizer);
7477 return fragment.nodes.where((e) => e is Element).single;
Jennifer Messerly 2013/06/06 05:55:53 singleWhere ?
blois 2013/06/06 16:59:42 I did this to avoid the case (which seems somewhat
Jennifer Messerly 2013/06/06 19:31:32 oh, I was just thinking these two are equivalent:
7478 }
7479 7479
7480 /** 7480 /**
7481 * Creates the HTML element specified by the tag name. 7481 * Creates the HTML element specified by the tag name.
7482 * 7482 *
7483 * This is similar to [Document.createElement]. 7483 * This is similar to [Document.createElement].
7484 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then 7484 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
7485 * this will create an [UnknownElement]. 7485 * this will create an [UnknownElement].
7486 * 7486 *
7487 * var divElement = new Element.tag('div'); 7487 * var divElement = new Element.tag('div');
7488 * print(divElement is DivElement); // 'true' 7488 * print(divElement is DivElement); // 'true'
(...skipping 650 matching lines...) Expand 10 before | Expand all | Expand 10 after
8139 @Experimental 8139 @Experimental
8140 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; 8140 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
8141 8141
8142 void _ensureTemplate() { 8142 void _ensureTemplate() {
8143 if (!isTemplate) { 8143 if (!isTemplate) {
8144 throw new UnsupportedError('$this is not a template.'); 8144 throw new UnsupportedError('$this is not a template.');
8145 } 8145 }
8146 TemplateElement.decorate(this); 8146 TemplateElement.decorate(this);
8147 } 8147 }
8148 8148
8149 /**
8150 * Create a DocumentFragment from the HTML fragment and ensure that it follows
8151 * the sanitization rules specified by the validator or treeSanitizer.
8152 *
8153 * If the default validation behavior is too restrictive then a new
8154 * NodeValidator should be created, either extending or wrapping a default
8155 * validator and overriding the validation APIs.
8156 *
8157 * The treeSanitizer is used to walk the generated node tree and sanitize it.
8158 * A custom treeSanitizer can also be provided to perform special validation
8159 * rules but since the API is more complex to implement this is discouraged.
8160 *
8161 * The returned tree is guaranteed to only contain nodes and attributes which
8162 * are allowed by the provided validator.
8163 *
8164 * See also:
8165 *
8166 * * [NodeValidator]
8167 * * [NodeTreeSanitizer]
8168 */
8169 DocumentFragment createFragment(String html,
8170 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
8171
8172 if (treeSanitizer == null) {
8173 if (validator == null) {
8174 validator = new NodeValidatorBuilder.common();
8175 }
8176 treeSanitizer = new NodeTreeSanitizer(validator);
8177 }
Jennifer Messerly 2013/06/06 05:55:53 add error condition for case where validator is un
blois 2013/06/06 16:59:42 Done.
8178
8179 return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer);
8180 }
8181
8182 void set innerHtml(String html) {
8183 this.setInnerHtml(html);
8184 }
8185
8186 void setInnerHtml(String html,
8187 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
8188 text = null;
8189 append(createFragment(
8190 html, validator: validator, treeSanitizer: treeSanitizer));
8191 }
8192
8193 String get innerHtml => deprecatedInnerHtml;
8194
8149 8195
8150 @DomName('Element.abortEvent') 8196 @DomName('Element.abortEvent')
8151 @DocsEditable 8197 @DocsEditable
8152 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 8198 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
8153 8199
8154 @DomName('Element.beforecopyEvent') 8200 @DomName('Element.beforecopyEvent')
8155 @DocsEditable 8201 @DocsEditable
8156 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 8202 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
8157 8203
8158 @DomName('Element.beforecutEvent') 8204 @DomName('Element.beforecutEvent')
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
8370 @DocsEditable 8416 @DocsEditable
8371 bool hidden; 8417 bool hidden;
8372 8418
8373 @DomName('Element.id') 8419 @DomName('Element.id')
8374 @DocsEditable 8420 @DocsEditable
8375 String id; 8421 String id;
8376 8422
8377 @JSName('innerHTML') 8423 @JSName('innerHTML')
8378 @DomName('Element.innerHTML') 8424 @DomName('Element.innerHTML')
8379 @DocsEditable 8425 @DocsEditable
8380 String innerHtml; 8426 String deprecatedInnerHtml;
8381 8427
8382 @DomName('Element.isContentEditable') 8428 @DomName('Element.isContentEditable')
8383 @DocsEditable 8429 @DocsEditable
8384 final bool isContentEditable; 8430 final bool isContentEditable;
8385 8431
8386 @DomName('Element.lang') 8432 @DomName('Element.lang')
8387 @DocsEditable 8433 @DocsEditable
8388 String lang; 8434 String lang;
8389 8435
8390 @JSName('outerHTML') 8436 @JSName('outerHTML')
(...skipping 558 matching lines...) Expand 10 before | Expand all | Expand 10 after
8949 8995
8950 @DomName('Element.onwebkitfullscreenerror') 8996 @DomName('Element.onwebkitfullscreenerror')
8951 @DocsEditable 8997 @DocsEditable
8952 // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html 8998 // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
8953 @Experimental 8999 @Experimental
8954 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 9000 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
8955 9001
8956 } 9002 }
8957 9003
8958 9004
8959 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
8960 class _ElementFactoryProvider { 9005 class _ElementFactoryProvider {
8961 static const _CUSTOM_PARENT_TAG_MAP = const {
Jennifer Messerly 2013/06/06 05:55:53 awesome to see this go!
8962 'body' : 'html',
8963 'head' : 'html',
8964 'caption' : 'table',
8965 'td': 'tr',
8966 'th': 'tr',
8967 'colgroup': 'table',
8968 'col' : 'colgroup',
8969 'tr' : 'tbody',
8970 'tbody' : 'table',
8971 'tfoot' : 'table',
8972 'thead' : 'table',
8973 'track' : 'audio',
8974 };
8975 9006
8976 @DomName('Document.createElement') 9007 static DocumentFragment _parseHtml(Element context, String html,
8977 static Element createElement_html(String html) { 9008 NodeTreeSanitizer treeSanitizer) {
8978 // TODO(jacobr): this method can be made more robust and performant. 9009
8979 // 1) Cache the dummy parent elements required to use innerHTML rather than 9010 var doc = document.implementation.createHtmlDocument('');
8980 // creating them every call. 9011 var contextElement;
8981 // 2) Verify that the html does not contain leading or trailing text nodes. 9012 if (context == null || context is BodyElement) {
8982 // 3) Verify that the html does not contain both <head> and <body> tags. 9013 contextElement = doc.body;
8983 // 4) Detatch the created element from its dummy parent. 9014 } else {
8984 String parentTag = 'div'; 9015 contextElement = doc.$dom_createElement(context.tagName);
8985 String tag; 9016 doc.body.append(contextElement);
8986 final match = _START_TAG_REGEXP.firstMatch(html);
8987 if (match != null) {
8988 tag = match.group(1).toLowerCase();
8989 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
8990 return _createTableForIE(html, tag);
8991 }
8992 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
8993 if (parentTag == null) parentTag = 'div';
8994 } 9017 }
8995 9018
8996 final temp = new Element.tag(parentTag); 9019 if (Range.supportsCreateContextualFragment) {
8997 temp.innerHtml = html; 9020 var range = doc.$dom_createRange();
9021 range.selectNodeContents(contextElement);
9022 var fragment = range.createContextualFragment(html);
8998 9023
8999 Element element; 9024 treeSanitizer.sanitizeTree(fragment);
9000 if (temp.children.length == 1) { 9025 return fragment;
9001 element = temp.children[0];
9002 } else if (parentTag == 'html' && temp.children.length == 2) {
9003 // In html5 the root <html> tag will always have a <body> and a <head>,
9004 // even though the inner html only contains one of them.
9005 element = temp.children[tag == 'head' ? 0 : 1];
9006 } else { 9026 } else {
9007 _singleNode(temp.children); 9027 contextElement.deprecatedInnerHtml = html;
9028
9029 treeSanitizer.sanitizeTree(contextElement);
9030
9031 var fragment = new DocumentFragment();
9032 while (contextElement.$dom_firstChild != null) {
9033 fragment.append(contextElement.$dom_firstChild);
9034 }
9035 return fragment;
9008 } 9036 }
9009 element.remove();
9010 return element;
9011 }
9012
9013 /**
9014 * IE table elements don't support innerHTML (even in standards mode).
9015 * Instead we use a div and inject the table element in the innerHtml string.
9016 * This technique works on other browsers too, but it's probably slower,
9017 * so we only use it when running on IE.
9018 *
9019 * See also innerHTML:
9020 * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx>
9021 * and Building Tables Dynamically:
9022 * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>.
9023 */
9024 static Element _createTableForIE(String html, String tag) {
9025 var div = new Element.tag('div');
9026 div.innerHtml = '<table>$html</table>';
9027 var table = _singleNode(div.children);
9028 Element element;
9029 switch (tag) {
9030 case 'td':
9031 case 'th':
9032 TableRowElement row = _singleNode(table.rows);
9033 element = _singleNode(row.cells);
9034 break;
9035 case 'tr':
9036 element = _singleNode(table.rows);
9037 break;
9038 case 'tbody':
9039 element = _singleNode(table.tBodies);
9040 break;
9041 case 'thead':
9042 element = table.tHead;
9043 break;
9044 case 'tfoot':
9045 element = table.tFoot;
9046 break;
9047 case 'caption':
9048 element = table.caption;
9049 break;
9050 case 'colgroup':
9051 element = _getColgroup(table);
9052 break;
9053 case 'col':
9054 element = _singleNode(_getColgroup(table).children);
9055 break;
9056 }
9057 element.remove();
9058 return element;
9059 }
9060
9061 static TableColElement _getColgroup(TableElement table) {
9062 // TODO(jmesserly): is there a better way to do this?
9063 return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP')
9064 .toList());
9065 }
9066
9067 static Node _singleNode(List<Node> list) {
9068 if (list.length == 1) return list[0];
9069 throw new ArgumentError('HTML had ${list.length} '
9070 'top level elements but 1 expected');
9071 } 9037 }
9072 9038
9073 @DomName('Document.createElement') 9039 @DomName('Document.createElement')
9074 // Optimization to improve performance until the dart2js compiler inlines this 9040 // Optimization to improve performance until the dart2js compiler inlines this
9075 // method. 9041 // method.
9076 static dynamic createElement_tag(String tag) => 9042 static dynamic createElement_tag(String tag) =>
9077 // Firefox may return a JS function for some types (Embed, Object). 9043 // Firefox may return a JS function for some types (Embed, Object).
9078 JS('Element|=Object', 'document.createElement(#)', tag); 9044 JS('Element|=Object', 'document.createElement(#)', tag);
9079 } 9045 }
9080 9046
(...skipping 11527 matching lines...) Expand 10 before | Expand all | Expand 10 after
20608 }'''; 20574 }''';
20609 document.head.append(style); 20575 document.head.append(style);
20610 } 20576 }
20611 20577
20612 /** 20578 /**
20613 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for 20579 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for
20614 * more information. 20580 * more information.
20615 */ 20581 */
20616 @Experimental 20582 @Experimental
20617 static Map<String, CustomBindingSyntax> syntax = {}; 20583 static Map<String, CustomBindingSyntax> syntax = {};
20584
20585 // An override to place the contents into content rather than as child nodes.
20586 void setInnerHtml(String html,
20587 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
20588 text = null;
20589 var fragment = createFragment(
20590 html, validator: validator, treeSanitizer: treeSanitizer);
20591
20592 content.append(fragment);
20593 }
20618 } 20594 }
20619 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20595 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
20620 // for details. All rights reserved. Use of this source code is governed by a 20596 // for details. All rights reserved. Use of this source code is governed by a
20621 // BSD-style license that can be found in the LICENSE file. 20597 // BSD-style license that can be found in the LICENSE file.
20622 20598
20623 // WARNING: Do not edit - generated code. 20599 // WARNING: Do not edit - generated code.
20624 20600
20625 20601
20626 @DomName('Text') 20602 @DomName('Text')
20627 class Text extends CharacterData native "Text" { 20603 class Text extends CharacterData native "Text" {
(...skipping 5055 matching lines...) Expand 10 before | Expand all | Expand 10 after
25683 const _CustomEventStreamProvider(this._eventTypeGetter); 25659 const _CustomEventStreamProvider(this._eventTypeGetter);
25684 25660
25685 Stream<T> forTarget(EventTarget e, {bool useCapture: false}) { 25661 Stream<T> forTarget(EventTarget e, {bool useCapture: false}) {
25686 return new _EventStream(e, _eventTypeGetter(e), useCapture); 25662 return new _EventStream(e, _eventTypeGetter(e), useCapture);
25687 } 25663 }
25688 25664
25689 String getEventType(EventTarget target) { 25665 String getEventType(EventTarget target) {
25690 return _eventTypeGetter(target); 25666 return _eventTypeGetter(target);
25691 } 25667 }
25692 } 25668 }
25669 // DO NOT EDIT- this file is generated from running tool/generator.sh.
25670
25671 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25672 // for details. All rights reserved. Use of this source code is governed by a
25673 // BSD-style license that can be found in the LICENSE file.
25674
25675
25676 /**
25677 * A Dart DOM validator generated from Caja whitelists.
25678 *
25679 * This contains a whitelist of known HTML tagNames and attributes and will only
25680 * accept known good values.
25681 *
25682 * See also:
25683 *
25684 * * https://code.google.com/p/google-caja/wiki/CajaWhitelists
25685 */
25686 class _Html5NodeValidator implements NodeValidator {
25687 static final Set<String> _allowedElements = new Set.from([
25688 'A',
25689 'ABBR',
25690 'ACRONYM',
25691 'ADDRESS',
25692 'AREA',
25693 'ARTICLE',
25694 'ASIDE',
25695 'AUDIO',
25696 'B',
25697 'BDI',
25698 'BDO',
25699 'BIG',
25700 'BLOCKQUOTE',
25701 'BR',
25702 'BUTTON',
25703 'CANVAS',
25704 'CAPTION',
25705 'CENTER',
25706 'CITE',
25707 'CODE',
25708 'COL',
25709 'COLGROUP',
25710 'COMMAND',
25711 'DATA',
25712 'DATALIST',
25713 'DD',
25714 'DEL',
25715 'DETAILS',
25716 'DFN',
25717 'DIR',
25718 'DIV',
25719 'DL',
25720 'DT',
25721 'EM',
25722 'FIELDSET',
25723 'FIGCAPTION',
25724 'FIGURE',
25725 'FONT',
25726 'FOOTER',
25727 'FORM',
25728 'H1',
25729 'H2',
25730 'H3',
25731 'H4',
25732 'H5',
25733 'H6',
25734 'HEADER',
25735 'HGROUP',
25736 'HR',
25737 'I',
25738 'IFRAME',
25739 'IMG',
25740 'INPUT',
25741 'INS',
25742 'KBD',
25743 'LABEL',
25744 'LEGEND',
25745 'LI',
25746 'MAP',
25747 'MARK',
25748 'MENU',
25749 'METER',
25750 'NAV',
25751 'NOBR',
25752 'OL',
25753 'OPTGROUP',
25754 'OPTION',
25755 'OUTPUT',
25756 'P',
25757 'PRE',
25758 'PROGRESS',
25759 'Q',
25760 'S',
25761 'SAMP',
25762 'SECTION',
25763 'SELECT',
25764 'SMALL',
25765 'SOURCE',
25766 'SPAN',
25767 'STRIKE',
25768 'STRONG',
25769 'SUB',
25770 'SUMMARY',
25771 'SUP',
25772 'TABLE',
25773 'TBODY',
25774 'TD',
25775 'TEXTAREA',
25776 'TFOOT',
25777 'TH',
25778 'THEAD',
25779 'TIME',
25780 'TR',
25781 'TRACK',
25782 'TT',
25783 'U',
25784 'UL',
25785 'VAR',
25786 'VIDEO',
25787 'WBR',
25788 ]);
25789
25790 static const _standardAttributes = const <String>[
25791 '*::class',
25792 '*::dir',
25793 '*::draggable',
25794 '*::hidden',
25795 '*::id',
25796 '*::inert',
25797 '*::itemprop',
25798 '*::itemref',
25799 '*::itemscope',
25800 '*::lang',
25801 '*::spellcheck',
25802 '*::title',
25803 '*::translate',
25804 'A::accesskey',
25805 'A::coords',
25806 'A::hreflang',
25807 'A::name',
25808 'A::shape',
25809 'A::tabindex',
25810 'A::target',
25811 'A::type',
25812 'AREA::accesskey',
25813 'AREA::alt',
25814 'AREA::coords',
25815 'AREA::nohref',
25816 'AREA::shape',
25817 'AREA::tabindex',
25818 'AREA::target',
25819 'AUDIO::controls',
25820 'AUDIO::loop',
25821 'AUDIO::mediagroup',
25822 'AUDIO::muted',
25823 'AUDIO::preload',
25824 'BDO::dir',
25825 'BODY::alink',
25826 'BODY::bgcolor',
25827 'BODY::link',
25828 'BODY::text',
25829 'BODY::vlink',
25830 'BR::clear',
25831 'BUTTON::accesskey',
25832 'BUTTON::disabled',
25833 'BUTTON::name',
25834 'BUTTON::tabindex',
25835 'BUTTON::type',
25836 'BUTTON::value',
25837 'CANVAS::height',
25838 'CANVAS::width',
25839 'CAPTION::align',
25840 'COL::align',
25841 'COL::char',
25842 'COL::charoff',
25843 'COL::span',
25844 'COL::valign',
25845 'COL::width',
25846 'COLGROUP::align',
25847 'COLGROUP::char',
25848 'COLGROUP::charoff',
25849 'COLGROUP::span',
25850 'COLGROUP::valign',
25851 'COLGROUP::width',
25852 'COMMAND::checked',
25853 'COMMAND::command',
25854 'COMMAND::disabled',
25855 'COMMAND::label',
25856 'COMMAND::radiogroup',
25857 'COMMAND::type',
25858 'DATA::value',
25859 'DEL::datetime',
25860 'DETAILS::open',
25861 'DIR::compact',
25862 'DIV::align',
25863 'DL::compact',
25864 'FIELDSET::disabled',
25865 'FONT::color',
25866 'FONT::face',
25867 'FONT::size',
25868 'FORM::accept',
25869 'FORM::autocomplete',
25870 'FORM::enctype',
25871 'FORM::method',
25872 'FORM::name',
25873 'FORM::novalidate',
25874 'FORM::target',
25875 'FRAME::name',
25876 'H1::align',
25877 'H2::align',
25878 'H3::align',
25879 'H4::align',
25880 'H5::align',
25881 'H6::align',
25882 'HR::align',
25883 'HR::noshade',
25884 'HR::size',
25885 'HR::width',
25886 'HTML::version',
25887 'IFRAME::align',
25888 'IFRAME::frameborder',
25889 'IFRAME::height',
25890 'IFRAME::marginheight',
25891 'IFRAME::marginwidth',
25892 'IFRAME::width',
25893 'IMG::align',
25894 'IMG::alt',
25895 'IMG::border',
25896 'IMG::height',
25897 'IMG::hspace',
25898 'IMG::ismap',
25899 'IMG::name',
25900 'IMG::usemap',
25901 'IMG::vspace',
25902 'IMG::width',
25903 'INPUT::accept',
25904 'INPUT::accesskey',
25905 'INPUT::align',
25906 'INPUT::alt',
25907 'INPUT::autocomplete',
25908 'INPUT::checked',
25909 'INPUT::disabled',
25910 'INPUT::inputmode',
25911 'INPUT::ismap',
25912 'INPUT::list',
25913 'INPUT::max',
25914 'INPUT::maxlength',
25915 'INPUT::min',
25916 'INPUT::multiple',
25917 'INPUT::name',
25918 'INPUT::placeholder',
25919 'INPUT::readonly',
25920 'INPUT::required',
25921 'INPUT::size',
25922 'INPUT::step',
25923 'INPUT::tabindex',
25924 'INPUT::type',
25925 'INPUT::usemap',
25926 'INPUT::value',
25927 'INS::datetime',
25928 'KEYGEN::disabled',
25929 'KEYGEN::keytype',
25930 'KEYGEN::name',
25931 'LABEL::accesskey',
25932 'LABEL::for',
25933 'LEGEND::accesskey',
25934 'LEGEND::align',
25935 'LI::type',
25936 'LI::value',
25937 'LINK::sizes',
25938 'MAP::name',
25939 'MENU::compact',
25940 'MENU::label',
25941 'MENU::type',
25942 'METER::high',
25943 'METER::low',
25944 'METER::max',
25945 'METER::min',
25946 'METER::value',
25947 'OBJECT::typemustmatch',
25948 'OL::compact',
25949 'OL::reversed',
25950 'OL::start',
25951 'OL::type',
25952 'OPTGROUP::disabled',
25953 'OPTGROUP::label',
25954 'OPTION::disabled',
25955 'OPTION::label',
25956 'OPTION::selected',
25957 'OPTION::value',
25958 'OUTPUT::for',
25959 'OUTPUT::name',
25960 'P::align',
25961 'PRE::width',
25962 'PROGRESS::max',
25963 'PROGRESS::min',
25964 'PROGRESS::value',
25965 'SELECT::autocomplete',
25966 'SELECT::disabled',
25967 'SELECT::multiple',
25968 'SELECT::name',
25969 'SELECT::required',
25970 'SELECT::size',
25971 'SELECT::tabindex',
25972 'SOURCE::type',
25973 'TABLE::align',
25974 'TABLE::bgcolor',
25975 'TABLE::border',
25976 'TABLE::cellpadding',
25977 'TABLE::cellspacing',
25978 'TABLE::frame',
25979 'TABLE::rules',
25980 'TABLE::summary',
25981 'TABLE::width',
25982 'TBODY::align',
25983 'TBODY::char',
25984 'TBODY::charoff',
25985 'TBODY::valign',
25986 'TD::abbr',
25987 'TD::align',
25988 'TD::axis',
25989 'TD::bgcolor',
25990 'TD::char',
25991 'TD::charoff',
25992 'TD::colspan',
25993 'TD::headers',
25994 'TD::height',
25995 'TD::nowrap',
25996 'TD::rowspan',
25997 'TD::scope',
25998 'TD::valign',
25999 'TD::width',
26000 'TEXTAREA::accesskey',
26001 'TEXTAREA::autocomplete',
26002 'TEXTAREA::cols',
26003 'TEXTAREA::disabled',
26004 'TEXTAREA::inputmode',
26005 'TEXTAREA::name',
26006 'TEXTAREA::placeholder',
26007 'TEXTAREA::readonly',
26008 'TEXTAREA::required',
26009 'TEXTAREA::rows',
26010 'TEXTAREA::tabindex',
26011 'TEXTAREA::wrap',
26012 'TFOOT::align',
26013 'TFOOT::char',
26014 'TFOOT::charoff',
26015 'TFOOT::valign',
26016 'TH::abbr',
26017 'TH::align',
26018 'TH::axis',
26019 'TH::bgcolor',
26020 'TH::char',
26021 'TH::charoff',
26022 'TH::colspan',
26023 'TH::headers',
26024 'TH::height',
26025 'TH::nowrap',
26026 'TH::rowspan',
26027 'TH::scope',
26028 'TH::valign',
26029 'TH::width',
26030 'THEAD::align',
26031 'THEAD::char',
26032 'THEAD::charoff',
26033 'THEAD::valign',
26034 'TR::align',
26035 'TR::bgcolor',
26036 'TR::char',
26037 'TR::charoff',
26038 'TR::valign',
26039 'TRACK::default',
26040 'TRACK::kind',
26041 'TRACK::label',
26042 'TRACK::srclang',
26043 'UL::compact',
26044 'UL::type',
26045 'VIDEO::controls',
26046 'VIDEO::height',
26047 'VIDEO::loop',
26048 'VIDEO::mediagroup',
26049 'VIDEO::muted',
26050 'VIDEO::preload',
26051 'VIDEO::width',
26052 ];
26053
26054 static const _uriAttributes = const <String>[
26055 'A::href',
26056 'AREA::href',
26057 'BLOCKQUOTE::cite',
26058 'BODY::background',
26059 'COMMAND::icon',
26060 'DEL::cite',
26061 'FORM::action',
26062 'IMG::src',
26063 'INPUT::src',
26064 'INS::cite',
26065 'Q::cite',
26066 'VIDEO::poster',
26067 ];
26068
26069 final UriPolicy uriPolicy;
26070
26071 static final Map<String, Function> _attributeValidators = {};
26072
26073 /**
26074 * All known URI attributes will be validated against the UriPolicy, if
26075 * [uriPolicy] is null then a default UriPolicy will be used.
26076 */
26077 _Html5NodeValidator({UriPolicy uriPolicy}):
26078 this.uriPolicy = uriPolicy != null ? uriPolicy : new UriPolicy() {
Jennifer Messerly 2013/06/06 05:55:53 style, I think ":" goes on next line, and "this."
blois 2013/06/06 16:59:42 Done.
26079
26080 if (_attributeValidators.isEmpty) {
26081 for (var attr in _standardAttributes) {
26082 _attributeValidators[attr] = _standardAttributeValidator;
26083 }
26084
26085 for (var attr in _uriAttributes) {
26086 _attributeValidators[attr] = _uriAttributeValidator;
26087 }
26088 }
26089 }
26090
26091 bool allowsElement(Element element) {
26092 return _allowedElements.contains(element.tagName);
26093 }
26094
26095 bool allowsAttribute(Element element, String attributeName, String value) {
26096 var tagName = element.tagName;
26097 var validator = _attributeValidators['$tagName::$attributeName'];
26098 if (validator == null) {
26099 validator = _attributeValidators['*::$attributeName'];
26100 }
26101 if (validator == null) {
26102 return false;
26103 }
26104 return validator(element, attributeName, value, this);
26105 }
26106
26107 static bool _standardAttributeValidator(Element element, String attributeName,
26108 String value, _Html5NodeValidator context) {
26109 return true;
26110 }
26111
26112 static bool _uriAttributeValidator(Element element, String attributeName,
26113 String value, _Html5NodeValidator context) {
26114 return context.uriPolicy.allowsUri(value);
26115 }
26116 }
25693 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 26117 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
25694 // for details. All rights reserved. Use of this source code is governed by a 26118 // for details. All rights reserved. Use of this source code is governed by a
25695 // BSD-style license that can be found in the LICENSE file. 26119 // BSD-style license that can be found in the LICENSE file.
25696 26120
25697 26121
25698 abstract class ImmutableListMixin<E> implements List<E> { 26122 abstract class ImmutableListMixin<E> implements List<E> {
25699 // From Iterable<$E>: 26123 // From Iterable<$E>:
25700 Iterator<E> get iterator { 26124 Iterator<E> get iterator {
25701 // Note: NodeLists are not fixed size. And most probably length shouldn't 26125 // Note: NodeLists are not fixed size. And most probably length shouldn't
25702 // be cached in both iterator _and_ forEach method. For now caching it 26126 // be cached in both iterator _and_ forEach method. For now caching it
(...skipping 61 matching lines...) Expand 10 before | Expand all | Expand 10 after
25764 26188
25765 void fillRange(int start, int end, [E fillValue]) { 26189 void fillRange(int start, int end, [E fillValue]) {
25766 throw new UnsupportedError("Cannot modify an immutable List."); 26190 throw new UnsupportedError("Cannot modify an immutable List.");
25767 } 26191 }
25768 } 26192 }
25769 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 26193 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
25770 // for details. All rights reserved. Use of this source code is governed by a 26194 // for details. All rights reserved. Use of this source code is governed by a
25771 // BSD-style license that can be found in the LICENSE file. 26195 // BSD-style license that can be found in the LICENSE file.
25772 26196
25773 26197
25774 /** 26198 _serialize(var message) {
25775 * Internal class that does the actual calculations to determine keyCode and 26199 return new _JsSerializer().traverse(message);
25776 * charCode for keydown, keypress, and keyup events for all browsers. 26200 }
25777 */ 26201
25778 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> { 26202 class _JsSerializer extends _Serializer {
25779 // This code inspired by Closure's KeyHandling library. 26203
25780 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl er.js.source.html 26204 visitSendPortSync(SendPortSync x) {
25781 26205 if (x is _JsSendPortSync) return visitJsSendPortSync(x);
25782 /** 26206 if (x is _LocalSendPortSync) return visitLocalSendPortSync(x);
25783 * The set of keys that have been pressed down without seeing their 26207 if (x is _RemoteSendPortSync) return visitRemoteSendPortSync(x);
25784 * corresponding keyup event. 26208 throw "Unknown port type $x";
25785 */ 26209 }
25786 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[]; 26210
25787 26211 visitJsSendPortSync(_JsSendPortSync x) {
25788 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ 26212 return [ 'sendport', 'nativejs', x._id ];
25789 final String _type; 26213 }
25790 26214
25791 /** The element we are watching for events to happen on. */ 26215 visitLocalSendPortSync(_LocalSendPortSync x) {
25792 final EventTarget _target; 26216 return [ 'sendport', 'dart',
25793 26217 ReceivePortSync._isolateId, x._receivePort._portId ];
25794 // The distance to shift from upper case alphabet Roman letters to lower case. 26218 }
25795 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; 26219
25796 26220 visitSendPort(SendPort x) {
25797 /** Controller to produce KeyEvents for the stream. */ 26221 throw new UnimplementedError('Asynchronous send port not yet implemented.');
25798 final StreamController _controller = new StreamController(sync: true); 26222 }
25799 26223
25800 static const _EVENT_TYPE = 'KeyEvent'; 26224 visitRemoteSendPortSync(_RemoteSendPortSync x) {
25801 26225 return [ 'sendport', 'dart', x._isolateId, x._portId ];
25802 /** 26226 }
25803 * An enumeration of key identifiers currently part of the W3C draft for DOM3 26227 }
25804 * and their mappings to keyCodes. 26228
25805 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set 26229 _deserialize(var message) {
25806 */ 26230 return new _JsDeserializer().deserialize(message);
25807 static const Map<String, int> _keyIdentifier = const { 26231 }
25808 'Up': KeyCode.UP, 26232
25809 'Down': KeyCode.DOWN, 26233
25810 'Left': KeyCode.LEFT, 26234 class _JsDeserializer extends _Deserializer {
25811 'Right': KeyCode.RIGHT, 26235
25812 'Enter': KeyCode.ENTER, 26236 static const _UNSPECIFIED = const Object();
25813 'F1': KeyCode.F1, 26237
25814 'F2': KeyCode.F2, 26238 deserializeSendPort(List x) {
25815 'F3': KeyCode.F3, 26239 String tag = x[1];
25816 'F4': KeyCode.F4, 26240 switch (tag) {
25817 'F5': KeyCode.F5, 26241 case 'nativejs':
25818 'F6': KeyCode.F6, 26242 num id = x[2];
25819 'F7': KeyCode.F7, 26243 return new _JsSendPortSync(id);
25820 'F8': KeyCode.F8, 26244 case 'dart':
25821 'F9': KeyCode.F9, 26245 num isolateId = x[2];
25822 'F10': KeyCode.F10, 26246 num portId = x[3];
25823 'F11': KeyCode.F11, 26247 return ReceivePortSync._lookup(isolateId, portId);
25824 'F12': KeyCode.F12, 26248 default:
25825 'U+007F': KeyCode.DELETE, 26249 throw 'Illegal SendPortSync type: $tag';
25826 'Home': KeyCode.HOME, 26250 }
25827 'End': KeyCode.END, 26251 }
25828 'PageUp': KeyCode.PAGE_UP, 26252 }
25829 'PageDown': KeyCode.PAGE_DOWN, 26253
25830 'Insert': KeyCode.INSERT 26254 // The receiver is JS.
25831 }; 26255 class _JsSendPortSync implements SendPortSync {
25832 26256
25833 /** Return a stream for KeyEvents for the specified target. */ 26257 final num _id;
25834 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) { 26258 _JsSendPortSync(this._id);
25835 return new _KeyboardEventHandler.initializeAllEventListeners( 26259
25836 _type, e).stream; 26260 callSync(var message) {
25837 } 26261 var serialized = _serialize(message);
25838 26262 var result = _callPortSync(_id, serialized);
25839 /** 26263 return _deserialize(result);
25840 * Accessor to the stream associated with a particular KeyboardEvent 26264 }
25841 * EventTarget. 26265
25842 * 26266 bool operator==(var other) {
25843 * [forTarget] must be called to initialize this stream to listen to a 26267 return (other is _JsSendPortSync) && (_id == other._id);
25844 * particular EventTarget. 26268 }
25845 */ 26269
25846 Stream<KeyEvent> get stream { 26270 int get hashCode => _id;
25847 if(_target != null) { 26271 }
25848 return _controller.stream; 26272
26273 // TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
26274 // The receiver is a different Dart isolate, compiled to JS.
26275 class _RemoteSendPortSync implements SendPortSync {
26276
26277 int _isolateId;
26278 int _portId;
26279 _RemoteSendPortSync(this._isolateId, this._portId);
26280
26281 callSync(var message) {
26282 var serialized = _serialize(message);
26283 var result = _call(_isolateId, _portId, serialized);
26284 return _deserialize(result);
26285 }
26286
26287 static _call(int isolateId, int portId, var message) {
26288 var target = 'dart-port-$isolateId-$portId';
26289 // TODO(vsm): Make this re-entrant.
26290 // TODO(vsm): Set this up set once, on the first call.
26291 var source = '$target-result';
26292 var result = null;
26293 window.on[source].first.then((Event e) {
26294 result = json.parse(_getPortSyncEventData(e));
26295 });
26296 _dispatchEvent(target, [source, message]);
26297 return result;
26298 }
26299
26300 bool operator==(var other) {
26301 return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
26302 && (_portId == other._portId);
26303 }
26304
26305 int get hashCode => _isolateId >> 16 + _portId;
26306 }
26307
26308 // The receiver is in the same Dart isolate, compiled to JS.
26309 class _LocalSendPortSync implements SendPortSync {
26310
26311 ReceivePortSync _receivePort;
26312
26313 _LocalSendPortSync._internal(this._receivePort);
26314
26315 callSync(var message) {
26316 // TODO(vsm): Do a more efficient deep copy.
26317 var copy = _deserialize(_serialize(message));
26318 var result = _receivePort._callback(copy);
26319 return _deserialize(_serialize(result));
26320 }
26321
26322 bool operator==(var other) {
26323 return (other is _LocalSendPortSync)
26324 && (_receivePort == other._receivePort);
26325 }
26326
26327 int get hashCode => _receivePort.hashCode;
26328 }
26329
26330 // TODO(vsm): Move this to dart:isolate. This will take some
26331 // refactoring as there are dependences here on the DOM. Users
26332 // interact with this class (or interface if we change it) directly -
26333 // new ReceivePortSync. I think most of the DOM logic could be
26334 // delayed until the corresponding SendPort is registered on the
26335 // window.
26336
26337 // A Dart ReceivePortSync (tagged 'dart' when serialized) is
26338 // identifiable / resolvable by the combination of its isolateid and
26339 // portid. When a corresponding SendPort is used within the same
26340 // isolate, the _portMap below can be used to obtain the
26341 // ReceivePortSync directly. Across isolates (or from JS), an
26342 // EventListener can be used to communicate with the port indirectly.
26343 class ReceivePortSync {
26344
26345 static Map<int, ReceivePortSync> _portMap;
26346 static int _portIdCount;
26347 static int _cachedIsolateId;
26348
26349 num _portId;
26350 Function _callback;
26351 StreamSubscription _portSubscription;
26352
26353 ReceivePortSync() {
26354 if (_portIdCount == null) {
26355 _portIdCount = 0;
26356 _portMap = new Map<int, ReceivePortSync>();
26357 }
26358 _portId = _portIdCount++;
26359 _portMap[_portId] = this;
26360 }
26361
26362 static int get _isolateId {
26363 // TODO(vsm): Make this coherent with existing isolate code.
26364 if (_cachedIsolateId == null) {
26365 _cachedIsolateId = _getNewIsolateId();
26366 }
26367 return _cachedIsolateId;
26368 }
26369
26370 static String _getListenerName(isolateId, portId) =>
26371 'dart-port-$isolateId-$portId';
26372 String get _listenerName => _getListenerName(_isolateId, _portId);
26373
26374 void receive(callback(var message)) {
26375 _callback = callback;
26376 if (_portSubscription == null) {
26377 _portSubscription = window.on[_listenerName].listen((Event e) {
26378 var data = json.parse(_getPortSyncEventData(e));
26379 var replyTo = data[0];
26380 var message = _deserialize(data[1]);
26381 var result = _callback(message);
26382 _dispatchEvent(replyTo, _serialize(result));
26383 });
26384 }
26385 }
26386
26387 void close() {
26388 _portMap.remove(_portId);
26389 if (_portSubscription != null) _portSubscription.cancel();
26390 }
26391
26392 SendPortSync toSendPort() {
26393 return new _LocalSendPortSync._internal(this);
26394 }
26395
26396 static SendPortSync _lookup(int isolateId, int portId) {
26397 if (isolateId == _isolateId) {
26398 return _portMap[portId].toSendPort();
25849 } else { 26399 } else {
25850 throw new StateError("Not initialized. Call forTarget to access a stream " 26400 return new _RemoteSendPortSync(isolateId, portId);
25851 "initialized with a particular EventTarget."); 26401 }
25852 } 26402 }
25853 } 26403 }
25854 26404
25855 /** 26405 get _isolateId => ReceivePortSync._isolateId;
25856 * General constructor, performs basic initialization for our improved 26406
25857 * KeyboardEvent controller. 26407 void _dispatchEvent(String receiver, var message) {
25858 */ 26408 var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
25859 _KeyboardEventHandler(this._type) : 26409 detail: json.stringify(message));
25860 _target = null, super(_EVENT_TYPE) { 26410 window.dispatchEvent(event);
25861 } 26411 }
25862 26412
25863 /** 26413 String _getPortSyncEventData(CustomEvent event) => event.detail;
25864 * Hook up all event listeners under the covers so we can estimate keycodes
25865 * and charcodes when they are not provided.
25866 */
25867 _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) :
25868 super(_EVENT_TYPE) {
25869 Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
25870 processKeyDown);
25871 Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
25872 processKeyPress);
25873 Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
25874 processKeyUp);
25875 }
25876
25877 /**
25878 * Notify all callback listeners that a KeyEvent of the relevant type has
25879 * occurred.
25880 */
25881 bool _dispatch(KeyEvent event) {
25882 if (event.type == _type)
25883 _controller.add(event);
25884 }
25885
25886 /** Determine if caps lock is one of the currently depressed keys. */
25887 bool get _capsLockOn =>
25888 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
25889
25890 /**
25891 * Given the previously recorded keydown key codes, see if we can determine
25892 * the keycode of this keypress [event]. (Generally browsers only provide
25893 * charCode information for keypress events, but with a little
25894 * reverse-engineering, we can also determine the keyCode.) Returns
25895 * KeyCode.UNKNOWN if the keycode could not be determined.
25896 */
25897 int _determineKeyCodeForKeypress(KeyboardEvent event) {
25898 // Note: This function is a work in progress. We'll expand this function
25899 // once we get more information about other keyboards.
25900 for (var prevEvent in _keyDownList) {
25901 if (prevEvent._shadowCharCode == event.charCode) {
25902 return prevEvent.keyCode;
25903 }
25904 if ((event.shiftKey || _capsLockOn) && event.charCode >= "A".codeUnits[0]
25905 && event.charCode <= "Z".codeUnits[0] && event.charCode +
25906 _ROMAN_ALPHABET_OFFSET == prevEvent._shadowCharCode) {
25907 return prevEvent.keyCode;
25908 }
25909 }
25910 return KeyCode.UNKNOWN;
25911 }
25912
25913 /**
25914 * Given the charater code returned from a keyDown [event], try to ascertain
25915 * and return the corresponding charCode for the character that was pressed.
25916 * This information is not shown to the user, but used to help polyfill
25917 * keypress events.
25918 */
25919 int _findCharCodeKeyDown(KeyboardEvent event) {
25920 if (event.keyLocation == 3) { // Numpad keys.
25921 switch (event.keyCode) {
25922 case KeyCode.NUM_ZERO:
25923 // Even though this function returns _charCodes_, for some cases the
25924 // KeyCode == the charCode we want, in which case we use the keycode
25925 // constant for readability.
25926 return KeyCode.ZERO;
25927 case KeyCode.NUM_ONE:
25928 return KeyCode.ONE;
25929 case KeyCode.NUM_TWO:
25930 return KeyCode.TWO;
25931 case KeyCode.NUM_THREE:
25932 return KeyCode.THREE;
25933 case KeyCode.NUM_FOUR:
25934 return KeyCode.FOUR;
25935 case KeyCode.NUM_FIVE:
25936 return KeyCode.FIVE;
25937 case KeyCode.NUM_SIX:
25938 return KeyCode.SIX;
25939 case KeyCode.NUM_SEVEN:
25940 return KeyCode.SEVEN;
25941 case KeyCode.NUM_EIGHT:
25942 return KeyCode.EIGHT;
25943 case KeyCode.NUM_NINE:
25944 return KeyCode.NINE;
25945 case KeyCode.NUM_MULTIPLY:
25946 return 42; // Char code for *
25947 case KeyCode.NUM_PLUS:
25948 return 43; // +
25949 case KeyCode.NUM_MINUS:
25950 return 45; // -
25951 case KeyCode.NUM_PERIOD:
25952 return 46; // .
25953 case KeyCode.NUM_DIVISION:
25954 return 47; // /
25955 }
25956 } else if (event.keyCode >= 65 && event.keyCode <= 90) {
25957 // Set the "char code" for key down as the lower case letter. Again, this
25958 // will not show up for the user, but will be helpful in estimating
25959 // keyCode locations and other information during the keyPress event.
25960 return event.keyCode + _ROMAN_ALPHABET_OFFSET;
25961 }
25962 switch(event.keyCode) {
25963 case KeyCode.SEMICOLON:
25964 return KeyCode.FF_SEMICOLON;
25965 case KeyCode.EQUALS:
25966 return KeyCode.FF_EQUALS;
25967 case KeyCode.COMMA:
25968 return 44; // Ascii value for ,
25969 case KeyCode.DASH:
25970 return 45; // -
25971 case KeyCode.PERIOD:
25972 return 46; // .
25973 case KeyCode.SLASH:
25974 return 47; // /
25975 case KeyCode.APOSTROPHE:
25976 return 96; // `
25977 case KeyCode.OPEN_SQUARE_BRACKET:
25978 return 91; // [
25979 case KeyCode.BACKSLASH:
25980 return 92; // \
25981 case KeyCode.CLOSE_SQUARE_BRACKET:
25982 return 93; // ]
25983 case KeyCode.SINGLE_QUOTE:
25984 return 39; // '
25985 }
25986 return event.keyCode;
25987 }
25988
25989 /**
25990 * Returns true if the key fires a keypress event in the current browser.
25991 */
25992 bool _firesKeyPressEvent(KeyEvent event) {
25993 if (!Device.isIE && !Device.isWebKit) {
25994 return true;
25995 }
25996
25997 if (Device.userAgent.contains('Mac') && event.altKey) {
25998 return KeyCode.isCharacterKey(event.keyCode);
25999 }
26000
26001 // Alt but not AltGr which is represented as Alt+Ctrl.
26002 if (event.altKey && !event.ctrlKey) {
26003 return false;
26004 }
26005
26006 // Saves Ctrl or Alt + key for IE and WebKit, which won't fire keypress.
26007 if (!event.shiftKey &&
26008 (_keyDownList.last.keyCode == KeyCode.CTRL ||
26009 _keyDownList.last.keyCode == KeyCode.ALT ||
26010 Device.userAgent.contains('Mac') &&
26011 _keyDownList.last.keyCode == KeyCode.META)) {
26012 return false;
26013 }
26014
26015 // Some keys with Ctrl/Shift do not issue keypress in WebKit.
26016 if (Device.isWebKit && event.ctrlKey && event.shiftKey && (
26017 event.keyCode == KeyCode.BACKSLASH ||
26018 event.keyCode == KeyCode.OPEN_SQUARE_BRACKET ||
26019 event.keyCode == KeyCode.CLOSE_SQUARE_BRACKET ||
26020 event.keyCode == KeyCode.TILDE ||
26021 event.keyCode == KeyCode.SEMICOLON || event.keyCode == KeyCode.DASH ||
26022 event.keyCode == KeyCode.EQUALS || event.keyCode == KeyCode.COMMA ||
26023 event.keyCode == KeyCode.PERIOD || event.keyCode == KeyCode.SLASH ||
26024 event.keyCode == KeyCode.APOSTROPHE ||
26025 event.keyCode == KeyCode.SINGLE_QUOTE)) {
26026 return false;
26027 }
26028
26029 switch (event.keyCode) {
26030 case KeyCode.ENTER:
26031 // IE9 does not fire keypress on ENTER.
26032 return !Device.isIE;
26033 case KeyCode.ESC:
26034 return !Device.isWebKit;
26035 }
26036
26037 return KeyCode.isCharacterKey(event.keyCode);
26038 }
26039
26040 /**
26041 * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
26042 * Opera all use).
26043 */
26044 int _normalizeKeyCodes(KeyboardEvent event) {
26045 // Note: This may change once we get input about non-US keyboards.
26046 if (Device.isFirefox) {
26047 switch(event.keyCode) {
26048 case KeyCode.FF_EQUALS:
26049 return KeyCode.EQUALS;
26050 case KeyCode.FF_SEMICOLON:
26051 return KeyCode.SEMICOLON;
26052 case KeyCode.MAC_FF_META:
26053 return KeyCode.META;
26054 case KeyCode.WIN_KEY_FF_LINUX:
26055 return KeyCode.WIN_KEY;
26056 }
26057 }
26058 return event.keyCode;
26059 }
26060
26061 /** Handle keydown events. */
26062 void processKeyDown(KeyboardEvent e) {
26063 // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
26064 // before we've caught a key-up event. If the last-key was one of these
26065 // we reset the state.
26066 if (_keyDownList.length > 0 &&
26067 (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey ||
26068 _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey ||
26069 Device.userAgent.contains('Mac') &&
26070 _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) {
26071 _keyDownList.clear();
26072 }
26073
26074 var event = new KeyEvent(e);
26075 event._shadowKeyCode = _normalizeKeyCodes(event);
26076 // Technically a "keydown" event doesn't have a charCode. This is
26077 // calculated nonetheless to provide us with more information in giving
26078 // as much information as possible on keypress about keycode and also
26079 // charCode.
26080 event._shadowCharCode = _findCharCodeKeyDown(event);
26081 if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode &&
26082 !_firesKeyPressEvent(event)) {
26083 // Some browsers have quirks not firing keypress events where all other
26084 // browsers do. This makes them more consistent.
26085 processKeyPress(event);
26086 }
26087 _keyDownList.add(event);
26088 _dispatch(event);
26089 }
26090
26091 /** Handle keypress events. */
26092 void processKeyPress(KeyboardEvent event) {
26093 var e = new KeyEvent(event);
26094 // IE reports the character code in the keyCode field for keypress events.
26095 // There are two exceptions however, Enter and Escape.
26096 if (Device.isIE) {
26097 if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) {
26098 e._shadowCharCode = 0;
26099 } else {
26100 e._shadowCharCode = e.keyCode;
26101 }
26102 } else if (Device.isOpera) {
26103 // Opera reports the character code in the keyCode field.
26104 e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0;
26105 }
26106 // Now we guestimate about what the keycode is that was actually
26107 // pressed, given previous keydown information.
26108 e._shadowKeyCode = _determineKeyCodeForKeypress(e);
26109
26110 // Correct the key value for certain browser-specific quirks.
26111 if (e._shadowKeyIdentifier != null &&
26112 _keyIdentifier.containsKey(e._shadowKeyIdentifier)) {
26113 // This is needed for Safari Windows because it currently doesn't give a
26114 // keyCode/which for non printable keys.
26115 e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier];
26116 }
26117 e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
26118 _dispatch(e);
26119 }
26120
26121 /** Handle keyup events. */
26122 void processKeyUp(KeyboardEvent event) {
26123 var e = new KeyEvent(event);
26124 KeyboardEvent toRemove = null;
26125 for (var key in _keyDownList) {
26126 if (key.keyCode == e.keyCode) {
26127 toRemove = key;
26128 }
26129 }
26130 if (toRemove != null) {
26131 _keyDownList.removeWhere((element) => element == toRemove);
26132 } else if (_keyDownList.length > 0) {
26133 // This happens when we've reached some international keyboard case we
26134 // haven't accounted for or we haven't correctly eliminated all browser
26135 // inconsistencies. Filing bugs on when this is reached is welcome!
26136 _keyDownList.removeLast();
26137 }
26138 _dispatch(e);
26139 }
26140 }
26141
26142
26143 /**
26144 * Records KeyboardEvents that occur on a particular element, and provides a
26145 * stream of outgoing KeyEvents with cross-browser consistent keyCode and
26146 * charCode values despite the fact that a multitude of browsers that have
26147 * varying keyboard default behavior.
26148 *
26149 * Example usage:
26150 *
26151 * KeyboardEventStream.onKeyDown(document.body).listen(
26152 * keydownHandlerTest);
26153 *
26154 * This class is very much a work in progress, and we'd love to get information
26155 * on how we can make this class work with as many international keyboards as
26156 * possible. Bugs welcome!
26157 */
26158 class KeyboardEventStream {
26159
26160 /** Named constructor to produce a stream for onKeyPress events. */
26161 static Stream<KeyEvent> onKeyPress(EventTarget target) =>
26162 new _KeyboardEventHandler('keypress').forTarget(target);
26163
26164 /** Named constructor to produce a stream for onKeyUp events. */
26165 static Stream<KeyEvent> onKeyUp(EventTarget target) =>
26166 new _KeyboardEventHandler('keyup').forTarget(target);
26167
26168 /** Named constructor to produce a stream for onKeyDown events. */
26169 static Stream<KeyEvent> onKeyDown(EventTarget target) =>
26170 new _KeyboardEventHandler('keydown').forTarget(target);
26171 }
26172 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 26414 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
26173 // for details. All rights reserved. Use of this source code is governed by a 26415 // for details. All rights reserved. Use of this source code is governed by a
26174 // BSD-style license that can be found in the LICENSE file. 26416 // BSD-style license that can be found in the LICENSE file.
26175 26417
26176 26418
26177 /** 26419 /**
26178 * Defines the keycode values for keys that are returned by 26420 * Defines the keycode values for keys that are returned by
26179 * KeyboardEvent.keyCode. 26421 * KeyboardEvent.keyCode.
26180 * 26422 *
26181 * Important note: There is substantial divergence in how different browsers 26423 * Important note: There is substantial divergence in how different browsers
(...skipping 747 matching lines...) Expand 10 before | Expand all | Expand 10 after
26929 * Sound) key 27171 * Sound) key
26930 */ 27172 */
26931 static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound"; 27173 static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound";
26932 27174
26933 /** 27175 /**
26934 * Key value used when an implementation is unable to identify another key 27176 * Key value used when an implementation is unable to identify another key
26935 * value, due to either hardware, platform, or software constraints 27177 * value, due to either hardware, platform, or software constraints
26936 */ 27178 */
26937 static const String UNIDENTIFIED = "Unidentified"; 27179 static const String UNIDENTIFIED = "Unidentified";
26938 } 27180 }
27181 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
27182 // for details. All rights reserved. Use of this source code is governed by a
27183 // BSD-style license that can be found in the LICENSE file.
27184
27185
27186 /**
27187 * Internal class that does the actual calculations to determine keyCode and
27188 * charCode for keydown, keypress, and keyup events for all browsers.
27189 */
27190 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> {
27191 // This code inspired by Closure's KeyHandling library.
27192 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl er.js.source.html
27193
27194 /**
27195 * The set of keys that have been pressed down without seeing their
27196 * corresponding keyup event.
27197 */
27198 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
27199
27200 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
27201 final String _type;
27202
27203 /** The element we are watching for events to happen on. */
27204 final EventTarget _target;
27205
27206 // The distance to shift from upper case alphabet Roman letters to lower case.
27207 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0];
27208
27209 /** Controller to produce KeyEvents for the stream. */
27210 final StreamController _controller = new StreamController(sync: true);
27211
27212 static const _EVENT_TYPE = 'KeyEvent';
27213
27214 /**
27215 * An enumeration of key identifiers currently part of the W3C draft for DOM3
27216 * and their mappings to keyCodes.
27217 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
27218 */
27219 static const Map<String, int> _keyIdentifier = const {
27220 'Up': KeyCode.UP,
27221 'Down': KeyCode.DOWN,
27222 'Left': KeyCode.LEFT,
27223 'Right': KeyCode.RIGHT,
27224 'Enter': KeyCode.ENTER,
27225 'F1': KeyCode.F1,
27226 'F2': KeyCode.F2,
27227 'F3': KeyCode.F3,
27228 'F4': KeyCode.F4,
27229 'F5': KeyCode.F5,
27230 'F6': KeyCode.F6,
27231 'F7': KeyCode.F7,
27232 'F8': KeyCode.F8,
27233 'F9': KeyCode.F9,
27234 'F10': KeyCode.F10,
27235 'F11': KeyCode.F11,
27236 'F12': KeyCode.F12,
27237 'U+007F': KeyCode.DELETE,
27238 'Home': KeyCode.HOME,
27239 'End': KeyCode.END,
27240 'PageUp': KeyCode.PAGE_UP,
27241 'PageDown': KeyCode.PAGE_DOWN,
27242 'Insert': KeyCode.INSERT
27243 };
27244
27245 /** Return a stream for KeyEvents for the specified target. */
27246 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
27247 return new _KeyboardEventHandler.initializeAllEventListeners(
27248 _type, e).stream;
27249 }
27250
27251 /**
27252 * Accessor to the stream associated with a particular KeyboardEvent
27253 * EventTarget.
27254 *
27255 * [forTarget] must be called to initialize this stream to listen to a
27256 * particular EventTarget.
27257 */
27258 Stream<KeyEvent> get stream {
27259 if(_target != null) {
27260 return _controller.stream;
27261 } else {
27262 throw new StateError("Not initialized. Call forTarget to access a stream "
27263 "initialized with a particular EventTarget.");
27264 }
27265 }
27266
27267 /**
27268 * General constructor, performs basic initialization for our improved
27269 * KeyboardEvent controller.
27270 */
27271 _KeyboardEventHandler(this._type) :
27272 _target = null, super(_EVENT_TYPE) {
27273 }
27274
27275 /**
27276 * Hook up all event listeners under the covers so we can estimate keycodes
27277 * and charcodes when they are not provided.
27278 */
27279 _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) :
27280 super(_EVENT_TYPE) {
27281 Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
27282 processKeyDown);
27283 Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
27284 processKeyPress);
27285 Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
27286 processKeyUp);
27287 }
27288
27289 /**
27290 * Notify all callback listeners that a KeyEvent of the relevant type has
27291 * occurred.
27292 */
27293 bool _dispatch(KeyEvent event) {
27294 if (event.type == _type)
27295 _controller.add(event);
27296 }
27297
27298 /** Determine if caps lock is one of the currently depressed keys. */
27299 bool get _capsLockOn =>
27300 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
27301
27302 /**
27303 * Given the previously recorded keydown key codes, see if we can determine
27304 * the keycode of this keypress [event]. (Generally browsers only provide
27305 * charCode information for keypress events, but with a little
27306 * reverse-engineering, we can also determine the keyCode.) Returns
27307 * KeyCode.UNKNOWN if the keycode could not be determined.
27308 */
27309 int _determineKeyCodeForKeypress(KeyboardEvent event) {
27310 // Note: This function is a work in progress. We'll expand this function
27311 // once we get more information about other keyboards.
27312 for (var prevEvent in _keyDownList) {
27313 if (prevEvent._shadowCharCode == event.charCode) {
27314 return prevEvent.keyCode;
27315 }
27316 if ((event.shiftKey || _capsLockOn) && event.charCode >= "A".codeUnits[0]
27317 && event.charCode <= "Z".codeUnits[0] && event.charCode +
27318 _ROMAN_ALPHABET_OFFSET == prevEvent._shadowCharCode) {
27319 return prevEvent.keyCode;
27320 }
27321 }
27322 return KeyCode.UNKNOWN;
27323 }
27324
27325 /**
27326 * Given the charater code returned from a keyDown [event], try to ascertain
27327 * and return the corresponding charCode for the character that was pressed.
27328 * This information is not shown to the user, but used to help polyfill
27329 * keypress events.
27330 */
27331 int _findCharCodeKeyDown(KeyboardEvent event) {
27332 if (event.keyLocation == 3) { // Numpad keys.
27333 switch (event.keyCode) {
27334 case KeyCode.NUM_ZERO:
27335 // Even though this function returns _charCodes_, for some cases the
27336 // KeyCode == the charCode we want, in which case we use the keycode
27337 // constant for readability.
27338 return KeyCode.ZERO;
27339 case KeyCode.NUM_ONE:
27340 return KeyCode.ONE;
27341 case KeyCode.NUM_TWO:
27342 return KeyCode.TWO;
27343 case KeyCode.NUM_THREE:
27344 return KeyCode.THREE;
27345 case KeyCode.NUM_FOUR:
27346 return KeyCode.FOUR;
27347 case KeyCode.NUM_FIVE:
27348 return KeyCode.FIVE;
27349 case KeyCode.NUM_SIX:
27350 return KeyCode.SIX;
27351 case KeyCode.NUM_SEVEN:
27352 return KeyCode.SEVEN;
27353 case KeyCode.NUM_EIGHT:
27354 return KeyCode.EIGHT;
27355 case KeyCode.NUM_NINE:
27356 return KeyCode.NINE;
27357 case KeyCode.NUM_MULTIPLY:
27358 return 42; // Char code for *
27359 case KeyCode.NUM_PLUS:
27360 return 43; // +
27361 case KeyCode.NUM_MINUS:
27362 return 45; // -
27363 case KeyCode.NUM_PERIOD:
27364 return 46; // .
27365 case KeyCode.NUM_DIVISION:
27366 return 47; // /
27367 }
27368 } else if (event.keyCode >= 65 && event.keyCode <= 90) {
27369 // Set the "char code" for key down as the lower case letter. Again, this
27370 // will not show up for the user, but will be helpful in estimating
27371 // keyCode locations and other information during the keyPress event.
27372 return event.keyCode + _ROMAN_ALPHABET_OFFSET;
27373 }
27374 switch(event.keyCode) {
27375 case KeyCode.SEMICOLON:
27376 return KeyCode.FF_SEMICOLON;
27377 case KeyCode.EQUALS:
27378 return KeyCode.FF_EQUALS;
27379 case KeyCode.COMMA:
27380 return 44; // Ascii value for ,
27381 case KeyCode.DASH:
27382 return 45; // -
27383 case KeyCode.PERIOD:
27384 return 46; // .
27385 case KeyCode.SLASH:
27386 return 47; // /
27387 case KeyCode.APOSTROPHE:
27388 return 96; // `
27389 case KeyCode.OPEN_SQUARE_BRACKET:
27390 return 91; // [
27391 case KeyCode.BACKSLASH:
27392 return 92; // \
27393 case KeyCode.CLOSE_SQUARE_BRACKET:
27394 return 93; // ]
27395 case KeyCode.SINGLE_QUOTE:
27396 return 39; // '
27397 }
27398 return event.keyCode;
27399 }
27400
27401 /**
27402 * Returns true if the key fires a keypress event in the current browser.
27403 */
27404 bool _firesKeyPressEvent(KeyEvent event) {
27405 if (!Device.isIE && !Device.isWebKit) {
27406 return true;
27407 }
27408
27409 if (Device.userAgent.contains('Mac') && event.altKey) {
27410 return KeyCode.isCharacterKey(event.keyCode);
27411 }
27412
27413 // Alt but not AltGr which is represented as Alt+Ctrl.
27414 if (event.altKey && !event.ctrlKey) {
27415 return false;
27416 }
27417
27418 // Saves Ctrl or Alt + key for IE and WebKit, which won't fire keypress.
27419 if (!event.shiftKey &&
27420 (_keyDownList.last.keyCode == KeyCode.CTRL ||
27421 _keyDownList.last.keyCode == KeyCode.ALT ||
27422 Device.userAgent.contains('Mac') &&
27423 _keyDownList.last.keyCode == KeyCode.META)) {
27424 return false;
27425 }
27426
27427 // Some keys with Ctrl/Shift do not issue keypress in WebKit.
27428 if (Device.isWebKit && event.ctrlKey && event.shiftKey && (
27429 event.keyCode == KeyCode.BACKSLASH ||
27430 event.keyCode == KeyCode.OPEN_SQUARE_BRACKET ||
27431 event.keyCode == KeyCode.CLOSE_SQUARE_BRACKET ||
27432 event.keyCode == KeyCode.TILDE ||
27433 event.keyCode == KeyCode.SEMICOLON || event.keyCode == KeyCode.DASH ||
27434 event.keyCode == KeyCode.EQUALS || event.keyCode == KeyCode.COMMA ||
27435 event.keyCode == KeyCode.PERIOD || event.keyCode == KeyCode.SLASH ||
27436 event.keyCode == KeyCode.APOSTROPHE ||
27437 event.keyCode == KeyCode.SINGLE_QUOTE)) {
27438 return false;
27439 }
27440
27441 switch (event.keyCode) {
27442 case KeyCode.ENTER:
27443 // IE9 does not fire keypress on ENTER.
27444 return !Device.isIE;
27445 case KeyCode.ESC:
27446 return !Device.isWebKit;
27447 }
27448
27449 return KeyCode.isCharacterKey(event.keyCode);
27450 }
27451
27452 /**
27453 * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
27454 * Opera all use).
27455 */
27456 int _normalizeKeyCodes(KeyboardEvent event) {
27457 // Note: This may change once we get input about non-US keyboards.
27458 if (Device.isFirefox) {
27459 switch(event.keyCode) {
27460 case KeyCode.FF_EQUALS:
27461 return KeyCode.EQUALS;
27462 case KeyCode.FF_SEMICOLON:
27463 return KeyCode.SEMICOLON;
27464 case KeyCode.MAC_FF_META:
27465 return KeyCode.META;
27466 case KeyCode.WIN_KEY_FF_LINUX:
27467 return KeyCode.WIN_KEY;
27468 }
27469 }
27470 return event.keyCode;
27471 }
27472
27473 /** Handle keydown events. */
27474 void processKeyDown(KeyboardEvent e) {
27475 // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
27476 // before we've caught a key-up event. If the last-key was one of these
27477 // we reset the state.
27478 if (_keyDownList.length > 0 &&
27479 (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey ||
27480 _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey ||
27481 Device.userAgent.contains('Mac') &&
27482 _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) {
27483 _keyDownList.clear();
27484 }
27485
27486 var event = new KeyEvent(e);
27487 event._shadowKeyCode = _normalizeKeyCodes(event);
27488 // Technically a "keydown" event doesn't have a charCode. This is
27489 // calculated nonetheless to provide us with more information in giving
27490 // as much information as possible on keypress about keycode and also
27491 // charCode.
27492 event._shadowCharCode = _findCharCodeKeyDown(event);
27493 if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode &&
27494 !_firesKeyPressEvent(event)) {
27495 // Some browsers have quirks not firing keypress events where all other
27496 // browsers do. This makes them more consistent.
27497 processKeyPress(event);
27498 }
27499 _keyDownList.add(event);
27500 _dispatch(event);
27501 }
27502
27503 /** Handle keypress events. */
27504 void processKeyPress(KeyboardEvent event) {
27505 var e = new KeyEvent(event);
27506 // IE reports the character code in the keyCode field for keypress events.
27507 // There are two exceptions however, Enter and Escape.
27508 if (Device.isIE) {
27509 if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) {
27510 e._shadowCharCode = 0;
27511 } else {
27512 e._shadowCharCode = e.keyCode;
27513 }
27514 } else if (Device.isOpera) {
27515 // Opera reports the character code in the keyCode field.
27516 e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0;
27517 }
27518 // Now we guestimate about what the keycode is that was actually
27519 // pressed, given previous keydown information.
27520 e._shadowKeyCode = _determineKeyCodeForKeypress(e);
27521
27522 // Correct the key value for certain browser-specific quirks.
27523 if (e._shadowKeyIdentifier != null &&
27524 _keyIdentifier.containsKey(e._shadowKeyIdentifier)) {
27525 // This is needed for Safari Windows because it currently doesn't give a
27526 // keyCode/which for non printable keys.
27527 e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier];
27528 }
27529 e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
27530 _dispatch(e);
27531 }
27532
27533 /** Handle keyup events. */
27534 void processKeyUp(KeyboardEvent event) {
27535 var e = new KeyEvent(event);
27536 KeyboardEvent toRemove = null;
27537 for (var key in _keyDownList) {
27538 if (key.keyCode == e.keyCode) {
27539 toRemove = key;
27540 }
27541 }
27542 if (toRemove != null) {
27543 _keyDownList.removeWhere((element) => element == toRemove);
27544 } else if (_keyDownList.length > 0) {
27545 // This happens when we've reached some international keyboard case we
27546 // haven't accounted for or we haven't correctly eliminated all browser
27547 // inconsistencies. Filing bugs on when this is reached is welcome!
27548 _keyDownList.removeLast();
27549 }
27550 _dispatch(e);
27551 }
27552 }
27553
27554
27555 /**
27556 * Records KeyboardEvents that occur on a particular element, and provides a
27557 * stream of outgoing KeyEvents with cross-browser consistent keyCode and
27558 * charCode values despite the fact that a multitude of browsers that have
27559 * varying keyboard default behavior.
27560 *
27561 * Example usage:
27562 *
27563 * KeyboardEventStream.onKeyDown(document.body).listen(
27564 * keydownHandlerTest);
27565 *
27566 * This class is very much a work in progress, and we'd love to get information
27567 * on how we can make this class work with as many international keyboards as
27568 * possible. Bugs welcome!
27569 */
27570 class KeyboardEventStream {
27571
27572 /** Named constructor to produce a stream for onKeyPress events. */
27573 static Stream<KeyEvent> onKeyPress(EventTarget target) =>
27574 new _KeyboardEventHandler('keypress').forTarget(target);
27575
27576 /** Named constructor to produce a stream for onKeyUp events. */
27577 static Stream<KeyEvent> onKeyUp(EventTarget target) =>
27578 new _KeyboardEventHandler('keyup').forTarget(target);
27579
27580 /** Named constructor to produce a stream for onKeyDown events. */
27581 static Stream<KeyEvent> onKeyDown(EventTarget target) =>
27582 new _KeyboardEventHandler('keydown').forTarget(target);
27583 }
27584 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
27585 // for details. All rights reserved. Use of this source code is governed by a
27586 // BSD-style license that can be found in the LICENSE file.
27587
27588
27589 typedef void _MicrotaskCallback();
27590
27591 /**
27592 * This class attempts to invoke a callback as soon as the current event stack
27593 * unwinds, but before the browser repaints.
27594 */
27595 abstract class _MicrotaskScheduler {
27596 bool _nextMicrotaskFrameScheduled = false;
27597 final _MicrotaskCallback _callback;
27598
27599 _MicrotaskScheduler(this._callback);
27600
27601 /**
27602 * Creates the best possible microtask scheduler for the current platform.
27603 */
27604 factory _MicrotaskScheduler.best(_MicrotaskCallback callback) {
27605 if (Window._supportsSetImmediate) {
27606 return new _SetImmediateScheduler(callback);
27607 } else if (MutationObserver.supported) {
27608 return new _MutationObserverScheduler(callback);
27609 }
27610 return new _PostMessageScheduler(callback);
27611 }
27612
27613 /**
27614 * Schedules a microtask callback if one has not been scheduled already.
27615 */
27616 void maybeSchedule() {
27617 if (this._nextMicrotaskFrameScheduled) {
27618 return;
27619 }
27620 this._nextMicrotaskFrameScheduled = true;
27621 this._schedule();
27622 }
27623
27624 /**
27625 * Does the actual scheduling of the callback.
27626 */
27627 void _schedule();
27628
27629 /**
27630 * Handles the microtask callback and forwards it if necessary.
27631 */
27632 void _onCallback() {
27633 // Ignore spurious messages.
27634 if (!_nextMicrotaskFrameScheduled) {
27635 return;
27636 }
27637 _nextMicrotaskFrameScheduled = false;
27638 this._callback();
27639 }
27640 }
27641
27642 /**
27643 * Scheduler which uses window.postMessage to schedule events.
27644 */
27645 class _PostMessageScheduler extends _MicrotaskScheduler {
27646 const _MICROTASK_MESSAGE = "DART-MICROTASK";
27647
27648 _PostMessageScheduler(_MicrotaskCallback callback): super(callback) {
27649 // Messages from other windows do not cause a security risk as
27650 // all we care about is that _handleMessage is called
27651 // after the current event loop is unwound and calling the function is
27652 // a noop when zero requests are pending.
27653 window.onMessage.listen(this._handleMessage);
27654 }
27655
27656 void _schedule() {
27657 window.postMessage(_MICROTASK_MESSAGE, "*");
27658 }
27659
27660 void _handleMessage(e) {
27661 this._onCallback();
27662 }
27663 }
27664
27665 /**
27666 * Scheduler which uses a MutationObserver to schedule events.
27667 */
27668 class _MutationObserverScheduler extends _MicrotaskScheduler {
27669 MutationObserver _observer;
27670 Element _dummy;
27671
27672 _MutationObserverScheduler(_MicrotaskCallback callback): super(callback) {
27673 // Mutation events get fired as soon as the current event stack is unwound
27674 // so we just make a dummy event and listen for that.
27675 _observer = new MutationObserver(this._handleMutation);
27676 _dummy = new DivElement();
27677 _observer.observe(_dummy, attributes: true);
27678 }
27679
27680 void _schedule() {
27681 // Toggle it to trigger the mutation event.
27682 _dummy.hidden = !_dummy.hidden;
27683 }
27684
27685 _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
27686 this._onCallback();
27687 }
27688 }
27689
27690 /**
27691 * Scheduler which uses window.setImmediate to schedule events.
27692 */
27693 class _SetImmediateScheduler extends _MicrotaskScheduler {
27694 _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
27695
27696 void _schedule() {
27697 window._setImmediate(_handleImmediate);
27698 }
27699
27700 void _handleImmediate() {
27701 this._onCallback();
27702 }
27703 }
27704
27705 List<TimeoutHandler> _pendingMicrotasks;
27706 _MicrotaskScheduler _microtaskScheduler = null;
27707
27708 void _maybeScheduleMicrotaskFrame() {
27709 if (_microtaskScheduler == null) {
27710 _microtaskScheduler =
27711 new _MicrotaskScheduler.best(_completeMicrotasks);
27712 }
27713 _microtaskScheduler.maybeSchedule();
27714 }
27715
27716 /**
27717 * Registers a [callback] which is called after the current execution stack
27718 * unwinds.
27719 */
27720 void _addMicrotaskCallback(TimeoutHandler callback) {
27721 if (_pendingMicrotasks == null) {
27722 _pendingMicrotasks = <TimeoutHandler>[];
27723 _maybeScheduleMicrotaskFrame();
27724 }
27725 _pendingMicrotasks.add(callback);
27726 }
27727
27728
27729 /**
27730 * Complete all pending microtasks.
27731 */
27732 void _completeMicrotasks() {
27733 var callbacks = _pendingMicrotasks;
27734 _pendingMicrotasks = null;
27735 for (var callback in callbacks) {
27736 callback();
27737 }
27738 }
27739 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27740 // for details. All rights reserved. Use of this source code is governed by a
27741 // BSD-style license that can be found in the LICENSE file.
27742
27743
27744
27745 /**
27746 * Class which helps construct standard node validation policies.
27747 *
27748 * By default this will not accept anything, but the 'allow*' functions can be
27749 * used to expand what types of elements or attributes are allowed.
27750 *
27751 * All allow functions are additive- elements will be accepted if they are
27752 * accepted by any specific rule.
27753 */
27754 class NodeValidatorBuilder implements NodeValidator {
27755
27756 final List<NodeValidator> _validators = <NodeValidator>[];
27757
27758 NodeValidatorBuilder() {
27759 }
27760
27761 /**
27762 * Creates a new NodeValidatorBuilder which accepts common constructs.
27763 *
27764 * By default this will accept HTML5 elements and attributes with the default
27765 * [UriPolicy] and templating elements.
27766 */
27767 factory NodeValidatorBuilder.common() {
27768 return new NodeValidatorBuilder()
27769 ..allowHtml5()
27770 ..allowTemplating();
27771 }
27772
27773 /**
27774 * Allows navigation elements- Form and Anchor tags, along with common
27775 * attributes.
27776 *
27777 * The UriPolicy can be used to restrict the locations the navigation elements
27778 * are allowed to direct to. By default this will use the default [UriPolicy].
27779 */
27780 void allowNavigation([UriPolicy uriPolicy]) {
27781 if (uriPolicy == null) {
27782 uriPolicy = new UriPolicy();
27783 }
27784 add(new _SimpleNodeValidator.allowNavigation(uriPolicy));
27785 }
27786
27787 /**
27788 * Allows image elements.
27789 *
27790 * The UriPolicy can be used to restrict the locations the images may be
27791 * loaded from. By default this will use the default [UriPolicy].
27792 */
27793 void allowImages([UriPolicy uriPolicy]) {
27794 if (uriPolicy == null) {
27795 uriPolicy = new UriPolicy();
27796 }
27797 add(new _SimpleNodeValidator.allowImages(uriPolicy));
27798 }
27799
27800 /**
27801 * Allow basic text elements.
27802 *
27803 * This allows a subset of HTML5 elements, specifically just these tags and
27804 * no attributes.
27805 *
27806 * * B
27807 * * BLOCKQUOTE
27808 * * BR
27809 * * EM
27810 * * H1
27811 * * H2
27812 * * H3
27813 * * H4
27814 * * H5
27815 * * H6
27816 * * HR
27817 * * I
27818 * * LI
27819 * * OL
27820 * * P
27821 * * SPAN
27822 * * UL
27823 */
27824 void allowTextElements() {
27825 add(new _SimpleNodeValidator.allowTextElements());
27826 }
27827
27828 /**
27829 * Allow common safe HTML5 elements and attributes.
27830 *
27831 * This list is based off of the Caja whitelists at:
27832 * https://code.google.com/p/google-caja/wiki/CajaWhitelists.
27833 *
27834 * Common things which are not allowed are script elements, style attributes
27835 * and any script handlers.
27836 */
27837 void allowHtml5({UriPolicy uriPolicy}) {
27838 add(new _Html5NodeValidator(uriPolicy: uriPolicy));
27839 }
27840
27841 /**
27842 * Allow SVG elements and attributes except for known bad ones.
27843 */
27844 void allowSvg() {
27845 add(new _SvgNodeValidator());
27846 }
27847
27848 /**
27849 * Allow custom elements with the specified tag name and specified attributes.
27850 *
27851 * This will allow the elements as custom tags (such as <x-foo></x-foo>),
27852 * but will not allow tag extensions. Use [allowTagExtension] to allow
27853 * tag extensions.
27854 */
27855 void allowCustomElement(String tagName,
27856 {UriPolicy uriPolicy,
27857 Iterable<String> attributes,
27858 Iterable<String> uriAttributes}) {
27859
27860 var tagNameUpper = tagName.toUpperCase();
27861 var attrs;
27862 if (attributes != null) {
27863 attrs =
27864 attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
27865 }
27866 var uriAttrs;
27867 if (uriAttributes != null) {
27868 uriAttrs =
27869 uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
27870 }
27871 if (uriPolicy == null) {
27872 uriPolicy = new UriPolicy();
27873 }
27874
27875 add(new _CustomElementNodeValidator(
27876 uriPolicy,
27877 [tagNameUpper],
27878 attrs,
27879 uriAttrs,
27880 false,
27881 true));
27882 }
27883
27884 /**
27885 * Allow custom tag extensions with the specified type name and specified
27886 * attributes.
27887 *
27888 * This will allow tag extensions (such as <div is="x-foo"></div>),
27889 * but will not allow custom tags. Use [allowCustomElement] to allow
27890 * custom tags.
27891 */
27892 void allowTagExtension(String tagName, String baseName,
27893 {UriPolicy uriPolicy,
27894 Iterable<String> attributes,
27895 Iterable<String> uriAttributes}) {
27896
27897 var baseNameUpper = baseName.toUpperCase();
27898 var tagNameUpper = tagName.toUpperCase();
27899 var attrs;
27900 if (attributes != null) {
27901 attrs =
27902 attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
27903 }
27904 var uriAttrs;
27905 if (uriAttributes != null) {
27906 uriAttrs =
27907 uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
27908 }
27909 if (uriPolicy == null) {
27910 uriPolicy = new UriPolicy();
27911 }
27912
27913 add(new _CustomElementNodeValidator(
27914 uriPolicy,
27915 [tagNameUpper, baseNameUpper],
27916 attrs,
27917 uriAttrs,
27918 true,
27919 false));
27920 }
27921
27922 void allowElement(String tagName, {UriPolicy uriPolicy,
27923 Iterable<String> attributes,
27924 Iterable<String> uriAttributes}) {
27925
27926 allowCustomElement(tagName, uriPolicy: uriPolicy,
27927 attributes: attributes,
27928 uriAttributes: uriAttributes);
27929 }
27930
27931 /**
27932 * Allow templating elements (such as <template> and template-related
27933 * attributes.
27934 *
27935 * This still requires other validators to allow regular attributes to be
27936 * bound (such as [allowHtml5]).
27937 */
27938 void allowTemplating() {
27939 add(new _TemplatingNodeValidator());
27940 }
27941
27942 /**
27943 * Add an additional validator to the current list of validators.
27944 *
27945 * Elements and attributes will be accepted if they are accepted by any
27946 * validators.
27947 */
27948 void add(NodeValidator validator) {
27949 _validators.add(validator);
27950 }
27951
27952 bool allowsElement(Element element) {
27953 return _validators.any((v) => v.allowsElement(element));
27954 }
27955
27956 bool allowsAttribute(Element element, String attributeName, String value) {
27957 return _validators.any(
27958 (v) => v.allowsAttribute(element, attributeName, value));
27959 }
27960 }
27961
27962 class _SimpleNodeValidator implements NodeValidator {
27963 final Set<String> allowedElements;
27964 final Set<String> allowedAttributes;
27965 final Set<String> allowedUriAttributes;
27966 final UriPolicy uriPolicy;
27967
27968 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) {
27969 return new _SimpleNodeValidator(uriPolicy,
27970 allowedElements: [
27971 'A',
27972 'FORM'],
27973 allowedAttributes: [
27974 'A::accesskey',
27975 'A::coords',
27976 'A::hreflang',
27977 'A::name',
27978 'A::shape',
27979 'A::tabindex',
27980 'A::target',
27981 'A::type',
27982 'FORM::accept',
27983 'FORM::autocomplete',
27984 'FORM::enctype',
27985 'FORM::method',
27986 'FORM::name',
27987 'FORM::novalidate',
27988 'FORM::target',
27989 ],
27990 allowedUriAttributes: [
27991 'A::href',
27992 'FORM::action',
27993 ]);
27994 }
27995
27996 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) {
27997 return new _SimpleNodeValidator(uriPolicy,
27998 allowedElements: [
27999 'IMG'
28000 ],
28001 allowedAttributes: [
28002 'IMG::align',
28003 'IMG::alt',
28004 'IMG::border',
28005 'IMG::height',
28006 'IMG::hspace',
28007 'IMG::ismap',
28008 'IMG::name',
28009 'IMG::usemap',
28010 'IMG::vspace',
28011 'IMG::width',
28012 ],
28013 allowedUriAttributes: [
28014 'IMG::src',
28015 ]);
28016 }
28017
28018 factory _SimpleNodeValidator.allowTextElements() {
28019 return new _SimpleNodeValidator(null,
28020 allowedElements: [
28021 'B',
28022 'BLOCKQUOTE',
28023 'BR',
28024 'EM',
28025 'H1',
28026 'H2',
28027 'H3',
28028 'H4',
28029 'H5',
28030 'H6',
28031 'HR',
28032 'I',
28033 'LI',
28034 'OL',
28035 'P',
28036 'SPAN',
28037 'UL',
28038 ]);
28039 }
28040
28041 /**
28042 * Elements must be uppercased tag names. For example `'IMG'`.
28043 * Attributes must be uppercased tag name followed by :: followed by
28044 * lowercase attribute name. For example `'IMG:src'`.
28045 */
28046 _SimpleNodeValidator(this.uriPolicy,
28047 {Iterable<String> allowedElements, Iterable<String> allowedAttributes,
28048 Iterable<String> allowedUriAttributes}):
28049 this.allowedElements = allowedElements != null ?
28050 new Set.from(allowedElements) : new Set(),
28051 this.allowedAttributes = allowedAttributes != null ?
28052 new Set.from(allowedAttributes) : new Set(),
28053 this.allowedUriAttributes = allowedUriAttributes != null ?
28054 new Set.from(allowedUriAttributes) : new Set();
28055
28056 bool allowsElement(Element element) {
28057 return allowedElements.contains(element.tagName);
28058 }
28059
28060 bool allowsAttribute(Element element, String attributeName, String value) {
28061 var tagName = element.tagName;
28062 if (allowedUriAttributes.contains('$tagName::$attributeName')) {
28063 return uriPolicy.allowsUri(value);
28064 } else if (allowedUriAttributes.contains('*::$attributeName')) {
28065 return uriPolicy.allowsUri(value);
28066 } else if (allowedAttributes.contains('$tagName::$attributeName')) {
28067 return true;
28068 } else if (allowedAttributes.contains('*::$attributeName')) {
28069 return true;
28070 } else if (allowedAttributes.contains('$tagName::*')) {
28071 return true;
28072 } else if (allowedAttributes.contains('*::*')) {
28073 return true;
28074 }
28075 return false;
28076 }
28077 }
28078
28079 class _CustomElementNodeValidator extends _SimpleNodeValidator {
28080 final bool allowTypeExtension;
28081 final bool allowCustomTag;
28082
28083 _CustomElementNodeValidator(UriPolicy uriPolicy,
28084 Iterable<String> allowedElements,
28085 Iterable<String> allowedAttributes,
28086 Iterable<String> allowedUriAttributes,
28087 bool allowTypeExtension,
28088 bool allowCustomTag):
28089
28090 super(uriPolicy,
28091 allowedElements: allowedElements,
28092 allowedAttributes: allowedAttributes,
28093 allowedUriAttributes: allowedUriAttributes),
28094 this.allowTypeExtension = allowTypeExtension == true,
28095 this.allowCustomTag = allowCustomTag == true;
28096
28097 bool allowsElement(Element element) {
28098 if (allowTypeExtension) {
28099 var isAttr = element.attributes['is'];
28100 if (isAttr != null) {
28101 return allowedElements.contains(isAttr.toUpperCase()) &&
28102 allowedElements.contains(element.tagName);
28103 }
28104 }
28105 return allowCustomTag && allowedElements.contains(element.tagName);
28106 }
28107
28108 bool allowsAttribute(Element element, String attributeName, String value) {
28109 if (allowsElement(element)) {
28110 if (allowTypeExtension && attributeName == 'is' &&
28111 allowedElements.contains(value.toUpperCase())) {
28112 return true;
28113 }
28114 return super.allowsAttribute(element, attributeName, value);
28115 }
28116 return false;
28117 }
28118 }
28119
28120 class _TemplatingNodeValidator extends _SimpleNodeValidator {
28121 static const _TEMPLATE_ATTRS =
28122 const <String>['bind', 'if', 'ref', 'repeat', 'syntax'];
28123
28124 final Set<String> _templateAttrs;
28125
28126 _TemplatingNodeValidator():
28127 super(null,
28128 allowedElements: [
28129 'TEMPLATE'
28130 ],
28131 allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')),
28132 _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) {
28133 }
28134
28135 bool allowsAttribute(Element element, String attributeName, String value) {
28136 if (super.allowsAttribute(element, attributeName, value)) {
28137 return true;
28138 }
28139
28140 if (attributeName == 'template' && value == "") {
28141 return true;
28142 }
28143
28144 if (element.attributes['template'] == "" ) {
28145 return _templateAttrs.contains(attributeName);
28146 }
28147 return false;
28148 }
28149 }
28150
28151
28152 class _SvgNodeValidator implements NodeValidator {
28153 bool allowsElement(Element element) {
28154 if (element is svg.ScriptElement) {
28155 return false;
28156 }
28157 if (element is svg.SvgElement) {
28158 return true;
28159 }
28160 return false;
28161 }
28162
28163 bool allowsAttribute(Element element, String attributeName, String value) {
28164 if (attributeName == 'is' || attributeName.startsWith('on')) {
28165 return false;
28166 }
28167 return allowsElement(element);
28168 }
28169 }
26939 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28170 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
26940 // for details. All rights reserved. Use of this source code is governed by a 28171 // for details. All rights reserved. Use of this source code is governed by a
26941 // BSD-style license that can be found in the LICENSE file. 28172 // BSD-style license that can be found in the LICENSE file.
26942 28173
26943 28174
26944 // This code is inspired by ChangeSummary: 28175 // This code is inspired by ChangeSummary:
26945 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 28176 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
26946 // ...which underlies MDV. Since we don't need the functionality of 28177 // ...which underlies MDV. Since we don't need the functionality of
26947 // ChangeSummary, we just implement what we need for data bindings. 28178 // ChangeSummary, we just implement what we need for data bindings.
26948 // This allows our implementation to be much simpler. 28179 // This allows our implementation to be much simpler.
(...skipping 489 matching lines...) Expand 10 before | Expand all | Expand 10 after
27438 * Truncates coordinates to integers and returns the result as a new 28669 * Truncates coordinates to integers and returns the result as a new
27439 * rectangle. 28670 * rectangle.
27440 */ 28671 */
27441 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 28672 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
27442 height.toInt()); 28673 height.toInt());
27443 28674
27444 Point get topLeft => new Point(this.left, this.top); 28675 Point get topLeft => new Point(this.left, this.top);
27445 Point get bottomRight => new Point(this.left + this.width, 28676 Point get bottomRight => new Point(this.left + this.width,
27446 this.top + this.height); 28677 this.top + this.height);
27447 } 28678 }
28679 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28680 // for details. All rights reserved. Use of this source code is governed by a
28681 // BSD-style license that can be found in the LICENSE file.
28682
28683 // Patch file for the dart:isolate library.
28684
28685
28686 /********************************************************
28687 Inserted from lib/isolate/serialization.dart
28688 ********************************************************/
28689
28690 class _MessageTraverserVisitedMap {
28691
28692 operator[](var object) => null;
28693 void operator[]=(var object, var info) { }
28694
28695 void reset() { }
28696 void cleanup() { }
28697
28698 }
28699
28700 /** Abstract visitor for dart objects that can be sent as isolate messages. */
28701 abstract class _MessageTraverser {
28702
28703 _MessageTraverserVisitedMap _visited;
28704 _MessageTraverser() : _visited = new _MessageTraverserVisitedMap();
28705
28706 /** Visitor's entry point. */
28707 traverse(var x) {
28708 if (isPrimitive(x)) return visitPrimitive(x);
28709 _visited.reset();
28710 var result;
28711 try {
28712 result = _dispatch(x);
28713 } finally {
28714 _visited.cleanup();
28715 }
28716 return result;
28717 }
28718
28719 _dispatch(var x) {
28720 if (isPrimitive(x)) return visitPrimitive(x);
28721 if (x is List) return visitList(x);
28722 if (x is Map) return visitMap(x);
28723 if (x is SendPort) return visitSendPort(x);
28724 if (x is SendPortSync) return visitSendPortSync(x);
28725
28726 // Overridable fallback.
28727 return visitObject(x);
28728 }
28729
28730 visitPrimitive(x);
28731 visitList(List x);
28732 visitMap(Map x);
28733 visitSendPort(SendPort x);
28734 visitSendPortSync(SendPortSync x);
28735
28736 visitObject(Object x) {
28737 // TODO(floitsch): make this a real exception. (which one)?
28738 throw "Message serialization: Illegal value $x passed";
28739 }
28740
28741 static bool isPrimitive(x) {
28742 return (x == null) || (x is String) || (x is num) || (x is bool);
28743 }
28744 }
28745
28746
28747 /** Visitor that serializes a message as a JSON array. */
28748 abstract class _Serializer extends _MessageTraverser {
28749 int _nextFreeRefId = 0;
28750
28751 visitPrimitive(x) => x;
28752
28753 visitList(List list) {
28754 int copyId = _visited[list];
28755 if (copyId != null) return ['ref', copyId];
28756
28757 int id = _nextFreeRefId++;
28758 _visited[list] = id;
28759 var jsArray = _serializeList(list);
28760 // TODO(floitsch): we are losing the generic type.
28761 return ['list', id, jsArray];
28762 }
28763
28764 visitMap(Map map) {
28765 int copyId = _visited[map];
28766 if (copyId != null) return ['ref', copyId];
28767
28768 int id = _nextFreeRefId++;
28769 _visited[map] = id;
28770 var keys = _serializeList(map.keys.toList());
28771 var values = _serializeList(map.values.toList());
28772 // TODO(floitsch): we are losing the generic type.
28773 return ['map', id, keys, values];
28774 }
28775
28776 _serializeList(List list) {
28777 int len = list.length;
28778 var result = new List(len);
28779 for (int i = 0; i < len; i++) {
28780 result[i] = _dispatch(list[i]);
28781 }
28782 return result;
28783 }
28784 }
28785
28786 /** Deserializes arrays created with [_Serializer]. */
28787 abstract class _Deserializer {
28788 Map<int, dynamic> _deserialized;
28789
28790 _Deserializer();
28791
28792 static bool isPrimitive(x) {
28793 return (x == null) || (x is String) || (x is num) || (x is bool);
28794 }
28795
28796 deserialize(x) {
28797 if (isPrimitive(x)) return x;
28798 // TODO(floitsch): this should be new HashMap<int, dynamic>()
28799 _deserialized = new HashMap();
28800 return _deserializeHelper(x);
28801 }
28802
28803 _deserializeHelper(x) {
28804 if (isPrimitive(x)) return x;
28805 assert(x is List);
28806 switch (x[0]) {
28807 case 'ref': return _deserializeRef(x);
28808 case 'list': return _deserializeList(x);
28809 case 'map': return _deserializeMap(x);
28810 case 'sendport': return deserializeSendPort(x);
28811 default: return deserializeObject(x);
28812 }
28813 }
28814
28815 _deserializeRef(List x) {
28816 int id = x[1];
28817 var result = _deserialized[id];
28818 assert(result != null);
28819 return result;
28820 }
28821
28822 List _deserializeList(List x) {
28823 int id = x[1];
28824 // We rely on the fact that Dart-lists are directly mapped to Js-arrays.
28825 List dartList = x[2];
28826 _deserialized[id] = dartList;
28827 int len = dartList.length;
28828 for (int i = 0; i < len; i++) {
28829 dartList[i] = _deserializeHelper(dartList[i]);
28830 }
28831 return dartList;
28832 }
28833
28834 Map _deserializeMap(List x) {
28835 Map result = new Map();
28836 int id = x[1];
28837 _deserialized[id] = result;
28838 List keys = x[2];
28839 List values = x[3];
28840 int len = keys.length;
28841 assert(len == values.length);
28842 for (int i = 0; i < len; i++) {
28843 var key = _deserializeHelper(keys[i]);
28844 var value = _deserializeHelper(values[i]);
28845 result[key] = value;
28846 }
28847 return result;
28848 }
28849
28850 deserializeSendPort(List x);
28851
28852 deserializeObject(List x) {
28853 // TODO(floitsch): Use real exception (which one?).
28854 throw "Unexpected serialized object";
28855 }
28856 }
28857
27448 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28858 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27449 // for details. All rights reserved. Use of this source code is governed by a 28859 // for details. All rights reserved. Use of this source code is governed by a
27450 // BSD-style license that can be found in the LICENSE file. 28860 // BSD-style license that can be found in the LICENSE file.
27451 28861
27452 28862
27453 // This code is a port of Model-Driven-Views: 28863 // This code is a port of Model-Driven-Views:
27454 // https://github.com/polymer-project/mdv 28864 // https://github.com/polymer-project/mdv
27455 // The code mostly comes from src/template_element.js 28865 // The code mostly comes from src/template_element.js
27456 28866
27457 typedef void _ChangeHandler(value); 28867 typedef void _ChangeHandler(value);
(...skipping 768 matching lines...) Expand 10 before | Expand all | Expand 10 after
28226 _sub.cancel(); 29636 _sub.cancel();
28227 _sub = null; 29637 _sub = null;
28228 } 29638 }
28229 29639
28230 void abandon() { 29640 void abandon() {
28231 unobserve(); 29641 unobserve();
28232 _valueBinding.cancel(); 29642 _valueBinding.cancel();
28233 inputs.dispose(); 29643 inputs.dispose();
28234 } 29644 }
28235 } 29645 }
28236 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28237 // for details. All rights reserved. Use of this source code is governed by a
28238 // BSD-style license that can be found in the LICENSE file.
28239
28240
28241 class _HttpRequestUtils {
28242
28243 // Helper for factory HttpRequest.get
28244 static HttpRequest get(String url,
28245 onComplete(HttpRequest request),
28246 bool withCredentials) {
28247 final request = new HttpRequest();
28248 request.open('GET', url, async: true);
28249
28250 request.withCredentials = withCredentials;
28251
28252 request.onReadyStateChange.listen((e) {
28253 if (request.readyState == HttpRequest.DONE) {
28254 onComplete(request);
28255 }
28256 });
28257
28258 request.send();
28259
28260 return request;
28261 }
28262 }
28263 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28264 // for details. All rights reserved. Use of this source code is governed by a
28265 // BSD-style license that can be found in the LICENSE file.
28266
28267
28268 _serialize(var message) {
28269 return new _JsSerializer().traverse(message);
28270 }
28271
28272 class _JsSerializer extends _Serializer {
28273
28274 visitSendPortSync(SendPortSync x) {
28275 if (x is _JsSendPortSync) return visitJsSendPortSync(x);
28276 if (x is _LocalSendPortSync) return visitLocalSendPortSync(x);
28277 if (x is _RemoteSendPortSync) return visitRemoteSendPortSync(x);
28278 throw "Unknown port type $x";
28279 }
28280
28281 visitJsSendPortSync(_JsSendPortSync x) {
28282 return [ 'sendport', 'nativejs', x._id ];
28283 }
28284
28285 visitLocalSendPortSync(_LocalSendPortSync x) {
28286 return [ 'sendport', 'dart',
28287 ReceivePortSync._isolateId, x._receivePort._portId ];
28288 }
28289
28290 visitSendPort(SendPort x) {
28291 throw new UnimplementedError('Asynchronous send port not yet implemented.');
28292 }
28293
28294 visitRemoteSendPortSync(_RemoteSendPortSync x) {
28295 return [ 'sendport', 'dart', x._isolateId, x._portId ];
28296 }
28297 }
28298
28299 _deserialize(var message) {
28300 return new _JsDeserializer().deserialize(message);
28301 }
28302
28303
28304 class _JsDeserializer extends _Deserializer {
28305
28306 static const _UNSPECIFIED = const Object();
28307
28308 deserializeSendPort(List x) {
28309 String tag = x[1];
28310 switch (tag) {
28311 case 'nativejs':
28312 num id = x[2];
28313 return new _JsSendPortSync(id);
28314 case 'dart':
28315 num isolateId = x[2];
28316 num portId = x[3];
28317 return ReceivePortSync._lookup(isolateId, portId);
28318 default:
28319 throw 'Illegal SendPortSync type: $tag';
28320 }
28321 }
28322 }
28323
28324 // The receiver is JS.
28325 class _JsSendPortSync implements SendPortSync {
28326
28327 final num _id;
28328 _JsSendPortSync(this._id);
28329
28330 callSync(var message) {
28331 var serialized = _serialize(message);
28332 var result = _callPortSync(_id, serialized);
28333 return _deserialize(result);
28334 }
28335
28336 bool operator==(var other) {
28337 return (other is _JsSendPortSync) && (_id == other._id);
28338 }
28339
28340 int get hashCode => _id;
28341 }
28342
28343 // TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
28344 // The receiver is a different Dart isolate, compiled to JS.
28345 class _RemoteSendPortSync implements SendPortSync {
28346
28347 int _isolateId;
28348 int _portId;
28349 _RemoteSendPortSync(this._isolateId, this._portId);
28350
28351 callSync(var message) {
28352 var serialized = _serialize(message);
28353 var result = _call(_isolateId, _portId, serialized);
28354 return _deserialize(result);
28355 }
28356
28357 static _call(int isolateId, int portId, var message) {
28358 var target = 'dart-port-$isolateId-$portId';
28359 // TODO(vsm): Make this re-entrant.
28360 // TODO(vsm): Set this up set once, on the first call.
28361 var source = '$target-result';
28362 var result = null;
28363 window.on[source].first.then((Event e) {
28364 result = json.parse(_getPortSyncEventData(e));
28365 });
28366 _dispatchEvent(target, [source, message]);
28367 return result;
28368 }
28369
28370 bool operator==(var other) {
28371 return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
28372 && (_portId == other._portId);
28373 }
28374
28375 int get hashCode => _isolateId >> 16 + _portId;
28376 }
28377
28378 // The receiver is in the same Dart isolate, compiled to JS.
28379 class _LocalSendPortSync implements SendPortSync {
28380
28381 ReceivePortSync _receivePort;
28382
28383 _LocalSendPortSync._internal(this._receivePort);
28384
28385 callSync(var message) {
28386 // TODO(vsm): Do a more efficient deep copy.
28387 var copy = _deserialize(_serialize(message));
28388 var result = _receivePort._callback(copy);
28389 return _deserialize(_serialize(result));
28390 }
28391
28392 bool operator==(var other) {
28393 return (other is _LocalSendPortSync)
28394 && (_receivePort == other._receivePort);
28395 }
28396
28397 int get hashCode => _receivePort.hashCode;
28398 }
28399
28400 // TODO(vsm): Move this to dart:isolate. This will take some
28401 // refactoring as there are dependences here on the DOM. Users
28402 // interact with this class (or interface if we change it) directly -
28403 // new ReceivePortSync. I think most of the DOM logic could be
28404 // delayed until the corresponding SendPort is registered on the
28405 // window.
28406
28407 // A Dart ReceivePortSync (tagged 'dart' when serialized) is
28408 // identifiable / resolvable by the combination of its isolateid and
28409 // portid. When a corresponding SendPort is used within the same
28410 // isolate, the _portMap below can be used to obtain the
28411 // ReceivePortSync directly. Across isolates (or from JS), an
28412 // EventListener can be used to communicate with the port indirectly.
28413 class ReceivePortSync {
28414
28415 static Map<int, ReceivePortSync> _portMap;
28416 static int _portIdCount;
28417 static int _cachedIsolateId;
28418
28419 num _portId;
28420 Function _callback;
28421 StreamSubscription _portSubscription;
28422
28423 ReceivePortSync() {
28424 if (_portIdCount == null) {
28425 _portIdCount = 0;
28426 _portMap = new Map<int, ReceivePortSync>();
28427 }
28428 _portId = _portIdCount++;
28429 _portMap[_portId] = this;
28430 }
28431
28432 static int get _isolateId {
28433 // TODO(vsm): Make this coherent with existing isolate code.
28434 if (_cachedIsolateId == null) {
28435 _cachedIsolateId = _getNewIsolateId();
28436 }
28437 return _cachedIsolateId;
28438 }
28439
28440 static String _getListenerName(isolateId, portId) =>
28441 'dart-port-$isolateId-$portId';
28442 String get _listenerName => _getListenerName(_isolateId, _portId);
28443
28444 void receive(callback(var message)) {
28445 _callback = callback;
28446 if (_portSubscription == null) {
28447 _portSubscription = window.on[_listenerName].listen((Event e) {
28448 var data = json.parse(_getPortSyncEventData(e));
28449 var replyTo = data[0];
28450 var message = _deserialize(data[1]);
28451 var result = _callback(message);
28452 _dispatchEvent(replyTo, _serialize(result));
28453 });
28454 }
28455 }
28456
28457 void close() {
28458 _portMap.remove(_portId);
28459 if (_portSubscription != null) _portSubscription.cancel();
28460 }
28461
28462 SendPortSync toSendPort() {
28463 return new _LocalSendPortSync._internal(this);
28464 }
28465
28466 static SendPortSync _lookup(int isolateId, int portId) {
28467 if (isolateId == _isolateId) {
28468 return _portMap[portId].toSendPort();
28469 } else {
28470 return new _RemoteSendPortSync(isolateId, portId);
28471 }
28472 }
28473 }
28474
28475 get _isolateId => ReceivePortSync._isolateId;
28476
28477 void _dispatchEvent(String receiver, var message) {
28478 var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
28479 detail: json.stringify(message));
28480 window.dispatchEvent(event);
28481 }
28482
28483 String _getPortSyncEventData(CustomEvent event) => event.detail;
28484 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28485 // for details. All rights reserved. Use of this source code is governed by a
28486 // BSD-style license that can be found in the LICENSE file.
28487
28488
28489 typedef void _MicrotaskCallback();
28490
28491 /**
28492 * This class attempts to invoke a callback as soon as the current event stack
28493 * unwinds, but before the browser repaints.
28494 */
28495 abstract class _MicrotaskScheduler {
28496 bool _nextMicrotaskFrameScheduled = false;
28497 final _MicrotaskCallback _callback;
28498
28499 _MicrotaskScheduler(this._callback);
28500
28501 /**
28502 * Creates the best possible microtask scheduler for the current platform.
28503 */
28504 factory _MicrotaskScheduler.best(_MicrotaskCallback callback) {
28505 if (Window._supportsSetImmediate) {
28506 return new _SetImmediateScheduler(callback);
28507 } else if (MutationObserver.supported) {
28508 return new _MutationObserverScheduler(callback);
28509 }
28510 return new _PostMessageScheduler(callback);
28511 }
28512
28513 /**
28514 * Schedules a microtask callback if one has not been scheduled already.
28515 */
28516 void maybeSchedule() {
28517 if (this._nextMicrotaskFrameScheduled) {
28518 return;
28519 }
28520 this._nextMicrotaskFrameScheduled = true;
28521 this._schedule();
28522 }
28523
28524 /**
28525 * Does the actual scheduling of the callback.
28526 */
28527 void _schedule();
28528
28529 /**
28530 * Handles the microtask callback and forwards it if necessary.
28531 */
28532 void _onCallback() {
28533 // Ignore spurious messages.
28534 if (!_nextMicrotaskFrameScheduled) {
28535 return;
28536 }
28537 _nextMicrotaskFrameScheduled = false;
28538 this._callback();
28539 }
28540 }
28541
28542 /**
28543 * Scheduler which uses window.postMessage to schedule events.
28544 */
28545 class _PostMessageScheduler extends _MicrotaskScheduler {
28546 const _MICROTASK_MESSAGE = "DART-MICROTASK";
28547
28548 _PostMessageScheduler(_MicrotaskCallback callback): super(callback) {
28549 // Messages from other windows do not cause a security risk as
28550 // all we care about is that _handleMessage is called
28551 // after the current event loop is unwound and calling the function is
28552 // a noop when zero requests are pending.
28553 window.onMessage.listen(this._handleMessage);
28554 }
28555
28556 void _schedule() {
28557 window.postMessage(_MICROTASK_MESSAGE, "*");
28558 }
28559
28560 void _handleMessage(e) {
28561 this._onCallback();
28562 }
28563 }
28564
28565 /**
28566 * Scheduler which uses a MutationObserver to schedule events.
28567 */
28568 class _MutationObserverScheduler extends _MicrotaskScheduler {
28569 MutationObserver _observer;
28570 Element _dummy;
28571
28572 _MutationObserverScheduler(_MicrotaskCallback callback): super(callback) {
28573 // Mutation events get fired as soon as the current event stack is unwound
28574 // so we just make a dummy event and listen for that.
28575 _observer = new MutationObserver(this._handleMutation);
28576 _dummy = new DivElement();
28577 _observer.observe(_dummy, attributes: true);
28578 }
28579
28580 void _schedule() {
28581 // Toggle it to trigger the mutation event.
28582 _dummy.hidden = !_dummy.hidden;
28583 }
28584
28585 _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
28586 this._onCallback();
28587 }
28588 }
28589
28590 /**
28591 * Scheduler which uses window.setImmediate to schedule events.
28592 */
28593 class _SetImmediateScheduler extends _MicrotaskScheduler {
28594 _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
28595
28596 void _schedule() {
28597 window._setImmediate(_handleImmediate);
28598 }
28599
28600 void _handleImmediate() {
28601 this._onCallback();
28602 }
28603 }
28604
28605 List<TimeoutHandler> _pendingMicrotasks;
28606 _MicrotaskScheduler _microtaskScheduler = null;
28607
28608 void _maybeScheduleMicrotaskFrame() {
28609 if (_microtaskScheduler == null) {
28610 _microtaskScheduler =
28611 new _MicrotaskScheduler.best(_completeMicrotasks);
28612 }
28613 _microtaskScheduler.maybeSchedule();
28614 }
28615
28616 /**
28617 * Registers a [callback] which is called after the current execution stack
28618 * unwinds.
28619 */
28620 void _addMicrotaskCallback(TimeoutHandler callback) {
28621 if (_pendingMicrotasks == null) {
28622 _pendingMicrotasks = <TimeoutHandler>[];
28623 _maybeScheduleMicrotaskFrame();
28624 }
28625 _pendingMicrotasks.add(callback);
28626 }
28627
28628
28629 /**
28630 * Complete all pending microtasks.
28631 */
28632 void _completeMicrotasks() {
28633 var callbacks = _pendingMicrotasks;
28634 _pendingMicrotasks = null;
28635 for (var callback in callbacks) {
28636 callback();
28637 }
28638 }
28639 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28640 // for details. All rights reserved. Use of this source code is governed by a
28641 // BSD-style license that can be found in the LICENSE file.
28642
28643 // Patch file for the dart:isolate library.
28644
28645
28646 /********************************************************
28647 Inserted from lib/isolate/serialization.dart
28648 ********************************************************/
28649
28650 class _MessageTraverserVisitedMap {
28651
28652 operator[](var object) => null;
28653 void operator[]=(var object, var info) { }
28654
28655 void reset() { }
28656 void cleanup() { }
28657
28658 }
28659
28660 /** Abstract visitor for dart objects that can be sent as isolate messages. */
28661 abstract class _MessageTraverser {
28662
28663 _MessageTraverserVisitedMap _visited;
28664 _MessageTraverser() : _visited = new _MessageTraverserVisitedMap();
28665
28666 /** Visitor's entry point. */
28667 traverse(var x) {
28668 if (isPrimitive(x)) return visitPrimitive(x);
28669 _visited.reset();
28670 var result;
28671 try {
28672 result = _dispatch(x);
28673 } finally {
28674 _visited.cleanup();
28675 }
28676 return result;
28677 }
28678
28679 _dispatch(var x) {
28680 if (isPrimitive(x)) return visitPrimitive(x);
28681 if (x is List) return visitList(x);
28682 if (x is Map) return visitMap(x);
28683 if (x is SendPort) return visitSendPort(x);
28684 if (x is SendPortSync) return visitSendPortSync(x);
28685
28686 // Overridable fallback.
28687 return visitObject(x);
28688 }
28689
28690 visitPrimitive(x);
28691 visitList(List x);
28692 visitMap(Map x);
28693 visitSendPort(SendPort x);
28694 visitSendPortSync(SendPortSync x);
28695
28696 visitObject(Object x) {
28697 // TODO(floitsch): make this a real exception. (which one)?
28698 throw "Message serialization: Illegal value $x passed";
28699 }
28700
28701 static bool isPrimitive(x) {
28702 return (x == null) || (x is String) || (x is num) || (x is bool);
28703 }
28704 }
28705
28706
28707 /** Visitor that serializes a message as a JSON array. */
28708 abstract class _Serializer extends _MessageTraverser {
28709 int _nextFreeRefId = 0;
28710
28711 visitPrimitive(x) => x;
28712
28713 visitList(List list) {
28714 int copyId = _visited[list];
28715 if (copyId != null) return ['ref', copyId];
28716
28717 int id = _nextFreeRefId++;
28718 _visited[list] = id;
28719 var jsArray = _serializeList(list);
28720 // TODO(floitsch): we are losing the generic type.
28721 return ['list', id, jsArray];
28722 }
28723
28724 visitMap(Map map) {
28725 int copyId = _visited[map];
28726 if (copyId != null) return ['ref', copyId];
28727
28728 int id = _nextFreeRefId++;
28729 _visited[map] = id;
28730 var keys = _serializeList(map.keys.toList());
28731 var values = _serializeList(map.values.toList());
28732 // TODO(floitsch): we are losing the generic type.
28733 return ['map', id, keys, values];
28734 }
28735
28736 _serializeList(List list) {
28737 int len = list.length;
28738 var result = new List(len);
28739 for (int i = 0; i < len; i++) {
28740 result[i] = _dispatch(list[i]);
28741 }
28742 return result;
28743 }
28744 }
28745
28746 /** Deserializes arrays created with [_Serializer]. */
28747 abstract class _Deserializer {
28748 Map<int, dynamic> _deserialized;
28749
28750 _Deserializer();
28751
28752 static bool isPrimitive(x) {
28753 return (x == null) || (x is String) || (x is num) || (x is bool);
28754 }
28755
28756 deserialize(x) {
28757 if (isPrimitive(x)) return x;
28758 // TODO(floitsch): this should be new HashMap<int, dynamic>()
28759 _deserialized = new HashMap();
28760 return _deserializeHelper(x);
28761 }
28762
28763 _deserializeHelper(x) {
28764 if (isPrimitive(x)) return x;
28765 assert(x is List);
28766 switch (x[0]) {
28767 case 'ref': return _deserializeRef(x);
28768 case 'list': return _deserializeList(x);
28769 case 'map': return _deserializeMap(x);
28770 case 'sendport': return deserializeSendPort(x);
28771 default: return deserializeObject(x);
28772 }
28773 }
28774
28775 _deserializeRef(List x) {
28776 int id = x[1];
28777 var result = _deserialized[id];
28778 assert(result != null);
28779 return result;
28780 }
28781
28782 List _deserializeList(List x) {
28783 int id = x[1];
28784 // We rely on the fact that Dart-lists are directly mapped to Js-arrays.
28785 List dartList = x[2];
28786 _deserialized[id] = dartList;
28787 int len = dartList.length;
28788 for (int i = 0; i < len; i++) {
28789 dartList[i] = _deserializeHelper(dartList[i]);
28790 }
28791 return dartList;
28792 }
28793
28794 Map _deserializeMap(List x) {
28795 Map result = new Map();
28796 int id = x[1];
28797 _deserialized[id] = result;
28798 List keys = x[2];
28799 List values = x[3];
28800 int len = keys.length;
28801 assert(len == values.length);
28802 for (int i = 0; i < len; i++) {
28803 var key = _deserializeHelper(keys[i]);
28804 var value = _deserializeHelper(values[i]);
28805 result[key] = value;
28806 }
28807 return result;
28808 }
28809
28810 deserializeSendPort(List x);
28811
28812 deserializeObject(List x) {
28813 // TODO(floitsch): Use real exception (which one?).
28814 throw "Unexpected serialized object";
28815 }
28816 }
28817
28818 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 29646 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
28819 // for details. All rights reserved. Use of this source code is governed by a 29647 // for details. All rights reserved. Use of this source code is governed by a
28820 // BSD-style license that can be found in the LICENSE file. 29648 // BSD-style license that can be found in the LICENSE file.
29649
29650
29651
29652 /**
29653 * Interface used to validate that only accepted elements and attributes are
29654 * allowed while parsing HTML strings into DOM nodes.
29655 */
29656 abstract class NodeValidator {
29657
29658 /**
29659 * Construct a default NodeValidator which only accepts whitelisted HTML5
29660 * elements and attributes.
29661 *
29662 * If a uriPolicy is not specified then the default uriPolicy will be used.
29663 */
29664 factory NodeValidator({UriPolicy uriPolicy}) =>
29665 new _Html5NodeValidator(uriPolicy: uriPolicy);
29666
29667 /**
29668 * Returns true if the tagName is an accepted type.
29669 */
29670 bool allowsElement(Element element);
29671
29672 /**
29673 * Returns true if the attribute is allowed.
29674 *
29675 * The attributeName parameter will always be in lowercase.
29676 *
29677 * See [allowsElement] for format of tagName.
29678 */
29679 bool allowsAttribute(Element element, String attributeName, String value);
29680 }
29681
29682 /**
29683 * Performs sanitization of a node tree after construction to ensure that it
29684 * does not contain any disallowed elements or attributes.
29685 *
29686 * In general custom implementations of this class should not be necessary and
29687 * all validation customization should be done in custom NodeValidators, but
29688 * custom implementations of this class can be created to perform more complex
29689 * tree sanitization.
29690 */
29691 abstract class NodeTreeSanitizer {
29692
29693 /**
29694 * Constructs a default tree sanitizer which will remove all elements and
29695 * attributes which are not allowed by the provided validator.
29696 */
29697 factory NodeTreeSanitizer(NodeValidator validator) =>
29698 new _ValidatingTreeSanitizer(validator);
29699
29700 /**
29701 * Called with the root of the tree which is to be sanitized.
29702 *
29703 * This method needs to walk the entire tree and either remove elements and
29704 * attributes which are not recognized as safe or throw an exception which
29705 * will mark the entire tree as unsafe.
29706 */
29707 void sanitizeTree(Node node);
29708 }
29709
29710 /**
29711 * Defines the policy for what types of uris are allowed for particular
29712 * attribute values.
29713 *
29714 * This can be used to provide custom rules such as allowing all http:// URIs
29715 * for image attributes but only same-origin URIs for anchor tags.
29716 */
29717 abstract class UriPolicy {
29718 /**
29719 * Constructs the default UriPolicy which is to only allow Uris to the same
29720 * origin as the application was launched from.
29721 *
29722 * This will block all ftp: mailto: URIs. It will also block accessing
29723 * https://example.com if the app is running from http://example.com.
29724 */
29725 factory UriPolicy() => new _SameOriginUriPolicy();
29726
29727 /**
29728 * Checks if the uri is allowed on the specified attribute.
29729 *
29730 * The uri provided may or may not be a relative path.
29731 */
29732 bool allowsUri(String uri);
29733 }
29734
29735 /**
29736 * Allows URIs to the same origin as the current application was loaded from
29737 * (such as https://example.com:80).
29738 */
29739 class _SameOriginUriPolicy implements UriPolicy {
29740 final AnchorElement _hiddenAnchor = new AnchorElement();
29741
29742 bool allowsUri(String uri) {
29743 _hiddenAnchor.href = uri;
29744 return _hiddenAnchor.href.startsWith(window.location.origin);
29745 }
29746 }
29747
29748
29749 /**
29750 * Standard tree sanitizer which validates a node tree against the provided
29751 * validator and removes any nodes or attributes which are not allowed.
29752 */
29753 class _ValidatingTreeSanitizer implements NodeTreeSanitizer {
29754 final NodeValidator validator;
29755 _ValidatingTreeSanitizer(this.validator) {}
29756
29757 void sanitizeTree(Node node) {
29758 void walk(Node node) {
29759 sanitizeNode(node);
29760
29761 var child = node.$dom_lastChild;
29762 while (child != null) {
29763 // Child may be removed during the walk.
29764 var nextChild = child.previousNode;
29765 walk(child);
29766 child = nextChild;
29767 }
29768 }
29769 walk(node);
29770 }
29771
29772 void sanitizeNode(Node node) {
29773 switch (node.nodeType) {
29774 case Node.ELEMENT_NODE:
29775 Element element = node;
29776 var attrs = element.attributes;
29777 if (!validator.allowsElement(element)) {
29778 element.remove();
29779 break;
29780 }
29781
29782 var isAttr = attrs['is'];
29783 if (isAttr != null) {
29784 if (!validator.allowsAttribute(element, 'is', isAttr)) {
29785 element.remove();
29786 break;
29787 }
29788 }
29789
29790 // TODO(blois): Need to be able to get all attributes, irrespective of
29791 // XMLNS.
29792 var keys = attrs.keys.toList();
29793 for (var i = attrs.length - 1; i >= 0; --i) {
29794 var name = keys[i];
29795 if (!validator.allowsAttribute(element, name, attrs[name])) {
29796 attrs.remove(name);
29797 }
29798 }
29799 break;
29800 case Node.COMMENT_NODE:
29801 case Node.DOCUMENT_FRAGMENT_NODE:
29802 case Node.TEXT_NODE:
29803 case Node.CDATA_SECTION_NODE:
29804 break;
29805 default:
29806 node.remove();
29807 }
29808 }
29809 }
29810 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
29811 // for details. All rights reserved. Use of this source code is governed by a
29812 // BSD-style license that can be found in the LICENSE file.
28821 29813
28822 29814
28823 /** 29815 /**
28824 * Helper class to implement custom events which wrap DOM events. 29816 * Helper class to implement custom events which wrap DOM events.
28825 */ 29817 */
28826 class _WrappedEvent implements Event { 29818 class _WrappedEvent implements Event {
28827 final Event wrapped; 29819 final Event wrapped;
28828 _WrappedEvent(this.wrapped); 29820 _WrappedEvent(this.wrapped);
28829 29821
28830 bool get bubbles => wrapped.bubbles; 29822 bool get bubbles => wrapped.bubbles;
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
28941 return _iterator.moveNext(); 29933 return _iterator.moveNext();
28942 } 29934 }
28943 29935
28944 E get current => _iterator.current; 29936 E get current => _iterator.current;
28945 } 29937 }
28946 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 29938 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28947 // for details. All rights reserved. Use of this source code is governed by a 29939 // for details. All rights reserved. Use of this source code is governed by a
28948 // BSD-style license that can be found in the LICENSE file. 29940 // BSD-style license that can be found in the LICENSE file.
28949 29941
28950 29942
29943 class _HttpRequestUtils {
29944
29945 // Helper for factory HttpRequest.get
29946 static HttpRequest get(String url,
29947 onComplete(HttpRequest request),
29948 bool withCredentials) {
29949 final request = new HttpRequest();
29950 request.open('GET', url, async: true);
29951
29952 request.withCredentials = withCredentials;
29953
29954 request.onReadyStateChange.listen((e) {
29955 if (request.readyState == HttpRequest.DONE) {
29956 onComplete(request);
29957 }
29958 });
29959
29960 request.send();
29961
29962 return request;
29963 }
29964 }
29965 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
29966 // for details. All rights reserved. Use of this source code is governed by a
29967 // BSD-style license that can be found in the LICENSE file.
29968
29969
28951 // Conversions for Window. These check if the window is the local 29970 // Conversions for Window. These check if the window is the local
28952 // window, and if it's not, wraps or unwraps it with a secure wrapper. 29971 // window, and if it's not, wraps or unwraps it with a secure wrapper.
28953 // We need to test for EventTarget here as well as it's a base type. 29972 // We need to test for EventTarget here as well as it's a base type.
28954 // We omit an unwrapper for Window as no methods take a non-local 29973 // We omit an unwrapper for Window as no methods take a non-local
28955 // window as a parameter. 29974 // window as a parameter.
28956 29975
28957 29976
28958 DateTime _convertNativeToDart_DateTime(date) { 29977 DateTime _convertNativeToDart_DateTime(date) {
28959 var millisSinceEpoch = JS('int', '#.getTime()', date); 29978 var millisSinceEpoch = JS('int', '#.getTime()', date);
28960 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 29979 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
29357 _position = nextPosition; 30376 _position = nextPosition;
29358 return true; 30377 return true;
29359 } 30378 }
29360 _current = null; 30379 _current = null;
29361 _position = _array.length; 30380 _position = _array.length;
29362 return false; 30381 return false;
29363 } 30382 }
29364 30383
29365 T get current => _current; 30384 T get current => _current;
29366 } 30385 }
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/html/dartium/html_dartium.dart » ('j') | sdk/lib/html/dartium/html_dartium.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698