🌐 AI搜索 & 代理 主页
Skip to content

Conversation

@ac-mmi
Copy link
Contributor

@ac-mmi ac-mmi commented Jul 21, 2024

This pull request addresses #5214 issue in jQuery.cleanData where certain elements were being skipped during the cleanup process. The problem occurred when an element removed itself from the DOM during its own cleanup, causing subsequent elements in the list to be missed.

@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Jul 21, 2024

CLA Signed

The committers listed above are authorized under a signed CLA.

Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

Thanks for the PR. Please don't change code formatting, though. Run npm test locally & you'll see the build fails; you need to fix it.

Also, all such changes need tests.

@ac-mmi ac-mmi requested a review from mgol July 21, 2024 13:20
@ac-mmi
Copy link
Contributor Author

ac-mmi commented Jul 26, 2024

@mgol Done

Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

jQuery.cleanData is not an API you'd call yourself; jQuery calls it. One of the suggested solutions on the issue was to update the internal getAll function to avoid getElementsByTagName which returns a live collection and just falling back to always use querySelectorAll. That also wouldn't require any changes to jQuery.cleanData and it'd reduce size instead of increasing it.

We'd need some perf tests done for this change, though, to make sure we don't regress significantly.

Also, as I mentioned before, all such changes need tests; there are no added in this PR. Make also sure existing tests & static checks are passing; npm test definitely does not pass on your PR.

The majority of the work here is with the perf tests & writing good unit tests, not in actual code changes.

@ac-mmi
Copy link
Contributor Author

ac-mmi commented Jul 27, 2024

@mgol All tests passed done.

Screenshot (1809)

@mgol
Copy link
Member

mgol commented Jul 29, 2024

Most of my last review still applies.

@ac-mmi
Copy link
Contributor Author

ac-mmi commented Jul 29, 2024

@mgol Done changed getAll.js and all tests passed

Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

We still need tests for what this PR is supposed to fix. Also, we need to have some performance tests comparing before & after done with test cases & results posted in a comment on this PR. This is to make sure we don’t regress too much on performance.

@ac-mmi
Copy link
Contributor Author

ac-mmi commented Jul 31, 2024

@mgol I added tests that specifically address the issues I aim to fix and conducted a performance test on the updated getAll() functionality using 1000 <span></span> items. Here are the results:

Original implementation time: 1 ms
New implementation time: 6.5 ms

@ac-mmi ac-mmi requested a review from mgol July 31, 2024 17:06
@mgol
Copy link
Member

mgol commented Jul 31, 2024

@ac-mmi can you share the test code, please?

@ac-mmi ac-mmi requested a review from mgol August 1, 2024 17:07
@ac-mmi
Copy link
Contributor Author

ac-mmi commented Aug 3, 2024

@mgol added the test for the issue addressing #5214

Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

We're getting closer, but I still have some remarks.

Please also share the code for measuring performance so that I can run it by myself.

@ac-mmi
Copy link
Contributor Author

ac-mmi commented Aug 5, 2024

@mgol Code for measuring performance

function measurePerformance(fn, context, tag, iterations = 1000) {
    var startTime = performance.now();
    for (let i = 0; i < iterations; i++) {
        fn(context, tag);
    }
    var endTime = performance.now();
    return endTime - startTime;
}

var context = document.createElement("div");
context.innerHTML = "<span></span>".repeat(1000);

// Original implementation using getElementsByTagName
function originalGetAll(context, tag) {
    var ret;
    if (typeof context.getElementsByTagName !== "undefined") {
        ret = context.getElementsByTagName(tag || "*");
    } else if (typeof context.querySelectorAll !== "undefined") {
        ret = context.querySelectorAll(tag || "*");
    } else {
        ret = [];
    }

    if (tag === undefined || (tag && nodeName(context, tag))) {
        return jQuery.merge([context], ret);
    }

    return ret;
}

// New implementation using querySelectorAll
function newGetAll(context, tag) {
    var ret;
    if (typeof context.querySelectorAll !== "undefined") {
        ret = context.querySelectorAll(tag || "*");
    } else {
        ret = [];
    }

    if (tag === undefined || (tag && nodeName(context, tag))) {
        return jQuery.merge([context], ret);
    }

    return ret;
}

function nodeName( elem, name ) {
	return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
}

console.log("New implementation time: " + measurePerformance(newGetAll, context, "span") + "ms");

console.log("Original implementation time: " + measurePerformance(originalGetAll, context, "span") + "ms");


@mgol mgol added the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Aug 5, 2024
@ac-mmi ac-mmi requested a review from mgol August 9, 2024 19:21
@mgol
Copy link
Member

