| 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 ebc0c1d4a6b2de78cb6832a619d2d8d60b3fcec4..1a3ab9a4676b311ebe75aac97e3838a429bfa13d 100644
 | 
| --- a/sdk/lib/html/dart2js/html_dart2js.dart
 | 
| +++ b/sdk/lib/html/dart2js/html_dart2js.dart
 | 
| @@ -7626,21 +7626,18 @@ class Document extends Node  native "Document"
 | 
|  class DocumentFragment extends Node implements ParentNode native "DocumentFragment" {
 | 
|    factory DocumentFragment() => document.createDocumentFragment();
 | 
|  
 | 
| -  factory DocumentFragment.html(String html) {
 | 
| -    final fragment = new DocumentFragment();
 | 
| -    fragment.innerHtml = html;
 | 
| -    return fragment;
 | 
| +  factory DocumentFragment.html(String html,
 | 
| +      {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
 | 
| +
 | 
| +    return document.body.createFragment(html,
 | 
| +      validator: validator, treeSanitizer: treeSanitizer);
 | 
|    }
 | 
|  
 | 
| -  factory DocumentFragment.svg(String svgContent) {
 | 
| -    final fragment = new DocumentFragment();
 | 
| -    final e = new svg.SvgSvgElement();
 | 
| -    e.innerHtml = svgContent;
 | 
| +  factory DocumentFragment.svg(String svgContent,
 | 
| +      {NodeValidator validator, NodeTreeSanitizer 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;
 | 
| +    return new svg.SvgSvgElement().createFragment(svgContent,
 | 
| +        validator: validator, treeSanitizer: treeSanitizer);
 | 
|    }
 | 
|  
 | 
|    // Native field is used only by Dart code so does not lead to instantiation
 | 
| @@ -7674,17 +7671,16 @@ 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.nodes.clear();
 | 
| +    this.setInnerHtml(value);
 | 
| +  }
 | 
|  
 | 
| -    final e = new Element.tag("div");
 | 
| -    e.innerHtml = value;
 | 
| +  void setInnerHtml(String html,
 | 
| +    {NodeValidator validator, NodeTreeSanitizer 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);
 | 
| +    this.nodes.clear();
 | 
| +    append(document.body.createFragment(
 | 
| +        html, validator: validator, treeSanitizer: treeSanitizer));
 | 
|    }
 | 
|  
 | 
|    /**
 | 
| @@ -8766,20 +8762,34 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
 | 
|    /**
 | 
|     * Creates an HTML element from a valid fragment of HTML.
 | 
|     *
 | 
| -   * The [html] fragment must represent valid HTML with a single element root,
 | 
| -   * which will be parsed and returned.
 | 
| +   *     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.
 | 
|     *
 | 
| -   * Important: the contents of [html] should not contain any user-supplied
 | 
| -   * data. Without strict data validation it is impossible to prevent script
 | 
| -   * injection exploits.
 | 
| +   * 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.
 | 
|     *
 | 
| -   * It is instead recommended that elements be constructed via [Element.tag]
 | 
| -   * and text be added via [text].
 | 
| +   * Unless a validator is provided this will perform the default validation
 | 
| +   * and remove all scriptable elements and attributes.
 | 
| +   *
 | 
| +   * See also:
 | 
| +   *
 | 
| +   * * [NodeValidator]
 | 
|     *
 | 
| -   *     var element = new Element.html('<div class="foo">content</div>');
 | 
|     */
 | 
| -  factory Element.html(String html) =>
 | 
| -      _ElementFactoryProvider.createElement_html(html);
 | 
| +  factory Element.html(String html,
 | 
| +      {NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
 | 
| +    var fragment = document.body.createFragment(html, validator: validator,
 | 
| +        treeSanitizer: treeSanitizer);
 | 
| +
 | 
| +    if (fragment._firstElementChild != fragment._lastElementChild) {
 | 
| +      throw new StateError("More than one element root");
 | 
| +    }
 | 
| +    return fragment._firstElementChild;
 | 
| +  }
 | 
|  
 | 
|    /**
 | 
|     * Creates the HTML element specified by the tag name.
 | 
| @@ -9615,15 +9625,127 @@ abstract class Element extends Node implements ParentNode, ChildNode native "Ele
 | 
|      return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
 | 
|    }
 | 
|  
 | 
| -  @JSName('innerHTML')
 | 
| -  @DomName('HTMLElement.innerHTML')
 | 
| -  String get innerHtml => JS('String', '#.innerHTML', this);
 | 
| +  static HtmlDocument _parseDocument;
 | 
| +  static NodeValidatorBuilder _defaultValidator;
 | 
| +  static _ValidatingTreeSanitizer _defaultSanitizer;
 | 
|  
 | 
| -  void set innerHtml(String value) {
 | 
| -    JS('', '#.innerHTML = #', this, value);
 | 
| -    // Polyfill relies on mutation observers for upgrading, but we want it
 | 
| -    // immediate.
 | 
| -    Platform.upgradeCustomElements(this);
 | 
| +  /**
 | 
| +   * 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;
 | 
| +
 | 
| +  /**
 | 
| +   * 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;
 | 
|    }
 | 
|  
 | 
|    // To suppress missing implicit constructor warnings.
 | 
| @@ -9862,6 +9984,11 @@ 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
 | 
| @@ -10437,119 +10564,8 @@ 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');
 | 
| -  }
 | 
| +class _ElementFactoryProvider {
 | 
|  
 | 
|    @DomName('Document.createElement')
 | 
|    // Optimization to improve performance until the dart2js compiler inlines this
 | 
| @@ -22748,6 +22764,22 @@ 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
 | 
| @@ -28493,6 +28525,455 @@ 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.
 | 
| @@ -28574,404 +29055,222 @@ abstract class ImmutableListMixin<E> implements List<E> {
 | 
|  // BSD-style license that can be found in the LICENSE file.
 | 
|  
 | 
|  
 | 
| -/**
 | 
| - * 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
 | 
| +_serialize(var message) {
 | 
| +  return new _JsSerializer().traverse(message);
 | 
| +}
 | 
|  
 | 
| -  /**
 | 
| -   * The set of keys that have been pressed down without seeing their
 | 
| -   * corresponding keyup event.
 | 
| -   */
 | 
| -  final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
 | 
| +class _JsSerializer extends _Serializer {
 | 
|  
 | 
| -  /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
 | 
| -  final String _type;
 | 
| +  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";
 | 
| +  }
 | 
|  
 | 
| -  /** The element we are watching for events to happen on. */
 | 
| -  final EventTarget _target;
 | 
| +  visitJsSendPortSync(_JsSendPortSync x) {
 | 
| +    return [ 'sendport', 'nativejs', x._id ];
 | 
| +  }
 | 
|  
 | 
| -  // 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];
 | 
| +  visitLocalSendPortSync(_LocalSendPortSync x) {
 | 
| +    return [ 'sendport', 'dart',
 | 
| +             ReceivePortSync._isolateId, x._receivePort._portId ];
 | 
| +  }
 | 
|  
 | 
| -  /** Controller to produce KeyEvents for the stream. */
 | 
| -  final StreamController _controller = new StreamController(sync: true);
 | 
| +  visitSendPort(SendPort x) {
 | 
| +    throw new UnimplementedError('Asynchronous send port not yet implemented.');
 | 
| +  }
 | 
|  
 | 
| -  static const _EVENT_TYPE = 'KeyEvent';
 | 
| +  visitRemoteSendPortSync(_RemoteSendPortSync x) {
 | 
| +    return [ 'sendport', 'dart', x._isolateId, x._portId ];
 | 
| +  }
 | 
| +}
 | 
|  
 | 
| -  /**
 | 
| -   * 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
 | 
| -  };
 | 
| +_deserialize(var message) {
 | 
| +  return new _JsDeserializer().deserialize(message);
 | 
| +}
 | 
|  
 | 
| -  /** Return a stream for KeyEvents for the specified target. */
 | 
| -  Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
 | 
| -    return new _KeyboardEventHandler.initializeAllEventListeners(
 | 
| -        _type, e).stream;
 | 
| -  }
 | 
|  
 | 
| -  /**
 | 
| -   * 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.");
 | 
| +class _JsDeserializer extends _Deserializer {
 | 
| +
 | 
| +  static const _UNSPECIFIED = const Object();
 | 
| +
 | 
| +  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';
 | 
|      }
 | 
|    }
 | 
| +}
 | 
|  
 | 
| -  /**
 | 
| -   * General constructor, performs basic initialization for our improved
 | 
| -   * KeyboardEvent controller.
 | 
| -   */
 | 
| -  _KeyboardEventHandler(this._type) :
 | 
| -    _target = null, super(_EVENT_TYPE) {
 | 
| -  }
 | 
| +// The receiver is JS.
 | 
| +class _JsSendPortSync implements SendPortSync {
 | 
|  
 | 
| -  /**
 | 
| -   * 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);
 | 
| +  final num _id;
 | 
| +  _JsSendPortSync(this._id);
 | 
| +
 | 
| +  callSync(var message) {
 | 
| +    var serialized = _serialize(message);
 | 
| +    var result = _callPortSync(_id, serialized);
 | 
| +    return _deserialize(result);
 | 
|    }
 | 
|  
 | 
| -  /**
 | 
| -   * Notify all callback listeners that a KeyEvent of the relevant type has
 | 
| -   * occurred.
 | 
| -   */
 | 
| -  bool _dispatch(KeyEvent event) {
 | 
| -    if (event.type == _type)
 | 
| -      _controller.add(event);
 | 
| +  bool operator==(var other) {
 | 
| +    return (other is _JsSendPortSync) && (_id == other._id);
 | 
|    }
 | 
|  
 | 
| -  /** Determine if caps lock is one of the currently depressed keys. */
 | 
| -  bool get _capsLockOn =>
 | 
| -      _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
 | 
| +  int get hashCode => _id;
 | 
| +}
 | 
|  
 | 
| -  /**
 | 
| -   * 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;
 | 
| -  }
 | 
| +// TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
 | 
| +// The receiver is a different Dart isolate, compiled to JS.
 | 
| +class _RemoteSendPortSync implements SendPortSync {
 | 
|  
 | 
| -  /**
 | 
| -   * 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;
 | 
| +  int _isolateId;
 | 
| +  int _portId;
 | 
| +  _RemoteSendPortSync(this._isolateId, this._portId);
 | 
| +
 | 
| +  callSync(var message) {
 | 
| +    var serialized = _serialize(message);
 | 
| +    var result = _call(_isolateId, _portId, serialized);
 | 
| +    return _deserialize(result);
 | 
|    }
 | 
|  
 | 
| -  /**
 | 
| -   * Returns true if the key fires a keypress event in the current browser.
 | 
| -   */
 | 
| -  bool _firesKeyPressEvent(KeyEvent event) {
 | 
| -    if (!Device.isIE && !Device.isWebKit) {
 | 
| -      return true;
 | 
| -    }
 | 
| +  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;
 | 
| +  }
 | 
|  
 | 
| -    if (Device.userAgent.contains('Mac') && event.altKey) {
 | 
| -      return KeyCode.isCharacterKey(event.keyCode);
 | 
| -    }
 | 
| +  bool operator==(var other) {
 | 
| +    return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
 | 
| +      && (_portId == other._portId);
 | 
| +  }
 | 
|  
 | 
| -    // Alt but not AltGr which is represented as Alt+Ctrl.
 | 
| -    if (event.altKey && !event.ctrlKey) {
 | 
| -      return false;
 | 
| -    }
 | 
| +  int get hashCode => _isolateId >> 16 + _portId;
 | 
| +}
 | 
|  
 | 
| -    // 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 receiver is in the same Dart isolate, compiled to JS.
 | 
| +class _LocalSendPortSync implements SendPortSync {
 | 
|  
 | 
| -    // 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;
 | 
| -    }
 | 
| +  ReceivePortSync _receivePort;
 | 
|  
 | 
| -    switch (event.keyCode) {
 | 
| -      case KeyCode.ENTER:
 | 
| -        // IE9 does not fire keypress on ENTER.
 | 
| -        return !Device.isIE;
 | 
| -      case KeyCode.ESC:
 | 
| -        return !Device.isWebKit;
 | 
| -    }
 | 
| +  _LocalSendPortSync._internal(this._receivePort);
 | 
|  
 | 
| -    return KeyCode.isCharacterKey(event.keyCode);
 | 
| +  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));
 | 
|    }
 | 
|  
 | 
| -  /**
 | 
| -   * 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;
 | 
| -      }
 | 
| -    }
 | 
| -    return event.keyCode;
 | 
| +  bool operator==(var other) {
 | 
| +    return (other is _LocalSendPortSync)
 | 
| +      && (_receivePort == other._receivePort);
 | 
|    }
 | 
|  
 | 
| -  /** 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();
 | 
| -    }
 | 
| +  int get hashCode => _receivePort.hashCode;
 | 
| +}
 | 
|  
 | 
| -    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);
 | 
| -  }
 | 
| +// 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.
 | 
|  
 | 
| -  /** 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);
 | 
| +// 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 {
 | 
|  
 | 
| -    // 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];
 | 
| +  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>();
 | 
|      }
 | 
| -    e._shadowAltKey = _keyDownList.any((var element) => element.altKey);
 | 
| -    _dispatch(e);
 | 
| +    _portId = _portIdCount++;
 | 
| +    _portMap[_portId] = this;
 | 
|    }
 | 
|  
 | 
| -  /** 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;
 | 
| -      }
 | 
| +  static int get _isolateId {
 | 
| +    // TODO(vsm): Make this coherent with existing isolate code.
 | 
| +    if (_cachedIsolateId == null) {
 | 
| +      _cachedIsolateId = _getNewIsolateId();
 | 
|      }
 | 
| -    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();
 | 
| +    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));
 | 
| +      });
 | 
|      }
 | 
| -    _dispatch(e);
 | 
|    }
 | 
| -}
 | 
|  
 | 
| +  void close() {
 | 
| +    _portMap.remove(_portId);
 | 
| +    if (_portSubscription != null) _portSubscription.cancel();
 | 
| +  }
 | 
|  
 | 
| -/**
 | 
| - * 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 {
 | 
| +  SendPortSync toSendPort() {
 | 
| +    return new _LocalSendPortSync._internal(this);
 | 
| +  }
 | 
|  
 | 
| -  /** Named constructor to produce a stream for onKeyPress events. */
 | 
