Skip to content

Make clamp_to compliant with the WebIDL specification#694

Merged
mrobinson merged 5 commits intoservo:mainfrom
Taym95:fix-WebIDL-conversion-to-clamped-i64
Jan 5, 2026
Merged

Make clamp_to compliant with the WebIDL specification#694
mrobinson merged 5 commits intoservo:mainfrom
Taym95:fix-WebIDL-conversion-to-clamped-i64

Conversation

@Taym95
Copy link
Copy Markdown
Member

@Taym95 Taym95 commented Jan 2, 2026

This PR fixes the implementation of WebIDL clamp numeric conversions to match the behavior required by the WebIDL specification and Firefox.

Companion PR: servo/servo#41640

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.

Please restore the PR template and also explain what the fix is that you made in the PR title and description.

@mrobinson
Copy link
Copy Markdown
Member

Please restore the PR template and also explain what the fix is that you made in the PR title and description.

Apologies, mozjs does not have a PR template, but I think it's helpful to write more than "fix" in the PR title and description. Please describe the problem and how these changes fix the problem.

@Taym95 Taym95 requested review from jdm and mrobinson January 2, 2026 20:38
@Taym95
Copy link
Copy Markdown
Member Author

Taym95 commented Jan 2, 2026

Please restore the PR template and also explain what the fix is that you made in the PR title and description.

Apologies, mozjs does not have a PR template, but I think it's helpful to write more than "fix" in the PR title and description. Please describe the problem and how these changes fix the problem.

The underlying issue is already fully discussed in the referenced issue, which is linked from this PR.

This mozjs PR is scoped to the mozjs change, I opened a corresponding Servo PR respecting the PR template and additional context.

I’ve also added more detail to this PR description.

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.

Oh, I think it also makes sense to link to the specification in the rustdoc as well. Another option would be to use specification text, but whatever you like is fine. There just needs to be an explanation of what's going on here as this is quite tricky code.

Signed-off-by: Taym Haddadi <[email protected]>
@Taym95 Taym95 requested a review from mrobinson January 3, 2026 23:33
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.

Sorry. I have a doubt about the code itself that I didn't notice before. Otherwise this seems good as it is passing tests.

Comment on lines +261 to +267
/*
* It was a tie (since moving away from 0 by 0.5 gave us the exact integer
* we want). Since we rounded away from 0, we either already have an even
* number or we have an odd number but the number we want is one closer to
* 0. So just unconditionally masking out the ones bit should do the trick
* to get us the value we want.
*/
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.

Let's keep the comment style consistent in the function by switching to //.

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 followed Gecko and copied the comments as is, I will switch them to // for consistency.

Comment on lines +230 to +231
/// This function is ported from Gecko’s `PrimitiveConversionTraits_Clamp`.
/// [Gecko source]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226
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.

The markdown link from my suggestion is now broken as the text inside [...] has to match some from a link in the preceding paragraph. You can fix it with:

Suggested change
/// This function is ported from Gecko’s `PrimitiveConversionTraits_Clamp`.
/// [Gecko source]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226
/// This function is ported from Gecko’s [`PrimitiveConversionTraits_Clamp`].
///
/// [`PrimitiveConversionTraits_Clamp`]: https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#226

By the way if you use an IDE like Visual Studio Code or Zed, if you hover over the function name it should give you a formatted view of the rustdoc and let you test clicking on links.

* 0. So just unconditionally masking out the ones bit should do the trick
* to get us the value we want.
*/
truncated = (((to_truncate as i64) & !1) as f64).cast();
Copy link
Copy Markdown
Member

@mrobinson mrobinson Jan 4, 2026

Choose a reason for hiding this comment

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

Hrm. Shouldn't you be truncating truncate here instead of to_truncate? to_truncate has been rounded, but not truncated via the cast.

Suggested change
truncated = (((to_truncate as i64) & !1) as f64).cast();
truncated = truncated &= !1;

With that change does Servo still pass the tests?

If that doesn't work I also wonder about the as f64 here. Theoretically this function should only be called if D is an integral type. Gecko ensures this with a static cast, but that doesn't seem to be possible in Rust unfortunately. If the above doesn't work, does the following?

Suggested change
truncated = (((to_truncate as i64) & !1) as f64).cast();
truncated = ((to_truncate as i64) & !1).cast();

Copy link
Copy Markdown
Member Author

@Taym95 Taym95 Jan 4, 2026

Choose a reason for hiding this comment

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

No — to_truncate is intentional here and matches the Gecko implementation,the tie check must compare against the value after the ±0.5 adjustment and truncation step, using the already cast value would lose the information needed to detect exact .5 ties.

I ran the Servo tests before committing, and they are passing with this change.

Copy link
Copy Markdown
Member

@mrobinson mrobinson Jan 4, 2026

Choose a reason for hiding this comment

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

Here is the Gecko code:

 T truncated = static_cast<T>(toTruncate);

  if (truncated == toTruncate) {
    /*
     * It was a tie (since moving away from 0 by 0.5 gave us the exact integer
     * we want). Since we rounded away from 0, we either already have an even
     * number or we have an odd number but the number we want is one closer to
     * 0. So just unconditionally masking out the ones bit should do the trick
     * to get us the value we want.
     */
    truncated &= ~1;
  }

