Add "created-watcher" to the web_components package. This was adapted from
Pete's CL (https://codereview.chromium.org/184033007/). I've marked the
differences in the CL.

R=jmesserly@google.com

Review URL: https://codereview.chromium.org//319263002

git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart/pkg/web_components@37144 260f80e4-7a28-3924-810f-c04153c831b5
diff --git a/lib/dart_support.js b/lib/dart_support.js
index 503faee..4e1995a 100644
--- a/lib/dart_support.js
+++ b/lib/dart_support.js
@@ -2,6 +2,7 @@
 // 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.
 
+// Teaches dart2js about the wrapping that is done by the Shadow DOM polyfill.
 (function() {
   var ShadowDOMPolyfill = window.ShadowDOMPolyfill;
   if (!ShadowDOMPolyfill) return;
@@ -63,3 +64,58 @@
     }
   });
 })();
+
+// Updates document.registerElement so Dart can see when Javascript custom
+// elements are created, and wrap them to provide a Dart friendly API.
+(function (doc) {
+  var upgraders = {};
+  var originalRegisterElement = doc.registerElement;
+  if (!originalRegisterElement) {
+    throw new Error('document.registerElement is not present.');
+  }
+
+  function registerElement(name, options) {
+    var proto, extendsOption;
+    if (options !== undefined) {
+      proto = options.prototype;
+    } else {
+      proto = Object.create(HTMLElement.prototype);
+      options = {protoptype: proto};
+    }
+
+    var original = proto.createdCallback;
+    var newCallback = function() {
+      original.call(this);
+      var name = this.getAttribute('is') || this.localName;
+      var upgrader = upgraders[name.toLowerCase()];
+      if (upgrader) upgrader(this);
+    };
+
+    var descriptor = Object.getOwnPropertyDescriptor(proto, 'createdCallback');
+    if (!descriptor || descriptor.writable) {
+      proto.createdCallback = newCallback;
+    } else if (descriptor.configurable) {
+      descriptor['value'] = newCallback;
+      Object.defineProperty(proto, 'createdCallback', descriptor);
+    } else {
+      console.error("Couldn't patch prototype to notify Dart when " + name +
+          " elements are created. This can be fixed by making the " +
+          "createdCallback in " + name + " a configurable property.");
+    }
+    return originalRegisterElement.call(this, name, options);
+  }
+
+  function registerDartTypeUpgrader(name, upgrader) {
+    if (!upgrader) return;
+    name = name.toLowerCase();
+    var existing = upgraders[name];
+    if (existing) {
+      console.error('Already have a Dart type associated with ' + name);
+      return;
+    }
+    upgraders[name] = upgrader;
+  }
+
+  doc._registerDartTypeUpgrader = registerDartTypeUpgrader;
+  doc.registerElement = registerElement;
+})(document);
diff --git a/lib/interop.dart b/lib/interop.dart
new file mode 100644
index 0000000..058ce76
--- /dev/null
+++ b/lib/interop.dart
@@ -0,0 +1,33 @@
+// Copyright (c) 2014, 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.
+
+/// Provides support for associating a Dart type for Javascript Custom Elements.
+/// 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;
+
+final _doc = new JsObject.fromBrowserObject(document);
+
+/// Returns whether [registerDartType] is supported, which requires to have
+/// `dart_support.js` already loaded in the page.
+bool get isSupported => _doc.hasProperty('_registerDartTypeUpgrader');
+
+/// Watches when Javascript custom elements named [tagName] are created and
+/// associates the created element with the given [dartType]. Only one Dart type
+/// can be registered for a given tag name.
+void registerDartType(String tagName, Type dartType, {String extendsTag}) {
+  if (!isSupported) {
+    throw new UnsupportedError("Couldn't find "
+        "`document._registerDartTypeUpgrader`. Please make sure that "
+        "`packages/web_components/dart_support.js` is loaded and available "
+        "before calling this function.");
+  }
+
+  var upgrader = document.createElementUpgrader(
+      dartType, extendsTag: extendsTag);
+  _doc.callMethod('_registerDartTypeUpgrader', [tagName, upgrader.upgrade]);
+}
diff --git a/pubspec.yaml b/pubspec.yaml
index 1cc75cd..e635c55 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -1,5 +1,5 @@
 name: web_components