| -  static Stream<KeyEvent> onKeyPress(EventTarget target) =>
 | 
| -      new _KeyboardEventHandler('keypress').forTarget(target);
 | 
| +  static SendPortSync _lookup(int isolateId, int portId) {
 | 
| +    if (isolateId == _isolateId) {
 | 
| +      return _portMap[portId].toSendPort();
 | 
| +    } else {
 | 
| +      return new _RemoteSendPortSync(isolateId, portId);
 | 
| +    }
 | 
| +  }
 | 
| +}
 | 
|  
 | 
| -  /** Named constructor to produce a stream for onKeyUp events. */
 | 
| -  static Stream<KeyEvent> onKeyUp(EventTarget target) =>
 | 
| -      new _KeyboardEventHandler('keyup').forTarget(target);
 | 
| +get _isolateId => ReceivePortSync._isolateId;
 | 
|  
 | 
| -  /** Named constructor to produce a stream for onKeyDown events. */
 | 
| -  static Stream<KeyEvent> onKeyDown(EventTarget target) =>
 | 
| -    new _KeyboardEventHandler('keydown').forTarget(target);
 | 
| +void _dispatchEvent(String receiver, var message) {
 | 
| +  var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
 | 
| +    detail: json.stringify(message));
 | 
| +  window.dispatchEvent(event);
 | 
|  }
 | 
| +
 | 
| +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.
 | 
| @@ -29696,703 +29995,1310 @@ abstract class KeyName {
 | 
|    static const String DEAD_ABOVE_DOT = "DeadAboveDot";
 | 
|  
 | 
|    /**
 | 
| -   * The Combining Diaeresis (Double Dot Abode, Umlaut, Greek Dialytika,
 | 
| -   * Double Derivative, Dead Diaeresis) key
 | 
| +   * 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";
 | 
| +
 | 
| +  /**
 | 
| +   * The Combining Katakana-Hiragana Voiced Sound Mark (Dead Voiced Sound) key
 | 
| +   */
 | 
| +  static const String DEAD_VOICED_SOUND = "DeadVoicedSound";
 | 
| +
 | 
| +  /**
 | 
| +   * 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) 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.
 | 
| +
 | 
| +
 | 
| +/**
 | 
| + * 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 set of keys that have been pressed down without seeing their
 | 
| +   * corresponding keyup event.
 | 
| +   */
 | 
| +  final List<KeyboardEvent> _keyDownList = <KeyboardEvent>[];
 | 
| +
 | 
| +  /** The type of KeyEvent we are tracking (keyup, keydown, keypress). */
 | 
| +  final String _type;
 | 
| +
 | 
| +  /** The element we are watching for events to happen on. */
 | 
| +  final EventTarget _target;
 | 
| +
 | 
| +  // 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];
 | 
| +
 | 
| +  /** Controller to produce KeyEvents for the stream. */
 | 
| +  final StreamController _controller = new StreamController(sync: true);
 | 
| +
 | 
| +  static const _EVENT_TYPE = 'KeyEvent';
 | 
| +
 | 
| +  /**
 | 
| +   * 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
 | 
| +  };
 | 
| +
 | 
| +  /** Return a stream for KeyEvents for the specified target. */
 | 
| +  Stream<KeyEvent> forTarget(EventTarget e, {bool useCapture: false}) {
 | 
| +    return new _KeyboardEventHandler.initializeAllEventListeners(
 | 
| +        _type, e).stream;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * 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.");
 | 
| +    }
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * General constructor, performs basic initialization for our improved
 | 
| +   * KeyboardEvent controller.
 | 
| +   */
 | 
| +  _KeyboardEventHandler(this._type) :
 | 
| +    _target = null, super(_EVENT_TYPE) {
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * 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);
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Notify all callback listeners that a KeyEvent of the relevant type has
 | 
| +   * occurred.
 | 
| +   */
 | 
| +  bool _dispatch(KeyEvent event) {
 | 
| +    if (event.type == _type)
 | 
| +      _controller.add(event);
 | 
| +  }
 | 
| +
 | 
| +  /** Determine if caps lock is one of the currently depressed keys. */
 | 
| +  bool get _capsLockOn =>
 | 
| +      _keyDownList.any((var element) => element.keyCode == KeyCode.CAPS_LOCK);
 | 
| +
 | 
| +  /**
 | 
| +   * 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;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * 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;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * Returns true if the key fires a keypress event in the current browser.
 | 
|     */
 | 
| -  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";
 | 
| +  bool _firesKeyPressEvent(KeyEvent event) {
 | 
| +    if (!Device.isIE && !Device.isWebKit) {
 | 
| +      return true;
 | 
| +    }
 | 
|  
 | 
| -  /** The Combining Caron (Hacek, V Above, Dead Caron) key */
 | 
| -  static const String DEAD_CARON = "DeadCaron";
 | 
| +    if (Device.userAgent.contains('Mac') && event.altKey) {
 | 
| +      return KeyCode.isCharacterKey(event.keyCode);
 | 
| +    }
 | 
|  
 | 
| -  /** The Combining Cedilla (Dead Cedilla) key */
 | 
| -  static const String DEAD_CEDILLA = "DeadCedilla";
 | 
| +    // Alt but not AltGr which is represented as Alt+Ctrl.
 | 
| +    if (event.altKey && !event.ctrlKey) {
 | 
| +      return false;
 | 
| +    }
 | 
|  
 | 
| -  /** The Combining Ogonek (Nasal Hook, Dead Ogonek) key */
 | 
| -  static const String DEAD_OGONEK = "DeadOgonek";
 | 
| +    // 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 Greek Ypogegrammeni (Greek Non-Spacing Iota Below, Iota
 | 
| -   * Subscript, Dead Iota) key
 | 
| -   */
 | 
| -  static const String DEAD_IOTA = "DeadIota";
 | 
| +    // 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 Katakana-Hiragana Voiced Sound Mark (Dead Voiced Sound) key
 | 
| -   */
 | 
| -  static const String DEAD_VOICED_SOUND = "DeadVoicedSound";
 | 
| +    switch (event.keyCode) {
 | 
| +      case KeyCode.ENTER:
 | 
| +        // IE9 does not fire keypress on ENTER.
 | 
| +        return !Device.isIE;
 | 
| +      case KeyCode.ESC:
 | 
| +        return !Device.isWebKit;
 | 
| +    }
 | 
|  
 | 
| -  /**
 | 
| -   * The Combining Katakana-Hiragana Semi-Voiced Sound Mark (Dead Semivoiced
 | 
| -   * Sound) key
 | 
| -   */
 | 
| -  static const String DEC_SEMIVOICED_SOUND= "DeadSemivoicedSound";
 | 
| +    return KeyCode.isCharacterKey(event.keyCode);
 | 
| +  }
 | 
