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