Skip to content
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

add shadow dom v1 support to DOM atoms #5762

Merged
merged 9 commits into from
Apr 30, 2018

Conversation

43081j
Copy link
Contributor

@43081j 43081j commented Apr 11, 2018

This fixes #5761.

Though it raises another issue: that in several places we only support shadow dom v0, rather than the v1 spec. most of our tests use v0 too, so we don't test against v1 apis like attachShadow etc.

"* /deep/ #neverShownDiv"));

// neverShown is in an older shadow DOM, and thus isn't displayed
assertFalse(bot.dom.isShown(neverShownDiv));
Copy link
Contributor Author

Choose a reason for hiding this comment

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

FYI i removed this test because AFAIK its invalid per the old spec and doesn't apply in the new spec (as createShadowRoot has gone).

this test relies on the fact chrome once allowed you to create a shadow root multiple times, which was removed shortly after. now it just throws an exception or doesn't do anything.

@43081j
Copy link
Contributor Author

43081j commented Apr 11, 2018

unfortunately the tests in shadow_dom_test are very confusing and thrown together. they depend heavily in shadow dom V0 and behaviours that lasted for only a couple of browser releases.

most of the tests in there are to assert that "older shadow roots" work correctly. but there is no such thing as an "older shadow root", it was deprecated long ago and throws exceptions in most or all browsers.

i tried to strip those out so we can at least have a passing build, but it leaves us with a mess of a test file/suite. so i think if we can at least agree we shouldn't be testing non-existent browser features (removed v0 things only chrome had temporarily), its better i just try to rewrite that test file.

@barancev
Copy link
Member

With this patch the test is still broken for me (in chrome):

localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html
3 of 3 tests run in 8ms.
0 passed, 3 failed.
3 ms/test. 92 files loaded.
.
23:41:26.861  Start
23:41:26.864  testGetParentNodeInComposedDom : FAILED (run individually)
23:41:26.865  ERROR in testGetParentNodeInComposedDom
Expected <null> but was <[object HTMLContentElement]> (HTMLContentElement)
Error: Expected <null> but was <[object HTMLContentElement]> (HTMLContentElement)
    at Object.goog.testing.asserts.raiseException (http://localhost:2310/third_party/closure/goog/testing/asserts.js:1283:11)
    at _assert (http://localhost:2310/third_party/closure/goog/testing/asserts.js:189:26)
    at assertEquals (http://localhost:2310/third_party/closure/goog/testing/asserts.js:418:3)
    at testGetParentNodeInComposedDom (http://localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html:121:5)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:899:21)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:790:12)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runSetUpPage_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:803:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:781:10)
    at new goog.Promise (http://localhost:2310/third_party/closure/goog/promise/promise.js:172:16)
    at goog.testing.TestCase.runTestsReturningPromise (http://localhost:2310/third_party/closure/goog/testing/testcase.js:780:10)
    at goog.testing.TestRunner.execute (http://localhost:2310/third_party/closure/goog/testing/testrunner.js:264:19)
    at http://localhost:2310/third_party/closure/goog/testing/jsunit.js:179:12
23:41:26.869  testGetVisibleTextInComposedDom : FAILED (run individually)
23:41:26.869  ERROR in testGetVisibleTextInComposedDom
Expected <Older Child
As the older child of a nested shadow root, this is the most likely to go wrong bit of the page.
Older Child Contents
Parent
Parent Contents
Base-level Stuff> (String) but was <> (String)
Error: Expected <Older Child
As the older child of a nested shadow root, this is the most likely to go wrong bit of the page.
Older Child Contents
Parent
Parent Contents
Base-level Stuff> (String) but was <> (String)
    at Object.goog.testing.asserts.raiseException (http://localhost:2310/third_party/closure/goog/testing/asserts.js:1283:11)
    at _assert (http://localhost:2310/third_party/closure/goog/testing/asserts.js:189:26)
    at assertEquals (http://localhost:2310/third_party/closure/goog/testing/asserts.js:418:3)
    at testGetVisibleTextInComposedDom (http://localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html:175:5)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:899:21)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.finishTestInvocation_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:999:10)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.safeTearDown_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:864:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:934:15)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:790:12)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runSetUpPage_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:803:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:781:10)
    at new goog.Promise (http://localhost:2310/third_party/closure/goog/promise/promise.js:172:16)
    at goog.testing.TestCase.runTestsReturningPromise (http://localhost:2310/third_party/closure/goog/testing/testcase.js:780:10)
    at goog.testing.TestRunner.execute (http://localhost:2310/third_party/closure/goog/testing/testrunner.js:264:19)
    at http://localhost:2310/third_party/closure/goog/testing/jsunit.js:179:12
23:41:26.869  testIsShownInComposedDom : FAILED (run individually)
23:41:26.869  ERROR in testIsShownInComposedDom
Argument to isShown must be of type Element
Error: Argument to isShown must be of type Element
    at Object.bot.dom.isShown_ (http://localhost:2310/javascript/atoms/dom.js:456:11)
    at Object.bot.dom.isShown (http://localhost:2310/javascript/atoms/dom.js:607:18)
    at testIsShownInComposedDom (http://localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html:197:25)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:899:21)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.finishTestInvocation_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:999:10)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.safeTearDown_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:864:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:934:15)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.finishTestInvocation_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:999:10)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.safeTearDown_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:864:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:934:15)
    at goog.testing.TestCase.safeRunTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:849:8)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runNextTest_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:839:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:790:12)
    at goog.testing.TestCase.invokeTestFunction_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:927:19)
    at goog.testing.TestCase.runSetUpPage_ (http://localhost:2310/third_party/closure/goog/testing/testcase.js:803:8)
    at goog.testing.TestCase.<anonymous> (http://localhost:2310/third_party/closure/goog/testing/testcase.js:781:10)
    at new goog.Promise (http://localhost:2310/third_party/closure/goog/promise/promise.js:172:16)
    at goog.testing.TestCase.runTestsReturningPromise (http://localhost:2310/third_party/closure/goog/testing/testcase.js:780:10)
    at goog.testing.TestRunner.execute (http://localhost:2310/third_party/closure/goog/testing/testrunner.js:264:19)
    at http://localhost:2310/third_party/closure/goog/testing/jsunit.js:179:12
23:41:26.869  Done
 
Error: Failed to execute 'createShadowRoot' on 'Element': Shadow root cannot be created on a host which already hosts a shadow tree.
    at http://localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html:91:9

@43081j
Copy link
Contributor Author

43081j commented Apr 16, 2018

@barancev to be honest those tests are mostly useless in newer chrome.

most of the coverage in there is over this concept of older shadow roots (multiple roots). its not even supported anymore by spec or by browsers. i think the tests just need rewriting completely in that file.

ill give that a go tomorrow if i get chance

@43081j 43081j force-pushed the visible-text-shadow branch 3 times, most recently from 70aadcf to ae758b0 Compare April 19, 2018 08:16
@43081j
Copy link
Contributor Author

43081j commented Apr 19, 2018

im unable to get these builds working.

i rewrote the tests but locally im having trouble running the build, so i was hoping to see if they pass in CI. though it seems CI builds fail because "chrome is not reachable".

can anyone take a look at this? maybe its because chrome 66 was released?

@43081j
Copy link
Contributor Author

43081j commented Apr 20, 2018

@barancev @p0deje any chance either of you could take a look at this?

we're in great need of these fixes so i just need to get the builds working and the tests passing. but it seems they fail persistently now because of unreachable chrome

@barancev
Copy link
Member

Test results:

localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html
9 of 9 tests run in 17ms.
1 passed, 8 failed.
2 ms/test. 92 files loaded.

To run a single test you can start a debug server with a command go debug-server and open test URL in a browser: http://localhost:2310/javascript/atoms/test/html5/shadow_dom_test.html

@43081j
Copy link
Contributor Author

43081j commented Apr 22, 2018

ah i wasn't aware you could do that, will give it a go.

but you can see the CI builds fail for seemingly unrelated reasons (to my changes). we should probably still figure that out..

@43081j
Copy link
Contributor Author

43081j commented Apr 22, 2018

those tests pass now.

though CI builds fail still, due to focus tests. they pass locally, so i guess its something to do with the fact the window isn't focused on CI builds maybe. not too sure.

also, see my comments in the code review when you have a min if you can

}
} while (elem && elem.nodeType != goog.dom.NodeType.ELEMENT);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

i removed this loop because elem was never modified, so it would either run once or forever... guess we never ended up with this being as truthy

Copy link
Member

Choose a reason for hiding this comment

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

I suspect there should be parent instead of elem, looks like a bug.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we have some recursion below to deal with traversing the tree anyway, so i guess this removal is fine.

@@ -601,7 +603,7 @@ bot.dom.isShown = function(elem, opt_ignoreOpacity) {
return false;
}
var parent = bot.dom.getParentElement(e);
return !parent || displayed(parent);
return parent && displayed(parent);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

does this reversal seem right? it seems before we were assuming a parentless element was visible. we should be assuming the opposite

Copy link
Member

Choose a reason for hiding this comment

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

Yes, it's correct, an element that has no parent is a top-level element (a body or an svg document), and it is always visible, no matter of its properties.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

an element without a parentNode could be a disconnected node.

also, as far as you go up the chain, there will be a parentNode, until it is #document.

body has a parentNode of document, and we check that elsewhere (we return true somewhere else when node === document). so i dont see a situation where we would end up with a parentless node that is still somehow part of the document.

Copy link
Contributor

Choose a reason for hiding this comment

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

This line needs to be reverted: we're walking up parent elements not nodes.

document.body.parentElement === document.documentElement
document.documentElement.parentElement === null

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we're walking up parent nodes. this calls getParentNodeInComposedDom which (as seen below) uses parentNode

Copy link
Contributor

Choose a reason for hiding this comment

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

The line immediately preceding this one:

var parent = bot.dom.getParentElement(e);

getParentElement returns the parent element or null.

Copy link
Contributor Author

@43081j 43081j Apr 23, 2018

Choose a reason for hiding this comment

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

ah my mistake, it shouldn't really be doing either to be honest. an element without a parent is more likely to be a disconnected element than document, its not the best assertion really.

we are better off asserting that it is document or not. if it isn't we do this line as is, otherwise we return true (we do this elsewhere already as part of this, but this line will run first).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

in actual fact @jleyba why do we have this branch at all? getParentNodeInComposedDom works without shadow DOM support (it checks for support internally). why not drop this else completely and use the same function?

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember the historical reasoning for parentElement vs parentNode, but checking that everything is under a document sounds reasonable to me (pending my other comments for that branch)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes and that check already happens as part of the shadow branch. no changes are needed in that case, just the else needs dropping.

if (parent.shadowRoot && node.assignedSlot !== undefined) {
// Can be null on purpose, meaning it has no parent as
// it hasn't yet been slotted
parent = node.assignedSlot ? node.assignedSlot.parentNode : null;
Copy link
Contributor

Choose a reason for hiding this comment

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

why not just

return node.assignedSlot ? node.assignedSlot.parentNode : null;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

consistency, the existing code didn't do an early return so i didn't either. if you'd rather have an early return, i'll change the other conditions below too.

@@ -601,7 +603,7 @@ bot.dom.isShown = function(elem, opt_ignoreOpacity) {
return false;
}
var parent = bot.dom.getParentElement(e);
return !parent || displayed(parent);
return parent && displayed(parent);
Copy link
Contributor

Choose a reason for hiding this comment

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

This line needs to be reverted: we're walking up parent elements not nodes.

document.body.parentElement === document.documentElement
document.documentElement.parentElement === null

return !parent || displayed(parent);
}

if (parent && (parent.nodeType == goog.dom.NodeType.DOCUMENT ||
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add some tests to check that nodes within a HTML comment are not shown

}

if (parent && (parent.nodeType == goog.dom.NodeType.DOCUMENT ||
parent.nodeType == goog.dom.NodeType.DOCUMENT_FRAGMENT)) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If you're going to require elements be attached to a document to be visible, then wouldn't anything in a document fragment (an incomplete document) not be visible?

Also, can an element be the child of CData? Are those cases handled properly? (you've opened pandora's box by touching one of the gnarliest pieces of code in Selenium...)


var parent = bot.dom.getParentNodeInComposedDom(e);

if (parent instanceof ShadowRoot) {
Copy link
Contributor

Choose a reason for hiding this comment

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

If we're going to standardize on a single definition of displayed, then this needs to be

if (bot.dom.IS_SHADOW_DOM_ENABLED && parent instanceof ShadowRoot) {

Otherwise the ShadowRoot reference will trigger an error in older browsers

@@ -601,7 +603,7 @@ bot.dom.isShown = function(elem, opt_ignoreOpacity) {
return false;
}
var parent = bot.dom.getParentElement(e);
return !parent || displayed(parent);
return parent && displayed(parent);
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't remember the historical reasoning for parentElement vs parentNode, but checking that everything is under a document sounds reasonable to me (pending my other comments for that branch)


var parent = bot.dom.getParentNodeInComposedDom(e);

if (bot.dom.IS_SHADOW_DOM_ENABLED && (parent instanceof ShadowRoot)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jleyba take a look at this now. so we no longer need an if/else as the parent node traversal already checks if shadow dom v0 or v1 is supported.

and this means now we automatically get the document/shadowRoot assertion further down and disconnected elements are falsey.

if (bot.dom.getEffectiveStyle(e, 'display') == 'none') {
// Any element with a display style equal to 'none' or that has an ancestor
// with display style equal to 'none' is not shown.
displayed = function(e) {
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: remove the var displayed; declaration above and change this to:

/**
 * @param {?Node} e
 * @return {boolean}
 */
function displayed(e) {

The extra type annotations are annoying, but helps improve the compiler's accuracy in type checking

// Any element with a display style equal to 'none' or that has an ancestor
// with display style equal to 'none' is not shown.
displayed = function(e) {
if (bot.dom.isElement(e) && bot.dom.getEffectiveStyle(e, 'display') == 'none') {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be

!bot.dom.isElement(e) || bot.dom.getEffectiveStyle(e, 'display') == 'none'

Since this function should only be called when checking the visibility of a parent, we're assuming the DOM is well-formed and e will never be a text node.

Copy link
Contributor Author

@43081j 43081j Apr 23, 2018

Choose a reason for hiding this comment

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

i would rather keep it as is to be honest. if its an element, we check the style, otherwise we continue traversing. we shouldn't really be making assumptions on what can and can't be a parent, but instead just traversing what we are given IMO.
in cases when its not an element, we continue traversing. its very simple this way with no assumptions.

@43081j 43081j changed the title add shadow dom v1 support to getVisibleText add shadow dom v1 support to DOM atoms Apr 24, 2018
@43081j
Copy link
Contributor Author

43081j commented Apr 24, 2018

@jleyba @barancev

this seems ready now, one last look over it if you could?

current CI build fails for seemingly unrelated reasons. looks like the failing tests are all focus related, i can reproduce them locally by having chrome unfocused (certain events dont fire if chrome has no focus as a whole).

@shs96c shs96c merged commit 471efd9 into SeleniumHQ:master Apr 30, 2018
@shs96c
Copy link
Member

shs96c commented Apr 30, 2018

@43081j, sorry it took so long to finish this review, but thank you! Thanks to @barancev and @jleyba for reviewing this too!

@43081j
Copy link
Contributor Author

43081j commented Apr 30, 2018

much appreciated. thanks to you all for the help, reviews, etc. this'll be of great use to a whole bunch of people

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

getVisibleText vs getElementText, regression?
5 participants