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>