-
Notifications
You must be signed in to change notification settings - Fork 4.6k
Provide cross-browser node containment checking #7033
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
nodeContains to @wordpress/dom package
aduth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Honestly, I'd prefer to add a polyfill so
Node#containsjust works throughout our code base, but with the breakdown of packages and dependencies, I'm not sure where and how it should be added.
This is a good question, and one which we should address directly, as it impacts consistency of a few different things:
- We have a polyfill utility on the server, and its primary purpose as used today is to serve as a progressive enhancement for unsupporting / misbehaving browsers without impacting those using competent browsers.
- We have an
element-closestdependency which is pulled into various files where we useNode#closestto bring support for IE11- This has shown to be problematic, because (a) the code itself should probably not be responsible for ensuring the polyfill exists, (b) the same issue above of developer needing to know it exists, (c) each bundle is probably including its own copy, and (d) it stagnates and is often not removed if future refactoring drops usage.
- Case in point, these imports in current master are totally unnecessary, as
Element#closestexists nowhere in the files they're used.import 'element-closest'; import 'element-closest'; import 'element-closest';
- Case in point, these imports in current master are totally unnecessary, as
- This has shown to be problematic, because (a) the code itself should probably not be responsible for ensuring the polyfill exists, (b) the same issue above of developer needing to know it exists, (c) each bundle is probably including its own copy, and (d) it stagnates and is often not removed if future refactoring drops usage.
- This proposed set of changes would be a new third pattern for handling browser incompatibilities
I think our progressive enhancement polyfill utility is the best path forward, because developers shouldn't have to think about this, they should use the browser APIs as the specification dictates it should behave. These are isolated and can be dropped easily when browser support requirements change.
But, as you noted, a challenge here is knowing how to configure the dependencies such that the polyfills are ensured to be loaded. In that sense, and given that these are true progressive enhancements which do nothing if a browser is detected to support a given feature, I'd say: Load it everywhere; every WordPress admin screen; for any features we ever use.
packages/dom/src/test/dom.js
Outdated
| expect( nodeContainsFunction( parentNode, orphanTextNode ) ).toBeFalsy(); | ||
| } ); | ||
|
|
||
| it( 'it returns true when the parent contains a candidate element as a child', () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should have a test:
it( 'returns true when passed node itself', () => {...which I have a feeling will fail as current implemented. See also my concluding notes about us wanting to maintain this.
From specification:
The contains(other) method, when invoked, must return true if other is an inclusive descendant [...] An inclusive descendant is an object or one of its descendants.
https://dom.spec.whatwg.org/#dom-node-contains
https://dom.spec.whatwg.org/#concept-tree-inclusive-descendant
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for catching that! I added a test. Surprisingly, it does work due to using Node#contains if the node is an element. I don't love that this is IE-specific fix, but I was keeping my understanding of our browser support in mind and only know about IE's broken implementation.
See also my concluding notes about us wanting to maintain this.
It's possible I'm not reading well because it's later here, but I don't see direct commentary on wanting to maintain this. What am I missing?
I'd rather add an existing polyfill, but when I searched the NPM registry, all I found were implementations like nodeContains and nothing to patch in a working Node#contains. It wasn't an exhaustive search, and I'll look again in the morning.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Surprisingly, it does work due to using Node#contains if the node is an element.
There are some non-element Node#contains we should want to support, e.g.
const node = document.createTextNode( '' );
node.contains( node );
// true
document.contains( document );
// trueI added a test case for the latter, which fails at the moment.
It's possible I'm not reading well because it's later here, but I don't see direct commentary on wanting to maintain this. What am I missing?
Sorry, in retrospect it wasn't a direct commentary. Indirect in the sense that we're already discovering bugs, and if a stable normalization solution already exists and is well-vetted, we should opt for it instead than try to recreate it ourselves.
I'd rather add an existing polyfill, but when I searched the NPM registry, all I found were implementations like nodeContains and nothing to patch in a working Node#contains.
The one from Financial Times' polyfill.io looks promising:
Typically it's used through the polyfill.io API (which is pretty neat in detecting, by user-agent, which features to polyfill), we could maybe reference directly by the link:
https://unpkg.com/[email protected]/polyfills/Node/prototype/contains/polyfill.js
The polyfill condition looks quite simple too, just document.contains
https://unpkg.com/[email protected]/polyfills/Node/prototype/contains/detect.js
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a test case for the latter, which fails at the moment.
Thanks!
🤦♂️ The ironic thing is that this is related to the bug I wanted to solve in the IE implementation: Not working for non-element nodes.
Thanks for finding a good external polyfill for this. It's good to now know about that library.
I updated this PR to add the polyfill before the 'wp-dom' script since appears to be a foundational script for the packages (even for wp-element through its wp-utils dependencies). I believe this may fall short of how widely you wanted to load this, but what do you think?
This was intentionally exaggerated to drive home the point. There might be some techniques we can leverage to limit its impact. For example, we should probably not need to load it if:
|
Got it. I will give this some thought in the morning. And please feel free to comment if anything else occurs to you. Thanks! |
8262970 to
eaf67c0
Compare
eaf67c0 to
629775c
Compare
aduth
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Confirmed that document.contains( document ) works in IE11 while on editor:
And that the polyfill is loaded in IE11, but not in Chrome.
I updated this PR to add the polyfill before the 'wp-dom' script since appears to be a foundational script for the packages (even for wp-element through its wp-utils dependencies). I believe this may fall short of how widely you wanted to load this, but what do you think?
I don't think it's correct, but neither is our other current usage. There's a couple things I'd like to see, but not necessarily as part of these changes:
- Avoid polyfills being considered as dependencies of specific scripts, unless we're certain we can maintain those dependencies.
- There's nothing about
wp-domthat depends on theNode#containspolyfill. Wherever we're using it should be the dependency - But then again, this highlights the bigger problem that this is a source of maintenance headaches and is likely not worth maintaining, vs. load always
- There's nothing about
- Better names for the script enqueues, which avoids potential plugin incompatibilities. Probably something with a WP-specific prefix, e.g.
wp-polyfill-node-contains
|
Thanks for the review and discussing a better approach to polyfills. I'll merge this so I can work on things that depend on it, but I agree attaching to It seems like it would be good to simply print the polyfill conditions before printing other scripts if there are scripts to be printed. Doing so appears less straightforward than I expected, but I'm hoping to look at it again later today. |
|
I strongly agree that we should remove all usages of Unless we mitigate it by wrapping all DOM operations into functions that are stored in |
|
Created task at #7159 for tracking future improvements. |

Description
IE11's
Node#containsimplementation does not support checking whether text nodes are contained within an element, andNode#containsis used by at least theAutocompletecomponent. This PR adds anodeContainsfunction to the@wordpress/dompackage to provide containment checking that works cross-browser.Honestly, I'd prefer to add a polyfill so
Node#containsjust works throughout our code base, but with the breakdown of packages and dependencies, I'm not sure where and how it should be added. I see how we are including external polyfills inlib/client-assets.phpbut have not identified a good external polyfill for this.I'm putting this out there because it is working, and we can convert it to a polyfill if desired.
This update is part of getting autocompletion working in IE11. It was originally part of #6667.
How has this been tested?
Unit tests.
Checklist: