-
Notifications
You must be signed in to change notification settings - Fork 20.6k
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
Issues using ShadowDom and ShadowRoot elements #1784
Comments
This is also present on jQuery 1.11.1. The main issue is that the internal .contains() function, which is used by .offset() and other places such as isHidden(), uses either the element function "contains" or, failing that, the element function compareDocumentPosition, to see whether the element in question is underneath element.ownerDocument. For elements that are underneath a shadow root, this is always false. Unfortunately there is no built-in function or accessor which will give the shadow root for a given element, which means that traversal is currently the only guaranteed way of retrieving the host of a shadow root. Putting this into the internal .contains() function would no doubt increase the running time of this function massively. However, I think it would be acceptable (From my own limited knowledge) to use a guardian function in .offset(), isHidden, etc, which works on the principle of: if ((browser has shadow DOM support) && (element matches CSS selector ":host *")) { traverse to find shadow host and use that for .contains() } which would hopefully not add too much of an overhead. Thoughts? |
If it's an issue with contains, then it affects a multitude of jQuery methods and method chains (any that end up using |
Well, okay, to elaborate, it only actually makes a difference if, out of the two arguments passed in, one is under a shadow root and the other isn't. For example, I agree it would affect a lot of different methods, especially anything which is using contains to check whether an element is currently within the DOM, however a proper fix would hopefully be able to do something better than node traversal to find the root/host. Full traversal every time contains is called, on both arguments, would probably have a bit of a speed impact? |
Seems like this needs to be solved via some changes or additions to DOM methods? I can't imagine that the design of shadow root would effectively destroy the use of DOM |
@TeaSeaLancs We could limit the performance degradation to cases where shadow roots need to be found. However, @dmethvin brings up a good point. Sometimes we just need to get browsers to fix the problem. |
It's a bit of a funny thing I think. On the one hand, elements under a shadow root are interpreted as their own portion of the DOM, separate from the main body, so Also note that the w3c spec details that for an element under a shadow root, the property On the other hand though there currently isn't any method or function specified as part of the w3c spec which acts like I don't necessarily think that the intention is to conceal the containment as such, as given a shadow host element, you can easily retrieve it's shadow root by using the property |
I'm going to pull @mikesherov into this for his opinion, and also to see if he could recommend a good place to raise the issue. |
I just ran into this issue. I'm going to have to agree with TeaSeaLancs, it looks like the specification needs to be rethought and updated. There doesn't seem to be a palatable fix with the current DOM methods. |
Here's a discussion from last year about this: http://lists.w3.org/Archives/Public/public-webapps/2013JanMar/0881.html |
I wrote patch gh-1976 that makes If would be great to have some native browser function for this, but until that time we need to deal somehow with existing tools. Solution is not pretty, but it works as expected and doesn't create significant overhead for elements that are not inside ShadowDOM. |
if (elem.createShadowRoot) {
var rect = elem.getBoundingClientRect();
// disconnected or hidden
if (!rect.height && !rect.width) {
return box;
}
box = rect;
} else {
// Make sure it's not a disconnected DOM node
if ( !jQuery.contains( docElem, elem ) ) {
return box;
}
box = elem.getBoundingClientRect();
} This is another way to fix this issue. This method does not need traversation over tree to detect if element is disconnected. One problem here is that for hidden elements, jQuery currently returns box Anyway, I will just leave it here as an alternative solution. |
Also, as far as I see, there is no tests for such behavior. And if you think what that method will not break compatibility, then we can even drop the condition there and execute always first part. |
It's possible we could use something like the solution @NekR proposes. The docs for .offset() specifically say it doesn't work for hidden elements. |
@dmethvin, in this case code above can be simplified to just |
I think there's a point missing here: If it was only ever as simple as @nazar-pc indicates, why does the case to check whether it's a disconnected DOM node exist in the first place? Is it a workaround for browsers which is no longer needed? Or is it an optimisation step to prevent the needless call of a potentially expensive function? If it's the former then sure, but if it's the latter do we not have to think about performance? |
Thanks to PhpStorm and how it integrates git I found where that code started: |
So the question becomes does that bug still affect any of the browsers which are supported by jQuery 2.x? If not then it seems like a fairly decent solution for .offset() I'm not sure whether the solution would work for all of the issues reported with working with elements under a shadow DOM though (Which would ostensibly be any function which uses jQuery.contains() as a method of filtering out disconnected nodes). What do you think? |
Actually the ticket @nazar-pc references seems to indicate that we were trying to support a return value of top/left 0 for disconnected elements. So any change should preserve that. |
Anything that uses native functions should work fine just like with @dmethvin, but that gives really big overhead to handle disconnected nodes especially with ShadowDOM support. Maybe with performance consideration (number of elements in DOM tree with ShadowDOM might much bigger than before) we can drop support for this edge case? In my opinion it worth that, especially with new major version. Also, it will not break any well-written code (it should be something wrong with getting offset of disconnected node). |
I agree with that completely. The problem is, people write bad code all the time, sometimes by accident when cases like this quietly return values that make it seem like everything is okay. I would have preferred that ticket be closed with, "Why are you getting the offset of an element not in the document? We'll update the docs to state that's not valid." It's very possible though that some code now depends on the zero return values. That's what has me concerned. Any other opinions? |
From an API perspective, the best behavior would be to throw when offset is meaningless (e.g., truly disconnected elements or non-elements). From a documentation perspective, the best behavior would be to exclude such elements from the range of valid input, in which case the return value is unspecified (and allowed to be cross-version/cross-branch inconsistent) but So I'm perfectly content with @NekR's proposal, provided it doesn't introduce an inconsistency between Edit: added other examples of meaningless input |
Well, guys, I have Chromium 39.0.2171.65 with native support for ShadowDOM and other stuff and guess what: var y = document.createElement('div')
y.getBoundingClientRect() outputs
Now Firefox Nightly 38.0a1 (2015-01-25), guess output?
So, why do we need to check for anything here, it already gives correct zeros. Where problem comes is Finally: if (!elem.ownerDocument.documentElement) {
return { top: 0, left: 0 };
}
box = elem.getBoundingClientRect(); Pretty straightforward. Not sure why it wasn't this way from the beginning. What you think? |
Moreover, in |
@dmethvin wrote:
That ticket removes scroll positions for disconnected nodes, but not for hidden nodes. If we will use only getBoundingClientRect() then it also will remove scroll positions for hidden nodes. Seems correct to me that hidden elements always should have zeroes too. @nazar-pc wrote:
Did not get your comment. You mean for attached and visible nodes it always returns zeroes? If so, then very strange because for me on Chrome 40 it works correctly. To summarize:
@nazar-pc wrote:
If it works then it's very nice way, I am just not sure about IE8 because him disconnected nodes are not really disconnected. Does anyone have IE8 to test it? |
No, in example next to that line you can see that it returns zeros on disconnected node.
Does jQuery 3.0 will support this very old browser? Or you meant compat version? |
Oh, yes, I overlooked that, sorry. This is exactly why I proposed use of only
I really do not know about which version we talk here, but for me it seems like both versions are broken in ShadowDOM, so both should be fixed. If I am wrong, please correct me. |
API-wise, jQuery 3.0 and jQuery Compat 3.0 are the same. The only difference is the browsers they support, IE8 being the odd one of course. We'd want the API to work (have consistent results) in the latest Chrome if a site is using either branch. There definitely needs to be some IE8 experimenting done here, because calling |
Honestly, I'd prefer throwing an error instead of returning inaccurate |
Add guard for ```createShadowRoot``` existence and return early if user-agent does not support ShadowDOM and elem is disconnected Fixes jquery#1784
Unfortunately I cannot run tests on IE8, even if I checkout "compat" branch. As you may see here https://github.com/NekR/jquery/compare/1784-fix-offset-in-shadow-dom?expand=1 I use this condition: if ( !elem.createShadowRoot && !jQuery.contains( docElem, elem ) ) { which uses Questions:
|
It doesn't violate our currently-documented API; http://api.jquery.com/offset/ already includes "jQuery does not support getting the offset coordinates of hidden elements". And even if that were not the case, backwards-incompatible changes are allowed with the upcoming major version bump.
I don't like this conflation of unrelated behaviors.
Please make a pull request; further discussion can take place there.
Our general guideline is that a separate PR is required only when the solution looks sufficiently different between the two branches that cherry-pick is impractical. For this ticket, one will be enough.
That depends on whether or not we want to guarantee such return values. For now, limit new tests to Shadow DOM. |
There are tests for ShadowDOM in my fork |
Ok, I have no problem with it. Just wondered about possible problems. If you think it will not be so much problem then it's probably ok. Other answers in PR. |
Remove IE8 unrelated code: ShadowDOM checks and getBoundingClientRect quirks Fixes jquery#1784
Originally reported by rictic at: http://bugs.jquery.com/ticket/15201
JSBin link to reproduce: http://jsbin.com/tamugo/2/edit (try in Chrome (or Firefox with dom.webcomponents.enabled turned on))
An element that is in the shadow dom appears in some ways to not be contained in the document. .offset() assumes that if the element isn't contained in the document then it's disconnected and returns an offset of {top: 0, left: 0}.
When .offset() checks to see if the element is disconnected it could try walking up the DOM not only by checking .parentNode as .contains() does but also by looking at .host, which is how one travels up from a shadow root into its containing element.
Issue reported for jQuery 2.1.1
The text was updated successfully, but these errors were encountered: