Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
feat(custom-element): patch customElement v1 APIs (#1133)
Browse files Browse the repository at this point in the history
  • Loading branch information
JiaLiPassion authored and mhevery committed Sep 11, 2018
1 parent 60adc9c commit 427705f
Show file tree
Hide file tree
Showing 11 changed files with 239 additions and 15 deletions.
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 2 additions & 2 deletions file-size-limit.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
{
"path": "dist/zone.min.js",
"checkTarget": true,
"limit": 42050
"limit": 42500
}
]
}
}
5 changes: 5 additions & 0 deletions karma-build-jasmine.es6.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

module.exports = function (config) {
require('./karma-build-jasmine.conf.js')(config);
config.client.entrypoint = 'browser_es6_entry_point';
};
6 changes: 6 additions & 0 deletions karma-dist-sauce-jasmine.es6.conf.js
Original file line number Diff line number Diff line change
@@ -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';
};
6 changes: 5 additions & 1 deletion lib/browser/browser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) => {
Expand Down
41 changes: 30 additions & 11 deletions lib/browser/register-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,16 @@ import {attachOriginToPatched, isBrowser, isMix, ObjectGetOwnPropertyDescriptor,

import {_redefineProperty} from './define-property';

export function registerElementPatch(_global: any) {
if ((!isBrowser && !isMix) || !('registerElement' in (<any>_global).document)) {
function patchCallbacks(target: any, targetName: string, method: string, callbacks: string[]) {
const symbol = Zone.__symbol__(method);
if (target[symbol]) {
return;
}

const _registerElement = (<any>document).registerElement;
const callbacks =
['createdCallback', 'attachedCallback', 'detachedCallback', 'attributeChangedCallback'];

(<any>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);
Expand All @@ -38,8 +35,30 @@ export function registerElementPatch(_global: any) {
});
}

return _registerElement.call(document, name, opts);
return nativeDelegate.call(target, name, opts, options);
};

attachOriginToPatched((<any>document).registerElement, _registerElement);
attachOriginToPatched(target[method], nativeDelegate);
}

export function registerElementPatch(_global: any) {
if ((!isBrowser && !isMix) || !('registerElement' in (<any>_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);
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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\"",
Expand Down
61 changes: 61 additions & 0 deletions sauce.es6.conf.js
Original file line number Diff line number Diff line change
@@ -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('');
}
};
108 changes: 108 additions & 0 deletions test/browser/custom-element.spec.js
Original file line number Diff line number Diff line change
@@ -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');
});
});
9 changes: 9 additions & 0 deletions test/browser_es6_entry_point.ts
Original file line number Diff line number Diff line change
@@ -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';
10 changes: 9 additions & 1 deletion test/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down

1 comment on commit 427705f

@eciuca
Copy link

@eciuca eciuca commented on 427705f Nov 5, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi, I need this fix. Can I hope for a new release soon? Thank you!

Please sign in to comment.