blob: 11614ee9c51bafec2d19ca7e1fd001bcdf24f2e2 [file] [log] [blame]
<!DOCTYPE html>
<html>
<head>
<script src="../../../resources/js-test.js"></script>
<script src="resources/shadow-dom.js"></script>
</head>
<body>
<p id="description"></p>
<div id="sandbox"></div>
<pre id="console"></pre>
<script>
description("Tests to ensure that shadow DOM boundary is not crossed during event propagation. Can only run within DRT.");
function moveMouseOver(element)
{
if (!window.eventSender || !window.internals)
return;
var defaultPaddingSize = 20;
var x = element.offsetLeft + element.offsetWidth / 2;
var y;
if (element.hasChildNodes() || window.internals.shadowRoot(element))
y = element.offsetTop + defaultPaddingSize;
else
y = element.offsetTop + element.offsetHeight / 2;
eventSender.mouseMoveTo(x, y);
}
var eventRecords = {};
function clearEventRecords()
{
eventRecords = {};
}
function dispatchedEvent(eventType)
{
var events = eventRecords[eventType];
if (!events)
return [];
return events;
}
function recordEvent(event)
{
var eventType = event.type
if (!eventRecords[eventType]) {
eventRecords[eventType] = []
}
// Records each event in the following format per event type:
// eventRecords[eventType] = ['target.id(<-relatedTarget.id)(@currentTarget.id)',,,]
// * RelatedTarget and currentTarget may be omitted if they are not defined.
// A new event is pushed back to the array of its event type.
var eventString = '';
eventString += event.target.id;
if (event.relatedTarget)
eventString += '(<-' + event.relatedTarget.id + ')';
if (event.currentTarget)
eventString += '(@' + event.currentTarget.id + ')';
if (event.eventPhase == 1)
eventString += '(capturing phase)';
if (event.target && event.currentTarget && event.target.id == event.currentTarget.id)
shouldBe("event.eventPhase", "2", true);
eventRecords[eventType].push(eventString);
}
function prepareDOMTree(parent)
{
parent.appendChild(
createDOM('div', {'id': 'divA', 'style': 'padding-top: 40px'},
createDOM('div', {'id': 'divB', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
createDOM('div', {'id': 'divC', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
createDOM('div', {'id': 'shadowD', 'style': 'padding-top: 40px'},
createShadowRoot(
createDOM('div', {'id': 'divE', 'style': 'padding-top: 40px'},
createDOM('div', {'id': 'shadowF', 'style': 'padding-top: 40px'},
createShadowRoot(
createDOM('div', {'id': 'shadowG', 'style': 'padding-top: 40px'},
createShadowRoot(
createDOM('div', {'id': 'divH', 'style': 'width: 40px; height: 40px', 'tabindex': 0}),
createDOM('div', {'id': 'divI', 'style': 'width: 40px; height: 40px', 'tabindex': 0})))))),
createDOM('div', {'id': 'divJ', 'style': 'padding-top: 40px'},
createDOM('div', {'id': 'shadowK', 'style': 'padding-top: 40px'},
createShadowRoot(
createDOM('div', {'id': 'divL', 'style': 'width: 40px; height: 40px', 'tabindex': 0}))))))));
var ids = ['divA', 'divB', 'divC',
'shadowD', 'shadowD/divE', 'shadowD/shadowF', 'shadowD/shadowF/shadowG',
'shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
'shadowD/divJ', 'shadowD/shadowK', 'shadowD/shadowK/divL'];
for (var i = 0; i < ids.length; ++i) {
var element = getNodeInComposedTree(ids[i]);
element.addEventListener('mouseover', recordEvent, false);
element.addEventListener('mouseout', recordEvent, false);
element.addEventListener('focusin', recordEvent, false);
element.addEventListener('focusout', recordEvent, false);
element.addEventListener('focus', recordEvent, true); // capturing phase
element.addEventListener('blur', recordEvent, true); // capturing phase
}
}
function moveMouse(oldElementId, newElementId, message)
{
debug('\n' + message + '\n' + 'Moving mouse from ' + oldElementId + ' to ' + newElementId);
moveMouseOver(getNodeInComposedTree(oldElementId));
clearEventRecords();
moveMouseOver(getNodeInComposedTree(newElementId));
}
function moveFocus(oldElementId, newElementId, message)
{
debug('\n' + message + '\n' + 'Moving focus from ' + oldElementId + ' to ' + newElementId);
getNodeInComposedTree(oldElementId).focus();
clearEventRecords();
getNodeInComposedTree(newElementId).focus();
}
function test()
{
prepareDOMTree(document.getElementById('sandbox'));
// Test for mouseover/mouseout events.
moveMouse('divB', 'divC',
'Move mouse from a node to its sibling node. All nodes are outside of shadow boundary.');
shouldBe('dispatchedEvent("mouseover")', '["divC(<-divB)(@divC)", "divC(<-divB)(@divA)"]');
shouldBe('dispatchedEvent("mouseout")', '["divB(<-divC)(@divB)", "divB(<-divC)(@divA)"]');
moveMouse('divB', 'divA',
'Target is an ancestor of relatedTarget. All nodes are outside of shadow boundary.');
shouldBe('dispatchedEvent("mouseover")', '["divA(<-divB)(@divA)"]');
shouldBe('dispatchedEvent("mouseout")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
moveMouse('divA', 'divB',
'RelatedTarget is an ancestor of target. All nodes are outside of shadow boundary.');
shouldBe('dispatchedEvent("mouseover")', '["divB(<-divA)(@divB)", "divB(<-divA)(@divA)"]');
shouldBe('dispatchedEvent("mouseout")', '["divA(<-divB)(@divA)"]');
moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowF/shadowG/divI',
'Both target and relatedTarget are immediate children of the same shadow root.');
shouldBe('dispatchedEvent("mouseover")', '["divI(<-divH)(@divI)"]');
shouldBe('dispatchedEvent("mouseout")', '["divH(<-divI)(@divH)"]');
moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/divE',
'Target is an ancestor of relatedTarget.');
shouldBe('dispatchedEvent("mouseover")', '["divE(<-shadowF)(@divE)"]');
shouldBe('dispatchedEvent("mouseout")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
moveMouse('shadowD/shadowF/shadowG/divI', 'shadowD/shadowF',
'Target (shadow host) is an ancestor of relatedTarget.');
shouldBe('dispatchedEvent("mouseover")', '[]');
shouldBe('dispatchedEvent("mouseout")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
moveMouse('shadowD/shadowF/shadowG', 'shadowD',
'Target (shadow host) is an ancestor of relatedTarget (shadow host).');
shouldBe('dispatchedEvent("mouseover")', '[]');
shouldBe('dispatchedEvent("mouseout")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
moveMouse('shadowD/divE', 'shadowD/shadowF/shadowG/divI',
'RelatedTarget is ancestor of target.');
shouldBe('dispatchedEvent("mouseover")', '["divI(<-divE)(@divI)", "shadowG(<-divE)(@shadowG)", "shadowF(<-divE)(@shadowF)", "shadowF(<-divE)(@divE)"]');
shouldBe('dispatchedEvent("mouseout")', '["divE(<-shadowF)(@divE)"]');
moveMouse('shadowD/shadowF', 'shadowD/shadowF/shadowG/divI',
'RelatedTarget (shadow host) is ancestor of target.');
shouldBe('dispatchedEvent("mouseover")', '["divI(<-shadowF)(@divI)", "shadowG(<-shadowF)(@shadowG)"]');
shouldBe('dispatchedEvent("mouseout")', '[]');
moveMouse('shadowD', 'shadowD/shadowF/shadowG',
'RelatedTarget (shadow host) is an ancestor of target (shadow host).');
shouldBe('dispatchedEvent("mouseover")', '["shadowG(<-shadowD)(@shadowG)", "shadowF(<-shadowD)(@shadowF)", "shadowF(<-shadowD)(@divE)"]');
shouldBe('dispatchedEvent("mouseout")', '[]');
moveMouse('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
'Target and relatedTarget exist in separated subtree, crossing shadow boundaries. Making sure that event is not dispatched beyond the lowest common boundary.');
shouldBe('dispatchedEvent("mouseover")', '["divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)", "shadowK(<-shadowF)(@divJ)"]');
shouldBe('dispatchedEvent("mouseout")', '["divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)", "shadowF(<-shadowK)(@divE)"]');
// Test for focusin/focusout events.
moveFocus('divB', 'divC',
'Move focus from a node to its sibling node. All nodes are outside of shadow boundary.');
shouldBe('dispatchedEvent("focusin")', '["divC(<-divB)(@divC)", "divC(<-divB)(@divA)"]');
shouldBe('dispatchedEvent("focusout")', '["divB(<-divC)(@divB)", "divB(<-divC)(@divA)"]');
moveFocus('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
'Old focused node and new focused node exist in separated subtrees, crossing shadow boundaries. Making sure that an event is not dispatched beyond the lowest common boundary.');
shouldBe('dispatchedEvent("focusin")', '["divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)", "shadowK(<-shadowF)(@divJ)"]');
shouldBe('dispatchedEvent("focusout")', '["divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)", "shadowF(<-shadowK)(@divE)"]');
// Omitted test cases where either a oldFocusedNode or newFocusedNode is an ancestor of the other.
// Due to a focus transfer mechanism on shadow hosts, a focused node should be a leaf node in general.
// Test for focus/blur events. Event listners should be registerd on captureing phase.
moveFocus('divB', 'divC',
'Move focus from a node to its sibling node. All nodes are outside of shadow boundary.');
shouldBe('dispatchedEvent("focus")', '["divC(<-divB)(@divA)(capturing phase)", "divC(<-divB)(@divC)"]');
shouldBe('dispatchedEvent("blur")', '["divB(<-divC)(@divA)(capturing phase)", "divB(<-divC)(@divB)"]');
moveFocus('shadowD/shadowF/shadowG/divH', 'shadowD/shadowK/divL',
'Old focused node and new focused node exist in separated subtrees, crossing shadow boundaries. Making sure that an event is not dispatched beyond the lowest common boundary.');
shouldBe('dispatchedEvent("focus")', '["shadowK(<-shadowF)(@divJ)(capturing phase)", "divL(<-shadowF)(@divL)", "shadowK(<-shadowF)(@shadowK)"]');
shouldBe('dispatchedEvent("blur")', '["shadowF(<-shadowK)(@divE)(capturing phase)", "divH(<-shadowK)(@divH)", "shadowG(<-shadowK)(@shadowG)", "shadowF(<-shadowK)(@shadowF)"]');
}
test();
</script>
</body>
</html>