From fba850158be2e95599fa8e4b85ff403b22b57a36 Mon Sep 17 00:00:00 2001 From: Nolan Lawson Date: Tue, 24 Jan 2023 08:10:47 -0800 Subject: [PATCH] fix(engine-dom): remove scoped custom element registry (#3310) --- .circleci/config.yml | 20 - .../create-custom-element-scoped.ts | 100 ---- .../custom-elements/create-custom-element.ts | 10 +- packages/@lwc/features/src/flags.ts | 1 - packages/@lwc/features/src/types.ts | 8 - .../scripts/karma-configs/test/tags.js | 2 - .../scripts/karma-plugins/env.js | 2 - .../scripts/shared/options.js | 4 - .../custom-elements-registry/index.spec.js | 21 - .../test/custom-elements/index.spec.js | 518 +----------------- .../test/custom-elements/x/nonce17/nonce17.js | 7 - .../test/custom-elements/x/nonce18/nonce18.js | 7 - .../test/custom-elements/x/nonce2/nonce2.js | 7 - .../test/custom-elements/x/nonce20/nonce20.js | 7 - .../test/custom-elements/x/nonce3/nonce3.js | 7 - .../x/observeFoo/observeFoo.js | 7 - .../x/observeNothing/observeNothing.js | 3 - .../observeNothingThrow.js | 7 - .../test/upgradable-element/index.spec.js | 55 -- .../x/interop2/interop2.html | 3 - .../upgradable-element/x/interop2/interop2.js | 3 - scripts/bundlesize/bundlesize.config.json | 2 +- 22 files changed, 15 insertions(+), 786 deletions(-) delete mode 100644 packages/@lwc/engine-dom/src/custom-elements/create-custom-element-scoped.ts delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/nonce17/nonce17.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/nonce18/nonce18.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/nonce2/nonce2.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/nonce20/nonce20.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/nonce3/nonce3.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/observeFoo/observeFoo.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/observeNothing/observeNothing.js delete mode 100644 packages/@lwc/integration-karma/test/custom-elements/x/observeNothingThrow/observeNothingThrow.js delete mode 100644 packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.html delete mode 100644 packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.js diff --git a/.circleci/config.yml b/.circleci/config.yml index 97c7780786..b7ab108516 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -51,9 +51,6 @@ commands: enable_native_custom_element_lifecycle: type: boolean default: false - enable_scoped_custom_element_registry: - type: boolean - default: false disable_aria_reflection_polyfill: type: boolean default: false @@ -85,7 +82,6 @@ commands: <<# parameters.disable_synthetic >> DISABLE_SYNTHETIC=1 <> \ <<# parameters.force_native_shadow_mode >> FORCE_NATIVE_SHADOW_MODE_FOR_TEST=1 <> \ <<# parameters.enable_native_custom_element_lifecycle >> ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE=1 <> \ - <<# parameters.enable_scoped_custom_element_registry >> ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY=1 <> \ <<# parameters.disable_aria_reflection_polyfill >> DISABLE_ARIA_REFLECTION_POLYFILL=1 <> \ <<# parameters.node_env_production >> NODE_ENV_FOR_TEST=production <> \ <<# parameters.disable_synthetic_shadow_support_in_compiler >> DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER=1 <> \ @@ -176,9 +172,6 @@ commands: enable_native_custom_element_lifecycle: type: boolean default: false - enable_scoped_custom_element_registry: - type: boolean - default: false disable_aria_reflection_polyfill: type: boolean default: false @@ -201,7 +194,6 @@ commands: disable_synthetic: << parameters.disable_synthetic >> force_native_shadow_mode: << parameters.force_native_shadow_mode >> enable_native_custom_element_lifecycle: << parameters.enable_native_custom_element_lifecycle >> - enable_scoped_custom_element_registry: << parameters.enable_scoped_custom_element_registry >> disable_aria_reflection_polyfill: << parameters.disable_aria_reflection_polyfill >> node_env_production: << parameters.node_env_production >> disable_synthetic_shadow_support_in_compiler: << parameters.disable_synthetic_shadow_support_in_compiler >> @@ -264,21 +256,9 @@ jobs: force_native_shadow_mode: true - run_karma: enable_native_custom_element_lifecycle: true - - run_karma: - enable_scoped_custom_element_registry: true - - run_karma: - disable_synthetic: true - enable_native_custom_element_lifecycle: true - - run_karma: - disable_synthetic: true - enable_scoped_custom_element_registry: true - - run_karma: - enable_native_custom_element_lifecycle: true - enable_scoped_custom_element_registry: true - run_karma: disable_synthetic: true enable_native_custom_element_lifecycle: true - enable_scoped_custom_element_registry: true - run_karma: disable_synthetic: true disable_aria_reflection_polyfill: true diff --git a/packages/@lwc/engine-dom/src/custom-elements/create-custom-element-scoped.ts b/packages/@lwc/engine-dom/src/custom-elements/create-custom-element-scoped.ts deleted file mode 100644 index bfaff33e48..0000000000 --- a/packages/@lwc/engine-dom/src/custom-elements/create-custom-element-scoped.ts +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (c) 2018, salesforce.com, inc. - * All rights reserved. - * SPDX-License-Identifier: MIT - * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT - */ -import { isUndefined } from '@lwc/shared'; -import features from '@lwc/features'; -import { createScopedRegistry, CreateScopedConstructor } from './create-scoped-registry'; -import { hasCustomElements } from './has-custom-elements'; -import type { LifecycleCallback } from '@lwc/engine-core'; - -let createScopedConstructor: CreateScopedConstructor | undefined; -let CachedHTMLElement: typeof HTMLElement | undefined; - -// We only call `createScopedRegistry()` if the browser supports custom elements and -// ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY is enabled, because we don't want to patch eagerly if the flag is disabled -// or we're in a legacy browser. -if (features.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - if (hasCustomElements) { - // If ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY is true, then we eagerly initialize the scoped registry. - // It's assumed that ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY is set *before* LWC loads, and never changes. - // - // Why not lazily patch in `createCustomElement`? Well, this could lead to subtle bugs, e.g.: - // - // 1. LWC loads - // 2. `const Ctor = class extends HTMLElement {}` - // 3. `lwc.createElement(...)` // here we lazily patch - // 4. `customElements.define('x-foo', Ctor)` // throws error because class is bound to stale HTMLElement - // - // To reduce the risk of this, it's safer to patch the registry eagerly. - createScopedConstructor = createScopedRegistry(); - - // It's important to cache window.HTMLElement here. Otherwise, someone else could overwrite window.HTMLElement (e.g. - // another copy of the engine, or another scoping implementation) and we would get "Illegal constructor" errors - // because the HTMLElement prototypes are mixed up. - // - // The reason this happens is that the scoping implementation overwrites window.HTMLElement and expects to work - // with that version of HTMLElement. So if you load two copies of the scoping implementation in the same environment, - // the second one may accidentally grab window.HTMLElement from the first (when doing `class extends HTMLElement`). - // Caching avoids this problem. - CachedHTMLElement = window.HTMLElement; - } -} - -// Creates a constructor that is intended to be used as the UserConstructor in a scoped (pivots) registry. -// In this case, the upgradeCallback only needs to be defined once because we create these on-demand, -// multiple times per tag name. -const createUserConstructor = ( - HTMLElementToExtend: typeof HTMLElement, - upgradeCallback: LifecycleCallback, - connectedCallback?: LifecycleCallback, - disconnectedCallback?: LifecycleCallback -) => { - // TODO [#2972]: this class should expose observedAttributes as necessary - return class UserConstructor extends HTMLElementToExtend { - constructor() { - super(); - upgradeCallback(this); - } - - // Note that there is no need to do the "avoid defining connectedCallback/disconnectedCallback" optimization - // here, because in create-scoped-registry.ts, the registered class will always have these callbacks anyway. - // See: https://github.com/salesforce/lwc/pull/3162#issuecomment-1311851174 - connectedCallback() { - if (!isUndefined(connectedCallback)) { - connectedCallback(this); - } - } - - disconnectedCallback() { - if (!isUndefined(disconnectedCallback)) { - disconnectedCallback(this); - } - } - }; -}; - -export function createCustomElementScoped( - tagName: string, - upgradeCallback: LifecycleCallback, - connectedCallback?: LifecycleCallback, - disconnectedCallback?: LifecycleCallback -) { - if (isUndefined(createScopedConstructor) || isUndefined(CachedHTMLElement)) { - // This error should be impossible to hit - throw new Error( - 'The flag ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY must be set to true to use this feature' - ); - } - - const UserConstructor = createUserConstructor( - CachedHTMLElement, - upgradeCallback, - connectedCallback, - disconnectedCallback - ); - const ScopedConstructor = createScopedConstructor(tagName, UserConstructor); - return new ScopedConstructor(UserConstructor); -} diff --git a/packages/@lwc/engine-dom/src/custom-elements/create-custom-element.ts b/packages/@lwc/engine-dom/src/custom-elements/create-custom-element.ts index 655604d3fb..4a839b4e2f 100644 --- a/packages/@lwc/engine-dom/src/custom-elements/create-custom-element.ts +++ b/packages/@lwc/engine-dom/src/custom-elements/create-custom-element.ts @@ -4,11 +4,9 @@ * SPDX-License-Identifier: MIT * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT */ -import features from '@lwc/features'; import { hasCustomElements } from './has-custom-elements'; import { createCustomElementCompat } from './create-custom-element-compat'; import { createCustomElementUsingUpgradableConstructor } from './create-custom-element-using-upgradable-constructor'; -import { createCustomElementScoped } from './create-custom-element-scoped'; import type { LifecycleCallback } from '@lwc/engine-core'; /** @@ -32,12 +30,8 @@ export let createCustomElement: ( ) => HTMLElement; if (hasCustomElements) { - if (features.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - createCustomElement = createCustomElementScoped; - } else { - // use the global registry, with an upgradable constructor for the defined custom element - createCustomElement = createCustomElementUsingUpgradableConstructor; - } + // use the global registry, with an upgradable constructor for the defined custom element + createCustomElement = createCustomElementUsingUpgradableConstructor; } else { // no registry available here createCustomElement = createCustomElementCompat; diff --git a/packages/@lwc/features/src/flags.ts b/packages/@lwc/features/src/flags.ts index 8b0ea7176f..7fdc638701 100644 --- a/packages/@lwc/features/src/flags.ts +++ b/packages/@lwc/features/src/flags.ts @@ -15,7 +15,6 @@ const features: FeatureFlagMap = { ENABLE_WIRE_SYNC_EMIT: null, ENABLE_LIGHT_GET_ROOT_NODE_PATCH: null, DISABLE_LIGHT_DOM_UNSCOPED_CSS: null, - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY: null, ENABLE_FROZEN_TEMPLATE: null, DISABLE_ARIA_REFLECTION_POLYFILL: null, ENABLE_PROGRAMMATIC_STYLESHEETS: null, diff --git a/packages/@lwc/features/src/types.ts b/packages/@lwc/features/src/types.ts index 3a3182dae3..5dd8969796 100644 --- a/packages/@lwc/features/src/types.ts +++ b/packages/@lwc/features/src/types.ts @@ -57,14 +57,6 @@ export interface FeatureFlagMap { */ DISABLE_LIGHT_DOM_UNSCOPED_CSS: FeatureFlagValue; - /** - * Flag to enable scoped custom element registry (aka pivots). This patches the global custom elements registry - * to support having LWC components with the same tag name as third-party custom elements. - * - * If this flag is disabled, then LWC components with the same tag name as third-party custom elements may conflict. - */ - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY: FeatureFlagValue; - /** * Flag to enable the "frozen template" feature. With this flag enabled, the template object * imported from HTML files is frozen and cannot be modified. E.g. this will throw: diff --git a/packages/@lwc/integration-karma/scripts/karma-configs/test/tags.js b/packages/@lwc/integration-karma/scripts/karma-configs/test/tags.js index 92d3820b6b..22c87ded23 100644 --- a/packages/@lwc/integration-karma/scripts/karma-configs/test/tags.js +++ b/packages/@lwc/integration-karma/scripts/karma-configs/test/tags.js @@ -12,7 +12,6 @@ const { SYNTHETIC_SHADOW_ENABLED, FORCE_NATIVE_SHADOW_MODE_FOR_TEST, ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE, - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY, DISABLE_ARIA_REFLECTION_POLYFILL, DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER, NODE_ENV_FOR_TEST, @@ -24,7 +23,6 @@ const TAGS = [ FORCE_NATIVE_SHADOW_MODE_FOR_TEST && 'force-native-shadow-mode', COMPAT && 'compat', ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE && 'native-lifecycle', - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY && 'scoped-registry', DISABLE_ARIA_REFLECTION_POLYFILL && 'disable-aria-polyfill', DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER && 'disable-synthetic-in-compiler', `NODE_ENV-${NODE_ENV_FOR_TEST}`, diff --git a/packages/@lwc/integration-karma/scripts/karma-plugins/env.js b/packages/@lwc/integration-karma/scripts/karma-plugins/env.js index f44f8d99b6..10ea83f375 100644 --- a/packages/@lwc/integration-karma/scripts/karma-plugins/env.js +++ b/packages/@lwc/integration-karma/scripts/karma-plugins/env.js @@ -20,7 +20,6 @@ const { FORCE_NATIVE_SHADOW_MODE_FOR_TEST, SYNTHETIC_SHADOW_ENABLED, ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE, - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY, DISABLE_ARIA_REFLECTION_POLYFILL, NODE_ENV_FOR_TEST, } = require('../shared/options'); @@ -39,7 +38,6 @@ function createEnvFile() { window.lwcRuntimeFlags = { ENABLE_FORCE_NATIVE_SHADOW_MODE_FOR_TEST: ${FORCE_NATIVE_SHADOW_MODE_FOR_TEST}, ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE: ${ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE}, - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY: ${ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY}, DISABLE_ARIA_REFLECTION_POLYFILL: ${DISABLE_ARIA_REFLECTION_POLYFILL} }; window.process = { diff --git a/packages/@lwc/integration-karma/scripts/shared/options.js b/packages/@lwc/integration-karma/scripts/shared/options.js index 04fcb2fa4c..6ba5f8ca3a 100644 --- a/packages/@lwc/integration-karma/scripts/shared/options.js +++ b/packages/@lwc/integration-karma/scripts/shared/options.js @@ -18,9 +18,6 @@ const FORCE_NATIVE_SHADOW_MODE_FOR_TEST = Boolean(process.env.FORCE_NATIVE_SHADO const ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE = Boolean( process.env.ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE ); -const ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY = Boolean( - process.env.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY -); const DISABLE_ARIA_REFLECTION_POLYFILL = Boolean(process.env.DISABLE_ARIA_REFLECTION_POLYFILL); const DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER = Boolean( process.env.DISABLE_SYNTHETIC_SHADOW_SUPPORT_IN_COMPILER @@ -31,7 +28,6 @@ module.exports = { // Test configuration COMPAT, ENABLE_NATIVE_CUSTOM_ELEMENT_LIFECYCLE, - ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY, DISABLE_ARIA_REFLECTION_POLYFILL, NODE_ENV_FOR_TEST, FORCE_NATIVE_SHADOW_MODE_FOR_TEST, diff --git a/packages/@lwc/integration-karma/test/custom-elements-registry/index.spec.js b/packages/@lwc/integration-karma/test/custom-elements-registry/index.spec.js index 0866aada87..d2ec503949 100644 --- a/packages/@lwc/integration-karma/test/custom-elements-registry/index.spec.js +++ b/packages/@lwc/integration-karma/test/custom-elements-registry/index.spec.js @@ -131,27 +131,6 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { }); describe('two copies of LWC engine loaded', () => { - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - it('no errors on duplicate tag names', () => { - evaluate(engineScripts); - evaluate(`(${createLWC})()`); - expect( - iframe.contentDocument.querySelector('x-foo').shadowRoot.querySelector('h1') - .textContent - ).toEqual('Hello LWC'); - evaluate(engineScripts); - evaluate(`(${createLWC})({ text: 'Hello LWC 2' })`); - const elements = iframe.contentDocument.querySelectorAll('x-foo'); - expect(elements.length).toEqual(2); - expect(elements[0].shadowRoot.querySelector('h1').textContent).toEqual( - 'Hello LWC' - ); - expect(elements[1].shadowRoot.querySelector('h1').textContent).toEqual( - 'Hello LWC 2' - ); - }); - } - [false, true].forEach((customElement) => { const testName = customElement ? 'with CustomElementConstructor' diff --git a/packages/@lwc/integration-karma/test/custom-elements/index.spec.js b/packages/@lwc/integration-karma/test/custom-elements/index.spec.js index 5f24a8f6af..63cde581a6 100644 --- a/packages/@lwc/integration-karma/test/custom-elements/index.spec.js +++ b/packages/@lwc/integration-karma/test/custom-elements/index.spec.js @@ -1,7 +1,5 @@ import { createElement } from 'lwc'; import Nonce1 from 'x/nonce1'; -import Nonce2 from 'x/nonce2'; -import Nonce3 from 'x/nonce3'; import Nonce4 from 'x/nonce4'; import Nonce5 from 'x/nonce5'; import Nonce6 from 'x/nonce6'; @@ -15,13 +13,7 @@ import Nonce13 from 'x/nonce13'; import Nonce14 from 'x/nonce14'; import Nonce15 from 'x/nonce15'; import Nonce16 from 'x/nonce16'; -import Nonce17 from 'x/nonce17'; -import Nonce18 from 'x/nonce18'; import Nonce19 from 'x/nonce19'; -import Nonce20 from 'x/nonce20'; -import ObserveNothing from 'x/observeNothing'; -import ObserveFoo from 'x/observeFoo'; -import ObserveNothingThrow from 'x/observeNothingThrow'; import Component from 'x/component'; const invalidTagNameError = /(not a valid custom element name|must contain a hyphen)/; @@ -59,103 +51,18 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { expect(Ctor).toBe(Nonce1.CustomElementConstructor); }); }); - - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - it('using lwc.createElement', () => { - const tagName = 'x-nonce2'; - expect(customElements.get(tagName)).toBeUndefined(); - let resolvedCtor; - const promise = customElements.whenDefined(tagName).then((ctor) => { - resolvedCtor = ctor; - }); - const elm = createElement(tagName, { is: Nonce2 }); - const Ctor = customElements.get(tagName); - //lwc.createElement is "invisible" to customElements.get - expect(Ctor).toBeUndefined(); - document.body.appendChild(elm); - expect(elm.expectedTagName).toEqual(tagName); - class MyCustomComponent extends HTMLElement {} - // Do an explicit microtask here - return Promise.resolve() - .then(() => { - // If the `resolvedCtor` is undefined at this point, which happens after a microtask - // (Promise.resolve()`), then `whenDefined` probably does not leak the pivot constructor. - // (We could wait for some timeout to be safe, but `Promise.resolve()` should be enough.) - expect(resolvedCtor).toEqual(undefined); - customElements.define(tagName, MyCustomComponent); - return promise; - }) - .then(() => { - expect(resolvedCtor).toBe(MyCustomComponent); - return customElements.whenDefined(tagName); - }) - .then((thisResolvedCtor) => { - expect(thisResolvedCtor).toBe(MyCustomComponent); - }); - }); - - it('using vanilla custom elements, multiple whenDefined promises', () => { - const tagName = 'x-nonce20'; - expect(customElements.get(tagName)).toBeUndefined(); - let resolvedCtor1; - let resolvedCtor2; - const promise1 = customElements.whenDefined(tagName).then((ctor) => { - resolvedCtor1 = ctor; - }); - const promise2 = customElements.whenDefined(tagName).then((ctor) => { - resolvedCtor2 = ctor; - }); - expect(customElements.get(tagName)).toBeUndefined(); - createElement('x-nonce20', { is: Nonce20 }); - - class Vanilla extends HTMLElement {} - - // The promise is only resolved when customElements.define is called, - // not when lwc.createElement is called. lwc.createElement is - // "invisible" to customElements.whenDefined - return Promise.resolve() - .then(() => { - expect(resolvedCtor1).toBeUndefined(); - expect(resolvedCtor2).toBeUndefined(); - customElements.define(tagName, Vanilla); - const Ctor = customElements.get(tagName); - expect(Ctor).toBe(Vanilla); - return Promise.all([promise1, promise2]); - }) - .then(() => { - expect(resolvedCtor1).toBe(Vanilla); - expect(resolvedCtor2).toBe(Vanilla); - }); - }); - } }); describe('patched registry', () => { - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - it('throws error for unsupported "extends" option', () => { - expect(() => { - customElements.define('x-unsupported-extends', class extends HTMLElement {}, { - extends: 'button', - }); - }).toThrowError( - 'NotSupportedError: "extends" key in customElements.define() options is not supported.' - ); - }); - } - - if (!window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - // This test doesn't make sense for the scoped registry, because it uses a PivotCtor - // which is different from the UserCtor - it('elements have the same constructor as defined in the registry', () => { - const tagName = 'x-same-ctor-as-in-registry'; - class Component extends HTMLElement {} - customElements.define(tagName, Component); + it('elements have the same constructor as defined in the registry', () => { + const tagName = 'x-same-ctor-as-in-registry'; + class Component extends HTMLElement {} + customElements.define(tagName, Component); - const elm = document.createElement(tagName); - expect(elm instanceof Component).toEqual(true); - expect(elm.constructor).toBe(Component); - }); - } + const elm = document.createElement(tagName); + expect(elm instanceof Component).toEqual(true); + expect(elm.constructor).toBe(Component); + }); it('throws error for duplicate tag definition', () => { class Foo extends HTMLElement {} @@ -177,22 +84,6 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { }).toThrowError(sameConstructorError); }); - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - it('allows non-LWC custom element to use the same tag name as LWC custom elements', () => { - const elm = createElement('x-nonce3', { is: Nonce3 }); - document.body.appendChild(elm); - expect(elm.tagName.toLowerCase()).toEqual('x-nonce3'); - expect(elm.expectedTagName).toEqual('x-nonce3'); - - class Foo extends HTMLElement {} - - customElements.define('x-nonce3', Foo); - const elm2 = new Foo(); - document.body.appendChild(elm2); - expect(elm2.tagName.toLowerCase()).toEqual('x-nonce3'); - }); - } - it('allows two LWC custom elements to use the same tag name', () => { const elm1 = createElement('x-nonce4', { is: Nonce4 }); document.body.appendChild(elm1); @@ -207,19 +98,8 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { }); describe('same constructor returned from get/whenDefined', () => { - function defineFirstElement(tagName, Component) { - // For the scoped registry, we want to test the global customElements API and - // make sure it consistently returns the same constructor. For vanilla custom elements, - // we can test lwc.createElement instead since it's also registered globally. - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - customElements.define(tagName, Component.CustomElementConstructor); - } else { - createElement(tagName, { is: Component }); - } - } - it('get() should always return the same constructor', () => { - defineFirstElement('x-nonce10', Nonce10); + createElement('x-nonce10', { is: Nonce10 }); const firstCtor = customElements.get('x-nonce10'); expect(firstCtor).not.toBeUndefined(); createElement('x-nonce10', { is: Nonce11 }); @@ -229,7 +109,7 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { }); it('whenDefined() should always return the same constructor - defined before whenDefined', () => { - defineFirstElement('x-nonce6', Nonce6); + createElement('x-nonce6', { is: Nonce6 }); let firstCtor; return customElements .whenDefined('x-nonce6') @@ -249,7 +129,7 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { it('whenDefined() should always return the same constructor - defined after whenDefined', () => { // Check `cE.whenDefined()` called _before_ `cE.define()` const promise = customElements.whenDefined('x-nonce12'); - defineFirstElement('x-nonce12', Nonce12); + createElement('x-nonce12', { is: Nonce12 }); let firstCtor; return promise .then((Ctor) => { @@ -407,68 +287,9 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { upgradeCalled = true; }; - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - // In scoped registry mode, we throw an error - expect(() => { - new Ctor(upgradeCallback); - }).toThrowError( - /Failed to create custom element: the provided constructor is not a constructor\./ - ); - } else { - // In non-scoped registry mode, we just silently ignore the upgradeCallback - document.body.appendChild(new Ctor(upgradeCallback)); - } + document.body.appendChild(new Ctor(upgradeCallback)); expect(upgradeCalled).toEqual(false); }); - - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - it('pre-existing custom element, LWC defined first', () => { - const tagName = 'x-preexisting-in-dom'; - const vanilla = document.createElement(tagName); - document.body.appendChild(vanilla); - const lwcElm = createElement(tagName, { is: Component }); - document.body.appendChild(lwcElm); - - customElements.define( - tagName, - class extends HTMLElement { - constructor() { - super(); - this.notLWC = true; - } - } - ); - - expect(vanilla.notLWC).toEqual(true); - }); - - it('does not allow passing arbitrary UserCtors into the PivotCtor', () => { - const tagName = 'x-nonce-17'; - document.body.appendChild(createElement(tagName, { is: Nonce17 })); - const Ctor = document.createElement(tagName).constructor; - - class MyUserCtor extends HTMLElement { - isMyUserCtor = true; - } - - expect(() => { - new Ctor(MyUserCtor); - }).toThrowError( - /Failed to create custom element: the provided constructor is unregistered: MyUserCtor\./ - ); - }); - - it('does not allow passing non-functions into the PivotCtor', () => { - const tagName = 'x-nonce-18'; - document.body.appendChild(createElement(tagName, { is: Nonce18 })); - const Ctor = document.createElement(tagName).constructor; - expect(() => { - new Ctor(null); - }).toThrowError( - /Failed to create custom element: the provided constructor is not a constructor\./ - ); - }); - } }); describe('errors', () => { @@ -644,319 +465,4 @@ if (SUPPORTS_CUSTOM_ELEMENTS) { expect(elm.getAttribute('foo')).toBeNull(); }); }); - - // These tests only make sense for scoped custom elements, because we create two elements with the same tag name - if (window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - describe('Observed attributes on vanilla component with same tag as LWC component', () => { - let observations; - - beforeEach(() => { - observations = []; - }); - - function createVanillaElement(tagName) { - class Observed extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback(name, oldValue, newValue) { - observations.push({ name, oldValue, newValue }); - } - } - - customElements.define(tagName, Observed); - - const observed = new Observed(); - document.body.appendChild(observed); - return observed; - } - - function createVanillaElementWithSuper(tagName) { - class Observed extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback(name, oldValue, newValue) { - observations.push({ name, oldValue, newValue }); - } - } - - customElements.define(tagName, Observed); - - const observed = new Observed(); - document.body.appendChild(observed); - return observed; - } - - function createVanillaElementUpgradedWithNoPreexistingAttrs(tagName) { - const elm = document.createElement(tagName); - document.body.appendChild(elm); - - class Observed extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback(name, oldValue, newValue) { - observations.push({ name, oldValue, newValue }); - } - } - - customElements.define(tagName, Observed); - - return elm; - } - - function createVanillaElementUpgradedWithPreexistingAttr(tagName) { - const elm = document.createElement(tagName); - elm.setAttribute('foo', 'preexisting'); - document.body.appendChild(elm); - - class Observed extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback(name, oldValue, newValue) { - observations.push({ name, oldValue, newValue }); - } - } - - customElements.define(tagName, Observed); - - return elm; - } - - const scenarios = [ - { - name: 'Basic', - tagName: 'x-observed-attr', - createVanillaElement: createVanillaElement, - LwcComponent: ObserveNothing, - }, - { - name: 'attributeChangedCallback on super', - tagName: 'x-observed-attr-super', - createVanillaElement: createVanillaElementWithSuper, - LwcComponent: ObserveNothing, - }, - { - name: 'same observed attributes on both LWC and vanilla components', - tagName: 'x-observed-attr-same', - createVanillaElement: createVanillaElement, - LwcComponent: ObserveFoo, - }, - { - name: 'Upgrade', - tagName: 'x-observed-attr-upgrade', - createVanillaElement: createVanillaElementUpgradedWithNoPreexistingAttrs, - LwcComponent: ObserveNothing, - }, - { - name: 'Upgrade with preexisting attribute', - tagName: 'x-observed-attr-upgrade-preexisting', - createVanillaElement: createVanillaElementUpgradedWithPreexistingAttr, - LwcComponent: ObserveNothing, - expectedPreexistingObservations: [ - { - name: 'foo', - oldValue: null, - newValue: 'preexisting', - }, - ], - }, - ]; - - scenarios.forEach( - ({ - name, - tagName, - createVanillaElement, - LwcComponent, - expectedPreexistingObservations = [], - }) => { - // Register an LWC component, then a vanilla one that has observed attributes. - // It should still work with pivots. - it(name, () => { - const elm1 = createElement(tagName, { is: LwcComponent }); - document.body.appendChild(elm1); - const elm2 = createVanillaElement(tagName); - document.body.appendChild(elm2); - const initialValue = elm2.getAttribute('foo'); - // set an attr - elm2.setAttribute('foo', 'bar'); - const firstChange = { - name: 'foo', - oldValue: initialValue, - newValue: 'bar', - }; - expect(observations).toEqual([ - ...expectedPreexistingObservations, - firstChange, - ]); - // remove an attr - elm2.removeAttribute('foo'); - const secondChange = { - name: 'foo', - oldValue: 'bar', - newValue: null, - }; - expect(observations).toEqual([ - ...expectedPreexistingObservations, - firstChange, - secondChange, - ]); - // set and remove an attr that is not observed - elm2.setAttribute('unobserved', 'true'); - elm2.removeAttribute('unobserved'); - expect(observations).toEqual([ - ...expectedPreexistingObservations, - firstChange, - secondChange, - ]); - }); - } - ); - - it('custom element with attributeChangedCallback but no observedAttributes', () => { - // The LWC component observes foo, but the vanilla component doesn't, - // so its attributeChangedCallback should never fire - const elm1 = createElement('x-no-observed-attrs', { is: ObserveFoo }); - document.body.appendChild(elm1); - const observations = []; - - class Custom extends HTMLElement { - attributeChangedCallback(name, oldValue, newValue) { - observations.push({ name, oldValue, newValue }); - } - } - - customElements.define('x-no-observed-attrs', Custom); - const elm2 = new Custom(); - document.body.appendChild(elm2); - - elm2.setAttribute('foo', 'bar'); - elm2.removeAttribute('foo'); - expect(elm2.getAttribute('foo')).toBeNull(); - expect(observations).toEqual([]); - }); - - it('LWC element with attributeChangedCallback but no observedAttributes', () => { - // The LWC component observes nothing, but the vanilla component observes foo. - // Changing the foo attribute on the LWC component should not fire attributeChangedCallback. - class Custom extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback() { - throw new Error('should not be invoked'); - } - } - - customElements.define('x-no-observed-attrs-2', Custom); - - const lwcElm = createElement('x-no-observed-attrs-2', { - is: ObserveNothingThrow, - }); - document.body.appendChild(lwcElm); - - lwcElm.setAttribute('foo', 'bar'); - lwcElm.removeAttribute('foo'); - expect(lwcElm.getAttribute('foo')).toBeNull(); - }); - - it('attributeChangedCallback fires on upgrade', () => { - // The LWC component observes nothing, but the vanilla component observes foo - const tagName = 'x-attr-change-on-upgrade'; - const lwcElm = createElement(tagName, { - is: ObserveNothingThrow, // throws if invoked, which should not happen - }); - document.body.appendChild(lwcElm); - const nativeElm = document.createElement(tagName); - nativeElm.setAttribute('foo', '1'); - document.body.appendChild(nativeElm); - - const invocations = []; - - // At this point, because we've already defined an LWC component with the same tag - // name, the native element should be forced to use the manual attributeChangedCallback - // logic. It should be called during the upgrade, since the attribute was already set. - customElements.define( - tagName, - class extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback(name, oldVal, newVal) { - invocations.push([name, oldVal, newVal]); - } - } - ); - - nativeElm.setAttribute('foo', '2'); - - return new Promise((resolve) => setTimeout(resolve)).then(() => { - expect(invocations).toEqual([ - ['foo', null, '1'], - ['foo', '1', '2'], - ]); - }); - }); - - describe('attributeChangedCallback timing', () => { - let originalOnError; - let errors; - - const onError = (e) => { - e.preventDefault(); // avoids logging the error to the console - errors.push(e); - }; - - beforeEach(() => { - errors = []; - - // Nulling out window.onerror disables Jasmine's global error handler, so we can listen for errors - // ourselves. There doesn't seem to be a better way to disable Jasmine's behavior here. - // https://github.com/jasmine/jasmine/pull/1860 - originalOnError = window.onerror; - window.onerror = null; - window.addEventListener('error', onError); - }); - - afterEach(() => { - window.onerror = originalOnError; - window.removeEventListener('error', onError); - }); - - ['native', 'pivot'].forEach((behavior) => { - it(`Element that observes attribute and throws in attributeChangedCallback - ${behavior}`, () => { - const tagName = `x-observes-and-throws-${behavior}`; - - if (behavior === 'pivot') { - // Registering an LWC component with the same tag name triggers the pivot behavior - // for the native element - createElement(tagName, { is: ObserveNothing }); - } - - class Custom extends HTMLElement { - static observedAttributes = ['foo']; - - attributeChangedCallback() { - throw new Error('Error in attributeChangedCallback!'); - } - } - - customElements.define(tagName, Custom); - - const elm = document.createElement(tagName); - document.body.appendChild(elm); - - // None of these should throw synchronously, because this matches native browser behavior. - // Details: https://github.com/salesforce/lwc/pull/2724#discussion_r899066735 - elm.setAttribute('foo', 'bar'); - elm.removeAttribute('foo'); - - return new Promise((resolve) => setTimeout(resolve)).then(() => { - expect(errors.length).toEqual(2); - for (const { message } of errors) { - expect(message).toContain('Error in attributeChangedCallback!'); - } - }); - }); - }); - }); - }); - } } diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/nonce17/nonce17.js b/packages/@lwc/integration-karma/test/custom-elements/x/nonce17/nonce17.js deleted file mode 100644 index ee0b089ebc..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/nonce17/nonce17.js +++ /dev/null @@ -1,7 +0,0 @@ -import { api, LightningElement } from 'lwc'; - -export default class extends LightningElement { - @api get expectedTagName() { - return 'x-nonce17'; - } -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/nonce18/nonce18.js b/packages/@lwc/integration-karma/test/custom-elements/x/nonce18/nonce18.js deleted file mode 100644 index 657ec7e2b8..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/nonce18/nonce18.js +++ /dev/null @@ -1,7 +0,0 @@ -import { api, LightningElement } from 'lwc'; - -export default class extends LightningElement { - @api get expectedTagName() { - return 'x-nonce18'; - } -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/nonce2/nonce2.js b/packages/@lwc/integration-karma/test/custom-elements/x/nonce2/nonce2.js deleted file mode 100644 index 435b83a897..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/nonce2/nonce2.js +++ /dev/null @@ -1,7 +0,0 @@ -import { LightningElement, api } from 'lwc'; - -export default class extends LightningElement { - @api get expectedTagName() { - return 'x-nonce2'; - } -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/nonce20/nonce20.js b/packages/@lwc/integration-karma/test/custom-elements/x/nonce20/nonce20.js deleted file mode 100644 index 310f3b0dac..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/nonce20/nonce20.js +++ /dev/null @@ -1,7 +0,0 @@ -import { api, LightningElement } from 'lwc'; - -export default class extends LightningElement { - @api get expectedTagName() { - return 'x-nonce20'; - } -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/nonce3/nonce3.js b/packages/@lwc/integration-karma/test/custom-elements/x/nonce3/nonce3.js deleted file mode 100644 index d5738ab8df..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/nonce3/nonce3.js +++ /dev/null @@ -1,7 +0,0 @@ -import { LightningElement, api } from 'lwc'; - -export default class extends LightningElement { - @api get expectedTagName() { - return 'x-nonce3'; - } -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/observeFoo/observeFoo.js b/packages/@lwc/integration-karma/test/custom-elements/x/observeFoo/observeFoo.js deleted file mode 100644 index 5fcdb33a38..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/observeFoo/observeFoo.js +++ /dev/null @@ -1,7 +0,0 @@ -import { LightningElement, api } from 'lwc'; - -export default class extends LightningElement { - @api foo = 'foo'; - - attributeChangedCallback() {} -} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/observeNothing/observeNothing.js b/packages/@lwc/integration-karma/test/custom-elements/x/observeNothing/observeNothing.js deleted file mode 100644 index ca8dce94e0..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/observeNothing/observeNothing.js +++ /dev/null @@ -1,3 +0,0 @@ -import { LightningElement } from 'lwc'; - -export default class extends LightningElement {} diff --git a/packages/@lwc/integration-karma/test/custom-elements/x/observeNothingThrow/observeNothingThrow.js b/packages/@lwc/integration-karma/test/custom-elements/x/observeNothingThrow/observeNothingThrow.js deleted file mode 100644 index 84d963a47c..0000000000 --- a/packages/@lwc/integration-karma/test/custom-elements/x/observeNothingThrow/observeNothingThrow.js +++ /dev/null @@ -1,7 +0,0 @@ -import { LightningElement } from 'lwc'; - -export default class extends LightningElement { - attributeChangedCallback() { - throw new Error('should never be invoked'); - } -} diff --git a/packages/@lwc/integration-karma/test/upgradable-element/index.spec.js b/packages/@lwc/integration-karma/test/upgradable-element/index.spec.js index 2dee36e005..d552ec65cc 100644 --- a/packages/@lwc/integration-karma/test/upgradable-element/index.spec.js +++ b/packages/@lwc/integration-karma/test/upgradable-element/index.spec.js @@ -2,7 +2,6 @@ import { createElement } from 'lwc'; import InteropParent from 'x/interopParent'; import Interop from 'x/interop'; -import Interop2 from 'x/interop2'; import Child from 'x/child'; import DuplicateChild from 'x/dupChild'; @@ -54,57 +53,3 @@ it('should create elements with correct component behavior even when they share 'Duplicate Child Component' ); }); - -// In compat mode, the custom registry is bypassed and an equivalent implementation is used, -// the native registry restrictions are not applicable -const SUPPORTS_CUSTOM_ELEMENTS = !process.env.COMPAT && 'customElements' in window; -if (SUPPORTS_CUSTOM_ELEMENTS && window.lwcRuntimeFlags.ENABLE_SCOPED_CUSTOM_ELEMENT_REGISTRY) { - describe('duplicate registration', () => { - function testDuplicateNativeRegistration(testCase, tag, wcClazz) { - it(`should not throw when registering a duplicate tag in custom element registry with ${testCase}`, () => { - // First time the x-interop tag will be registered - const interop = createElement(tag, { is: Interop }); - document.body.appendChild(interop); - customElements.define(tag, wcClazz); - const native = document.createElement(tag); - document.body.appendChild(native); - expect(interop.tagName.toLowerCase()).toEqual(tag); - expect(native.tagName.toLowerCase()).toEqual(tag); - }); - } - testDuplicateNativeRegistration( - 'anonymous class', - 'x-interop-wc', - class extends HTMLElement {} - ); - testDuplicateNativeRegistration( - 'CustomElementConstructor getter', - 'x-interop-lwc-constructor', - Interop.CustomElementConstructor - ); - - function testDuplicateLwcRegistration(testCase, tag, WebComponentConstructor) { - it(`should register duplicate tag via lwc.createElement to override element registered with ${testCase}`, () => { - customElements.define(tag, WebComponentConstructor); - const customElement = document.createElement(tag); - document.body.appendChild(customElement); - const lwcElement = createElement(tag, { is: Interop }); - document.body.appendChild(lwcElement); - // Both elements will be created with the same tag - expect(customElement.tagName.toLowerCase()).toEqual(tag); - expect(lwcElement.tagName.toLowerCase()).toEqual(tag); - }); - } - testDuplicateLwcRegistration( - 'anonymous class', - 'x-interop-native-wc', - class extends HTMLElement {} - ); - testDuplicateLwcRegistration( - 'CustomElementConstructor getter', - 'x-interop-lwc-wcclass', - // must use a different constructor or else this will fail because of the other test - Interop2.CustomElementConstructor - ); - }); -} diff --git a/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.html b/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.html deleted file mode 100644 index 60187f75bd..0000000000 --- a/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.html +++ /dev/null @@ -1,3 +0,0 @@ - diff --git a/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.js b/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.js deleted file mode 100644 index 1ea533ed1f..0000000000 --- a/packages/@lwc/integration-karma/test/upgradable-element/x/interop2/interop2.js +++ /dev/null @@ -1,3 +0,0 @@ -import { LightningElement } from 'lwc'; - -export default class Interop2 extends LightningElement {} diff --git a/scripts/bundlesize/bundlesize.config.json b/scripts/bundlesize/bundlesize.config.json index 4f165edb67..40f29a282b 100644 --- a/scripts/bundlesize/bundlesize.config.json +++ b/scripts/bundlesize/bundlesize.config.json @@ -2,7 +2,7 @@ "files": [ { "path": "packages/lwc/dist/engine-dom/umd/es2017/engine-dom.min.js", - "maxSize": "21.5KB" + "maxSize": "20KB" }, { "path": "packages/lwc/dist/synthetic-shadow/umd/es2017/synthetic-shadow.min.js",