summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/shadow-dom/focus-navigation/resources
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/shadow-dom/focus-navigation/resources')
-rw-r--r--testing/web-platform/tests/shadow-dom/focus-navigation/resources/focus-utils.js164
-rw-r--r--testing/web-platform/tests/shadow-dom/focus-navigation/resources/shadow-dom.js165
2 files changed, 329 insertions, 0 deletions
diff --git a/testing/web-platform/tests/shadow-dom/focus-navigation/resources/focus-utils.js b/testing/web-platform/tests/shadow-dom/focus-navigation/resources/focus-utils.js
new file mode 100644
index 0000000000..f593267cc3
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/focus-navigation/resources/focus-utils.js
@@ -0,0 +1,164 @@
+'use strict';
+
+function navigateFocusForward() {
+ // TAB = '\ue004'
+ return test_driver.send_keys(document.body, "\ue004");
+}
+
+async function navigateFocusBackward() {
+ return new test_driver.Actions()
+ .keyDown('\uE050')
+ .keyDown('\uE004')
+ .keyUp('\uE004')
+ .keyUp('\uE050')
+ .send();
+}
+
+// If shadow root is open, can find element using element path
+// If shadow root is open, can find the shadowRoot from the element
+
+function innermostActiveElement(element) {
+ element = element || document.activeElement;
+ if (isIFrameElement(element)) {
+ if (element.contentDocument.activeElement)
+ return innermostActiveElement(element.contentDocument.activeElement);
+ return element;
+ }
+ if (isShadowHost(element)) {
+ let shadowRoot = element.shadowRoot;
+ if (shadowRoot) {
+ if (shadowRoot.activeElement)
+ return innermostActiveElement(shadowRoot.activeElement);
+ }
+ }
+ return element;
+}
+
+function isInnermostActiveElement(path) {
+ const element = getNodeInComposedTree(path);
+ if (!element)
+ return false;
+ return element === innermostActiveElement();
+}
+
+async function shouldNavigateFocus(fromElement, direction) {
+ if (!fromElement)
+ return false;
+
+ fromElement.focus();
+ if (fromElement !== innermostActiveElement())
+ return false;
+
+ if (direction == 'forward')
+ await navigateFocusForward();
+ else
+ await navigateFocusBackward();
+
+ return true;
+}
+
+async function assert_focus_navigation_element(fromPath, toPath, direction) {
+ const fromElement = getNodeInComposedTree(fromPath);
+ const result = await shouldNavigateFocus(fromElement, direction);
+ assert_true(result, 'Failed to focus ' + fromPath);
+
+ const message =
+ `Focus should move ${direction} from ${fromPath} to ${toPath}`;
+ const toElement = getNodeInComposedTree(toPath);
+ assert_equals(innermostActiveElement(), toElement, message);
+}
+
+async function assert_focus_navigation_elements(elements, direction) {
+ assert_true(
+ elements.length >= 2,
+ 'length of elements should be greater than or equal to 2.');
+ for (var i = 0; i + 1 < elements.length; ++i)
+ await assert_focus_navigation_element(elements[i], elements[i + 1], direction);
+
+}
+
+async function assert_focus_navigation_forward(elements) {
+ return assert_focus_navigation_elements(elements, 'forward');
+}
+
+async function assert_focus_navigation_backward(elements) {
+ return assert_focus_navigation_elements(elements, 'backward');
+}
+
+async function assert_focus_navigation_bidirectional(elements) {
+ await assert_focus_navigation_forward(elements);
+ elements.reverse();
+ await assert_focus_navigation_backward(elements);
+}
+
+
+// If shadow root is closed, need to pass shadowRoot and element to find
+// innermost active element
+
+function isShadowHostOfRoot(shadowRoot, node) {
+ return shadowRoot && shadowRoot.host.isEqualNode(node);
+}
+
+function innermostActiveElementWithShadowRoot(shadowRoot, element) {
+ element = element || document.activeElement;
+ if (isIFrameElement(element)) {
+ if (element.contentDocument.activeElement)
+ return innermostActiveElementWithShadowRoot(shadowRoot, element.contentDocument.activeElement);
+ return element;
+ }
+ if (isShadowHostOfRoot(shadowRoot, element)) {
+ if (shadowRoot.activeElement)
+ return innermostActiveElementWithShadowRoot(shadowRoot, shadowRoot.activeElement);
+ }
+ return element;
+}
+
+async function shouldNavigateFocusWithShadowRoot(from, direction) {
+ const [fromElement, shadowRoot] = from;
+ if (!fromElement)
+ return false;
+
+ fromElement.focus();
+ if (fromElement !== innermostActiveElementWithShadowRoot(shadowRoot))
+ return false;
+
+ if (direction == 'forward')
+ await navigateFocusForward();
+ else
+ await navigateFocusBackward();
+
+ return true;
+}
+
+async function assert_focus_navigation_element_with_shadow_root(from, to, direction) {
+ const result = await shouldNavigateFocusWithShadowRoot(from, direction);
+ const [fromElement] = from;
+ const [toElement, toShadowRoot] = to;
+ assert_true(result, 'Failed to focus ' + fromElement.id);
+ const message =
+ `Focus should move ${direction} from ${fromElement.id} to ${toElement.id}`;
+ assert_equals(innermostActiveElementWithShadowRoot(toShadowRoot), toElement, message);
+}
+
+async function assert_focus_navigation_elements_with_shadow_root(elements, direction) {
+ assert_true(
+ elements.length >= 2,
+ 'length of elements should be greater than or equal to 2.');
+ for (var i = 0; i + 1 < elements.length; ++i)
+ await assert_focus_navigation_element_with_shadow_root(elements[i], elements[i + 1], direction);
+}
+
+async function assert_focus_navigation_forward_with_shadow_root(elements) {
+ return assert_focus_navigation_elements_with_shadow_root(elements, 'forward');
+}
+
+async function assert_focus_navigation_backward_with_shadow_root(elements) {
+ return assert_focus_navigation_elements_with_shadow_root(elements, 'backward');
+}
+
+async function assert_focus_navigation_bidirectional_with_shadow_root(elements) {
+ await assert_focus_navigation_forward_with_shadow_root(elements);
+ elements.reverse();
+ await assert_focus_navigation_backward_with_shadow_root(elements);
+}
+
diff --git a/testing/web-platform/tests/shadow-dom/focus-navigation/resources/shadow-dom.js b/testing/web-platform/tests/shadow-dom/focus-navigation/resources/shadow-dom.js
new file mode 100644
index 0000000000..2789f05850
--- /dev/null
+++ b/testing/web-platform/tests/shadow-dom/focus-navigation/resources/shadow-dom.js
@@ -0,0 +1,165 @@
+function removeWhiteSpaceOnlyTextNodes(node) {
+ for (var i = 0; i < node.childNodes.length; i++) {
+ var child = node.childNodes[i];
+ if (child.nodeType === Node.TEXT_NODE &&
+ child.nodeValue.trim().length == 0) {
+ node.removeChild(child);
+ i--;
+ } else if (
+ child.nodeType === Node.ELEMENT_NODE ||
+ child.nodeType === Node.DOCUMENT_FRAGMENT_NODE) {
+ removeWhiteSpaceOnlyTextNodes(child);
+ }
+ }
+ if (node.shadowRoot) {
+ removeWhiteSpaceOnlyTextNodes(node.shadowRoot);
+ }
+}
+
+function convertTemplatesToShadowRootsWithin(node) {
+ var nodes = node.querySelectorAll('template');
+ for (var i = 0; i < nodes.length; ++i) {
+ var template = nodes[i];
+ var mode = template.getAttribute('data-mode');
+ var delegatesFocus = template.hasAttribute('data-delegatesFocus');
+ var parent = template.parentNode;
+ parent.removeChild(template);
+ var shadowRoot;
+ if (!mode) {
+ shadowRoot = parent.attachShadow({ mode: 'open' });
+ } else {
+ shadowRoot =
+ parent.attachShadow({ 'mode': mode, 'delegatesFocus': delegatesFocus });
+ }
+ var expose = template.getAttribute('data-expose-as');
+ if (expose)
+ window[expose] = shadowRoot;
+ if (template.id)
+ shadowRoot.id = template.id;
+ var fragments = document.importNode(template.content, true);
+ shadowRoot.appendChild(fragments);
+
+ convertTemplatesToShadowRootsWithin(shadowRoot);
+ }
+}
+
+function isShadowHost(node) {
+ return node && node.nodeType == Node.ELEMENT_NODE && node.shadowRoot;
+}
+
+function isIFrameElement(element) {
+ return element && element.nodeName == 'IFRAME';
+}
+
+// Returns node from shadow/iframe tree "path".
+function getNodeInComposedTree(path) {
+ var ids = path.split('/');
+ var node = document.getElementById(ids[0]);
+ for (var i = 1; node != null && i < ids.length; ++i) {
+ if (isIFrameElement(node))
+ node = node.contentDocument.getElementById(ids[i]);
+ else if (isShadowHost(node))
+ node = node.shadowRoot.getElementById(ids[i]);
+ else
+ return null;
+ }
+ return node;
+}
+
+function createTestTree(node) {
+ let ids = {};
+
+ function attachShadowFromTemplate(template) {
+ let parent = template.parentNode;
+ parent.removeChild(template);
+ let shadowRoot;
+ if (template.getAttribute('data-slot-assignment') === 'manual') {
+ shadowRoot =
+ parent.attachShadow({
+ mode: template.getAttribute('data-mode'),
+ slotAssignment: 'manual'
+ });
+ } else {
+ shadowRoot =
+ parent.attachShadow({ mode: template.getAttribute('data-mode') });
+ }
+ let id = template.id;
+ if (id) {
+ shadowRoot.id = id;
+ ids[id] = shadowRoot;
+ }
+ shadowRoot.appendChild(document.importNode(template.content, true));
+ return shadowRoot;
+ }
+
+ function walk(root) {
+ if (root.id) {
+ ids[root.id] = root;
+ }
+ for (let e of Array.from(root.querySelectorAll('[id]'))) {
+ ids[e.id] = e;
+ }
+ for (let e of Array.from(root.querySelectorAll('template'))) {
+ walk(attachShadowFromTemplate(e));
+ }
+ }
+
+ walk(node.cloneNode(true));
+ return ids;
+}
+
+function dispatchEventWithLog(nodes, target, event) {
+ function labelFor(e) {
+ return e.id || e.tagName;
+ }
+
+ let log = [];
+ let attachedNodes = [];
+ for (let label in nodes) {
+ let startingNode = nodes[label];
+ for (let node = startingNode; node; node = node.parentNode) {
+ if (attachedNodes.indexOf(node) >= 0)
+ continue;
+ let id = node.id;
+ if (!id)
+ continue;
+ attachedNodes.push(node);
+ node.addEventListener(event.type, (e) => {
+ // Record [currentTarget, target, relatedTarget, composedPath()]
+ log.push([
+ id, labelFor(e.target),
+ e.relatedTarget ? labelFor(e.relatedTarget) : null,
+ e.composedPath().map((n) => {
+ return labelFor(n);
+ })
+ ]);
+ });
+ }
+ }
+ target.dispatchEvent(event);
+ return log;
+}
+
+// This function assumes that testharness.js is available.
+function assert_event_path_equals(actual, expected) {
+ assert_equals(actual.length, expected.length);
+ for (let i = 0; i < actual.length; ++i) {
+ assert_equals(
+ actual[i][0], expected[i][0],
+ 'currentTarget at ' + i + ' should be same');
+ assert_equals(
+ actual[i][1], expected[i][1], 'target at ' + i + ' should be same');
+ assert_equals(
+ actual[i][2], expected[i][2],
+ 'relatedTarget at ' + i + ' should be same');
+ assert_array_equals(
+ actual[i][3], expected[i][3],
+ 'composedPath at ' + i + ' should be same');
+ }
+}
+
+function assert_background_color(path, color) {
+ assert_equals(
+ window.getComputedStyle(getNodeInComposedTree(path)).backgroundColor,
+ color, 'backgroundColor for ' + path + ' should be ' + color);
+}