-version: 0.3.5
+version: 0.3.5-dev
 author: Polymer.dart Authors <web-ui-dev@dartlang.org>
 homepage: https://www.dartlang.org/polymer-dart/
 description: >
diff --git a/test/interop2_test.dart b/test/interop2_test.dart
new file mode 100644
index 0000000..e318b9c
--- /dev/null
+++ b/test/interop2_test.dart
@@ -0,0 +1,92 @@
+// 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(sigmund): delete this file after issue 19322 is resolved.
+// This test is a trimmed down version of interop_test. interop_test is
+// currently failing in dart2js because of bug/19322, so we have this copy
+// that excludes the causes of failure to get some test coverage in .js.
+library web_components.interop2_test;
+
+import 'dart:html';
+import 'dart:async';
+import 'dart:js' show context, JsObject;
+import 'package:unittest/html_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:web_components/interop.dart';
+
+main() {
+  useHtmlConfiguration();
+  registerDartType('x-a', XAWrapper);
+  registerDartType('x-b', XBWrapper, extendsTag: 'div');
+  registerDartType('x-c', XCWrapper);
+
+  test('interop is supported', () {
+    expect(isSupported, isTrue);
+  });
+
+  test('previously created elements are not upgraded', () {
+    var a = document.querySelector('x-a');
+    expect(a is HtmlElement, isTrue, reason: 'x-a is HtmlElement');
+    expect(a is XAWrapper, isFalse, reason: 'x-a should not be upgraded yet');
+    expect(_readX(a), 0);
+
+    var c = document.querySelector('x-c');
+    expect(c is HtmlElement, isTrue, reason: 'x-c is HtmlElement');
+    expect(c is XCWrapper, isFalse, reason: 'x-c should not be upgraded yet');
+    expect(_readX(c), null, reason: 'x-c has not been registered in JS yet');
+  });
+
+  test('events seen for anything created after registering Dart type', () {
+    context.callMethod('addA');
+    var list = document.querySelectorAll('x-a');
+    expect(list.length, 2);
+    var a = list[1];
+    expect(a is HtmlElement, isTrue, reason: 'x-a is HtmlElement');
+    expect(a is XAWrapper, isTrue, reason: 'x-a is upgraded to XAWrapper');
+    expect(a.x, 2);
+    expect(a.wrapperCount, 0);
+  });
+
+  test('events seen if Dart type is registered before registerElement', () {
+    var c = document.querySelector('x-c');
+    expect(c is XCWrapper, isFalse);
+    expect(_readX(c), null, reason: 'x-c has not been registered in JS yet');
+
+    context.callMethod('registerC');
+    c = document.querySelector('x-c');
+    expect(c is XCWrapper, isTrue);
+    expect(c.x, 3);
+    expect(c.wrapperCount, 1);
+
+    context.callMethod('addC');
+    var list = document.querySelectorAll('x-c');
+    expect(list.length, 2);
+    expect(list[0], c);
+    c = list[1];
+    expect(c is HtmlElement, isTrue, reason: 'x-c is HtmlElement');
+    expect(c is XCWrapper, isTrue, reason: 'x-c is upgraded to XCWrapper');
+    expect(c.x, 4);
+    expect(c.wrapperCount, 2);
+  });
+}
+int _count = 0;
+
+abstract class Wrapper {
+  int wrapperCount = _count++;
+  int get x => _readX(this);
+}
+
+_readX(e) => new JsObject.fromBrowserObject(e)['x'];
+
+class XAWrapper extends HtmlElement with Wrapper {
+  XAWrapper.created() : super.created();
+}
+
+class XBWrapper extends DivElement with Wrapper {
+  XBWrapper.created() : super.created();
+}
+
+class XCWrapper extends HtmlElement with Wrapper {
+  XCWrapper.created() : super.created();
+}
diff --git a/test/interop2_test.html b/test/interop2_test.html
new file mode 100644
index 0000000..3c4f10e
--- /dev/null
+++ b/test/interop2_test.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="dart.unittest" content="full-stack-traces">
+  <title> interop_test.html </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/platform.js"></script>
+  <script src="/packages/web_components/dart_support.js"></script>
+</head>
+<body>
+  <h1> Running interop_test </h1>
+  <script>
+    var counter = 0;
+    var A = function() {};
+    A.prototype = Object.create(HTMLElement.prototype);
+    A.prototype.inc = function() { this.x = counter++; };
+    A.prototype.createdCallback = function() { this.inc(); };
+
+    var B = function() {};
+    B.prototype = Object.create(HTMLDivElement.prototype);
+    B.prototype.createdCallback = function() { this.x = counter++; };
+    B.extends = 'div';
+
+    document.registerElement('x-a', A);
+    document.registerElement('x-b', B);
+
+    function registerC() {
+      var proto = Object.create(HTMLElement.prototype, {
+        inc: { value: function() { this.x = counter++; } }, 
+        createdCallback: {
+          value: function() { this.inc(); },
+          configurable: true},
+      });
+      document.registerElement('x-c', {prototype: proto});
+    }
+    function addA() {
+      document.body.appendChild(document.createElement('x-a'));
+    }
+    function addB() {
+      document.body.appendChild(document.createElement('div', 'x-b'));
+    }
+    function addC() {
+      document.body.appendChild(document.createElement('x-c'));
+    }
+  </script>
+  <x-a id="i1"></x-a>
+  <div is="x-b" id="i2"></div>
+  <x-c id="i3"></x-b>
+  <script type="text/javascript"
+      src="/root_dart/tools/testing/dart/test_controller.js"></script>
+  %TEST_SCRIPTS%
+</body>
+</html>
diff --git a/test/interop_test.dart b/test/interop_test.dart
new file mode 100644
index 0000000..89b2971
--- /dev/null
+++ b/test/interop_test.dart
@@ -0,0 +1,102 @@
+// 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.
+
+library template_wrappers_test;
+
+import 'dart:html';
+import 'dart:async';
+import 'dart:js' show context, JsObject;
+import 'package:unittest/html_config.dart';
+import 'package:unittest/unittest.dart';
+import 'package:web_components/interop.dart';
+
+main() {
+  useHtmlConfiguration();
+  registerDartType('x-a', XAWrapper);
+  registerDartType('x-b', XBWrapper, extendsTag: 'div');
+  registerDartType('x-c', XCWrapper);
+
+  test('interop is supported', () {
+    expect(isSupported, isTrue);
+  });
+
+  test('previously created elements are not upgraded', () {
+    var a = document.querySelector('x-a');
+    expect(a is HtmlElement, isTrue, reason: 'x-a is HtmlElement');
+    expect(a is XAWrapper, isFalse, reason: 'x-a should not be upgraded yet');
+    expect(_readX(a), 0);
+
+    var b = document.querySelector('[is=x-b]');
+    expect(b is DivElement, isTrue, reason: 'x-b is DivElement');
+    expect(b is XBWrapper, isFalse, reason: 'x-b should not be upgraded yet');
+    expect(_readX(b), 1);
+
+    var c = document.querySelector('x-c');
+    expect(c is HtmlElement, isTrue, reason: 'x-c is HtmlElement');
+    expect(c is XCWrapper, isFalse, reason: 'x-c should not be upgraded yet');
+    expect(_readX(c), null, reason: 'x-c has not been registered in JS yet');
+  });
+
+  test('events seen for anything created after registering Dart type', () {
+    context.callMethod('addA');
+    var list = document.querySelectorAll('x-a');
+    expect(list.length, 2);
+    var a = list[1];
+    expect(a is HtmlElement, isTrue, reason: 'x-a is HtmlElement');
+    expect(a is XAWrapper, isTrue, reason: 'x-a is upgraded to XAWrapper');
+    expect(a.x, 2);
+    expect(a.wrapperCount, 0);
+
+    context.callMethod('addB');
+    list = document.querySelectorAll('[is=x-b]');
+    expect(list.length, 2);
+    var b = list[1];
+    expect(b is DivElement, isTrue, reason: 'x-b is DivElement');
+    expect(b is XBWrapper, isTrue, reason: 'x-b is upgraded to XBWrapper');
+    expect(b.x, 3);
+    expect(b.wrapperCount, 1);
+  });
+
+  test('events seen if Dart type is registered before registerElement', () {
+    var c = document.querySelector('x-c');
+    expect(c is XCWrapper, isFalse);
+    expect(_readX(c), null, reason: 'x-c has not been registered in JS yet');
+
+    context.callMethod('registerC');
+    c = document.querySelector('x-c');
+    expect(c is XCWrapper, isTrue);
+    expect(c.x, 4);
+    expect(c.wrapperCount, 2);
+
+    context.callMethod('addC');
+    var list = document.querySelectorAll('x-c');
+    expect(list.length, 2);
+    expect(list[0], c);
+    c = list[1];
+    expect(c is HtmlElement, isTrue, reason: 'x-c is HtmlElement');
+    expect(c is XCWrapper, isTrue, reason: 'x-c is upgraded to XCWrapper');
+    expect(c.x, 5);
+    expect(c.wrapperCount, 3);
+  });
+}
+int _count = 0;
+
+abstract class Wrapper {
+  int wrapperCount = _count++;
+  int get x => _readX(this);
+}
+
+_readX(e) => new JsObject.fromBrowserObject(e)['x'];
+
+class XAWrapper extends HtmlElement with Wrapper {
+  XAWrapper.created() : super.created();
+}
+
+class XBWrapper extends DivElement with Wrapper {
+  XBWrapper.created() : super.created();
+}
+
+class XCWrapper extends HtmlElement with Wrapper {
+  XCWrapper.created() : super.created();
+}
diff --git a/test/interop_test.html b/test/interop_test.html
new file mode 100644
index 0000000..3c4f10e
--- /dev/null
+++ b/test/interop_test.html
@@ -0,0 +1,59 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta http-equiv="X-UA-Compatible" content="IE=edge">
+  <meta name="dart.unittest" content="full-stack-traces">
+  <title> interop_test.html </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/platform.js"></script>
+  <script src="/packages/web_components/dart_support.js"></script>
+</head>
+<body>
+  <h1> Running interop_test </h1>
+  <script>
+    var counter = 0;
+    var A = function() {};
+    A.prototype = Object.create(HTMLElement.prototype);
+    A.prototype.inc = function() { this.x = counter++; };
+    A.prototype.createdCallback = function() { this.inc(); };
+
+    var B = function() {};
+    B.prototype = Object.create(HTMLDivElement.prototype);
+    B.prototype.createdCallback = function() { this.x = counter++; };
+    B.extends = 'div';
+
+    document.registerElement('x-a', A);
+    document.registerElement('x-b', B);
+
+    function registerC() {
+      var proto = Object.create(HTMLElement.prototype, {
+        inc: { value: function() { this.x = counter++; } }, 
+        createdCallback: {
+          value: function() { this.inc(); },
+          configurable: true},
+      });
+      document.registerElement('x-c', {prototype: proto});
+    }
+    function addA() {
+      document.body.appendChild(document.createElement('x-a'));
+    }
+    function addB() {
+      document.body.appendChild(document.createElement('div', 'x-b'));
+    }
+    function addC() {
+      document.body.appendChild(document.createElement('x-c'));
+    }
+  </script>
+  <x-a id="i1"></x-a>
+  <div is="x-b" id="i2"></div>
+  <x-c id="i3"></x-b>
+  <script type="text/javascript"
+      src="/root_dart/tools/testing/dart/test_controller.js"></script>
+  %TEST_SCRIPTS%
+</body>
+</html>