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