From 04dad9350eb9c15a5fd8b2c80750dbd04f9042e6 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:35:34 -0800 Subject: [PATCH 1/5] feat(playwright): add device projects for cross-browser testing --- packages/fast-html/playwright.config.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/fast-html/playwright.config.ts b/packages/fast-html/playwright.config.ts index cc5227dbd6b..3328388a589 100644 --- a/packages/fast-html/playwright.config.ts +++ b/packages/fast-html/playwright.config.ts @@ -1,9 +1,14 @@ -import { defineConfig } from "@playwright/test"; +import { defineConfig, devices } from "@playwright/test"; export default defineConfig({ testDir: ".", testMatch: "**/*.spec.ts", retries: 3, + projects: [ + { name: "chromium", use: { ...devices["Desktop Chrome"] } }, + { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { name: "webkit", use: { ...devices["Desktop Safari"] } }, + ], webServer: { command: "npm run test-server", port: 5173, From bc83f617feb5a4ac6479dc5fd05799ac177533da Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:44:48 -0800 Subject: [PATCH 2/5] feat(hydration): add hydrationStarted callback and update documentation --- packages/fast-element/docs/api-report.api.md | 1 + .../src/components/element-controller.ts | 100 ++++++++---------- ...oncontrollercallbacks.elementdidhydrate.md | 2 +- ...ncontrollercallbacks.elementwillhydrate.md | 2 +- ...ioncontrollercallbacks.hydrationstarted.md | 27 +++++ ...st-element.hydrationcontrollercallbacks.md | 15 ++- 6 files changed, 88 insertions(+), 59 deletions(-) create mode 100644 sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.hydrationstarted.md diff --git a/packages/fast-element/docs/api-report.api.md b/packages/fast-element/docs/api-report.api.md index 64499803df7..986594dec9c 100644 --- a/packages/fast-element/docs/api-report.api.md +++ b/packages/fast-element/docs/api-report.api.md @@ -631,6 +631,7 @@ export interface HydrationControllerCallbacks { /** - * Called before hydration has started + * Called once when the first element enters the hydration pipeline. + * This is the earliest point at which we know a component has been + * async-defined with `defer-and-hydrate`, a template is pending via + * ``, and the element has `needs-hydration`. + */ + hydrationStarted?(): void; + + /** + * Called before an individual element's hydration begins */ elementWillHydrate?(source: TElement): void; /** - * Called after hydration has finished + * Called after an individual element's hydration has finished */ elementDidHydrate?(source: TElement): void; @@ -887,6 +895,11 @@ export class HydratableElementController< */ public static lifecycleCallbacks: HydrationControllerCallbacks = {}; + /** + * Whether the hydrationStarted callback has already been invoked. + */ + private static hydrationStarted: boolean = false; + /** * An idle callback ID used to track hydration completion */ @@ -946,7 +959,11 @@ export class HydratableElementController< // If there are no more hydrating instances, invoke the hydrationComplete callback if (HydratableElementController.hydratingInstances?.size === 0) { - HydratableElementController.notifyHydrationComplete(); + try { + HydratableElementController.lifecycleCallbacks.hydrationComplete?.(); + } catch { + // A lifecycle callback must never prevent post-hydration cleanup. + } // Reset to the default strategy after hydration is complete ElementController.setStrategy(ElementController); @@ -986,7 +1003,23 @@ export class HydratableElementController< return; } - this.notifyWillHydrate(); + if (!HydratableElementController.hydrationStarted) { + HydratableElementController.hydrationStarted = true; + + try { + HydratableElementController.lifecycleCallbacks.hydrationStarted?.(); + } catch { + // A lifecycle callback must never prevent hydration. + } + } + + try { + HydratableElementController.lifecycleCallbacks.elementWillHydrate?.( + this.source + ); + } catch { + // A lifecycle callback must never prevent hydration. + } this.stage = Stages.connecting; @@ -1053,11 +1086,17 @@ export class HydratableElementController< return; } + try { + HydratableElementController.lifecycleCallbacks.elementDidHydrate?.( + this.source + ); + } catch { + // A lifecycle callback must never prevent hydration. + } + const name = this.definition.name; const instances = HydratableElementController.hydratingInstances.get(name); - this.notifyDidHydrate(); - if (instances) { instances.delete(this.source); @@ -1076,55 +1115,6 @@ export class HydratableElementController< } } - /** - * Notifies that hydration is about to start for this element. - * Safely invokes the configured elementWillHydrate callback, if any. - */ - private notifyWillHydrate(): void { - const callback = - HydratableElementController.lifecycleCallbacks.elementWillHydrate; - - if (callback) { - try { - callback(this.source); - } catch { - // A lifecycle callback must never prevent hydration. - } - } - } - - /** - * Notifies that hydration has finished for this element. - * Safely invokes the configured elementDidHydrate callback, if any. - */ - private notifyDidHydrate(): void { - const callback = HydratableElementController.lifecycleCallbacks.elementDidHydrate; - - if (callback) { - try { - callback(this.source); - } catch { - // A lifecycle callback must never prevent hydration. - } - } - } - - /** - * Notifies that all elements have completed hydration. - * Safely invokes the configured hydrationComplete callback, if any. - */ - private static notifyHydrationComplete(): void { - const callback = HydratableElementController.lifecycleCallbacks.hydrationComplete; - - if (callback) { - try { - callback(); - } catch { - // A lifecycle callback must never prevent post-hydration cleanup. - } - } - } - /** * Unregisters the hydration observer when the element is disconnected. */ diff --git a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementdidhydrate.md b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementdidhydrate.md index 17ac8049802..c52ed5bafb8 100644 --- a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementdidhydrate.md +++ b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementdidhydrate.md @@ -15,7 +15,7 @@ navigationOptions: ## HydrationControllerCallbacks.elementDidHydrate() method -Called after hydration has finished +Called after an individual element's hydration has finished **Signature:** diff --git a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementwillhydrate.md b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementwillhydrate.md index ac4fe89d424..95a284416bd 100644 --- a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementwillhydrate.md +++ b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.elementwillhydrate.md @@ -15,7 +15,7 @@ navigationOptions: ## HydrationControllerCallbacks.elementWillHydrate() method -Called before hydration has started +Called before an individual element's hydration begins **Signature:** diff --git a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.hydrationstarted.md b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.hydrationstarted.md new file mode 100644 index 00000000000..f80b9f82b1a --- /dev/null +++ b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.hydrationstarted.md @@ -0,0 +1,27 @@ +--- +id: "fast-element.hydrationcontrollercallbacks.hydrationstarted" +title: "HydrationControllerCallbacks.hydrationStarted() method" +layout: 2x-api +eleventyNavigation: + key: "api2xfast-element.hydrationcontrollercallbacks.hydrationstarted" + parent: "api2xfast-element" + title: "HydrationControllerCallbacks.hydrationStarted() method" +navigationOptions: + activeKey: "api2xfast-element.hydrationcontrollercallbacks.hydrationstarted" +--- + + +[@microsoft/fast-element](../fast-element/index.html) > [HydrationControllerCallbacks](../fast-element.hydrationcontrollercallbacks/index.html) > [hydrationStarted](../fast-element.hydrationcontrollercallbacks.hydrationstarted/index.html) + +## HydrationControllerCallbacks.hydrationStarted() method + +Called once when the first element enters the hydration pipeline. This is the earliest point at which we know a component has been async-defined with `defer-and-hydrate`, a template is pending via ``, and the element has `needs-hydration`. + +**Signature:** + +```typescript +hydrationStarted?(): void; +``` +**Returns:** + +void diff --git a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.md b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.md index 87416ef4a76..4755712e88f 100644 --- a/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.md +++ b/sites/website/src/docs/2.x/api/fast-element.hydrationcontrollercallbacks.md @@ -43,7 +43,7 @@ Description -_(Optional)_ Called after hydration has finished +_(Optional)_ Called after an individual element's hydration has finished @@ -54,7 +54,7 @@ _(Optional)_ Called after hydration has finished -_(Optional)_ Called before hydration has started +_(Optional)_ Called before an individual element's hydration begins @@ -68,5 +68,16 @@ _(Optional)_ Called before hydration has started _(Optional)_ Called after all elements have completed hydration + + + +[hydrationStarted()?](../fast-element.hydrationcontrollercallbacks.hydrationstarted/) + + + + +_(Optional)_ Called once when the first element enters the hydration pipeline. This is the earliest point at which we know a component has been async-defined with `defer-and-hydrate`, a template is pending via ``, and the element has `needs-hydration`. + + From c466ce4d983a0faa671c6ad7db321ffba938cf07 Mon Sep 17 00:00:00 2001 From: John Kreitlow <863023+radium-v@users.noreply.github.com> Date: Fri, 13 Feb 2026 15:55:43 -0800 Subject: [PATCH 3/5] test(performance-metrics): enhance performance tracking and add hydration metrics --- .../performance-metrics/fast-card.css | 4 + .../fixtures/performance-metrics/index.html | 210 +++++++++++++++- .../test/fixtures/performance-metrics/main.ts | 57 ++++- .../performance-metrics.spec.ts | 229 +++++++++++++++++- packages/fast-html/test/index.html | 1 + 5 files changed, 474 insertions(+), 27 deletions(-) diff --git a/packages/fast-html/test/fixtures/performance-metrics/fast-card.css b/packages/fast-html/test/fixtures/performance-metrics/fast-card.css index b010b61ae08..8215ab5168e 100644 --- a/packages/fast-html/test/fixtures/performance-metrics/fast-card.css +++ b/packages/fast-html/test/fixtures/performance-metrics/fast-card.css @@ -3,4 +3,8 @@ width: 200px; height: 200px; border: 1px solid #ccc; + + dd { + display: inline-block; + } } diff --git a/packages/fast-html/test/fixtures/performance-metrics/index.html b/packages/fast-html/test/fixtures/performance-metrics/index.html index 9086294f970..a8ada05573a 100644 --- a/packages/fast-html/test/fixtures/performance-metrics/index.html +++ b/packages/fast-html/test/fixtures/performance-metrics/index.html @@ -3,9 +3,10 @@ Performance Metrics Test + - + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +