From 59af1ffe4e965bbbb0ed4728d223978970c41f0a Mon Sep 17 00:00:00 2001 From: Khushal Sagar Date: Tue, 3 Nov 2026 19:07:27 +0000 Subject: [PATCH 1/3] Signal tool stability with waitForToolsUntil() and ToolChangeEvent --- index.bs | 223 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 213 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index 847677e..83fe0bd 100644 --- a/index.bs +++ b/index.bs @@ -190,6 +190,9 @@ A model context is a [=struct=] with the following [=struct/items=]: object's event loop, and because it is event-loop-local, it can get out of sync from the [=traversable navigable=]'s more "global" map. + : pending tool readiness promises + :: a [=set=] of {{Promise}} objects, initially empty. + A tool definition is a [=struct=] with the following [=struct/items=]: @@ -436,8 +439,8 @@ follows:
-To notify documents of a tool change given a {{Document}} |tool owner| and a [=list=] of -[=origins=] |exposed origins|, run these steps: +To notify documents of a tool change given a {{Document}} |tool owner|, a [=list=] of +[=origins=] or "*" |exposed origins|, and a [=boolean=] |ready|, run these steps: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -451,11 +454,21 @@ To notify documents of a tool change given a {{Document}} |tool owner 1. If |targetDocument| is not [=allowed to use=] the "{{tools}}" feature, then [=iteration/continue=]. - 1. If [=tool is exposed to an origin=] given |tool owner|'s [=Document/origin=], |exposed - origins|, and |targetDocument|'s [=Document/origin=], then [=queue a global task=] on the - [=webmcp task source=] given |targetDocument|'s [=relevant global object=] to [=fire an - event=] named {{ModelContext/toolchange}} at |targetDocument|'s [=Document/associated - ModelContext|associated ModelContext=]. + 1. If |exposed origins| is "*", or [=tool is exposed to an origin=] given |tool + owner|'s [=Document/origin=], |exposed origins|, and |targetDocument|'s [=Document/origin=] + returns true, then [=queue a global task=] on the [=webmcp task source=] given + |targetDocument|'s [=relevant global object=] to [=fire an event=] named + {{ModelContext/toolchange}} at |targetDocument|'s [=Document/associated + ModelContext|associated ModelContext=], using {{ToolChangeEvent}}, with its + {{ToolChangeEvent/ready}} attribute initialized to |ready|. + +Note: Because a {{ModelContext/toolchange}} event triggered by a [=Document/tools-ready=] state +change cannot be filtered by a specific tool's {{ModelContextRegisterToolOptions/exposedTo}} +origins, it uses "*" to notify all eligible documents in the [=traversable navigable=] +(which will expand to the [=browsing context group=] once issue #227 is addressed). +Implementations can optimize this by only notifying documents that have registered a +{{ModelContext/toolchange}} event listener.

This algorithm's use of the [=webmcp task source=], and the fact that it runs [=in parallel=], @@ -500,6 +513,70 @@ a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |accessing origin