The value that is modified here is truncated which has already been cast to the final type. I believe that Gecko code is trying to compare the rounded value to the rounded+cast value and then modify the rounded+cast value if they are equal. In your case you are comparing the rounded value to the rounded+cast value and then modifying the rounded (but not cast value). Instead you cast to i64, which I think is redoing the rounding cast but to a type of a potentially different size than D.

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.

FWIW, I think this might work fine when doing this operation from f64 to i64, but I suspect it will not work exactly the same was as Gecko in the general case. That could potentially be why this still passes tests.

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 think I see your concern, In Gecko the low bit is cleared on the already cast value, while in the Rust version the even adjustment is computed via an i64 intermediate and then cast back to D, right?

given the WebIDL bounds, this produces the same result today, but I agree the difference can be confusing since the comments are copied from Gecko. I can adjust the code to operate directly on truncated if that’s clearer.

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.

Yes, I think we should just use the truncated value here and then maybe put a warning in the rustdoc saying that the function should only be used when the target is an integer type. Thank you!

@Taym95
Copy link
Copy Markdown
Member Author

Taym95 commented Jan 4, 2026

Sorry. I have a doubt about the code itself that I didn't notice before. Otherwise this seems good as it is passing tests.

I don’t think there is any remaining doubt about the code. It follows the Gecko implementation and the WPT tests are passing.

@Taym95 Taym95 requested a review from mrobinson January 4, 2026 20:20
// number or we have an odd number but the number we want is one closer to
// 0. So just unconditionally masking out the ones bit should do the trick
// to get us the value we want.
truncated = (((truncated.cast() as i64) & !1) as f64).cast();
Copy link
Copy Markdown
Member

@mrobinson mrobinson Jan 4, 2026

Choose a reason for hiding this comment

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

So this is:

  1. Converting an integer to a float (I think this is what truncated.cast() does)
  2. Converting the float back to an integer. (as i64)
  3. Doing a bitwise operation on the integer. (& !1)
  4. Converting the integer back to a float. (as f64)
  5. Converting that float back to an integer. (.cast())

In the Gecko code it directly does the bitwise operation on the integer. 😦 I wish we could do the same here. Two options:

  1. Do the truncation and bit flipping in i64 and then convert at the end. We cannot even do this easily as the various integer types (such as u8) do not implement As<i64> This could be done with TryInto but I don't know the performance implications or how performance sensitive this code is.
  2. Ensure that D implements the necessary bit operations as well as num-traits crate's One trait. This might work, but I have not tried it.

Maybe someone with a better grasp of Rust generics can help here.

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.

In the meantime I've dismissed my review so it doesn't block this change.

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.

Yes truncated = truncated & !D::one(); works here.

@mrobinson mrobinson dismissed their stale review January 4, 2026 23:53

I am not entirely sure what the best approach is here.

@Taym95 Taym95 force-pushed the fix-WebIDL-conversion-to-clamped-i64 branch from 0c7bfff to a2edddb Compare January 5, 2026 00:03
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.

Excellent. Thank you!

@mrobinson mrobinson changed the title Fix WebIDL conversion to clamped i64 Making clamp_to compliant with the WebIDL specification Jan 5, 2026
@mrobinson mrobinson changed the title Making clamp_to compliant with the WebIDL specification Make clamp_to compliant with the WebIDL specification Jan 5, 2026
@mrobinson mrobinson added this pull request to the merge queue Jan 5, 2026
Merged via the queue into servo:main with commit 60479fc Jan 5, 2026
36 checks passed
@Taym95 Taym95 mentioned this pull request Jan 5, 2026
@sagudev
Copy link
Copy Markdown
Member

sagudev commented Jan 5, 2026

@Taym95 It is useful to link companion servo PR in this PR description.

@mrobinson
Copy link
Copy Markdown
Member

@sagudev That's probably my fault. There was a link to the original issue in the description, but I was worried that it would close the issue so I removed it.

@sagudev
Copy link
Copy Markdown
Member

sagudev commented Jan 5, 2026

I meant companion PR in servo, not the issue (issue is usually linked in the companion PR).

@Taym95
Copy link
Copy Markdown
Member Author

Taym95 commented Jan 5, 2026

It seems there isn’t a PR template or a defined process here yet, Having one similar to what we use in the Servo repo, could help set expectations and avoid this kind of back and forth.

@Taym95 Taym95 deleted the fix-WebIDL-conversion-to-clamped-i64 branch January 5, 2026 09:36
github-merge-queue bot pushed a commit that referenced this pull request Jan 5, 2026
Incorporates #694

Signed-off-by: Taym Haddadi <[email protected]>
github-merge-queue bot pushed a commit to servo/servo that referenced this pull request Jan 5, 2026
This PR fixes the implementation of WebIDL clamp numeric conversions to
match the behavior required by the WebIDL specification and
[Firefox](https://searchfox.org/firefox-main/rev/aee7c0f24f488cd7f5a835803b48dd0c0cb2fd5f/dom/bindings/PrimitiveConversions.h#225-269).

Testing: more test in tests/wpt/tests/FileAPI/blob/Blob-slice.any should
pass.
Fixes: #41638

mozjs PR: servo/mozjs#694

---------

Signed-off-by: Taym Haddadi <[email protected]>
@sagudev
Copy link
Copy Markdown
Member

sagudev commented Jan 5, 2026

It seems there isn’t a PR template or a defined process here yet, Having one similar to what we use in the Servo repo, could help set expectations and avoid this kind of back and forth.

I've opened #696

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