From ad0888d2632cecedac9e3219dd7ee3a560e79c3d Mon Sep 17 00:00:00 2001 From: nicholasrice Date: Wed, 23 Aug 2023 10:52:53 -0700 Subject: [PATCH] add JSON stringify no-op to structures that maintain circular references --- packages/web-components/fast-element/docs/api-report.md | 4 ++++ .../fast-element/src/components/controller.spec.ts | 7 +++++++ .../fast-element/src/components/controller.ts | 8 +++++++- packages/web-components/fast-element/src/interfaces.ts | 2 ++ .../fast-element/src/observation/observable.ts | 5 +++++ .../web-components/fast-element/src/templating/view.ts | 7 +++++++ .../web-components/fast-foundation/docs/api-report.md | 1 + packages/web-components/fast-foundation/src/di/di.ts | 8 ++++++++ 8 files changed, 41 insertions(+), 1 deletion(-) diff --git a/packages/web-components/fast-element/docs/api-report.md b/packages/web-components/fast-element/docs/api-report.md index 71525968e53..aca01b6be01 100644 --- a/packages/web-components/fast-element/docs/api-report.md +++ b/packages/web-components/fast-element/docs/api-report.md @@ -186,6 +186,8 @@ export class Controller extends PropertyChangeNotifier { set styles(value: ElementStyles | null); get template(): ElementViewTemplate | null; set template(value: ElementViewTemplate | null); + // @internal + toJSON: Function; readonly view: ElementView | null; } @@ -384,6 +386,8 @@ export class HTMLView implements ElementView, SyntheticView { lastChild: Node; remove(): void; source: any | null; + // @internal + toJSON: Function; unbind(): void; } diff --git a/packages/web-components/fast-element/src/components/controller.spec.ts b/packages/web-components/fast-element/src/components/controller.spec.ts index 172d741baee..6821aa814ba 100644 --- a/packages/web-components/fast-element/src/components/controller.spec.ts +++ b/packages/web-components/fast-element/src/components/controller.spec.ts @@ -511,4 +511,11 @@ describe("The Controller", () => { expect(behavior.bound).to.equal(false); }); }) + it("should not throw if DOM stringified", () => { + const controller = createController(); + + expect(() => { + JSON.stringify(controller.element); + }).to.not.throw(); + }); }); diff --git a/packages/web-components/fast-element/src/components/controller.ts b/packages/web-components/fast-element/src/components/controller.ts index 41d1e4b3c2d..5bb458bcedd 100644 --- a/packages/web-components/fast-element/src/components/controller.ts +++ b/packages/web-components/fast-element/src/components/controller.ts @@ -1,5 +1,5 @@ import { DOM } from "../dom.js"; -import type { Mutable } from "../interfaces.js"; +import { Mutable, noop } from "../interfaces.js"; import type { Behavior } from "../observation/behavior.js"; import { PropertyChangeNotifier } from "../observation/notifier.js"; import { defaultExecutionContext, Observable } from "../observation/observable.js"; @@ -421,6 +421,12 @@ export class Controller extends PropertyChangeNotifier { this.needsInitialization = false; } + /** + * Opts out of JSON stringification. + * @internal + */ + toJSON = noop; + private renderTemplate(template: ElementViewTemplate | null | undefined): void { const element = this.element; // When getting the host to render to, we start by looking diff --git a/packages/web-components/fast-element/src/interfaces.ts b/packages/web-components/fast-element/src/interfaces.ts index e338a8e1202..8fc49a8eb32 100644 --- a/packages/web-components/fast-element/src/interfaces.ts +++ b/packages/web-components/fast-element/src/interfaces.ts @@ -27,3 +27,5 @@ export const isFunction = (object: any): object is Function => export type Mutable = { -readonly [P in keyof T]: T[P]; }; + +export const noop = new Function(); diff --git a/packages/web-components/fast-element/src/observation/observable.ts b/packages/web-components/fast-element/src/observation/observable.ts index 336633025e5..ed5b9c4c7c1 100644 --- a/packages/web-components/fast-element/src/observation/observable.ts +++ b/packages/web-components/fast-element/src/observation/observable.ts @@ -169,6 +169,11 @@ export const Observable = FAST.getById(KernelServiceId.observable, () => { super(binding, initialSubscriber); } + /** + * Opts out of JSON stringification. + */ + public toJson = null; + public observe(source: TSource, context: ExecutionContext): TReturn { if (this.needsRefresh && this.last !== null) { this.disconnect(); diff --git a/packages/web-components/fast-element/src/templating/view.ts b/packages/web-components/fast-element/src/templating/view.ts index 9c0fe2ba03b..da5f7717edb 100644 --- a/packages/web-components/fast-element/src/templating/view.ts +++ b/packages/web-components/fast-element/src/templating/view.ts @@ -1,3 +1,4 @@ +import { noop } from "../interfaces.js"; import type { Behavior } from "../observation/behavior.js"; import type { ExecutionContext } from "../observation/observable.js"; @@ -273,4 +274,10 @@ export class HTMLView implements ElementView, SyntheticView { } } } + + /** + * Opts out of JSON stringification. + * @internal + */ + toJSON = noop; } diff --git a/packages/web-components/fast-foundation/docs/api-report.md b/packages/web-components/fast-foundation/docs/api-report.md index 9608458411e..ec70463c2ca 100644 --- a/packages/web-components/fast-foundation/docs/api-report.md +++ b/packages/web-components/fast-foundation/docs/api-report.md @@ -604,6 +604,7 @@ export class ContainerImpl implements Container { registerWithContext(context: any, ...params: any[]): Container; // (undocumented) get responsibleForOwnerRequests(): boolean; + toJSON: Function; } // @public diff --git a/packages/web-components/fast-foundation/src/di/di.ts b/packages/web-components/fast-foundation/src/di/di.ts index 5281f4b590a..b2ad0482040 100644 --- a/packages/web-components/fast-foundation/src/di/di.ts +++ b/packages/web-components/fast-foundation/src/di/di.ts @@ -4,6 +4,8 @@ */ import { Constructable, emptyArray, FASTElement } from "@microsoft/fast-element"; import type { Class } from "../interfaces.js"; +/* eslint-disable-next-line */ +const noop = new Function(); // Tiny polyfill for TypeScript's Reflect metadata API. const metadataByTarget = new Map>(); @@ -1496,6 +1498,12 @@ export class ContainerImpl implements Container { private resolvers: Map; private context: any = null; + /** + * Opts out of JSON stringification. + * @internal + */ + toJSON = noop; + public get parent() { if (this._parent === void 0) { this._parent = this.config.parentLocator(this.owner) as ContainerImpl;