+
+To update the tools-ready state given a {{Document}} |document|, run these steps: + +1. [=Assert=]: these steps are running on |document|'s [=relevant agent=]'s [=agent/event loop=]. + +1. Let |context| be |document|'s [=Document/associated ModelContext|associated + ModelContext=]'s [=ModelContext/internal context=]. + +1. Let |newReadyState| be true if |context|'s [=model context/pending tool readiness promises=] is + [=set/is empty|empty=]; otherwise false. + +1. If |document|'s [=Document/tools-ready=] is |newReadyState|, then return. + +1. Set |document|'s [=Document/tools-ready=] to |newReadyState|. + +1. If |document| is not [=Document/fully active=], then return. + +1. [=In parallel=], [=notify documents of a tool change=] given |document|, "*", and + |newReadyState|. + +
+ +
+To wait for tools to be ready given a [=traversable navigable=] |traversable|, run these +steps: + +1. [=Assert=]: these steps are running [=in parallel=]. + +1. Let |areAllReady| be an algorithm that returns a [=boolean=] by running these steps: + + 1. Let |activeDocument| be |traversable|'s [=navigable/active document=]. + + 1. If |activeDocument| is null or is not [=Document/fully active=], then return true. + + Note: If |traversable| is destroyed while waiting, its [=navigable/active document=] is + disconnected and is no longer [=Document/fully active=]. Returning true here immediately + unblocks the wait so the caller can check [=Document/fully active=] and abort. + + 1. [=list/For each=] [=navigable=] |navigable| of |activeDocument|'s [=Document/inclusive + descendant navigables=]: + + 1. Let |document| be |navigable|'s [=navigable/active document=]. + + 1. If |document|'s [=Document/tools-ready=] is false, then return false. + + Note: Although [=Document/tools-ready=] is modified on |document|'s [=relevant agent=]'s + [=agent/event loop=] and read here [=in parallel=], implementations synchronize updates to + [=Document/tools-ready=] (for example, via inter-process communication to the browser + process) ordered with updates to [=model context/tool map=]. + + 1. Return true. + +1. If the result of running |areAllReady| is true, then return. + +1. Wait until either the result of running |areAllReady| is true, or an [=implementation-defined=] + timeout has elapsed since this step began. + + Note: The timeout is measured from when this wait begins. Calling + {{ModelContext/waitForToolsUntil()}} with additional promises while a wait is already in progress + does not reset or extend the timeout, and calling it after |areAllReady| returns true does not + delay an operation that has already been unblocked. + +
+
The tool execute steps, given a [=string=] |toolName|, a {{Document}} |targetDocument|, a [=string=] |inputArguments|, an algorithm |completionSteps|, and a [=unique internal value=] |uuid|, @@ -663,8 +740,12 @@ To unregister a tool given a {{ModelContext}} |mo <{iframe}> was removed. In that case, |targetDocument| has no [=node navigable=] from which to find other documents to notify. -1. [=In parallel=], [=notify documents of a tool change=] given |targetDocument| and |exposed - origins|. +1. Let |toolsReady| be |targetDocument|'s [=Document/tools-ready=]. + +1. If |toolsReady| is false, then return. + +1. [=In parallel=], [=notify documents of a tool change=] given |targetDocument|, |exposed + origins|, and |toolsReady|.
@@ -700,6 +781,12 @@ To serialize a tool schema given an {{object}}-or-undefined |schema|: Each {{Document}} object has an associated {{ModelContext}}, which is a {{ModelContext}} object. +Each {{Document}} object has a tools-ready [=boolean=], initially true. + +Issue(#328): [=Document/tools-ready=] should be false when the {{Document}} is created. This gives +authors an opportunity to use {{ModelContext/waitForToolsUntil()}} until a deterministic +{{Document}} lifecycle point. The exact lifecycle point still needs to be resolved. + Upon creation of the {{Document}} object, its [=Document/associated ModelContext|associated ModelContext=] must be set to a [=new=] {{ModelContext}} object created in the {{Document}}'s [=relevant realm=]. @@ -727,7 +814,10 @@ The {{ModelContext}} interface provides methods for web applications to register [Exposed=Window, SecureContext] interface ModelContext : EventTarget { + readonly attribute boolean toolsReady; + Promise<undefined> registerTool(ModelContextTool tool, optional ModelContextRegisterToolOptions options = {}); + undefined waitForToolsUntil(Promise<any> promise); Promise<sequence<RegisteredTool>> getTools(optional ModelContextGetToolOptions options = {}); Promise<DOMString> executeTool(RegisteredTool tool, optional object inputObject, optional ModelContextExecuteToolOptions options = {}); @@ -742,6 +832,12 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. <dl class="domintro"> + <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/toolsReady}}</code></dt> + <dd> + <p>Returns true if the document's tools are ready to be read, or false if the document is + waiting on promises passed to {{ModelContext/waitForToolsUntil()}}.</p> + </dd> + <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/registerTool(tool, options)}}</code></dt> <dd> <p>Registers a tool that [=agents=] can invoke. Returns a rejected promise if a tool with the @@ -750,6 +846,14 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. or {{ModelContextTool/outputSchema}} is invalid.</p> </dd> + <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/waitForToolsUntil(promise)}}</code></dt> + <dd> + <p>Signals that the document is asynchronously registering or updating its tools, and that tool + consumers should wait until <var ignore>promise</var> settles (or a timeout elapses) before + reading the document's tools. Multiple promises can be registered; the document's tools are + considered ready once all registered promises have settled.</p> + </dd> + <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/getTools(options)}}</code></dt> <dd> <p>Returns a promise that resolves to a list of registered tools from this document and its @@ -784,6 +888,33 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. ``` </div> +<div class=example id=waiting-for-tools> + The following delays tool discovery until asynchronous tool registration completes: + + ```js + const toolsReady = (async () => { + const config = await fetch('/api/tools-config').then(r => r.json()); + for (const tool of config.tools) { + await document.modelContext.registerTool({ + name: tool.name, + description: tool.description, + execute: async (input) => { /* ... */ } + }); + } + })(); + + document.modelContext.waitForToolsUntil(toolsReady); + ``` +</div> + + +<div algorithm> +The <dfn attribute for=ModelContext>toolsReady</dfn> getter steps are: + +1. Return [=this=]'s [=relevant global object=]'s [=associated Document|associated + <code>Document</code>=]'s [=Document/tools-ready=]. + +</div> <div algorithm> The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var>)</dfn> method steps are: @@ -914,9 +1045,12 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var 1. Set [=this=]'s [=ModelContext/internal context=]'s [=model context/tool map=][|tool name|] to |tool definition|. +1. Let |toolsReady| be |tool owner|'s [=Document/tools-ready=]. + 1. Run the following steps [=in parallel=]: - 1. [=Notify documents of a tool change=] given |tool owner| and |exposed origins|. + 1. If |toolsReady| is true, then [=notify documents of a tool change=] given |tool owner|, + |exposed origins|, and |toolsReady|. 1. [=Queue a global task=] on the [=webmcp task source=] given |global| to [=resolve=] |promise| with undefined. @@ -925,6 +1059,37 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var </div> +<div algorithm> +The <dfn method for=ModelContext>waitForToolsUntil(<var>promise</var>)</dfn> method steps are: + +1. Let |global| be [=this=]'s [=relevant global object=]. + +1. Let |document| be |global|'s [=associated Document|associated <code>Document</code>=]. + +1. If |document| is not [=Document/fully active=], then [=exception/throw=] an "{{InvalidStateError}}" + {{DOMException}}. + +1. If |document| is not [=allowed to use=] the "{{tools}}" feature, then [=exception/throw=] a + "{{NotAllowedError}}" {{DOMException}}. + +1. Let |context| be [=this=]'s [=ModelContext/internal context=]. + +1. [=set/Append=] |promise| to |context|'s [=model context/pending tool readiness promises=]. + +1. [=Update the tools-ready state=] given |document|. + +1. [=promise/React=] to |promise|: + + - If |promise| was fulfilled or rejected, then: + + 1. [=set/Remove=] |promise| from |context|'s [=model context/pending tool readiness + promises=]. + + 1. If |document| is [=Document/fully active=], then [=update the tools-ready state=] given + |document|. + +</div> + <div algorithm> The <dfn method for=ModelContext>getTools(<var>options</var>)</dfn> method steps are: @@ -956,6 +1121,11 @@ The <dfn method for=ModelContext>getTools(<var>options</var>)</dfn> method steps 1. Run the following steps [=in parallel=]: + 1. [=Wait for tools to be ready=] given |toolRequestor|'s [=node navigable=]'s + [=navigable/traversable navigable=]. + + 1. If |toolRequestor| is not [=Document/fully active=], then return. + 1. Let |discoveredTools| be an empty [=list=]. 1. Let |navigables| be |toolRequestor|'s [=node navigable=]'s [=navigable/traversable @@ -1460,6 +1630,29 @@ that must be supported, as [=event handler IDL attributes=], by all {{ModelConte <td><dfn event for="ModelContext">toolcancel</dfn> </table> +<h4 id="tool-change-event">ToolChangeEvent Interface</h4> + +The {{ToolChangeEvent}} interface represents a {{ModelContext/toolchange}} event that is dispatched +at a {{ModelContext}} object when the tools exposed to its {{Document}} change or their readiness +state changes. + +<xmp class="idl"> +[Exposed=Window, SecureContext] +interface ToolChangeEvent : Event { + constructor(DOMString type, optional ToolChangeEventInit eventInitDict = {}); + readonly attribute boolean ready; +}; + +dictionary ToolChangeEventInit : EventInit { + boolean ready = true; +}; + + +
+ : ready + :: Returns true if the tools are ready to be read, or false if tool updates are in progress. +
+

ToolActivatedEvent Interface

The {{ToolActivatedEvent}} interface represents a {{ModelContext/toolactivated}} event that is @@ -1579,6 +1772,11 @@ steps: 1. [=Assert=]: |traversable|'s [=navigable/active document=] is [=Document/fully active=]. +1. [=Wait for tools to be ready=] given |traversable|. + +1. If |traversable|'s [=navigable/active document=] is null or is not [=Document/fully active=], + then return. + 1. Let |observation| be a new [=observation=]. 1. Let |flat descendants| be the [=Document/inclusive descendant navigables=] of |traversable|'s @@ -1633,6 +1831,11 @@ observation=] given any [=top-level browsing context=] in the [=user agent=] [=b group set=], at any time, although implementations typically reserve this operation for when the user is interacting with a [=browser agent=] while web content is in view. +Note: Calling {{ModelContext/waitForToolsUntil()}} only delays [=observations=] that the [=user +agent=] chooses to perform while a {{Document}}'s [=Document/tools-ready=] is false. Toggling +[=Document/tools-ready=] does not by itself cause or guarantee that the [=user agent=] will +[=perform an observation=]. +

Security and Privacy Considerations

From bb789294aa3c09b5b291a1b4d044753e7aac15aa Mon Sep 17 00:00:00 2001 From: Khushal Sagar Date: Wed, 11 Nov 2026 02:10:32 +0000 Subject: [PATCH 2/3] Scope to tools-ready tracking and observation delay; address review comments - Defer ToolChangeEvent/toolchange readiness dispatch and the getTools() wait to a follow-up PR. - wait for tools to be ready: capture the top-level active document up front, drop the null check, and return "ready" or "navigated". - perform an observation: return early on "navigated". - waitForToolsUntil(): ignore already-settled promises, split React into fulfilled/rejected branches, and always update tools-ready on settle. - Move tools-ready below the ModelContext creation paragraph, rename the example variable, and fix in-parallel indentation in registerTool(). --- index.bs | 164 ++++++++++++++++++++++--------------------------------- 1 file changed, 65 insertions(+), 99 deletions(-) diff --git a/index.bs b/index.bs index 83fe0bd..c3ffd61 100644 --- a/index.bs +++ b/index.bs @@ -439,8 +439,8 @@ follows:
-To notify documents of a tool change given a {{Document}} |tool owner|, a [=list=] of -[=origins=] or "*" |exposed origins|, and a [=boolean=] |ready|, run these steps: +To notify documents of a tool change given a {{Document}} |tool owner| and a [=list=] of +[=origins=] |exposed origins|, run these steps: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -454,21 +454,11 @@ To notify documents of a tool change given a {{Document}} |tool owner 1. If |targetDocument| is not [=allowed to use=] the "{{tools}}" feature, then [=iteration/continue=]. - 1. If |exposed origins| is "*", or [=tool is exposed to an origin=] given |tool - owner|'s [=Document/origin=], |exposed origins|, and |targetDocument|'s [=Document/origin=] - returns true, then [=queue a global task=] on the [=webmcp task source=] given - |targetDocument|'s [=relevant global object=] to [=fire an event=] named - {{ModelContext/toolchange}} at |targetDocument|'s [=Document/associated - ModelContext|associated ModelContext=], using {{ToolChangeEvent}}, with its - {{ToolChangeEvent/ready}} attribute initialized to |ready|. - -Note: Because a {{ModelContext/toolchange}} event triggered by a [=Document/tools-ready=] state -change cannot be filtered by a specific tool's {{ModelContextRegisterToolOptions/exposedTo}} -origins, it uses "*" to notify all eligible documents in the [=traversable navigable=] -(which will expand to the [=browsing context group=] once issue #227 is addressed). -Implementations can optimize this by only notifying documents that have registered a -{{ModelContext/toolchange}} event listener. + 1. If [=tool is exposed to an origin=] given |tool owner|'s [=Document/origin=], |exposed + origins|, and |targetDocument|'s [=Document/origin=], then [=queue a global task=] on the + [=webmcp task source=] given |targetDocument|'s [=relevant global object=] to [=fire an + event=] named {{ModelContext/toolchange}} at |targetDocument|'s [=Document/associated + ModelContext|associated ModelContext=].

This algorithm's use of the [=webmcp task source=], and the fact that it runs [=in parallel=], @@ -521,35 +511,27 @@ To update the tools-ready state given a {{Document}} |document|, run 1. Let |context| be |document|'s [=Document/associated ModelContext|associated ModelContext=]'s [=ModelContext/internal context=]. -1. Let |newReadyState| be true if |context|'s [=model context/pending tool readiness promises=] is - [=set/is empty|empty=]; otherwise false. - -1. If |document|'s [=Document/tools-ready=] is |newReadyState|, then return. - -1. Set |document|'s [=Document/tools-ready=] to |newReadyState|. - -1. If |document| is not [=Document/fully active=], then return. - -1. [=In parallel=], [=notify documents of a tool change=] given |document|, "*", and - |newReadyState|. +1. Set |document|'s [=Document/tools-ready=] to true if |context|'s [=model context/pending tool + readiness promises=] is [=set/is empty|empty=]; otherwise false.

-To wait for tools to be ready given a [=traversable navigable=] |traversable|, run these -steps: +To wait for tools to be ready given a [=top-level traversable=] |traversable|, returning +"ready" or "navigated", run these steps: 1. [=Assert=]: these steps are running [=in parallel=]. -1. Let |areAllReady| be an algorithm that returns a [=boolean=] by running these steps: +1. Let |activeDocument| be |traversable|'s [=navigable/active document=]. - 1. Let |activeDocument| be |traversable|'s [=navigable/active document=]. +1. [=Assert=]: |activeDocument| is [=Document/fully active=]. - 1. If |activeDocument| is null or is not [=Document/fully active=], then return true. +1. Let |areAllReady| be an algorithm that returns a [=boolean=] by running these steps: + + 1. If |activeDocument| is not [=Document/fully active=], then return true. - Note: If |traversable| is destroyed while waiting, its [=navigable/active document=] is - disconnected and is no longer [=Document/fully active=]. Returning true here immediately - unblocks the wait so the caller can check [=Document/fully active=] and abort. + Note: If |activeDocument| becomes inactive while waiting (for example, because |traversable| + navigated or was destroyed), returning true here immediately unblocks the wait. 1. [=list/For each=] [=navigable=] |navigable| of |activeDocument|'s [=Document/inclusive descendant navigables=]: @@ -565,16 +547,18 @@ steps: 1. Return true. -1. If the result of running |areAllReady| is true, then return. - -1. Wait until either the result of running |areAllReady| is true, or an [=implementation-defined=] - timeout has elapsed since this step began. +1. If the result of running |areAllReady| is false, then wait until either the result of running + |areAllReady| is true, or an [=implementation-defined=] timeout has elapsed since this step + began. Note: The timeout is measured from when this wait begins. Calling {{ModelContext/waitForToolsUntil()}} with additional promises while a wait is already in progress does not reset or extend the timeout, and calling it after |areAllReady| returns true does not delay an operation that has already been unblocked. +1. Return "ready" if |activeDocument| is [=Document/fully active=]; otherwise + "navigated". +
@@ -740,12 +724,8 @@ To unregister a tool given a {{ModelContext}} |mo <{iframe}> was removed. In that case, |targetDocument| has no [=node navigable=] from which to find other documents to notify. -1. Let |toolsReady| be |targetDocument|'s [=Document/tools-ready=]. - -1. If |toolsReady| is false, then return. - -1. [=In parallel=], [=notify documents of a tool change=] given |targetDocument|, |exposed - origins|, and |toolsReady|. +1. [=In parallel=], [=notify documents of a tool change=] given |targetDocument| and |exposed + origins|.
@@ -781,16 +761,17 @@ To serialize a tool schema given an {{object}}-or-undefined |schema|: Each {{Document}} object has an associated {{ModelContext}}, which is a {{ModelContext}} object. -Each {{Document}} object has a tools-ready [=boolean=], initially true. - -Issue(#328): [=Document/tools-ready=] should be false when the {{Document}} is created. This gives -authors an opportunity to use {{ModelContext/waitForToolsUntil()}} until a deterministic -{{Document}} lifecycle point. The exact lifecycle point still needs to be resolved. - Upon creation of the {{Document}} object, its [=Document/associated ModelContext|associated ModelContext=] must be set to a [=new=] {{ModelContext}} object created in the {{Document}}'s [=relevant realm=]. +Each {{Document}} object has a tools-ready [=boolean=], initially true. + +Issue(#328): [=Document/tools-ready=] should be false when the {{Document}} is created, unless the +{{Document}} is not [=allowed to use=] the "{{tools}}" feature. This gives authors an opportunity to +use {{ModelContext/waitForToolsUntil()}} until a deterministic {{Document}} lifecycle point. The +exact lifecycle point still needs to be resolved. +
@@ -834,8 +815,7 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. <dl class="domintro"> <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/toolsReady}}</code></dt> <dd> - <p>Returns true if the document's tools are ready to be read, or false if the document is - waiting on promises passed to {{ModelContext/waitForToolsUntil()}}.</p> + <p>Returns true if the document's tools are ready to be read.</p> </dd> <dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/registerTool(tool, options)}}</code></dt> @@ -892,7 +872,7 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. The following delays tool discovery until asynchronous tool registration completes: ```js - const toolsReady = (async () => { + const toolsRegistered = (async () => { const config = await fetch('/api/tools-config').then(r => r.json()); for (const tool of config.tools) { await document.modelContext.registerTool({ @@ -903,7 +883,7 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}. } })(); - document.modelContext.waitForToolsUntil(toolsReady); + document.modelContext.waitForToolsUntil(toolsRegistered); ``` </div> @@ -1045,15 +1025,12 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var 1. Set [=this=]'s [=ModelContext/internal context=]'s [=model context/tool map=][|tool name|] to |tool definition|. -1. Let |toolsReady| be |tool owner|'s [=Document/tools-ready=]. - 1. Run the following steps [=in parallel=]: - 1. If |toolsReady| is true, then [=notify documents of a tool change=] given |tool owner|, - |exposed origins|, and |toolsReady|. + 1. [=Notify documents of a tool change=] given |tool owner| and |exposed origins|. - 1. [=Queue a global task=] on the [=webmcp task source=] given |global| to [=resolve=] |promise| - with undefined. + 1. [=Queue a global task=] on the [=webmcp task source=] given |global| to [=resolve=] |promise| + with undefined. 1. Return |promise|. @@ -1072,21 +1049,36 @@ The <dfn method for=ModelContext>waitForToolsUntil(<var>promise</var>)</dfn> met 1. If |document| is not [=allowed to use=] the "{{tools}}" feature, then [=exception/throw=] a "{{NotAllowedError}}" {{DOMException}}. +1. If |promise|.\[[Promise]].\[[PromiseState]] is not `pending`, then: + + 1. If |promise|.\[[Promise]].\[[PromiseState]] is `rejected`, then [=mark as handled|mark=] + |promise| as handled. + + Note: When |promise| is `pending`, [=promise/react|reacting=] to it in the steps below marks + it as handled. If it is already `rejected` when passed in, returning early skips + [=promise/react=], so we explicitly mark it as handled here to prevent an + `unhandledrejection` event from firing. + + 1. Return. + 1. Let |context| be [=this=]'s [=ModelContext/internal context=]. 1. [=set/Append=] |promise| to |context|'s [=model context/pending tool readiness promises=]. 1. [=Update the tools-ready state=] given |document|. -1. [=promise/React=] to |promise|: +1. Let |settleSteps| be the following steps: + + 1. [=set/Remove=] |promise| from |context|'s [=model context/pending tool readiness + promises=]. + + 1. [=Update the tools-ready state=] given |document|. - - If |promise| was fulfilled or rejected, then: +1. [=promise/React=] to |promise|: - 1. [=set/Remove=] |promise| from |context|'s [=model context/pending tool readiness - promises=]. + - If |promise| was fulfilled, then run |settleSteps|. - 1. If |document| is [=Document/fully active=], then [=update the tools-ready state=] given - |document|. + - If |promise| was rejected, then run |settleSteps|. </div> @@ -1121,11 +1113,6 @@ The <dfn method for=ModelContext>getTools(<var>options</var>)</dfn> method steps 1. Run the following steps [=in parallel=]: - 1. [=Wait for tools to be ready=] given |toolRequestor|'s [=node navigable=]'s - [=navigable/traversable navigable=]. - - 1. If |toolRequestor| is not [=Document/fully active=], then return. - 1. Let |discoveredTools| be an empty [=list=]. 1. Let |navigables| be |toolRequestor|'s [=node navigable=]'s [=navigable/traversable @@ -1630,29 +1617,6 @@ that must be supported, as [=event handler IDL attributes=], by all {{ModelConte <td><dfn event for="ModelContext">toolcancel</dfn> </table> -<h4 id="tool-change-event">ToolChangeEvent Interface</h4> - -The {{ToolChangeEvent}} interface represents a {{ModelContext/toolchange}} event that is dispatched -at a {{ModelContext}} object when the tools exposed to its {{Document}} change or their readiness -state changes. - -<xmp class="idl"> -[Exposed=Window, SecureContext] -interface ToolChangeEvent : Event { - constructor(DOMString type, optional ToolChangeEventInit eventInitDict = {}); - readonly attribute boolean ready; -}; - -dictionary ToolChangeEventInit : EventInit { - boolean ready = true; -}; - - -
- : ready - :: Returns true if the tools are ready to be read, or false if tool updates are in progress. -
-

