@roxybrowser/playwright 2.0.2-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +115 -0
- package/dist/apiRequestContext.d.ts +52 -0
- package/dist/apiRequestContext.d.ts.map +1 -0
- package/dist/apiRequestContext.js +632 -0
- package/dist/apiRequestContext.js.map +1 -0
- package/dist/ariaSnapshot.d.ts +38 -0
- package/dist/ariaSnapshot.d.ts.map +1 -0
- package/dist/ariaSnapshot.js +1137 -0
- package/dist/ariaSnapshot.js.map +1 -0
- package/dist/assertions.d.ts +2 -0
- package/dist/assertions.d.ts.map +1 -0
- package/dist/assertions.js +6 -0
- package/dist/assertions.js.map +1 -0
- package/dist/bin/roxybrowser-mcp.d.ts +3 -0
- package/dist/bin/roxybrowser-mcp.d.ts.map +1 -0
- package/dist/bin/roxybrowser-mcp.js +7 -0
- package/dist/bin/roxybrowser-mcp.js.map +1 -0
- package/dist/browser.d.ts +15 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +67 -0
- package/dist/browser.js.map +1 -0
- package/dist/browserContext.d.ts +156 -0
- package/dist/browserContext.d.ts.map +1 -0
- package/dist/browserContext.js +1129 -0
- package/dist/browserContext.js.map +1 -0
- package/dist/browserContextClock.d.ts +54 -0
- package/dist/browserContextClock.d.ts.map +1 -0
- package/dist/browserContextClock.js +147 -0
- package/dist/browserContextClock.js.map +1 -0
- package/dist/browserType.d.ts +16 -0
- package/dist/browserType.d.ts.map +1 -0
- package/dist/browserType.js +68 -0
- package/dist/browserType.js.map +1 -0
- package/dist/bundle.d.ts +20 -0
- package/dist/bundle.d.ts.map +1 -0
- package/dist/bundle.js +20 -0
- package/dist/bundle.js.map +1 -0
- package/dist/clock.d.ts +43 -0
- package/dist/clock.d.ts.map +1 -0
- package/dist/clock.js +70 -0
- package/dist/clock.js.map +1 -0
- package/dist/elementHandle.d.ts +100 -0
- package/dist/elementHandle.d.ts.map +1 -0
- package/dist/elementHandle.js +423 -0
- package/dist/elementHandle.js.map +1 -0
- package/dist/errors.d.ts +10 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +19 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluation.d.ts +4 -0
- package/dist/evaluation.d.ts.map +1 -0
- package/dist/evaluation.js +9 -0
- package/dist/evaluation.js.map +1 -0
- package/dist/frame.d.ts +139 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +451 -0
- package/dist/frame.js.map +1 -0
- package/dist/httpHeaders.d.ts +6 -0
- package/dist/httpHeaders.d.ts.map +1 -0
- package/dist/httpHeaders.js +23 -0
- package/dist/httpHeaders.js.map +1 -0
- package/dist/human/controller.d.ts +15 -0
- package/dist/human/controller.d.ts.map +1 -0
- package/dist/human/controller.js +67 -0
- package/dist/human/controller.js.map +1 -0
- package/dist/human/profile.d.ts +5 -0
- package/dist/human/profile.d.ts.map +1 -0
- package/dist/human/profile.js +50 -0
- package/dist/human/profile.js.map +1 -0
- package/dist/human/types.d.ts +28 -0
- package/dist/human/types.d.ts.map +1 -0
- package/dist/human/types.js +2 -0
- package/dist/human/types.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/inputFiles.d.ts +18 -0
- package/dist/inputFiles.d.ts.map +1 -0
- package/dist/inputFiles.js +192 -0
- package/dist/inputFiles.js.map +1 -0
- package/dist/jsHandle.d.ts +32 -0
- package/dist/jsHandle.d.ts.map +1 -0
- package/dist/jsHandle.js +148 -0
- package/dist/jsHandle.js.map +1 -0
- package/dist/locator.d.ts +190 -0
- package/dist/locator.d.ts.map +1 -0
- package/dist/locator.js +883 -0
- package/dist/locator.js.map +1 -0
- package/dist/locatorSelectors.d.ts +11 -0
- package/dist/locatorSelectors.d.ts.map +1 -0
- package/dist/locatorSelectors.js +50 -0
- package/dist/locatorSelectors.js.map +1 -0
- package/dist/mcp/backend/common.d.ts +4 -0
- package/dist/mcp/backend/common.d.ts.map +1 -0
- package/dist/mcp/backend/common.js +40 -0
- package/dist/mcp/backend/common.js.map +1 -0
- package/dist/mcp/backend/connect.d.ts +10 -0
- package/dist/mcp/backend/connect.d.ts.map +1 -0
- package/dist/mcp/backend/connect.js +30 -0
- package/dist/mcp/backend/connect.js.map +1 -0
- package/dist/mcp/backend/console.d.ts +13 -0
- package/dist/mcp/backend/console.d.ts.map +1 -0
- package/dist/mcp/backend/console.js +36 -0
- package/dist/mcp/backend/console.js.map +1 -0
- package/dist/mcp/backend/context.d.ts +27 -0
- package/dist/mcp/backend/context.d.ts.map +1 -0
- package/dist/mcp/backend/context.js +33 -0
- package/dist/mcp/backend/context.js.map +1 -0
- package/dist/mcp/backend/dialogs.d.ts +7 -0
- package/dist/mcp/backend/dialogs.d.ts.map +1 -0
- package/dist/mcp/backend/dialogs.js +24 -0
- package/dist/mcp/backend/dialogs.js.map +1 -0
- package/dist/mcp/backend/evaluate.d.ts +9 -0
- package/dist/mcp/backend/evaluate.d.ts.map +1 -0
- package/dist/mcp/backend/evaluate.js +31 -0
- package/dist/mcp/backend/evaluate.js.map +1 -0
- package/dist/mcp/backend/files.d.ts +9 -0
- package/dist/mcp/backend/files.d.ts.map +1 -0
- package/dist/mcp/backend/files.js +39 -0
- package/dist/mcp/backend/files.js.map +1 -0
- package/dist/mcp/backend/keyboard.d.ts +22 -0
- package/dist/mcp/backend/keyboard.d.ts.map +1 -0
- package/dist/mcp/backend/keyboard.js +67 -0
- package/dist/mcp/backend/keyboard.js.map +1 -0
- package/dist/mcp/backend/navigate.d.ts +4 -0
- package/dist/mcp/backend/navigate.d.ts.map +1 -0
- package/dist/mcp/backend/navigate.js +82 -0
- package/dist/mcp/backend/navigate.js.map +1 -0
- package/dist/mcp/backend/network.d.ts +17 -0
- package/dist/mcp/backend/network.d.ts.map +1 -0
- package/dist/mcp/backend/network.js +144 -0
- package/dist/mcp/backend/network.js.map +1 -0
- package/dist/mcp/backend/response.d.ts +26 -0
- package/dist/mcp/backend/response.d.ts.map +1 -0
- package/dist/mcp/backend/response.js +109 -0
- package/dist/mcp/backend/response.js.map +1 -0
- package/dist/mcp/backend/runCode.d.ts +6 -0
- package/dist/mcp/backend/runCode.d.ts.map +1 -0
- package/dist/mcp/backend/runCode.js +20 -0
- package/dist/mcp/backend/runCode.js.map +1 -0
- package/dist/mcp/backend/screenshot.d.ts +13 -0
- package/dist/mcp/backend/screenshot.d.ts.map +1 -0
- package/dist/mcp/backend/screenshot.js +37 -0
- package/dist/mcp/backend/screenshot.js.map +1 -0
- package/dist/mcp/backend/snapshot.d.ts +70 -0
- package/dist/mcp/backend/snapshot.d.ts.map +1 -0
- package/dist/mcp/backend/snapshot.js +185 -0
- package/dist/mcp/backend/snapshot.js.map +1 -0
- package/dist/mcp/backend/tab.d.ts +72 -0
- package/dist/mcp/backend/tab.d.ts.map +1 -0
- package/dist/mcp/backend/tab.js +99 -0
- package/dist/mcp/backend/tab.js.map +1 -0
- package/dist/mcp/backend/tabs.d.ts +13 -0
- package/dist/mcp/backend/tabs.d.ts.map +1 -0
- package/dist/mcp/backend/tabs.js +44 -0
- package/dist/mcp/backend/tabs.js.map +1 -0
- package/dist/mcp/backend/tool.d.ts +52 -0
- package/dist/mcp/backend/tool.d.ts.map +1 -0
- package/dist/mcp/backend/tool.js +28 -0
- package/dist/mcp/backend/tool.js.map +1 -0
- package/dist/mcp/backend/tools.d.ts +4 -0
- package/dist/mcp/backend/tools.d.ts.map +1 -0
- package/dist/mcp/backend/tools.js +30 -0
- package/dist/mcp/backend/tools.js.map +1 -0
- package/dist/mcp/connectedBrowser.d.ts +3 -0
- package/dist/mcp/connectedBrowser.d.ts.map +1 -0
- package/dist/mcp/connectedBrowser.js +2153 -0
- package/dist/mcp/connectedBrowser.js.map +1 -0
- package/dist/mcp/errors.d.ts +6 -0
- package/dist/mcp/errors.d.ts.map +1 -0
- package/dist/mcp/errors.js +12 -0
- package/dist/mcp/errors.js.map +1 -0
- package/dist/mcp/format.d.ts +12 -0
- package/dist/mcp/format.d.ts.map +1 -0
- package/dist/mcp/format.js +48 -0
- package/dist/mcp/format.js.map +1 -0
- package/dist/mcp/index.d.ts +6 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +5 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/output.d.ts +7 -0
- package/dist/mcp/output.d.ts.map +1 -0
- package/dist/mcp/output.js +39 -0
- package/dist/mcp/output.js.map +1 -0
- package/dist/mcp/runtime.d.ts +122 -0
- package/dist/mcp/runtime.d.ts.map +1 -0
- package/dist/mcp/runtime.js +524 -0
- package/dist/mcp/runtime.js.map +1 -0
- package/dist/mcp/schemas.d.ts +61 -0
- package/dist/mcp/schemas.d.ts.map +1 -0
- package/dist/mcp/schemas.js +41 -0
- package/dist/mcp/schemas.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +117 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/snapshot.d.ts +3 -0
- package/dist/mcp/snapshot.d.ts.map +1 -0
- package/dist/mcp/snapshot.js +3 -0
- package/dist/mcp/snapshot.js.map +1 -0
- package/dist/mcp/tool.d.ts +19 -0
- package/dist/mcp/tool.d.ts.map +1 -0
- package/dist/mcp/tool.js +10 -0
- package/dist/mcp/tool.js.map +1 -0
- package/dist/mcp/tools/common.d.ts +6 -0
- package/dist/mcp/tools/common.d.ts.map +1 -0
- package/dist/mcp/tools/common.js +34 -0
- package/dist/mcp/tools/common.js.map +1 -0
- package/dist/mcp/tools/connect.d.ts +6 -0
- package/dist/mcp/tools/connect.d.ts.map +1 -0
- package/dist/mcp/tools/connect.js +28 -0
- package/dist/mcp/tools/connect.js.map +1 -0
- package/dist/mcp/tools/console.d.ts +6 -0
- package/dist/mcp/tools/console.d.ts.map +1 -0
- package/dist/mcp/tools/console.js +36 -0
- package/dist/mcp/tools/console.js.map +1 -0
- package/dist/mcp/tools/dialog.d.ts +6 -0
- package/dist/mcp/tools/dialog.d.ts.map +1 -0
- package/dist/mcp/tools/dialog.js +22 -0
- package/dist/mcp/tools/dialog.js.map +1 -0
- package/dist/mcp/tools/evaluate.d.ts +6 -0
- package/dist/mcp/tools/evaluate.d.ts.map +1 -0
- package/dist/mcp/tools/evaluate.js +31 -0
- package/dist/mcp/tools/evaluate.js.map +1 -0
- package/dist/mcp/tools/form.d.ts +6 -0
- package/dist/mcp/tools/form.d.ts.map +1 -0
- package/dist/mcp/tools/form.js +129 -0
- package/dist/mcp/tools/form.js.map +1 -0
- package/dist/mcp/tools/index.d.ts +3 -0
- package/dist/mcp/tools/index.d.ts.map +1 -0
- package/dist/mcp/tools/index.js +7 -0
- package/dist/mcp/tools/index.js.map +1 -0
- package/dist/mcp/tools/keyboard.d.ts +3 -0
- package/dist/mcp/tools/keyboard.d.ts.map +1 -0
- package/dist/mcp/tools/keyboard.js +44 -0
- package/dist/mcp/tools/keyboard.js.map +1 -0
- package/dist/mcp/tools/mouse.d.ts +3 -0
- package/dist/mcp/tools/mouse.d.ts.map +1 -0
- package/dist/mcp/tools/mouse.js +89 -0
- package/dist/mcp/tools/mouse.js.map +1 -0
- package/dist/mcp/tools/navigate.d.ts +6 -0
- package/dist/mcp/tools/navigate.d.ts.map +1 -0
- package/dist/mcp/tools/navigate.js +74 -0
- package/dist/mcp/tools/navigate.js.map +1 -0
- package/dist/mcp/tools/network.d.ts +6 -0
- package/dist/mcp/tools/network.d.ts.map +1 -0
- package/dist/mcp/tools/network.js +142 -0
- package/dist/mcp/tools/network.js.map +1 -0
- package/dist/mcp/tools/runCode.d.ts +6 -0
- package/dist/mcp/tools/runCode.d.ts.map +1 -0
- package/dist/mcp/tools/runCode.js +24 -0
- package/dist/mcp/tools/runCode.js.map +1 -0
- package/dist/mcp/tools/screenshot.d.ts +6 -0
- package/dist/mcp/tools/screenshot.d.ts.map +1 -0
- package/dist/mcp/tools/screenshot.js +46 -0
- package/dist/mcp/tools/screenshot.js.map +1 -0
- package/dist/mcp/tools/snapshot.d.ts +6 -0
- package/dist/mcp/tools/snapshot.d.ts.map +1 -0
- package/dist/mcp/tools/snapshot.js +56 -0
- package/dist/mcp/tools/snapshot.js.map +1 -0
- package/dist/mcp/tools/tabs.d.ts +6 -0
- package/dist/mcp/tools/tabs.d.ts.map +1 -0
- package/dist/mcp/tools/tabs.js +34 -0
- package/dist/mcp/tools/tabs.js.map +1 -0
- package/dist/mcp/transports/http.d.ts +3 -0
- package/dist/mcp/transports/http.d.ts.map +1 -0
- package/dist/mcp/transports/http.js +134 -0
- package/dist/mcp/transports/http.js.map +1 -0
- package/dist/mcp/transports/inMemory.d.ts +3 -0
- package/dist/mcp/transports/inMemory.d.ts.map +1 -0
- package/dist/mcp/transports/inMemory.js +18 -0
- package/dist/mcp/transports/inMemory.js.map +1 -0
- package/dist/mcp/transports/stdio.d.ts +3 -0
- package/dist/mcp/transports/stdio.d.ts.map +1 -0
- package/dist/mcp/transports/stdio.js +16 -0
- package/dist/mcp/transports/stdio.js.map +1 -0
- package/dist/mcp/types.d.ts +193 -0
- package/dist/mcp/types.d.ts.map +1 -0
- package/dist/mcp/types.js +2 -0
- package/dist/mcp/types.js.map +1 -0
- package/dist/navigationResult.d.ts +10 -0
- package/dist/navigationResult.d.ts.map +1 -0
- package/dist/navigationResult.js +15 -0
- package/dist/navigationResult.js.map +1 -0
- package/dist/page.d.ts +907 -0
- package/dist/page.d.ts.map +1 -0
- package/dist/page.js +6385 -0
- package/dist/page.js.map +1 -0
- package/dist/pageApiReport.d.ts +31 -0
- package/dist/pageApiReport.d.ts.map +1 -0
- package/dist/pageApiReport.js +218 -0
- package/dist/pageApiReport.js.map +1 -0
- package/dist/pageResponse.d.ts +18 -0
- package/dist/pageResponse.d.ts.map +1 -0
- package/dist/pageResponse.js +23 -0
- package/dist/pageResponse.js.map +1 -0
- package/dist/processCleanup.d.ts +8 -0
- package/dist/processCleanup.d.ts.map +1 -0
- package/dist/processCleanup.js +413 -0
- package/dist/processCleanup.js.map +1 -0
- package/dist/protocol/adapter.d.ts +406 -0
- package/dist/protocol/adapter.d.ts.map +1 -0
- package/dist/protocol/adapter.js +8 -0
- package/dist/protocol/adapter.js.map +1 -0
- package/dist/protocol/bidi/backend.d.ts +8 -0
- package/dist/protocol/bidi/backend.d.ts.map +1 -0
- package/dist/protocol/bidi/backend.js +3616 -0
- package/dist/protocol/bidi/backend.js.map +1 -0
- package/dist/protocol/bidi/client.d.ts +104 -0
- package/dist/protocol/bidi/client.d.ts.map +1 -0
- package/dist/protocol/bidi/client.js +228 -0
- package/dist/protocol/bidi/client.js.map +1 -0
- package/dist/protocol/capabilities.d.ts +11 -0
- package/dist/protocol/capabilities.d.ts.map +1 -0
- package/dist/protocol/capabilities.js +2 -0
- package/dist/protocol/capabilities.js.map +1 -0
- package/dist/protocol/cdp/backend.d.ts +24 -0
- package/dist/protocol/cdp/backend.d.ts.map +1 -0
- package/dist/protocol/cdp/backend.js +8428 -0
- package/dist/protocol/cdp/backend.js.map +1 -0
- package/dist/protocol/evaluate.d.ts +2 -0
- package/dist/protocol/evaluate.d.ts.map +1 -0
- package/dist/protocol/evaluate.js +14 -0
- package/dist/protocol/evaluate.js.map +1 -0
- package/dist/protocol/evaluationSerializer.d.ts +6 -0
- package/dist/protocol/evaluationSerializer.d.ts.map +1 -0
- package/dist/protocol/evaluationSerializer.js +234 -0
- package/dist/protocol/evaluationSerializer.js.map +1 -0
- package/dist/protocol/keyboardInput.d.ts +34 -0
- package/dist/protocol/keyboardInput.d.ts.map +1 -0
- package/dist/protocol/keyboardInput.js +246 -0
- package/dist/protocol/keyboardInput.js.map +1 -0
- package/dist/protocol/routing.d.ts +40 -0
- package/dist/protocol/routing.d.ts.map +1 -0
- package/dist/protocol/routing.js +2 -0
- package/dist/protocol/routing.js.map +1 -0
- package/dist/protocol/selectorRuntime.d.ts +30 -0
- package/dist/protocol/selectorRuntime.d.ts.map +1 -0
- package/dist/protocol/selectorRuntime.js +1814 -0
- package/dist/protocol/selectorRuntime.js.map +1 -0
- package/dist/protocol/webdriver-classic/backend.d.ts +6 -0
- package/dist/protocol/webdriver-classic/backend.d.ts.map +1 -0
- package/dist/protocol/webdriver-classic/backend.js +158 -0
- package/dist/protocol/webdriver-classic/backend.js.map +1 -0
- package/dist/routeHandler.d.ts +15 -0
- package/dist/routeHandler.d.ts.map +1 -0
- package/dist/routeHandler.js +2 -0
- package/dist/routeHandler.js.map +1 -0
- package/dist/roxybrowser.bundle.js +78236 -0
- package/dist/roxybrowser.bundle.js.map +1 -0
- package/dist/screencast.d.ts +57 -0
- package/dist/screencast.d.ts.map +1 -0
- package/dist/screencast.js +155 -0
- package/dist/screencast.js.map +1 -0
- package/dist/screencastActions.d.ts +2 -0
- package/dist/screencastActions.d.ts.map +1 -0
- package/dist/screencastActions.js +183 -0
- package/dist/screencastActions.js.map +1 -0
- package/dist/screencastOverlay.d.ts +3 -0
- package/dist/screencastOverlay.d.ts.map +1 -0
- package/dist/screencastOverlay.js +119 -0
- package/dist/screencastOverlay.js.map +1 -0
- package/dist/screenshotOptions.d.ts +26 -0
- package/dist/screenshotOptions.d.ts.map +1 -0
- package/dist/screenshotOptions.js +171 -0
- package/dist/screenshotOptions.js.map +1 -0
- package/dist/screenshotPreparation.d.ts +21 -0
- package/dist/screenshotPreparation.d.ts.map +1 -0
- package/dist/screenshotPreparation.js +199 -0
- package/dist/screenshotPreparation.js.map +1 -0
- package/dist/selectOptionValues.d.ts +10 -0
- package/dist/selectOptionValues.d.ts.map +1 -0
- package/dist/selectOptionValues.js +60 -0
- package/dist/selectOptionValues.js.map +1 -0
- package/dist/selectors.d.ts +3 -0
- package/dist/selectors.d.ts.map +1 -0
- package/dist/selectors.js +287 -0
- package/dist/selectors.js.map +1 -0
- package/dist/types/api.d.ts +2164 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +2 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/events.d.ts +158 -0
- package/dist/types/events.d.ts.map +1 -0
- package/dist/types/events.js +2 -0
- package/dist/types/events.js.map +1 -0
- package/dist/types/options.d.ts +263 -0
- package/dist/types/options.d.ts.map +1 -0
- package/dist/types/options.js +2 -0
- package/dist/types/options.js.map +1 -0
- package/dist/urlMatch.d.ts +19 -0
- package/dist/urlMatch.d.ts.map +1 -0
- package/dist/urlMatch.js +183 -0
- package/dist/urlMatch.js.map +1 -0
- package/dist/utilityScriptSerializers.d.ts +61 -0
- package/dist/utilityScriptSerializers.d.ts.map +1 -0
- package/dist/utilityScriptSerializers.js +297 -0
- package/dist/utilityScriptSerializers.js.map +1 -0
- package/dist/vendor/playwright/ariaSnapshotEvaluate.d.ts +2 -0
- package/dist/vendor/playwright/ariaSnapshotEvaluate.d.ts.map +1 -0
- package/dist/vendor/playwright/ariaSnapshotEvaluate.js +207 -0
- package/dist/vendor/playwright/ariaSnapshotEvaluate.js.map +1 -0
- package/dist/vendor/playwright/generated/clockSource.d.ts +9 -0
- package/dist/vendor/playwright/generated/clockSource.d.ts.map +1 -0
- package/dist/vendor/playwright/generated/clockSource.js +9 -0
- package/dist/vendor/playwright/generated/clockSource.js.map +1 -0
- package/dist/vendor/playwright/generated/injectedScriptSource.d.ts +9 -0
- package/dist/vendor/playwright/generated/injectedScriptSource.d.ts.map +1 -0
- package/dist/vendor/playwright/generated/injectedScriptSource.js +9 -0
- package/dist/vendor/playwright/generated/injectedScriptSource.js.map +1 -0
- package/dist/video.d.ts +39 -0
- package/dist/video.d.ts.map +1 -0
- package/dist/video.js +235 -0
- package/dist/video.js.map +1 -0
- package/dist/waitForSelector.d.ts +7 -0
- package/dist/waitForSelector.d.ts.map +1 -0
- package/dist/waitForSelector.js +23 -0
- package/dist/waitForSelector.js.map +1 -0
- package/dist/worker.d.ts +45 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +192 -0
- package/dist/worker.js.map +1 -0
- package/package.json +82 -0
|
@@ -0,0 +1,1814 @@
|
|
|
1
|
+
export const SCROLL_INTO_VIEW_IF_NEEDED_SOURCE = `(element) => {
|
|
2
|
+
const hasLayoutBox = (node) => {
|
|
3
|
+
const rects = node.getClientRects();
|
|
4
|
+
return rects.length > 0 && Array.from(rects).some((rect) => rect.width > 0 || rect.height > 0);
|
|
5
|
+
};
|
|
6
|
+
const findScrollableTarget = (node) => {
|
|
7
|
+
if (hasLayoutBox(node))
|
|
8
|
+
return node;
|
|
9
|
+
const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
|
|
10
|
+
let descendant = walker.nextNode();
|
|
11
|
+
while (descendant) {
|
|
12
|
+
if (hasLayoutBox(descendant))
|
|
13
|
+
return descendant;
|
|
14
|
+
descendant = walker.nextNode();
|
|
15
|
+
}
|
|
16
|
+
let ancestor = node.parentElement;
|
|
17
|
+
while (ancestor) {
|
|
18
|
+
if (hasLayoutBox(ancestor))
|
|
19
|
+
return ancestor;
|
|
20
|
+
ancestor = ancestor.parentElement;
|
|
21
|
+
}
|
|
22
|
+
return node;
|
|
23
|
+
};
|
|
24
|
+
findScrollableTarget(element).scrollIntoView({ block: "center", inline: "nearest", behavior: "instant" });
|
|
25
|
+
}`;
|
|
26
|
+
function selectorRuntimeOperation(payload) {
|
|
27
|
+
const resolveBridgeScope = () => {
|
|
28
|
+
const candidates = [
|
|
29
|
+
globalThis
|
|
30
|
+
];
|
|
31
|
+
try {
|
|
32
|
+
if (globalThis.top) {
|
|
33
|
+
candidates.unshift(globalThis.top);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
catch { }
|
|
37
|
+
for (const candidate of candidates) {
|
|
38
|
+
try {
|
|
39
|
+
if (candidate.__roxyHandleStore) {
|
|
40
|
+
return candidate;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
catch { }
|
|
44
|
+
}
|
|
45
|
+
return globalThis;
|
|
46
|
+
};
|
|
47
|
+
const globalState = resolveBridgeScope();
|
|
48
|
+
globalState.__roxyHandleStore ??= {};
|
|
49
|
+
globalState.__roxyNextHandleId ??= 0;
|
|
50
|
+
const normalize = (value) => (value ?? "").replace(/\s+/g, " ").trim();
|
|
51
|
+
const isDocumentNode = (node) => !!node && typeof node === "object" && "nodeType" in node && node.nodeType === 9;
|
|
52
|
+
const isElementNode = (node) => !!node && typeof node === "object" && "nodeType" in node && node.nodeType === 1;
|
|
53
|
+
const isNode = (node) => !!node && typeof node === "object" && "nodeType" in node;
|
|
54
|
+
const isTextNode = (node) => isNode(node) && node.nodeType === 3;
|
|
55
|
+
const isDocumentFragmentNode = (node) => isNode(node) && node.nodeType === 11;
|
|
56
|
+
const tagNameOf = (node) => isElementNode(node) ? node.tagName.toLowerCase() : "";
|
|
57
|
+
const isHtmlElementLike = (node) => isElementNode(node) && "style" in node && "innerText" in node;
|
|
58
|
+
const isInputElement = (element) => tagNameOf(element) === "input";
|
|
59
|
+
const isTextAreaElement = (element) => tagNameOf(element) === "textarea";
|
|
60
|
+
const isSelectElement = (element) => tagNameOf(element) === "select";
|
|
61
|
+
const isOptionElement = (element) => tagNameOf(element) === "option";
|
|
62
|
+
const isOptGroupElement = (element) => tagNameOf(element) === "optgroup";
|
|
63
|
+
const isFormControlElement = (element) => ["button", "input", "select", "textarea", "optgroup", "option", "fieldset"].includes(tagNameOf(element));
|
|
64
|
+
const textForSelector = (element) => {
|
|
65
|
+
if (isInputElement(element) &&
|
|
66
|
+
["button", "submit", "reset"].includes(element.type)) {
|
|
67
|
+
return element.value;
|
|
68
|
+
}
|
|
69
|
+
return element.textContent ?? "";
|
|
70
|
+
};
|
|
71
|
+
const immediateTextNodesForSelector = (element) => {
|
|
72
|
+
const immediateTextNodes = [];
|
|
73
|
+
let currentImmediate = "";
|
|
74
|
+
for (const node of Array.from(element.childNodes)) {
|
|
75
|
+
if (isTextNode(node)) {
|
|
76
|
+
currentImmediate += node.nodeValue ?? "";
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (node.nodeType === Node.COMMENT_NODE) {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
if (currentImmediate) {
|
|
83
|
+
immediateTextNodes.push(currentImmediate);
|
|
84
|
+
}
|
|
85
|
+
currentImmediate = "";
|
|
86
|
+
}
|
|
87
|
+
if (currentImmediate) {
|
|
88
|
+
immediateTextNodes.push(currentImmediate);
|
|
89
|
+
}
|
|
90
|
+
return immediateTextNodes;
|
|
91
|
+
};
|
|
92
|
+
const matchesTextSelector = (element, selector) => {
|
|
93
|
+
if (selector.internal) {
|
|
94
|
+
return matchesPattern(textForSelector(element), selector, "value");
|
|
95
|
+
}
|
|
96
|
+
if (selector.exact && !selector.isRegex) {
|
|
97
|
+
if (isInputElement(element) &&
|
|
98
|
+
["button", "submit", "reset"].includes(element.type)) {
|
|
99
|
+
return matchesPattern(textForSelector(element), selector, "value");
|
|
100
|
+
}
|
|
101
|
+
const immediateTextNodes = immediateTextNodesForSelector(element);
|
|
102
|
+
if (!normalize(selector.value) && !immediateTextNodes.length) {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
return immediateTextNodes.some((text) => matchesPattern(text, selector, "value"));
|
|
106
|
+
}
|
|
107
|
+
return matchesPattern(textForSelector(element), selector, "value");
|
|
108
|
+
};
|
|
109
|
+
const elementFullTextForSelector = (element) => textForSelector(element);
|
|
110
|
+
const elementImmediateTextForSelector = (element) => immediateTextNodesForSelector(element);
|
|
111
|
+
const elementMatchesCssTextPseudoSelf = (element, pseudo) => {
|
|
112
|
+
if (shouldSkipTextSelectorElement(element)) {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
if (pseudo.name === "text") {
|
|
116
|
+
if (pseudo.args.length !== 1) {
|
|
117
|
+
throw new Error(`"text" engine expects a single string`);
|
|
118
|
+
}
|
|
119
|
+
return normalize(elementFullTextForSelector(element))
|
|
120
|
+
.toLowerCase()
|
|
121
|
+
.includes(normalize(pseudo.args[0]).toLowerCase());
|
|
122
|
+
}
|
|
123
|
+
if (pseudo.name === "has-text") {
|
|
124
|
+
if (pseudo.args.length !== 1) {
|
|
125
|
+
throw new Error(`"has-text" engine expects a single string`);
|
|
126
|
+
}
|
|
127
|
+
return normalize(elementFullTextForSelector(element))
|
|
128
|
+
.toLowerCase()
|
|
129
|
+
.includes(normalize(pseudo.args[0]).toLowerCase());
|
|
130
|
+
}
|
|
131
|
+
if (pseudo.name === "text-is") {
|
|
132
|
+
if (pseudo.args.length !== 1) {
|
|
133
|
+
throw new Error(`"text-is" engine expects a single string`);
|
|
134
|
+
}
|
|
135
|
+
const text = normalize(pseudo.args[0]);
|
|
136
|
+
const immediate = elementImmediateTextForSelector(element);
|
|
137
|
+
if (!text && !immediate.length) {
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
140
|
+
return immediate.some((candidate) => normalize(candidate) === text);
|
|
141
|
+
}
|
|
142
|
+
if (pseudo.args.length === 0 ||
|
|
143
|
+
pseudo.args[0] === undefined ||
|
|
144
|
+
pseudo.args.length > 2 ||
|
|
145
|
+
(pseudo.args.length === 2 && typeof pseudo.args[1] !== "string")) {
|
|
146
|
+
throw new Error(`"text-matches" engine expects a regexp body and optional regexp flags`);
|
|
147
|
+
}
|
|
148
|
+
const regexp = new RegExp(pseudo.args[0], pseudo.args[1]);
|
|
149
|
+
return regexp.test(elementFullTextForSelector(element));
|
|
150
|
+
};
|
|
151
|
+
const activelyFocused = (node) => {
|
|
152
|
+
const root = node.getRootNode();
|
|
153
|
+
const activeElement = isDocumentNode(root)
|
|
154
|
+
? root.activeElement
|
|
155
|
+
: isDocumentFragmentNode(root) && "activeElement" in root
|
|
156
|
+
? root.activeElement
|
|
157
|
+
: null;
|
|
158
|
+
return activeElement === node && !!node.ownerDocument && node.ownerDocument.hasFocus();
|
|
159
|
+
};
|
|
160
|
+
const focusElement = (element) => {
|
|
161
|
+
const wasFocused = activelyFocused(element);
|
|
162
|
+
if ("focus" in element && typeof element.focus === "function") {
|
|
163
|
+
element.focus();
|
|
164
|
+
element.focus();
|
|
165
|
+
}
|
|
166
|
+
if (payload.resetSelectionIfNotFocused && !wasFocused && isInputElement(element)) {
|
|
167
|
+
try {
|
|
168
|
+
element.setSelectionRange(0, 0);
|
|
169
|
+
}
|
|
170
|
+
catch { }
|
|
171
|
+
}
|
|
172
|
+
};
|
|
173
|
+
const isFrameElement = (node) => {
|
|
174
|
+
const tagName = tagNameOf(node);
|
|
175
|
+
return tagName === "iframe" || tagName === "frame";
|
|
176
|
+
};
|
|
177
|
+
const pushUnique = (items, candidate) => {
|
|
178
|
+
if (!items.includes(candidate)) {
|
|
179
|
+
items.push(candidate);
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
const toElements = (list) => {
|
|
183
|
+
const elements = [];
|
|
184
|
+
for (let index = 0; index < list.length; index += 1) {
|
|
185
|
+
const item = list[index];
|
|
186
|
+
if (item) {
|
|
187
|
+
elements.push(item);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return elements;
|
|
191
|
+
};
|
|
192
|
+
const isInternalOverlayElement = (element) => {
|
|
193
|
+
const tagName = element.tagName.toLowerCase();
|
|
194
|
+
return (element.id === "__roxy_screencast_actions_style__" ||
|
|
195
|
+
element.id === "__roxy_screencast_overlay_style__" ||
|
|
196
|
+
tagName === "x-pw-action-overlays" ||
|
|
197
|
+
tagName === "x-pw-user-overlays" ||
|
|
198
|
+
element.closest("x-pw-action-overlays,x-pw-user-overlays,[data-roxy-highlight-overlay]") !== null);
|
|
199
|
+
};
|
|
200
|
+
const shouldSkipTextSelectorElement = (element) => {
|
|
201
|
+
const document = element.ownerDocument;
|
|
202
|
+
return (element.nodeName === "SCRIPT" ||
|
|
203
|
+
element.nodeName === "NOSCRIPT" ||
|
|
204
|
+
element.nodeName === "STYLE" ||
|
|
205
|
+
!!document.head?.contains(element) ||
|
|
206
|
+
isInternalOverlayElement(element));
|
|
207
|
+
};
|
|
208
|
+
const querySelectorAllPierce = (root, selector, shadowSelector = selector) => {
|
|
209
|
+
const matches = [];
|
|
210
|
+
const visitRoot = (currentRoot, isInitialRoot) => {
|
|
211
|
+
const currentSelector = isInitialRoot ? selector : shadowSelector;
|
|
212
|
+
for (const element of toElements(currentRoot.querySelectorAll(currentSelector))) {
|
|
213
|
+
if (!isInternalOverlayElement(element)) {
|
|
214
|
+
pushUnique(matches, element);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (isElementNode(currentRoot)) {
|
|
218
|
+
const shadowRoot = currentRoot.shadowRoot;
|
|
219
|
+
if (shadowRoot) {
|
|
220
|
+
visitRoot(shadowRoot, false);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
for (const element of toElements(currentRoot.querySelectorAll("*"))) {
|
|
224
|
+
const shadowRoot = element.shadowRoot;
|
|
225
|
+
if (shadowRoot) {
|
|
226
|
+
visitRoot(shadowRoot, false);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
};
|
|
230
|
+
visitRoot(root, true);
|
|
231
|
+
return matches;
|
|
232
|
+
};
|
|
233
|
+
const relativeCssSelector = (selector) => /^[>+~]/.test(selector.trim()) ? `:scope ${selector}` : selector;
|
|
234
|
+
const scopeCssSelectorList = (selector) => splitCssSelectorList(selector)
|
|
235
|
+
.map((part) => `:scope ${part}`)
|
|
236
|
+
.join(", ");
|
|
237
|
+
const splitCssSelectorList = (selector) => {
|
|
238
|
+
const parts = [];
|
|
239
|
+
let quote;
|
|
240
|
+
let escapeNext = false;
|
|
241
|
+
let bracketDepth = 0;
|
|
242
|
+
let parenDepth = 0;
|
|
243
|
+
let start = 0;
|
|
244
|
+
for (let index = 0; index < selector.length; index += 1) {
|
|
245
|
+
const char = selector[index];
|
|
246
|
+
if (escapeNext) {
|
|
247
|
+
escapeNext = false;
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
if (quote) {
|
|
251
|
+
if (char === "\\") {
|
|
252
|
+
escapeNext = true;
|
|
253
|
+
}
|
|
254
|
+
else if (char === quote) {
|
|
255
|
+
quote = undefined;
|
|
256
|
+
}
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
if (char === '"' || char === "'") {
|
|
260
|
+
quote = char;
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
263
|
+
if (char === "[") {
|
|
264
|
+
bracketDepth += 1;
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
if (char === "]") {
|
|
268
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
if (char === "(") {
|
|
272
|
+
parenDepth += 1;
|
|
273
|
+
continue;
|
|
274
|
+
}
|
|
275
|
+
if (char === ")") {
|
|
276
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
277
|
+
continue;
|
|
278
|
+
}
|
|
279
|
+
if (char === "," && bracketDepth === 0 && parenDepth === 0) {
|
|
280
|
+
const part = selector.slice(start, index).trim();
|
|
281
|
+
if (part) {
|
|
282
|
+
parts.push(part);
|
|
283
|
+
}
|
|
284
|
+
start = index + 1;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const finalPart = selector.slice(start).trim();
|
|
288
|
+
if (finalPart) {
|
|
289
|
+
parts.push(finalPart);
|
|
290
|
+
}
|
|
291
|
+
return parts.length ? parts : [selector];
|
|
292
|
+
};
|
|
293
|
+
const queryCss = (root, selector, includeRoot) => {
|
|
294
|
+
const normalizedSelector = normalizeHasScopeSelector(relativeCssSelector(selector));
|
|
295
|
+
const cssTextPseudoSelector = parseCssTextPseudoSelector(normalizedSelector);
|
|
296
|
+
if (cssTextPseudoSelector) {
|
|
297
|
+
return queryCssTextPseudo(root, cssTextPseudoSelector, includeRoot);
|
|
298
|
+
}
|
|
299
|
+
const matches = [];
|
|
300
|
+
if (includeRoot && isElementNode(root) && root.matches(normalizedSelector)) {
|
|
301
|
+
matches.push(root);
|
|
302
|
+
}
|
|
303
|
+
if (!isElementNode(root)) {
|
|
304
|
+
for (const element of querySelectorAllPierce(root, normalizedSelector)) {
|
|
305
|
+
pushUnique(matches, element);
|
|
306
|
+
}
|
|
307
|
+
return matches;
|
|
308
|
+
}
|
|
309
|
+
if (!/^[>+~]/.test(selector.trim()) && !selector.includes(":scope")) {
|
|
310
|
+
const querySelector = isElementNode(root) && root.tagName.toLowerCase() !== "html"
|
|
311
|
+
? scopeCssSelectorList(normalizedSelector)
|
|
312
|
+
: normalizedSelector;
|
|
313
|
+
for (const element of querySelectorAllPierce(root, querySelector, normalizedSelector)) {
|
|
314
|
+
pushUnique(matches, element);
|
|
315
|
+
}
|
|
316
|
+
return matches;
|
|
317
|
+
}
|
|
318
|
+
const scopeAttribute = `data-roxy-scope-${Date.now().toString(36)}-${Math.floor(Math.random() * 1_000_000).toString(36)}`;
|
|
319
|
+
const hadScopeAttribute = root.hasAttribute(scopeAttribute);
|
|
320
|
+
const previousScopeAttribute = root.getAttribute(scopeAttribute);
|
|
321
|
+
root.setAttribute(scopeAttribute, "");
|
|
322
|
+
try {
|
|
323
|
+
const scopedSelector = normalizedSelector.replace(/:scope\b/g, `[${scopeAttribute}]`);
|
|
324
|
+
const queryRoot = root.parentElement ?? root.getRootNode();
|
|
325
|
+
const candidates = isDocumentNode(queryRoot) || isDocumentFragmentNode(queryRoot) || isElementNode(queryRoot)
|
|
326
|
+
? querySelectorAllPierce(queryRoot, scopedSelector)
|
|
327
|
+
: [];
|
|
328
|
+
for (const element of candidates) {
|
|
329
|
+
pushUnique(matches, element);
|
|
330
|
+
}
|
|
331
|
+
return matches;
|
|
332
|
+
}
|
|
333
|
+
finally {
|
|
334
|
+
if (hadScopeAttribute && previousScopeAttribute !== null) {
|
|
335
|
+
root.setAttribute(scopeAttribute, previousScopeAttribute);
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
root.removeAttribute(scopeAttribute);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const normalizeHasScopeSelector = (selector) => selector.replace(/:has\(\s*:scope\s*([>+~])/g, ":has($1");
|
|
343
|
+
const parseCssTextPseudoSelector = (selector) => {
|
|
344
|
+
const head = splitCssTextPseudoHead(selector);
|
|
345
|
+
if (!head) {
|
|
346
|
+
return null;
|
|
347
|
+
}
|
|
348
|
+
const pseudos = [];
|
|
349
|
+
let quote;
|
|
350
|
+
let escapeNext = false;
|
|
351
|
+
let bracketDepth = 0;
|
|
352
|
+
let replacement = "";
|
|
353
|
+
let cursor = 0;
|
|
354
|
+
for (let index = 0; index < head.selector.length; index += 1) {
|
|
355
|
+
const char = head.selector[index];
|
|
356
|
+
if (escapeNext) {
|
|
357
|
+
escapeNext = false;
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
if (quote) {
|
|
361
|
+
if (char === "\\") {
|
|
362
|
+
escapeNext = true;
|
|
363
|
+
}
|
|
364
|
+
else if (char === quote) {
|
|
365
|
+
quote = undefined;
|
|
366
|
+
}
|
|
367
|
+
continue;
|
|
368
|
+
}
|
|
369
|
+
if (char === '"' || char === "'") {
|
|
370
|
+
quote = char;
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
if (char === "[") {
|
|
374
|
+
bracketDepth += 1;
|
|
375
|
+
continue;
|
|
376
|
+
}
|
|
377
|
+
if (char === "]") {
|
|
378
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
379
|
+
continue;
|
|
380
|
+
}
|
|
381
|
+
if (bracketDepth !== 0 || char !== ":") {
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const nameMatch = /^:(text-is|text-matches|has-text|text)\s*\(/.exec(head.selector.slice(index));
|
|
385
|
+
if (!nameMatch) {
|
|
386
|
+
continue;
|
|
387
|
+
}
|
|
388
|
+
const name = nameMatch[1];
|
|
389
|
+
const argsStart = index + nameMatch[0].length;
|
|
390
|
+
const argsEnd = findCssFunctionEnd(head.selector, argsStart);
|
|
391
|
+
if (argsEnd === -1) {
|
|
392
|
+
continue;
|
|
393
|
+
}
|
|
394
|
+
const argsSource = head.selector.slice(argsStart, argsEnd);
|
|
395
|
+
const args = parseCssTextPseudoArgs(name, argsSource);
|
|
396
|
+
pseudos.push({
|
|
397
|
+
name,
|
|
398
|
+
args,
|
|
399
|
+
start: index,
|
|
400
|
+
end: argsEnd + 1
|
|
401
|
+
});
|
|
402
|
+
replacement += head.selector.slice(cursor, index);
|
|
403
|
+
cursor = argsEnd + 1;
|
|
404
|
+
index = argsEnd;
|
|
405
|
+
}
|
|
406
|
+
replacement += head.selector.slice(cursor);
|
|
407
|
+
const trimmedReplacement = replacement.trim();
|
|
408
|
+
const baseSelector = trimmedReplacement || "*";
|
|
409
|
+
return {
|
|
410
|
+
baseSelector,
|
|
411
|
+
...(head.descendantSelector ? { descendantSelector: head.descendantSelector } : {}),
|
|
412
|
+
pseudos
|
|
413
|
+
};
|
|
414
|
+
};
|
|
415
|
+
const splitCssTextPseudoHead = (selector) => {
|
|
416
|
+
let quote;
|
|
417
|
+
let escapeNext = false;
|
|
418
|
+
let bracketDepth = 0;
|
|
419
|
+
let parenDepth = 0;
|
|
420
|
+
let firstPseudoEnd = -1;
|
|
421
|
+
for (let index = 0; index < selector.length; index += 1) {
|
|
422
|
+
const char = selector[index];
|
|
423
|
+
if (escapeNext) {
|
|
424
|
+
escapeNext = false;
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
if (quote) {
|
|
428
|
+
if (char === "\\") {
|
|
429
|
+
escapeNext = true;
|
|
430
|
+
}
|
|
431
|
+
else if (char === quote) {
|
|
432
|
+
quote = undefined;
|
|
433
|
+
}
|
|
434
|
+
continue;
|
|
435
|
+
}
|
|
436
|
+
if (char === '"' || char === "'") {
|
|
437
|
+
quote = char;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
if (char === "[") {
|
|
441
|
+
bracketDepth += 1;
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
if (char === "]") {
|
|
445
|
+
bracketDepth = Math.max(0, bracketDepth - 1);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
if (char === "(") {
|
|
449
|
+
parenDepth += 1;
|
|
450
|
+
continue;
|
|
451
|
+
}
|
|
452
|
+
if (char === ")") {
|
|
453
|
+
parenDepth = Math.max(0, parenDepth - 1);
|
|
454
|
+
continue;
|
|
455
|
+
}
|
|
456
|
+
if (bracketDepth === 0 && parenDepth === 0 && char === ":") {
|
|
457
|
+
const nameMatch = /^:(text-is|text-matches|has-text|text)\s*\(/.exec(selector.slice(index));
|
|
458
|
+
if (nameMatch) {
|
|
459
|
+
const argsStart = index + nameMatch[0].length;
|
|
460
|
+
const argsEnd = findCssFunctionEnd(selector, argsStart);
|
|
461
|
+
if (argsEnd !== -1) {
|
|
462
|
+
firstPseudoEnd = argsEnd + 1;
|
|
463
|
+
index = argsEnd;
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
if (firstPseudoEnd !== -1 && bracketDepth === 0 && parenDepth === 0 && /\s/.test(char)) {
|
|
469
|
+
const headSelector = selector.slice(0, index).trim();
|
|
470
|
+
const descendantSelector = selector.slice(index).trim();
|
|
471
|
+
if (headSelector && descendantSelector) {
|
|
472
|
+
return { selector: headSelector, descendantSelector };
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return firstPseudoEnd === -1 ? null : { selector };
|
|
477
|
+
};
|
|
478
|
+
const findCssFunctionEnd = (selector, argsStart) => {
|
|
479
|
+
let quote;
|
|
480
|
+
let escapeNext = false;
|
|
481
|
+
let depth = 1;
|
|
482
|
+
for (let index = argsStart; index < selector.length; index += 1) {
|
|
483
|
+
const char = selector[index];
|
|
484
|
+
if (escapeNext) {
|
|
485
|
+
escapeNext = false;
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
if (quote) {
|
|
489
|
+
if (char === "\\") {
|
|
490
|
+
escapeNext = true;
|
|
491
|
+
}
|
|
492
|
+
else if (char === quote) {
|
|
493
|
+
quote = undefined;
|
|
494
|
+
}
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
if (char === '"' || char === "'") {
|
|
498
|
+
quote = char;
|
|
499
|
+
continue;
|
|
500
|
+
}
|
|
501
|
+
if (char === "(") {
|
|
502
|
+
depth += 1;
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
if (char === ")") {
|
|
506
|
+
depth -= 1;
|
|
507
|
+
if (depth === 0) {
|
|
508
|
+
return index;
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
return -1;
|
|
513
|
+
};
|
|
514
|
+
const parseCssTextPseudoArgs = (name, argsSource) => {
|
|
515
|
+
const args = [];
|
|
516
|
+
let quote;
|
|
517
|
+
let escapeNext = false;
|
|
518
|
+
let current = "";
|
|
519
|
+
let hasNonWhitespaceOutsideString = false;
|
|
520
|
+
for (let index = 0; index < argsSource.length; index += 1) {
|
|
521
|
+
const char = argsSource[index];
|
|
522
|
+
if (escapeNext) {
|
|
523
|
+
current += cssUnescapeCharacter(char);
|
|
524
|
+
escapeNext = false;
|
|
525
|
+
continue;
|
|
526
|
+
}
|
|
527
|
+
if (quote) {
|
|
528
|
+
if (char === "\\") {
|
|
529
|
+
escapeNext = true;
|
|
530
|
+
}
|
|
531
|
+
else if (char === quote) {
|
|
532
|
+
quote = undefined;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
current += char;
|
|
536
|
+
}
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
if (char === '"' || char === "'") {
|
|
540
|
+
quote = char;
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
if (char === ",") {
|
|
544
|
+
args.push(current);
|
|
545
|
+
current = "";
|
|
546
|
+
hasNonWhitespaceOutsideString = false;
|
|
547
|
+
continue;
|
|
548
|
+
}
|
|
549
|
+
if (!/\s/.test(char)) {
|
|
550
|
+
hasNonWhitespaceOutsideString = true;
|
|
551
|
+
}
|
|
552
|
+
current += char;
|
|
553
|
+
}
|
|
554
|
+
if (quote || hasNonWhitespaceOutsideString) {
|
|
555
|
+
throwCssTextPseudoArgumentError(name);
|
|
556
|
+
}
|
|
557
|
+
args.push(current);
|
|
558
|
+
return args.map((arg) => arg.trim());
|
|
559
|
+
};
|
|
560
|
+
const cssUnescapeCharacter = (char) => {
|
|
561
|
+
if (char.toLowerCase() === "a") {
|
|
562
|
+
return "\n";
|
|
563
|
+
}
|
|
564
|
+
if (char === "n") {
|
|
565
|
+
return "\n";
|
|
566
|
+
}
|
|
567
|
+
if (char === "t") {
|
|
568
|
+
return "\t";
|
|
569
|
+
}
|
|
570
|
+
return char;
|
|
571
|
+
};
|
|
572
|
+
const throwCssTextPseudoArgumentError = (name) => {
|
|
573
|
+
if (name === "text") {
|
|
574
|
+
throw new Error(`"text" engine expects a single string`);
|
|
575
|
+
}
|
|
576
|
+
if (name === "text-is") {
|
|
577
|
+
throw new Error(`"text-is" engine expects a single string`);
|
|
578
|
+
}
|
|
579
|
+
if (name === "has-text") {
|
|
580
|
+
throw new Error(`"has-text" engine expects a single string`);
|
|
581
|
+
}
|
|
582
|
+
throw new Error(`"text-matches" engine expects a regexp body and optional regexp flags`);
|
|
583
|
+
};
|
|
584
|
+
const elementMatchesCssTextPseudos = (element, pseudos) => {
|
|
585
|
+
for (const pseudo of pseudos) {
|
|
586
|
+
if (!elementMatchesCssTextPseudoSelf(element, pseudo)) {
|
|
587
|
+
return false;
|
|
588
|
+
}
|
|
589
|
+
if (pseudo.name === "text" || pseudo.name === "text-matches") {
|
|
590
|
+
const childElements = descendantsOf(element, false);
|
|
591
|
+
if (childElements.some((child) => elementMatchesCssTextPseudoSelf(child, pseudo))) {
|
|
592
|
+
return false;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
return true;
|
|
597
|
+
};
|
|
598
|
+
const queryCssTextPseudo = (root, parsed, includeRoot) => {
|
|
599
|
+
const candidates = parsed.baseSelector === "*"
|
|
600
|
+
? descendantsOf(root, includeRoot)
|
|
601
|
+
: queryCss(root, parsed.baseSelector, includeRoot);
|
|
602
|
+
const matching = candidates.filter((element) => elementMatchesCssTextPseudos(element, parsed.pseudos));
|
|
603
|
+
if (!parsed.descendantSelector) {
|
|
604
|
+
return matching;
|
|
605
|
+
}
|
|
606
|
+
const descendants = [];
|
|
607
|
+
for (const element of matching) {
|
|
608
|
+
for (const descendant of queryCss(element, parsed.descendantSelector, false)) {
|
|
609
|
+
pushUnique(descendants, descendant);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return descendants;
|
|
613
|
+
};
|
|
614
|
+
const compilePattern = (selector, kind) => {
|
|
615
|
+
const value = kind === "value" ? selector.value : kind === "name" ? selector.name ?? "" : selector.label ?? "";
|
|
616
|
+
const isRegex = kind === "value" ? selector.isRegex : kind === "name" ? selector.nameIsRegex : selector.labelIsRegex;
|
|
617
|
+
const flags = kind === "value" ? selector.regexFlags : kind === "name" ? selector.nameRegexFlags : selector.labelRegexFlags;
|
|
618
|
+
if (isRegex) {
|
|
619
|
+
return new RegExp(value, flags ?? "");
|
|
620
|
+
}
|
|
621
|
+
return value;
|
|
622
|
+
};
|
|
623
|
+
const matchesPattern = (candidate, selector, kind) => {
|
|
624
|
+
const pattern = compilePattern(selector, kind);
|
|
625
|
+
const normalizedCandidate = normalize(candidate);
|
|
626
|
+
if (pattern instanceof RegExp) {
|
|
627
|
+
return pattern.test(normalizedCandidate);
|
|
628
|
+
}
|
|
629
|
+
const normalizedPattern = normalize(pattern);
|
|
630
|
+
if (selector.exact) {
|
|
631
|
+
return normalizedCandidate === normalizedPattern;
|
|
632
|
+
}
|
|
633
|
+
return normalizedCandidate.toLowerCase().includes(normalizedPattern.toLowerCase());
|
|
634
|
+
};
|
|
635
|
+
const implicitRole = (element) => {
|
|
636
|
+
const tagName = element.tagName.toLowerCase();
|
|
637
|
+
if (tagName === "button")
|
|
638
|
+
return "button";
|
|
639
|
+
if (tagName === "a" && element.hasAttribute("href"))
|
|
640
|
+
return "link";
|
|
641
|
+
if (tagName === "textarea")
|
|
642
|
+
return "textbox";
|
|
643
|
+
if (tagName === "select") {
|
|
644
|
+
return element.hasAttribute("multiple") ? "listbox" : "combobox";
|
|
645
|
+
}
|
|
646
|
+
if (tagName === "img")
|
|
647
|
+
return "img";
|
|
648
|
+
if (tagName !== "input")
|
|
649
|
+
return null;
|
|
650
|
+
const type = (element.getAttribute("type") ?? "text").toLowerCase();
|
|
651
|
+
switch (type) {
|
|
652
|
+
case "button":
|
|
653
|
+
case "submit":
|
|
654
|
+
case "reset":
|
|
655
|
+
return "button";
|
|
656
|
+
case "checkbox":
|
|
657
|
+
return "checkbox";
|
|
658
|
+
case "radio":
|
|
659
|
+
return "radio";
|
|
660
|
+
case "range":
|
|
661
|
+
return "slider";
|
|
662
|
+
case "email":
|
|
663
|
+
case "password":
|
|
664
|
+
case "search":
|
|
665
|
+
case "tel":
|
|
666
|
+
case "text":
|
|
667
|
+
case "url":
|
|
668
|
+
return "textbox";
|
|
669
|
+
default:
|
|
670
|
+
return null;
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
const roleOf = (element) => normalize(element.getAttribute("role")) || implicitRole(element);
|
|
674
|
+
const accessibleName = (element) => {
|
|
675
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
676
|
+
if (labelledBy) {
|
|
677
|
+
const ids = labelledBy.split(/\s+/);
|
|
678
|
+
const parts = [];
|
|
679
|
+
for (const id of ids) {
|
|
680
|
+
const node = document.getElementById(id);
|
|
681
|
+
if (node) {
|
|
682
|
+
parts.push(normalize(node.innerText || node.textContent));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
const text = parts.join(" ");
|
|
686
|
+
if (text)
|
|
687
|
+
return normalize(text);
|
|
688
|
+
}
|
|
689
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
690
|
+
if (ariaLabel)
|
|
691
|
+
return normalize(ariaLabel);
|
|
692
|
+
const labels = "labels" in element
|
|
693
|
+
? Array.from((element
|
|
694
|
+
.labels ?? []))
|
|
695
|
+
: [];
|
|
696
|
+
if (labels.length) {
|
|
697
|
+
const labelText = labels
|
|
698
|
+
.map((label) => normalize(label.innerText || label.textContent))
|
|
699
|
+
.filter(Boolean)
|
|
700
|
+
.join(" ");
|
|
701
|
+
if (labelText)
|
|
702
|
+
return normalize(labelText);
|
|
703
|
+
}
|
|
704
|
+
if (isInputElement(element) && ["button", "submit", "reset"].includes(element.type)) {
|
|
705
|
+
return normalize(element.value);
|
|
706
|
+
}
|
|
707
|
+
return normalize(element.innerText || element.textContent);
|
|
708
|
+
};
|
|
709
|
+
const labelTextForControl = (element) => {
|
|
710
|
+
const ariaLabelledBy = element.getAttribute("aria-labelledby");
|
|
711
|
+
if (ariaLabelledBy) {
|
|
712
|
+
const text = ariaLabelledBy
|
|
713
|
+
.split(/\s+/)
|
|
714
|
+
.map((id) => document.getElementById(id))
|
|
715
|
+
.filter((node) => Boolean(node))
|
|
716
|
+
.map((node) => normalize(node.innerText || node.textContent))
|
|
717
|
+
.join(" ");
|
|
718
|
+
if (text) {
|
|
719
|
+
return normalize(text);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
723
|
+
if (ariaLabel) {
|
|
724
|
+
return normalize(ariaLabel);
|
|
725
|
+
}
|
|
726
|
+
const labels = "labels" in element
|
|
727
|
+
? Array.from((element
|
|
728
|
+
.labels ?? []))
|
|
729
|
+
: [];
|
|
730
|
+
if (labels.length) {
|
|
731
|
+
return normalize(labels
|
|
732
|
+
.map((label) => normalize(label.innerText || label.textContent))
|
|
733
|
+
.filter(Boolean)
|
|
734
|
+
.join(" "));
|
|
735
|
+
}
|
|
736
|
+
return "";
|
|
737
|
+
};
|
|
738
|
+
const attributeValueForSelector = (element, selector) => {
|
|
739
|
+
switch (selector.label) {
|
|
740
|
+
case "alt":
|
|
741
|
+
return element.getAttribute("alt");
|
|
742
|
+
case "label":
|
|
743
|
+
return labelTextForControl(element);
|
|
744
|
+
case "placeholder":
|
|
745
|
+
return element.getAttribute("placeholder");
|
|
746
|
+
case "testId":
|
|
747
|
+
return (element.getAttribute("data-testid") ??
|
|
748
|
+
element.getAttribute("data-test-id") ??
|
|
749
|
+
element.getAttribute("data-test"));
|
|
750
|
+
case "title":
|
|
751
|
+
return element.getAttribute("title");
|
|
752
|
+
default:
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
const descendantsOf = (root, includeRoot, pierceShadow = true) => {
|
|
757
|
+
const descendants = [];
|
|
758
|
+
if (includeRoot && isElementNode(root)) {
|
|
759
|
+
descendants.push(root);
|
|
760
|
+
}
|
|
761
|
+
if (isDocumentNode(root)) {
|
|
762
|
+
if (includeRoot && root.documentElement) {
|
|
763
|
+
pushUnique(descendants, root.documentElement);
|
|
764
|
+
}
|
|
765
|
+
const elements = pierceShadow
|
|
766
|
+
? querySelectorAllPierce(root, "*")
|
|
767
|
+
: Array.from(root.querySelectorAll("*"));
|
|
768
|
+
for (const element of elements) {
|
|
769
|
+
pushUnique(descendants, element);
|
|
770
|
+
}
|
|
771
|
+
return descendants;
|
|
772
|
+
}
|
|
773
|
+
const elements = pierceShadow
|
|
774
|
+
? querySelectorAllPierce(root, "*")
|
|
775
|
+
: Array.from(root.querySelectorAll("*"));
|
|
776
|
+
for (const element of elements) {
|
|
777
|
+
pushUnique(descendants, element);
|
|
778
|
+
}
|
|
779
|
+
return descendants;
|
|
780
|
+
};
|
|
781
|
+
const xpathCandidates = (root, expression, includeRoot) => {
|
|
782
|
+
const ownerDocument = isDocumentNode(root) ? root : root.ownerDocument ?? document;
|
|
783
|
+
const result = ownerDocument.evaluate(expression, root, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
784
|
+
const elements = [];
|
|
785
|
+
for (let index = 0; index < result.snapshotLength; index += 1) {
|
|
786
|
+
const node = result.snapshotItem(index);
|
|
787
|
+
if (!isElementNode(node)) {
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
if (!includeRoot && isElementNode(root) && node === root) {
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
pushUnique(elements, node);
|
|
794
|
+
}
|
|
795
|
+
return elements;
|
|
796
|
+
};
|
|
797
|
+
const candidatesFromRoot = (root, selector, includeRoot) => {
|
|
798
|
+
if (selector.strategy === "control") {
|
|
799
|
+
return [];
|
|
800
|
+
}
|
|
801
|
+
if (selector.strategy === "css") {
|
|
802
|
+
if (selector.label) {
|
|
803
|
+
return descendantsOf(root, includeRoot).filter((element) => {
|
|
804
|
+
const value = attributeValueForSelector(element, selector);
|
|
805
|
+
return value !== null && matchesPattern(value, selector, "value");
|
|
806
|
+
});
|
|
807
|
+
}
|
|
808
|
+
return queryCss(root, selector.value, includeRoot);
|
|
809
|
+
}
|
|
810
|
+
if (selector.strategy === "xpath") {
|
|
811
|
+
return xpathCandidates(root, selector.value, includeRoot);
|
|
812
|
+
}
|
|
813
|
+
const descendants = descendantsOf(root, includeRoot, !selector.light);
|
|
814
|
+
if (selector.strategy === "text") {
|
|
815
|
+
const matching = descendants.filter((element) => {
|
|
816
|
+
if (shouldSkipTextSelectorElement(element)) {
|
|
817
|
+
return false;
|
|
818
|
+
}
|
|
819
|
+
return matchesTextSelector(element, selector);
|
|
820
|
+
});
|
|
821
|
+
return matching.filter((element) => {
|
|
822
|
+
const childElements = descendantsOf(element, false, !selector.light);
|
|
823
|
+
return !childElements.some((child) => !shouldSkipTextSelectorElement(child) &&
|
|
824
|
+
matchesTextSelector(child, selector));
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
return descendants.filter((element) => {
|
|
828
|
+
if (roleOf(element) !== selector.value) {
|
|
829
|
+
return false;
|
|
830
|
+
}
|
|
831
|
+
if (selector.name === undefined && !selector.nameIsRegex) {
|
|
832
|
+
return true;
|
|
833
|
+
}
|
|
834
|
+
return matchesPattern(accessibleName(element), selector, "name");
|
|
835
|
+
});
|
|
836
|
+
};
|
|
837
|
+
const applyPick = (elements, pick) => {
|
|
838
|
+
if (!pick) {
|
|
839
|
+
return elements;
|
|
840
|
+
}
|
|
841
|
+
if (pick.kind === "first") {
|
|
842
|
+
return elements.slice(0, 1);
|
|
843
|
+
}
|
|
844
|
+
if (pick.kind === "last") {
|
|
845
|
+
return elements.slice(-1);
|
|
846
|
+
}
|
|
847
|
+
const pickedElement = elements[pick.index];
|
|
848
|
+
return pickedElement ? [pickedElement] : [];
|
|
849
|
+
};
|
|
850
|
+
const applyMatchPick = (matches, pick) => {
|
|
851
|
+
if (!pick) {
|
|
852
|
+
return matches;
|
|
853
|
+
}
|
|
854
|
+
if (pick.kind === "first") {
|
|
855
|
+
return matches.slice(0, 1);
|
|
856
|
+
}
|
|
857
|
+
if (pick.kind === "last") {
|
|
858
|
+
return matches.slice(-1);
|
|
859
|
+
}
|
|
860
|
+
const pickedMatch = matches[pick.index];
|
|
861
|
+
return pickedMatch ? [pickedMatch] : [];
|
|
862
|
+
};
|
|
863
|
+
const compareElementsInDomOrder = (left, right) => {
|
|
864
|
+
if (left === right) {
|
|
865
|
+
return 0;
|
|
866
|
+
}
|
|
867
|
+
const position = left.compareDocumentPosition(right);
|
|
868
|
+
if (position & Node.DOCUMENT_POSITION_PRECEDING) {
|
|
869
|
+
return 1;
|
|
870
|
+
}
|
|
871
|
+
if (position & Node.DOCUMENT_POSITION_FOLLOWING) {
|
|
872
|
+
return -1;
|
|
873
|
+
}
|
|
874
|
+
return 0;
|
|
875
|
+
};
|
|
876
|
+
const resolveSelectorChain = (roots, chain, pick, hasScope) => {
|
|
877
|
+
if (!chain.length) {
|
|
878
|
+
return applyPick(roots.filter((node) => isElementNode(node)), pick);
|
|
879
|
+
}
|
|
880
|
+
let current = roots.map((root) => ({
|
|
881
|
+
node: root,
|
|
882
|
+
capture: null
|
|
883
|
+
}));
|
|
884
|
+
for (let index = 0; index < chain.length; index += 1) {
|
|
885
|
+
const selector = chain[index];
|
|
886
|
+
if (selector.strategy === "control" && selector.value === "enter-frame") {
|
|
887
|
+
const next = [];
|
|
888
|
+
for (const match of current) {
|
|
889
|
+
if (!isFrameElement(match.node)) {
|
|
890
|
+
continue;
|
|
891
|
+
}
|
|
892
|
+
const contentDocument = match.node.contentDocument;
|
|
893
|
+
if (contentDocument) {
|
|
894
|
+
next.push({
|
|
895
|
+
node: contentDocument,
|
|
896
|
+
capture: match.capture
|
|
897
|
+
});
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
current = next;
|
|
901
|
+
continue;
|
|
902
|
+
}
|
|
903
|
+
if (selector.strategy === "control" && selector.value === "pick" && selector.pick) {
|
|
904
|
+
if (selector.pick.kind === "nth" && current.some((match) => match.capture)) {
|
|
905
|
+
throw new Error("Can't query n-th element");
|
|
906
|
+
}
|
|
907
|
+
current = applyMatchPick(current, selector.pick);
|
|
908
|
+
continue;
|
|
909
|
+
}
|
|
910
|
+
const includeRoot = (!hasScope && index === 0) || selector.strategy === "text";
|
|
911
|
+
const next = [];
|
|
912
|
+
if (selector.composite && selector.hasChain) {
|
|
913
|
+
if (selector.composite === "and") {
|
|
914
|
+
const andElements = new Set(resolveSelectorChain(roots, selector.hasChain, undefined, hasScope));
|
|
915
|
+
for (const match of current) {
|
|
916
|
+
const node = match.capture ?? match.node;
|
|
917
|
+
if (isElementNode(node) && andElements.has(node)) {
|
|
918
|
+
next.push(match);
|
|
919
|
+
}
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
else if (selector.composite === "or") {
|
|
923
|
+
for (const match of current) {
|
|
924
|
+
next.push(match);
|
|
925
|
+
}
|
|
926
|
+
for (const candidate of resolveSelectorChain(roots, selector.hasChain, undefined, hasScope)) {
|
|
927
|
+
if (!next.some((entry) => entry.node === candidate && entry.capture === null)) {
|
|
928
|
+
next.push({
|
|
929
|
+
node: candidate,
|
|
930
|
+
capture: null
|
|
931
|
+
});
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
next.sort((left, right) => compareElementsInDomOrder(left.capture ?? left.node, right.capture ?? right.node));
|
|
935
|
+
}
|
|
936
|
+
else {
|
|
937
|
+
for (const match of current) {
|
|
938
|
+
for (const candidate of resolveSelectorChain([match.node], selector.hasChain, undefined, true)) {
|
|
939
|
+
const capture = match.capture;
|
|
940
|
+
if (!next.some((entry) => entry.node === candidate && entry.capture === capture)) {
|
|
941
|
+
next.push({
|
|
942
|
+
node: candidate,
|
|
943
|
+
capture
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
current = next;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
for (const match of current) {
|
|
953
|
+
if (selector.filter) {
|
|
954
|
+
const matched = isElementNode(match.node) && matchesFilterSelector(match.node, selector);
|
|
955
|
+
if (selector.negate ? !matched : matched) {
|
|
956
|
+
next.push(match);
|
|
957
|
+
}
|
|
958
|
+
continue;
|
|
959
|
+
}
|
|
960
|
+
for (const candidate of candidatesFromRoot(match.node, selector, includeRoot)) {
|
|
961
|
+
const capture = selector.capture ? candidate : match.capture;
|
|
962
|
+
if (!next.some((entry) => entry.node === candidate && entry.capture === capture)) {
|
|
963
|
+
next.push({
|
|
964
|
+
node: candidate,
|
|
965
|
+
capture
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
current = next;
|
|
971
|
+
}
|
|
972
|
+
const resolved = [];
|
|
973
|
+
for (const match of current) {
|
|
974
|
+
const node = match.capture ?? match.node;
|
|
975
|
+
if (isElementNode(node)) {
|
|
976
|
+
pushUnique(resolved, node);
|
|
977
|
+
}
|
|
978
|
+
}
|
|
979
|
+
return applyPick(resolved, pick);
|
|
980
|
+
};
|
|
981
|
+
const resolveReference = (reference) => {
|
|
982
|
+
if (reference.handleId) {
|
|
983
|
+
const node = globalState.__roxyHandleStore?.[reference.handleId] ?? null;
|
|
984
|
+
return node ? [node] : [];
|
|
985
|
+
}
|
|
986
|
+
const roots = reference.scope
|
|
987
|
+
? resolveReference(reference.scope)
|
|
988
|
+
.filter((node) => isElementNode(node) || isDocumentNode(node))
|
|
989
|
+
: [document];
|
|
990
|
+
return resolveSelectorChain(roots, reference.chain, reference.pick, Boolean(reference.scope));
|
|
991
|
+
};
|
|
992
|
+
const matchesFilterSelector = (element, selector) => {
|
|
993
|
+
if (selector.strategy === "control" && selector.value === "visible") {
|
|
994
|
+
return isHtmlElementLike(element) && isVisible(element) === selector.visible;
|
|
995
|
+
}
|
|
996
|
+
if (selector.hasChain) {
|
|
997
|
+
return resolveSelectorChain([element], selector.hasChain, undefined, true).length > 0;
|
|
998
|
+
}
|
|
999
|
+
if (selector.strategy === "text") {
|
|
1000
|
+
return !shouldSkipTextSelectorElement(element) && matchesTextSelector(element, selector);
|
|
1001
|
+
}
|
|
1002
|
+
return candidatesFromRoot(element, selector, true).includes(element);
|
|
1003
|
+
};
|
|
1004
|
+
const reviveArgument = (value) => {
|
|
1005
|
+
if (Array.isArray(value)) {
|
|
1006
|
+
return value.map((entry) => reviveArgument(entry));
|
|
1007
|
+
}
|
|
1008
|
+
if (value && typeof value === "object") {
|
|
1009
|
+
if ("__roxyElementHandle" in value) {
|
|
1010
|
+
const reference = value
|
|
1011
|
+
.__roxyElementHandle;
|
|
1012
|
+
return resolveReference(reference)[0] ?? null;
|
|
1013
|
+
}
|
|
1014
|
+
return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, reviveArgument(entry)]));
|
|
1015
|
+
}
|
|
1016
|
+
return value;
|
|
1017
|
+
};
|
|
1018
|
+
const isVisible = (element) => {
|
|
1019
|
+
const style = window.getComputedStyle(element);
|
|
1020
|
+
if (style.display === "contents") {
|
|
1021
|
+
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
1022
|
+
if (isElementNode(child) && isVisible(child)) {
|
|
1023
|
+
return true;
|
|
1024
|
+
}
|
|
1025
|
+
if (isTextNode(child) && isVisibleTextNode(child)) {
|
|
1026
|
+
return true;
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
return false;
|
|
1030
|
+
}
|
|
1031
|
+
if (!hasVisibleStyle(element, style)) {
|
|
1032
|
+
return false;
|
|
1033
|
+
}
|
|
1034
|
+
const rect = element.getBoundingClientRect();
|
|
1035
|
+
return rect.width > 0 && rect.height > 0;
|
|
1036
|
+
};
|
|
1037
|
+
const isVisibleTextNode = (node) => {
|
|
1038
|
+
const range = node.ownerDocument.createRange();
|
|
1039
|
+
range.selectNode(node);
|
|
1040
|
+
const rect = range.getBoundingClientRect();
|
|
1041
|
+
range.detach();
|
|
1042
|
+
return rect.width > 0 && rect.height > 0;
|
|
1043
|
+
};
|
|
1044
|
+
const hasVisibleStyle = (element, style = window.getComputedStyle(element)) => {
|
|
1045
|
+
if ("checkVisibility" in element &&
|
|
1046
|
+
typeof element.checkVisibility === "function" &&
|
|
1047
|
+
!element.checkVisibility()) {
|
|
1048
|
+
return false;
|
|
1049
|
+
}
|
|
1050
|
+
const detailsOrSummary = element.closest("details,summary");
|
|
1051
|
+
if (detailsOrSummary &&
|
|
1052
|
+
detailsOrSummary !== element &&
|
|
1053
|
+
tagNameOf(detailsOrSummary) === "details" &&
|
|
1054
|
+
"open" in detailsOrSummary &&
|
|
1055
|
+
!detailsOrSummary.open) {
|
|
1056
|
+
return false;
|
|
1057
|
+
}
|
|
1058
|
+
if (style.visibility !== "visible") {
|
|
1059
|
+
return false;
|
|
1060
|
+
}
|
|
1061
|
+
let current = element.parentElement;
|
|
1062
|
+
while (current) {
|
|
1063
|
+
const parentStyle = window.getComputedStyle(current);
|
|
1064
|
+
if (parentStyle.visibility !== "visible" || parentStyle.display === "none") {
|
|
1065
|
+
return false;
|
|
1066
|
+
}
|
|
1067
|
+
current = current.parentElement;
|
|
1068
|
+
}
|
|
1069
|
+
return true;
|
|
1070
|
+
};
|
|
1071
|
+
const isDisabled = (element) => {
|
|
1072
|
+
if (isOptionElement(element) && element.parentElement && isOptGroupElement(element.parentElement) && element.parentElement.disabled) {
|
|
1073
|
+
return true;
|
|
1074
|
+
}
|
|
1075
|
+
if (isFormControlElement(element)) {
|
|
1076
|
+
return element.disabled;
|
|
1077
|
+
}
|
|
1078
|
+
let current = element;
|
|
1079
|
+
while (current) {
|
|
1080
|
+
const ariaDisabled = current.getAttribute("aria-disabled")?.toLowerCase();
|
|
1081
|
+
if (ariaDisabled === "true") {
|
|
1082
|
+
return true;
|
|
1083
|
+
}
|
|
1084
|
+
if (ariaDisabled === "false") {
|
|
1085
|
+
return false;
|
|
1086
|
+
}
|
|
1087
|
+
current = parentElementOrShadowHost(current);
|
|
1088
|
+
}
|
|
1089
|
+
return false;
|
|
1090
|
+
};
|
|
1091
|
+
const isEditable = (element) => {
|
|
1092
|
+
if (isInputElement(element) ||
|
|
1093
|
+
isTextAreaElement(element) ||
|
|
1094
|
+
isSelectElement(element)) {
|
|
1095
|
+
return !element.hasAttribute("readonly") && !isDisabled(element);
|
|
1096
|
+
}
|
|
1097
|
+
if (isHtmlElementLike(element) && element.isContentEditable) {
|
|
1098
|
+
return !isDisabled(element);
|
|
1099
|
+
}
|
|
1100
|
+
const ariaReadonlyRoles = new Set([
|
|
1101
|
+
"checkbox",
|
|
1102
|
+
"combobox",
|
|
1103
|
+
"grid",
|
|
1104
|
+
"gridcell",
|
|
1105
|
+
"listbox",
|
|
1106
|
+
"radiogroup",
|
|
1107
|
+
"slider",
|
|
1108
|
+
"spinbutton",
|
|
1109
|
+
"textbox",
|
|
1110
|
+
"columnheader",
|
|
1111
|
+
"rowheader",
|
|
1112
|
+
"searchbox",
|
|
1113
|
+
"switch",
|
|
1114
|
+
"treegrid"
|
|
1115
|
+
]);
|
|
1116
|
+
if (ariaReadonlyRoles.has(element.getAttribute("role") ?? "")) {
|
|
1117
|
+
return !isDisabled(element) && element.getAttribute("aria-readonly") !== "true";
|
|
1118
|
+
}
|
|
1119
|
+
throw new Error("Element is not an <input>, <textarea>, <select> or [contenteditable] and does not have a role allowing [aria-readonly]");
|
|
1120
|
+
};
|
|
1121
|
+
const isEnabled = (element) => !isDisabled(element);
|
|
1122
|
+
const fillActionabilityError = (element) => {
|
|
1123
|
+
if (!payload.force && !isVisible(element)) {
|
|
1124
|
+
return "Element is not visible.";
|
|
1125
|
+
}
|
|
1126
|
+
if (!payload.force && !isEnabled(element)) {
|
|
1127
|
+
return "Element is not enabled.";
|
|
1128
|
+
}
|
|
1129
|
+
if (!payload.force && !isEditable(element)) {
|
|
1130
|
+
return "Element is not editable.";
|
|
1131
|
+
}
|
|
1132
|
+
return null;
|
|
1133
|
+
};
|
|
1134
|
+
const waitForFillActionability = (element) => {
|
|
1135
|
+
const assertActionable = () => {
|
|
1136
|
+
const error = fillActionabilityError(element);
|
|
1137
|
+
if (error) {
|
|
1138
|
+
throw new Error(error);
|
|
1139
|
+
}
|
|
1140
|
+
};
|
|
1141
|
+
if (payload.force || !payload.timeoutMs || payload.timeoutMs <= 0) {
|
|
1142
|
+
assertActionable();
|
|
1143
|
+
return;
|
|
1144
|
+
}
|
|
1145
|
+
return new Promise((resolve, reject) => {
|
|
1146
|
+
const deadline = Date.now() + payload.timeoutMs;
|
|
1147
|
+
const tick = () => {
|
|
1148
|
+
try {
|
|
1149
|
+
assertActionable();
|
|
1150
|
+
resolve();
|
|
1151
|
+
}
|
|
1152
|
+
catch (error) {
|
|
1153
|
+
if (Date.now() + 50 > deadline) {
|
|
1154
|
+
reject(error);
|
|
1155
|
+
return;
|
|
1156
|
+
}
|
|
1157
|
+
setTimeout(tick, 50);
|
|
1158
|
+
}
|
|
1159
|
+
};
|
|
1160
|
+
tick();
|
|
1161
|
+
});
|
|
1162
|
+
};
|
|
1163
|
+
const isChecked = (element) => {
|
|
1164
|
+
if (isInputElement(element)) {
|
|
1165
|
+
return element.checked;
|
|
1166
|
+
}
|
|
1167
|
+
const ariaChecked = element.getAttribute("aria-checked");
|
|
1168
|
+
return ariaChecked === "true" || ariaChecked === "mixed";
|
|
1169
|
+
};
|
|
1170
|
+
const checkboxLikeRoles = new Set([
|
|
1171
|
+
"checkbox",
|
|
1172
|
+
"menuitemcheckbox",
|
|
1173
|
+
"menuitemradio",
|
|
1174
|
+
"option",
|
|
1175
|
+
"radio",
|
|
1176
|
+
"switch",
|
|
1177
|
+
"treeitem"
|
|
1178
|
+
]);
|
|
1179
|
+
const checkedState = (element) => {
|
|
1180
|
+
if (isInputElement(element)) {
|
|
1181
|
+
const type = element.type.toLowerCase();
|
|
1182
|
+
if (type !== "checkbox" && type !== "radio") {
|
|
1183
|
+
throw new Error("Not a checkbox or radio button");
|
|
1184
|
+
}
|
|
1185
|
+
return element.checked;
|
|
1186
|
+
}
|
|
1187
|
+
if (!checkboxLikeRoles.has(element.getAttribute("role") ?? "")) {
|
|
1188
|
+
throw new Error("Not a checkbox or radio button");
|
|
1189
|
+
}
|
|
1190
|
+
return isChecked(element);
|
|
1191
|
+
};
|
|
1192
|
+
const checkedStateDetails = (element) => {
|
|
1193
|
+
if (isInputElement(element)) {
|
|
1194
|
+
const type = element.type.toLowerCase();
|
|
1195
|
+
if (type !== "checkbox" && type !== "radio") {
|
|
1196
|
+
throw new Error("Not a checkbox or radio button");
|
|
1197
|
+
}
|
|
1198
|
+
return {
|
|
1199
|
+
matches: element.checked,
|
|
1200
|
+
isRadio: type === "radio"
|
|
1201
|
+
};
|
|
1202
|
+
}
|
|
1203
|
+
const role = (element.getAttribute("role") ?? "").toLowerCase();
|
|
1204
|
+
if (!checkboxLikeRoles.has(role)) {
|
|
1205
|
+
throw new Error("Not a checkbox or radio button");
|
|
1206
|
+
}
|
|
1207
|
+
return {
|
|
1208
|
+
matches: isChecked(element),
|
|
1209
|
+
isRadio: false
|
|
1210
|
+
};
|
|
1211
|
+
};
|
|
1212
|
+
const inputValue = (element) => {
|
|
1213
|
+
if (isInputElement(element) ||
|
|
1214
|
+
isTextAreaElement(element) ||
|
|
1215
|
+
isSelectElement(element)) {
|
|
1216
|
+
return element.value;
|
|
1217
|
+
}
|
|
1218
|
+
throw new Error("Node is not an <input>, <textarea> or <select> element.");
|
|
1219
|
+
};
|
|
1220
|
+
const fillInputValue = (input, value) => {
|
|
1221
|
+
const type = input.type.toLowerCase();
|
|
1222
|
+
const inputTypesToSetValue = new Set(["color", "date", "time", "datetime-local", "month", "range", "week"]);
|
|
1223
|
+
const inputTypesToTypeInto = new Set(["", "email", "number", "password", "search", "tel", "text", "url"]);
|
|
1224
|
+
if (!inputTypesToTypeInto.has(type) && !inputTypesToSetValue.has(type)) {
|
|
1225
|
+
throw new Error(`Input of type "${type}" cannot be filled`);
|
|
1226
|
+
}
|
|
1227
|
+
if (type === "number") {
|
|
1228
|
+
value = value.trim();
|
|
1229
|
+
if (isNaN(Number(value))) {
|
|
1230
|
+
throw new Error("Cannot type text into input[type=number]");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (type === "color") {
|
|
1234
|
+
value = value.toLowerCase();
|
|
1235
|
+
}
|
|
1236
|
+
if (inputTypesToSetValue.has(type)) {
|
|
1237
|
+
value = value.trim();
|
|
1238
|
+
input.value = value;
|
|
1239
|
+
if (input.value !== value) {
|
|
1240
|
+
throw new Error("Malformed value");
|
|
1241
|
+
}
|
|
1242
|
+
}
|
|
1243
|
+
return value;
|
|
1244
|
+
};
|
|
1245
|
+
const innerTextValue = (element) => {
|
|
1246
|
+
if (isHtmlElementLike(element)) {
|
|
1247
|
+
return element.innerText;
|
|
1248
|
+
}
|
|
1249
|
+
throw new Error("Node is not an HTMLElement.");
|
|
1250
|
+
};
|
|
1251
|
+
const innerHTMLValue = (element) => {
|
|
1252
|
+
if (isElementNode(element) && "innerHTML" in element) {
|
|
1253
|
+
return element.innerHTML;
|
|
1254
|
+
}
|
|
1255
|
+
throw new Error("Node does not expose innerHTML.");
|
|
1256
|
+
};
|
|
1257
|
+
const createDOMEvent = (type, eventInit) => {
|
|
1258
|
+
const baseInit = {
|
|
1259
|
+
bubbles: true,
|
|
1260
|
+
cancelable: true,
|
|
1261
|
+
composed: true,
|
|
1262
|
+
...(eventInit && typeof eventInit === "object" ? eventInit : {})
|
|
1263
|
+
};
|
|
1264
|
+
if (type.startsWith("mouse") || type === "click" || type === "dblclick" || type === "contextmenu") {
|
|
1265
|
+
return new MouseEvent(type, baseInit);
|
|
1266
|
+
}
|
|
1267
|
+
if (type === "wheel") {
|
|
1268
|
+
return new WheelEvent(type, baseInit);
|
|
1269
|
+
}
|
|
1270
|
+
if (type.startsWith("drag") || type === "drop") {
|
|
1271
|
+
return new DragEvent(type, baseInit);
|
|
1272
|
+
}
|
|
1273
|
+
if (type.startsWith("key")) {
|
|
1274
|
+
return new KeyboardEvent(type, baseInit);
|
|
1275
|
+
}
|
|
1276
|
+
if (type === "input") {
|
|
1277
|
+
return new InputEvent(type, baseInit);
|
|
1278
|
+
}
|
|
1279
|
+
return new Event(type, baseInit);
|
|
1280
|
+
};
|
|
1281
|
+
const formatElementForStrictViolation = (element) => {
|
|
1282
|
+
const tag = element.tagName.toLowerCase();
|
|
1283
|
+
const id = "id" in element && typeof element.id === "string" && element.id ? `#${element.id}` : "";
|
|
1284
|
+
const className = "className" in element &&
|
|
1285
|
+
typeof element.className === "string" &&
|
|
1286
|
+
element.className.trim()
|
|
1287
|
+
? `.${element.className.trim().replace(/\s+/g, ".")}`
|
|
1288
|
+
: "";
|
|
1289
|
+
const text = normalize((isHtmlElementLike(element)
|
|
1290
|
+
? element.innerText || element.textContent || ""
|
|
1291
|
+
: element.textContent || ""));
|
|
1292
|
+
return `${tag}${id}${className}${text ? ` (${text})` : ""}`;
|
|
1293
|
+
};
|
|
1294
|
+
const nodeToActionElement = (node) => {
|
|
1295
|
+
if (isElementNode(node)) {
|
|
1296
|
+
return node;
|
|
1297
|
+
}
|
|
1298
|
+
return isTextNode(node) && isElementNode(node.parentElement) ? node.parentElement : null;
|
|
1299
|
+
};
|
|
1300
|
+
const parentElementOrShadowHost = (element) => {
|
|
1301
|
+
if (element.parentElement) {
|
|
1302
|
+
return element.parentElement;
|
|
1303
|
+
}
|
|
1304
|
+
if (!element.parentNode) {
|
|
1305
|
+
return undefined;
|
|
1306
|
+
}
|
|
1307
|
+
if (element.parentNode.nodeType === 11 && element.parentNode.host) {
|
|
1308
|
+
return element.parentNode.host;
|
|
1309
|
+
}
|
|
1310
|
+
return undefined;
|
|
1311
|
+
};
|
|
1312
|
+
const isInsideScope = (scope, element) => {
|
|
1313
|
+
while (element) {
|
|
1314
|
+
if (element === scope) {
|
|
1315
|
+
return true;
|
|
1316
|
+
}
|
|
1317
|
+
element = parentElementOrShadowHost(element) ?? null;
|
|
1318
|
+
}
|
|
1319
|
+
return false;
|
|
1320
|
+
};
|
|
1321
|
+
const resolveSingleElementFrom = (elements) => {
|
|
1322
|
+
if (!payload.reference.pick && elements.length > 1) {
|
|
1323
|
+
const preview = elements
|
|
1324
|
+
.slice(0, 3)
|
|
1325
|
+
.map((element) => formatElementForStrictViolation(element))
|
|
1326
|
+
.join(", ");
|
|
1327
|
+
throw new Error(`strict mode violation: locator resolved to ${elements.length} elements${preview ? `: ${preview}` : ""}`);
|
|
1328
|
+
}
|
|
1329
|
+
return elements[0] ?? null;
|
|
1330
|
+
};
|
|
1331
|
+
const resolveSingleElement = () => resolveSingleElementFrom(resolveReference(payload.reference).filter((node) => isElementNode(node)));
|
|
1332
|
+
const resolveSingleNode = () => resolveReference(payload.reference)[0] ?? null;
|
|
1333
|
+
const retargetElement = (node, behavior) => {
|
|
1334
|
+
let element = node ? nodeToActionElement(node) : null;
|
|
1335
|
+
if (!element) {
|
|
1336
|
+
return null;
|
|
1337
|
+
}
|
|
1338
|
+
if (behavior === "none") {
|
|
1339
|
+
return element;
|
|
1340
|
+
}
|
|
1341
|
+
if (!element.matches("input, textarea, select") && !(isHtmlElementLike(element) && element.isContentEditable)) {
|
|
1342
|
+
if (behavior === "button-link") {
|
|
1343
|
+
element = element.closest("button, [role=button], a, [role=link]") ?? element;
|
|
1344
|
+
}
|
|
1345
|
+
else {
|
|
1346
|
+
element = element.closest("button, [role=button], [role=checkbox], [role=radio]") ?? element;
|
|
1347
|
+
}
|
|
1348
|
+
}
|
|
1349
|
+
if (behavior === "follow-label") {
|
|
1350
|
+
if (!element.matches("a, input, textarea, button, select, [role=link], [role=button], [role=checkbox], [role=radio]") &&
|
|
1351
|
+
!(isHtmlElementLike(element) && element.isContentEditable)) {
|
|
1352
|
+
const enclosingLabel = element.closest("label");
|
|
1353
|
+
if (enclosingLabel && tagNameOf(enclosingLabel) === "label" && "control" in enclosingLabel && enclosingLabel.control) {
|
|
1354
|
+
element = enclosingLabel.control;
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
}
|
|
1358
|
+
return element;
|
|
1359
|
+
};
|
|
1360
|
+
const resolveRetargetedElement = (behavior) => retargetElement(resolveSingleNode(), behavior);
|
|
1361
|
+
const callUserFunction = (subject) => {
|
|
1362
|
+
const expression = payload.expression ?? "undefined";
|
|
1363
|
+
let result = payload.isFunction === true
|
|
1364
|
+
? (0, eval)(`(${expression})`)
|
|
1365
|
+
: (0, eval)(expression);
|
|
1366
|
+
if (payload.isFunction === true) {
|
|
1367
|
+
result = result(subject, reviveArgument(payload.arg));
|
|
1368
|
+
}
|
|
1369
|
+
else if (payload.isFunction === undefined && typeof result === "function") {
|
|
1370
|
+
result = result(subject, reviveArgument(payload.arg));
|
|
1371
|
+
}
|
|
1372
|
+
return result;
|
|
1373
|
+
};
|
|
1374
|
+
const resolveActionPointOnce = () => {
|
|
1375
|
+
const firstNode = resolveSingleNode();
|
|
1376
|
+
if (!firstNode) {
|
|
1377
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1378
|
+
}
|
|
1379
|
+
if (!firstNode.isConnected) {
|
|
1380
|
+
throw new Error("Element is not attached to the DOM");
|
|
1381
|
+
}
|
|
1382
|
+
const firstElement = payload.retargetForAction === "follow-label"
|
|
1383
|
+
? retargetElement(firstNode, "follow-label")
|
|
1384
|
+
: nodeToActionElement(firstNode);
|
|
1385
|
+
if (!firstElement) {
|
|
1386
|
+
throw new Error("Element is not attached to the DOM");
|
|
1387
|
+
}
|
|
1388
|
+
firstElement.scrollIntoView({
|
|
1389
|
+
block: "center",
|
|
1390
|
+
inline: "center",
|
|
1391
|
+
behavior: "instant"
|
|
1392
|
+
});
|
|
1393
|
+
if (!hasVisibleStyle(firstElement)) {
|
|
1394
|
+
throw new Error("Element is not visible.");
|
|
1395
|
+
}
|
|
1396
|
+
if (payload.waitForEnabled && !isEnabled(firstElement)) {
|
|
1397
|
+
throw new Error("Element is not enabled.");
|
|
1398
|
+
}
|
|
1399
|
+
const viewportRect = {
|
|
1400
|
+
bottom: window.innerHeight,
|
|
1401
|
+
left: 0,
|
|
1402
|
+
right: window.innerWidth,
|
|
1403
|
+
top: 0
|
|
1404
|
+
};
|
|
1405
|
+
const intersectWithViewport = (rect) => {
|
|
1406
|
+
const left = Math.max(rect.left, viewportRect.left);
|
|
1407
|
+
const right = Math.min(rect.right, viewportRect.right);
|
|
1408
|
+
const top = Math.max(rect.top, viewportRect.top);
|
|
1409
|
+
const bottom = Math.min(rect.bottom, viewportRect.bottom);
|
|
1410
|
+
if (right - left <= 0 || bottom - top <= 0) {
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
return new DOMRect(left, top, right - left, bottom - top);
|
|
1414
|
+
};
|
|
1415
|
+
const chooseActionRect = (element) => {
|
|
1416
|
+
for (const candidate of Array.from(element.getClientRects())) {
|
|
1417
|
+
const visiblePart = intersectWithViewport(candidate);
|
|
1418
|
+
if (visiblePart && visiblePart.width * visiblePart.height > 0.99) {
|
|
1419
|
+
return visiblePart;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
const visibleBoundingBox = intersectWithViewport(element.getBoundingClientRect());
|
|
1423
|
+
if (visibleBoundingBox && visibleBoundingBox.width * visibleBoundingBox.height > 0.99) {
|
|
1424
|
+
return visibleBoundingBox;
|
|
1425
|
+
}
|
|
1426
|
+
return null;
|
|
1427
|
+
};
|
|
1428
|
+
const rect = isTextNode(firstNode)
|
|
1429
|
+
? (() => {
|
|
1430
|
+
const range = document.createRange();
|
|
1431
|
+
range.selectNodeContents(firstNode);
|
|
1432
|
+
const rangeRect = (() => {
|
|
1433
|
+
for (const candidate of Array.from(range.getClientRects())) {
|
|
1434
|
+
const visiblePart = intersectWithViewport(candidate);
|
|
1435
|
+
if (visiblePart && visiblePart.width * visiblePart.height > 0.99) {
|
|
1436
|
+
return visiblePart;
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
return intersectWithViewport(range.getBoundingClientRect());
|
|
1440
|
+
})();
|
|
1441
|
+
range.detach();
|
|
1442
|
+
return rangeRect;
|
|
1443
|
+
})()
|
|
1444
|
+
: chooseActionRect(firstElement);
|
|
1445
|
+
if (!rect || rect.width <= 0 || rect.height <= 0) {
|
|
1446
|
+
throw new Error("Element is outside of the viewport.");
|
|
1447
|
+
}
|
|
1448
|
+
const offsetX = payload.position ? payload.position.x : rect.width / 2;
|
|
1449
|
+
const offsetY = payload.position ? payload.position.y : rect.height / 2;
|
|
1450
|
+
let frameOffsetX = 0;
|
|
1451
|
+
let frameOffsetY = 0;
|
|
1452
|
+
let currentWindow = firstElement.ownerDocument.defaultView;
|
|
1453
|
+
while (currentWindow && isElementNode(currentWindow.frameElement)) {
|
|
1454
|
+
const frameRect = currentWindow.frameElement.getBoundingClientRect();
|
|
1455
|
+
frameOffsetX += frameRect.left;
|
|
1456
|
+
frameOffsetY += frameRect.top;
|
|
1457
|
+
currentWindow = currentWindow.parent === currentWindow ? null : currentWindow.parent;
|
|
1458
|
+
}
|
|
1459
|
+
const x = frameOffsetX + rect.left + offsetX;
|
|
1460
|
+
const y = frameOffsetY + rect.top + offsetY;
|
|
1461
|
+
if (!payload.force) {
|
|
1462
|
+
const hitTarget = firstElement.ownerDocument.elementFromPoint(rect.left + offsetX, rect.top + offsetY);
|
|
1463
|
+
if (hitTarget && !isInsideScope(firstElement, hitTarget) && !isInsideScope(hitTarget, firstElement)) {
|
|
1464
|
+
throw new Error("Element intercepts pointer events.");
|
|
1465
|
+
}
|
|
1466
|
+
}
|
|
1467
|
+
return { x, y };
|
|
1468
|
+
};
|
|
1469
|
+
const frameOffsetForElement = (element) => {
|
|
1470
|
+
let x = 0;
|
|
1471
|
+
let y = 0;
|
|
1472
|
+
let currentWindow = element.ownerDocument.defaultView;
|
|
1473
|
+
while (currentWindow && isElementNode(currentWindow.frameElement)) {
|
|
1474
|
+
const frameRect = currentWindow.frameElement.getBoundingClientRect();
|
|
1475
|
+
x += frameRect.left;
|
|
1476
|
+
y += frameRect.top;
|
|
1477
|
+
currentWindow = currentWindow.parent === currentWindow ? null : currentWindow.parent;
|
|
1478
|
+
}
|
|
1479
|
+
return { x, y };
|
|
1480
|
+
};
|
|
1481
|
+
const shouldRetryActionPointError = (error) => {
|
|
1482
|
+
if (!(error instanceof Error)) {
|
|
1483
|
+
return false;
|
|
1484
|
+
}
|
|
1485
|
+
return (error.message === "No element found." ||
|
|
1486
|
+
error.message === "Element is not visible." ||
|
|
1487
|
+
error.message === "Element is not enabled." ||
|
|
1488
|
+
error.message === "Element does not have an actionable bounding box." ||
|
|
1489
|
+
error.message === "Element intercepts pointer events.");
|
|
1490
|
+
};
|
|
1491
|
+
const resolvedElements = resolveReference(payload.reference);
|
|
1492
|
+
const firstResolvedNode = () => resolveReference(payload.reference)[0] ?? null;
|
|
1493
|
+
switch (payload.operation) {
|
|
1494
|
+
case "count":
|
|
1495
|
+
return resolvedElements.length;
|
|
1496
|
+
case "boundingBox":
|
|
1497
|
+
{
|
|
1498
|
+
const firstElement = resolveSingleElement();
|
|
1499
|
+
if (!firstElement || !firstElement.isConnected) {
|
|
1500
|
+
return null;
|
|
1501
|
+
}
|
|
1502
|
+
const rect = firstElement.getBoundingClientRect();
|
|
1503
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
1504
|
+
return null;
|
|
1505
|
+
}
|
|
1506
|
+
const offset = frameOffsetForElement(firstElement);
|
|
1507
|
+
return {
|
|
1508
|
+
x: rect.x + offset.x,
|
|
1509
|
+
y: rect.y + offset.y,
|
|
1510
|
+
width: rect.width,
|
|
1511
|
+
height: rect.height
|
|
1512
|
+
};
|
|
1513
|
+
}
|
|
1514
|
+
case "createHandle":
|
|
1515
|
+
{
|
|
1516
|
+
resolveSingleElement();
|
|
1517
|
+
const firstNode = resolveReference(payload.reference)[0] ?? null;
|
|
1518
|
+
if (!firstNode) {
|
|
1519
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1520
|
+
}
|
|
1521
|
+
const handleId = `handle:${++globalState.__roxyNextHandleId}`;
|
|
1522
|
+
globalState.__roxyHandleStore[handleId] = firstNode;
|
|
1523
|
+
return {
|
|
1524
|
+
handleId
|
|
1525
|
+
};
|
|
1526
|
+
}
|
|
1527
|
+
case "evaluate":
|
|
1528
|
+
{
|
|
1529
|
+
const firstElement = resolveSingleElement();
|
|
1530
|
+
if (!firstElement) {
|
|
1531
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1532
|
+
}
|
|
1533
|
+
return callUserFunction(firstElement);
|
|
1534
|
+
}
|
|
1535
|
+
case "evaluateAll":
|
|
1536
|
+
return callUserFunction(resolvedElements);
|
|
1537
|
+
case "dispatchEvent":
|
|
1538
|
+
{
|
|
1539
|
+
const firstElement = resolveSingleElement();
|
|
1540
|
+
if (!firstElement) {
|
|
1541
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1542
|
+
}
|
|
1543
|
+
const eventInit = reviveArgument(payload.arg);
|
|
1544
|
+
const event = createDOMEvent(String(payload.name ?? "event"), eventInit);
|
|
1545
|
+
firstElement.dispatchEvent(event);
|
|
1546
|
+
return undefined;
|
|
1547
|
+
}
|
|
1548
|
+
case "textContent":
|
|
1549
|
+
{
|
|
1550
|
+
const firstNode = firstResolvedNode();
|
|
1551
|
+
return firstNode ? firstNode.textContent : null;
|
|
1552
|
+
}
|
|
1553
|
+
case "innerText":
|
|
1554
|
+
{
|
|
1555
|
+
const firstElement = resolveSingleElement();
|
|
1556
|
+
if (!firstElement) {
|
|
1557
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1558
|
+
}
|
|
1559
|
+
return innerTextValue(firstElement);
|
|
1560
|
+
}
|
|
1561
|
+
case "innerHTML":
|
|
1562
|
+
{
|
|
1563
|
+
const firstElement = resolveSingleElement();
|
|
1564
|
+
if (!firstElement) {
|
|
1565
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1566
|
+
}
|
|
1567
|
+
return innerHTMLValue(firstElement);
|
|
1568
|
+
}
|
|
1569
|
+
case "getAttribute":
|
|
1570
|
+
{
|
|
1571
|
+
const firstElement = resolveSingleElement();
|
|
1572
|
+
return firstElement ? firstElement.getAttribute(payload.name ?? "") : null;
|
|
1573
|
+
}
|
|
1574
|
+
case "inputValue":
|
|
1575
|
+
{
|
|
1576
|
+
const firstElement = resolveSingleElement();
|
|
1577
|
+
if (!firstElement) {
|
|
1578
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1579
|
+
}
|
|
1580
|
+
return inputValue(firstElement);
|
|
1581
|
+
}
|
|
1582
|
+
case "isChecked":
|
|
1583
|
+
{
|
|
1584
|
+
const firstElement = resolveRetargetedElement("follow-label");
|
|
1585
|
+
return firstElement ? checkedState(firstElement) : false;
|
|
1586
|
+
}
|
|
1587
|
+
case "checkedState":
|
|
1588
|
+
{
|
|
1589
|
+
const firstElement = resolveRetargetedElement("follow-label");
|
|
1590
|
+
if (!firstElement) {
|
|
1591
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1592
|
+
}
|
|
1593
|
+
return checkedState(firstElement);
|
|
1594
|
+
}
|
|
1595
|
+
case "checkedStateDetails":
|
|
1596
|
+
{
|
|
1597
|
+
const firstElement = resolveRetargetedElement("follow-label");
|
|
1598
|
+
if (!firstElement) {
|
|
1599
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1600
|
+
}
|
|
1601
|
+
return checkedStateDetails(firstElement);
|
|
1602
|
+
}
|
|
1603
|
+
case "isDisabled":
|
|
1604
|
+
{
|
|
1605
|
+
const firstElement = resolveSingleElement();
|
|
1606
|
+
return firstElement ? isDisabled(firstElement) : false;
|
|
1607
|
+
}
|
|
1608
|
+
case "isEditable":
|
|
1609
|
+
{
|
|
1610
|
+
const firstElement = resolveSingleElement();
|
|
1611
|
+
return firstElement ? isEditable(firstElement) : false;
|
|
1612
|
+
}
|
|
1613
|
+
case "isEnabled":
|
|
1614
|
+
{
|
|
1615
|
+
const firstElement = resolveSingleElement();
|
|
1616
|
+
return firstElement ? isEnabled(firstElement) : false;
|
|
1617
|
+
}
|
|
1618
|
+
case "isVisible":
|
|
1619
|
+
{
|
|
1620
|
+
const firstElement = resolveSingleElement();
|
|
1621
|
+
return firstElement ? isVisible(firstElement) : false;
|
|
1622
|
+
}
|
|
1623
|
+
case "focus":
|
|
1624
|
+
{
|
|
1625
|
+
const firstElement = resolveSingleElement();
|
|
1626
|
+
if (!firstElement) {
|
|
1627
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1628
|
+
}
|
|
1629
|
+
focusElement(firstElement);
|
|
1630
|
+
return true;
|
|
1631
|
+
}
|
|
1632
|
+
case "check":
|
|
1633
|
+
{
|
|
1634
|
+
const firstElement = resolveRetargetedElement("follow-label");
|
|
1635
|
+
if (!firstElement) {
|
|
1636
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1637
|
+
}
|
|
1638
|
+
const desired = payload.checked ?? true;
|
|
1639
|
+
if (isInputElement(firstElement) && firstElement.type.toLowerCase() === "radio" && !desired) {
|
|
1640
|
+
throw new Error("Cannot uncheck radio button");
|
|
1641
|
+
}
|
|
1642
|
+
if (checkedState(firstElement) === desired) {
|
|
1643
|
+
return true;
|
|
1644
|
+
}
|
|
1645
|
+
if (payload.force === false && !isVisible(firstElement)) {
|
|
1646
|
+
throw new Error("Element is not visible.");
|
|
1647
|
+
}
|
|
1648
|
+
return true;
|
|
1649
|
+
}
|
|
1650
|
+
case "domClick":
|
|
1651
|
+
{
|
|
1652
|
+
const firstElement = resolveRetargetedElement("follow-label");
|
|
1653
|
+
if (!firstElement) {
|
|
1654
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1655
|
+
}
|
|
1656
|
+
if ("click" in firstElement && typeof firstElement.click === "function") {
|
|
1657
|
+
firstElement.click();
|
|
1658
|
+
}
|
|
1659
|
+
else {
|
|
1660
|
+
firstElement.dispatchEvent(new MouseEvent("click", {
|
|
1661
|
+
bubbles: true,
|
|
1662
|
+
cancelable: true,
|
|
1663
|
+
composed: true
|
|
1664
|
+
}));
|
|
1665
|
+
}
|
|
1666
|
+
return true;
|
|
1667
|
+
}
|
|
1668
|
+
case "selectOption":
|
|
1669
|
+
{
|
|
1670
|
+
const firstElement = resolveSingleElement();
|
|
1671
|
+
if (!firstElement || !isSelectElement(firstElement)) {
|
|
1672
|
+
throw new Error("Element is not a <select> element.");
|
|
1673
|
+
}
|
|
1674
|
+
const requested = payload.values ?? [];
|
|
1675
|
+
const options = Array.from(firstElement.options);
|
|
1676
|
+
const findMatch = (candidate) => {
|
|
1677
|
+
return options.find((option, index) => {
|
|
1678
|
+
if (candidate.index !== undefined && index !== candidate.index) {
|
|
1679
|
+
return false;
|
|
1680
|
+
}
|
|
1681
|
+
if (candidate.value !== undefined && option.value !== candidate.value) {
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
if (candidate.label !== undefined && option.label !== candidate.label) {
|
|
1685
|
+
return false;
|
|
1686
|
+
}
|
|
1687
|
+
if (candidate.index === undefined && candidate.value === undefined && candidate.label === undefined) {
|
|
1688
|
+
return false;
|
|
1689
|
+
}
|
|
1690
|
+
return true;
|
|
1691
|
+
}) ?? (candidate.index === undefined && candidate.label === undefined && candidate.value !== undefined
|
|
1692
|
+
? options.find((option) => option.label === candidate.value)
|
|
1693
|
+
: undefined);
|
|
1694
|
+
};
|
|
1695
|
+
const isOptionEnabled = (option) => {
|
|
1696
|
+
let parent = option.parentElement;
|
|
1697
|
+
while (parent) {
|
|
1698
|
+
if (isElementNode(parent) && isOptGroupElement(parent) && parent.disabled) {
|
|
1699
|
+
return false;
|
|
1700
|
+
}
|
|
1701
|
+
parent = parent.parentElement;
|
|
1702
|
+
}
|
|
1703
|
+
return !option.disabled;
|
|
1704
|
+
};
|
|
1705
|
+
const matchedOptions = [];
|
|
1706
|
+
for (const candidate of requested) {
|
|
1707
|
+
const match = findMatch(candidate);
|
|
1708
|
+
if (!match) {
|
|
1709
|
+
return {
|
|
1710
|
+
__needsRetry: true,
|
|
1711
|
+
reason: "No matching option"
|
|
1712
|
+
};
|
|
1713
|
+
}
|
|
1714
|
+
if (isDisabled(firstElement) || !isOptionEnabled(match)) {
|
|
1715
|
+
throw new Error("option being selected is not enabled");
|
|
1716
|
+
}
|
|
1717
|
+
matchedOptions.push(match);
|
|
1718
|
+
if (!firstElement.multiple) {
|
|
1719
|
+
break;
|
|
1720
|
+
}
|
|
1721
|
+
}
|
|
1722
|
+
const selectedValues = [];
|
|
1723
|
+
if (firstElement.multiple || requested.length === 0) {
|
|
1724
|
+
for (const option of options) {
|
|
1725
|
+
option.selected = false;
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
for (const match of matchedOptions) {
|
|
1729
|
+
match.selected = true;
|
|
1730
|
+
selectedValues.push(match.value);
|
|
1731
|
+
if (!firstElement.multiple) {
|
|
1732
|
+
break;
|
|
1733
|
+
}
|
|
1734
|
+
}
|
|
1735
|
+
firstElement.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
1736
|
+
firstElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1737
|
+
return selectedValues;
|
|
1738
|
+
}
|
|
1739
|
+
case "fill":
|
|
1740
|
+
{
|
|
1741
|
+
const firstElement = resolveSingleElement();
|
|
1742
|
+
if (!firstElement) {
|
|
1743
|
+
throw new Error(payload.missingMessage ?? "No element found.");
|
|
1744
|
+
}
|
|
1745
|
+
const fillElement = () => {
|
|
1746
|
+
if ("focus" in firstElement && typeof firstElement.focus === "function") {
|
|
1747
|
+
firstElement.focus();
|
|
1748
|
+
}
|
|
1749
|
+
if (isInputElement(firstElement)) {
|
|
1750
|
+
firstElement.value = fillInputValue(firstElement, payload.value ?? "");
|
|
1751
|
+
}
|
|
1752
|
+
else if (isTextAreaElement(firstElement)) {
|
|
1753
|
+
firstElement.value = payload.value ?? "";
|
|
1754
|
+
}
|
|
1755
|
+
else if (isHtmlElementLike(firstElement) && firstElement.isContentEditable) {
|
|
1756
|
+
const selection = window.getSelection();
|
|
1757
|
+
const range = document.createRange();
|
|
1758
|
+
range.selectNodeContents(firstElement);
|
|
1759
|
+
selection?.removeAllRanges();
|
|
1760
|
+
selection?.addRange(range);
|
|
1761
|
+
const value = payload.value ?? "";
|
|
1762
|
+
const edited = value
|
|
1763
|
+
? document.execCommand("insertText", false, value)
|
|
1764
|
+
: document.execCommand("delete");
|
|
1765
|
+
if (!edited) {
|
|
1766
|
+
firstElement.textContent = value;
|
|
1767
|
+
}
|
|
1768
|
+
return true;
|
|
1769
|
+
}
|
|
1770
|
+
else {
|
|
1771
|
+
throw new Error("Element is not an <input>, <textarea> or [contenteditable] element");
|
|
1772
|
+
}
|
|
1773
|
+
firstElement.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
1774
|
+
firstElement.dispatchEvent(new Event("change", { bubbles: true }));
|
|
1775
|
+
return true;
|
|
1776
|
+
};
|
|
1777
|
+
if (payload.timeoutMs !== undefined) {
|
|
1778
|
+
const waitResult = waitForFillActionability(firstElement);
|
|
1779
|
+
return waitResult instanceof Promise ? waitResult.then(fillElement) : fillElement();
|
|
1780
|
+
}
|
|
1781
|
+
const error = fillActionabilityError(firstElement);
|
|
1782
|
+
if (error) {
|
|
1783
|
+
throw new Error(error);
|
|
1784
|
+
}
|
|
1785
|
+
return fillElement();
|
|
1786
|
+
}
|
|
1787
|
+
case "actionPoint":
|
|
1788
|
+
{
|
|
1789
|
+
if (payload.force || !payload.timeoutMs || payload.timeoutMs <= 0) {
|
|
1790
|
+
return resolveActionPointOnce();
|
|
1791
|
+
}
|
|
1792
|
+
return new Promise((resolve, reject) => {
|
|
1793
|
+
const deadline = Date.now() + payload.timeoutMs;
|
|
1794
|
+
const tick = () => {
|
|
1795
|
+
try {
|
|
1796
|
+
resolve(resolveActionPointOnce());
|
|
1797
|
+
}
|
|
1798
|
+
catch (error) {
|
|
1799
|
+
if (!shouldRetryActionPointError(error) || Date.now() + 50 > deadline) {
|
|
1800
|
+
reject(error);
|
|
1801
|
+
return;
|
|
1802
|
+
}
|
|
1803
|
+
setTimeout(tick, 50);
|
|
1804
|
+
}
|
|
1805
|
+
};
|
|
1806
|
+
tick();
|
|
1807
|
+
});
|
|
1808
|
+
}
|
|
1809
|
+
default:
|
|
1810
|
+
throw new Error(`Unsupported selector runtime operation: ${payload.operation}`);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
export const SELECTOR_RUNTIME_SOURCE = selectorRuntimeOperation.toString();
|
|
1814
|
+
//# sourceMappingURL=selectorRuntime.js.map
|