Skip to content

feat(os-api): add waitForReady endpoint#2319

Open
Ryosuke-Asano wants to merge 10 commits intomainfrom
feature/wait-functions
Open

feat(os-api): add waitForReady endpoint#2319
Ryosuke-Asano wants to merge 10 commits intomainfrom
feature/wait-functions

Conversation

@Ryosuke-Asano
Copy link
Member

Summary

  • Add waitForReady endpoint to OS API
  • Waits for document readyState to be 'interactive' or 'complete' (DOMContentLoaded)
  • Useful for waiting until DOM is ready for manipulation after navigation

Endpoint

POST /instances/:id/waitForReady

Parameters

Name Type Required Description
timeout number Timeout in ms (default: 15000)

Response

{ "ok": true }

Files Changed

  • types.ts - Add waitForReady to interface
  • routes.sys.mts - Add /instances/:id/waitForReady endpoint
  • WebScraperServices.sys.mts - Implementation
  • TabManagerServices.sys.mts - Implementation
  • NRWebScraperChild.sys.mts - Message handler

Test Plan

# Create instance
curl -X POST http://localhost:58261/scraper/instances

# Navigate and wait for ready
curl -X POST http://localhost:58261/scraper/instances/ID/navigate \
  -H 'Content-Type: application/json' \
  -d '{"url": "https://example.com"}'

curl -X POST http://localhost:58261/scraper/instances/ID/waitForReady

…xt editor support

- Add setClipboard() method to set system clipboard text
  - New endpoint: POST /scraper/clipboard and POST /tabs/clipboard
  - Uses Firefox's nsIClipboardHelper for cross-platform support

- Add dispatchTextInput() method for Draft.js and similar editors
  - New endpoint: POST /instances/:id/dispatchTextInput
  - Properly fires beforeinput event with inputType: insertText
  - Draft.js listens for this event to update internal state

- Update BrowserAutomationService interface in types.ts
- Add route handlers in shared/routes.sys.mts
- Implement methods in WebScraperServices.sys.mts and TabManagerServices.sys.mts
- Add message handler in NRWebScraperChild.sys.mts
- Add DOM operations in DOMOperations.ts and DOMWriteOperations.ts

These changes enable proper text input in rich text editors like Twitter's
Draft.js-based composer, which was previously failing with setTextContent().
- Move setClipboard and dispatchTextInput inside class body in WebScraperServices
- Move setClipboard and dispatchTextInput inside class body in TabManagerServices
- Refactor dispatchTextInput to reuse setTextContent logic (removes code duplication)
- Don't set textContent directly - let the editor handle insertion via beforeinput
- Fire beforeinput event first, if not cancelled, fire input/change events
- Fallback to execCommand if InputEvent not available
- Last resort: set textContent directly

This approach works with Draft.js and similar editors that manage their own
internal state, while still working in headless mode without clipboard access.
- Remove setClipboard method (security concerns: no auth, no size limit)
- Remove /clipboard endpoint from routes
- dispatchTextInput is sufficient for rich text editor input
- Works in headless mode without clipboard access
- Setting textContent directly breaks Draft.js because it doesn't update
  the editor's internal EditorState
- Fall back to execCommand only (deprecated but works)
- Return false if all methods fail instead of silently succeeding
- Add waitForReady to BrowserAutomationService interface
- Add /instances/:id/waitForReady endpoint to routes
- Implement waitForReady in WebScraperServices and TabManagerServices
- Add WebScraper:WaitForReady message handler

waitForReady waits for document readyState to be 'interactive' or 'complete'
(DOMContentLoaded), useful for waiting until DOM is ready for manipulation.
Copilot AI review requested due to automatic review settings March 2, 2026 15:06
- Add 'state' parameter to waitForElement: attached/visible/hidden/detached
- Add isVisible() helper to check element visibility
- Add waitForDetached() for waiting element removal
- Default state is 'attached' (backward compatible)

Usage:
  POST /instances/:id/waitForElement
  {"selector": ".result", "state": "visible"}
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds two new endpoints to the OS API browser automation service:

  1. POST /instances/:id/waitForReady — waits until the document readyState is interactive or complete
  2. POST /instances/:id/dispatchTextInput — dispatches a beforeinput/input/change event sequence for rich text editors (e.g., Draft.js), without directly mutating textContent

Changes:

  • Adds waitForReady and dispatchTextInput to the BrowserAutomationService interface and route registrations
  • Implements both methods in WebScraperServices and TabManagerServices (delegating to the actor)
  • Implements dispatchTextInput in DOMWriteOperations/DOMOperations and adds message handling in NRWebScraperChild

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
shared/types.ts Adds waitForReady and dispatchTextInput to the BrowserAutomationService interface
shared/routes.sys.mts Registers the new waitForReady and dispatchTextInput HTTP routes
WebScraperServices.sys.mts Implements waitForReady and dispatchTextInput for the scraper service; has orphaned comment fragment
TabManagerServices.sys.mts Implements waitForReady and dispatchTextInput for the tab manager service; has orphaned comment fragment
DOMWriteOperations.ts Implements dispatchTextInput with beforeinput/input/change event dispatching
DOMOperations.ts Exposes dispatchTextInput via the facade
NRWebScraperChild.sys.mts Adds message handlers for both new operations; has duplicate WaitForReady case and missing text field in the data type

