Skip to content

Conversation

@TimvdLippe
Copy link
Contributor

This implements the web-facing API's behind a flag, where we further design the embedding API in a
follow-up PR.

It passes all relevant WPT tests, since the HTML
specification leaves it up to user agents to
determine when to process these protocol handlers.

It also uses once_cell to lazily construct the
regex, which is what the CSP crate also uses for
its regexes 1.

Part of #40615

@TimvdLippe TimvdLippe requested a review from mrobinson November 13, 2025 16:53
@TimvdLippe TimvdLippe requested a review from gterzian as a code owner November 13, 2025 16:53
@TimvdLippe TimvdLippe added the T-linux-wpt Do a try run of the WPT label Nov 13, 2025
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Nov 13, 2025
@github-actions github-actions bot removed the T-linux-wpt Do a try run of the WPT label Nov 13, 2025
@github-actions
Copy link

🔨 Triggering try run (#19339219739) for Linux (WPT)

@github-actions
Copy link

Test results for linux-wpt from try job (#19339219739):

Flaky unexpected result (36)
  • OK /IndexedDB/idbfactory_open.any.html
    • FAIL [expected PASS] subtest: Calling open() with version argument 1.5 should not throw.

      assert_equals: version expected 1 but got 9007199254740991
      

  • OK /_mozilla/mozilla/getBoundingClientRect.html (#39668)
    • FAIL [expected PASS] subtest: getBoundingClientRect 1

      assert_equals: expected 62 but got 60.35
      

  • OK /_mozilla/webxr/create_session.https.html
    • FAIL [expected PASS] subtest: create_session

      can't access property "simulateDeviceConnection", navigator.xr.test is undefined
      

  • ERROR [expected TIMEOUT] /_mozilla/webxr/sessionavailable.https.html
  • CRASH [expected OK] /_webgl/conformance/context/context-hidden-alpha.html
  • CRASH [expected OK] /_webgl/conformance/ogles/GL/vec/vec_009_to_016.html
  • CRASH [expected OK] /_webgl/conformance/programs/use-program-crash-with-discard-in-fragment-shader.html
  • CRASH [expected OK] /_webgl/conformance/textures/image/tex-2d-luminance_alpha-luminance_alpha-unsigned_byte.html
  • OK /content-security-policy/frame-ancestors/frame-ancestors-path-ignored.window.html (#36468)
    • PASS [expected FAIL] subtest: A 'frame-ancestors' CSP directive with a URL that includes a path should be ignored.
  • OK /cookiestore/cookieStore_getAll_set_creation_url.https.any.html
    • FAIL [expected PASS] subtest: cookieStore.set and cookieStore.getAll use the creation url

      assert_equals: expected 1 but got 2
      

  • CRASH [expected OK] /credential-management/idlharness.https.window.html
  • FAIL [expected PASS] /css/css-backgrounds/background-size-042.html
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-sans-serif (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-rounded (drawing text in a canvas)
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-weight: '400' should prefer '400' over '450 460'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 20deg' should prefer 'oblique 0deg' over 'oblique -50deg -20deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

    • FAIL [expected PASS] subtest: Matching font-style: 'oblique 0deg' should prefer 'oblique 5deg' over 'oblique 15deg 20deg'

      assert_equals: Unexpected font on test element expected 487 but got 532
      

  • PASS [expected FAIL] /css/selectors/invalidation/any-link-attribute-removal.html (#35054)
  • OK /custom-elements/form-associated/ElementInternals-setFormValue.html (#29174)
    • PASS [expected FAIL] subtest: Single value - Non-empty name exists
  • TIMEOUT [expected CRASH] /fetch/metadata/window-open.https.sub.html (#40339)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • FAIL [expected PASS] subtest: Link with onclick navigation and href navigation

      assert_equals: expected "href" but got "click"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/initial-empty-document/iframe-nosrc.html (#34819)
    • PASS [expected FAIL] subtest: link click
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-cross-origin.sub.window.html (#29056)
    • PASS [expected FAIL] subtest: Cross-origin navigation started from unload handler must be ignored
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin-fragment.html (#20768)
    • FAIL [expected PASS] subtest: Tests that a fragment navigation in the unload handler will not block the initial navigation

      assert_equals: expected "" but got "#fragment"
      

  • OK /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/a-click.html (#28697)
    • PASS [expected FAIL] subtest: aElement.click() before the load event must NOT replace
  • OK /html/browsers/browsing-the-web/overlapping-navigations-and-traversals/cross-document-nav-cross-document-nav.html (#29181)
    • PASS [expected FAIL] subtest: cross-document navigation then cross-document navigation
  • CRASH [expected OK] /html/browsers/the-window-object/window-reuse-in-nested-browsing-contexts.tentative.html
  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-1.html (#39694)
    • PASS [expected FAIL] subtest: Meta refresh is blocked by the allow-scripts sandbox flag at its creation time, not when refresh comes due
  • OK /html/semantics/forms/form-submission-0/jsurl-form-submit.tentative.html (#36489)
    • PASS [expected FAIL] subtest: Verifies that form submissions scheduled inside javascript: urls take precedence over the javascript: url's return value.
  • OK [expected CRASH] /html/semantics/forms/the-fieldset-element/disabled-003.html (#31730, #39631)
  • TIMEOUT [expected ERROR] /html/semantics/links/links-created-by-a-and-area-elements/target_blank_implicit_noopener_base.html (#40347)
  • OK [expected ERROR] /html/user-activation/no-activation-thru-escape-key.html (#40343)
  • OK /preload/prefetch-document.html (#37210)
    • FAIL [expected PASS] subtest: different-site document prefetch with 'as=document' should not be consumed

      assert_equals: expected 2 but got 1
      

  • CRASH [expected OK] /trusted-types/eval-csp-no-tt.html
  • CRASH [expected OK] /upgrade-insecure-requests/gen/srcdoc-inherit.meta/unset/sharedworker-classic.https.html
  • CRASH [expected TIMEOUT] /wasm/webapi/invalid-code.any.worker.html
  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)
  • OK /webxr/xrSession_features_deviceSupport.https.html (#24357)
    • FAIL [expected PASS] subtest: Immersive XRSession requests with no supported device should reject

      assert_unreached: Should have rejected: undefined Reached unreachable code
      

  • ERROR [expected OK] /workers/baseurl/alpha/sharedworker-in-worker.html (#21315)
Stable unexpected results that are known to be intermittent (26)
  • OK /IndexedDB/idbcursor-continuePrimaryKey-exceptions.any.worker.html (#39277)
    • FAIL [expected PASS] subtest: IDBCursor continuePrimaryKey() on object store cursor

      assert_throws_dom: continuePrimaryKey() should throw if source is not an index function "function() {
              cursor.continuePrimaryKey(2, 2);
            }" threw object "TypeError: cursor.continuePrimaryKey is not a function" that is not a DOMException InvalidAccessError: property "code" is equal to undefined, expected 15
      

  • OK /IndexedDB/idbobjectstore_getAll.any.html (#39276)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • OK /IndexedDB/idbobjectstore_getAll.any.worker.html (#39400)
    • PASS [expected FAIL] subtest: Get all values with transaction.commit()
  • OK /IndexedDB/key-conversion-exceptions.any.html (#39305)
    • FAIL [expected PASS] subtest: IDBCursor continue() method with throwing/invalid keys

      assert_throws_exactly: key conversion with throwing getter should rethrow function "() => {
            receiver[method](key);
          }" threw object "TypeError: receiver[method] is not a function" but we expected it to throw object "getter: throwing from getter"
      

  • OK /IndexedDB/key-conversion-exceptions.any.worker.html (#39284)
    • FAIL [expected PASS] subtest: IDBCursor continue() method with throwing/invalid keys

      assert_throws_exactly: key conversion with throwing getter should rethrow function "() => {
            receiver[method](key);
          }" threw object "TypeError: receiver[method] is not a function" but we expected it to throw object "getter: throwing from getter"
      

    • FAIL [expected PASS] subtest: IDBCursor update() method with throwing/invalid keys

      assert_throws_exactly: throwing getter should rethrow during clone function "() => {
            cursor.update(value);
          }" threw object "TypeError: cursor.update is not a function" but we expected it to throw object "getter: throwing from getter"
      

  • FAIL [expected PASS] /_mozilla/mozilla/sslfail.html (#10760)
  • TIMEOUT [expected OK] /_mozilla/mozilla/window_resize_event.html (#36741)
    • TIMEOUT [expected PASS] subtest: Popup onresize event fires after resizeTo

      Test timed out
      

  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • PASS [expected FAIL] subtest: WebGL test #45
    • PASS [expected FAIL] subtest: WebGL test #47
    • PASS [expected FAIL] subtest: WebGL test #49
    • PASS [expected FAIL] subtest: WebGL test #51
    • FAIL [expected PASS] subtest: WebGL test #53

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #55

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #57

      assert_true: Texture was smaller than the expected size 2x2 expected true got false
      

    • FAIL [expected PASS] subtest: WebGL test #59

      assert_true: getError expected: INVALID_VALUE. Was NO_ERROR : when calling texSubImage2D with the same texture upload with offset 1, 1 expected true got false
      

    • PASS [expected FAIL] subtest: WebGL test #61
    • PASS [expected FAIL] subtest: WebGL test #63
    • And 14 more unexpected results...
  • OK /cookies/value/value-ctl.html (#40338)
    • PASS [expected FAIL] subtest: Cookie with %x0 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x1 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x2 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x3 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x4 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x5 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x6 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x7 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %x8 in value is rejected (DOM).
    • PASS [expected FAIL] subtest: Cookie with %xa in value is rejected (DOM).
    • And 22 more unexpected results...
  • OK /css/css-cascade/layer-font-face-override.html (#35935)
    • FAIL [expected PASS] subtest: @font-face override update with appended sheet 1

      assert_equals: expected "80px" but got "38.3166666666667px"
      

    • FAIL [expected PASS] subtest: @font-face override update with appended sheet 2

      assert_equals: expected "80px" but got "38.3166666666667px"
      

  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(nastaliq)
  • OK /fetch/api/response/response-stream-with-broken-then.any.html (#35419)
    • PASS [expected FAIL] subtest: Attempt to inject {done: false, value: bye} via Object.prototype.then.
    • PASS [expected FAIL] subtest: Attempt to inject value: undefined via Object.prototype.then.
    • PASS [expected FAIL] subtest: Attempt to inject undefined via Object.prototype.then.
    • PASS [expected FAIL] subtest: Attempt to inject 8.2 via Object.prototype.then.
    • PASS [expected FAIL] subtest: intercepting arraybuffer to text conversion via Object.prototype.then should not be possible
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-dest
    • PASS [expected FAIL] subtest: sec-fetch-user
  • OK /fetch/metadata/generated/css-font-face.sub.tentative.html (#34624)
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-site destination
  • OK /fetch/metadata/generated/element-img-environment-change.https.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Origin -> Same-Site -> Same-Origin redirect, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-site - Cross-Site -> Same-Site, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Origin -> Same Origin, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Site -> Same Origin, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-site - Same-Site -> Cross-Site, no attributes
    • PASS [expected FAIL] subtest: sec-fetch-mode - attributes: crossorigin=anonymous
    • PASS [expected FAIL] subtest: sec-fetch-dest - no attributes
  • OK /fetch/metadata/generated/element-img-environment-change.sub.html (#30111)
    • PASS [expected FAIL] subtest: sec-fetch-site - Not sent to non-trustworthy cross-site destination, no attributes
    • FAIL [expected PASS] subtest: sec-fetch-dest - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy same-origin destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

    • FAIL [expected PASS] subtest: sec-fetch-site - HTTPS downgrade-upgrade, no attributes

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • ERROR /fetch/metadata/generated/serviceworker.https.sub.html (#36247)
    • FAIL [expected PASS] subtest: sec-fetch-site - Same origin, no options - registration

      promise_test: Unhandled rejection with value: object "Error: Failed to query for recorded headers."
      

  • OK [expected ERROR] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • OK /html/browsers/history/the-history-interface/traverse_the_history_3.html (#21383)
    • FAIL [expected PASS] subtest: Multiple history traversals, last would be aborted

      assert_array_equals: Pages opened during history navigation expected property 1 to be 3 but got 2 (expected array [6, 3] got [6, 2])
      

  • OK /html/browsers/windows/embedded-opener-remove-frame.html (#23867)
    • FAIL [expected PASS] subtest: opener of discarded auxiliary browsing context

      assert_object_equals: property "get" expected function "function opener() {
          [native code]
      }" got function "function opener() {
          [native code]
      }"
      

  • OK /html/semantics/forms/form-submission-0/multipart-formdata.window.html (#28725)
    • PASS [expected FAIL] subtest: multipart/form-data: 0x00 in value (formdata event)
  • OK /html/semantics/forms/form-submission-0/text-plain.window.html (#28687)
    • FAIL [expected PASS] subtest: text/plain: Basic test (formdata event)

      assert_equals: expected "basic=test\r\n" but got ""
      

    • PASS [expected FAIL] subtest: text/plain: Basic File test (normal form)
    • PASS [expected FAIL] subtest: text/plain: 0x00 in value (normal form)
    • PASS [expected FAIL] subtest: text/plain: 0x00 in filename (formdata event)
  • OK /html/semantics/forms/form-submission-0/urlencoded2.window.html (#28687)
    • PASS [expected FAIL] subtest: application/x-www-form-urlencoded: Basic File test (normal form)
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • PASS [expected FAIL] subtest: Reload domComplete > Original domComplete
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventStart > Original domContentLoadedEventStart
    • PASS [expected FAIL] subtest: Reload fetchStart > Original fetchStart
    • PASS [expected FAIL] subtest: Reload loadEventEnd > Original loadEventEnd
    • PASS [expected FAIL] subtest: Reload loadEventStart > Original loadEventStart
  • OK /trusted-types/trusted-types-navigation.html?26-30 (#38807)
    • PASS [expected FAIL] subtest: Navigate a window via form-submission with javascript:-urls in report-only mode.
    • PASS [expected FAIL] subtest: Navigate a frame via form-submission with javascript:-urls in enforcing mode.
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • PASS [expected FAIL] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.
Stable unexpected results (2)
  • OK /html/dom/idlharness.https.html?exclude=(Document|Window|HTML.+)
    • PASS [expected FAIL] subtest: Navigator interface: operation registerProtocolHandler(DOMString, USVString)
    • PASS [expected FAIL] subtest: Navigator interface: operation unregisterProtocolHandler(DOMString, USVString)
    • PASS [expected FAIL] subtest: Navigator interface: window.navigator must inherit property "registerProtocolHandler(DOMString, USVString)" with the proper type
    • PASS [expected FAIL] subtest: Navigator interface: calling registerProtocolHandler(DOMString, USVString) on window.navigator with too few arguments must throw TypeError
    • PASS [expected FAIL] subtest: Navigator interface: window.navigator must inherit property "unregisterProtocolHandler(DOMString, USVString)" with the proper type
    • PASS [expected FAIL] subtest: Navigator interface: calling unregisterProtocolHandler(DOMString, USVString) on window.navigator with too few arguments must throw TypeError
  • OK /html/dom/usvstring-reflection.https.html
    • PASS [expected FAIL] subtest: RegisterProtocolHandler URL: unpaired surrogate codepoint should not make any exceptions.
    • PASS [expected FAIL] subtest: UnregisterProtocolHandler URL: unpaired surrogate codepoint should not make any exceptions.

@github-actions
Copy link

⚠️ Try run (#19339219739) failed.

@TimvdLippe TimvdLippe force-pushed the implement-protocol-handlers branch 2 times, most recently from 11239ab to 2af3cc8 Compare November 13, 2025 18:32
@servo-highfive servo-highfive added S-needs-code-changes Changes have not yet been made that were requested by a reviewer. and removed S-awaiting-review There is new code that needs to be reviewed. labels Nov 14, 2025
@TimvdLippe TimvdLippe force-pushed the implement-protocol-handlers branch from 2af3cc8 to 4e017f7 Compare November 14, 2025 07:51
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-needs-code-changes Changes have not yet been made that were requested by a reviewer. labels Nov 14, 2025
@TimvdLippe TimvdLippe requested a review from yezhizhen November 14, 2025 07:52
This implements the web-facing API's behind a flag,
where we further design the embedding API in a
follow-up PR.

It passes all relevant WPT tests, since the HTML
specification leaves it up to user agents to
determine when to process these protocol handlers.

It also uses `once_cell` to lazily construct the
regex, which is what the CSP crate also uses for
its regexes [1].

Part of servo#40615

[1]: https://github.com/rust-ammonia/rust-content-security-policy/blob/db8f2e97fed57a6f903ee4f5e8109e5ed4c10141/src/lib.rs#L1550-L1569

Signed-off-by: Tim van der Lippe <tvanderlippe@gmail.com>
@TimvdLippe TimvdLippe force-pushed the implement-protocol-handlers branch from 4e017f7 to 9a74aff Compare November 14, 2025 12:20
@TimvdLippe TimvdLippe requested a review from yezhizhen November 14, 2025 12:20
@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Nov 14, 2025
@TimvdLippe TimvdLippe added this pull request to the merge queue Nov 14, 2025
@servo-highfive servo-highfive added the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Nov 14, 2025
Merged via the queue into servo:main with commit c782da7 Nov 14, 2025
32 checks passed
@TimvdLippe TimvdLippe deleted the implement-protocol-handlers branch November 14, 2025 13:40
@servo-highfive servo-highfive removed the S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. label Nov 14, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants