diff --git a/packages/@lwc/engine-core/src/framework/mutation-logger.ts b/packages/@lwc/engine-core/src/framework/mutation-logger.ts index 1482b0a87e..15de4602b7 100644 --- a/packages/@lwc/engine-core/src/framework/mutation-logger.ts +++ b/packages/@lwc/engine-core/src/framework/mutation-logger.ts @@ -60,6 +60,18 @@ function safelyCallGetter(target: any, key: PropertyKey) { } } +function isRevokedProxy(target: object) { + try { + // `str in obj` will never throw for normal objects or active proxies, + // but the operation is not allowed for revoked proxies + // eslint-disable-next-line @typescript-eslint/no-unused-expressions + '' in target; + return false; + } catch (_) { + return true; + } +} + /** * Flush all the logs we've written so far and return the current logs. */ @@ -126,7 +138,9 @@ export function trackTargetForMutationLogging(key: PropertyKey, target: any) { // Guard against recursive objects - don't traverse forever return; } - if (isObject(target) && !isNull(target)) { + + // Revoked proxies (e.g. window props in LWS sandboxes) throw an error if we try to track them + if (isObject(target) && !isNull(target) && !isRevokedProxy(target)) { // only track non-primitives; others are invalid as WeakMap keys targetsToPropertyKeys.set(target, key); diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/error.txt b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/error.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/expected.html b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/expected.html new file mode 100644 index 0000000000..6538e926a9 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/expected.html @@ -0,0 +1,5 @@ + + + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/index.js b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/index.js new file mode 100644 index 0000000000..cd34090d2c --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/index.js @@ -0,0 +1,3 @@ +export const tagName = 'x-component'; +export { default } from 'x/component'; +export * from 'x/component'; diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.html b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.html new file mode 100755 index 0000000000..40649dcf22 --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.html @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.js b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.js new file mode 100755 index 0000000000..87ee4ae14f --- /dev/null +++ b/packages/@lwc/engine-server/src/__tests__/fixtures/track-revoked-proxy-fails/modules/x/component/component.js @@ -0,0 +1,26 @@ +import { LightningElement, track } from 'lwc'; +import tmpl from './component.html'; + +const { revoke, proxy } = Proxy.revocable({}, {}); +revoke(); + +export default class Rehydration extends LightningElement { + // Doesn't need to be used, just needs to be tracked; see W-17739481 + @track reactive = proxy; + + connectedCallback() { + Promise.resolve().then(() => { + this.reactive = 1; + }); + } + + render() { + if (!this.rendered) { + this.rendered = true; + } else { + throw new Error('Reactivity should be disabled on SSR.'); + } + + return tmpl; + } +}