ToolActivatedEvent Interface

The {{ToolActivatedEvent}} interface represents a {{ModelContext/toolactivated}} event that is @@ -1772,10 +1736,12 @@ steps: 1. [=Assert=]: |traversable|'s [=navigable/active document=] is [=Document/fully active=]. -1. [=Wait for tools to be ready=] given |traversable|. +1. If the result of [=waiting for tools to be ready=] given |traversable| is + "navigated", then return. -1. If |traversable|'s [=navigable/active document=] is null or is not [=Document/fully active=], - then return. + Note: If |traversable| navigated to a new {{Document}} while waiting, this observation aborts + and implementations will typically [=perform an observation=] again for the new + [=navigable/active document=]. 1. Let |observation| be a new [=observation=]. From c12e1262923687892f0e8ada25d73498b327e58b Mon Sep 17 00:00:00 2001 From: Khushal Sagar Date: Wed, 11 Nov 2026 02:42:46 +0000 Subject: [PATCH 3/3] Return "abort" from wait for tools to be ready The navigable can also be destroyed while waiting, so "navigated" was too narrow. Update the note in perform an observation to cover both cases. --- index.bs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/index.bs b/index.bs index c3ffd61..62e7dd1 100644 --- a/index.bs +++ b/index.bs @@ -518,7 +518,7 @@ To update the tools-ready state given a {{Document}} |document|, run
To wait for tools to be ready given a [=top-level traversable=] |traversable|, returning -"ready" or "navigated", run these steps: +"ready" or "abort", run these steps: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -557,7 +557,7 @@ To wait for tools to be ready given a [=top-level traversable=] |trav delay an operation that has already been unblocked. 1. Return "ready" if |activeDocument| is [=Document/fully active=]; otherwise - "navigated". + "abort".
@@ -1737,11 +1737,11 @@ steps: 1. [=Assert=]: |traversable|'s [=navigable/active document=] is [=Document/fully active=]. 1. If the result of [=waiting for tools to be ready=] given |traversable| is - "navigated", then return. + "abort", then return. - Note: If |traversable| navigated to a new {{Document}} while waiting, this observation aborts - and implementations will typically [=perform an observation=] again for the new - [=navigable/active document=]. + Note: This happens if |traversable| navigated to a new {{Document}} or was destroyed while + waiting. In the navigation case, implementations will typically [=perform an observation=] + again for the new [=navigable/active document=]. 1. Let |observation| be a new [=observation=].