mgol commented Aug 12, 2024

Construction of a perf test

@ac-mmi the perf tests you provided test just raw performance of various versions of getAll. However, what's more important is its effect on real jQuery APIs. Most of getAll usage is meant to just find scripts which are usually limited in numbers. However, two methods: remove and empty - use it to find all the nodes within the current one.

I created a test case using three versions of jQuery:

  1. A real jQuery Git one.
  2. With a patched getAll to remove the getElementsByTagName path.
  3. With a patched getAll to wrap the getElementsByTagName path with an additional slice call:
    if ( typeof context.getElementsByTagName !== "undefined" ) {
    	ret = arr.slice.call(context.getElementsByTagName( tag || "*" ));
    (arr is https://github.com/jquery/jquery/blob/dbc9dac7aecb106b66050342ff8daf1ecdd4239f/src/var/arr.js)

The first version is the fastest but the third one is faster than the second one. The differences between (2) & (3) in Chrome & Safari are small enough that I'd opt to (2), but in Firefox they are massive.

Test case: https://jsperf.app/gifera/1/preview

Environment

Tests done on macOS Sonoma 14.6.1 (23G93) on a MacBook Pro with the M3 Max chip.

Browsers:

  1. Chrome 127.0.6533.100
  2. Firefox 129.0
  3. Safari 17.6 (19618.3.11.11.5)

Results

Chrome Firefox Safari
jQuery Git 3,343,353 ±2.70% (🏆) 6,055,305 ±2.15% (🏆) 7,645,512 ±3.95% (🏆)
no gEBTN 2,640,264 ±2.28% (21% ⬇️) 3,533,072 ±2.11% (42% ⬇️) 6,172,783 ±4.52% (19% ⬇️)
gEBTN + slice 2,854,539 ±2.40% (15% ⬇️) 4,848,646 ±1.58% (20% ⬇️) 6,012,539 ±6.10% (21% ⬇️)

Conclusions

The perf difference in Firefox is staggering so I'd go with the slice version. That will get us a 15-21% perf cut on empty on large-ish structures, hopefully that's acceptable to fix the issue at hand.

Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

Thanks for the updates; see my latest comments. Please make sure to address them all before re-requesting review - I bumped one of the older comments that was not addressed.

@ac-mmi ac-mmi requested a review from mgol August 27, 2024 08:51
@ac-mmi ac-mmi requested a review from mgol August 29, 2024 06:25
Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

Just a few nits, other than that, mostly looks good!

@mgol mgol added Needs review Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. labels Aug 30, 2024
@ac-mmi ac-mmi requested a review from mgol August 30, 2024 17:00
Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

Thanks, this LGTM, but I want to discuss it with the team before merging.

@mgol mgol added this to the 3.7.2 milestone Sep 2, 2024
Copy link
Member

@timmywil timmywil left a comment

Choose a reason for hiding this comment

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

LGTM

@timmywil timmywil removed the Discuss in Meeting Reserved for Issues and PRs that anyone would like to discuss in the weekly meeting. label Sep 9, 2024
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>
@mgol mgol removed the Needs review label Sep 9, 2024
Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

I noticed one more issue.

When doing the change, don't remove the comment we added to src/manipulation/getAll.js via a suggestion comment.

@ac-mmi ac-mmi requested a review from mgol September 10, 2024 08:00
Copy link
Member

@mgol mgol left a comment

Choose a reason for hiding this comment

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

Thanks!

@mgol mgol changed the title Fix: Prevent jQuery.cleanData from Skipping Elements During Cleanup Manipulation: Make jQuery.cleanData not skip elements during cleanup Sep 10, 2024
@mgol mgol merged commit 3cad5c4 into jquery:main Sep 10, 2024
mgol pushed a commit that referenced this pull request Sep 10, 2024
When passing a result of `getElementByTagsName` to `jQuery.cleanData`, convert
it to an array first. Otherwise, a live NodeList is passed and if any of the
event cleanups remove the element itself, a collection is modified during the
iteration, making `jQuery.cleanData` skip cleanup for some elements.

Fixes gh-5214
Closes gh-5523

Co-authored-by: Michał Gołębiowski-Owczarek <m.goleb@gmail.com>
Co-authored-by: Richard Gibson <richard.gibson@gmail.com>

(cherry picked from commit 3cad5c4)
@mgol
Copy link
Member

mgol commented Sep 10, 2024

Landed on main in 3cad5c4 & on 3.x-stable in 752e911. Thanks!

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Sep 24, 2025
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Development

Successfully merging this pull request may close these issues.

4 participants