Skip to content

script: Add basic support for tab navigation#42952

Merged
mrobinson merged 1 commit into
servo:mainfrom
mrobinson:tab-navigation-1
Mar 2, 2026
Merged

script: Add basic support for tab navigation#42952
mrobinson merged 1 commit into
servo:mainfrom
mrobinson:tab-navigation-1

Conversation

@mrobinson
Copy link
Copy Markdown
Member

This change adds very basic support for tab navigation, but without
support for focus scopes. Followup changes will refine the behavior of
this implementation to follow the specification around focus scopes,
shadow DOM, and slots. In particular delegatesFocus is not supported
here yet.

Testing: This causes quite a few WPT tests to start passing.

@mrobinson mrobinson requested a review from gterzian as a code owner March 2, 2026 11:04
@servo-highfive servo-highfive added the S-awaiting-review There is new code that needs to be reviewed. label Mar 2, 2026
@mrobinson mrobinson added the T-linux-wpt Do a try run of the WPT label Mar 2, 2026
@github-actions github-actions Bot removed the T-linux-wpt Do a try run of the WPT label Mar 2, 2026
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

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

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

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

Flaky unexpected result (26)
  • OK [expected TIMEOUT] /IndexedDB/idbfactory_open.any.worker.html
    • PASS [expected FAIL] subtest: Calling open() with version argument 1.5 should not throw.
    • PASS [expected TIMEOUT] subtest: Calling open() with version argument 9007199254740991 should not throw.
    • PASS [expected TIMEOUT] subtest: Calling open() with version argument undefined should not throw.
  • OK /_mozilla/css/offset_properties_inline.html (#40543)
    • FAIL [expected PASS] subtest: offsetTop

      assert_equals: offsetTop of #inline-1 should be 0. expected 0 but got -1
      

    • FAIL [expected PASS] subtest: offsetLeft

      assert_equals: offsetLeft of #inline-2 should be 40. expected 40 but got 25
      

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

      assert_equals: expected 62 but got 60.35
      

  • 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 10 more unexpected results...
  • CRASH [expected OK] /_webgl/conformance2/rendering/element-index-uint.html
  • OK /css/CSS2/linebox/vertical-align-top-bottom-001.html (#41015)
    • FAIL [expected PASS] subtest: text-top+

      assert_approx_equals: expected "44" +/- 0 but got 41
      

    • FAIL [expected PASS] subtest: text-top+top

      assert_approx_equals: expected "5" +/- 0 but got 3
      

    • FAIL [expected PASS] subtest: text-top+text-top

      assert_approx_equals: expected "5" +/- 0 but got 3
      

    • FAIL [expected PASS] subtest: text-top+text-bottom

      assert_approx_equals: expected "40" +/- 0 but got 37
      

    • FAIL [expected PASS] subtest: text-bottom+

      assert_approx_equals: expected "44" +/- 0 but got 45
      

    • FAIL [expected PASS] subtest: text-bottom+top

      assert_approx_equals: expected "5" +/- 0 but got 7
      

    • FAIL [expected PASS] subtest: text-bottom+text-top

      assert_approx_equals: expected "5" +/- 0 but got 7
      

    • FAIL [expected PASS] subtest: bottom+

      assert_approx_equals: expected "49" +/- 0 but got 48
      

    • FAIL [expected PASS] subtest: bottom+text-top

      assert_approx_equals: expected "45" +/- 0 but got 43
      

    • FAIL [expected PASS] subtest: bottom+text-bottom

      assert_approx_equals: expected "45" +/- 0 but got 44
      

  • 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
  • ERROR /fetch/metadata/generated/serviceworker.https.sub.html (#36247)
    • PASS [expected FAIL] subtest: sec-fetch-site - Same origin, no options - registration
  • 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/replace-before-load/a-click.html (#28697)
    • PASS [expected FAIL] subtest: aElement.click() before the load event must NOT replace
  • OK /html/browsers/history/the-history-interface/traverse_the_history_3.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • CRASH [expected OK] /html/browsers/windows/browsing-context-window.html
  • OK /html/semantics/document-metadata/the-meta-element/pragma-directives/attr-meta-http-equiv-refresh/allow-scripts-flag-changing-2.html (#39703)
    • FAIL [expected PASS] subtest: Meta refresh of the original iframe is not blocked if moved into a sandboxed iframe

      uncaught exception: Error: assert_unreached: The iframe into which the meta was moved must not refresh Reached unreachable code
      

  • OK /html/semantics/embedded-content/media-elements/media_fragment_seek.html (#24114)
    • PASS [expected FAIL] subtest: Video should seek to time specified in media fragment syntax
  • TIMEOUT [expected OK] /html/semantics/embedded-content/the-iframe-element/iframe_sandbox_navigate_other_frame_popup.sub.html (#39702)
    • TIMEOUT [expected FAIL] subtest: Sandboxed iframe can not navigate other frame's popup

      Test timed out
      

  • 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.
  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • CRASH [expected OK] /html/webappapis/dynamic-markup-insertion/opening-the-input-stream/remove-initial-about-blankness.window.html (#28684)
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventEnd > Original domContentLoadedEventEnd
  • FAIL [expected PASS] /png/apng/fcTL-dispose-previous.html (#41561)
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • PASS [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
    • FAIL [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ a default policy throwing an exception in enforcing mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

    • FAIL [expected NOTRUN] subtest: Navigate a window via anchor with javascript:-urls w/ a default policy throwing an exception in report-only mode.

      promise_test: Unhandled rejection with value: "Unexpected message received: \"No securitypolicyviolation reported!\""
      

  • OK /webaudio/the-audio-api/the-audiobuffersourcenode-interface/sub-sample-buffer-stitching.html (#22849)
    • FAIL [expected PASS] subtest: buffer-stitching-2

      assert_approx_equals: Stitched sine‑wave buffers at sample rate 43800 sample[37848] |-3.055380717133469e-22 - -0.69260573387146| = 0.69260573387146 > 0.0038986 expected -0.69260573387146 +/- 0.0038986 but got -3.055380717133469e-22
      

  • OK [expected TIMEOUT] /webstorage/localstorage-about-blank-3P-iframe-opens-3P-window.partitioned.html (#29053)
    • PASS [expected TIMEOUT] subtest: StorageKey: test 3P about:blank window opened from a 3P iframe
  • OK [expected ERROR] /webxr/render_state_update.https.html (#27535)
  • CRASH [expected OK] /webxr/xrSession_input_events_end.https.html
  • CRASH [expected OK] /workers/constructors/Worker/use-base-url.html
Stable unexpected results that are known to be intermittent (19)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in <script> tags.

      Test timed out
      

  • 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
      

  • CRASH [expected PASS] /_mozilla/shadow-dom/move-element-with-ua-shadow-tree-crash.html (#39473)
  • OK /beacon/beacon-basic.https.window.html (#41723)
    • PASS [expected FAIL] subtest: Payload size restriction should be accumulated: type = string
  • OK /css/css-cascade/layer-cssom-order-reverse.html (#36094)
    • PASS [expected FAIL] subtest: Delete layer invalidates @font-face
  • OK /css/css-fonts/generic-family-keywords-001.html (#37467)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong)
  • OK /css/css-fonts/generic-family-keywords-002.html (#40929)
    • FAIL [expected PASS] subtest: font-family: -webkit-serif treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-sans-serif treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-cursive treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-fantasy treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-monospace treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-system-ui treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • FAIL [expected PASS] subtest: font-family: -webkit-math treated as <font-family>, not <generic-name>

      assert_equals: expected 30 but got 50
      

    • PASS [expected FAIL] subtest: font-family: -webkit-generic(fangsong) treated as <font-family>, not <generic-name>
    • PASS [expected FAIL] subtest: font-family: -webkit-generic(kai) treated as <font-family>, not <generic-name>
    • PASS [expected FAIL] subtest: font-family: -webkit-generic(khmer-mul) treated as <font-family>, not <generic-name>
    • And 12 more unexpected results...
  • OK /css/css-fonts/generic-family-keywords-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted fantasy (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted monospace (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted system-ui (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted math (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(fangsong) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(kai) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted generic(khmer-mul) (drawing text in a canvas)
  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-dest
  • TIMEOUT /fetch/metadata/generated/css-images.sub.tentative.html (#29047)
    • FAIL [expected PASS] subtest: content sec-fetch-dest - Not sent to non-trustworthy same-site destination

      assert_unreached: Reached unreachable code
      

    • FAIL [expected PASS] subtest: content sec-fetch-site - HTTPS downgrade-upgrade

      assert_unreached: Reached unreachable code
      

  • OK [expected ERROR] /fetch/metadata/window-open.https.sub.html (#40339)
  • 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/history/the-history-interface/traverse_the_history_5.html (#21383)
    • PASS [expected FAIL] subtest: Multiple history traversals, last would be aborted
  • TIMEOUT /html/interaction/focus/the-autofocus-attribute/supported-elements.html (#24145)
    • TIMEOUT [expected FAIL] subtest: Element with tabindex should support autofocus

      Test timed out
      

    • NOTRUN [expected PASS] subtest: Non-HTMLElement should not support autofocus
  • OK /html/webappapis/user-prompts/print-during-unload.html (#35944)
    • PASS [expected FAIL] subtest: print() during unload
  • OK /resource-timing/buffer-full-add-then-clear.html (#40819)
    • FAIL [expected PASS] subtest: Test that if the buffer is cleared after entries were added to the secondary buffer, those entries make it into the primary one

      assert_equals: Number of entries does not match the expected value. expected 3 but got 0
      

  • OK /resource-timing/test_resource_timing.html (#25720)
    • PASS [expected FAIL] subtest: PerformanceEntry has correct name, initiatorType, startTime, and duration (link)
  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • TIMEOUT [expected NOTRUN] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.

      Test timed out
      

Stable unexpected results (8)
  • OK /css/css-overflow/scroll-markers/column-scroll-marker-focus-001.html
    • PASS [expected FAIL] subtest: Column #1
    • PASS [expected FAIL] subtest: Column #0
  • TIMEOUT /css/selectors/focus-visible-013.html
    • FAIL [expected TIMEOUT] subtest: :focus-visible does not match after mouse click even if previous focused element was matching :focus-visible

      assert_equals: outlineColor for DIV#initial should be green expected "rgb(0, 128, 0)" but got "rgb(255, 0, 0)"
      

  • OK [expected TIMEOUT] /css/selectors/focus-visible-script-focus-012.html
    • FAIL [expected TIMEOUT] subtest: Script focus after keyboard focus does match :focus-visible

      assert_equals: outlineColor for DIV#target should be green expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
      

  • OK [expected TIMEOUT] /css/selectors/focus-visible-script-focus-013.html
    • FAIL [expected TIMEOUT] subtest: Script focus after blur after keyboard focus does match :focus-visible

      assert_equals: outlineColor for DIV#target should be green expected "rgb(0, 128, 0)" but got "rgb(0, 0, 0)"
      

  • OK [expected TIMEOUT] /html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-negative.html
    • PASS [expected TIMEOUT] subtest: The element with a negative tabindex must not be focused by press 'Tab' key
  • OK [expected TIMEOUT] /html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-positive.html
    • PASS [expected TIMEOUT] subtest: The element with a positive tabindex must be focused by press 'Tab' key
  • OK [expected TIMEOUT] /html/interaction/focus/sequential-focus-navigation-and-the-tabindex-attribute/focus-tabindex-zero.html
    • PASS [expected TIMEOUT] subtest: The element with a zero tabindex must be focused by press 'Tab' key
  • OK /shadow-dom/focus/focus-tabindex-order-shadow-negative.html
    • PASS [expected FAIL] subtest: Order when all elements in shadow tree has negative tabindex

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Mar 2, 2026

⚠️ Try run (#22574078780) failed!

winning_node_and_tab_index = Some((element, element_tab_index));
continue;
};
// If the candidate is a less than the current winner, then it replaces it as the winner.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

did you mean to write "is a lesser tab index"?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sure. It was a bit implied from the text, but I agree that your wording is clearer. I'll clean up all the comments.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've updated most of the comments to be a bit clearer and also have renamed some variables to make it clear what the candidate is.

Comment on lines +1755 to +1757
for node in root_node
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
Copy link
Copy Markdown
Member

@simonwuelker simonwuelker Mar 2, 2026

Choose a reason for hiding this comment

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

I wonder if its worth placing a (ordered) list of elements with a non-negative tabindex attribute on the document, to avoid this traversal. But we can worry about that later.

Copy link
Copy Markdown
Member Author

@mrobinson mrobinson Mar 2, 2026

Choose a reason for hiding this comment

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

We did consider this actually! It's a bit tricky because you still need to track the relative order of elements with equal tab indices in the DOM is. In the end we looked at what Gecko, WebKit, and Blink do. They seem to walk the DOM in this case. Even though it's expensive, I guess it's cheap enough as pressing tab is not a high frequency part of the code. Our plan was to do it this way and then optimize if we found an issue later.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That makes sense!

Comment on lines +1782 to +1785
let Some((_, winning_tab_index)) = winning_node_and_tab_index else {
winning_node_and_tab_index = Some((element, element_tab_index));
continue;
};
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If i understand tab navigation correctly then we will never find a better element if winning_tab_index is 1 and the focus direction is "forward", so can we return early here if that happens?

The same applies to find_first_tab_focusable_element.

Copy link
Copy Markdown
Member Author

@mrobinson mrobinson Mar 2, 2026

Choose a reason for hiding this comment

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

Hrm. In fact, I think we can generalize this. If we find any element with a tab index equal to current + 1 we can exit early. I'll make that change.

Comment thread components/script/dom/document_event_handler.rs Outdated
@mrobinson
Copy link
Copy Markdown
Member Author

@simonwuelker Thanks for the review. I've updated a new version of the change. PTAL when you have a moment.

Comment on lines +1755 to +1757
for node in root_node
.upcast::<Node>()
.traverse_preorder(ShadowIncluding::Yes)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That makes sense!

Comment on lines +1857 to +1861
FocusDirection::Forward => match winning_node_and_tab_index {
Some((_, winning_tab_index))
if compare_tab_indices(element_tab_index, winning_tab_index) !=
Ordering::Less => {},
_ => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This match statement is pretty convoluted, consider using something like

if winning_node_and_tab_index.is_none_or(|(_, winning_tab_index)| {
  compare_tab_indices(element_tab_index, winning_tab_index) == Ordering::Less 
  }) {
    // ...
}

(same for the other match arm below)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've done this. It's indeed a bit clearer.

Comment on lines +1862 to +1864
// Only promote a candidate to the current winner if it has a lesser tab
// index than the current winner or there is currently no winer.
winning_node_and_tab_index = Some((candidate_element, element_tab_index));
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

if element_tab_index is 1 at this point then we can immediately return the candidate element, because we're not going to find another element with higher precedence (= lesser tab index)

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Okay. This is done now.

@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 Mar 2, 2026
Comment on lines +1730 to +1731
// Step 8. If result is true and target is a focusable area
// that is click focusable, then Run the focusing steps at target.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Its unclear what spec algorithm this implements, please put a link either here or at the top of the function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Sorry. I'll remove these comments. They are from the event handling code that handles focus changes. I believe this part of tab navigation is not really specified.

@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 Mar 2, 2026
Copy link
Copy Markdown
Member

@simonwuelker simonwuelker left a comment

Choose a reason for hiding this comment

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

Very nice!

@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label Mar 2, 2026
@mrobinson mrobinson enabled auto-merge March 2, 2026 17:20
@mrobinson mrobinson added this pull request to the merge queue Mar 2, 2026
@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 Mar 2, 2026
@github-merge-queue github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Mar 2, 2026
@servo-highfive servo-highfive added S-tests-failed The changes caused existing tests to fail. and removed S-awaiting-merge The PR is in the process of compiling and running tests on the automated CI. labels Mar 2, 2026
This change adds very basic support for tab navigation, but without
support for focus scopes. Followup changes will refine the behavior of
this implementation to follow the specification around focus scopes,
shadow DOM, and slots. In particular `delegatesFocus` is not supported
here yet.

Signed-off-by: Martin Robinson <[email protected]>
Co-authored-by: Oriol Brufau <[email protected]>
@servo-highfive servo-highfive added S-awaiting-review There is new code that needs to be reviewed. and removed S-tests-failed The changes caused existing tests to fail. labels Mar 2, 2026
@mrobinson mrobinson enabled auto-merge March 2, 2026 20:07
@mrobinson mrobinson added this pull request to the merge queue Mar 2, 2026
@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 Mar 2, 2026
Merged via the queue into servo:main with commit 1d9d440 Mar 2, 2026
33 checks passed
@mrobinson mrobinson deleted the tab-navigation-1 branch March 2, 2026 22:26
@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 Mar 2, 2026
simonwuelker pushed a commit to simonwuelker/servo that referenced this pull request Mar 3, 2026
This change adds very basic support for tab navigation, but without
support for focus scopes. Followup changes will refine the behavior of
this implementation to follow the specification around focus scopes,
shadow DOM, and slots. In particular `delegatesFocus` is not supported
here yet.

Testing: This causes quite a few WPT tests to start passing.

Signed-off-by: Martin Robinson <[email protected]>
Co-authored-by: Oriol Brufau <[email protected]>
offline-ant pushed a commit to offline-ant/havi that referenced this pull request Jun 4, 2026
This change adds very basic support for tab navigation, but without
support for focus scopes. Followup changes will refine the behavior of
this implementation to follow the specification around focus scopes,
shadow DOM, and slots. In particular `delegatesFocus` is not supported
here yet.

Testing: This causes quite a few WPT tests to start passing.

Signed-off-by: Martin Robinson <[email protected]>
Co-authored-by: Oriol Brufau <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

S-awaiting-review There is new code that needs to be reviewed.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants