CustomElementProxy annotation

R=sigmund@google.com

Review URL: https://codereview.chromium.org//839013003
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9ce9c28..6aeadae 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,12 @@
+#### 0.10.1
+  * Added the `CustomElementProxy` annotation. This can be added to any class
+    which proxies a javascript custom element and is the equivalent of calling
+    `registerDartType`. In order to use this you will need to be using the
+    `initialize` package, and call its `run` method from your main function. It
+    is also recommended that you include the transformer from that package to
+    remove the use of mirrors at runtime, see
+    [initialize](https://github.com/dart-lang/initialize) for more information.
+
 #### 0.10.0
   * Updated to the `0.5.1` js version.
   * **Breaking Change** To remain consistent with the js repository all the
diff --git a/lib/custom_element_proxy.dart b/lib/custom_element_proxy.dart
new file mode 100644
index 0000000..f9df88f
--- /dev/null
+++ b/lib/custom_element_proxy.dart
@@ -0,0 +1,22 @@
+// Copyright (c) 2015, 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.
+import 'package:initialize/initialize.dart';
+import 'interop.dart';
+
+/// Annotation for a dart class which proxies a javascript custom element.
+/// This will not work unless `interop_support.js` is loaded.
+// TODO(jakemac): Add an @HtmlImport here to a new file which includes
+// `interop_support.js`. We will need to point everything else at that html file
+// as well for deduplication purposes (could even just copy it in as an inline
+// script so the js file no longer exists?).
+class CustomElementProxy implements Initializer<Type> {
+  final String tagName;
+  final String extendsTag;
+
+  const CustomElementProxy(this.tagName, {this.extendsTag});
+
+  void initialize(Type t) {
+    registerDartType(tagName, t, extendsTag: extendsTag);
+  }
+}
diff --git a/lib/interop.dart b/lib/interop.dart
index 1766850..4779b80 100644
--- a/lib/interop.dart
+++ b/lib/interop.dart
@@ -6,7 +6,6 @@
 /// This will not work unless `dart_support.js` is loaded.
 library web_components.interop;
 
-import 'dart:async' show Stream, StreamController;
 import 'dart:html' show document, Element;
 import 'dart:js' show JsObject, JsFunction;
 
@@ -27,8 +26,8 @@
         "available before calling this function.");
   }
 
-  var upgrader = document.createElementUpgrader(
-      dartType, extendsTag: extendsTag);
+  var upgrader =
+      document.createElementUpgrader(dartType, extendsTag: extendsTag);
 
   // Unfortunately the dart:html upgrader will throw on an already-upgraded
   // element, so we need to duplicate the type check to prevent that.
diff --git a/lib/interop_support.js b/lib/interop_support.js
index 790f828..9153f3c 100644
--- a/lib/interop_support.js
+++ b/lib/interop_support.js
@@ -35,7 +35,7 @@
 
     var original = proto.createdCallback;
     var newCallback = function() {
-      original.call(this);
+      if (original) original.call(this);
       var name = (this.getAttribute('is') || this.localName).toLowerCase();
       var upgrader = upgraders[name];
       if (upgrader) {
diff --git a/pubspec.yaml b/pubspec.yaml
index 4f21a81..ca97552 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: web_components
-version: 0.10.0
+version: 0.10.1
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 homepage: https://www.dartlang.org/polymer-dart/
 description: >
@@ -9,8 +9,14 @@
   standard tag. Shadow DOM is designed to provide encapsulation for custom
   elements, by hiding DOM subtrees under shadow roots. HTML Imports let authors
   bundle code and HTML as if they were libraries.
+dependencies:
+  initialize: '>=0.2.0 <0.3.0'
 dev_dependencies:
   unittest: '>0.11.0 <0.12.0'
   browser: '>0.10.0 <0.11.0'
+transformers:
+- initialize:
+    entry_point: test/custom_element_proxy_test.dart
+    html_entry_point: test/custom_element_proxy_test.html
 environment:
   sdk: ">=1.4.0-dev.6.6 <2.0.0"
diff --git a/test/custom_element_proxy_test.dart b/test/custom_element_proxy_test.dart
new file mode 100644
index 0000000..4e3c188
--- /dev/null
+++ b/test/custom_element_proxy_test.dart
@@ -0,0 +1,63 @@
+// Copyright (c) 2015, 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.
+library custom_element_proxy_test;
+
+import 'dart:html';
+import 'dart:js';
+import 'package:initialize/initialize.dart' as init;
+import 'package:unittest/html_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:web_components/custom_element_proxy.dart';
+
+@CustomElementProxy('basic-element')
+class BasicElement extends HtmlElement {
+  BasicElement.created() : super.created();
+
+  factory BasicElement() => document.createElement('basic-element');
+
+  bool get isBasicElement =>
+      new JsObject.fromBrowserObject(this)['isBasicElement'];
+}
+
+@CustomElementProxy('extended-element', extendsTag: 'input')
+class ExtendedElement extends InputElement {
+  ExtendedElement.created() : super.created();
+
+  factory ExtendedElement() =>
+      document.createElement('input', 'extended-element');
+
+  bool get isExtendedElement =>
+      new JsObject.fromBrowserObject(this)['isExtendedElement'];
+}
+
+main() {
+  useHtmlConfiguration();
+  init.run();
+
+  var container = querySelector('#container') as DivElement;
+
+  tearDown(() {
+    container.children.clear();
+  });
+
+  test('basic custom element', () {
+    container.append(new BasicElement());
+    container.appendHtml('<basic-element></basic_element>');
+    var elements = container.querySelectorAll('basic-element');
+    for (var element in elements) {
+      expect(element.runtimeType, BasicElement);
+      expect(element.isBasicElement, isTrue);
+    }
+  });
+
+  test('extends custom element', () {
+    container.append(new ExtendedElement());
+    container.appendHtml('<input is="extended-element" />');
+    var elements = container.querySelectorAll('extended-element');
+    for (var element in elements) {
+      expect(element.runtimeType, ExtendedElement);
+      expect(element.isExtendedElement, isTrue);
+    }
+  });
+}
diff --git a/test/custom_element_proxy_test.html b/test/custom_element_proxy_test.html
new file mode 100644
index 0000000..87053ed
--- /dev/null
+++ b/test/custom_element_proxy_test.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="dart.unittest" content="full-stack-traces">
+  <title> custom_element_proxy test </title>
+  <style>
+     .unittest-table { font-family:monospace; border:1px; }
+     .unittest-pass { background: #6b3;}
+     .unittest-fail { background: #d55;}
+     .unittest-error { background: #a11;}
+  </style>
+  <script src="/packages/web_components/webcomponents.js"></script>
+  <script src="/packages/web_components/interop_support.js"></script>
+  <script src="/packages/web_components/dart_support.js"></script>
+</head>
+<body>
+  <h1> Running custom_element_proxy </h1>
+  <div id="container"></div>
+
+  <script>
+    var basicElementProto = Object.create(HTMLElement.prototype);
+    basicElementProto.isBasicElement = true;
+    document.registerElement('basic-element', {
+      prototype: basicElementProto
+    });
+
+    var extendedElementProto = Object.create(HTMLInputElement.prototype);
+    extendedElementProto.isExtendedElement = true;
+    document.registerElement('extended-element', {
+      prototype: extendedElementProto,
+      extends: 'input'
+    });
+  </script>
+
+  <script type="text/javascript"
+      src="/root_dart/tools/testing/dart/test_controller.js"></script>
+  <script type="application/dart" src="custom_element_proxy_test.dart"></script>
+  <script type="text/javascript" src="/packages/browser/dart.js"></script>
+</body>
+</html>