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