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

Unified Diff: sdk/lib/html/dart2js/html_dart2js.dart

Side-by-side diff isn't available for this file because of its large size.
Issue 23523002: Revert "First rev of Safe DOM" (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
Download patch
« no previous file with comments | « pkg/unittest/lib/html_enhanced_config.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: sdk/lib/html/dart2js/html_dart2js.dart
diff --git a/sdk/lib/html/dart2js/html_dart2js.dart b/sdk/lib/html/dart2js/html_dart2js.dart
index c9ead86e406ffdaa3843d6d67e58c146a074d6c6..955ddaccc4ead6ad46edd0adf4b1e920251954f1 100644
--- a/sdk/lib/html/dart2js/html_dart2js.dart
+++ b/sdk/lib/html/dart2js/html_dart2js.dart
@@ -7626,18 +7626,21 @@ class Document extends Node native "Document"
class DocumentFragment extends Node implements ParentNode native "DocumentFragment" {
factory DocumentFragment() => document.createDocumentFragment();
- factory DocumentFragment.html(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
-
- return document.body.createFragment(html,
- validator: validator, treeSanitizer: treeSanitizer);
+ factory DocumentFragment.html(String html) {
+ final fragment = new DocumentFragment();
+ fragment.innerHtml = html;
+ return fragment;
}
- factory DocumentFragment.svg(String svgContent,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
+ factory DocumentFragment.svg(String svgContent) {
+ final fragment = new DocumentFragment();
+ final e = new svg.SvgSvgElement();
+ e.innerHtml = svgContent;
- return new svg.SvgSvgElement().createFragment(svgContent,
- validator: validator, treeSanitizer: treeSanitizer);
+ // Copy list first since we don't want liveness during iteration.
+ final List nodes = new List.from(e.nodes);
+ fragment.nodes.addAll(nodes);
+ return fragment;
}
// Native field is used only by Dart code so does not lead to instantiation
@@ -7671,16 +7674,17 @@ class DocumentFragment extends Node implements ParentNode native "DocumentFragme
return e.innerHtml;
}
+ // TODO(nweiz): Do we want to support some variant of innerHtml for XML and/or
+ // SVG strings?
void set innerHtml(String value) {
- this.setInnerHtml(value);
- }
+ this.nodes.clear();
- void setInnerHtml(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
+ final e = new Element.tag("div");
+ e.innerHtml = value;
- this.nodes.clear();
- append(document.body.createFragment(
- html, validator: validator, treeSanitizer: treeSanitizer));
+ // Copy list first since we don't want liveness during iteration.
+ List nodes = new List.from(e.nodes, growable: false);
+ this.nodes.addAll(nodes);
}
/**
@@ -8762,31 +8766,20 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
/**
* Creates an HTML element from a valid fragment of HTML.
*
- * var element = new Element.html('<div class="foo">content</div>');
- *
- * The HTML fragment should contain only one single root element, any
- * leading or trailing text nodes will be removed.
- *
- * The HTML fragment is parsed as if it occurred within the context of a
- * `<body>` tag, this means that special elements such as `<caption>` which
- * must be parsed within the scope of a `<table>` element will be dropped. Use
- * [createFragment] to parse contextual HTML fragments.
+ * The [html] fragment must represent valid HTML with a single element root,
+ * which will be parsed and returned.
*
- * Unless a validator is provided this will perform the default validation
- * and remove all scriptable elements and attributes.
+ * Important: the contents of [html] should not contain any user-supplied
+ * data. Without strict data validation it is impossible to prevent script
+ * injection exploits.
*
- * See also:
- *
- * * [NodeValidator]
+ * It is instead recommended that elements be constructed via [Element.tag]
+ * and text be added via [text].
*
+ * var element = new Element.html('<div class="foo">content</div>');
*/
- factory Element.html(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
- var fragment = document.body.createFragment(html, validator: validator,
- treeSanitizer: treeSanitizer);
-
- return fragment.nodes.where((e) => e is Element).single;
- }
+ factory Element.html(String html) =>
+ _ElementFactoryProvider.createElement_html(html);
/**
* Creates the HTML element specified by the tag name.
@@ -9622,127 +9615,15 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
}
- static HtmlDocument _parseDocument;
- static NodeValidatorBuilder _defaultValidator;
- static _ValidatingTreeSanitizer _defaultSanitizer;
-
- /**
- * Create a DocumentFragment from the HTML fragment and ensure that it follows
- * the sanitization rules specified by the validator or treeSanitizer.
- *
- * If the default validation behavior is too restrictive then a new
- * NodeValidator should be created, either extending or wrapping a default
- * validator and overriding the validation APIs.
- *
- * The treeSanitizer is used to walk the generated node tree and sanitize it.
- * A custom treeSanitizer can also be provided to perform special validation
- * rules but since the API is more complex to implement this is discouraged.
- *
- * The returned tree is guaranteed to only contain nodes and attributes which
- * are allowed by the provided validator.
- *
- * See also:
- *
- * * [NodeValidator]
- * * [NodeTreeSanitizer]
- */
- DocumentFragment createFragment(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
- if (treeSanitizer == null) {
- if (validator == null) {
- if (_defaultValidator == null) {
- _defaultValidator = new NodeValidatorBuilder.common();
- }
- validator = _defaultValidator;
- }
- if (_defaultSanitizer == null) {
- _defaultSanitizer = new _ValidatingTreeSanitizer(validator);
- } else {
- _defaultSanitizer.validator = validator;
- }
- treeSanitizer = _defaultSanitizer;
- } else if (validator != null) {
- throw new ArgumentError(
- 'validator can only be passed if treeSanitizer is null');
- }
-
- if (_parseDocument == null) {
- _parseDocument = document.implementation.createHtmlDocument('');
- }
- var contextElement;
- if (this is BodyElement) {
- contextElement = _parseDocument.body;
- } else {
- contextElement = _parseDocument.$dom_createElement(tagName);
- _parseDocument.body.append(contextElement);
- }
- var fragment;
- if (Range.supportsCreateContextualFragment) {
- var range = _parseDocument.$dom_createRange();
- range.selectNodeContents(contextElement);
- fragment = range.createContextualFragment(html);
- } else {
- contextElement._innerHtml = html;
-
- fragment = _parseDocument.createDocumentFragment();
- while (contextElement.firstChild != null) {
- fragment.append(contextElement.firstChild);
- }
- }
- if (contextElement != _parseDocument.body) {
- contextElement.remove();
- }
-
- treeSanitizer.sanitizeTree(fragment);
- return fragment;
- }
-
- /**
- * Parses the HTML fragment and sets it as the contents of this element.
- *
- * This uses the default sanitization behavior to sanitize the HTML fragment,
- * use [setInnerHtml] to override the default behavior.
- */
- void set innerHtml(String html) {
- this.setInnerHtml(html);
- }
-
- /**
- * Parses the HTML fragment and sets it as the contents of this element.
- * This ensures that the generated content follows the sanitization rules
- * specified by the validator or treeSanitizer.
- *
- * If the default validation behavior is too restrictive then a new
- * NodeValidator should be created, either extending or wrapping a default
- * validator and overriding the validation APIs.
- *
- * The treeSanitizer is used to walk the generated node tree and sanitize it.
- * A custom treeSanitizer can also be provided to perform special validation
- * rules but since the API is more complex to implement this is discouraged.
- *
- * The resulting tree is guaranteed to only contain nodes and attributes which
- * are allowed by the provided validator.
- *
- * See also:
- *
- * * [NodeValidator]
- * * [NodeTreeSanitizer]
- */
- void setInnerHtml(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
- text = null;
- append(createFragment(
- html, validator: validator, treeSanitizer: treeSanitizer));
- }
- String get innerHtml => _innerHtml;
+ @JSName('innerHTML')
+ @DomName('HTMLElement.innerHTML')
+ String get innerHtml => JS('String', '#.innerHTML', this);
- /**
- * For use while transitioning to the safe [innerHtml] or [setInnerHtml].
- * Unsafe because it opens the app to cross-site scripting vulnerabilities.
- */
- @deprecated
- void set unsafeInnerHtml(String html) {
- _innerHtml = html;
+ void set innerHtml(String value) {
+ JS('', '#.innerHTML = #', this, value);
+ // Polyfill relies on mutation observers for upgrading, but we want it
+ // immediate.
+ Platform.upgradeCustomElements(this);
}
// To suppress missing implicit constructor warnings.
@@ -9981,11 +9862,6 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
@DocsEditable()
bool hidden;
- @JSName('innerHTML')
- @DomName('Element.innerHTML')
- @DocsEditable()
- String _innerHtml;
-
@DomName('Element.inputMethodContext')
@DocsEditable()
@Experimental() // untriaged
@@ -10561,8 +10437,119 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
}
-
+final _START_TAG_REGEXP = new RegExp('<(\\w+)');
class _ElementFactoryProvider {
+ static const _CUSTOM_PARENT_TAG_MAP = const {
+ 'body' : 'html',
+ 'head' : 'html',
+ 'caption' : 'table',
+ 'td': 'tr',
+ 'th': 'tr',
+ 'colgroup': 'table',
+ 'col' : 'colgroup',
+ 'tr' : 'tbody',
+ 'tbody' : 'table',
+ 'tfoot' : 'table',
+ 'thead' : 'table',
+ 'track' : 'audio',
+ };
+
+ @DomName('Document.createElement')
+ static Element createElement_html(String html) {
+ // TODO(jacobr): this method can be made more robust and performant.
+ // 1) Cache the dummy parent elements required to use innerHTML rather than
+ // creating them every call.
+ // 2) Verify that the html does not contain leading or trailing text nodes.
+ // 3) Verify that the html does not contain both <head> and <body> tags.
+ // 4) Detatch the created element from its dummy parent.
+ String parentTag = 'div';
+ String tag;
+ final match = _START_TAG_REGEXP.firstMatch(html);
+ if (match != null) {
+ tag = match.group(1).toLowerCase();
+ if (Device.isIE && Element._TABLE_TAGS.containsKey(tag)) {
+ return _createTableForIE(html, tag);
+ }
+ parentTag = _CUSTOM_PARENT_TAG_MAP[tag];
+ if (parentTag == null) parentTag = 'div';
+ }
+
+ final temp = new Element.tag(parentTag);
+ temp.innerHtml = html;
+
+ Element element;
+ if (temp.children.length == 1) {
+ element = temp.children[0];
+ } else if (parentTag == 'html' && temp.children.length == 2) {
+ // In html5 the root <html> tag will always have a <body> and a <head>,
+ // even though the inner html only contains one of them.
+ element = temp.children[tag == 'head' ? 0 : 1];
+ } else {
+ _singleNode(temp.children);
+ }
+ element.remove();
+ return element;
+ }
+
+ /**
+ * IE table elements don't support innerHTML (even in standards mode).
+ * Instead we use a div and inject the table element in the innerHtml string.
+ * This technique works on other browsers too, but it's probably slower,
+ * so we only use it when running on IE.
+ *
+ * See also innerHTML:
+ * <http://msdn.microsoft.com/en-us/library/ie/ms533897(v=vs.85).aspx>
+ * and Building Tables Dynamically:
+ * <http://msdn.microsoft.com/en-us/library/ie/ms532998(v=vs.85).aspx>.
+ */
+ static Element _createTableForIE(String html, String tag) {
+ var div = new Element.tag('div');
+ div.innerHtml = '<table>$html</table>';
+ var table = _singleNode(div.children);
+ Element element;
+ switch (tag) {
+ case 'td':
+ case 'th':
+ TableRowElement row = _singleNode(table.rows);
+ element = _singleNode(row.cells);
+ break;
+ case 'tr':
+ element = _singleNode(table.rows);
+ break;
+ case 'tbody':
+ element = _singleNode(table.tBodies);
+ break;
+ case 'thead':
+ element = table.tHead;
+ break;
+ case 'tfoot':
+ element = table.tFoot;
+ break;
+ case 'caption':
+ element = table.caption;
+ break;
+ case 'colgroup':
+ element = _getColgroup(table);
+ break;
+ case 'col':
+ element = _singleNode(_getColgroup(table).children);
+ break;
+ }
+ element.remove();
+ return element;
+ }
+
+ static TableColElement _getColgroup(TableElement table) {
+ // TODO(jmesserly): is there a better way to do this?
+ return _singleNode(table.children.where((n) => n.tagName == 'COLGROUP')
+ .toList());
+ }
+
+ static Node _singleNode(List<Node> list) {
+ if (list.length == 1) return list[0];
+ throw new ArgumentError('HTML had ${list.length} '
+ 'top level elements but 1 expected');
+ }
@DomName('Document.createElement')
// Optimization to improve performance until the dart2js compiler inlines this
@@ -22761,22 +22748,6 @@ option[template] {
}''';
document.head.append(style);
}
-
- /**
- * An override to place the contents into content rather than as child nodes.
- *
- * See also:
- *
- * * <https://dvcs.w3.org/hg/webcomponents/raw-file/tip/spec/templates/index.html#innerhtml-on-templates>
- */
- void setInnerHtml(String html,
- {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
- text = null;
- var fragment = createFragment(
- html, validator: validator, treeSanitizer: treeSanitizer);
-
- content.append(fragment);
- }
}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
@@ -28531,455 +28502,6 @@ class _CustomEventStreamProvider<T extends Event>
return _eventTypeGetter(target);
}
}
-// DO NOT EDIT- this file is generated from running tool/generator.sh.
-
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-
-/**
- * A Dart DOM validator generated from Caja whitelists.
- *
- * This contains a whitelist of known HTML tagNames and attributes and will only
- * accept known good values.
- *
- * See also:
- *
- * * <https://code.google.com/p/google-caja/wiki/CajaWhitelists>
- */
-class _Html5NodeValidator implements NodeValidator {
-
- static final Set<String> _allowedElements = new Set.from([
- 'A',
- 'ABBR',
- 'ACRONYM',
- 'ADDRESS',
- 'AREA',
- 'ARTICLE',
- 'ASIDE',
- 'AUDIO',
- 'B',
- 'BDI',
- 'BDO',
- 'BIG',
- 'BLOCKQUOTE',
- 'BR',
- 'BUTTON',
- 'CANVAS',
- 'CAPTION',
- 'CENTER',
- 'CITE',
- 'CODE',
- 'COL',
- 'COLGROUP',
- 'COMMAND',
- 'DATA',
- 'DATALIST',
- 'DD',
- 'DEL',
- 'DETAILS',
- 'DFN',
- 'DIR',
- 'DIV',
- 'DL',
- 'DT',
- 'EM',
- 'FIELDSET',
- 'FIGCAPTION',
- 'FIGURE',
- 'FONT',
- 'FOOTER',
- 'FORM',
- 'H1',
- 'H2',
- 'H3',
- 'H4',
- 'H5',
- 'H6',
- 'HEADER',
- 'HGROUP',
- 'HR',
- 'I',
- 'IFRAME',
- 'IMG',
- 'INPUT',
- 'INS',
- 'KBD',
- 'LABEL',
- 'LEGEND',
- 'LI',
- 'MAP',
- 'MARK',
- 'MENU',
- 'METER',
- 'NAV',
- 'NOBR',
- 'OL',
- 'OPTGROUP',
- 'OPTION',
- 'OUTPUT',
- 'P',
- 'PRE',
- 'PROGRESS',
- 'Q',
- 'S',
- 'SAMP',
- 'SECTION',
- 'SELECT',
- 'SMALL',
- 'SOURCE',
- 'SPAN',
- 'STRIKE',
- 'STRONG',
- 'SUB',
- 'SUMMARY',
- 'SUP',
- 'TABLE',
- 'TBODY',
- 'TD',
- 'TEXTAREA',
- 'TFOOT',
- 'TH',
- 'THEAD',
- 'TIME',
- 'TR',
- 'TRACK',
- 'TT',
- 'U',
- 'UL',
- 'VAR',
- 'VIDEO',
- 'WBR',
- ]);
-
- static const _standardAttributes = const <String>[
- '*::class',
- '*::dir',
- '*::draggable',
- '*::hidden',
- '*::id',
- '*::inert',
- '*::itemprop',
- '*::itemref',
- '*::itemscope',
- '*::lang',
- '*::spellcheck',
- '*::title',
- '*::translate',
- 'A::accesskey',
- 'A::coords',
- 'A::hreflang',
- 'A::name',
- 'A::shape',
- 'A::tabindex',
- 'A::target',
- 'A::type',
- 'AREA::accesskey',
- 'AREA::alt',
- 'AREA::coords',
- 'AREA::nohref',
- 'AREA::shape',
- 'AREA::tabindex',
- 'AREA::target',
- 'AUDIO::controls',
- 'AUDIO::loop',
- 'AUDIO::mediagroup',
- 'AUDIO::muted',
- 'AUDIO::preload',
- 'BDO::dir',
- 'BODY::alink',
- 'BODY::bgcolor',
- 'BODY::link',
- 'BODY::text',
- 'BODY::vlink',
- 'BR::clear',
- 'BUTTON::accesskey',
- 'BUTTON::disabled',
- 'BUTTON::name',
- 'BUTTON::tabindex',
- 'BUTTON::type',
- 'BUTTON::value',
- 'CANVAS::height',
- 'CANVAS::width',
- 'CAPTION::align',
- 'COL::align',
- 'COL::char',
- 'COL::charoff',
- 'COL::span',
- 'COL::valign',
- 'COL::width',
- 'COLGROUP::align',
- 'COLGROUP::char',
- 'COLGROUP::charoff',
- 'COLGROUP::span',
- 'COLGROUP::valign',
- 'COLGROUP::width',
- 'COMMAND::checked',
- 'COMMAND::command',
- 'COMMAND::disabled',
- 'COMMAND::label',
- 'COMMAND::radiogroup',
- 'COMMAND::type',
- 'DATA::value',
- 'DEL::datetime',
- 'DETAILS::open',
- 'DIR::compact',
- 'DIV::align',
- 'DL::compact',
- 'FIELDSET::disabled',
- 'FONT::color',
- 'FONT::face',
- 'FONT::size',
- 'FORM::accept',
- 'FORM::autocomplete',
- 'FORM::enctype',
- 'FORM::method',
- 'FORM::name',
- 'FORM::novalidate',
- 'FORM::target',
- 'FRAME::name',
- 'H1::align',
- 'H2::align',
- 'H3::align',
- 'H4::align',
- 'H5::align',
- 'H6::align',
- 'HR::align',
- 'HR::noshade',
- 'HR::size',
- 'HR::width',
- 'HTML::version',
- 'IFRAME::align',
- 'IFRAME::frameborder',
- 'IFRAME::height',
- 'IFRAME::marginheight',
- 'IFRAME::marginwidth',
- 'IFRAME::width',
- 'IMG::align',
- 'IMG::alt',
- 'IMG::border',
- 'IMG::height',
- 'IMG::hspace',
- 'IMG::ismap',
- 'IMG::name',
- 'IMG::usemap',
- 'IMG::vspace',
- 'IMG::width',
- 'INPUT::accept',
- 'INPUT::accesskey',
- 'INPUT::align',
- 'INPUT::alt',
- 'INPUT::autocomplete',
- 'INPUT::checked',
- 'INPUT::disabled',
- 'INPUT::inputmode',
- 'INPUT::ismap',
- 'INPUT::list',
- 'INPUT::max',
- 'INPUT::maxlength',
- 'INPUT::min',
- 'INPUT::multiple',
- 'INPUT::name',
- 'INPUT::placeholder',
- 'INPUT::readonly',
- 'INPUT::required',
- 'INPUT::size',
- 'INPUT::step',
- 'INPUT::tabindex',
- 'INPUT::type',
- 'INPUT::usemap',
- 'INPUT::value',
- 'INS::datetime',
- 'KEYGEN::disabled',
- 'KEYGEN::keytype',
- 'KEYGEN::name',
- 'LABEL::accesskey',
- 'LABEL::for',
- 'LEGEND::accesskey',
- 'LEGEND::align',
- 'LI::type',
- 'LI::value',
- 'LINK::sizes',
- 'MAP::name',
- 'MENU::compact',
- 'MENU::label',
- 'MENU::type',
- 'METER::high',
- 'METER::low',
- 'METER::max',
- 'METER::min',
- 'METER::value',
- 'OBJECT::typemustmatch',
- 'OL::compact',
- 'OL::reversed',
- 'OL::start',
- 'OL::type',
- 'OPTGROUP::disabled',
- 'OPTGROUP::label',
- 'OPTION::disabled',
- 'OPTION::label',
- 'OPTION::selected',
- 'OPTION::value',
- 'OUTPUT::for',
- 'OUTPUT::name',
- 'P::align',
- 'PRE::width',
- 'PROGRESS::max',
- 'PROGRESS::min',
- 'PROGRESS::value',
- 'SELECT::autocomplete',
- 'SELECT::disabled',
- 'SELECT::multiple',
- 'SELECT::name',
- 'SELECT::required',
- 'SELECT::size',
- 'SELECT::tabindex',
- 'SOURCE::type',
- 'TABLE::align',
- 'TABLE::bgcolor',
- 'TABLE::border',
- 'TABLE::cellpadding',
- 'TABLE::cellspacing',
- 'TABLE::frame',
- 'TABLE::rules',
- 'TABLE::summary',
- 'TABLE::width',
- 'TBODY::align',
- 'TBODY::char',
- 'TBODY::charoff',
- 'TBODY::valign',
- 'TD::abbr',
- 'TD::align',
- 'TD::axis',
- 'TD::bgcolor',
- 'TD::char',
- 'TD::charoff',
- 'TD::colspan',
- 'TD::headers',
- 'TD::height',
- 'TD::nowrap',
- 'TD::rowspan',
- 'TD::scope',
- 'TD::valign',
- 'TD::width',
- 'TEXTAREA::accesskey',
- 'TEXTAREA::autocomplete',
- 'TEXTAREA::cols',
- 'TEXTAREA::disabled',
- 'TEXTAREA::inputmode',
- 'TEXTAREA::name',
- 'TEXTAREA::placeholder',
- 'TEXTAREA::readonly',
- 'TEXTAREA::required',
- 'TEXTAREA::rows',
- 'TEXTAREA::tabindex',
- 'TEXTAREA::wrap',
- 'TFOOT::align',
- 'TFOOT::char',
- 'TFOOT::charoff',
- 'TFOOT::valign',
- 'TH::abbr',
- 'TH::align',
- 'TH::axis',
- 'TH::bgcolor',
- 'TH::char',
- 'TH::charoff',
- 'TH::colspan',
- 'TH::headers',
- 'TH::height',
- 'TH::nowrap',
- 'TH::rowspan',
- 'TH::scope',
- 'TH::valign',
- 'TH::width',
- 'THEAD::align',
- 'THEAD::char',
- 'THEAD::charoff',
- 'THEAD::valign',
- 'TR::align',
- 'TR::bgcolor',
- 'TR::char',
- 'TR::charoff',
- 'TR::valign',
- 'TRACK::default',
- 'TRACK::kind',
- 'TRACK::label',
- 'TRACK::srclang',
- 'UL::compact',
- 'UL::type',
- 'VIDEO::controls',
- 'VIDEO::height',
- 'VIDEO::loop',
- 'VIDEO::mediagroup',
- 'VIDEO::muted',
- 'VIDEO::preload',
- 'VIDEO::width',
- ];
-
- static const _uriAttributes = const <String>[
- 'A::href',
- 'AREA::href',
- 'BLOCKQUOTE::cite',
- 'BODY::background',
- 'COMMAND::icon',
- 'DEL::cite',
- 'FORM::action',
- 'IMG::src',
- 'INPUT::src',
- 'INS::cite',
- 'Q::cite',
- 'VIDEO::poster',
- ];
-
- final UriPolicy uriPolicy;
-
- static final Map<String, Function> _attributeValidators = {};
-
- /**
- * All known URI attributes will be validated against the UriPolicy, if
- * [uriPolicy] is null then a default UriPolicy will be used.
- */
- _Html5NodeValidator({UriPolicy uriPolicy})
- :uriPolicy = uriPolicy != null ? uriPolicy : new UriPolicy() {
-
- if (_attributeValidators.isEmpty) {
- for (var attr in _standardAttributes) {
- _attributeValidators[attr] = _standardAttributeValidator;
- }
-
- for (var attr in _uriAttributes) {
- _attributeValidators[attr] = _uriAttributeValidator;
- }
- }
- }
-
- bool allowsElement(Element element) {
- return _allowedElements.contains(element.tagName);
- }
-
- bool allowsAttribute(Element element, String attributeName, String value) {
- var tagName = element.tagName;
- var validator = _attributeValidators['$tagName::$attributeName'];
- if (validator == null) {
- validator = _attributeValidators['*::$attributeName'];
- }
- if (validator == null) {
- return false;
- }
- return validator(element, attributeName, value, this);
- }
-
- static bool _standardAttributeValidator(Element element, String attributeName,
- String value, _Html5NodeValidator context) {
- return true;
- }
-
- static bool _uriAttributeValidator(Element element, String attributeName,
- String value, _Html5NodeValidator context) {
- return context.uriPolicy.allowsUri(value);
- }
-}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -29061,222 +28583,404 @@ abstract class ImmutableListMixin<E> implements List<E> {
// BSD-style license that can be found in the LICENSE file.
-_serialize(var message) {
- return new _JsSerializer().traverse(message);
-}
-
-class _JsSerializer extends _Serializer {
-
- visitSendPortSync(SendPortSync x) {
- if (x is _JsSendPortSync) return visitJsSendPortSync(x);
- if (x is _LocalSendPortSync) return visitLocalSendPortSync(x);
- if (x is _RemoteSendPortSync) return visitRemoteSendPortSync(x);
- throw "Unknown port type $x";
- }
+/**
+ * Internal class that does the actual calculations to determine keyCode and
+ * charCode for keydown, keypress, and keyup events for all browsers.
+ */
+class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> {
+ // This code inspired by Closure's KeyHandling library.
+ // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandler.js.source.html
- visitJsSendPortSync(_JsSendPortSync x) {
- return [ 'sendport', 'nativejs', x._id ];
- }
+ /**
+ * The set of keys that have been pressed down without seeing their
+ * corresponding keyup event.
+ */
+ final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
- visitLocalSendPortSync(_LocalSendPortSync x) {
- return [ 'sendport', 'dart',
- ReceivePortSync._isolateId, x._receivePort._portId ];
- }
+ /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
+ final String _type;
- visitSendPort(SendPort x) {
- throw new UnimplementedError('Asynchronous send port not yet implemented.');
- }
+ /** The element we are watching for events to happen on. */
+ final EventTarget _target;
- visitRemoteSendPortSync(_RemoteSendPortSync x) {
- return [ 'sendport', 'dart', x._isolateId, x._portId ];
- }
-}
+ // The distance to shift from upper case alphabet Roman letters to lower case.
+ static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0];
-_deserialize(var message) {
- return new _JsDeserializer().deserialize(message);
-}
+ /** Controller to produce KeyEvents for the stream. */
+ final StreamController _controller = new StreamController(sync: true);
+ static const _EVENT_TYPE = 'KeyEvent';
-class _JsDeserializer extends _Deserializer {
+ /**
+ * An enumeration of key identifiers currently part of the W3C draft for DOM3
+ * and their mappings to keyCodes.
+ * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
+ */
+ static const Map<String, int> _keyIdentifier = const {
+ 'Up': KeyCode.UP,
+ 'Down': KeyCode.DOWN,
+ 'Left': KeyCode.LEFT,
+ 'Right': KeyCode.RIGHT,
+ 'Enter': KeyCode.ENTER,
+ 'F1': KeyCode.F1,
+ 'F2': KeyCode.F2,
+ 'F3': KeyCode.F3,
+ 'F4': KeyCode.F4,
+ 'F5': KeyCode.F5,
+ 'F6': KeyCode.F6,
+ 'F7': KeyCode.F7,
+ 'F8': KeyCode.F8,
+ 'F9': KeyCode.F9,
+ 'F10': KeyCode.F10,
+ 'F11': KeyCode.F11,
+ 'F12': KeyCode.F12,
+ 'U+007F': KeyCode.DELETE,
+ 'Home': KeyCode.HOME,
+ 'End': KeyCode.END,
+ 'PageUp': KeyCode.PAGE_UP,
+ 'PageDown': KeyCode.PAGE_DOWN,
+ 'Insert': KeyCode.INSERT
+ };
- static const _UNSPECIFIED = const Object();
+ /** Return a stream for KeyEvents for the specified target. */
+ Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
+ return new _KeyboardEventHandler.initializeAllEventListeners(
+ _type, e).stream;
+ }
- deserializeSendPort(List x) {
- String tag = x[1];
- switch (tag) {
- case 'nativejs':
- num id = x[2];
- return new _JsSendPortSync(id);
- case 'dart':
- num isolateId = x[2];
- num portId = x[3];
- return ReceivePortSync._lookup(isolateId, portId);
- default:
- throw 'Illegal SendPortSync type: $tag';
+ /**
+ * Accessor to the stream associated with a particular KeyboardEvent
+ * EventTarget.
+ *
+ * [forTarget] must be called to initialize this stream to listen to a
+ * particular EventTarget.
+ */
+ Stream<KeyEvent> get stream {
+ if(_target != null) {
+ return _controller.stream;
+ } else {
+ throw new StateError("Not initialized. Call forTarget to access a stream "
+ "initialized with a particular EventTarget.");
}
}
-}
-// The receiver is JS.
-class _JsSendPortSync implements SendPortSync {
-
- final num _id;
- _JsSendPortSync(this._id);
-
- callSync(var message) {
- var serialized = _serialize(message);
- var result = _callPortSync(_id, serialized);
- return _deserialize(result);
+ /**
+ * General constructor, performs basic initialization for our improved
+ * KeyboardEvent controller.
+ */
+ _KeyboardEventHandler(this._type) :
+ _target = null, super(_EVENT_TYPE) {
}
- bool operator==(var other) {
- return (other is _JsSendPortSync) && (_id == other._id);
+ /**
+ * Hook up all event listeners under the covers so we can estimate keycodes
+ * and charcodes when they are not provided.
+ */
+ _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) :
+ super(_EVENT_TYPE) {
+ Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
+ processKeyDown);
+ Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
+ processKeyPress);
+ Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
+ processKeyUp);
}
- int get hashCode => _id;
-}
-
-// TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
-// The receiver is a different Dart isolate, compiled to JS.
-class _RemoteSendPortSync implements SendPortSync {
+ /**
+ * Notify all callback listeners that a KeyEvent of the relevant type has
+ * occurred.
+ */
+ bool _dispatch(KeyEvent event) {
+ if (event.type == _type)
+ _controller.add(event);
+ }
- int _isolateId;
- int _portId;
- _RemoteSendPortSync(this._isolateId, this._portId);
+ /** Determine if caps lock is one of the currently depressed keys. */
+ bool get _capsLockOn =>
+ _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
- callSync(var message) {
- var serialized = _serialize(message);
- var result = _call(_isolateId, _portId, serialized);
- return _deserialize(result);
+ /**
+ * Given the previously recorded keydown key codes, see if we can determine
+ * the keycode of this keypress [event]. (Generally browsers only provide
+ * charCode information for keypress events, but with a little
+ * reverse-engineering, we can also determine the keyCode.) Returns
+ * KeyCode.UNKNOWN if the keycode could not be determined.
+ */
+ int _determineKeyCodeForKeypress(KeyboardEvent event) {
+ // Note: This function is a work in progress. We'll expand this function
+ // once we get more information about other keyboards.
+ for (var prevEvent in _keyDownList) {
+ if (prevEvent._shadowCharCode == event.charCode) {
+ return prevEvent.keyCode;
+ }
+ if ((event.shiftKey || _capsLockOn) && event.charCode >= "A".codeUnits[0]
+ && event.charCode <= "Z".codeUnits[0] && event.charCode +
+ _ROMAN_ALPHABET_OFFSET == prevEvent._shadowCharCode) {
+ return prevEvent.keyCode;
+ }
+ }
+ return KeyCode.UNKNOWN;
}
- static _call(int isolateId, int portId, var message) {
- var target = 'dart-port-$isolateId-$portId';
- // TODO(vsm): Make this re-entrant.
- // TODO(vsm): Set this up set once, on the first call.
- var source = '$target-result';
- var result = null;
- window.on[source].first.then((Event e) {
- result = json.parse(_getPortSyncEventData(e));
- });
- _dispatchEvent(target, [source, message]);
- return result;
+ /**
+ * Given the charater code returned from a keyDown [event], try to ascertain
+ * and return the corresponding charCode for the character that was pressed.
+ * This information is not shown to the user, but used to help polyfill
+ * keypress events.
+ */
+ int _findCharCodeKeyDown(KeyboardEvent event) {
+ if (event.keyLocation == 3) { // Numpad keys.
+ switch (event.keyCode) {
+ case KeyCode.NUM_ZERO:
+ // Even though this function returns _charCodes_, for some cases the
+ // KeyCode == the charCode we want, in which case we use the keycode
+ // constant for readability.
+ return KeyCode.ZERO;
+ case KeyCode.NUM_ONE:
+ return KeyCode.ONE;
+ case KeyCode.NUM_TWO:
+ return KeyCode.TWO;
+ case KeyCode.NUM_THREE:
+ return KeyCode.THREE;
+ case KeyCode.NUM_FOUR:
+ return KeyCode.FOUR;
+ case KeyCode.NUM_FIVE:
+ return KeyCode.FIVE;
+ case KeyCode.NUM_SIX:
+ return KeyCode.SIX;
+ case KeyCode.NUM_SEVEN:
+ return KeyCode.SEVEN;
+ case KeyCode.NUM_EIGHT:
+ return KeyCode.EIGHT;
+ case KeyCode.NUM_NINE:
+ return KeyCode.NINE;
+ case KeyCode.NUM_MULTIPLY:
+ return 42; // Char code for *
+ case KeyCode.NUM_PLUS:
+ return 43; // +
+ case KeyCode.NUM_MINUS:
+ return 45; // -
+ case KeyCode.NUM_PERIOD:
+ return 46; // .
+ case KeyCode.NUM_DIVISION:
+ return 47; // /
+ }
+ } else if (event.keyCode >= 65 && event.keyCode <= 90) {
+ // Set the "char code" for key down as the lower case letter. Again, this
+ // will not show up for the user, but will be helpful in estimating
+ // keyCode locations and other information during the keyPress event.
+ return event.keyCode + _ROMAN_ALPHABET_OFFSET;
+ }
+ switch(event.keyCode) {
+ case KeyCode.SEMICOLON:
+ return KeyCode.FF_SEMICOLON;
+ case KeyCode.EQUALS:
+ return KeyCode.FF_EQUALS;
+ case KeyCode.COMMA:
+ return 44; // Ascii value for ,
+ case KeyCode.DASH:
+ return 45; // -
+ case KeyCode.PERIOD:
+ return 46; // .
+ case KeyCode.SLASH:
+ return 47; // /
+ case KeyCode.APOSTROPHE:
+ return 96; // `
+ case KeyCode.OPEN_SQUARE_BRACKET:
+ return 91; // [
+ case KeyCode.BACKSLASH:
+ return 92; // \
+ case KeyCode.CLOSE_SQUARE_BRACKET:
+ return 93; // ]
+ case KeyCode.SINGLE_QUOTE:
+ return 39; // '
+ }
+ return event.keyCode;
}
- bool operator==(var other) {
- return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
- && (_portId == other._portId);
- }
+ /**
+ * Returns true if the key fires a keypress event in the current browser.
+ */
+ bool _firesKeyPressEvent(KeyEvent event) {
+ if (!Device.isIE && !Device.isWebKit) {
+ return true;
+ }
- int get hashCode => _isolateId >> 16 + _portId;
-}
+ if (Device.userAgent.contains('Mac') && event.altKey) {
+ return KeyCode.isCharacterKey(event.keyCode);
+ }
-// The receiver is in the same Dart isolate, compiled to JS.
-class _LocalSendPortSync implements SendPortSync {
+ // Alt but not AltGr which is represented as Alt+Ctrl.
+ if (event.altKey && !event.ctrlKey) {
+ return false;
+ }
- ReceivePortSync _receivePort;
+ // Saves Ctrl or Alt + key for IE and WebKit, which won't fire keypress.
+ if (!event.shiftKey &&
+ (_keyDownList.last.keyCode == KeyCode.CTRL ||
+ _keyDownList.last.keyCode == KeyCode.ALT ||
+ Device.userAgent.contains('Mac') &&
+ _keyDownList.last.keyCode == KeyCode.META)) {
+ return false;
+ }
- _LocalSendPortSync._internal(this._receivePort);
+ // Some keys with Ctrl/Shift do not issue keypress in WebKit.
+ if (Device.isWebKit && event.ctrlKey && event.shiftKey && (
+ event.keyCode == KeyCode.BACKSLASH ||
+ event.keyCode == KeyCode.OPEN_SQUARE_BRACKET ||
+ event.keyCode == KeyCode.CLOSE_SQUARE_BRACKET ||
+ event.keyCode == KeyCode.TILDE ||
+ event.keyCode == KeyCode.SEMICOLON || event.keyCode == KeyCode.DASH ||
+ event.keyCode == KeyCode.EQUALS || event.keyCode == KeyCode.COMMA ||
+ event.keyCode == KeyCode.PERIOD || event.keyCode == KeyCode.SLASH ||
+ event.keyCode == KeyCode.APOSTROPHE ||
+ event.keyCode == KeyCode.SINGLE_QUOTE)) {
+ return false;
+ }
- callSync(var message) {
- // TODO(vsm): Do a more efficient deep copy.
- var copy = _deserialize(_serialize(message));
- var result = _receivePort._callback(copy);
- return _deserialize(_serialize(result));
- }
+ switch (event.keyCode) {
+ case KeyCode.ENTER:
+ // IE9 does not fire keypress on ENTER.
+ return !Device.isIE;
+ case KeyCode.ESC:
+ return !Device.isWebKit;
+ }
- bool operator==(var other) {
- return (other is _LocalSendPortSync)
- && (_receivePort == other._receivePort);
+ return KeyCode.isCharacterKey(event.keyCode);
}
- int get hashCode => _receivePort.hashCode;
-}
-
-// TODO(vsm): Move this to dart:isolate. This will take some
-// refactoring as there are dependences here on the DOM. Users
-// interact with this class (or interface if we change it) directly -
-// new ReceivePortSync. I think most of the DOM logic could be
-// delayed until the corresponding SendPort is registered on the
-// window.
-
-// A Dart ReceivePortSync (tagged 'dart' when serialized) is
-// identifiable / resolvable by the combination of its isolateid and
-// portid. When a corresponding SendPort is used within the same
-// isolate, the _portMap below can be used to obtain the
-// ReceivePortSync directly. Across isolates (or from JS), an
-// EventListener can be used to communicate with the port indirectly.
-class ReceivePortSync {
-
- static Map<int, ReceivePortSync> _portMap;
- static int _portIdCount;
- static int _cachedIsolateId;
-
- num _portId;
- Function _callback;
- StreamSubscription _portSubscription;
-
- ReceivePortSync() {
- if (_portIdCount == null) {
- _portIdCount = 0;
- _portMap = new Map<int, ReceivePortSync>();
+ /**
+ * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
+ * Opera all use).
+ */
+ int _normalizeKeyCodes(KeyboardEvent event) {
+ // Note: This may change once we get input about non-US keyboards.
+ if (Device.isFirefox) {
+ switch(event.keyCode) {
+ case KeyCode.FF_EQUALS:
+ return KeyCode.EQUALS;
+ case KeyCode.FF_SEMICOLON:
+ return KeyCode.SEMICOLON;
+ case KeyCode.MAC_FF_META:
+ return KeyCode.META;
+ case KeyCode.WIN_KEY_FF_LINUX:
+ return KeyCode.WIN_KEY;
+ }
}
- _portId = _portIdCount++;
- _portMap[_portId] = this;
+ return event.keyCode;
}
- static int get _isolateId {
- // TODO(vsm): Make this coherent with existing isolate code.
- if (_cachedIsolateId == null) {
- _cachedIsolateId = _getNewIsolateId();
+ /** Handle keydown events. */
+ void processKeyDown(KeyboardEvent e) {
+ // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
+ // before we've caught a key-up event. If the last-key was one of these
+ // we reset the state.
+ if (_keyDownList.length > 0 &&
+ (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey ||
+ _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey ||
+ Device.userAgent.contains('Mac') &&
+ _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) {
+ _keyDownList.clear();
}
- return _cachedIsolateId;
- }
-
- static String _getListenerName(isolateId, portId) =>
- 'dart-port-$isolateId-$portId';
- String get _listenerName => _getListenerName(_isolateId, _portId);
- void receive(callback(var message)) {
- _callback = callback;
- if (_portSubscription == null) {
- _portSubscription = window.on[_listenerName].listen((Event e) {
- var data = json.parse(_getPortSyncEventData(e));
- var replyTo = data[0];
- var message = _deserialize(data[1]);
- var result = _callback(message);
- _dispatchEvent(replyTo, _serialize(result));
- });
+ var event = new KeyEvent(e);
+ event._shadowKeyCode = _normalizeKeyCodes(event);
+ // Technically a "keydown" event doesn't have a charCode. This is
+ // calculated nonetheless to provide us with more information in giving
+ // as much information as possible on keypress about keycode and also
+ // charCode.
+ event._shadowCharCode = _findCharCodeKeyDown(event);
+ if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode &&
+ !_firesKeyPressEvent(event)) {
+ // Some browsers have quirks not firing keypress events where all other
+ // browsers do. This makes them more consistent.
+ processKeyPress(e);
}
+ _keyDownList.add(event);
+ _dispatch(event);
}
- void close() {
- _portMap.remove(_portId);
- if (_portSubscription != null) _portSubscription.cancel();
- }
+ /** Handle keypress events. */
+ void processKeyPress(KeyboardEvent event) {
+ var e = new KeyEvent(event);
+ // IE reports the character code in the keyCode field for keypress events.
+ // There are two exceptions however, Enter and Escape.
+ if (Device.isIE) {
+ if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) {
+ e._shadowCharCode = 0;
+ } else {
+ e._shadowCharCode = e.keyCode;
+ }
+ } else if (Device.isOpera) {
+ // Opera reports the character code in the keyCode field.
+ e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0;
+ }
+ // Now we guestimate about what the keycode is that was actually
+ // pressed, given previous keydown information.
+ e._shadowKeyCode = _determineKeyCodeForKeypress(e);
- SendPortSync toSendPort() {
- return new _LocalSendPortSync._internal(this);
+ // Correct the key value for certain browser-specific quirks.
+ if (e._shadowKeyIdentifier != null &&
+ _keyIdentifier.containsKey(e._shadowKeyIdentifier)) {
+ // This is needed for Safari Windows because it currently doesn't give a
+ // keyCode/which for non printable keys.
+ e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier];
+ }
+ e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
+ _dispatch(e);
}
- static SendPortSync _lookup(int isolateId, int portId) {
- if (isolateId == _isolateId) {
- return _portMap[portId].toSendPort();
- } else {
- return new _RemoteSendPortSync(isolateId, portId);
+ /** Handle keyup events. */
+ void processKeyUp(KeyboardEvent event) {
+ var e = new KeyEvent(event);
+ KeyboardEvent toRemove = null;
+ for (var key in _keyDownList) {
+ if (key.keyCode == e.keyCode) {
+ toRemove = key;
+ }
+ }
+ if (toRemove != null) {
+ _keyDownList.removeWhere((element) => element == toRemove);
+ } else if (_keyDownList.length > 0) {
+ // This happens when we've reached some international keyboard case we
+ // haven't accounted for or we haven't correctly eliminated all browser
+ // inconsistencies. Filing bugs on when this is reached is welcome!
+ _keyDownList.removeLast();
}
+ _dispatch(e);
}
}
-get _isolateId => ReceivePortSync._isolateId;
-void _dispatchEvent(String receiver, var message) {
- var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
- detail: json.stringify(message));
- window.dispatchEvent(event);
-}
+/**
+ * Records KeyboardEvents that occur on a particular element, and provides a
+ * stream of outgoing KeyEvents with cross-browser consistent keyCode and
+ * charCode values despite the fact that a multitude of browsers that have
+ * varying keyboard default behavior.
+ *
+ * Example usage:
+ *
+ * KeyboardEventStream.onKeyDown(document.body).listen(
+ * keydownHandlerTest);
+ *
+ * This class is very much a work in progress, and we'd love to get information
+ * on how we can make this class work with as many international keyboards as
+ * possible. Bugs welcome!
+ */
+class KeyboardEventStream {
-String _getPortSyncEventData(CustomEvent event) => event.detail;
+ /** Named constructor to produce a stream for onKeyPress events. */
+ static Stream<KeyEvent> onKeyPress(EventTarget target) =>
+ new _KeyboardEventHandler('keypress').forTarget(target);
+
+ /** Named constructor to produce a stream for onKeyUp events. */
+ static Stream<KeyEvent> onKeyUp(EventTarget target) =>
+ new _KeyboardEventHandler('keyup').forTarget(target);
+
+ /** Named constructor to produce a stream for onKeyDown events. */
+ static Stream<KeyEvent> onKeyDown(EventTarget target) =>
+ new _KeyboardEventHandler('keydown').forTarget(target);
+}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
@@ -29911,1400 +29615,793 @@ abstract class KeyName {
static const String SOFT_2 = "Soft2";
/** The Soft3 key */
- static const String SOFT_3 = "Soft3";
-
- /** The Soft4 key */
- static const String SOFT_4 = "Soft4";
-
- /** The Stop key */
- static const String STOP = "Stop";
-
- /** The Subtract key */
- static const String SUBTRACT = "Subtract";
-
- /** The Symbol Lock key */
- static const String SYMBOL_LOCK = "SymbolLock";
-
- /** The Up Arrow key */
- static const String UP = "Up";
-
- /** The diagonal Up-Left Arrow key */
- static const String UP_LEFT = "UpLeft";
-
- /** The diagonal Up-Right Arrow key */
- static const String UP_RIGHT = "UpRight";
-
- /** The Undo key */
- static const String UNDO = "Undo";
-
- /** The Volume Down key */
- static const String VOLUME_DOWN = "VolumeDown";
-
- /** The Volume Mute key */
- static const String VOLUMN_MUTE = "VolumeMute";
-
- /** The Volume Up key */
- static const String VOLUMN_UP = "VolumeUp";
-
- /** The Windows Logo key */
- static const String WIN = "Win";
-
- /** The Zoom key */
- static const String ZOOM = "Zoom";
-
- /**
- * The Backspace (Back) key. Note: This key value shall be also used for the
- * key labeled 'delete' MacOS keyboards when not modified by the 'Fn' key
- */
- static const String BACKSPACE = "Backspace";
-
- /** The Horizontal Tabulation (Tab) key */
- static const String TAB = "Tab";
-
- /** The Cancel key */
- static const String CANCEL = "Cancel";
-
- /** The Escape (Esc) key */
- static const String ESC = "Esc";
-
- /** The Space (Spacebar) key: */
- static const String SPACEBAR = "Spacebar";
-
- /**
- * The Delete (Del) Key. Note: This key value shall be also used for the key
- * labeled 'delete' MacOS keyboards when modified by the 'Fn' key
- */
- static const String DEL = "Del";
-
- /** The Combining Grave Accent (Greek Varia, Dead Grave) key */
- static const String DEAD_GRAVE = "DeadGrave";
-
- /**
- * The Combining Acute Accent (Stress Mark, Greek Oxia, Tonos, Dead Eacute)
- * key
- */
- static const String DEAD_EACUTE = "DeadEacute";
-
- /** The Combining Circumflex Accent (Hat, Dead Circumflex) key */
- static const String DEAD_CIRCUMFLEX = "DeadCircumflex";
-
- /** The Combining Tilde (Dead Tilde) key */
- static const String DEAD_TILDE = "DeadTilde";
-
- /** The Combining Macron (Long, Dead Macron) key */
- static const String DEAD_MACRON = "DeadMacron";
-
- /** The Combining Breve (Short, Dead Breve) key */
- static const String DEAD_BREVE = "DeadBreve";
-
- /** The Combining Dot Above (Derivative, Dead Above Dot) key */
- static const String DEAD_ABOVE_DOT = "DeadAboveDot";
-
- /**
- * The Combining Diaeresis (Double Dot Abode, Umlaut, Greek Dialytika,
- * Double Derivative, Dead Diaeresis) key
- */
- static const String DEAD_UMLAUT = "DeadUmlaut";
-
- /** The Combining Ring Above (Dead Above Ring) key */
- static const String DEAD_ABOVE_RING = "DeadAboveRing";
-
- /** The Combining Double Acute Accent (Dead Doubleacute) key */
- static const String DEAD_DOUBLEACUTE = "DeadDoubleacute";
-
- /** The Combining Caron (Hacek, V Above, Dead Caron) key */
- static const String DEAD_CARON = "DeadCaron";
-
- /** The Combining Cedilla (Dead Cedilla) key */
- static const String DEAD_CEDILLA = "DeadCedilla";
-
- /** The Combining Ogonek (Nasal Hook, Dead Ogonek) key */
- static const String DEAD_OGONEK = "DeadOgonek";
-
- /**
- * The Combining Greek Ypogegrammeni (Greek Non-Spacing Iota Below, Iota
- * Subscript, Dead Iota) key
- */
- static const String DEAD_IOTA = "DeadIota";
+ static const String SOFT_3 = "Soft3";
- /**
- * The Combining Katakana-Hiragana Voiced Sound Mark (Dead Voiced Sound) key
- */
- static const String DEAD_VOICED_SOUND = "DeadVoicedSound";
+ /** The Soft4 key */
+ static const String SOFT_4 = "Soft4";
- /**
- * The Combining Katakana-Hiragana Semi-Voiced Sound Mark (Dead Semivoiced
- * Sound) key
- */
- static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound";
+ /** The Stop key */
+ static const String STOP = "Stop";
- /**
- * Key value used when an implementation is unable to identify another key
- * value, due to either hardware, platform, or software constraints
- */
- static const String UNIDENTIFIED = "Unidentified";
-}
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
+ /** The Subtract key */
+ static const String SUBTRACT = "Subtract";
+ /** The Symbol Lock key */
+ static const String SYMBOL_LOCK = "SymbolLock";
-/**
- * Internal class that does the actual calculations to determine keyCode and
- * charCode for keydown, keypress, and keyup events for all browsers.
- */
-class _KeyboardEventHandler extends EventStreamProvider<KeyEvent> {
- // This code inspired by Closure's KeyHandling library.
- // http://closure-library.googlecode.com/svn/docs/closure_goog_events_keyhandler.js.source.html
+ /** The Up Arrow key */
+ static const String UP = "Up";
- /**
- * The set of keys that have been pressed down without seeing their
- * corresponding keyup event.
- */
- final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
+ /** The diagonal Up-Left Arrow key */
+ static const String UP_LEFT = "UpLeft";
- /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
- final String _type;
+ /** The diagonal Up-Right Arrow key */
+ static const String UP_RIGHT = "UpRight";
- /** The element we are watching for events to happen on. */
- final EventTarget _target;
+ /** The Undo key */
+ static const String UNDO = "Undo";
- // The distance to shift from upper case alphabet Roman letters to lower case.
- static final int _ROMAN_ALPHABET_OFFSET = "a".codeUnits[0] - "A".codeUnits[0];
+ /** The Volume Down key */
+ static const String VOLUME_DOWN = "VolumeDown";
- /** Controller to produce KeyEvents for the stream. */
- final StreamController _controller = new StreamController(sync: true);
+ /** The Volume Mute key */
+ static const String VOLUMN_MUTE = "VolumeMute";
- static const _EVENT_TYPE = 'KeyEvent';
+ /** The Volume Up key */
+ static const String VOLUMN_UP = "VolumeUp";
- /**
- * An enumeration of key identifiers currently part of the W3C draft for DOM3
- * and their mappings to keyCodes.
- * http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set
- */
- static const Map<String, int> _keyIdentifier = const {
- 'Up': KeyCode.UP,
- 'Down': KeyCode.DOWN,
- 'Left': KeyCode.LEFT,
- 'Right': KeyCode.RIGHT,
- 'Enter': KeyCode.ENTER,
- 'F1': KeyCode.F1,
- 'F2': KeyCode.F2,
- 'F3': KeyCode.F3,
- 'F4': KeyCode.F4,
- 'F5': KeyCode.F5,
- 'F6': KeyCode.F6,
- 'F7': KeyCode.F7,
- 'F8': KeyCode.F8,
- 'F9': KeyCode.F9,
- 'F10': KeyCode.F10,
- 'F11': KeyCode.F11,
- 'F12': KeyCode.F12,
- 'U+007F': KeyCode.DELETE,
- 'Home': KeyCode.HOME,
- 'End': KeyCode.END,
- 'PageUp': KeyCode.PAGE_UP,
- 'PageDown': KeyCode.PAGE_DOWN,
- 'Insert': KeyCode.INSERT
- };
+ /** The Windows Logo key */
+ static const String WIN = "Win";
- /** Return a stream for KeyEvents for the specified target. */
- Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
- return new _KeyboardEventHandler.initializeAllEventListeners(
- _type, e).stream;
- }
+ /** The Zoom key */
+ static const String ZOOM = "Zoom";
/**
- * Accessor to the stream associated with a particular KeyboardEvent
- * EventTarget.
- *
- * [forTarget] must be called to initialize this stream to listen to a
- * particular EventTarget.
+ * The Backspace (Back) key. Note: This key value shall be also used for the
+ * key labeled 'delete' MacOS keyboards when not modified by the 'Fn' key
*/
- Stream<KeyEvent> get stream {
- if(_target != null) {
- return _controller.stream;
- } else {
- throw new StateError("Not initialized. Call forTarget to access a stream "
- "initialized with a particular EventTarget.");
- }
- }
+ static const String BACKSPACE = "Backspace";
- /**
- * General constructor, performs basic initialization for our improved
- * KeyboardEvent controller.
- */
- _KeyboardEventHandler(this._type) :
- _target = null, super(_EVENT_TYPE) {
- }
+ /** The Horizontal Tabulation (Tab) key */
+ static const String TAB = "Tab";
- /**
- * Hook up all event listeners under the covers so we can estimate keycodes
- * and charcodes when they are not provided.
- */
- _KeyboardEventHandler.initializeAllEventListeners(this._type, this._target) :
- super(_EVENT_TYPE) {
- Element.keyDownEvent.forTarget(_target, useCapture: true).listen(
- processKeyDown);
- Element.keyPressEvent.forTarget(_target, useCapture: true).listen(
- processKeyPress);
- Element.keyUpEvent.forTarget(_target, useCapture: true).listen(
- processKeyUp);
- }
+ /** The Cancel key */
+ static const String CANCEL = "Cancel";
- /**
- * Notify all callback listeners that a KeyEvent of the relevant type has
- * occurred.
- */
- bool _dispatch(KeyEvent event) {
- if (event.type == _type)
- _controller.add(event);
- }
+ /** The Escape (Esc) key */
+ static const String ESC = "Esc";
- /** Determine if caps lock is one of the currently depressed keys. */
- bool get _capsLockOn =>
- _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
+ /** The Space (Spacebar) key: */
+ static const String SPACEBAR = "Spacebar";
/**
- * Given the previously recorded keydown key codes, see if we can determine
- * the keycode of this keypress [event]. (Generally browsers only provide
- * charCode information for keypress events, but with a little
- * reverse-engineering, we can also determine the keyCode.) Returns
- * KeyCode.UNKNOWN if the keycode could not be determined.
+ * The Delete (Del) Key. Note: This key value shall be also used for the key
+ * labeled 'delete' MacOS keyboards when modified by the 'Fn' key
*/
- int _determineKeyCodeForKeypress(KeyboardEvent event) {
- // Note: This function is a work in progress. We'll expand this function
- // once we get more information about other keyboards.
- for (var prevEvent in _keyDownList) {
- if (prevEvent._shadowCharCode == event.charCode) {
- return prevEvent.keyCode;
- }
- if ((event.shiftKey || _capsLockOn) && event.charCode >= "A".codeUnits[0]
- && event.charCode <= "Z".codeUnits[0] && event.charCode +
- _ROMAN_ALPHABET_OFFSET == prevEvent._shadowCharCode) {
- return prevEvent.keyCode;
- }
- }
- return KeyCode.UNKNOWN;
- }
+ static const String DEL = "Del";
- /**
- * Given the charater code returned from a keyDown [event], try to ascertain
- * and return the corresponding charCode for the character that was pressed.
- * This information is not shown to the user, but used to help polyfill
- * keypress events.
- */
- int _findCharCodeKeyDown(KeyboardEvent event) {
- if (event.keyLocation == 3) { // Numpad keys.
- switch (event.keyCode) {
- case KeyCode.NUM_ZERO:
- // Even though this function returns _charCodes_, for some cases the
- // KeyCode == the charCode we want, in which case we use the keycode
- // constant for readability.
- return KeyCode.ZERO;
- case KeyCode.NUM_ONE:
- return KeyCode.ONE;
- case KeyCode.NUM_TWO:
- return KeyCode.TWO;
- case KeyCode.NUM_THREE:
- return KeyCode.THREE;
- case KeyCode.NUM_FOUR:
- return KeyCode.FOUR;
- case KeyCode.NUM_FIVE:
- return KeyCode.FIVE;
- case KeyCode.NUM_SIX:
- return KeyCode.SIX;
- case KeyCode.NUM_SEVEN:
- return KeyCode.SEVEN;
- case KeyCode.NUM_EIGHT:
- return KeyCode.EIGHT;
- case KeyCode.NUM_NINE:
- return KeyCode.NINE;
- case KeyCode.NUM_MULTIPLY:
- return 42; // Char code for *
- case KeyCode.NUM_PLUS:
- return 43; // +
- case KeyCode.NUM_MINUS:
- return 45; // -
- case KeyCode.NUM_PERIOD:
- return 46; // .
- case KeyCode.NUM_DIVISION:
- return 47; // /
- }
- } else if (event.keyCode >= 65 && event.keyCode <= 90) {
- // Set the "char code" for key down as the lower case letter. Again, this
- // will not show up for the user, but will be helpful in estimating
- // keyCode locations and other information during the keyPress event.
- return event.keyCode + _ROMAN_ALPHABET_OFFSET;
- }
- switch(event.keyCode) {
- case KeyCode.SEMICOLON:
- return KeyCode.FF_SEMICOLON;
- case KeyCode.EQUALS:
- return KeyCode.FF_EQUALS;
- case KeyCode.COMMA:
- return 44; // Ascii value for ,
- case KeyCode.DASH:
- return 45; // -
- case KeyCode.PERIOD:
- return 46; // .
- case KeyCode.SLASH:
- return 47; // /
- case KeyCode.APOSTROPHE:
- return 96; // `
- case KeyCode.OPEN_SQUARE_BRACKET:
- return 91; // [
- case KeyCode.BACKSLASH:
- return 92; // \
- case KeyCode.CLOSE_SQUARE_BRACKET:
- return 93; // ]
- case KeyCode.SINGLE_QUOTE:
- return 39; // '
- }
- return event.keyCode;
- }
+ /** The Combining Grave Accent (Greek Varia, Dead Grave) key */
+ static const String DEAD_GRAVE = "DeadGrave";
/**
- * Returns true if the key fires a keypress event in the current browser.
+ * The Combining Acute Accent (Stress Mark, Greek Oxia, Tonos, Dead Eacute)
+ * key
*/
- bool _firesKeyPressEvent(KeyEvent event) {
- if (!Device.isIE && !Device.isWebKit) {
- return true;
- }
+ static const String DEAD_EACUTE = "DeadEacute";
- if (Device.userAgent.contains('Mac') && event.altKey) {
- return KeyCode.isCharacterKey(event.keyCode);
- }
+ /** The Combining Circumflex Accent (Hat, Dead Circumflex) key */
+ static const String DEAD_CIRCUMFLEX = "DeadCircumflex";
- // Alt but not AltGr which is represented as Alt+Ctrl.
- if (event.altKey && !event.ctrlKey) {
- return false;
- }
+ /** The Combining Tilde (Dead Tilde) key */
+ static const String DEAD_TILDE = "DeadTilde";
- // Saves Ctrl or Alt + key for IE and WebKit, which won't fire keypress.
- if (!event.shiftKey &&
- (_keyDownList.last.keyCode == KeyCode.CTRL ||
- _keyDownList.last.keyCode == KeyCode.ALT ||
- Device.userAgent.contains('Mac') &&
- _keyDownList.last.keyCode == KeyCode.META)) {
- return false;
- }
+ /** The Combining Macron (Long, Dead Macron) key */
+ static const String DEAD_MACRON = "DeadMacron";
- // Some keys with Ctrl/Shift do not issue keypress in WebKit.
- if (Device.isWebKit && event.ctrlKey && event.shiftKey && (
- event.keyCode == KeyCode.BACKSLASH ||
- event.keyCode == KeyCode.OPEN_SQUARE_BRACKET ||
- event.keyCode == KeyCode.CLOSE_SQUARE_BRACKET ||
- event.keyCode == KeyCode.TILDE ||
- event.keyCode == KeyCode.SEMICOLON || event.keyCode == KeyCode.DASH ||
- event.keyCode == KeyCode.EQUALS || event.keyCode == KeyCode.COMMA ||
- event.keyCode == KeyCode.PERIOD || event.keyCode == KeyCode.SLASH ||
- event.keyCode == KeyCode.APOSTROPHE ||
- event.keyCode == KeyCode.SINGLE_QUOTE)) {
- return false;
- }
+ /** The Combining Breve (Short, Dead Breve) key */
+ static const String DEAD_BREVE = "DeadBreve";
- switch (event.keyCode) {
- case KeyCode.ENTER:
- // IE9 does not fire keypress on ENTER.
- return !Device.isIE;
- case KeyCode.ESC:
- return !Device.isWebKit;
- }
+ /** The Combining Dot Above (Derivative, Dead Above Dot) key */
+ static const String DEAD_ABOVE_DOT = "DeadAboveDot";
- return KeyCode.isCharacterKey(event.keyCode);
- }
+ /**
+ * The Combining Diaeresis (Double Dot Abode, Umlaut, Greek Dialytika,
+ * Double Derivative, Dead Diaeresis) key
+ */
+ static const String DEAD_UMLAUT = "DeadUmlaut";
+
+ /** The Combining Ring Above (Dead Above Ring) key */
+ static const String DEAD_ABOVE_RING = "DeadAboveRing";
+
+ /** The Combining Double Acute Accent (Dead Doubleacute) key */
+ static const String DEAD_DOUBLEACUTE = "DeadDoubleacute";
+
+ /** The Combining Caron (Hacek, V Above, Dead Caron) key */
+ static const String DEAD_CARON = "DeadCaron";
+
+ /** The Combining Cedilla (Dead Cedilla) key */
+ static const String DEAD_CEDILLA = "DeadCedilla";
+
+ /** The Combining Ogonek (Nasal Hook, Dead Ogonek) key */
+ static const String DEAD_OGONEK = "DeadOgonek";
/**
- * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
- * Opera all use).
+ * The Combining Greek Ypogegrammeni (Greek Non-Spacing Iota Below, Iota
+ * Subscript, Dead Iota) key
*/
- int _normalizeKeyCodes(KeyboardEvent event) {
- // Note: This may change once we get input about non-US keyboards.
- if (Device.isFirefox) {
- switch(event.keyCode) {
- case KeyCode.FF_EQUALS:
- return KeyCode.EQUALS;
- case KeyCode.FF_SEMICOLON:
- return KeyCode.SEMICOLON;
- case KeyCode.MAC_FF_META:
- return KeyCode.META;
- case KeyCode.WIN_KEY_FF_LINUX:
- return KeyCode.WIN_KEY;
- }
- }
- return event.keyCode;
- }
+ static const String DEAD_IOTA = "DeadIota";
- /** Handle keydown events. */
- void processKeyDown(KeyboardEvent e) {
- // Ctrl-Tab and Alt-Tab can cause the focus to be moved to another window
- // before we've caught a key-up event. If the last-key was one of these
- // we reset the state.
- if (_keyDownList.length > 0 &&
- (_keyDownList.last.keyCode == KeyCode.CTRL && !e.ctrlKey ||
- _keyDownList.last.keyCode == KeyCode.ALT && !e.altKey ||
- Device.userAgent.contains('Mac') &&
- _keyDownList.last.keyCode == KeyCode.META && !e.metaKey)) {
- _keyDownList.clear();
- }
+ /**
+ * The Combining Katakana-Hiragana Voiced Sound Mark (Dead Voiced Sound) key
+ */
+ static const String DEAD_VOICED_SOUND = "DeadVoicedSound";
- var event = new KeyEvent(e);
- event._shadowKeyCode = _normalizeKeyCodes(event);
- // Technically a "keydown" event doesn't have a charCode. This is
- // calculated nonetheless to provide us with more information in giving
- // as much information as possible on keypress about keycode and also
- // charCode.
- event._shadowCharCode = _findCharCodeKeyDown(event);
- if (_keyDownList.length > 0 && event.keyCode != _keyDownList.last.keyCode &&
- !_firesKeyPressEvent(event)) {
- // Some browsers have quirks not firing keypress events where all other
- // browsers do. This makes them more consistent.
- processKeyPress(e);
- }
- _keyDownList.add(event);
- _dispatch(event);
+ /**
+ * The Combining Katakana-Hiragana Semi-Voiced Sound Mark (Dead Semivoiced
+ * Sound) key
+ */
+ static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound";
+
+ /**
+ * Key value used when an implementation is unable to identify another key
+ * value, due to either hardware, platform, or software constraints
+ */
+ static const String UNIDENTIFIED = "Unidentified";
+}
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+
+/**
+ * A utility class for representing two-dimensional positions.
+ */
+class Point {
+ final num x;
+ final num y;
+
+ const Point([num x = 0, num y = 0]): x = x, y = y;
+
+ String toString() => '($x, $y)';
+
+ bool operator ==(other) {
+ if (other is !Point) return false;
+ return x == other.x && y == other.y;
}
- /** Handle keypress events. */
- void processKeyPress(KeyboardEvent event) {
- var e = new KeyEvent(event);
- // IE reports the character code in the keyCode field for keypress events.
- // There are two exceptions however, Enter and Escape.
- if (Device.isIE) {
- if (e.keyCode == KeyCode.ENTER || e.keyCode == KeyCode.ESC) {
- e._shadowCharCode = 0;
- } else {
- e._shadowCharCode = e.keyCode;
- }
- } else if (Device.isOpera) {
- // Opera reports the character code in the keyCode field.
- e._shadowCharCode = KeyCode.isCharacterKey(e.keyCode) ? e.keyCode : 0;
- }
- // Now we guestimate about what the keycode is that was actually
- // pressed, given previous keydown information.
- e._shadowKeyCode = _determineKeyCodeForKeypress(e);
+ int get hashCode => JenkinsSmiHash.hash2(x.hashCode, y.hashCode);
- // Correct the key value for certain browser-specific quirks.
- if (e._shadowKeyIdentifier != null &&
- _keyIdentifier.containsKey(e._shadowKeyIdentifier)) {
- // This is needed for Safari Windows because it currently doesn't give a
- // keyCode/which for non printable keys.
- e._shadowKeyCode = _keyIdentifier[e._shadowKeyIdentifier];
- }
- e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
- _dispatch(e);
+ Point operator +(Point other) {
+ return new Point(x + other.x, y + other.y);
}
- /** Handle keyup events. */
- void processKeyUp(KeyboardEvent event) {
- var e = new KeyEvent(event);
- KeyboardEvent toRemove = null;
- for (var key in _keyDownList) {
- if (key.keyCode == e.keyCode) {
- toRemove = key;
- }
- }
- if (toRemove != null) {
- _keyDownList.removeWhere((element) => element == toRemove);
- } else if (_keyDownList.length > 0) {
- // This happens when we've reached some international keyboard case we
- // haven't accounted for or we haven't correctly eliminated all browser
- // inconsistencies. Filing bugs on when this is reached is welcome!
- _keyDownList.removeLast();
- }
- _dispatch(e);
+ Point operator -(Point other) {
+ return new Point(x - other.x, y - other.y);
}
-}
+ Point operator *(num factor) {
+ return new Point(x * factor, y * factor);
+ }
-/**
- * Records KeyboardEvents that occur on a particular element, and provides a
- * stream of outgoing KeyEvents with cross-browser consistent keyCode and
- * charCode values despite the fact that a multitude of browsers that have
- * varying keyboard default behavior.
- *
- * Example usage:
- *
- * KeyboardEventStream.onKeyDown(document.body).listen(
- * keydownHandlerTest);
- *
- * This class is very much a work in progress, and we'd love to get information
- * on how we can make this class work with as many international keyboards as
- * possible. Bugs welcome!
- */
-class KeyboardEventStream {
+ /**
+ * Get the straight line (Euclidean) distance between the origin (0, 0) and
+ * this point.
+ */
+ num get magnitude => sqrt(x * x + y * y);
- /** Named constructor to produce a stream for onKeyPress events. */
- static Stream<KeyEvent> onKeyPress(EventTarget target) =>
- new _KeyboardEventHandler('keypress').forTarget(target);
+ /**
+ * Returns the distance between two points.
+ */
+ double distanceTo(Point other) {
+ var dx = x - other.x;
+ var dy = y - other.y;
+ return sqrt(dx * dx + dy * dy);
+ }
+
+ /**
+ * Returns the squared distance between two points.
+ *
+ * Squared distances can be used for comparisons when the actual value is not
+ * required.
+ */
+ num squaredDistanceTo(Point other) {
+ var dx = x - other.x;
+ var dy = y - other.y;
+ return dx * dx + dy * dy;
+ }
- /** Named constructor to produce a stream for onKeyUp events. */
- static Stream<KeyEvent> onKeyUp(EventTarget target) =>
- new _KeyboardEventHandler('keyup').forTarget(target);
+ Point ceil() => new Point(x.ceil(), y.ceil());
+ Point floor() => new Point(x.floor(), y.floor());
+ Point round() => new Point(x.round(), y.round());
- /** Named constructor to produce a stream for onKeyDown events. */
- static Stream<KeyEvent> onKeyDown(EventTarget target) =>
- new _KeyboardEventHandler('keydown').forTarget(target);
+ /**
+ * Truncates x and y to integers and returns the result as a new point.
+ */
+ Point toInt() => new Point(x.toInt(), y.toInt());
}
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
-typedef void _MicrotaskCallback();
-
/**
- * This class attempts to invoke a callback as soon as the current event stack
- * unwinds, but before the browser repaints.
+ * Contains the set of standard values returned by HTMLDocument.getReadyState.
*/
-abstract class _MicrotaskScheduler {
- bool _nextMicrotaskFrameScheduled = false;
- final _MicrotaskCallback _callback;
-
- _MicrotaskScheduler(this._callback);
-
- /**
- * Creates the best possible microtask scheduler for the current platform.
- */
- factory _MicrotaskScheduler.best(_MicrotaskCallback callback) {
- if (Window._supportsSetImmediate) {
- return new _SetImmediateScheduler(callback);
- } else if (MutationObserver.supported) {
- return new _MutationObserverScheduler(callback);
- }
- return new _PostMessageScheduler(callback);
- }
-
+abstract class ReadyState {
/**
- * Schedules a microtask callback if one has not been scheduled already.
+ * Indicates the document is still loading and parsing.
*/
- void maybeSchedule() {
- if (this._nextMicrotaskFrameScheduled) {
- return;
- }
- this._nextMicrotaskFrameScheduled = true;
- this._schedule();
- }
+ static const String LOADING = "loading";
/**
- * Does the actual scheduling of the callback.
+ * Indicates the document is finished parsing but is still loading
+ * subresources.
*/
- void _schedule();
+ static const String INTERACTIVE = "interactive";
/**
- * Handles the microtask callback and forwards it if necessary.
+ * Indicates the document and all subresources have been loaded.
*/
- void _onCallback() {
- // Ignore spurious messages.
- if (!_nextMicrotaskFrameScheduled) {
- return;
- }
- _nextMicrotaskFrameScheduled = false;
- this._callback();
- }
+ static const String COMPLETE = "complete";
}
+// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
/**
- * Scheduler which uses window.postMessage to schedule events.
+ * A base class for representing two-dimensional rectangles. This will hopefully
+ * be moved merged with the dart:math Rect.
*/
-class _PostMessageScheduler extends _MicrotaskScheduler {
- const _MICROTASK_MESSAGE = "DART-MICROTASK";
+// TODO(efortuna): Merge with Math rect after finalizing with Florian.
+abstract class RectBase {
+ // Not used, but keeps the VM from complaining about Rect having a const
+ // constructor and this one not.
+ const RectBase();
- _PostMessageScheduler(_MicrotaskCallback callback): super(callback) {
- // Messages from other windows do not cause a security risk as
- // all we care about is that _handleMessage is called
- // after the current event loop is unwound and calling the function is
- // a noop when zero requests are pending.
- window.onMessage.listen(this._handleMessage);
- }
+ num get left;
+ num get top;
+ num get width;
+ num get height;
- void _schedule() {
- window.postMessage(_MICROTASK_MESSAGE, "*");
+ num get right => left + width;
+ num get bottom => top + height;
+
+ // NOTE! All code below should be common with Rect.
+
+ String toString() {
+ return '($left, $top, $width, $height)';
}
- void _handleMessage(e) {
- this._onCallback();
+ bool operator ==(other) {
+ if (other is !Rect) return false;
+ return left == other.left && top == other.top && width == other.width &&
+ height == other.height;
}
-}
-/**
- * Scheduler which uses a MutationObserver to schedule events.
- */
-class _MutationObserverScheduler extends _MicrotaskScheduler {
- MutationObserver _observer;
- Element _dummy;
+ int get hashCode => JenkinsSmiHash.hash4(left.hashCode, top.hashCode,
+ width.hashCode, height.hashCode);
- _MutationObserverScheduler(_MicrotaskCallback callback): super(callback) {
- // Mutation events get fired as soon as the current event stack is unwound
- // so we just make a dummy event and listen for that.
- _observer = new MutationObserver(this._handleMutation);
- _dummy = new DivElement();
- _observer.observe(_dummy, attributes: true);
- }
+ /**
+ * Computes the intersection of this rectangle and the rectangle parameter.
+ * Returns null if there is no intersection.
+ */
+ Rect intersection(Rect rect) {
+ var x0 = max(left, rect.left);
+ var x1 = min(left + width, rect.left + rect.width);
- void _schedule() {
- // Toggle it to trigger the mutation event.
- _dummy.hidden = !_dummy.hidden;
- }
+ if (x0 <= x1) {
+ var y0 = max(top, rect.top);
+ var y1 = min(top + height, rect.top + rect.height);
- _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
- this._onCallback();
+ if (y0 <= y1) {
+ return new Rect(x0, y0, x1 - x0, y1 - y0);
+ }
+ }
+ return null;
}
-}
-/**
- * Scheduler which uses window.setImmediate to schedule events.
- */
-class _SetImmediateScheduler extends _MicrotaskScheduler {
- _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
- void _schedule() {
- window._setImmediate(_handleImmediate);
+ /**
+ * Returns whether a rectangle intersects this rectangle.
+ */
+ bool intersects(Rect other) {
+ return (left <= other.left + other.width && other.left <= left + width &&
+ top <= other.top + other.height && other.top <= top + height);
}
- void _handleImmediate() {
- this._onCallback();
- }
-}
+ /**
+ * Returns a new rectangle which completely contains this rectangle and the
+ * input rectangle.
+ */
+ Rect union(Rect rect) {
+ var right = max(this.left + this.width, rect.left + rect.width);
+ var bottom = max(this.top + this.height, rect.top + rect.height);
-List<TimeoutHandler> _pendingMicrotasks;
-_MicrotaskScheduler _microtaskScheduler = null;
+ var left = min(this.left, rect.left);
+ var top = min(this.top, rect.top);
-void _maybeScheduleMicrotaskFrame() {
- if (_microtaskScheduler == null) {
- _microtaskScheduler =
- new _MicrotaskScheduler.best(_completeMicrotasks);
+ return new Rect(left, top, right - left, bottom - top);
}
- _microtaskScheduler.maybeSchedule();
-}
-/**
- * Registers a [callback] which is called after the current execution stack
- * unwinds.
- */
-void _addMicrotaskCallback(TimeoutHandler callback) {
- if (_pendingMicrotasks == null) {
- _pendingMicrotasks = <TimeoutHandler>[];
- _maybeScheduleMicrotaskFrame();
+ /**
+ * Tests whether this rectangle entirely contains another rectangle.
+ */
+ bool containsRect(Rect another) {
+ return left <= another.left &&
+ left + width >= another.left + another.width &&
+ top <= another.top &&
+ top + height >= another.top + another.height;
}
- _pendingMicrotasks.add(callback);
+
+ /**
+ * Tests whether this rectangle entirely contains a point.
+ */
+ bool containsPoint(Point another) {
+ return another.x >= left &&
+ another.x <= left + width &&
+ another.y >= top &&
+ another.y <= top + height;
+ }
+
+ Rect ceil() => new Rect(left.ceil(), top.ceil(), width.ceil(), height.ceil());
+ Rect floor() => new Rect(left.floor(), top.floor(), width.floor(),
+ height.floor());
+ Rect round() => new Rect(left.round(), top.round(), width.round(),
+ height.round());
+
+ /**
+ * Truncates coordinates to integers and returns the result as a new
+ * rectangle.
+ */
+ Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
+ height.toInt());
+
+ Point get topLeft => new Point(this.left, this.top);
+ Point get bottomRight => new Point(this.left + this.width,
+ this.top + this.height);
}
+
/**
- * Complete all pending microtasks.
+ * A class for representing two-dimensional rectangles.
+ *
+ * This class is distinctive from RectBase in that it enforces that its
+ * properties are immutable.
*/
-void _completeMicrotasks() {
- var callbacks = _pendingMicrotasks;
- _pendingMicrotasks = null;
- for (var callback in callbacks) {
- callback();
+class Rect extends RectBase {
+ final num left;
+ final num top;
+ final num width;
+ final num height;
+
+ const Rect(this.left, this.top, this.width, this.height): super();
+
+ factory Rect.fromPoints(Point a, Point b) {
+ var left;
+ var width;
+ if (a.x < b.x) {
+ left = a.x;
+ width = b.x - left;
+ } else {
+ left = b.x;
+ width = a.x - left;
+ }
+ var top;
+ var height;
+ if (a.y < b.y) {
+ top = a.y;
+ height = b.y - top;
+ } else {
+ top = b.y;
+ height = a.y - top;
+ }
+
+ return new Rect(left, top, width, height);
}
}
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+class _HttpRequestUtils {
-/**
- * Class which helps construct standard node validation policies.
- *
- * By default this will not accept anything, but the 'allow*' functions can be
- * used to expand what types of elements or attributes are allowed.
- *
- * All allow functions are additive- elements will be accepted if they are
- * accepted by any specific rule.
- *
- * It is important to remember that sanitization is not just intended to prevent
- * cross-site scripting attacks, but also to prevent information from being
- * displayed in unexpected ways. For example something displaying basic
- * formatted text may not expect `<video>` tags to appear. In this case an
- * empty NodeValidatorBuilder with just [allowTextElements] might be
- * appropriate.
- */
-class NodeValidatorBuilder implements NodeValidator {
+ // Helper for factory HttpRequest.get
+ static HttpRequest get(String url,
+ onComplete(HttpRequest request),
+ bool withCredentials) {
+ final request = new HttpRequest();
+ request.open('GET', url, async: true);
- final List<NodeValidator> _validators = <NodeValidator>[];
+ request.withCredentials = withCredentials;
- NodeValidatorBuilder() {
- }
+ request.onReadyStateChange.listen((e) {
+ if (request.readyState == HttpRequest.DONE) {
+ onComplete(request);
+ }
+ });
- /**
- * Creates a new NodeValidatorBuilder which accepts common constructs.
- *
- * By default this will accept HTML5 elements and attributes with the default
- * [UriPolicy] and templating elements.
- *
- * Notable syntax which is filtered:
- *
- * * Only known-good HTML5 elements and attributes are allowed.
- * * All URLs must be same-origin, use [allowNavigation] and [allowImages] to
- * specify additional URI policies.
- * * Inline-styles are not allowed.
- * * Custom element tags are disallowed, use [allowCustomElement].
- * * Custom tags extensions are disallowed, use [allowTagExtension].
- * * SVG Elements are not allowed, use [allowSvg].
- *
- * For scenarios where the HTML should only contain formatted text
- * [allowTextElements] is more appropriate.
- *
- * Use [allowSvg] to allow SVG elements.
- */
- NodeValidatorBuilder.common() {
- allowHtml5();
- allowTemplating();
+ request.send();
+
+ return request;
}
+}
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
- /**
- * Allows navigation elements- Form and Anchor tags, along with common
- * attributes.
- *
- * The UriPolicy can be used to restrict the locations the navigation elements
- * are allowed to direct to. By default this will use the default [UriPolicy].
- */
- void allowNavigation([UriPolicy uriPolicy]) {
- if (uriPolicy == null) {
- uriPolicy = new UriPolicy();
- }
- add(new _SimpleNodeValidator.allowNavigation(uriPolicy));
+
+_serialize(var message) {
+ return new _JsSerializer().traverse(message);
+}
+
+class _JsSerializer extends _Serializer {
+
+ visitSendPortSync(SendPortSync x) {
+ if (x is _JsSendPortSync) return visitJsSendPortSync(x);
+ if (x is _LocalSendPortSync) return visitLocalSendPortSync(x);
+ if (x is _RemoteSendPortSync) return visitRemoteSendPortSync(x);
+ throw "Unknown port type $x";
}
- /**
- * Allows image elements.
- *
- * The UriPolicy can be used to restrict the locations the images may be
- * loaded from. By default this will use the default [UriPolicy].
- */
- void allowImages([UriPolicy uriPolicy]) {
- if (uriPolicy == null) {
- uriPolicy = new UriPolicy();
- }
- add(new _SimpleNodeValidator.allowImages(uriPolicy));
+ visitJsSendPortSync(_JsSendPortSync x) {
+ return [ 'sendport', 'nativejs', x._id ];
}
- /**
- * Allow basic text elements.
- *
- * This allows a subset of HTML5 elements, specifically just these tags and
- * no attributes.
- *
- * * B
- * * BLOCKQUOTE
- * * BR
- * * EM
- * * H1
- * * H2
- * * H3
- * * H4
- * * H5
- * * H6
- * * HR
- * * I
- * * LI
- * * OL
- * * P
- * * SPAN
- * * UL
- */
- void allowTextElements() {
- add(new _SimpleNodeValidator.allowTextElements());
+ visitLocalSendPortSync(_LocalSendPortSync x) {
+ return [ 'sendport', 'dart',
+ ReceivePortSync._isolateId, x._receivePort._portId ];
}
- /**
- * Allow common safe HTML5 elements and attributes.
- *
- * This list is based off of the Caja whitelists at:
- * https://code.google.com/p/google-caja/wiki/CajaWhitelists.
- *
- * Common things which are not allowed are script elements, style attributes
- * and any script handlers.
- */
- void allowHtml5({UriPolicy uriPolicy}) {
- add(new _Html5NodeValidator(uriPolicy: uriPolicy));
+ visitSendPort(SendPort x) {
+ throw new UnimplementedError('Asynchronous send port not yet implemented.');
}
- /**
- * Allow SVG elements and attributes except for known bad ones.
- */
- void allowSvg() {
- add(new _SvgNodeValidator());
+ visitRemoteSendPortSync(_RemoteSendPortSync x) {
+ return [ 'sendport', 'dart', x._isolateId, x._portId ];
}
+}
- /**
- * Allow custom elements with the specified tag name and specified attributes.
- *
- * This will allow the elements as custom tags (such as <x-foo></x-foo>),
- * but will not allow tag extensions. Use [allowTagExtension] to allow
- * tag extensions.
- */
- void allowCustomElement(String tagName,
- {UriPolicy uriPolicy,
- Iterable<String> attributes,
- Iterable<String> uriAttributes}) {
+_deserialize(var message) {
+ return new _JsDeserializer().deserialize(message);
+}
- var tagNameUpper = tagName.toUpperCase();
- var attrs;
- if (attributes != null) {
- attrs =
- attributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
- }
- var uriAttrs;
- if (uriAttributes != null) {
- uriAttrs =
- uriAttributes.map((name) => '$tagNameUpper::${name.toLowerCase()}');
- }
- if (uriPolicy == null) {
- uriPolicy = new UriPolicy();
- }
- add(new _CustomElementNodeValidator(
- uriPolicy,
- [tagNameUpper],
- attrs,
- uriAttrs,
- false,
- true));
- }
+class _JsDeserializer extends _Deserializer {
- /**
- * Allow custom tag extensions with the specified type name and specified
- * attributes.
- *
- * This will allow tag extensions (such as <div is="x-foo"></div>),
- * but will not allow custom tags. Use [allowCustomElement] to allow
- * custom tags.
- */
- void allowTagExtension(String tagName, String baseName,
- {UriPolicy uriPolicy,
- Iterable<String> attributes,
- Iterable<String> uriAttributes}) {
+ static const _UNSPECIFIED = const Object();
- var baseNameUpper = baseName.toUpperCase();
- var tagNameUpper = tagName.toUpperCase();
- var attrs;
- if (attributes != null) {
- attrs =
- attributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
- }
- var uriAttrs;
- if (uriAttributes != null) {
- uriAttrs =
- uriAttributes.map((name) => '$baseNameUpper::${name.toLowerCase()}');
- }
- if (uriPolicy == null) {
- uriPolicy = new UriPolicy();
+ deserializeSendPort(List x) {
+ String tag = x[1];
+ switch (tag) {
+ case 'nativejs':
+ num id = x[2];
+ return new _JsSendPortSync(id);
+ case 'dart':
+ num isolateId = x[2];
+ num portId = x[3];
+ return ReceivePortSync._lookup(isolateId, portId);
+ default:
+ throw 'Illegal SendPortSync type: $tag';
}
-
- add(new _CustomElementNodeValidator(
- uriPolicy,
- [tagNameUpper, baseNameUpper],
- attrs,
- uriAttrs,
- true,
- false));
}
+}
- void allowElement(String tagName, {UriPolicy uriPolicy,
- Iterable<String> attributes,
- Iterable<String> uriAttributes}) {
+// The receiver is JS.
+class _JsSendPortSync implements SendPortSync {
- allowCustomElement(tagName, uriPolicy: uriPolicy,
- attributes: attributes,
- uriAttributes: uriAttributes);
- }
+ final num _id;
+ _JsSendPortSync(this._id);
- /**
- * Allow templating elements (such as <template> and template-related
- * attributes.
- *
- * This still requires other validators to allow regular attributes to be
- * bound (such as [allowHtml5]).
- */
- void allowTemplating() {
- add(new _TemplatingNodeValidator());
+ callSync(var message) {
+ var serialized = _serialize(message);
+ var result = _callPortSync(_id, serialized);
+ return _deserialize(result);
}
- /**
- * Add an additional validator to the current list of validators.
- *
- * Elements and attributes will be accepted if they are accepted by any
- * validators.
- */
- void add(NodeValidator validator) {
- _validators.add(validator);
- }
-
- bool allowsElement(Element element) {
- return _validators.any((v) => v.allowsElement(element));
- }
-
- bool allowsAttribute(Element element, String attributeName, String value) {
- return _validators.any(
- (v) => v.allowsAttribute(element, attributeName, value));
- }
-}
-
-class _SimpleNodeValidator implements NodeValidator {
- final Set<String> allowedElements;
- final Set<String> allowedAttributes;
- final Set<String> allowedUriAttributes;
- final UriPolicy uriPolicy;
-
- factory _SimpleNodeValidator.allowNavigation(UriPolicy uriPolicy) {
- return new _SimpleNodeValidator(uriPolicy,
- allowedElements: [
- 'A',
- 'FORM'],
- allowedAttributes: [
- 'A::accesskey',
- 'A::coords',
- 'A::hreflang',
- 'A::name',
- 'A::shape',
- 'A::tabindex',
- 'A::target',
- 'A::type',
- 'FORM::accept',
- 'FORM::autocomplete',
- 'FORM::enctype',
- 'FORM::method',
- 'FORM::name',
- 'FORM::novalidate',
- 'FORM::target',
- ],
- allowedUriAttributes: [
- 'A::href',
- 'FORM::action',
- ]);
- }
-
- factory _SimpleNodeValidator.allowImages(UriPolicy uriPolicy) {
- return new _SimpleNodeValidator(uriPolicy,
- allowedElements: [
- 'IMG'
- ],
- allowedAttributes: [
- 'IMG::align',
- 'IMG::alt',
- 'IMG::border',
- 'IMG::height',
- 'IMG::hspace',
- 'IMG::ismap',
- 'IMG::name',
- 'IMG::usemap',
- 'IMG::vspace',
- 'IMG::width',
- ],
- allowedUriAttributes: [
- 'IMG::src',
- ]);
- }
-
- factory _SimpleNodeValidator.allowTextElements() {
- return new _SimpleNodeValidator(null,
- allowedElements: [
- 'B',
- 'BLOCKQUOTE',
- 'BR',
- 'EM',
- 'H1',
- 'H2',
- 'H3',
- 'H4',
- 'H5',
- 'H6',
- 'HR',
- 'I',
- 'LI',
- 'OL',
- 'P',
- 'SPAN',
- 'UL',
- ]);
+ bool operator==(var other) {
+ return (other is _JsSendPortSync) && (_id == other._id);
}
- /**
- * Elements must be uppercased tag names. For example `'IMG'`.
- * Attributes must be uppercased tag name followed by :: followed by
- * lowercase attribute name. For example `'IMG:src'`.
- */
- _SimpleNodeValidator(this.uriPolicy,
- {Iterable<String> allowedElements, Iterable<String> allowedAttributes,
- Iterable<String> allowedUriAttributes}):
- this.allowedElements = allowedElements != null ?
- new Set.from(allowedElements) : new Set(),
- this.allowedAttributes = allowedAttributes != null ?
- new Set.from(allowedAttributes) : new Set(),
- this.allowedUriAttributes = allowedUriAttributes != null ?
- new Set.from(allowedUriAttributes) : new Set();
-
- bool allowsElement(Element element) {
- return allowedElements.contains(element.tagName);
- }
-
- bool allowsAttribute(Element element, String attributeName, String value) {
- var tagName = element.tagName;
- if (allowedUriAttributes.contains('$tagName::$attributeName')) {
- return uriPolicy.allowsUri(value);
- } else if (allowedUriAttributes.contains('*::$attributeName')) {
- return uriPolicy.allowsUri(value);
- } else if (allowedAttributes.contains('$tagName::$attributeName')) {
- return true;
- } else if (allowedAttributes.contains('*::$attributeName')) {
- return true;
- } else if (allowedAttributes.contains('$tagName::*')) {
- return true;
- } else if (allowedAttributes.contains('*::*')) {
- return true;
- }
- return false;
- }
+ int get hashCode => _id;
}
-class _CustomElementNodeValidator extends _SimpleNodeValidator {
- final bool allowTypeExtension;
- final bool allowCustomTag;
-
- _CustomElementNodeValidator(UriPolicy uriPolicy,
- Iterable<String> allowedElements,
- Iterable<String> allowedAttributes,
- Iterable<String> allowedUriAttributes,
- bool allowTypeExtension,
- bool allowCustomTag):
+// TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
+// The receiver is a different Dart isolate, compiled to JS.
+class _RemoteSendPortSync implements SendPortSync {
- super(uriPolicy,
- allowedElements: allowedElements,
- allowedAttributes: allowedAttributes,
- allowedUriAttributes: allowedUriAttributes),
- this.allowTypeExtension = allowTypeExtension == true,
- this.allowCustomTag = allowCustomTag == true;
+ int _isolateId;
+ int _portId;
+ _RemoteSendPortSync(this._isolateId, this._portId);
- bool allowsElement(Element element) {
- if (allowTypeExtension) {
- var isAttr = element.attributes['is'];
- if (isAttr != null) {
- return allowedElements.contains(isAttr.toUpperCase()) &&
- allowedElements.contains(element.tagName);
- }
- }
- return allowCustomTag && allowedElements.contains(element.tagName);
+ callSync(var message) {
+ var serialized = _serialize(message);
+ var result = _call(_isolateId, _portId, serialized);
+ return _deserialize(result);
}
- bool allowsAttribute(Element element, String attributeName, String value) {
- if (allowsElement(element)) {
- if (allowTypeExtension && attributeName == 'is' &&
- allowedElements.contains(value.toUpperCase())) {
- return true;
- }
- return super.allowsAttribute(element, attributeName, value);
- }
- return false;
+ static _call(int isolateId, int portId, var message) {
+ var target = 'dart-port-$isolateId-$portId';
+ // TODO(vsm): Make this re-entrant.
+ // TODO(vsm): Set this up set once, on the first call.
+ var source = '$target-result';
+ var result = null;
+ window.on[source].first.then((Event e) {
+ result = json.parse(_getPortSyncEventData(e));
+ });
+ _dispatchEvent(target, [source, message]);
+ return result;
}
-}
-
-class _TemplatingNodeValidator extends _SimpleNodeValidator {
- static const _TEMPLATE_ATTRS =
- const <String>['bind', 'if', 'ref', 'repeat', 'syntax'];
-
- final Set<String> _templateAttrs;
- _TemplatingNodeValidator():
- super(null,
- allowedElements: [
- 'TEMPLATE'
- ],
- allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')),
- _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) {
+ bool operator==(var other) {
+ return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
+ && (_portId == other._portId);
}
- bool allowsAttribute(Element element, String attributeName, String value) {
- if (super.allowsAttribute(element, attributeName, value)) {
- return true;
- }
+ int get hashCode => _isolateId >> 16 + _portId;
+}
- if (attributeName == 'template' && value == "") {
- return true;
- }
+// The receiver is in the same Dart isolate, compiled to JS.
+class _LocalSendPortSync implements SendPortSync {
- if (element.attributes['template'] == "" ) {
- return _templateAttrs.contains(attributeName);
- }
- return false;
- }
-}
+ ReceivePortSync _receivePort;
+ _LocalSendPortSync._internal(this._receivePort);
-class _SvgNodeValidator implements NodeValidator {
- bool allowsElement(Element element) {
- if (element is svg.ScriptElement) {
- return false;
- }
- if (element is svg.SvgElement) {
- return true;
- }
- return false;
+ callSync(var message) {
+ // TODO(vsm): Do a more efficient deep copy.
+ var copy = _deserialize(_serialize(message));
+ var result = _receivePort._callback(copy);
+ return _deserialize(_serialize(result));
}
- bool allowsAttribute(Element element, String attributeName, String value) {
- if (attributeName == 'is' || attributeName.startsWith('on')) {
- return false;
- }
- return allowsElement(element);
+ bool operator==(var other) {
+ return (other is _LocalSendPortSync)
+ && (_receivePort == other._receivePort);
}
-}
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
+ int get hashCode => _receivePort.hashCode;
+}
-/**
- * A utility class for representing two-dimensional positions.
- */
-class Point {
- final num x;
- final num y;
-
- const Point([num x = 0, num y = 0]): x = x, y = y;
+// TODO(vsm): Move this to dart:isolate. This will take some
+// refactoring as there are dependences here on the DOM. Users
+// interact with this class (or interface if we change it) directly -
+// new ReceivePortSync. I think most of the DOM logic could be
+// delayed until the corresponding SendPort is registered on the
+// window.
- String toString() => '($x, $y)';
+// A Dart ReceivePortSync (tagged 'dart' when serialized) is
+// identifiable / resolvable by the combination of its isolateid and
+// portid. When a corresponding SendPort is used within the same
+// isolate, the _portMap below can be used to obtain the
+// ReceivePortSync directly. Across isolates (or from JS), an
+// EventListener can be used to communicate with the port indirectly.
+class ReceivePortSync {
- bool operator ==(other) {
- if (other is !Point) return false;
- return x == other.x && y == other.y;
- }
+ static Map<int, ReceivePortSync> _portMap;
+ static int _portIdCount;
+ static int _cachedIsolateId;
- int get hashCode => JenkinsSmiHash.hash2(x.hashCode, y.hashCode);
+ num _portId;
+ Function _callback;
+ StreamSubscription _portSubscription;
- Point operator +(Point other) {
- return new Point(x + other.x, y + other.y);
+ ReceivePortSync() {
+ if (_portIdCount == null) {
+ _portIdCount = 0;
+ _portMap = new Map<int, ReceivePortSync>();
+ }
+ _portId = _portIdCount++;
+ _portMap[_portId] = this;
}
- Point operator -(Point other) {
- return new Point(x - other.x, y - other.y);
+ static int get _isolateId {
+ // TODO(vsm): Make this coherent with existing isolate code.
+ if (_cachedIsolateId == null) {
+ _cachedIsolateId = _getNewIsolateId();
+ }
+ return _cachedIsolateId;
}
- Point operator *(num factor) {
- return new Point(x * factor, y * factor);
+ static String _getListenerName(isolateId, portId) =>
+ 'dart-port-$isolateId-$portId';
+ String get _listenerName => _getListenerName(_isolateId, _portId);
+
+ void receive(callback(var message)) {
+ _callback = callback;
+ if (_portSubscription == null) {
+ _portSubscription = window.on[_listenerName].listen((Event e) {
+ var data = json.parse(_getPortSyncEventData(e));
+ var replyTo = data[0];
+ var message = _deserialize(data[1]);
+ var result = _callback(message);
+ _dispatchEvent(replyTo, _serialize(result));
+ });
+ }
}
- /**
- * Get the straight line (Euclidean) distance between the origin (0, 0) and
- * this point.
- */
- num get magnitude => sqrt(x * x + y * y);
+ void close() {
+ _portMap.remove(_portId);
+ if (_portSubscription != null) _portSubscription.cancel();
+ }
- /**
- * Returns the distance between two points.
- */
- double distanceTo(Point other) {
- var dx = x - other.x;
- var dy = y - other.y;
- return sqrt(dx * dx + dy * dy);
+ SendPortSync toSendPort() {
+ return new _LocalSendPortSync._internal(this);
}
- /**
- * Returns the squared distance between two points.
- *
- * Squared distances can be used for comparisons when the actual value is not
- * required.
- */
- num squaredDistanceTo(Point other) {
- var dx = x - other.x;
- var dy = y - other.y;
- return dx * dx + dy * dy;
+ static SendPortSync _lookup(int isolateId, int portId) {
+ if (isolateId == _isolateId) {
+ return _portMap[portId].toSendPort();
+ } else {
+ return new _RemoteSendPortSync(isolateId, portId);
+ }
}
+}
- Point ceil() => new Point(x.ceil(), y.ceil());
- Point floor() => new Point(x.floor(), y.floor());
- Point round() => new Point(x.round(), y.round());
+get _isolateId => ReceivePortSync._isolateId;
- /**
- * Truncates x and y to integers and returns the result as a new point.
- */
- Point toInt() => new Point(x.toInt(), y.toInt());
+void _dispatchEvent(String receiver, var message) {
+ var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
+ detail: json.stringify(message));
+ window.dispatchEvent(event);
}
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
+
+String _getPortSyncEventData(CustomEvent event) => event.detail;
+// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+typedef void _MicrotaskCallback();
+
/**
- * Contains the set of standard values returned by HTMLDocument.getReadyState.
+ * This class attempts to invoke a callback as soon as the current event stack
+ * unwinds, but before the browser repaints.
*/
-abstract class ReadyState {
+abstract class _MicrotaskScheduler {
+ bool _nextMicrotaskFrameScheduled = false;
+ final _MicrotaskCallback _callback;
+
+ _MicrotaskScheduler(this._callback);
+
/**
- * Indicates the document is still loading and parsing.
+ * Creates the best possible microtask scheduler for the current platform.
*/
- static const String LOADING = "loading";
+ factory _MicrotaskScheduler.best(_MicrotaskCallback callback) {
+ if (Window._supportsSetImmediate) {
+ return new _SetImmediateScheduler(callback);
+ } else if (MutationObserver.supported) {
+ return new _MutationObserverScheduler(callback);
+ }
+ return new _PostMessageScheduler(callback);
+ }
/**
- * Indicates the document is finished parsing but is still loading
- * subresources.
+ * Schedules a microtask callback if one has not been scheduled already.
*/
- static const String INTERACTIVE = "interactive";
+ void maybeSchedule() {
+ if (this._nextMicrotaskFrameScheduled) {
+ return;
+ }
+ this._nextMicrotaskFrameScheduled = true;
+ this._schedule();
+ }
/**
- * Indicates the document and all subresources have been loaded.
+ * Does the actual scheduling of the callback.
*/
- static const String COMPLETE = "complete";
-}
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
+ void _schedule();
+ /**
+ * Handles the microtask callback and forwards it if necessary.
+ */
+ void _onCallback() {
+ // Ignore spurious messages.
+ if (!_nextMicrotaskFrameScheduled) {
+ return;
+ }
+ _nextMicrotaskFrameScheduled = false;
+ this._callback();
+ }
+}
/**
- * A base class for representing two-dimensional rectangles. This will hopefully
- * be moved merged with the dart:math Rect.
+ * Scheduler which uses window.postMessage to schedule events.
*/
-// TODO(efortuna): Merge with Math rect after finalizing with Florian.
-abstract class RectBase {
- // Not used, but keeps the VM from complaining about Rect having a const
- // constructor and this one not.
- const RectBase();
-
- num get left;
- num get top;
- num get width;
- num get height;
-
- num get right => left + width;
- num get bottom => top + height;
-
- // NOTE! All code below should be common with Rect.
-
- String toString() {
- return '($left, $top, $width, $height)';
- }
+class _PostMessageScheduler extends _MicrotaskScheduler {
+ const _MICROTASK_MESSAGE = "DART-MICROTASK";
- bool operator ==(other) {
- if (other is !Rect) return false;
- return left == other.left && top == other.top && width == other.width &&
- height == other.height;
+ _PostMessageScheduler(_MicrotaskCallback callback): super(callback) {
+ // Messages from other windows do not cause a security risk as
+ // all we care about is that _handleMessage is called
+ // after the current event loop is unwound and calling the function is
+ // a noop when zero requests are pending.
+ window.onMessage.listen(this._handleMessage);
}
- int get hashCode => JenkinsSmiHash.hash4(left.hashCode, top.hashCode,
- width.hashCode, height.hashCode);
-
- /**
- * Computes the intersection of this rectangle and the rectangle parameter.
- * Returns null if there is no intersection.
- */
- Rect intersection(Rect rect) {
- var x0 = max(left, rect.left);
- var x1 = min(left + width, rect.left + rect.width);
-
- if (x0 <= x1) {
- var y0 = max(top, rect.top);
- var y1 = min(top + height, rect.top + rect.height);
-
- if (y0 <= y1) {
- return new Rect(x0, y0, x1 - x0, y1 - y0);
- }
- }
- return null;
+ void _schedule() {
+ window.postMessage(_MICROTASK_MESSAGE, "*");
}
-
- /**
- * Returns whether a rectangle intersects this rectangle.
- */
- bool intersects(Rect other) {
- return (left <= other.left + other.width && other.left <= left + width &&
- top <= other.top + other.height && other.top <= top + height);
+ void _handleMessage(e) {
+ this._onCallback();
}
+}
- /**
- * Returns a new rectangle which completely contains this rectangle and the
- * input rectangle.
- */
- Rect union(Rect rect) {
- var right = max(this.left + this.width, rect.left + rect.width);
- var bottom = max(this.top + this.height, rect.top + rect.height);
-
- var left = min(this.left, rect.left);
- var top = min(this.top, rect.top);
+/**
+ * Scheduler which uses a MutationObserver to schedule events.
+ */
+class _MutationObserverScheduler extends _MicrotaskScheduler {
+ MutationObserver _observer;
+ Element _dummy;
- return new Rect(left, top, right - left, bottom - top);
+ _MutationObserverScheduler(_MicrotaskCallback callback): super(callback) {
+ // Mutation events get fired as soon as the current event stack is unwound
+ // so we just make a dummy event and listen for that.
+ _observer = new MutationObserver(this._handleMutation);
+ _dummy = new DivElement();
+ _observer.observe(_dummy, attributes: true);
}
- /**
- * Tests whether this rectangle entirely contains another rectangle.
- */
- bool containsRect(Rect another) {
- return left <= another.left &&
- left + width >= another.left + another.width &&
- top <= another.top &&
- top + height >= another.top + another.height;
+ void _schedule() {
+ // Toggle it to trigger the mutation event.
+ _dummy.hidden = !_dummy.hidden;
}
- /**
- * Tests whether this rectangle entirely contains a point.
- */
- bool containsPoint(Point another) {
- return another.x >= left &&
- another.x <= left + width &&
- another.y >= top &&
- another.y <= top + height;
+ _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
+ this._onCallback();
}
+}
- Rect ceil() => new Rect(left.ceil(), top.ceil(), width.ceil(), height.ceil());
- Rect floor() => new Rect(left.floor(), top.floor(), width.floor(),
- height.floor());
- Rect round() => new Rect(left.round(), top.round(), width.round(),
- height.round());
-
- /**
- * Truncates coordinates to integers and returns the result as a new
- * rectangle.
- */
- Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
- height.toInt());
+/**
+ * Scheduler which uses window.setImmediate to schedule events.
+ */
+class _SetImmediateScheduler extends _MicrotaskScheduler {
+ _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
- Point get topLeft => new Point(this.left, this.top);
- Point get bottomRight => new Point(this.left + this.width,
- this.top + this.height);
+ void _schedule() {
+ window._setImmediate(_handleImmediate);
+ }
+
+ void _handleImmediate() {
+ this._onCallback();
+ }
}
+List<TimeoutHandler> _pendingMicrotasks;
+_MicrotaskScheduler _microtaskScheduler = null;
+void _maybeScheduleMicrotaskFrame() {
+ if (_microtaskScheduler == null) {
+ _microtaskScheduler =
+ new _MicrotaskScheduler.best(_completeMicrotasks);
+ }
+ _microtaskScheduler.maybeSchedule();
+}
/**
- * A class for representing two-dimensional rectangles.
- *
- * This class is distinctive from RectBase in that it enforces that its
- * properties are immutable.
+ * Registers a [callback] which is called after the current execution stack
+ * unwinds.
*/
-class Rect extends RectBase {
- final num left;
- final num top;
- final num width;
- final num height;
-
- const Rect(this.left, this.top, this.width, this.height): super();
+void _addMicrotaskCallback(TimeoutHandler callback) {
+ if (_pendingMicrotasks == null) {
+ _pendingMicrotasks = <TimeoutHandler>[];
+ _maybeScheduleMicrotaskFrame();
+ }
+ _pendingMicrotasks.add(callback);
+}
- factory Rect.fromPoints(Point a, Point b) {
- var left;
- var width;
- if (a.x < b.x) {
- left = a.x;
- width = b.x - left;
- } else {
- left = b.x;
- width = a.x - left;
- }
- var top;
- var height;
- if (a.y < b.y) {
- top = a.y;
- height = b.y - top;
- } else {
- top = b.y;
- height = a.y - top;
- }
- return new Rect(left, top, width, height);
+/**
+ * Complete all pending microtasks.
+ */
+void _completeMicrotasks() {
+ var callbacks = _pendingMicrotasks;
+ _pendingMicrotasks = null;
+ for (var callback in callbacks) {
+ callback();
}
}
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
@@ -31619,89 +30716,6 @@ class _WrappedIterator<E> implements Iterator<E> {
// BSD-style license that can be found in the LICENSE file.
-class _HttpRequestUtils {
-
- // Helper for factory HttpRequest.get
- static HttpRequest get(String url,
- onComplete(HttpRequest request),
- bool withCredentials) {
- final request = new HttpRequest();
- request.open('GET', url, async: true);
-
- request.withCredentials = withCredentials;
-
- request.onReadyStateChange.listen((e) {
- if (request.readyState == HttpRequest.DONE) {
- onComplete(request);
- }
- });
-
- request.send();
-
- return request;
- }
-}
-// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-
-// Iterator for arrays with fixed size.
-class FixedSizeListIterator<T> implements Iterator<T> {
- final List<T> _array;
- final int _length; // Cache array length for faster access.
- int _position;
- T _current;
-
- FixedSizeListIterator(List<T> array)
- : _array = array,
- _position = -1,
- _length = array.length;
-
- bool moveNext() {
- int nextPosition = _position + 1;
- if (nextPosition < _length) {
- _current = _array[nextPosition];
- _position = nextPosition;
- return true;
- }
- _current = null;
- _position = _length;
- return false;
- }
-
- T get current => _current;
-}
-
-// Iterator for arrays with variable size.
-class _VariableSizeListIterator<T> implements Iterator<T> {
- final List<T> _array;
- int _position;
- T _current;
-
- _VariableSizeListIterator(List<T> array)
- : _array = array,
- _position = -1;
-
- bool moveNext() {
- int nextPosition = _position + 1;
- if (nextPosition < _array.length) {
- _current = _array[nextPosition];
- _position = nextPosition;
- return true;
- }
- _current = null;
- _position = _array.length;
- return false;
- }
-
- T get current => _current;
-}
-// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
-// for details. All rights reserved. Use of this source code is governed by a
-// BSD-style license that can be found in the LICENSE file.
-
-
// Conversions for Window. These check if the window is the local
// window, and if it's not, wraps or unwraps it with a secure wrapper.
// We need to test for EventTarget here as well as it's a base type.
@@ -32130,202 +31144,59 @@ class Platform {
}
}
}
-// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
+// Copyright (c) 2011, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
+// Iterator for arrays with fixed size.
+class FixedSizeListIterator<T> implements Iterator<T> {
+ final List<T> _array;
+ final int _length; // Cache array length for faster access.
+ int _position;
+ T _current;
+
+ FixedSizeListIterator(List<T> array)
+ : _array = array,
+ _position = -1,
+ _length = array.length;
-/**
- * Interface used to validate that only accepted elements and attributes are
- * allowed while parsing HTML strings into DOM nodes.
- *
- * In general, customization of validation behavior should be done via the
- * [NodeValidatorBuilder] class to mitigate the chances of incorrectly
- * implementing validation rules.
- */
-abstract class NodeValidator {
-
- /**
- * Construct a default NodeValidator which only accepts whitelisted HTML5
- * elements and attributes.
- *
- * If a uriPolicy is not specified then the default uriPolicy will be used.
- */
- factory NodeValidator({UriPolicy uriPolicy}) =>
- new _Html5NodeValidator(uriPolicy: uriPolicy);
-
- factory NodeValidator.throws(NodeValidator base) =>
- new _ThrowsNodeValidator(base);
-
- /**
- * Returns true if the tagName is an accepted type.
- */
- bool allowsElement(Element element);
-
- /**
- * Returns true if the attribute is allowed.
- *
- * The attributeName parameter will always be in lowercase.
- *
- * See [allowsElement] for format of tagName.
- */
- bool allowsAttribute(Element element, String attributeName, String value);
-}
-
-/**
- * Performs sanitization of a node tree after construction to ensure that it
- * does not contain any disallowed elements or attributes.
- *
- * In general custom implementations of this class should not be necessary and
- * all validation customization should be done in custom NodeValidators, but
- * custom implementations of this class can be created to perform more complex
- * tree sanitization.
- */
-abstract class NodeTreeSanitizer {
-
- /**
- * Constructs a default tree sanitizer which will remove all elements and
- * attributes which are not allowed by the provided validator.
- */
- factory NodeTreeSanitizer(NodeValidator validator) =>
- new _ValidatingTreeSanitizer(validator);
-
- /**
- * Called with the root of the tree which is to be sanitized.
- *
- * This method needs to walk the entire tree and either remove elements and
- * attributes which are not recognized as safe or throw an exception which
- * will mark the entire tree as unsafe.
- */
- void sanitizeTree(Node node);
-}
-
-/**
- * Defines the policy for what types of uris are allowed for particular
- * attribute values.
- *
- * This can be used to provide custom rules such as allowing all http:// URIs
- * for image attributes but only same-origin URIs for anchor tags.
- */
-abstract class UriPolicy {
- /**
- * Constructs the default UriPolicy which is to only allow Uris to the same
- * origin as the application was launched from.
- *
- * This will block all ftp: mailto: URIs. It will also block accessing
- * https://example.com if the app is running from http://example.com.
- */
- factory UriPolicy() => new _SameOriginUriPolicy();
-
- /**
- * Checks if the uri is allowed on the specified attribute.
- *
- * The uri provided may or may not be a relative path.
- */
- bool allowsUri(String uri);
-}
-
-/**
- * Allows URIs to the same origin as the current application was loaded from
- * (such as https://example.com:80).
- */
-class _SameOriginUriPolicy implements UriPolicy {
- final AnchorElement _hiddenAnchor = new AnchorElement();
- final Location _loc = window.location;
-
- bool allowsUri(String uri) {
- _hiddenAnchor.href = uri;
- return _hiddenAnchor.hostname == _loc.hostname &&
- _hiddenAnchor.port == _loc.port &&
- _hiddenAnchor.protocol == _loc.protocol;
- }
-}
-
-
-class _ThrowsNodeValidator implements NodeValidator {
- final NodeValidator validator;
-
- _ThrowsNodeValidator(this.validator) {}
-
- bool allowsElement(Element element) {
- if (!validator.allowsElement(element)) {
- throw new ArgumentError(element.tagName);
+ bool moveNext() {
+ int nextPosition = _position + 1;
+ if (nextPosition < _length) {
+ _current = _array[nextPosition];
+ _position = nextPosition;
+ return true;
}
- return true;
+ _current = null;
+ _position = _length;
+ return false;
}
- bool allowsAttribute(Element element, String attributeName, String value) {
- if (!validator.allowsAttribute(element, attributeName, value)) {
- throw new ArgumentError('${element.tagName}[$attributeName="$value"]');
- }
- }
+ T get current => _current;
}
+// Iterator for arrays with variable size.
+class _VariableSizeListIterator<T> implements Iterator<T> {
+ final List<T> _array;
+ int _position;
+ T _current;
-/**
- * Standard tree sanitizer which validates a node tree against the provided
- * validator and removes any nodes or attributes which are not allowed.
- */
-class _ValidatingTreeSanitizer implements NodeTreeSanitizer {
- NodeValidator validator;
- _ValidatingTreeSanitizer(this.validator) {}
-
- void sanitizeTree(Node node) {
- void walk(Node node) {
- sanitizeNode(node);
-
- var child = node.lastChild;
- while (child != null) {
- // Child may be removed during the walk.
- var nextChild = child.previousNode;
- walk(child);
- child = nextChild;
- }
- }
- walk(node);
- }
-
- void sanitizeNode(Node node) {
- switch (node.nodeType) {
- case Node.ELEMENT_NODE:
- Element element = node;
- var attrs = element.attributes;
- if (!validator.allowsElement(element)) {
- element.remove();
- break;
- }
-
- var isAttr = attrs['is'];
- if (isAttr != null) {
- if (!validator.allowsAttribute(element, 'is', isAttr)) {
- element.remove();
- break;
- }
- }
-
- // TODO(blois): Need to be able to get all attributes, irrespective of
- // XMLNS.
- var keys = attrs.keys.toList();
- for (var i = attrs.length - 1; i >= 0; --i) {
- var name = keys[i];
- if (!validator.allowsAttribute(element, name, attrs[name])) {
- attrs.remove(name);
- }
- }
+ _VariableSizeListIterator(List<T> array)
+ : _array = array,
+ _position = -1;
- if (element is TemplateElement) {
- TemplateElement template = element;
- sanitizeTree(template.content);
- }
- break;
- case Node.COMMENT_NODE:
- case Node.DOCUMENT_FRAGMENT_NODE:
- case Node.TEXT_NODE:
- case Node.CDATA_SECTION_NODE:
- break;
- default:
- node.remove();
+ bool moveNext() {
+ int nextPosition = _position + 1;
+ if (nextPosition < _array.length) {
+ _current = _array[nextPosition];
+ _position = nextPosition;
+ return true;
}
+ _current = null;
+ _position = _array.length;
+ return false;
}
+
+ T get current => _current;
}
« no previous file with comments | « pkg/unittest/lib/html_enhanced_config.dart ('k') | sdk/lib/html/dartium/html_dartium.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698