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

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
« no previous file with comments | « no previous file | sdk/lib/html/dartium/html_dartium.dart » ('j') | tests/html/safe_dom_test.dart » ('J')
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 498 matching lines...) Expand 10 before | Expand all | Expand 10 after
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.
7464 * 7460 *
7465 * The [html] fragment must represent valid HTML with a single element root, 7461 * var element = new Element.html('<div class="foo">content</div>');
7466 * which will be parsed and returned.
7467 * 7462 *
7468 * Important: the contents of [html] should not contain any user-supplied 7463 * The HTML fragment should contain only one single root element, any
7469 * data. Without strict data validation it is impossible to prevent script 7464 * leading or trailing text nodes will be removed.
7470 * injection exploits.
7471 * 7465 *
7472 * It is instead recommended that elements be constructed via [Element.tag] 7466 * The HTML fragment is parsed as if it occurred within the context of a
7473 * and text be added via [text]. 7467 * `<body>` tag, this means that special elements such as `<caption>` which
7468 * must be parsed within the scope of a `<table>` element will be dropped. Use
7469 * [createFragment] to parse contextual HTML fragments.
7474 * 7470 *
7475 * var element = new Element.html('<div class="foo">content</div>'); 7471 * Unless a validator is provided this will perform the default validation
7472 * and remove all scriptable elements and attributes.
7473 *
7474 * See also:
7475 *
7476 * * [NodeValidator]
7477 *
7476 */ 7478 */
7477 factory Element.html(String html) => 7479 factory Element.html(String html,
7478 _ElementFactoryProvider.createElement_html(html); 7480 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
7481 var fragment = document.body.createFragment(html, validator: validator,
7482 treeSanitizer: treeSanitizer);
7483 return fragment.nodes.where((e) => e is Element).single;
7484 }
7479 7485
7480 /** 7486 /**
7481 * Creates the HTML element specified by the tag name. 7487 * Creates the HTML element specified by the tag name.
7482 * 7488 *
7483 * This is similar to [Document.createElement]. 7489 * This is similar to [Document.createElement].
7484 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then 7490 * [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
7485 * this will create an [UnknownElement]. 7491 * this will create an [UnknownElement].
7486 * 7492 *
7487 * var divElement = new Element.tag('div'); 7493 * var divElement = new Element.tag('div');
7488 * print(divElement is DivElement); // 'true' 7494 * print(divElement is DivElement); // 'true'
(...skipping 650 matching lines...) Expand 10 before | Expand all | Expand 10 after
8139 @Experimental 8145 @Experimental
8140 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate; 8146 bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
8141 8147
8142 void _ensureTemplate() { 8148 void _ensureTemplate() {
8143 if (!isTemplate) { 8149 if (!isTemplate) {
8144 throw new UnsupportedError('$this is not a template.'); 8150 throw new UnsupportedError('$this is not a template.');
8145 } 8151 }
8146 TemplateElement.decorate(this); 8152 TemplateElement.decorate(this);
8147 } 8153 }
8148 8154
8155 /**
8156 * Create a DocumentFragment from the HTML fragment and ensure that it follows
8157 * the sanitization rules specified by the validator or treeSanitizer.
8158 *
8159 * If the default validation behavior is too restrictive then a new
8160 * NodeValidator should be created, either extending or wrapping a default
8161 * validator and overriding the validation APIs.
8162 *
8163 * The treeSanitizer is used to walk the generated node tree and sanitize it.
8164 * A custom treeSanitizer can also be provided to perform special validation
8165 * rules but since the API is more complex to implement this is discouraged.
8166 *
8167 * The returned tree is guaranteed to only contain nodes and attributes which
8168 * are allowed by the provided validator.
8169 *
8170 * See also:
8171 *
8172 * * [NodeValidator]
8173 * * [NodeTreeSanitizer]
8174 */
8175 DocumentFragment createFragment(String html,
8176 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
8177
8178 if (treeSanitizer == null) {
8179 if (validator == null) {
8180 validator = new NodeValidatorBuilder.common();
8181 }
8182 treeSanitizer = new NodeTreeSanitizer(validator);
8183 } else if (validator != null) {
8184 throw new ArgumentError(
8185 'validator can only be passed if treeSanitizer is null');
8186 }
8187
8188 return _ElementFactoryProvider._parseHtml(this, html, treeSanitizer);
8189 }
8190
8191 void set innerHtml(String html) {
8192 this.setInnerHtml(html);
8193 }
8194
8195 void setInnerHtml(String html,
8196 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
8197 text = null;
8198 append(createFragment(
8199 html, validator: validator, treeSanitizer: treeSanitizer));
8200 }
8201
8202 String get innerHtml => $dom_innerHtml;
8203
8149 8204
8150 @DomName('Element.abortEvent') 8205 @DomName('Element.abortEvent')
8151 @DocsEditable 8206 @DocsEditable
8152 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort'); 8207 static const EventStreamProvider<Event> abortEvent = const EventStreamProvider <Event>('abort');
8153 8208
8154 @DomName('Element.beforecopyEvent') 8209 @DomName('Element.beforecopyEvent')
8155 @DocsEditable 8210 @DocsEditable
8156 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy'); 8211 static const EventStreamProvider<Event> beforeCopyEvent = const EventStreamPro vider<Event>('beforecopy');
8157 8212
8158 @DomName('Element.beforecutEvent') 8213 @DomName('Element.beforecutEvent')
(...skipping 211 matching lines...) Expand 10 before | Expand all | Expand 10 after
8370 @DocsEditable 8425 @DocsEditable
8371 bool hidden; 8426 bool hidden;
8372 8427
8373 @DomName('Element.id') 8428 @DomName('Element.id')
8374 @DocsEditable 8429 @DocsEditable
8375 String id; 8430 String id;
8376 8431
8377 @JSName('innerHTML') 8432 @JSName('innerHTML')
8378 @DomName('Element.innerHTML') 8433 @DomName('Element.innerHTML')
8379 @DocsEditable 8434 @DocsEditable
8380 String innerHtml; 8435 String $dom_innerHtml;
8381 8436
8382 @DomName('Element.isContentEditable') 8437 @DomName('Element.isContentEditable')
8383 @DocsEditable 8438 @DocsEditable
8384 final bool isContentEditable; 8439 final bool isContentEditable;
8385 8440
8386 @DomName('Element.lang') 8441 @DomName('Element.lang')
8387 @DocsEditable 8442 @DocsEditable
8388 String lang; 8443 String lang;
8389 8444
8390 @JSName('outerHTML') 8445 @JSName('outerHTML')
(...skipping 558 matching lines...) Expand 10 before | Expand all | Expand 10 after
8949 9004
8950 @DomName('Element.onwebkitfullscreenerror') 9005 @DomName('Element.onwebkitfullscreenerror')
8951 @DocsEditable 9006 @DocsEditable
8952 // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html 9007 // https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html
8953 @Experimental 9008 @Experimental
8954 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this); 9009 Stream<Event> get onFullscreenError => fullscreenErrorEvent.forTarget(this);
8955 9010
8956 } 9011 }
8957 9012
8958 9013
8959 final _START_TAG_REGEXP = new RegExp('<(\\w+)');
8960 class _ElementFactoryProvider { 9014 class _ElementFactoryProvider {
8961 static const _CUSTOM_PARENT_TAG_MAP = const {
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 9015
8976 @DomName('Document.createElement') 9016 static HtmlDocument _parseDocument;
8977 static Element createElement_html(String html) { 9017
8978 // TODO(jacobr): this method can be made more robust and performant. 9018 static DocumentFragment _parseHtml(Element context, String html,
8979 // 1) Cache the dummy parent elements required to use innerHTML rather than 9019 NodeTreeSanitizer treeSanitizer) {
8980 // creating them every call. 9020
8981 // 2) Verify that the html does not contain leading or trailing text nodes. 9021 if (_parseDocument == null) {
8982 // 3) Verify that the html does not contain both <head> and <body> tags. 9022 _parseDocument = document.implementation.createHtmlDocument('');
8983 // 4) Detatch the created element from its dummy parent. 9023 }
8984 String parentTag = 'div'; 9024 var contextElement;
8985 String tag; 9025 if (context == null || context is BodyElement) {
8986 final match = _START_TAG_REGEXP.firstMatch(html); 9026 contextElement = _parseDocument.body;
8987 if (match != null) { 9027 } else {
8988 tag = match.group(1).toLowerCase(); 9028 contextElement = _parseDocument.$dom_createElement(context.tagName);
8989 if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) { 9029 _parseDocument.body.append(contextElement);
8990 return _createTableForIE(html, tag);
8991 }
8992 parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
8993 if (parentTag == null) parentTag = 'div';
8994 } 9030 }
8995 9031
8996 final temp = new Element.tag(parentTag); 9032 if (Range.supportsCreateContextualFragment) {
8997 temp.innerHtml = html; 9033 var range = _parseDocument.$dom_createRange();
9034 range.selectNodeContents(contextElement);
9035 var fragment = range.createContextualFragment(html);
8998 9036
8999 Element element; 9037 if (contextElement != _parseDocument.body) {
9000 if (temp.children.length == 1) { 9038 contextElement.remove();
9001 element = temp.children[0]; 9039 }
9002 } else if (parentTag == 'html' && temp.children.length == 2) { 9040
9003 // In html5 the root <html> tag will always have a <body> and a <head>, 9041 treeSanitizer.sanitizeTree(fragment);
9004 // even though the inner html only contains one of them. 9042 return fragment;
9005 element = temp.children[tag == 'head' ? 0 : 1];
9006 } else { 9043 } else {
9007 _singleNode(temp.children); 9044 contextElement.$dom_innerHtml = html;
9045
9046 treeSanitizer.sanitizeTree(contextElement);
9047
9048 var fragment = new DocumentFragment();
9049 while (contextElement.$dom_firstChild != null) {
9050 fragment.append(contextElement.$dom_firstChild);
9051 }
9052 return fragment;
9008 } 9053 }
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 } 9054 }
9072 9055
9073 @DomName('Document.createElement') 9056 @DomName('Document.createElement')
9074 // Optimization to improve performance until the dart2js compiler inlines this 9057 // Optimization to improve performance until the dart2js compiler inlines this
9075 // method. 9058 // method.
9076 static dynamic createElement_tag(String tag) => 9059 static dynamic createElement_tag(String tag) =>
9077 // Firefox may return a JS function for some types (Embed, Object). 9060 // Firefox may return a JS function for some types (Embed, Object).
9078 JS('Element|=Object', 'document.createElement(#)', tag); 9061 JS('Element|=Object', 'document.createElement(#)', tag);
9079 } 9062 }
9080 9063
(...skipping 11527 matching lines...) Expand 10 before | Expand all | Expand 10 after
20608 }'''; 20591 }''';
20609 document.head.append(style); 20592 document.head.append(style);
20610 } 20593 }
20611 20594
20612 /** 20595 /**
20613 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for 20596 * A mapping of names to Custom Syntax objects. See [CustomBindingSyntax] for
20614 * more information. 20597 * more information.
20615 */ 20598 */
20616 @Experimental 20599 @Experimental
20617 static Map<String, CustomBindingSyntax> syntax = {}; 20600 static Map<String, CustomBindingSyntax> syntax = {};
20601
20602 // An override to place the contents into content rather than as child nodes.
20603 void setInnerHtml(String html,
20604 {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
20605 text = null;
20606 var fragment = createFragment(
20607 html, validator: validator, treeSanitizer: treeSanitizer);
20608
20609 content.append(fragment);
20610 }
20618 } 20611 }
20619 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 20612 // 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 20613 // 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. 20614 // BSD-style license that can be found in the LICENSE file.
20622 20615
20623 // WARNING: Do not edit - generated code. 20616 // WARNING: Do not edit - generated code.
20624 20617
20625 20618
20626 @DomName('Text') 20619 @DomName('Text')
20627 class Text extends CharacterData native "Text" { 20620 class Text extends CharacterData native "Text" {
(...skipping 5055 matching lines...) Expand 10 before | Expand all | Expand 10 after
25683 const _CustomEventStreamProvider(this._eventTypeGetter); 25676 const _CustomEventStreamProvider(this._eventTypeGetter);
25684 25677
25685 Stream<T> forTarget(EventTarget e, {bool useCapture: false}) { 25678 Stream<T> forTarget(EventTarget e, {bool useCapture: false}) {
25686 return new _EventStream(e, _eventTypeGetter(e), useCapture); 25679 return new _EventStream(e, _eventTypeGetter(e), useCapture);
25687 } 25680 }
25688 25681
25689 String getEventType(EventTarget target) { 25682 String getEventType(EventTarget target) {
25690 return _eventTypeGetter(target); 25683 return _eventTypeGetter(target);
25691 } 25684 }
25692 } 25685 }
25686 // DO NOT EDIT- this file is generated from running tool/generator.sh.
25687
25688 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
25689 // for details. All rights reserved. Use of this source code is governed by a
25690 // BSD-style license that can be found in the LICENSE file.
25691
25692
25693 /**
25694 * A Dart DOM validator generated from Caja whitelists.
25695 *
25696 * This contains a whitelist of known HTML tagNames and attributes and will only
25697 * accept known good values.
25698 *
25699 * See also:
25700 *
25701 * * https://code.google.com/p/google-caja/wiki/CajaWhitelists
25702 */
25703 class _Html5NodeValidator implements NodeValidator {
25704
25705 static final Set<String> _allowedElements = new Set.from([
25706 'A',
25707 'ABBR',
25708 'ACRONYM',
25709 'ADDRESS',
25710 'AREA',
25711 'ARTICLE',
25712 'ASIDE',
25713 'AUDIO',
25714 'B',
25715 'BDI',
25716 'BDO',
25717 'BIG',
25718 'BLOCKQUOTE',
25719 'BR',
25720 'BUTTON',
25721 'CANVAS',
25722 'CAPTION',
25723 'CENTER',
25724 'CITE',
25725 'CODE',
25726 'COL',
25727 'COLGROUP',
25728 'COMMAND',
25729 'DATA',
25730 'DATALIST',
25731 'DD',
25732 'DEL',
25733 'DETAILS',
25734 'DFN',
25735 'DIR',
25736 'DIV',
25737 'DL',
25738 'DT',
25739 'EM',
25740 'FIELDSET',
25741 'FIGCAPTION',
25742 'FIGURE',
25743 'FONT',
25744 'FOOTER',
25745 'FORM',
25746 'H1',
25747 'H2',
25748 'H3',
25749 'H4',
25750 'H5',
25751 'H6',
25752 'HEADER',
25753 'HGROUP',
25754 'HR',
25755 'I',
25756 'IFRAME',
25757 'IMG',
25758 'INPUT',
25759 'INS',
25760 'KBD',
25761 'LABEL',
25762 'LEGEND',
25763 'LI',
25764 'MAP',
25765 'MARK',
25766 'MENU',
25767 'METER',
25768 'NAV',
25769 'NOBR',
25770 'OL',
25771 'OPTGROUP',
25772 'OPTION',
25773 'OUTPUT',
25774 'P',
25775 'PRE',
25776 'PROGRESS',
25777 'Q',
25778 'S',
25779 'SAMP',
25780 'SECTION',
25781 'SELECT',
25782 'SMALL',
25783 'SOURCE',
25784 'SPAN',
25785 'STRIKE',
25786 'STRONG',
25787 'SUB',
25788 'SUMMARY',
25789 'SUP',
25790 'TABLE',
25791 'TBODY',
25792 'TD',
25793 'TEXTAREA',
25794 'TFOOT',
25795 'TH',
25796 'THEAD',
25797 'TIME',
25798 'TR',
25799 'TRACK',
25800 'TT',
25801 'U',
25802 'UL',
25803 'VAR',
25804 'VIDEO',
25805 'WBR',
25806 ]);
25807
25808 static const _standardAttributes = const <String>[
25809 '*::class',
25810 '*::dir',
25811 '*::draggable',
25812 '*::hidden',
25813 '*::id',
25814 '*::inert',
25815 '*::itemprop',
25816 '*::itemref',
25817 '*::itemscope',
25818 '*::lang',
25819 '*::spellcheck',
25820 '*::title',
25821 '*::translate',
25822 'A::accesskey',
25823 'A::coords',
25824 'A::hreflang',
25825 'A::name',
25826 'A::shape',
25827 'A::tabindex',
25828 'A::target',
25829 'A::type',
25830 'AREA::accesskey',
25831 'AREA::alt',
25832 'AREA::coords',
25833 'AREA::nohref',
25834 'AREA::shape',
25835 'AREA::tabindex',
25836 'AREA::target',
25837 'AUDIO::controls',
25838 'AUDIO::loop',
25839 'AUDIO::mediagroup',
25840 'AUDIO::muted',
25841 'AUDIO::preload',
25842 'BDO::dir',
25843 'BODY::alink',
25844 'BODY::bgcolor',
25845 'BODY::link',
25846 'BODY::text',
25847 'BODY::vlink',
25848 'BR::clear',
25849 'BUTTON::accesskey',
25850 'BUTTON::disabled',
25851 'BUTTON::name',
25852 'BUTTON::tabindex',
25853 'BUTTON::type',
25854 'BUTTON::value',
25855 'CANVAS::height',
25856 'CANVAS::width',
25857 'CAPTION::align',
25858 'COL::align',
25859 'COL::char',
25860 'COL::charoff',
25861 'COL::span',
25862 'COL::valign',
25863 'COL::width',
25864 'COLGROUP::align',
25865 'COLGROUP::char',
25866 'COLGROUP::charoff',
25867 'COLGROUP::span',
25868 'COLGROUP::valign',
25869 'COLGROUP::width',
25870 'COMMAND::checked',
25871 'COMMAND::command',
25872 'COMMAND::disabled',
25873 'COMMAND::label',
25874 'COMMAND::radiogroup',
25875 'COMMAND::type',
25876 'DATA::value',
25877 'DEL::datetime',
25878 'DETAILS::open',
25879 'DIR::compact',
25880 'DIV::align',
25881 'DL::compact',
25882 'FIELDSET::disabled',
25883 'FONT::color',
25884 'FONT::face',
25885 'FONT::size',
25886 'FORM::accept',
25887 'FORM::autocomplete',
25888 'FORM::enctype',
25889 'FORM::method',
25890 'FORM::name',
25891 'FORM::novalidate',
25892 'FORM::target',
25893 'FRAME::name',
25894 'H1::align',
25895 'H2::align',
25896 'H3::align',
25897 'H4::align',
25898 'H5::align',
25899 'H6::align',
25900 'HR::align',
25901 'HR::noshade',
25902 'HR::size',
25903 'HR::width',
25904 'HTML::version',
25905 'IFRAME::align',
25906 'IFRAME::frameborder',
25907 'IFRAME::height',
25908 'IFRAME::marginheight',
25909 'IFRAME::marginwidth',
25910 'IFRAME::width',
25911 'IMG::align',
25912 'IMG::alt',
25913 'IMG::border',
25914 'IMG::height',
25915 'IMG::hspace',
25916 'IMG::ismap',
25917 'IMG::name',
25918 'IMG::usemap',
25919 'IMG::vspace',
25920 'IMG::width',
25921 'INPUT::accept',
25922 'INPUT::accesskey',
25923 'INPUT::align',
25924 'INPUT::alt',
25925 'INPUT::autocomplete',
25926 'INPUT::checked',
25927 'INPUT::disabled',
25928 'INPUT::inputmode',
25929 'INPUT::ismap',
25930 'INPUT::list',
25931 'INPUT::max',
25932 'INPUT::maxlength',
25933 'INPUT::min',
25934 'INPUT::multiple',
25935 'INPUT::name',
25936 'INPUT::placeholder',
25937 'INPUT::readonly',
25938 'INPUT::required',
25939 'INPUT::size',
25940 'INPUT::step',
25941 'INPUT::tabindex',
25942 'INPUT::type',
25943 'INPUT::usemap',
25944 'INPUT::value',
25945 'INS::datetime',
25946 'KEYGEN::disabled',
25947 'KEYGEN::keytype',
25948 'KEYGEN::name',
25949 'LABEL::accesskey',
25950 'LABEL::for',
25951 'LEGEND::accesskey',
25952 'LEGEND::align',
25953 'LI::type',
25954 'LI::value',
25955 'LINK::sizes',
25956 'MAP::name',
25957 'MENU::compact',
25958 'MENU::label',
25959 'MENU::type',
25960 'METER::high',
25961 'METER::low',
25962 'METER::max',
25963 'METER::min',
25964 'METER::value',
25965 'OBJECT::typemustmatch',
25966 'OL::compact',
25967 'OL::reversed',
25968 'OL::start',
25969 'OL::type',
25970 'OPTGROUP::disabled',
25971 'OPTGROUP::label',
25972 'OPTION::disabled',
25973 'OPTION::label',
25974 'OPTION::selected',
25975 'OPTION::value',
25976 'OUTPUT::for',
25977 'OUTPUT::name',
25978 'P::align',
25979 'PRE::width',
25980 'PROGRESS::max',
25981 'PROGRESS::min',
25982 'PROGRESS::value',
25983 'SELECT::autocomplete',
25984 'SELECT::disabled',
25985 'SELECT::multiple',
25986 'SELECT::name',
25987 'SELECT::required',
25988 'SELECT::size',
25989 'SELECT::tabindex',
25990 'SOURCE::type',
25991 'TABLE::align',
25992 'TABLE::bgcolor',
25993 'TABLE::border',
25994 'TABLE::cellpadding',
25995 'TABLE::cellspacing',
25996 'TABLE::frame',
25997 'TABLE::rules',
25998 'TABLE::summary',
25999 'TABLE::width',
26000 'TBODY::align',
26001 'TBODY::char',
26002 'TBODY::charoff',
26003 'TBODY::valign',
26004 'TD::abbr',
26005 'TD::align',
26006 'TD::axis',
26007 'TD::bgcolor',
26008 'TD::char',
26009 'TD::charoff',
26010 'TD::colspan',
26011 'TD::headers',
26012 'TD::height',
26013 'TD::nowrap',
26014 'TD::rowspan',
26015 'TD::scope',
26016 'TD::valign',
26017 'TD::width',
26018 'TEXTAREA::accesskey',
26019 'TEXTAREA::autocomplete',
26020 'TEXTAREA::cols',
26021 'TEXTAREA::disabled',
26022 'TEXTAREA::inputmode',
26023 'TEXTAREA::name',
26024 'TEXTAREA::placeholder',
26025 'TEXTAREA::readonly',
26026 'TEXTAREA::required',
26027 'TEXTAREA::rows',
26028 'TEXTAREA::tabindex',
26029 'TEXTAREA::wrap',
26030 'TFOOT::align',
26031 'TFOOT::char',
26032 'TFOOT::charoff',
26033 'TFOOT::valign',
26034 'TH::abbr',
26035 'TH::align',
26036 'TH::axis',
26037 'TH::bgcolor',
26038 'TH::char',
26039 'TH::charoff',
26040 'TH::colspan',
26041 'TH::headers',
26042 'TH::height',
26043 'TH::nowrap',
26044 'TH::rowspan',
26045 'TH::scope',
26046 'TH::valign',
26047 'TH::width',
26048 'THEAD::align',
26049 'THEAD::char',
26050 'THEAD::charoff',
26051 'THEAD::valign',
26052 'TR::align',
26053 'TR::bgcolor',
26054 'TR::char',
26055 'TR::charoff',
26056 'TR::valign',
26057 'TRACK::default',
26058 'TRACK::kind',
26059 'TRACK::label',
26060 'TRACK::srclang',
26061 'UL::compact',
26062 'UL::type',
26063 'VIDEO::controls',
26064 'VIDEO::height',
26065 'VIDEO::loop',
26066 'VIDEO::mediagroup',
26067 'VIDEO::muted',
26068 'VIDEO::preload',
26069 'VIDEO::width',
26070 ];
26071
26072 static const _uriAttributes = const <String>[
26073 'A::href',
26074 'AREA::href',
26075 'BLOCKQUOTE::cite',
26076 'BODY::background',
26077 'COMMAND::icon',
26078 'DEL::cite',
26079 'FORM::action',
26080 'IMG::src',
26081 'INPUT::src',
26082 'INS::cite',
26083 'Q::cite',
26084 'VIDEO::poster',
26085 ];
26086
26087 final UriPolicy uriPolicy;
26088
26089 static final Map<String, Function> _attributeValidators = {};
26090
26091 /**
26092 * All known URI attributes will be validated against the UriPolicy, if
26093 * [uriPolicy] is null then a default UriPolicy will be used.
26094 */
26095 _Html5NodeValidator({UriPolicy uriPolicy})
26096 :uriPolicy = uriPolicy != null ? uriPolicy : new UriPolicy() {
26097
26098 if (_attributeValidators.isEmpty) {
26099 for (var attr in _standardAttributes) {
26100 _attributeValidators[attr] = _standardAttributeValidator;
26101 }
26102
26103 for (var attr in _uriAttributes) {
26104 _attributeValidators[attr] = _uriAttributeValidator;
26105 }
26106 }
26107 }
26108
26109 bool allowsElement(Element element) {
26110 return _allowedElements.contains(element.tagName);
26111 }
26112
26113 bool allowsAttribute(Element element, String attributeName, String value) {
26114 var tagName = element.tagName;
26115 var validator = _attributeValidators['$tagName::$attributeName'];
26116 if (validator == null) {
26117 validator = _attributeValidators['*::$attributeName'];
26118 }
26119 if (validator == null) {
26120 return false;
26121 }
26122 return validator(element, attributeName, value, this);
26123 }
26124
26125 static bool _standardAttributeValidator(Element element, String attributeName,
26126 String value, _Html5NodeValidator context) {
26127 return true;
26128 }
26129
26130 static bool _uriAttributeValidator(Element element, String attributeName,
26131 String value, _Html5NodeValidator context) {
26132 return context.uriPolicy.allowsUri(value);
26133 }
26134 }
25693 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 26135 // 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 26136 // 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. 26137 // BSD-style license that can be found in the LICENSE file.
25696 26138
25697 26139
25698 abstract class ImmutableListMixin<E> implements List<E> { 26140 abstract class ImmutableListMixin<E> implements List<E> {
25699 // From Iterable<$E>: 26141 // From Iterable<$E>:
25700 Iterator<E> get iterator { 26142 Iterator<E> get iterator {
25701 // Note: NodeLists are not fixed size. And most probably length shouldn't 26143 // 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 26144 // 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 26206
25765 void fillRange(int start, int end, [E fillValue]) { 26207 void fillRange(int start, int end, [E fillValue]) {
25766 throw new UnsupportedError("Cannot modify an immutable List."); 26208 throw new UnsupportedError("Cannot modify an immutable List.");
25767 } 26209 }
25768 } 26210 }
25769 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 26211 // 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 26212 // 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. 26213 // BSD-style license that can be found in the LICENSE file.
25772 26214
25773 26215
25774 /** 26216 _serialize(var message) {
25775 * Internal class that does the actual calculations to determine keyCode and 26217 return new _JsSerializer().traverse(message);
25776 * charCode for keydown, keypress, and keyup events for all browsers. 26218 }
25777 */ 26219
25778 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> { 26220 class _JsSerializer extends _Serializer {
25779 // This code inspired by Closure's KeyHandling library. 26221
25780 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl er.js.source.html 26222 visitSendPortSync(SendPortSync x) {
25781 26223 if (x is _JsSendPortSync) return visitJsSendPortSync(x);
25782 /** 26224 if (x is _LocalSendPortSync) return visitLocalSendPortSync(x);
25783 * The set of keys that have been pressed down without seeing their 26225 if (x is _RemoteSendPortSync) return visitRemoteSendPortSync(x);
25784 * corresponding keyup event. 26226 throw "Unknown port type $x";
25785 */ 26227 }
25786 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[]; 26228
25787 26229 visitJsSendPortSync(_JsSendPortSync x) {
25788 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */ 26230 return [ 'sendport', 'nativejs', x._id ];
25789 final String _type; 26231 }
25790 26232
25791 /** The element we are watching for events to happen on. */ 26233 visitLocalSendPortSync(_LocalSendPortSync x) {
25792 final EventTarget _target; 26234 return [ 'sendport', 'dart',
25793 26235 ReceivePortSync._isolateId, x._receivePort._portId ];
25794 // The distance to shift from upper case alphabet Roman letters to lower case. 26236 }
25795 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0]; 26237
25796 26238 visitSendPort(SendPort x) {
25797 /** Controller to produce KeyEvents for the stream. */ 26239 throw new UnimplementedError('Asynchronous send port not yet implemented.');
25798 final StreamController _controller = new StreamController(sync: true); 26240 }
25799 26241
25800 static const _EVENT_TYPE = 'KeyEvent'; 26242 visitRemoteSendPortSync(_RemoteSendPortSync x) {
25801 26243 return [ 'sendport', 'dart', x._isolateId, x._portId ];
25802 /** 26244 }
25803 * An enumeration of key identifiers currently part of the W3C draft for DOM3 26245 }
25804 * and their mappings to keyCodes. 26246
25805 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set 26247 _deserialize(var message) {
25806 */ 26248 return new _JsDeserializer().deserialize(message);
25807 static const Map<String, int> _keyIdentifier = const { 26249 }
25808 'Up': KeyCode.UP, 26250
25809 'Down': KeyCode.DOWN, 26251
25810 'Left': KeyCode.LEFT, 26252 class _JsDeserializer extends _Deserializer {
25811 'Right': KeyCode.RIGHT, 26253
25812 'Enter': KeyCode.ENTER, 26254 static const _UNSPECIFIED = const Object();
25813 'F1': KeyCode.F1, 26255
25814 'F2': KeyCode.F2, 26256 deserializeSendPort(List x) {
25815 'F3': KeyCode.F3, 26257 String tag = x[1];
25816 'F4': KeyCode.F4, 26258 switch (tag) {
25817 'F5': KeyCode.F5, 26259 case 'nativejs':
25818 'F6': KeyCode.F6, 26260 num id = x[2];
25819 'F7': KeyCode.F7, 26261 return new _JsSendPortSync(id);
25820 'F8': KeyCode.F8, 26262 case 'dart':
25821 'F9': KeyCode.F9, 26263 num isolateId = x[2];
25822 'F10': KeyCode.F10, 26264 num portId = x[3];
25823 'F11': KeyCode.F11, 26265 return ReceivePortSync._lookup(isolateId, portId);
25824 'F12': KeyCode.F12, 26266 default:
25825 'U+007F': KeyCode.DELETE, 26267 throw 'Illegal SendPortSync type: $tag';
25826 'Home': KeyCode.HOME, 26268 }
25827 'End': KeyCode.END, 26269 }
25828 'PageUp': KeyCode.PAGE_UP, 26270 }
25829 'PageDown': KeyCode.PAGE_DOWN, 26271
25830 'Insert': KeyCode.INSERT 26272 // The receiver is JS.
25831 }; 26273 class _JsSendPortSync implements SendPortSync {
25832 26274
25833 /** Return a stream for KeyEvents for the specified target. */ 26275 final num _id;
25834 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) { 26276 _JsSendPortSync(this._id);
25835 return new _KeyboardEventHandler.initializeAllEventListeners( 26277
25836 _type, e).stream; 26278 callSync(var message) {
25837 } 26279 var serialized = _serialize(message);
25838 26280 var result = _callPortSync(_id, serialized);
25839 /** 26281 return _deserialize(result);
25840 * Accessor to the stream associated with a particular KeyboardEvent 26282 }
25841 * EventTarget. 26283
25842 * 26284 bool operator==(var other) {
25843 * [forTarget] must be called to initialize this stream to listen to a 26285 return (other is _JsSendPortSync) && (_id == other._id);
25844 * particular EventTarget. 26286 }
25845 */ 26287
25846 Stream<KeyEvent> get stream { 26288 int get hashCode => _id;
25847 if(_target != null) { 26289 }
25848 return _controller.stream; 26290
26291 // TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
26292 // The receiver is a different Dart isolate, compiled to JS.
26293 class _RemoteSendPortSync implements SendPortSync {
26294
26295 int _isolateId;
26296 int _portId;
26297 _RemoteSendPortSync(this._isolateId, this._portId);
26298
26299 callSync(var message) {
26300 var serialized = _serialize(message);
26301 var result = _call(_isolateId, _portId, serialized);
26302 return _deserialize(result);
26303 }
26304
26305 static _call(int isolateId, int portId, var message) {
26306 var target = 'dart-port-$isolateId-$portId';
26307 // TODO(vsm): Make this re-entrant.
26308 // TODO(vsm): Set this up set once, on the first call.
26309 var source = '$target-result';
26310 var result = null;
26311 window.on[source].first.then((Event e) {
26312 result = json.parse(_getPortSyncEventData(e));
26313 });
26314 _dispatchEvent(target, [source, message]);
26315 return result;
26316 }
26317
26318 bool operator==(var other) {
26319 return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
26320 && (_portId == other._portId);
26321 }
26322
26323 int get hashCode => _isolateId >> 16 + _portId;
26324 }
26325
26326 // The receiver is in the same Dart isolate, compiled to JS.
26327 class _LocalSendPortSync implements SendPortSync {
26328
26329 ReceivePortSync _receivePort;
26330
26331 _LocalSendPortSync._internal(this._receivePort);
26332
26333 callSync(var message) {
26334 // TODO(vsm): Do a more efficient deep copy.
26335 var copy = _deserialize(_serialize(message));
26336 var result = _receivePort._callback(copy);
26337 return _deserialize(_serialize(result));
26338 }
26339
26340 bool operator==(var other) {
26341 return (other is _LocalSendPortSync)
26342 && (_receivePort == other._receivePort);
26343 }
26344
26345 int get hashCode => _receivePort.hashCode;
26346 }
26347
26348 // TODO(vsm): Move this to dart:isolate. This will take some
26349 // refactoring as there are dependences here on the DOM. Users
26350 // interact with this class (or interface if we change it) directly -
26351 // new ReceivePortSync. I think most of the DOM logic could be
26352 // delayed until the corresponding SendPort is registered on the
26353 // window.
26354
26355 // A Dart ReceivePortSync (tagged 'dart' when serialized) is
26356 // identifiable / resolvable by the combination of its isolateid and
26357 // portid. When a corresponding SendPort is used within the same
26358 // isolate, the _portMap below can be used to obtain the
26359 // ReceivePortSync directly. Across isolates (or from JS), an
26360 // EventListener can be used to communicate with the port indirectly.
26361 class ReceivePortSync {
26362
26363 static Map<int, ReceivePortSync> _portMap;
26364 static int _portIdCount;
26365 static int _cachedIsolateId;
26366
26367 num _portId;
26368 Function _callback;
26369 StreamSubscription _portSubscription;
26370
26371 ReceivePortSync() {
26372 if (_portIdCount == null) {
26373 _portIdCount = 0;
26374 _portMap = new Map<int, ReceivePortSync>();
26375 }
26376 _portId = _portIdCount++;
26377 _portMap[_portId] = this;
26378 }
26379
26380 static int get _isolateId {
26381 // TODO(vsm): Make this coherent with existing isolate code.
26382 if (_cachedIsolateId == null) {
26383 _cachedIsolateId = _getNewIsolateId();
26384 }
26385 return _cachedIsolateId;
26386 }
26387
26388 static String _getListenerName(isolateId, portId) =>
26389 'dart-port-$isolateId-$portId';
26390 String get _listenerName => _getListenerName(_isolateId, _portId);
26391
26392 void receive(callback(var message)) {
26393 _callback = callback;
26394 if (_portSubscription == null) {
26395 _portSubscription = window.on[_listenerName].listen((Event e) {
26396 var data = json.parse(_getPortSyncEventData(e));
26397 var replyTo = data[0];
26398 var message = _deserialize(data[1]);
26399 var result = _callback(message);
26400 _dispatchEvent(replyTo, _serialize(result));
26401 });
26402 }
26403 }
26404
26405 void close() {
26406 _portMap.remove(_portId);
26407 if (_portSubscription != null) _portSubscription.cancel();
26408 }
26409
26410 SendPortSync toSendPort() {
26411 return new _LocalSendPortSync._internal(this);
26412 }
26413
26414 static SendPortSync _lookup(int isolateId, int portId) {
26415 if (isolateId == _isolateId) {
26416 return _portMap[portId].toSendPort();
25849 } else { 26417 } else {
25850 throw new StateError("Not initialized. Call forTarget to access a stream " 26418 return new _RemoteSendPortSync(isolateId, portId);
25851 "initialized with a particular EventTarget."); 26419 }
25852 } 26420 }
25853 } 26421 }
25854 26422
25855 /** 26423 get _isolateId => ReceivePortSync._isolateId;
25856 * General constructor, performs basic initialization for our improved 26424
25857 * KeyboardEvent controller. 26425 void _dispatchEvent(String receiver, var message) {
25858 */ 26426 var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
25859 _KeyboardEventHandler(this._type) : 26427 detail: json.stringify(message));
25860 _target = null, super(_EVENT_TYPE) { 26428 window.dispatchEvent(event);
25861 } 26429 }
25862 26430
25863 /** 26431 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 26432 // 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 26433 // 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. 26434 // BSD-style license that can be found in the LICENSE file.
26175 26435
26176 26436
26177 /** 26437 /**
26178 * Defines the keycode values for keys that are returned by 26438 * Defines the keycode values for keys that are returned by
26179 * KeyboardEvent.keyCode. 26439 * KeyboardEvent.keyCode.
26180 * 26440 *
26181 * Important note: There is substantial divergence in how different browsers 26441 * 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 27189 * Sound) key
26930 */ 27190 */
26931 static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound"; 27191 static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound";
26932 27192
26933 /** 27193 /**
26934 * Key value used when an implementation is unable to identify another key 27194 * Key value used when an implementation is unable to identify another key
26935 * value, due to either hardware, platform, or software constraints 27195 * value, due to either hardware, platform, or software constraints
26936 */ 27196 */
26937 static const String UNIDENTIFIED = "Unidentified"; 27197 static const String UNIDENTIFIED = "Unidentified";
26938 } 27198 }
27199 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
27200 // for details. All rights reserved. Use of this source code is governed by a
27201 // BSD-style license that can be found in the LICENSE file.
27202
27203
27204 /**
27205 * Internal class that does the actual calculations to determine keyCode and
27206 * charCode for keydown, keypress, and keyup events for all browsers.
27207 */
27208 class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> {
27209 // This code inspired by Closure's KeyHandling library.
27210 // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandl er.js.source.html
27211
27212 /**
27213 * The set of keys that have been pressed down without seeing their
27214 * corresponding keyup event.
27215 */
27216 final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
27217
27218 /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
27219 final String _type;
27220
27221 /** The element we are watching for events to happen on. */
27222 final EventTarget _target;
27223
27224 // The distance to shift from upper case alphabet Roman letters to lower case.
27225 static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0];
27226
27227 /** Controller to produce KeyEvents for the stream. */
27228 final StreamController _controller = new StreamController(sync: true);
27229
27230 static const _EVENT_TYPE = 'KeyEvent';
27231
27232 /**
27233 * An enumeration of key identifiers currently part of the W3C draft for DOM3
27234 * and their mappings to keyCodes.
27235 * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
27236 */
27237 static const Map<String, int> _keyIdentifier = const {
27238 'Up': KeyCode.UP,
27239 'Down': KeyCode.DOWN,
27240 'Left': KeyCode.LEFT,
27241 'Right': KeyCode.RIGHT,
27242 'Enter': KeyCode.ENTER,
27243 'F1': KeyCode.F1,
27244 'F2': KeyCode.F2,
27245 'F3': KeyCode.F3,
27246 'F4': KeyCode.F4,
27247 'F5': KeyCode.F5,
27248 'F6': KeyCode.F6,
27249 'F7': KeyCode.F7,
27250 'F8': KeyCode.F8,
27251 'F9': KeyCode.F9,
27252 'F10': KeyCode.F10,
27253 'F11': KeyCode.F11,
27254 'F12': KeyCode.F12,
27255 'U+007F': KeyCode.DELETE,
27256 'Home': KeyCode.HOME,
27257 'End': KeyCode.END,
27258 'PageUp': KeyCode.PAGE_UP,
27259 'PageDown': KeyCode.PAGE_DOWN,
27260 'Insert': KeyCode.INSERT
27261 };
27262
27263 /** Return a stream for KeyEvents for the specified target. */
27264 Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
27265 return new _KeyboardEventHandler.initializeAllEventListeners(
27266 _type, e).stream;
27267 }
27268
27269 /**
27270 * Accessor to the stream associated with a particular KeyboardEvent
27271 * EventTarget.
27272 *
27273 * [forTarget] must be called to initialize this stream to listen to a
27274 * particular EventTarget.
27275 */
27276 Stream<KeyEvent> get stream {
27277 if(_target != null) {
27278 return _controller.stream;
27279 } else {
27280 throw new StateError("Not initialized. Call forTarget to access a stream "
27281 "initialized with a particular EventTarget.");
27282 }
27283 }
27284
27285 /**
27286 * General constructor, performs basic initialization for our improved
27287 * KeyboardEvent controller.
27288 */
27289 _KeyboardEventHandler(this._type) :
27290 _target = null, super(_EVENT_TYPE) {
27291 }
27292
27293 /**
27294 * Hook up all event listeners under the covers so we can estimate keycodes
27295 * and charcodes when they are not provided.
27296 */
27297 _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) :
27298 super(_EVENT_TYPE) {
27299 Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
27300 processKeyDown);
27301 Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
27302 processKeyPress);
27303 Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
27304 processKeyUp);
27305 }
27306
27307 /**
27308 * Notify all callback listeners that a KeyEvent of the relevant type has
27309 * occurred.
27310 */
27311 bool _dispatch(KeyEvent event) {
27312 if (event.type == _type)
27313 _controller.add(event);
27314 }
27315
27316 /** Determine if caps lock is one of the currently depressed keys. */
27317 bool get _capsLockOn =>
27318 _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
27319
27320 /**
27321 * Given the previously recorded keydown key codes, see if we can determine
27322 * the keycode of this keypress [event]. (Generally browsers only provide
27323 * charCode information for keypress events, but with a little
27324 * reverse-engineering, we can also determine the keyCode.) Returns
27325 * KeyCode.UNKNOWN if the keycode could not be determined.
27326 */
27327 int _determineKeyCodeForKeypress(KeyboardEvent event) {
27328 // Note: This function is a work in progress. We'll expand this function
27329 // once we get more information about other keyboards.
27330 for (var prevEvent in _keyDownList) {
27331 if (prevEvent._shadowCharCode == event.charCode) {
27332 return prevEvent.keyCode;
27333 }
27334 if ((event.shiftKey || _capsLockOn) && event.charCode >= "A".codeUnits[0]
27335 && event.charCode <= "Z".codeUnits[0] && event.charCode +
27336 _ROMAN_ALPHABET_OFFSET == prevEvent._shadowCharCode) {
27337 return prevEvent.keyCode;
27338 }
27339 }
27340 return KeyCode.UNKNOWN;
27341 }
27342
27343 /**
27344 * Given the charater code returned from a keyDown [event], try to ascertain
27345 * and return the corresponding charCode for the character that was pressed.
27346 * This information is not shown to the user, but used to help polyfill
27347 * keypress events.
27348 */
27349 int _findCharCodeKeyDown(KeyboardEvent event) {
27350 if (event.keyLocation == 3) { // Numpad keys.
27351 switch (event.keyCode) {
27352 case KeyCode.NUM_ZERO:
27353 // Even though this function returns _charCodes_, for some cases the
27354 // KeyCode == the charCode we want, in which case we use the keycode
27355 // constant for readability.
27356 return KeyCode.ZERO;
27357 case KeyCode.NUM_ONE:
27358 return KeyCode.ONE;
27359 case KeyCode.NUM_TWO:
27360 return KeyCode.TWO;
27361 case KeyCode.NUM_THREE:
27362 return KeyCode.THREE;
27363 case KeyCode.NUM_FOUR:
27364 return KeyCode.FOUR;
27365 case KeyCode.NUM_FIVE:
27366 return KeyCode.FIVE;
27367 case KeyCode.NUM_SIX:
27368 return KeyCode.SIX;
27369 case KeyCode.NUM_SEVEN:
27370 return KeyCode.SEVEN;
27371 case KeyCode.NUM_EIGHT:
27372 return KeyCode.EIGHT;
27373 case KeyCode.NUM_NINE:
27374 return KeyCode.NINE;
27375 case KeyCode.NUM_MULTIPLY:
27376 return 42; // Char code for *
27377 case KeyCode.NUM_PLUS:
27378 return 43; // +
27379 case KeyCode.NUM_MINUS:
27380 return 45; // -
27381 case KeyCode.NUM_PERIOD:
27382 return 46; // .
27383 case KeyCode.NUM_DIVISION:
27384 return 47; // /
27385 }
27386 } else if (event.keyCode >= 65 && event.keyCode <= 90) {
27387 // Set the "char code" for key down as the lower case letter. Again, this
27388 // will not show up for the user, but will be helpful in estimating
27389 // keyCode locations and other information during the keyPress event.
27390 return event.keyCode + _ROMAN_ALPHABET_OFFSET;
27391 }
27392 switch(event.keyCode) {
27393 case KeyCode.SEMICOLON:
27394 return KeyCode.FF_SEMICOLON;
27395 case KeyCode.EQUALS:
27396 return KeyCode.FF_EQUALS;
27397 case KeyCode.COMMA:
27398 return 44; // Ascii value for ,
27399 case KeyCode.DASH:
27400 return 45; // -
27401 case KeyCode.PERIOD:
27402 return 46; // .
27403 case KeyCode.SLASH:
27404 return 47; // /
27405 case KeyCode.APOSTROPHE:
27406 return 96; // `
27407 case KeyCode.OPEN_SQUARE_BRACKET:
27408 return 91; // [
27409 case KeyCode.BACKSLASH:
27410 return 92; // \
27411 case KeyCode.CLOSE_SQUARE_BRACKET:
27412 return 93; // ]
27413 case KeyCode.SINGLE_QUOTE:
27414 return 39; // '
27415 }
27416 return event.keyCode;
27417 }
27418
27419 /**
27420 * Returns true if the key fires a keypress event in the current browser.
27421 */
27422 bool _firesKeyPressEvent(KeyEvent event) {
27423 if (!Device.isIE && !Device.isWebKit) {
27424 return true;
27425 }
27426
27427 if (Device.userAgent.contains('Mac') && event.altKey) {
27428 return KeyCode.isCharacterKey(event.keyCode);
27429 }
27430
27431 // Alt but not AltGr which is represented as Alt+Ctrl.
27432 if (event.altKey && !event.ctrlKey) {
27433 return false;
27434 }
27435
27436 // Saves Ctrl or Alt + key for IE and WebKit, which won't fire keypress.
27437 if (!event.shiftKey &&
27438 (_keyDownList.last.keyCode == KeyCode.CTRL ||
27439 _keyDownList.last.keyCode == KeyCode.ALT ||
27440 Device.userAgent.contains('Mac') &&
27441 _keyDownList.last.keyCode == KeyCode.META)) {
27442 return false;
27443 }
27444
27445 // Some keys with Ctrl/Shift do not issue keypress in WebKit.
27446 if (Device.isWebKit && event.ctrlKey && event.shiftKey && (
27447 event.keyCode == KeyCode.BACKSLASH ||
27448 event.keyCode == KeyCode.OPEN_SQUARE_BRACKET ||
27449 event.keyCode == KeyCode.CLOSE_SQUARE_BRACKET ||
27450 event.keyCode == KeyCode.TILDE ||
27451 event.keyCode == KeyCode.SEMICOLON || event.keyCode == KeyCode.DASH ||
27452 event.keyCode == KeyCode.EQUALS || event.keyCode == KeyCode.COMMA ||
27453 event.keyCode == KeyCode.PERIOD || event.keyCode == KeyCode.SLASH ||
27454 event.keyCode == KeyCode.APOSTROPHE ||
27455 event.keyCode == KeyCode.SINGLE_QUOTE)) {
27456 return false;
27457 }
27458
27459 switch (event.keyCode) {
27460 case KeyCode.ENTER:
27461 // IE9 does not fire keypress on ENTER.
27462 return !Device.isIE;
27463 case KeyCode.ESC:
27464 return !Device.isWebKit;
27465 }
27466
27467 return KeyCode.isCharacterKey(event.keyCode);
27468 }
27469
27470 /**
27471 * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
27472 * Opera all use).
27473 */
27474 int _normalizeKeyCodes(KeyboardEvent event) {
27475 // Note: This may change once we get input about non-US keyboards.
27476 if (Device.isFirefox) {
27477 switch(event.keyCode) {
27478 case KeyCode.FF_EQUALS:
27479 return KeyCode.EQUALS;
27480 case KeyCode.FF_SEMICOLON:
27481 return KeyCode.SEMICOLON;
27482 case KeyCode.MAC_FF_META:
27483 return KeyCode.META;
27484 case KeyCode.WIN_KEY_FF_LINUX:
27485 return KeyCode.WIN_KEY;
27486 }
27487 }
27488 return event.keyCode;
27489 }
27490
27491 /** Handle keydown events. */
27492 void processKeyDown(KeyboardEvent e) {
27493 // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
27494 // before we've caught a key-up event. If the last-key was one of these
27495 // we reset the state.
27496 if (_keyDownList.length > 0 &&
27497 (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey ||
27498 _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey ||
27499 Device.userAgent.contains('Mac') &&
27500 _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) {
27501 _keyDownList.clear();
27502 }
27503
27504 var event = new KeyEvent(e);
27505 event._shadowKeyCode = _normalizeKeyCodes(event);
27506 // Technically a "keydown" event doesn't have a charCode. This is
27507 // calculated nonetheless to provide us with more information in giving
27508 // as much information as possible on keypress about keycode and also
27509 // charCode.
27510 event._shadowCharCode = _findCharCodeKeyDown(event);
27511 if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode &&
27512 !_firesKeyPressEvent(event)) {
27513 // Some browsers have quirks not firing keypress events where all other
27514 // browsers do. This makes them more consistent.
27515 processKeyPress(event);
27516 }
27517 _keyDownList.add(event);
27518 _dispatch(event);
27519 }
27520
27521 /** Handle keypress events. */
27522 void processKeyPress(KeyboardEvent event) {
27523 var e = new KeyEvent(event);
27524 // IE reports the character code in the keyCode field for keypress events.
27525 // There are two exceptions however, Enter and Escape.
27526 if (Device.isIE) {
27527 if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) {
27528 e._shadowCharCode = 0;
27529 } else {
27530 e._shadowCharCode = e.keyCode;
27531 }
27532 } else if (Device.isOpera) {
27533 // Opera reports the character code in the keyCode field.
27534 e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0;
27535 }
27536 // Now we guestimate about what the keycode is that was actually
27537 // pressed, given previous keydown information.
27538 e._shadowKeyCode = _determineKeyCodeForKeypress(e);
27539
27540 // Correct the key value for certain browser-specific quirks.
27541 if (e._shadowKeyIdentifier != null &&
27542 _keyIdentifier.containsKey(e._shadowKeyIdentifier)) {
27543 // This is needed for Safari Windows because it currently doesn't give a
27544 // keyCode/which for non printable keys.
27545 e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier];
27546 }
27547 e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
27548 _dispatch(e);
27549 }
27550
27551 /** Handle keyup events. */
27552 void processKeyUp(KeyboardEvent event) {
27553 var e = new KeyEvent(event);
27554 KeyboardEvent toRemove = null;
27555 for (var key in _keyDownList) {
27556 if (key.keyCode == e.keyCode) {
27557 toRemove = key;
27558 }
27559 }
27560 if (toRemove != null) {
27561 _keyDownList.removeWhere((element) => element == toRemove);
27562 } else if (_keyDownList.length > 0) {
27563 // This happens when we've reached some international keyboard case we
27564 // haven't accounted for or we haven't correctly eliminated all browser
27565 // inconsistencies. Filing bugs on when this is reached is welcome!
27566 _keyDownList.removeLast();
27567 }
27568 _dispatch(e);
27569 }
27570 }
27571
27572
27573 /**
27574 * Records KeyboardEvents that occur on a particular element, and provides a
27575 * stream of outgoing KeyEvents with cross-browser consistent keyCode and
27576 * charCode values despite the fact that a multitude of browsers that have
27577 * varying keyboard default behavior.
27578 *
27579 * Example usage:
27580 *
27581 * KeyboardEventStream.onKeyDown(document.body).listen(
27582 * keydownHandlerTest);
27583 *
27584 * This class is very much a work in progress, and we'd love to get information
27585 * on how we can make this class work with as many international keyboards as
27586 * possible. Bugs welcome!
27587 */
27588 class KeyboardEventStream {
27589
27590 /** Named constructor to produce a stream for onKeyPress events. */
27591 static Stream<KeyEvent> onKeyPress(EventTarget target) =>
27592 new _KeyboardEventHandler('keypress').forTarget(target);
27593
27594 /** Named constructor to produce a stream for onKeyUp events. */
27595 static Stream<KeyEvent> onKeyUp(EventTarget target) =>
27596 new _KeyboardEventHandler('keyup').forTarget(target);
27597
27598 /** Named constructor to produce a stream for onKeyDown events. */
27599 static Stream<KeyEvent> onKeyDown(EventTarget target) =>
27600 new _KeyboardEventHandler('keydown').forTarget(target);
27601 }
27602 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
27603 // for details. All rights reserved. Use of this source code is governed by a
27604 // BSD-style license that can be found in the LICENSE file.
27605
27606
27607 typedef void _MicrotaskCallback();
27608
27609 /**
27610 * This class attempts to invoke a callback as soon as the current event stack
27611 * unwinds, but before the browser repaints.
27612 */
27613 abstract class _MicrotaskScheduler {
27614 bool _nextMicrotaskFrameScheduled = false;
27615 final _MicrotaskCallback _callback;
27616
27617 _MicrotaskScheduler(this._callback);
27618
27619 /**
27620 * Creates the best possible microtask scheduler for the current platform.
27621 */
27622 factory _MicrotaskScheduler.best(_MicrotaskCallback callback) {
27623 if (Window._supportsSetImmediate) {
27624 return new _SetImmediateScheduler(callback);
27625 } else if (MutationObserver.supported) {
27626 return new _MutationObserverScheduler(callback);
27627 }
27628 return new _PostMessageScheduler(callback);
27629 }
27630
27631 /**
27632 * Schedules a microtask callback if one has not been scheduled already.
27633 */
27634 void maybeSchedule() {
27635 if (this._nextMicrotaskFrameScheduled) {
27636 return;
27637 }
27638 this._nextMicrotaskFrameScheduled = true;
27639 this._schedule();
27640 }
27641
27642 /**
27643 * Does the actual scheduling of the callback.
27644 */
27645 void _schedule();
27646
27647 /**
27648 * Handles the microtask callback and forwards it if necessary.
27649 */
27650 void _onCallback() {
27651 // Ignore spurious messages.
27652 if (!_nextMicrotaskFrameScheduled) {
27653 return;
27654 }
27655 _nextMicrotaskFrameScheduled = false;
27656 this._callback();
27657 }
27658 }
27659
27660 /**
27661 * Scheduler which uses window.postMessage to schedule events.
27662 */
27663 class _PostMessageScheduler extends _MicrotaskScheduler {
27664 const _MICROTASK_MESSAGE = "DART-MICROTASK";
27665
27666 _PostMessageScheduler(_MicrotaskCallback callback): super(callback) {
27667 // Messages from other windows do not cause a security risk as
27668 // all we care about is that _handleMessage is called
27669 // after the current event loop is unwound and calling the function is
27670 // a noop when zero requests are pending.
27671 window.onMessage.listen(this._handleMessage);
27672 }
27673
27674 void _schedule() {
27675 window.postMessage(_MICROTASK_MESSAGE, "*");
27676 }
27677
27678 void _handleMessage(e) {
27679 this._onCallback();
27680 }
27681 }
27682
27683 /**
27684 * Scheduler which uses a MutationObserver to schedule events.
27685 */
27686 class _MutationObserverScheduler extends _MicrotaskScheduler {
27687 MutationObserver _observer;
27688 Element _dummy;
27689
27690 _MutationObserverScheduler(_MicrotaskCallback callback): super(callback) {
27691 // Mutation events get fired as soon as the current event stack is unwound
27692 // so we just make a dummy event and listen for that.
27693 _observer = new MutationObserver(this._handleMutation);
27694 _dummy = new DivElement();
27695 _observer.observe(_dummy, attributes: true);
27696 }
27697
27698 void _schedule() {
27699 // Toggle it to trigger the mutation event.
27700 _dummy.hidden = !_dummy.hidden;
27701 }
27702
27703 _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
27704 this._onCallback();
27705 }
27706 }
27707
27708 /**
27709 * Scheduler which uses window.setImmediate to schedule events.
27710 */
27711 class _SetImmediateScheduler extends _MicrotaskScheduler {
27712 _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
27713
27714 void _schedule() {
27715 window._setImmediate(_handleImmediate);
27716 }
27717
27718 void _handleImmediate() {
27719 this._onCallback();
27720 }
27721 }
27722
27723 List<TimeoutHandler> _pendingMicrotasks;
27724 _MicrotaskScheduler _microtaskScheduler = null;
27725
27726 void _maybeScheduleMicrotaskFrame() {
27727 if (_microtaskScheduler == null) {
27728 _microtaskScheduler =
27729 new _MicrotaskScheduler.best(_completeMicrotasks);
27730 }
27731 _microtaskScheduler.maybeSchedule();
27732 }
27733
27734 /**
27735 * Registers a [callback] which is called after the current execution stack
27736 * unwinds.
27737 */
27738 void _addMicrotaskCallback(TimeoutHandler callback) {
27739 if (_pendingMicrotasks == null) {
27740 _pendingMicrotasks = <TimeoutHandler>[];
27741 _maybeScheduleMicrotaskFrame();
27742 }
27743 _pendingMicrotasks.add(callback);
27744 }
27745
27746
27747 /**
27748 * Complete all pending microtasks.
27749 */
27750 void _completeMicrotasks() {
27751 var callbacks = _pendingMicrotasks;
27752 _pendingMicrotasks = null;
27753 for (var callback in callbacks) {
27754 callback();
27755 }
27756 }
27757 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
27758 // for details. All rights reserved. Use of this source code is governed by a
27759 // BSD-style license that can be found in the LICENSE file.
27760
27761
27762
27763 /**
27764 * Class which helps construct standard node validation policies.
27765 *
27766 * By default this will not accept anything, but the 'allow*' functions can be
27767 * used to expand what types of elements or attributes are allowed.
27768 *
27769 * All allow functions are additive- elements will be accepted if they are
27770 * accepted by any specific rule.
27771 */
27772 class NodeValidatorBuilder implements NodeValidator {
27773
27774 final List<NodeValidator> _validators = <NodeValidator>[];
27775
27776 NodeValidatorBuilder() {
27777 }
27778
27779 /**
27780 * Creates a new NodeValidatorBuilder which accepts common constructs.
27781 *
27782 * By default this will accept HTML5 elements and attributes with the default
27783 * [UriPolicy] and templating elements.
27784 */
27785 factory NodeValidatorBuilder.common() {
27786 return new NodeValidatorBuilder()
27787 ..allowHtml5()
27788 ..allowTemplating();
27789 }
27790
27791 /**
27792 * Allows navigation elements- Form and Anchor tags, along with common
27793 * attributes.
27794 *
27795 * The UriPolicy can be used to restrict the locations the navigation elements
27796 * are allowed to direct to. By default this will use the default [UriPolicy].
27797 */
27798 void allowNavigation([UriPolicy uriPolicy]) {
27799 if (uriPolicy == null) {
27800 uriPolicy = new UriPolicy();
27801 }
27802 add(new _SimpleNodeValidator.allowNavigation(uriPolicy));
27803 }
27804
27805 /**
27806 * Allows image elements.
27807 *
27808 * The UriPolicy can be used to restrict the locations the images may be
27809 * loaded from. By default this will use the default [UriPolicy].
27810 */
27811 void allowImages([UriPolicy uriPolicy]) {
27812 if (uriPolicy == null) {
27813 uriPolicy = new UriPolicy();
27814 }
27815 add(new _SimpleNodeValidator.allowImages(uriPolicy));
27816 }
27817
27818 /**
27819 * Allow basic text elements.
27820 *
27821 * This allows a subset of HTML5 elements, specifically just these tags and
27822 * no attributes.
27823 *
27824 * * B
27825 * * BLOCKQUOTE
27826 * * BR
27827 * * EM
27828 * * H1
27829 * * H2
27830 * * H3
27831 * * H4
27832 * * H5
27833 * * H6
27834 * * HR
27835 * * I
27836 * * LI
27837 * * OL
27838 * * P
27839 * * SPAN
27840 * * UL
27841 */
27842 void allowTextElements() {
27843 add(new _SimpleNodeValidator.allowTextElements());
27844 }
27845
27846 /**
27847 * Allow common safe HTML5 elements and attributes.
27848 *
27849 * This list is based off of the Caja whitelists at:
27850 * https://code.google.com/p/google-caja/wiki/CajaWhitelists.
27851 *
27852 * Common things which are not allowed are script elements, style attributes
27853 * and any script handlers.
27854 */
27855 void allowHtml5({UriPolicy uriPolicy}) {
27856 add(new _Html5NodeValidator(uriPolicy: uriPolicy));
27857 }
27858
27859 /**
27860 * Allow SVG elements and attributes except for known bad ones.
27861 */
27862 void allowSvg() {
27863 add(new _SvgNodeValidator());
27864 }
27865
27866 /**
27867 * Allow custom elements with the specified tag name and specified attributes.
27868 *
27869 * This will allow the elements as custom tags (such as <x-foo></x-foo>),
27870 * but will not allow tag extensions. Use [allowTagExtension] to allow
27871 * tag extensions.
27872 */
27873 void allowCustomElement(String tagName,
27874 {UriPolicy uriPolicy,
27875 Iterable<String> attributes,
27876 Iterable<String> uriAttributes}) {
27877
27878 var tagNameUpper = tagName.toUpperCase();
27879 var attrs;
27880 if (attributes != null) {
27881 attrs =
27882 attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
27883 }
27884 var uriAttrs;
27885 if (uriAttributes != null) {
27886 uriAttrs =
27887 uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
27888 }
27889 if (uriPolicy == null) {
27890 uriPolicy = new UriPolicy();
27891 }
27892
27893 add(new _CustomElementNodeValidator(
27894 uriPolicy,
27895 [tagNameUpper],
27896 attrs,
27897 uriAttrs,
27898 false,
27899 true));
27900 }
27901
27902 /**
27903 * Allow custom tag extensions with the specified type name and specified
27904 * attributes.
27905 *
27906 * This will allow tag extensions (such as <div is="x-foo"></div>),
27907 * but will not allow custom tags. Use [allowCustomElement] to allow
27908 * custom tags.
27909 */
27910 void allowTagExtension(String tagName, String baseName,
27911 {UriPolicy uriPolicy,
27912 Iterable<String> attributes,
27913 Iterable<String> uriAttributes}) {
27914
27915 var baseNameUpper = baseName.toUpperCase();
27916 var tagNameUpper = tagName.toUpperCase();
27917 var attrs;
27918 if (attributes != null) {
27919 attrs =
27920 attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
27921 }
27922 var uriAttrs;
27923 if (uriAttributes != null) {
27924 uriAttrs =
27925 uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
27926 }
27927 if (uriPolicy == null) {
27928 uriPolicy = new UriPolicy();
27929 }
27930
27931 add(new _CustomElementNodeValidator(
27932 uriPolicy,
27933 [tagNameUpper, baseNameUpper],
27934 attrs,
27935 uriAttrs,
27936 true,
27937 false));
27938 }
27939
27940 void allowElement(String tagName, {UriPolicy uriPolicy,
27941 Iterable<String> attributes,
27942 Iterable<String> uriAttributes}) {
27943
27944 allowCustomElement(tagName, uriPolicy: uriPolicy,
27945 attributes: attributes,
27946 uriAttributes: uriAttributes);
27947 }
27948
27949 /**
27950 * Allow templating elements (such as <template> and template-related
27951 * attributes.
27952 *
27953 * This still requires other validators to allow regular attributes to be
27954 * bound (such as [allowHtml5]).
27955 */
27956 void allowTemplating() {
27957 add(new _TemplatingNodeValidator());
27958 }
27959
27960 /**
27961 * Add an additional validator to the current list of validators.
27962 *
27963 * Elements and attributes will be accepted if they are accepted by any
27964 * validators.
27965 */
27966 void add(NodeValidator validator) {
27967 _validators.add(validator);
27968 }
27969
27970 bool allowsElement(Element element) {
27971 return _validators.any((v) => v.allowsElement(element));
27972 }
27973
27974 bool allowsAttribute(Element element, String attributeName, String value) {
27975 return _validators.any(
27976 (v) => v.allowsAttribute(element, attributeName, value));
27977 }
27978 }
27979
27980 class _SimpleNodeValidator implements NodeValidator {
27981 final Set<String> allowedElements;
27982 final Set<String> allowedAttributes;
27983 final Set<String> allowedUriAttributes;
27984 final UriPolicy uriPolicy;
27985
27986 factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) {
27987 return new _SimpleNodeValidator(uriPolicy,
27988 allowedElements: [
27989 'A',
27990 'FORM'],
27991 allowedAttributes: [
27992 'A::accesskey',
27993 'A::coords',
27994 'A::hreflang',
27995 'A::name',
27996 'A::shape',
27997 'A::tabindex',
27998 'A::target',
27999 'A::type',
28000 'FORM::accept',
28001 'FORM::autocomplete',
28002 'FORM::enctype',
28003 'FORM::method',
28004 'FORM::name',
28005 'FORM::novalidate',
28006 'FORM::target',
28007 ],
28008 allowedUriAttributes: [
28009 'A::href',
28010 'FORM::action',
28011 ]);
28012 }
28013
28014 factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) {
28015 return new _SimpleNodeValidator(uriPolicy,
28016 allowedElements: [
28017 'IMG'
28018 ],
28019 allowedAttributes: [
28020 'IMG::align',
28021 'IMG::alt',
28022 'IMG::border',
28023 'IMG::height',
28024 'IMG::hspace',
28025 'IMG::ismap',
28026 'IMG::name',
28027 'IMG::usemap',
28028 'IMG::vspace',
28029 'IMG::width',
28030 ],
28031 allowedUriAttributes: [
28032 'IMG::src',
28033 ]);
28034 }
28035
28036 factory _SimpleNodeValidator.allowTextElements() {
28037 return new _SimpleNodeValidator(null,
28038 allowedElements: [
28039 'B',
28040 'BLOCKQUOTE',
28041 'BR',
28042 'EM',
28043 'H1',
28044 'H2',
28045 'H3',
28046 'H4',
28047 'H5',
28048 'H6',
28049 'HR',
28050 'I',
28051 'LI',
28052 'OL',
28053 'P',
28054 'SPAN',
28055 'UL',
28056 ]);
28057 }
28058
28059 /**
28060 * Elements must be uppercased tag names. For example `'IMG'`.
28061 * Attributes must be uppercased tag name followed by :: followed by
28062 * lowercase attribute name. For example `'IMG:src'`.
28063 */
28064 _SimpleNodeValidator(this.uriPolicy,
28065 {Iterable<String> allowedElements, Iterable<String> allowedAttributes,
28066 Iterable<String> allowedUriAttributes}):
28067 this.allowedElements = allowedElements != null ?
28068 new Set.from(allowedElements) : new Set(),
28069 this.allowedAttributes = allowedAttributes != null ?
28070 new Set.from(allowedAttributes) : new Set(),
28071 this.allowedUriAttributes = allowedUriAttributes != null ?
28072 new Set.from(allowedUriAttributes) : new Set();
28073
28074 bool allowsElement(Element element) {
28075 return allowedElements.contains(element.tagName);
28076 }
28077
28078 bool allowsAttribute(Element element, String attributeName, String value) {
28079 var tagName = element.tagName;
28080 if (allowedUriAttributes.contains('$tagName::$attributeName')) {
28081 return uriPolicy.allowsUri(value);
28082 } else if (allowedUriAttributes.contains('*::$attributeName')) {
28083 return uriPolicy.allowsUri(value);
28084 } else if (allowedAttributes.contains('$tagName::$attributeName')) {
28085 return true;
28086 } else if (allowedAttributes.contains('*::$attributeName')) {
28087 return true;
28088 } else if (allowedAttributes.contains('$tagName::*')) {
28089 return true;
28090 } else if (allowedAttributes.contains('*::*')) {
28091 return true;
28092 }
28093 return false;
28094 }
28095 }
28096
28097 class _CustomElementNodeValidator extends _SimpleNodeValidator {
28098 final bool allowTypeExtension;
28099 final bool allowCustomTag;
28100
28101 _CustomElementNodeValidator(UriPolicy uriPolicy,
28102 Iterable<String> allowedElements,
28103 Iterable<String> allowedAttributes,
28104 Iterable<String> allowedUriAttributes,
28105 bool allowTypeExtension,
28106 bool allowCustomTag):
28107
28108 super(uriPolicy,
28109 allowedElements: allowedElements,
28110 allowedAttributes: allowedAttributes,
28111 allowedUriAttributes: allowedUriAttributes),
28112 this.allowTypeExtension = allowTypeExtension == true,
28113 this.allowCustomTag = allowCustomTag == true;
28114
28115 bool allowsElement(Element element) {
28116 if (allowTypeExtension) {
28117 var isAttr = element.attributes['is'];
28118 if (isAttr != null) {
28119 return allowedElements.contains(isAttr.toUpperCase()) &&
28120 allowedElements.contains(element.tagName);
28121 }
28122 }
28123 return allowCustomTag && allowedElements.contains(element.tagName);
28124 }
28125
28126 bool allowsAttribute(Element element, String attributeName, String value) {
28127 if (allowsElement(element)) {
28128 if (allowTypeExtension && attributeName == 'is' &&
28129 allowedElements.contains(value.toUpperCase())) {
28130 return true;
28131 }
28132 return super.allowsAttribute(element, attributeName, value);
28133 }
28134 return false;
28135 }
28136 }
28137
28138 class _TemplatingNodeValidator extends _SimpleNodeValidator {
28139 static const _TEMPLATE_ATTRS =
28140 const <String>['bind', 'if', 'ref', 'repeat', 'syntax'];
28141
28142 final Set<String> _templateAttrs;
28143
28144 _TemplatingNodeValidator():
28145 super(null,
28146 allowedElements: [
28147 'TEMPLATE'
28148 ],
28149 allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')),
28150 _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) {
28151 }
28152
28153 bool allowsAttribute(Element element, String attributeName, String value) {
28154 if (super.allowsAttribute(element, attributeName, value)) {
28155 return true;
28156 }
28157
28158 if (attributeName == 'template' && value == "") {
28159 return true;
28160 }
28161
28162 if (element.attributes['template'] == "" ) {
28163 return _templateAttrs.contains(attributeName);
28164 }
28165 return false;
28166 }
28167 }
28168
28169
28170 class _SvgNodeValidator implements NodeValidator {
28171 bool allowsElement(Element element) {
28172 if (element is svg.ScriptElement) {
28173 return false;
28174 }
28175 if (element is svg.SvgElement) {
28176 return true;
28177 }
28178 return false;
28179 }
28180
28181 bool allowsAttribute(Element element, String attributeName, String value) {
28182 if (attributeName == 'is' || attributeName.startsWith('on')) {
28183 return false;
28184 }
28185 return allowsElement(element);
28186 }
28187 }
26939 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28188 // 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 28189 // 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. 28190 // BSD-style license that can be found in the LICENSE file.
26942 28191
26943 28192
26944 // This code is inspired by ChangeSummary: 28193 // This code is inspired by ChangeSummary:
26945 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js 28194 // https://github.com/rafaelw/ChangeSummary/blob/master/change_summary.js
26946 // ...which underlies MDV. Since we don't need the functionality of 28195 // ...which underlies MDV. Since we don't need the functionality of
26947 // ChangeSummary, we just implement what we need for data bindings. 28196 // ChangeSummary, we just implement what we need for data bindings.
26948 // This allows our implementation to be much simpler. 28197 // 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 28687 * Truncates coordinates to integers and returns the result as a new
27439 * rectangle. 28688 * rectangle.
27440 */ 28689 */
27441 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(), 28690 Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
27442 height.toInt()); 28691 height.toInt());
27443 28692
27444 Point get topLeft => new Point(this.left, this.top); 28693 Point get topLeft => new Point(this.left, this.top);
27445 Point get bottomRight => new Point(this.left + this.width, 28694 Point get bottomRight => new Point(this.left + this.width,
27446 this.top + this.height); 28695 this.top + this.height);
27447 } 28696 }
28697 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
28698 // for details. All rights reserved. Use of this source code is governed by a
28699 // BSD-style license that can be found in the LICENSE file.
28700
28701 // Patch file for the dart:isolate library.
28702
28703
28704 /********************************************************
28705 Inserted from lib/isolate/serialization.dart
28706 ********************************************************/
28707
28708 class _MessageTraverserVisitedMap {
28709
28710 operator[](var object) => null;
28711 void operator[]=(var object, var info) { }
28712
28713 void reset() { }
28714 void cleanup() { }
28715
28716 }
28717
28718 /** Abstract visitor for dart objects that can be sent as isolate messages. */
28719 abstract class _MessageTraverser {
28720
28721 _MessageTraverserVisitedMap _visited;
28722 _MessageTraverser() : _visited = new _MessageTraverserVisitedMap();
28723
28724 /** Visitor's entry point. */
28725 traverse(var x) {
28726 if (isPrimitive(x)) return visitPrimitive(x);
28727 _visited.reset();
28728 var result;
28729 try {
28730 result = _dispatch(x);
28731 } finally {
28732 _visited.cleanup();
28733 }
28734 return result;
28735 }
28736
28737 _dispatch(var x) {
28738 if (isPrimitive(x)) return visitPrimitive(x);
28739 if (x is List) return visitList(x);
28740 if (x is Map) return visitMap(x);
28741 if (x is SendPort) return visitSendPort(x);
28742 if (x is SendPortSync) return visitSendPortSync(x);
28743
28744 // Overridable fallback.
28745 return visitObject(x);
28746 }
28747
28748 visitPrimitive(x);
28749 visitList(List x);
28750 visitMap(Map x);
28751 visitSendPort(SendPort x);
28752 visitSendPortSync(SendPortSync x);
28753
28754 visitObject(Object x) {
28755 // TODO(floitsch): make this a real exception. (which one)?
28756 throw "Message serialization: Illegal value $x passed";
28757 }
28758
28759 static bool isPrimitive(x) {
28760 return (x == null) || (x is String) || (x is num) || (x is bool);
28761 }
28762 }
28763
28764
28765 /** Visitor that serializes a message as a JSON array. */
28766 abstract class _Serializer extends _MessageTraverser {
28767 int _nextFreeRefId = 0;
28768
28769 visitPrimitive(x) => x;
28770
28771 visitList(List list) {
28772 int copyId = _visited[list];
28773 if (copyId != null) return ['ref', copyId];
28774
28775 int id = _nextFreeRefId++;
28776 _visited[list] = id;
28777 var jsArray = _serializeList(list);
28778 // TODO(floitsch): we are losing the generic type.
28779 return ['list', id, jsArray];
28780 }
28781
28782 visitMap(Map map) {
28783 int copyId = _visited[map];
28784 if (copyId != null) return ['ref', copyId];
28785
28786 int id = _nextFreeRefId++;
28787 _visited[map] = id;
28788 var keys = _serializeList(map.keys.toList());
28789 var values = _serializeList(map.values.toList());
28790 // TODO(floitsch): we are losing the generic type.
28791 return ['map', id, keys, values];
28792 }
28793
28794 _serializeList(List list) {
28795 int len = list.length;
28796 var result = new List(len);
28797 for (int i = 0; i < len; i++) {
28798 result[i] = _dispatch(list[i]);
28799 }
28800 return result;
28801 }
28802 }
28803
28804 /** Deserializes arrays created with [_Serializer]. */
28805 abstract class _Deserializer {
28806 Map<int, dynamic> _deserialized;
28807
28808 _Deserializer();
28809
28810 static bool isPrimitive(x) {
28811 return (x == null) || (x is String) || (x is num) || (x is bool);
28812 }
28813
28814 deserialize(x) {
28815 if (isPrimitive(x)) return x;
28816 // TODO(floitsch): this should be new HashMap<int, dynamic>()
28817 _deserialized = new HashMap();
28818 return _deserializeHelper(x);
28819 }
28820
28821 _deserializeHelper(x) {
28822 if (isPrimitive(x)) return x;
28823 assert(x is List);
28824 switch (x[0]) {
28825 case 'ref': return _deserializeRef(x);
28826 case 'list': return _deserializeList(x);
28827 case 'map': return _deserializeMap(x);
28828 case 'sendport': return deserializeSendPort(x);
28829 default: return deserializeObject(x);
28830 }
28831 }
28832
28833 _deserializeRef(List x) {
28834 int id = x[1];
28835 var result = _deserialized[id];
28836 assert(result != null);
28837 return result;
28838 }
28839
28840 List _deserializeList(List x) {
28841 int id = x[1];
28842 // We rely on the fact that Dart-lists are directly mapped to Js-arrays.
28843 List dartList = x[2];
28844 _deserialized[id] = dartList;
28845 int len = dartList.length;
28846 for (int i = 0; i < len; i++) {
28847 dartList[i] = _deserializeHelper(dartList[i]);
28848 }
28849 return dartList;
28850 }
28851
28852 Map _deserializeMap(List x) {
28853 Map result = new Map();
28854 int id = x[1];
28855 _deserialized[id] = result;
28856 List keys = x[2];
28857 List values = x[3];
28858 int len = keys.length;
28859 assert(len == values.length);
28860 for (int i = 0; i < len; i++) {
28861 var key = _deserializeHelper(keys[i]);
28862 var value = _deserializeHelper(values[i]);
28863 result[key] = value;
28864 }
28865 return result;
28866 }
28867
28868 deserializeSendPort(List x);
28869
28870 deserializeObject(List x) {
28871 // TODO(floitsch): Use real exception (which one?).
28872 throw "Unexpected serialized object";
28873 }
28874 }
28875
27448 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file 28876 // 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 28877 // 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. 28878 // BSD-style license that can be found in the LICENSE file.
27451 28879
27452 28880
27453 // This code is a port of Model-Driven-Views: 28881 // This code is a port of Model-Driven-Views:
27454 // https://github.com/polymer-project/mdv 28882 // https://github.com/polymer-project/mdv
27455 // The code mostly comes from src/template_element.js 28883 // The code mostly comes from src/template_element.js
27456 28884
27457 typedef void _ChangeHandler(value); 28885 typedef void _ChangeHandler(value);
(...skipping 768 matching lines...) Expand 10 before | Expand all | Expand 10 after
28226 _sub.cancel(); 29654 _sub.cancel();
28227 _sub = null; 29655 _sub = null;
28228 } 29656 }
28229 29657
28230 void abandon() { 29658 void abandon() {
28231 unobserve(); 29659 unobserve();
28232 _valueBinding.cancel(); 29660 _valueBinding.cancel();
28233 inputs.dispose(); 29661 inputs.dispose();
28234 } 29662 }
28235 } 29663 }
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 29664 // 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 29665 // 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. 29666 // BSD-style license that can be found in the LICENSE file.
29667
29668
29669
29670 /**
29671 * Interface used to validate that only accepted elements and attributes are
29672 * allowed while parsing HTML strings into DOM nodes.
29673 */
29674 abstract class NodeValidator {
29675
29676 /**
29677 * Construct a default NodeValidator which only accepts whitelisted HTML5
29678 * elements and attributes.
29679 *
29680 * If a uriPolicy is not specified then the default uriPolicy will be used.
29681 */
29682 factory NodeValidator({UriPolicy uriPolicy}) =>
29683 new _Html5NodeValidator(uriPolicy: uriPolicy);
29684
29685 /**
29686 * Returns true if the tagName is an accepted type.
29687 */
29688 bool allowsElement(Element element);
29689
29690 /**
29691 * Returns true if the attribute is allowed.
29692 *
29693 * The attributeName parameter will always be in lowercase.
29694 *
29695 * See [allowsElement] for format of tagName.
29696 */
29697 bool allowsAttribute(Element element, String attributeName, String value);
29698 }
29699
29700 /**
29701 * Performs sanitization of a node tree after construction to ensure that it
29702 * does not contain any disallowed elements or attributes.
29703 *
29704 * In general custom implementations of this class should not be necessary and
29705 * all validation customization should be done in custom NodeValidators, but
29706 * custom implementations of this class can be created to perform more complex
29707 * tree sanitization.
29708 */
29709 abstract class NodeTreeSanitizer {
29710
29711 /**
29712 * Constructs a default tree sanitizer which will remove all elements and
29713 * attributes which are not allowed by the provided validator.
29714 */
29715 factory NodeTreeSanitizer(NodeValidator validator) =>
29716 new _ValidatingTreeSanitizer(validator);
29717
29718 /**
29719 * Called with the root of the tree which is to be sanitized.
29720 *
29721 * This method needs to walk the entire tree and either remove elements and
29722 * attributes which are not recognized as safe or throw an exception which
29723 * will mark the entire tree as unsafe.
29724 */
29725 void sanitizeTree(Node node);
29726 }
29727
29728 /**
29729 * Defines the policy for what types of uris are allowed for particular
29730 * attribute values.
29731 *
29732 * This can be used to provide custom rules such as allowing all http:// URIs
29733 * for image attributes but only same-origin URIs for anchor tags.
29734 */
29735 abstract class UriPolicy {
29736 /**
29737 * Constructs the default UriPolicy which is to only allow Uris to the same
29738 * origin as the application was launched from.
29739 *
29740 * This will block all ftp: mailto: URIs. It will also block accessing
29741 * https://example.com if the app is running from http://example.com.
29742 */
29743 factory UriPolicy() => new _SameOriginUriPolicy();
29744
29745 /**
29746 * Checks if the uri is allowed on the specified attribute.
29747 *
29748 * The uri provided may or may not be a relative path.
29749 */
29750 bool allowsUri(String uri);
29751 }
29752
29753 /**
29754 * Allows URIs to the same origin as the current application was loaded from
29755 * (such as https://example.com:80).
29756 */
29757 class _SameOriginUriPolicy implements UriPolicy {
29758 final AnchorElement _hiddenAnchor = new AnchorElement();
29759 final Location _loc = window.location;
29760
29761 bool allowsUri(String uri) {
29762 _hiddenAnchor.href = uri;
29763 return _hiddenAnchor.hostname == _loc.hostname &&
29764 _hiddenAnchor.port == _loc.port &&
29765 _hiddenAnchor.protocol == _loc.protocol;
29766 }
29767 }
29768
29769
29770 /**
29771 * Standard tree sanitizer which validates a node tree against the provided
29772 * validator and removes any nodes or attributes which are not allowed.
29773 */
29774 class _ValidatingTreeSanitizer implements NodeTreeSanitizer {
29775 final NodeValidator validator;
29776 _ValidatingTreeSanitizer(this.validator) {}
29777
29778 void sanitizeTree(Node node) {
29779 void walk(Node node) {
29780 sanitizeNode(node);
29781
29782 var child = node.$dom_lastChild;
29783 while (child != null) {
29784 // Child may be removed during the walk.
29785 var nextChild = child.previousNode;
29786 walk(child);
29787 child = nextChild;
29788 }
29789 }
29790 walk(node);
29791 }
29792
29793 void sanitizeNode(Node node) {
29794 switch (node.nodeType) {
29795 case Node.ELEMENT_NODE:
29796 Element element = node;
29797 var attrs = element.attributes;
29798 if (!validator.allowsElement(element)) {
29799 element.remove();
29800 break;
29801 }
29802
29803 var isAttr = attrs['is'];
29804 if (isAttr != null) {
29805 if (!validator.allowsAttribute(element, 'is', isAttr)) {
29806 element.remove();
29807 break;
29808 }
29809 }
29810
29811 // TODO(blois): Need to be able to get all attributes, irrespective of
29812 // XMLNS.
29813 var keys = attrs.keys.toList();
29814 for (var i = attrs.length - 1; i >= 0; --i) {
29815 var name = keys[i];
29816 if (!validator.allowsAttribute(element, name, attrs[name])) {
29817 attrs.remove(name);
29818 }
29819 }
29820
29821 if (element is TemplateElement) {
29822 TemplateElement template = element;
29823 sanitizeTree(template.content);
29824 }
29825 break;
29826 case Node.COMMENT_NODE:
29827 case Node.DOCUMENT_FRAGMENT_NODE:
29828 case Node.TEXT_NODE:
29829 case Node.CDATA_SECTION_NODE:
29830 break;
29831 default:
29832 node.remove();
29833 }
29834 }
29835 }
29836 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
29837 // for details. All rights reserved. Use of this source code is governed by a
29838 // BSD-style license that can be found in the LICENSE file.
28821 29839
28822 29840
28823 /** 29841 /**
28824 * Helper class to implement custom events which wrap DOM events. 29842 * Helper class to implement custom events which wrap DOM events.
28825 */ 29843 */
28826 class _WrappedEvent implements Event { 29844 class _WrappedEvent implements Event {
28827 final Event wrapped; 29845 final Event wrapped;
28828 _WrappedEvent(this.wrapped); 29846 _WrappedEvent(this.wrapped);
28829 29847
28830 bool get bubbles => wrapped.bubbles; 29848 bool get bubbles => wrapped.bubbles;
(...skipping 110 matching lines...) Expand 10 before | Expand all | Expand 10 after
28941 return _iterator.moveNext(); 29959 return _iterator.moveNext();
28942 } 29960 }
28943 29961
28944 E get current => _iterator.current; 29962 E get current => _iterator.current;
28945 } 29963 }
28946 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file 29964 // 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 29965 // 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. 29966 // BSD-style license that can be found in the LICENSE file.
28949 29967
28950 29968
29969 class _HttpRequestUtils {
29970
29971 // Helper for factory HttpRequest.get
29972 static HttpRequest get(String url,
29973 onComplete(HttpRequest request),
29974 bool withCredentials) {
29975 final request = new HttpRequest();
29976 request.open('GET', url, async: true);
29977
29978 request.withCredentials = withCredentials;
29979
29980 request.onReadyStateChange.listen((e) {
29981 if (request.readyState == HttpRequest.DONE) {
29982 onComplete(request);
29983 }
29984 });
29985
29986 request.send();
29987
29988 return request;
29989 }
29990 }
29991 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
29992 // for details. All rights reserved. Use of this source code is governed by a
29993 // BSD-style license that can be found in the LICENSE file.
29994
29995
28951 // Conversions for Window. These check if the window is the local 29996 // 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. 29997 // 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. 29998 // 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 29999 // We omit an unwrapper for Window as no methods take a non-local
28955 // window as a parameter. 30000 // window as a parameter.
28956 30001
28957 30002
28958 DateTime _convertNativeToDart_DateTime(date) { 30003 DateTime _convertNativeToDart_DateTime(date) {
28959 var millisSinceEpoch = JS('int', '#.getTime()', date); 30004 var millisSinceEpoch = JS('int', '#.getTime()', date);
28960 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true); 30005 return new DateTime.fromMillisecondsSinceEpoch(millisSinceEpoch, isUtc: true);
(...skipping 396 matching lines...) Expand 10 before | Expand all | Expand 10 after
29357 _position = nextPosition; 30402 _position = nextPosition;
29358 return true; 30403 return true;
29359 } 30404 }
29360 _current = null; 30405 _current = null;
29361 _position = _array.length; 30406 _position = _array.length;
29362 return false; 30407 return false;
29363 } 30408 }
29364 30409
29365 T get current => _current; 30410 T get current => _current;
29366 } 30411 }
OLDNEW
« no previous file with comments | « no previous file | sdk/lib/html/dartium/html_dartium.dart » ('j') | tests/html/safe_dom_test.dart » ('J')

Powered by Google App Engine
This is Rietveld 408576698