Thanks to visit codestin.com
Credit goes to github.com

Skip to content

script: Introduce Element::get_attribute_string_value#45023

Merged
TimvdLippe merged 1 commit into
servo:mainfrom
TimvdLippe:introduce-get-string-attribute-value
May 20, 2026
Merged

script: Introduce Element::get_attribute_string_value#45023
TimvdLippe merged 1 commit into
servo:mainfrom
TimvdLippe:introduce-get-string-attribute-value

Conversation

@TimvdLippe
Copy link
Copy Markdown
Contributor

Not all accesses to attributes require a cx, since most of them don't actually need the Attr node, but only the value contained within. Therefore, introduce a variant that directly accesses the value, to avoid materializing attribute nodes.

Doing so removes the need for one of the temp_cx in XPath, since it only requires the attribute value and not the full node.

Part of #45022

Testing: WPT

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

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

Copy link
Copy Markdown
Member

@mrobinson mrobinson left a comment

Choose a reason for hiding this comment

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

This is really interesting. I would say it likely makes sense to generalize this a bit to allow accessing AttrValue without having to do the conversion to Attr.

Comment on lines +2043 to +2053
pub(crate) fn get_attribute_string_value_with_namespace(
&self,
namespace: &Namespace,
local_name: &LocalName,
) -> Option<String> {
self.attrs
.borrow()
.iter()
.find(|a| a.local_name() == local_name && a.namespace() == namespace)
.map(|a| String::from(&**a.value()))
}
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.

Would it be possible to return Option<&str>` to avoid an allocation.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I wanted to do that, but I am not sure how to propagate the lifetime of it. Its lifetime relates to the attribute, not the self. Do you have any suggestions on how to do that?

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 is probably okay for now. Maybe I can take a look at this 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.

You likely need Ref::map to return the ref from self.attrs.borrow() to the caller.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I made some attempts, but got stuck since the Ref is inside an AttributesBorrow. Let's merge this PR and optimize this further in subsequent cleanups.

Comment thread components/script/dom/element/element.rs Outdated
@github-actions
Copy link
Copy Markdown

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

Flaky unexpected result (71)
  • TIMEOUT /FileAPI/url/url-in-tags-revoke.window.html (#19978)
    • TIMEOUT [expected PASS] subtest: Fetching a blob URL immediately before revoking it works in &lt;script&gt; tags.

      Test timed out
      

  • OK /IndexedDB/idbdatabase_deleteObjectStore.any.html (#43823)
    • PASS [expected FAIL] subtest: Deleted object store's name should be removed from database's list. Attempting to use a deleted IDBObjectStore should throw an InvalidStateError
  • TIMEOUT [expected OK] /_mozilla/mozilla/focus-scroll-when-visible.html
  • CRASH [expected OK] /_webgl/conformance/textures/image_bitmap_from_canvas/tex-2d-rgba-rgba-unsigned_byte.html
  • OK /_webgl/conformance/textures/misc/texture-upload-size.html (#21770)
    • FAIL [expected PASS] subtest: WebGL test #45

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

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

      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 #49

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

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

      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 #53
    • PASS [expected FAIL] subtest: WebGL test #55
    • PASS [expected FAIL] subtest: WebGL test #57
    • PASS [expected FAIL] subtest: WebGL test #59
    • FAIL [expected PASS] subtest: WebGL test #61

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

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

      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
      

    • And 6 more unexpected results...
  • CRASH [expected OK] /content-security-policy/meta/sandbox-iframe.html (#43478)
  • FAIL [expected PASS] /css/css-backgrounds/border-image-repeat-space-9.html
  • 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-003.html (#38994)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted sans-serif (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted cursive (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 generic(fangsong) (drawing text in a canvas)
    • PASS [expected FAIL] subtest: @font-face matching for quoted and unquoted ui-serif (drawing text in a canvas)
  • OK /css/css-fonts/variations/at-font-face-font-matching.html (#20684)
    • FAIL [expected PASS] subtest: Matching font-weight: '501' should prefer '502 510' over '503 520'

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

  • FAIL [expected PASS] /css/css-ui/compute-kind-widget-generated/grouped-kind-of-widget-fallback-border-image-source-001.html
  • FAIL [expected PASS] /css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-radio-input-border-end-end-radius-001.html
  • FAIL [expected PASS] /css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-radio-input-border-inline-start-style-001.html
  • FAIL [expected PASS] /css/css-ui/compute-kind-widget-generated/kind-of-widget-fallback-radio-input-border-right-color-001.html
  • FAIL [expected PASS] /css/filter-effects/kernel-unit-length-001.html
  • OK /fetch/content-length/api-and-duplicate-headers.any.html (#35873)
    • FAIL [expected PASS] subtest: fetch() and duplicate Content-Length/Content-Type headers

      promise_test: Unhandled rejection with value: object "TypeError: Network error: HTTP failure: client error (SendRequest)"
      

  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/accumulated-oversized-payload.https.window.html (#41705)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/multiple-iframes.https.window.html (#35176)
  • OK [expected ERROR] /fetch/fetch-later/quota/same-origin-iframe/sandboxed-iframe.https.window.html (#41704)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/005.html (#27062)
    • PASS [expected FAIL] subtest: Link with onclick navigation and href navigation
  • OK [expected TIMEOUT] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-requestsubmit.html (#44098)
    • FAIL [expected TIMEOUT] subtest: Replace before load, triggered by formElement.requestSubmit()

      assert_equals: expected "http://web-platform.test:8000/common/blank.html?thereplacement=" but got "http://web-platform.test:8000/html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/resources/code-injector.html?pipe=sub(none)&amp;code=%0A%20%20%20%20const%20form%20%3D%20document.createElement(%22form%22)%3B%0A%20%20%20%20form.action%20%3D%20%22%2Fcommon%2Fblank.html%22%3B%0A%0A%20%20%20%20const%20input%20%3D%20document.createElement(%22input%22)%3B%0A%20%20%20%20input.type%20%3D%20%22hidden%22%3B%0A%20%20%20%20input.name%20%3D%20%22thereplacement%22%3B%0A%20%20%20%20form.append(input)%3B%0A%0A%20%20%20%20document.currentScript.before(form)%3B%0A%20%20%20%20form.requestSubmit()%3B%0A%20%20"
      

  • OK [expected TIMEOUT] /html/browsers/browsing-the-web/navigating-across-documents/replace-before-load/form-submit.html (#44028)
  • CRASH [expected TIMEOUT] /html/browsers/browsing-the-web/overlapping-navigations-and-traversals/anchor-fragment-history-back-on-click.html
  • CRASH [expected OK] /html/browsers/windows/browsing-context-names/choose-_parent-004.html
  • CRASH [expected OK] /html/canvas/element/color-type/2d.color.type.u8p3.to.u8srgb.to.u8p3.html
  • OK [expected ERROR] /html/canvas/offscreen/text/2d.text.measure.getActualBoundingBox.tentative.html (#43710)
  • 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/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/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 /html/semantics/scripting-1/the-script-element/execution-timing/077.html (#22139)
    • FAIL [expected PASS] subtest: adding several types of scripts through the DOM and removing some of them confuses scheduler

      assert_array_equals: expected property 1 to be "Script #1 ran" but got "Script #3 ran" (expected array ["Script #2 ran", "Script #1 ran", "Script #3 ran", "Script #4 ran"] got ["Script #2 ran", "Script #3 ran", "Script #4 ran", "Script #1 ran"])
      

  • OK /html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.html (#33948)
    • FAIL [expected PASS] subtest: Revoking a blob URL immediately after calling import will not fail

      promise_test: Unhandled rejection with value: object "TypeError: Module fetching failed"
      

  • OK /html/semantics/scripting-1/the-script-element/module/dynamic-import/blob-url.any.worker.html (#33909)
    • FAIL [expected PASS] subtest: Revoking a blob URL immediately after calling import will not fail

      promise_test: Unhandled rejection with value: object "TypeError: Module fetching failed"
      

  • TIMEOUT [expected OK] /html/user-activation/navigation-state-reset-sameorigin.html
    • TIMEOUT [expected PASS] subtest: Post-navigation state reset.

      Test timed out
      

  • TIMEOUT [expected OK] /infrastructure/testdriver/click_nested.html (#43887)
    • NOTRUN [expected FAIL] subtest: TestDriver click method with multiple windows and nested iframe
  • OK /navigation-timing/test-navigation-type-reload.html (#33334)
    • PASS [expected FAIL] subtest: Reload domComplete &gt; Original domComplete
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventEnd &gt; Original domContentLoadedEventEnd
    • PASS [expected FAIL] subtest: Reload domContentLoadedEventStart &gt; Original domContentLoadedEventStart
    • PASS [expected FAIL] subtest: Reload domInteractive &gt; Original domInteractive
    • PASS [expected FAIL] subtest: Reload fetchStart &gt; Original fetchStart
    • PASS [expected FAIL] subtest: Reload loadEventEnd &gt; Original loadEventEnd
    • PASS [expected FAIL] subtest: Reload loadEventStart &gt; Original loadEventStart
  • PASS [expected FAIL] /png/apng/acTL-plays-two.html (#41191)
  • PASS [expected FAIL] /png/apng/fcTL-dispose-none.html (#41817)
  • 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
      

  • TIMEOUT /trusted-types/trusted-types-navigation.html?06-10 (#37920)
    • TIMEOUT [expected FAIL] subtest: Navigate a frame via anchor with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via anchor with javascript:-urls w/ default policy in report-only mode.
  • OK /trusted-types/trusted-types-navigation.html?16-20 (#44835)
    • FAIL [expected PASS] subtest: Navigate a frame via area with javascript:-urls in report-only mode.

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

  • TIMEOUT /trusted-types/trusted-types-navigation.html?31-35 (#38034)
    • TIMEOUT [expected PASS] subtest: Navigate a frame via form-submission with javascript:-urls in report-only mode.

      Test timed out
      

    • NOTRUN [expected TIMEOUT] subtest: Navigate a frame via form-submission with javascript:-urls w/ default policy in report-only mode.
  • CRASH [expected OK] /webaudio/the-audio-api/the-stereopanner-interface/stereopannernode-panning.html
  • CRASH [expected OK] /workers/SharedWorker-extendedLifetime-data.html
  • CRASH [expected TIMEOUT] /workers/SharedWorker_dataUrl.html
  • CRASH [expected OK] /workers/Worker-creation-happens-in-parallel.https.html
  • CRASH [expected OK] /workers/Worker-formdata.any.worker.html
  • CRASH [expected OK] /workers/Worker-multi-port.html
  • CRASH [expected OK] /workers/Worker-timeout-increasing-order.html
  • CRASH [expected OK] /workers/WorkerLocation_search_fragment.htm
  • CRASH [expected OK] /workers/WorkerNavigator_appName.htm
  • CRASH [expected OK] /workers/WorkerNavigator_onLine.htm
  • CRASH [expected OK] /workers/constructors/SharedWorker/global-members.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/interface-objects.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/setting-port-members.html
  • CRASH [expected OK] /workers/constructors/SharedWorker/unexpected-global-properties.html
  • CRASH [expected ERROR] /workers/examples/onconnect.any.sharedworker.html
  • CRASH [expected OK] /workers/importscripts_mime_local.any.worker.html
  • CRASH [expected OK] /workers/interfaces/DedicatedWorkerGlobalScope/postMessage/structured-clone-imagedata.html
  • CRASH [expected ERROR] /workers/interfaces/WorkerGlobalScope/location/returns-same-object.any.sharedworker.html
  • CRASH [expected OK] /workers/interfaces/WorkerGlobalScope/location/worker-separate-file.html
  • CRASH [expected OK] /workers/interfaces/WorkerGlobalScope/onerror/exception-in-onerror.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/WindowTimers/001.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/WindowTimers/003.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/importScripts/004.html
  • CRASH [expected ERROR] /workers/interfaces/WorkerUtils/importScripts/catch.sub.any.serviceworker.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/navigator/006.html
  • CRASH [expected OK] /workers/interfaces/WorkerUtils/navigator/008.worker.html
  • CRASH [expected OK] /workers/modules/shared-worker-import.window.html
  • CRASH [expected OK] /workers/semantics/encodings/004.worker.html
  • CRASH [expected OK] /workers/semantics/xhr/006.html
  • CRASH [expected OK] /xhr/formdata/constructor-submitter.html
Stable unexpected results that are known to be intermittent (11)
  • TIMEOUT [expected OK] /fetch/api/redirect/redirect-keepalive.https.any.html (#32153)
    • TIMEOUT [expected PASS] subtest: [keepalive][iframe][load] mixed content redirect; setting up

      Test timed out
      

  • OK /fetch/metadata/generated/css-font-face.https.sub.tentative.html (#32732)
    • PASS [expected FAIL] subtest: sec-fetch-mode
    • FAIL [expected PASS] subtest: sec-fetch-dest

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

    • 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-origin destination
    • PASS [expected FAIL] subtest: sec-fetch-storage-access - Not sent to non-trustworthy cross-site destination
  • ERROR [expected OK] /focus/focus-event-after-switching-iframes.sub.html (#40368)
  • OK /html/browsers/browsing-the-web/navigating-across-documents/navigation-unload-same-origin.window.html (#29049)
    • FAIL [expected PASS] subtest: Same-origin navigation started from unload handler must be ignored

      assert_equals: expected "?pass" but got "?fail"
      

  • 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
  • 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
    • NOTRUN [expected FAIL] subtest: Host element with delegatesFocus should support autofocus
  • OK /resource-timing/buffer-full-when-populate-entries.html (#44408)
    • PASS [expected FAIL] subtest: Test that a buffer full event does not bubble and that resourcetimingbufferfull is called only once per overflow
  • OK /sanitizer-api/sanitizer-inert-document.tentative.html (#44273)
    • PASS [expected FAIL] subtest: Test whether setHTML loads the image.
  • OK [expected TIMEOUT] /trusted-types/trusted-types-navigation.html?11-15 (#44834)
    • PASS [expected TIMEOUT] subtest: Navigate a window via area with javascript:-urls in report-only mode.
  • OK /trusted-types/trusted-types-reporting.html (#43737)
    • PASS [expected FAIL] subtest: Trusted Type violation report: creating a report-only-forbidden policy.
    • PASS [expected FAIL] subtest: Trusted Type violation report: creating a forbidden-but-not-reported policy.
    • PASS [expected FAIL] subtest: Trusted Type violation report: sample for SVGScriptElement href assignment by setAttribute
    • PASS [expected FAIL] subtest: Trusted Type violation report: sample for eval
    • PASS [expected FAIL] subtest: Trusted Type violation report: sample for custom element assignment
    • PASS [expected FAIL] subtest: Trusted Type violation report: Worker constructor

@github-actions
Copy link
Copy Markdown

✨ Try run (#26148094547) succeeded.

Not all accesses to attributes require a `cx`, since
most of them don't actually need the `Attr` node, but only
the value contained within. Therefore, introduce a variant
that directly accesses the value, to avoid materializing
attribute nodes.

Doing so removes the need for one of the `temp_cx` in XPath,
since it only requires the attribute value and not the full node.

Part of servo#45022

Signed-off-by: Tim van der Lippe <[email protected]>
@TimvdLippe TimvdLippe force-pushed the introduce-get-string-attribute-value branch from ef78082 to 05253ee Compare May 20, 2026 08:46
@servo-highfive servo-highfive removed the S-awaiting-review There is new code that needs to be reviewed. label May 20, 2026
@TimvdLippe TimvdLippe added this pull request to the merge queue May 20, 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 May 20, 2026
Merged via the queue into servo:main with commit 6d7d185 May 20, 2026
30 checks passed
@TimvdLippe TimvdLippe deleted the introduce-get-string-attribute-value branch May 20, 2026 12:21
@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 May 20, 2026
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.

4 participants