|  
 | 
|    /**
 | 
| -   * Key value used when an implementation is unable to identify another key
 | 
| -   * value, due to either hardware, platform, or software constraints
 | 
| +   * Normalize the keycodes to the IE KeyCodes (this is what Chrome, IE, and
 | 
| +   * Opera all use).
 | 
|     */
 | 
| -  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;
 | 
| +  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;
 | 
|    }
 | 
|  
 | 
| -  int get hashCode => JenkinsSmiHash.hash2(x.hashCode, y.hashCode);
 | 
| +  /** 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();
 | 
| +    }
 | 
|  
 | 
| -  Point operator +(Point other) {
 | 
| -    return new Point(x + other.x, y + other.y);
 | 
| +    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);
 | 
|    }
 | 
|  
 | 
| -  Point operator -(Point other) {
 | 
| -    return new Point(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);
 | 
| +
 | 
| +    // 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 *(num factor) {
 | 
| -    return new Point(x * factor, y * factor);
 | 
| +  /** 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 the straight line (Euclidean) distance between the origin (0, 0) and
 | 
| -   * this point.
 | 
| -   */
 | 
| -  num get magnitude => sqrt(x * x + y * y);
 | 
|  
 | 
| -  /**
 | 
| -   * 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);
 | 
| -  }
 | 
| +/**
 | 
| + * 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 {
 | 
|  
 | 
| -  /**
 | 
| -   * 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 onKeyPress events. */
 | 
| +  static Stream<KeyEvent> onKeyPress(EventTarget target) =>
 | 
| +      new _KeyboardEventHandler('keypress').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 onKeyUp events. */
 | 
| +  static Stream<KeyEvent> onKeyUp(EventTarget target) =>
 | 
| +      new _KeyboardEventHandler('keyup').forTarget(target);
 | 
|  
 | 
| -  /**
 | 
| -   * Truncates x and y to integers and returns the result as a new point.
 | 
| -   */
 | 
| -  Point toInt() => new Point(x.toInt(), y.toInt());
 | 
| +  /** Named constructor to produce a stream for onKeyDown events. */
 | 
| +  static Stream<KeyEvent> onKeyDown(EventTarget target) =>
 | 
| +    new _KeyboardEventHandler('keydown').forTarget(target);
 | 
|  }
 | 
| -// Copyright (c) 2011, 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.
 | 
|  
 | 
|  
 | 
| +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());
 | 
| +/**
 | 
| + * Scheduler which uses window.setImmediate to schedule events.
 | 
| + */
 | 
| +class _SetImmediateScheduler extends _MicrotaskScheduler {
 | 
| +  _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
 | 
|  
 | 
| -  /**
 | 
| -   * Truncates coordinates to integers and returns the result as a new
 | 
| -   * rectangle.
 | 
| -   */
 | 
| -  Rect toInt() => new Rect(left.toInt(), top.toInt(), width.toInt(),
 | 
| -      height.toInt());
 | 
| +  void _schedule() {
 | 
| +    window._setImmediate(_handleImmediate);
 | 
| +  }
 | 
|  
 | 
| -  Point get topLeft => new Point(this.left, this.top);
 | 
| -  Point get bottomRight => new Point(this.left + this.width,
 | 
| -      this.top + this.height);
 | 
| +  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();
 | 
| -
 | 
| -  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;
 | 
| -    }
 | 
| +void _addMicrotaskCallback(TimeoutHandler callback) {
 | 
| +  if (_pendingMicrotasks == null) {
 | 
| +    _pendingMicrotasks = <TimeoutHandler>[];
 | 
| +    _maybeScheduleMicrotaskFrame();
 | 
| +  }
 | 
| +  _pendingMicrotasks.add(callback);
 | 
| +}
 | 
|  
 | 
| -    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
 | 
| +// 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.
 | 
|  
 | 
|  
 | 
| -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);
 | 
| -      }
 | 
| -    });
 | 
| +/**
 | 
| + * 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 {
 | 
|  
 | 
| -    request.send();
 | 
| +  final List<NodeValidator> _validators = <NodeValidator>[];
 | 
|  
 | 
| -    return request;
 | 
| +  NodeValidatorBuilder() {
 | 
|    }
 | 
| -}
 | 
| -// 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.
 | 
| -
 | 
| -
 | 
| -_serialize(var message) {
 | 
| -  return new _JsSerializer().traverse(message);
 | 
| -}
 | 
|  
 | 
| -class _JsSerializer extends _Serializer {
 | 
| +  /**
 | 
| +   * 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();
 | 
| +  }
 | 
|  
 | 
| -  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 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));
 | 
|    }
 | 
|  
 | 
| -  visitJsSendPortSync(_JsSendPortSync x) {
 | 
| -    return [ 'sendport', 'nativejs', x._id ];
 | 
| +  /**
 | 
| +   * 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));
 | 
|    }
 | 
|  
 | 
| -  visitLocalSendPortSync(_LocalSendPortSync x) {
 | 
| -    return [ 'sendport', 'dart',
 | 
| -             ReceivePortSync._isolateId, x._receivePort._portId ];
 | 
| +  /**
 | 
| +   * 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());
 | 
|    }
 | 
|  
 | 
| -  visitSendPort(SendPort x) {
 | 
| -    throw new UnimplementedError('Asynchronous send port not yet implemented.');
 | 
| +  /**
 | 
| +   * 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));
 | 
|    }
 | 
|  
 | 
| -  visitRemoteSendPortSync(_RemoteSendPortSync x) {
 | 
| -    return [ 'sendport', 'dart', x._isolateId, x._portId ];
 | 
| +  /**
 | 
| +   * Allow SVG elements and attributes except for known bad ones.
 | 
| +   */
 | 
| +  void allowSvg() {
 | 
| +    add(new _SvgNodeValidator());
 | 
|    }
 | 
| -}
 | 
|  
 | 
| -_deserialize(var message) {
 | 
| -  return new _JsDeserializer().deserialize(message);
 | 
| -}
 | 
| +  /**
 | 
| +   * 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}) {
 | 
|  
 | 
| +    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();
 | 
| +    }
 | 
|  
 | 
| -class _JsDeserializer extends _Deserializer {
 | 
| +    add(new _CustomElementNodeValidator(
 | 
| +        uriPolicy,
 | 
| +        [tagNameUpper],
 | 
| +        attrs,
 | 
| +        uriAttrs,
 | 
| +        false,
 | 
| +        true));
 | 
| +  }
 | 
|  
 | 
| -  static const _UNSPECIFIED = const Object();
 | 
| +  /**
 | 
| +   * 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}) {
 | 
|  
 | 
| -  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';
 | 
| +    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();
 | 
|      }
 | 
| +
 | 
| +    add(new _CustomElementNodeValidator(
 | 
| +        uriPolicy,
 | 
| +        [tagNameUpper, baseNameUpper],
 | 
| +        attrs,
 | 
| +        uriAttrs,
 | 
| +        true,
 | 
| +        false));
 | 
|    }
 | 
| -}
 | 
|  
 | 
| -// The receiver is JS.
 | 
| -class _JsSendPortSync implements SendPortSync {
 | 
| +  void allowElement(String tagName, {UriPolicy uriPolicy,
 | 
| +    Iterable<String> attributes,
 | 
| +    Iterable<String> uriAttributes}) {
 | 
|  
 | 
| -  final num _id;
 | 
| -  _JsSendPortSync(this._id);
 | 
| +    allowCustomElement(tagName, uriPolicy: uriPolicy,
 | 
| +        attributes: attributes,
 | 
| +        uriAttributes: uriAttributes);
 | 
| +  }
 | 
|  
 | 
| -  callSync(var message) {
 | 
| -    var serialized = _serialize(message);
 | 
| -    var result = _callPortSync(_id, serialized);
 | 
| -    return _deserialize(result);
 | 
| +  /**
 | 
| +   * 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());
 | 
|    }
 | 
|  
 | 
| -  bool operator==(var other) {
 | 
| -    return (other is _JsSendPortSync) && (_id == other._id);
 | 
| +  /**
 | 
| +   * 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',
 | 
| +      ]);
 | 
|    }
 | 
|  
 | 
| -  int get hashCode => _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;
 | 
| +  }
 | 
|  }
 | 
|  
 | 
| -// TODO(vsm): Differentiate between Dart2Js and Dartium isolates.
 | 
| -// The receiver is a different Dart isolate, compiled to JS.
 | 
| -class _RemoteSendPortSync implements SendPortSync {
 | 
| +class _CustomElementNodeValidator extends _SimpleNodeValidator {
 | 
| +  final bool allowTypeExtension;
 | 
| +  final bool allowCustomTag;
 | 
|  
 | 
| -  int _isolateId;
 | 
| -  int _portId;
 | 
| -  _RemoteSendPortSync(this._isolateId, this._portId);
 | 
| +  _CustomElementNodeValidator(UriPolicy uriPolicy,
 | 
| +      Iterable<String> allowedElements,
 | 
| +      Iterable<String> allowedAttributes,
 | 
| +      Iterable<String> allowedUriAttributes,
 | 
| +      bool allowTypeExtension,
 | 
| +      bool allowCustomTag):
 | 
|  
 | 
| -  callSync(var message) {
 | 
| -    var serialized = _serialize(message);
 | 
| -    var result = _call(_isolateId, _portId, serialized);
 | 
| -    return _deserialize(result);
 | 
| -  }
 | 
| +      super(uriPolicy,
 | 
| +          allowedElements: allowedElements,
 | 
| +          allowedAttributes: allowedAttributes,
 | 
| +          allowedUriAttributes: allowedUriAttributes),
 | 
| +      this.allowTypeExtension = allowTypeExtension == true,
 | 
| +      this.allowCustomTag = allowCustomTag == true;
 | 
|  
 | 
| -  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;
 | 
| +  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);
 | 
|    }
 | 
|  
 | 
| -  bool operator==(var other) {
 | 
| -    return (other is _RemoteSendPortSync) && (_isolateId == other._isolateId)
 | 
| -      && (_portId == other._portId);
 | 
| +  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;
 | 
|    }
 | 
| -
 | 
| -  int get hashCode => _isolateId >> 16 + _portId;
 | 
|  }
 | 
|  
 | 
| -// The receiver is in the same Dart isolate, compiled to JS.
 | 
| -class _LocalSendPortSync implements SendPortSync {
 | 
| +class _TemplatingNodeValidator extends _SimpleNodeValidator {
 | 
| +  static const _TEMPLATE_ATTRS =
 | 
| +      const <String>['bind', 'if', 'ref', 'repeat', 'syntax'];
 | 
|  
 | 
| -  ReceivePortSync _receivePort;
 | 
| +  final Set<String> _templateAttrs;
 | 
|  
 | 
| -  _LocalSendPortSync._internal(this._receivePort);
 | 
| +  _TemplatingNodeValidator():
 | 
| +      super(null,
 | 
| +          allowedElements: [
 | 
| +            'TEMPLATE'
 | 
| +          ],
 | 
| +          allowedAttributes: _TEMPLATE_ATTRS.map((attr) => 'TEMPLATE::$attr')),
 | 
| +      _templateAttrs = new Set<String>.from(_TEMPLATE_ATTRS) {
 | 
| +  }
 | 
|  
 | 
| -  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 (super.allowsAttribute(element, attributeName, value)) {
 | 
| +      return true;
 | 
| +    }
 | 
| +
 | 
| +    if (attributeName == 'template' && value == "") {
 | 
| +      return true;
 | 
| +    }
 | 
| +
 | 
| +    if (element.attributes['template'] == "" ) {
 | 
| +      return _templateAttrs.contains(attributeName);
 | 
| +    }
 | 
| +    return false;
 | 
|    }
 | 
| +}
 | 
|  
 | 
| -  bool operator==(var other) {
 | 
| -    return (other is _LocalSendPortSync)
 | 
| -      && (_receivePort == other._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;
 | 
|    }
 | 
|  
 | 
| -  int get hashCode => _receivePort.hashCode;
 | 
| +  bool allowsAttribute(Element element, String attributeName, String value) {
 | 
| +    if (attributeName == 'is' || attributeName.startsWith('on')) {
 | 
| +      return false;
 | 
| +    }
 | 
| +    return allowsElement(element);
 | 
| +  }
 | 
|  }
 | 
| +// 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.
 | 
|  
 | 
| -// 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 {
 | 
| +/**
 | 
| + * A utility class for representing two-dimensional positions.
 | 
| + */
 | 
| +class Point {
 | 
| +  final num x;
 | 
| +  final num y;
 | 
|  
 | 
| -  static Map<int, ReceivePortSync> _portMap;
 | 
| -  static int _portIdCount;
 | 
| -  static int _cachedIsolateId;
 | 
| +  const Point([num x = 0, num y = 0]): x = x, y = y;
 | 
|  
 | 
| -  num _portId;
 | 
| -  Function _callback;
 | 
| -  StreamSubscription _portSubscription;
 | 
| +  String toString() => '($x, $y)';
 | 
|  
 | 
| -  ReceivePortSync() {
 | 
| -    if (_portIdCount == null) {
 | 
| -      _portIdCount = 0;
 | 
| -      _portMap = new Map<int, ReceivePortSync>();
 | 
| -    }
 | 
| -    _portId = _portIdCount++;
 | 
| -    _portMap[_portId] = this;
 | 
| +  bool operator ==(other) {
 | 
| +    if (other is !Point) return false;
 | 
| +    return 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;
 | 
| +  int get hashCode => JenkinsSmiHash.hash2(x.hashCode, y.hashCode);
 | 
| +
 | 
| +  Point operator +(Point other) {
 | 
| +    return new Point(x + other.x, y + other.y);
 | 
|    }
 | 
|  
 | 
| -  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));
 | 
| -      });
 | 
| -    }
 | 
| +  Point operator -(Point other) {
 | 
| +    return new Point(x - other.x, y - other.y);
 | 
|    }
 | 
|  
 | 
| -  void close() {
 | 
| -    _portMap.remove(_portId);
 | 
| -    if (_portSubscription != null) _portSubscription.cancel();
 | 
| +  Point operator *(num factor) {
 | 
| +    return new Point(x * factor, y * factor);
 | 
|    }
 | 
|  
 | 
| -  SendPortSync toSendPort() {
 | 
| -    return new _LocalSendPortSync._internal(this);
 | 
| +  /**
 | 
| +   * Get the straight line (Euclidean) distance between the origin (0, 0) and
 | 
| +   * this point.
 | 
| +   */
 | 
| +  num get magnitude => sqrt(x * x + y * y);
 | 
| +
 | 
| +  /**
 | 
| +   * 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);
 | 
|    }
 | 
|  
 | 
| -  static SendPortSync _lookup(int isolateId, int portId) {
 | 
| -    if (isolateId == _isolateId) {
 | 
| -      return _portMap[portId].toSendPort();
 | 
| -    } else {
 | 
| -      return new _RemoteSendPortSync(isolateId, portId);
 | 
| -    }
 | 
| +  /**
 | 
| +   * 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;
 | 
|    }
 | 
| -}
 | 
|  
 | 
| -get _isolateId => ReceivePortSync._isolateId;
 | 
| +  Point ceil() => new Point(x.ceil(), y.ceil());
 | 
| +  Point floor() => new Point(x.floor(), y.floor());
 | 
| +  Point round() => new Point(x.round(), y.round());
 | 
|  
 | 
| -void _dispatchEvent(String receiver, var message) {
 | 
| -  var event = new CustomEvent(receiver, canBubble: false, cancelable:false,
 | 
| -    detail: json.stringify(message));
 | 
| -  window.dispatchEvent(event);
 | 
| +  /**
 | 
| +   * Truncates x and y to integers and returns the result as a new point.
 | 
| +   */
 | 
| +  Point toInt() => new Point(x.toInt(), y.toInt());
 | 
|  }
 | 
| -
 | 
| -String _getPortSyncEventData(CustomEvent event) => event.detail;
 | 
| -// 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);
 | 
| +
 | 
| +      if (y0 <= y1) {
 | 
| +        return new Rect(x0, y0, x1 - x0, y1 - y0);
 | 
| +      }
 | 
| +    }
 | 
| +    return null;
 | 
|    }
 | 
|  
 | 
| -  _handleMutation(List<MutationRecord> mutations, MutationObserver observer) {
 | 
| -    this._onCallback();
 | 
| +
 | 
| +  /**
 | 
| +   * 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);
 | 
|    }
 | 
| -}
 | 
|  
 | 
| -/**
 | 
| - * Scheduler which uses window.setImmediate to schedule events.
 | 
| - */
 | 
| -class _SetImmediateScheduler extends _MicrotaskScheduler {
 | 
| -  _SetImmediateScheduler(_MicrotaskCallback callback): super(callback);
 | 
| +  /**
 | 
| +   * 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);
 | 
|  
 | 
| -  void _schedule() {
 | 
| -    window._setImmediate(_handleImmediate);
 | 
| +    var left = min(this.left, rect.left);
 | 
| +    var top = min(this.top, rect.top);
 | 
| +
 | 
| +    return new Rect(left, top, right - left, bottom - top);
 | 
|    }
 | 
|  
 | 
| -  void _handleImmediate() {
 | 
| -    this._onCallback();
 | 
| +  /**
 | 
| +   * 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;
 | 
| +  }
 | 
| +
 | 
| +  /**
 | 
| +   * 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);
 | 
|  }
 | 
|  
 | 
| -List<TimeoutHandler> _pendingMicrotasks;
 | 
| -_MicrotaskScheduler _microtaskScheduler = null;
 | 
|  
 | 
| -void _maybeScheduleMicrotaskFrame() {
 | 
| -  if (_microtaskScheduler == null) {
 | 
| -    _microtaskScheduler =
 | 
| -      new _MicrotaskScheduler.best(_completeMicrotasks);
 | 
| -  }
 | 
| -  _microtaskScheduler.maybeSchedule();
 | 
| -}
 | 
|  
 | 
|  /**
 | 
| - * Registers a [callback] which is called after the current execution stack
 | 
| - * unwinds.
 | 
| + * A class for representing two-dimensional rectangles.
 | 
| + *
 | 
| + * This class is distinctive from RectBase in that it enforces that its
 | 
| + * properties are immutable.
 | 
|   */
 | 
| -void _addMicrotaskCallback(TimeoutHandler callback) {
 | 
| -  if (_pendingMicrotasks == null) {
 | 
| -    _pendingMicrotasks = <TimeoutHandler>[];
 | 
| -    _maybeScheduleMicrotaskFrame();
 | 
| -  }
 | 
| -  _pendingMicrotasks.add(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;
 | 
| +    }
 | 
|  
 | 
| -/**
 | 
| - * Complete all pending microtasks.
 | 
| - */
 | 
| -void _completeMicrotasks() {
 | 
| -  var callbacks = _pendingMicrotasks;
 | 
| -  _pendingMicrotasks = null;
 | 
| -  for (var callback in callbacks) {
 | 
| -    callback();
 | 
| +    return new Rect(left, top, width, height);
 | 
|    }
 | 
|  }
 | 
|  // Copyright (c) 2012, the Dart project authors.  Please see the AUTHORS file
 | 
| @@ -30707,6 +31613,89 @@ 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.
 | 
| @@ -31135,59 +32124,202 @@ class Platform {
 | 
|      }
 | 
|    }
 | 
|  }
 | 
| -// Copyright (c) 2011, the Dart project authors.  Please see the AUTHORS file
 | 
| +// 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.
 | 
|  
 | 
|  
 | 
| -// 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;
 | 
| +/**
 | 
| + * 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);
 | 
|      }
 | 
| -    _current = null;
 | 
| -    _position = _length;
 | 
| -    return false;
 | 
| +    return true;
 | 
|    }
 | 
|  
 | 
| -  T get current => _current;
 | 
| +  bool allowsAttribute(Element element, String attributeName, String value) {
 | 
| +    if (!validator.allowsAttribute(element, attributeName, value)) {
 | 
| +      throw new ArgumentError('${element.tagName}[$attributeName="$value"]');
 | 
| +    }
 | 
| +  }
 | 
|  }
 | 
|  
 | 
| -// 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;
 | 
| +/**
 | 
| + * 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;
 | 
| +      }
 | 
|      }
 | 
| -    _current = null;
 | 
| -    _position = _array.length;
 | 
| -    return false;
 | 
| +    walk(node);
 | 
|    }
 | 
|  
 | 
| -  T get current => _current;
 | 
| +  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);
 | 
| +          }
 | 
| +        }
 | 
| +
 | 
| +        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();
 | 
| +    }
 | 
| +  }
 | 
|  }
 | 
| 
 |