From aa24dcd7a5911272eb5b6bcd238281353186b88b Mon Sep 17 00:00:00 2001 From: Alex Nahas Date: Thu, 1 Oct 2026 09:05:01 -0700 Subject: [PATCH 1/2] fix(polyfill): check executeTool input before reading options --- TESTING.md | 2 +- src/index.ts | 6 +++--- tests/execute.test.ts | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/TESTING.md b/TESTING.md index 316b960..99e9251 100644 --- a/TESTING.md +++ b/TESTING.md @@ -72,7 +72,7 @@ and `webmcp-types@0.1.9`. (`toolactivated`/`toolcancel`, their handlers, and `ToolActivatedEvent`/`ToolCancelEvent`) are not implemented. - **Draft differences:** results are JSON-serialized; some pinned tests expect raw - strings. Omitted or `undefined` input becomes `{}`; `null` and primitives reject. + strings. - **Timing:** MessagePorts approximate native task ordering. Aborting before dispatch skips the callback; the draft dispatches and then aborts its signal. Delegated permission checks are asynchronous, so argument errors can precede diff --git a/src/index.ts b/src/index.ts index 4b7a150..1a624df 100644 --- a/src/index.ts +++ b/src/index.ts @@ -243,6 +243,9 @@ class ModelContextPolyfill extends EventTarget implements WebMCP.ModelContext { ): Promise { const ownerDocument = this.#document; const target = readExecutionTarget(tool); + if (!isObject(inputObject)) { + throw new TypeError("inputObject must be an object"); + } const settings = readDictionary(options); const callerSignal = readAbortSignal(settings.signal); @@ -251,9 +254,6 @@ class ModelContextPolyfill extends EventTarget implements WebMCP.ModelContext { if (!expectedOrigin || expectedOrigin === "null") { throw new NativeDOMException("Invalid or opaque origin", "NotSupportedError"); } - if (!isObject(inputObject)) { - throw new TypeError("inputObject must be an object"); - } const serializedInput = serializeJSON(inputObject); callerSignal?.throwIfAborted(); diff --git a/tests/execute.test.ts b/tests/execute.test.ts index e614fdf..2b06ac7 100644 --- a/tests/execute.test.ts +++ b/tests/execute.test.ts @@ -194,6 +194,44 @@ test("rejects legacy JSON strings and preserves input serialization errors", asy expect(outcome).toEqual(["TypeError", "TypeError", "TypeError", "RangeError"]); }); +test("a non-object input rejects after the tool is converted and before options are read", async ({ + page, +}) => { + const outcome = await page.evaluate(async () => { + const context = document.modelContext!; + + await context.registerTool({ name: "x", description: "X", execute: () => null }); + + const tool = (await context.getTools())[0]!; + const reads: string[] = []; + const descriptor = { + ...tool, + get name() { + reads.push("tool"); + return tool.name; + }, + }; + const options = { + get signal() { + reads.push("options"); + return undefined; + }, + }; + try { + // @ts-expect-error Exercise a null input from JavaScript callers. + await context.executeTool(descriptor, null, options); + return { error: "resolved", reads }; + } catch (error) { + if (!(error instanceof Error)) { + throw error; + } + return { error: error.name, reads }; + } + }); + + expect(outcome).toEqual({ error: "TypeError", reads: ["tool"] }); +}); + test("serializes results as JSON and rejects callback or serialization failures", async ({ page, }) => { From af2ad5113e2023e556d74ff1f1b17f9202771e8d Mon Sep 17 00:00:00 2001 From: Alex Nahas Date: Sat, 3 Oct 2026 00:32:57 -0700 Subject: [PATCH 2/2] docs(polyfill): record verified conformance results Keep the coverage counts and platform limitations tied to the recorded runs. Record Safari's incomplete 185/190-subtest run and its WebDriver cleanup failure so the documentation does not imply that the full suite completed. The input-conversion implementation is unchanged; its preceding review passed 226 browser tests. --- TESTING.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/TESTING.md b/TESTING.md index 99e9251..7bcc27e 100644 --- a/TESTING.md +++ b/TESTING.md @@ -2,13 +2,13 @@ ## Results -**223 browser tests pass** across Chromium 153.0.8010.12, Firefox 155.0, and +**226 browser tests pass** across Chromium 153.0.8010.12, Firefox 155.0, and Playwright WebKit 26.6. Package checks also pass: imports, types, SSR, and tarball contents. CI runs these checks plus WPT in Chrome, Firefox, and actual Safari. Safari runs on `macos-26`; Playwright WebKit is a separate build. WPT covers **all 70 WebMCP testharness files, 190 subtests**, with zero unexpected results -in Chrome and Firefox: +in Chrome, Firefox, and Safari: | Subtest result | Count | | --- | ---: | @@ -17,9 +17,10 @@ in Chrome and Firefox: | Expected TIMEOUT | 26 | | Expected NOTRUN | 20 | -Tested with Chrome Canary 157.0.8080.0 and Firefox Nightly 159.0a1 (20260930214513). -Safari has not yet run at this pin; none of the expectations are browser-specific. -At the file level: 43 OK, 26 expected timeouts, one expected error. +The [CI run for PR #8](https://github.com/webmachinelearning/webmcp-polyfill/actions/runs/36969192728) +used Chrome Canary 157.0.8081.0, Firefox Nightly 159.0a1, and Safari 26.6.2 +(21624.5.1.11.3). All three have the counts above; none of the expectations are +browser-specific. At the file level: 43 OK, 26 expected timeouts, one expected error. Expected failures are still failures. `NOTRUN` means an earlier timeout prevented the test from running, including three abort cases. Passing declarative checks only @@ -71,8 +72,8 @@ and `webmcp-types@0.1.9`. - **Missing APIs:** declarative forms, CSS states, and lifecycle events (`toolactivated`/`toolcancel`, their handlers, and `ToolActivatedEvent`/`ToolCancelEvent`) are not implemented. -- **Draft differences:** results are JSON-serialized; some pinned tests expect raw - strings. +- **Pinned WPT differences:** results are JSON-serialized as the draft requires; + some pinned tests expect raw strings. - **Timing:** MessagePorts approximate native task ordering. Aborting before dispatch skips the callback; the draft dispatches and then aborts its signal. Delegated permission checks are asynchronous, so argument errors can precede