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 @@
+
+
+ I should render!
+
+
\ 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 @@
+
+ I should render!
+
\ 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;
+ }
+}