Comment on lines +626 to +630
if (this.tryExecCommand(rawWin, rawDoc, rawElement, "insertText", text)) {
rawElement.dispatchEvent(
new EventCtor("input", { bubbles: true, cancelable: true }),
);
rawElement.dispatchEvent(new EventCtor("change", { bubbles: true }));
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the execCommand fallback path succeeds (lines 626-631), input and change events are dispatched twice. The tryExecCommand method already dispatches input and change events internally before returning true (see DOMWriteOperations.tryExecCommand lines 50-53). The extra dispatches in the fallback block at lines 627-630 result in duplicate event firing, which could confuse event listeners in the page.

Suggested change
if (this.tryExecCommand(rawWin, rawDoc, rawElement, "insertText", text)) {
rawElement.dispatchEvent(
new EventCtor("input", { bubbles: true, cancelable: true }),
);
rawElement.dispatchEvent(new EventCtor("change", { bubbles: true }));
// Note: tryExecCommand is responsible for dispatching input/change events.
if (this.tryExecCommand(rawWin, rawDoc, rawElement, "insertText", text)) {

Copilot uses AI. Check for mistakes.
Comment on lines 202 to 203
}
break;
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "WebScraper:WaitForReady" case is added at line 202 in this PR, but the same case label already exists at line 141 (within the same switch (message.name) block). In JavaScript/TypeScript, switch cases are matched in order, so the case at line 202 will never be reached — it is dead code. The original handler at line 141 already handles this message correctly. This duplicate case should be removed.

Suggested change
}
break;

Copilot uses AI. Check for mistakes.
Comment on lines 1624 to 1625
return result;
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 1625-1626 contain an orphaned JSDoc comment fragment: * frameworks listen for to update their internal state. followed by */. There is no opening /** for this comment block. This is a syntax error that will cause a parse failure. The dispatchTextInput method is missing its full JSDoc documentation block — the opening /** and any preceding lines of the comment were not included. A complete JSDoc comment (with /**, description, @param tags, etc.) should precede the method.

Suggested change
return result;
}
/**
* Dispatches a text input event on an element, which many
* frameworks listen for to update their internal state.
*
* @param instanceId - The ID of the browser instance to use.
* @param selector - CSS selector for the target element.
* @param text - The text value to dispatch as input.

Copilot uses AI. Check for mistakes.
@@ -1751,6 +1762,25 @@ class TabManager {
await this._delayForUser(1000);
return result;
}
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lines 1764-1765 contain an orphaned JSDoc comment fragment: * frameworks listen for to update their internal state. followed by */. There is no opening /** for this comment block. This is a syntax error that will cause a parse failure. The dispatchTextInput method is missing its full JSDoc documentation block — the opening /** and any preceding lines of the comment were not included.

Suggested change
}
/**
* Dispatches a text input event on an element, simulating user typing.
* This triggers the same `textInput`-style events that many frameworks
* listen for to update their internal state.

Copilot uses AI. Check for mistakes.
Comment on lines +369 to +376
}
break;
case "WebScraper:DispatchTextInput":
if (message.data?.selector && typeof message.data?.text === "string") {
return domOps.dispatchTextInput(
message.data.selector,
message.data.text,
);
Copy link

Copilot AI Mar 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The NRWebScraperMessageData interface in webscraper/types.ts does not include a text field, but the WebScraper:DispatchTextInput case handler accesses message.data?.text. Since message.data is typed as NRWebScraperMessageData | undefined, TypeScript will flag message.data?.text as a type error (property text does not exist on NRWebScraperMessageData). The text field needs to be added to the NRWebScraperMessageData interface.

Suggested change
}
break;
case "WebScraper:DispatchTextInput":
if (message.data?.selector && typeof message.data?.text === "string") {
return domOps.dispatchTextInput(
message.data.selector,
message.data.text,
);
case "WebScraper:DispatchTextInput": {
const text = (
message.data as (NRWebScraperMessageData & { text?: string }) | undefined
)?.text;
if (message.data?.selector && typeof text === "string") {
return domOps.dispatchTextInput(message.data.selector, text);
}
break;
}

Copilot uses AI. Check for mistakes.
- Remove duplicate WebScraper:WaitForReady case in NRWebScraperChild
- Fix orphaned JSDoc comments for dispatchTextInput in both services
- Add type cast for text field in DispatchTextInput handler
- Remove duplicate event dispatching in DOMWriteOperations tryExecCommand fallback
- Add WaitForElementState type to types.ts
- Use WaitForElementState in routes, services, and child
- Add state parameter documentation to PR description
- Import WaitForElementState in WebScraperServices and TabManagerServices
…put docs

- Add isDeadObjectError() helper method to DOMWaitOperations
- Refactor all dead object error checks to use the helper
- Add detailed event sequence documentation to dispatchTextInput
- Clarify behavior when beforeinput is cancelled vs not cancelled
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants