OLD | NEW |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 Loading... |
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 } |
OLD | NEW |