Open Bug 1368659 Opened 8 years ago Updated 3 years ago

[eslint] Write a rule that detects DOM manipulation in a loop over that DOM

Categories

(Developer Infrastructure :: Lint and Formatting, enhancement, P3)

enhancement

Tracking

(Not tracked)

People

(Reporter: Gijs, Unassigned)

Details

Consider: for (let x of y.children) { z.appendChild(x); } This will only append half of y's children into z, because the DOM children and childNodes collections are live to DOM changes, and we're changing the set of children/childNodes mid-iteration because appendChild moves the nodes elsewhere. It would be nice if eslint errored in cases where we're using a for...of loop where both of these conditions hold: 1) the bound variable (ie x in the above example) for the loop is created out of a .children or .childNodes or other live DOM collection (at least one of getElementsBy* or querySelector* is live, and the other isn't - I always forget which) 2) the bound variable is passed as the first argument to insertBefore, appendChild or removeChild, or <bound>.remove() is called.
Gijs, can you suggest some valid options, and maybe point to somewhere we've done this incorrectly before?
Flags: needinfo?(gijskruitbosch+bugs)
Priority: -- → P3
(In reply to Mark Banner (:standard8) (afk 11 - 20 Aug) from comment #1) > Gijs, can you suggest some valid options, and maybe point to somewhere we've > done this incorrectly before? Reader mode is the infamous example (well, back in 2015 or whatever) of where we did this incorrectly with a traditional for loop: for (var i = 0; i < parent.childNodes.length; i++) { if (parent.childNodes[i].someCondition) { somewhereElse.appendChild(parent.childNodes[i]); } } would only check and re-append half the things. Alternatives include: 0) use a non-live thing (see comment #0 about querySelector* vs. getElements*, but this doesn't work for foo.children / foo.childNodes) 1) making a non-live array first, e.g. using Array.from(); 2) iterating in reverse 3) iterating with a while loop if you're removing everything anyway: while (node.firstChild) node.firstChild.remove(); 4) iterating with a while loop over nodes, ie: let kid = node.firstChild; while (kid) { let thisKid = kid; kid = kid.nextElementSibling; doSomethingWith(thisKid); } 5) separate the "find all the things to append / reinsert / remove" and the "do the append / reinsert / remove" steps by collecting a specific set of elements in a set/array first, and then bulk-removing/appending/inserting those. Does that help?
Flags: needinfo?(gijskruitbosch+bugs) → needinfo?(standard8)
Yes thanks.
Flags: needinfo?(standard8)
Product: Testing → Firefox Build System
Summary: Write a rule that detects DOM manipulation in a loop over that DOM → [eslint] Write a rule that detects DOM manipulation in a loop over that DOM
Product: Firefox Build System → Developer Infrastructure
Severity: normal → S3
You need to log in before you can comment on or make changes to this bug.