From 2c98e2c2b34c02354c2ac6dc9ce1fe6ee1f137dd Mon Sep 17 00:00:00 2001 From: JiaLiPassion Date: Mon, 10 Sep 2018 01:44:26 +0900 Subject: [PATCH] feat(custom-element): patch customElement v1 APIs --- .travis.yml | 2 + file-size-limit.json | 4 +- karma-build-jasmine.es6.conf.js | 5 ++ karma-dist-sauce-jasmine.es6.conf.js | 6 ++ lib/browser/browser.ts | 6 +- lib/browser/register-element.ts | 41 +++++++--- package.json | 2 + sauce.es6.conf.js | 61 +++++++++++++++ test/browser/custom-element.spec.js | 108 +++++++++++++++++++++++++++ test/browser_es6_entry_point.ts | 9 +++ test/main.ts | 10 ++- 11 files changed, 239 insertions(+), 15 deletions(-) create mode 100644 karma-build-jasmine.es6.conf.js create mode 100644 karma-dist-sauce-jasmine.es6.conf.js create mode 100644 sauce.es6.conf.js create mode 100644 test/browser/custom-element.spec.js create mode 100644 test/browser_es6_entry_point.ts diff --git a/.travis.yml b/.travis.yml index 2e51e3b18..28ab26261 100644 --- a/.travis.yml +++ b/.travis.yml @@ -54,3 +54,5 @@ script: - node_modules/.bin/karma start karma-build-sauce-selenium3-mocha.conf.js --single-run - node_modules/.bin/gulp test/node - node_modules/.bin/gulp test/node -no-patch-clock + - cp ./test/browser/custom-element.spec.js ./build/test/browser + - node_modules/.bin/karma start karma-dist-sauce-jasmine.es6.conf.js --single-run diff --git a/file-size-limit.json b/file-size-limit.json index e5e12f2ef..e0fd98bf9 100644 --- a/file-size-limit.json +++ b/file-size-limit.json @@ -3,7 +3,7 @@ { "path": "dist/zone.min.js", "checkTarget": true, - "limit": 42050 + "limit": 42500 } ] -} \ No newline at end of file +} diff --git a/karma-build-jasmine.es6.conf.js b/karma-build-jasmine.es6.conf.js new file mode 100644 index 000000000..4d69b0d88 --- /dev/null +++ b/karma-build-jasmine.es6.conf.js @@ -0,0 +1,5 @@ + +module.exports = function (config) { + require('./karma-build-jasmine.conf.js')(config); + config.client.entrypoint = 'browser_es6_entry_point'; +}; diff --git a/karma-dist-sauce-jasmine.es6.conf.js b/karma-dist-sauce-jasmine.es6.conf.js new file mode 100644 index 000000000..fdeff8f45 --- /dev/null +++ b/karma-dist-sauce-jasmine.es6.conf.js @@ -0,0 +1,6 @@ + +module.exports = function (config) { + require('./karma-dist-jasmine.conf.js')(config); + require('./sauce.es6.conf')(config); + config.client.entrypoint = 'browser_es6_entry_point'; +}; diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index bf01626d7..0d7ab2458 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -17,7 +17,7 @@ import {bindArguments, patchClass, patchMacroTask, patchMethod, patchOnPropertie import {propertyPatch} from './define-property'; import {eventTargetPatch, patchEvent} from './event-target'; import {propertyDescriptorPatch} from './property-descriptor'; -import {registerElementPatch} from './register-element'; +import {patchCustomElements, registerElementPatch} from './register-element'; Zone.__load_patch('util', (global: any, Zone: ZoneType, api: _ZonePrivate) => { api.patchOnProperties = patchOnProperties; @@ -74,7 +74,11 @@ Zone.__load_patch('EventTarget', (global: any, Zone: ZoneType, api: _ZonePrivate Zone.__load_patch('on_property', (global: any, Zone: ZoneType, api: _ZonePrivate) => { propertyDescriptorPatch(api, global); propertyPatch(); +}); + +Zone.__load_patch('customElements', (global: any, Zone: ZoneType, api: _ZonePrivate) => { registerElementPatch(global); + patchCustomElements(global); }); Zone.__load_patch('canvas', (global: any) => { diff --git a/lib/browser/register-element.ts b/lib/browser/register-element.ts index aa1abbd7a..f590f0c60 100644 --- a/lib/browser/register-element.ts +++ b/lib/browser/register-element.ts @@ -10,19 +10,16 @@ import {attachOriginToPatched, isBrowser, isMix, ObjectGetOwnPropertyDescriptor, import {_redefineProperty} from './define-property'; -export function registerElementPatch(_global: any) { - if ((!isBrowser && !isMix) || !('registerElement' in (_global).document)) { +function patchCallbacks(target: any, targetName: string, method: string, callbacks: string[]) { + const symbol = Zone.__symbol__(method); + if (target[symbol]) { return; } - - const _registerElement = (document).registerElement; - const callbacks = - ['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback']; - - (document).registerElement = function(name: any, opts: any) { + const nativeDelegate = target[symbol] = target[method]; + target[method] = function(name: any, opts: any, options?: any) { if (opts && opts.prototype) { callbacks.forEach(function(callback) { - const source = 'Document.registerElement::' + callback; + const source = `${targetName}.${method}::` + callback; const prototype = opts.prototype; if (prototype.hasOwnProperty(callback)) { const descriptor = ObjectGetOwnPropertyDescriptor(prototype, callback); @@ -38,8 +35,30 @@ export function registerElementPatch(_global: any) { }); } - return _registerElement.call(document, name, opts); + return nativeDelegate.call(target, name, opts, options); }; - attachOriginToPatched((document).registerElement, _registerElement); + attachOriginToPatched(target[method], nativeDelegate); +} + +export function registerElementPatch(_global: any) { + if ((!isBrowser && !isMix) || !('registerElement' in (_global).document)) { + return; + } + + const callbacks = + ['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback']; + + patchCallbacks(document, 'Document', 'registerElement', callbacks); +} + +export function patchCustomElements(_global: any) { + if ((!isBrowser && !isMix) || !('customElements' in _global)) { + return; + } + + const callbacks = + ['connectedCallback', 'disconnectedCallback', 'adoptedCallback', 'attributeChangedCallback']; + + patchCallbacks(_global.customElements, 'customElements', 'define', callbacks); } diff --git a/package.json b/package.json index e7b2dbd19..a3832b1ec 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "closure:test": "scripts/closure/closure_compiler.sh", "format": "gulp format:enforce", "karma-jasmine": "karma start karma-build-jasmine.conf.js", + "karma-jasmine:es6": "karma start karma-build-jasmine.es6.conf.js", "karma-jasmine:phantomjs": "karma start karma-build-jasmine-phantomjs.conf.js --single-run", "karma-jasmine:single": "karma start karma-build-jasmine.conf.js --single-run", "karma-jasmine:autoclose": "npm run karma-jasmine:single && npm run ws-client", @@ -38,6 +39,7 @@ "tsc:w": "tsc -w -p .", "tslint": "tslint -c tslint.json 'lib/**/*.ts'", "test": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine\"", + "test:es6": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:es6\"", "test:phantomjs": "npm run tsc && concurrently \"npm run tsc:w\" \"npm run ws-server\" \"npm run karma-jasmine:phantomjs\"", "test:phantomjs-single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine-phantomjs:autoclose\"", "test:single": "npm run tsc && concurrently \"npm run ws-server\" \"npm run karma-jasmine:autoclose\"", diff --git a/sauce.es6.conf.js b/sauce.es6.conf.js new file mode 100644 index 000000000..dd051f730 --- /dev/null +++ b/sauce.es6.conf.js @@ -0,0 +1,61 @@ +// Sauce configuration + +module.exports = function(config, ignoredLaunchers) { + // The WS server is not available with Sauce + config.files.unshift('test/saucelabs.js'); + + var basicLaunchers = { + 'SL_CHROME_66': {base: 'SauceLabs', browserName: 'chrome', version: '66'}, + }; + + var customLaunchers = {}; + if (!ignoredLaunchers) { + customLaunchers = basicLaunchers; + } else { + Object.keys(basicLaunchers).forEach(function(key) { + if (ignoredLaunchers + .filter(function(ignore) { + return ignore === key; + }) + .length === 0) { + customLaunchers[key] = basicLaunchers[key]; + } + }); + } + + config.set({ + captureTimeout: 120000, + browserNoActivityTimeout: 240000, + + sauceLabs: { + testName: 'Zone.js', + startConnect: false, + recordVideo: false, + recordScreenshots: false, + options: { + 'selenium-version': '2.53.0', + 'command-timeout': 600, + 'idle-timeout': 600, + 'max-duration': 5400 + } + }, + + customLaunchers: customLaunchers, + + browsers: Object.keys(customLaunchers), + + reporters: ['dots', 'saucelabs'], + + singleRun: true, + + plugins: ['karma-*'] + }); + + if (process.env.TRAVIS) { + config.sauceLabs.build = + 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')'; + config.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER; + + process.env.SAUCE_ACCESS_KEY = process.env.SAUCE_ACCESS_KEY.split('').reverse().join(''); + } +}; diff --git a/test/browser/custom-element.spec.js b/test/browser/custom-element.spec.js new file mode 100644 index 000000000..8f1c1db9f --- /dev/null +++ b/test/browser/custom-element.spec.js @@ -0,0 +1,108 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +/* + * check that document.registerElement(name, { prototype: proto }); + * is properly patched + */ + +function customElementsSupport() { + return 'registerElement' in document; +} +customElementsSupport.message = 'window.customElements'; + +describe('customElements', function() { + const testZone = Zone.current.fork({ name: 'test' }); + const bridge = { + connectedCallback: () => {}, + disconnectedCallback: () => {}, + adoptedCallback: () => {}, + attributeChangedCallback: () => {} + }; + + class TestCustomElement extends HTMLElement { + constructor() { + super(); + } + + static get observedAttributes() { + return ['attr1', 'attr2']; + } + + connectedCallback() { + return bridge.connectedCallback(); + } + + disconnectedCallback() { + return bridge.disconnectedCallback(); + } + + attributeChangedCallback(attrName, oldVal, newVal) { + return bridge.attributeChangedCallback(attrName, oldVal, newVal); + } + + adoptedCallback() { + return bridge.adoptedCallback(); + } + } + + testZone.run(() => { + customElements.define('x-test', TestCustomElement); + }); + + let elt; + + beforeEach(() => { + bridge.connectedCallback = () => {}; + bridge.disconnectedCallback = () => {}; + bridge.attributeChangedCallback = () => {}; + bridge.adoptedCallback = () => {}; + }); + + afterEach(() => { + if (elt) { + document.body.removeChild(elt); + elt = null; + } + }); + + it('should work with connectedCallback', function(done) { + bridge.connectedCallback = function() { + expect(Zone.current.name).toBe(testZone.name); + done(); + }; + + elt = document.createElement('x-test'); + document.body.appendChild(elt); + }); + + it('should work with disconnectedCallback', function(done) { + bridge.disconnectedCallback = function() { + expect(Zone.current.name).toBe(testZone.name); + done(); + }; + + elt = document.createElement('x-test'); + document.body.appendChild(elt); + document.body.removeChild(elt); + elt = null; + }); + + it('should work with attributeChanged', function(done) { + bridge.attributeChangedCallback = function(attrName, oldVal, newVal) { + expect(Zone.current.name).toBe(testZone.name); + expect(attrName).toEqual('attr1'); + expect(newVal).toEqual('value1'); + done(); + }; + + elt = document.createElement('x-test'); + document.body.appendChild(elt); + elt.setAttribute('attr1', 'value1'); + }); +}); diff --git a/test/browser_es6_entry_point.ts b/test/browser_es6_entry_point.ts new file mode 100644 index 000000000..d66e77c97 --- /dev/null +++ b/test/browser_es6_entry_point.ts @@ -0,0 +1,9 @@ +/** + * @license + * Copyright Google Inc. All Rights Reserved. + * + * Use of this source code is governed by an MIT-style license that can be + * found in the LICENSE file at https://angular.io/license + */ + +import './browser/custom-element.spec'; diff --git a/test/main.ts b/test/main.ts index a68c7ec49..049f9ba6f 100644 --- a/test/main.ts +++ b/test/main.ts @@ -14,11 +14,19 @@ declare const __karma__: { __karma__.loaded = function() {}; +let entryPoint = 'browser_entry_point'; + if (typeof __karma__ !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = (__karma__ as any).config.errorpolicy; + if ((__karma__ as any).config.entrypoint) { + entryPoint = (__karma__ as any).config.entrypoint; + } } else if (typeof process !== 'undefined') { (window as any)['__Zone_Error_BlacklistedStackFrames_policy'] = process.env.errorpolicy; + if (process.env.entrypoint) { + entryPoint = process.env.entrypoint; + } } (window as any).global = window; @@ -54,7 +62,7 @@ browserPatchedPromise.then(() => { // Setup test environment System.import(testFrameworkPatch).then(() => { System.import('/base/build/lib/common/error-rewrite').then(() => { - System.import('/base/build/test/browser_entry_point') + System.import(`/base/build/test/${entryPoint}`) .then( () => { __karma__.start();