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

event should not work on iframe content (closes #1791, closes #1822 in testcafe) #1789

Merged
merged 6 commits into from Oct 15, 2018
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 10 additions & 1 deletion src/client/sandbox/event/hover.js
Expand Up @@ -2,6 +2,8 @@ import INTERNAL_ATTRS from '../../../processing/dom/internal-attributes';
import SandboxBase from '../base';
import nativeMethods from '../native-methods';
import * as domUtils from '../../utils/dom';
import * as positionUtils from '../../utils/position';
import * as browserUtils from '../../utils/browser';

export default class HoverSandbox extends SandboxBase {
constructor (listeners) {
Expand Down Expand Up @@ -56,6 +58,13 @@ export default class HoverSandbox extends SandboxBase {
return jointParent;
}

_onHover ({ target, clientX, clientY }) {
const hoverIsDisabled = browserUtils.isIE && positionUtils.shouldIgnoreMouseEventInsideIframe(target, clientX, clientY);

if (!hoverIsDisabled)
this._hover(target);
}

_hover (el) {
if (!this.hoverElementFixed && !domUtils.isShadowUIElement(el)) {
const jointParent = this._clearHoverMarkerUntilJointParent(el);
Expand All @@ -77,6 +86,6 @@ export default class HoverSandbox extends SandboxBase {
attach (window) {
super.attach(window);

this.listeners.addInternalEventListener(window, ['mouseover', 'touchstart'], e => this._hover(e.target));
this.listeners.addInternalEventListener(window, ['mouseover', 'touchstart'], e => this._onHover(e));
}
}
5 changes: 4 additions & 1 deletion src/client/sandbox/event/simulator.js
Expand Up @@ -3,7 +3,7 @@ import nativeMethods from '../native-methods';
import * as browserUtils from '../../utils/browser';
import * as domUtils from '../../utils/dom';
import * as eventUtils from '../../utils/event';
import { getOffsetPosition, offsetToClientCoords } from '../../utils/position';
import { getOffsetPosition, offsetToClientCoords, shouldIgnoreMouseEventInsideIframe } from '../../utils/position';
import { getBordersWidth } from '../../utils/style';

const TOUCH_EVENT_RADIUS = 25;
Expand Down Expand Up @@ -529,6 +529,9 @@ export default class EventSimulator {
}

_dispatchMouseRelatedEvents (el, args, dataTransfer) {
if (shouldIgnoreMouseEventInsideIframe(el, args.clientX, args.clientY) && args.type !== 'mouseover' && args.type !== 'mouseenter')
Copy link
Contributor

Choose a reason for hiding this comment

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

I think that the type conditions should be the first for performance improvement.

return true;

const pointerRegExp = /mouse(down|up|move|over|out)/;

// NOTE: In IE, submit doesn't work if a click is simulated for some submit button's children (for example,
Expand Down
20 changes: 20 additions & 0 deletions src/client/utils/position.js
Expand Up @@ -226,6 +226,26 @@ export function getElementRectangle (el) {
return rectangle;
}

export function shouldIgnoreMouseEventInsideIframe (el, x, y) {
if (domUtils.getTagName(el) !== 'iframe')
return false;

const rect = getElementRectangle(el);
const borders = styleUtils.getBordersWidth(el);
const padding = styleUtils.getElementPadding(el);

// NOTE: we detect element's 'content' position: left, right, top and bottom
// which does not consider borders and paddings, so we need to
// subtract it for right and bottom, and add for left and top

const left = rect.left + borders.left + padding.left;
const top = rect.top + borders.top + padding.top;
const right = rect.left + rect.width - borders.right - padding.right;
Copy link
Contributor

Choose a reason for hiding this comment

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

Add comment for this line

const bottom = rect.top + rect.height - borders.bottom - padding.bottom;

return x >= left && x <= right && y >= top && y <= bottom;
}

function calcOffsetPosition (el, borders, offsetPosition) {
const isSvg = domUtils.isSVGElementOrChild(el);

Expand Down
141 changes: 141 additions & 0 deletions test/client/fixtures/sandbox/event/event-test.js
@@ -1,4 +1,5 @@
var browserUtils = hammerhead.utils.browser;
var eventUtils = hammerhead.utils.event;
var nativeMethods = hammerhead.nativeMethods;
var listeners = hammerhead.sandbox.event.listeners;
var focusBlur = hammerhead.sandbox.event.focusBlur;
Expand Down Expand Up @@ -314,6 +315,17 @@ test('should not wrap invalid event handlers (GH-1251)', function () {
});

strictEqual(listeningCtx.getEventCtx(target, 'click').wrappers.length, storedHandlerWrappersCount);

// NOTE: we need to remove global event handlers before next test starts
// we need try/catch statement because we have incorrect handler object
handlers.forEach(function (handler) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Why do we need a try/catch statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It is a test for invalid event handlers. See the line above: var handlers = [void 0, 1, null, 'str', {}];. The test adds some wrong event handlers, but does not remove them, so it leads to problems with next texts. I've added code which removes handlers in the same manner as code which adds handlers

try {
nativeMethods.windowRemoveEventListener.call(target, 'click', handler);
}
catch (e) {
//
}
});
};

testHandlers(window);
Expand All @@ -336,3 +348,132 @@ test('event.preventDefault call should change the event.defaultPrevented propert

eventSimulator.keydown(input);
});

asyncTest('mouse events in iframe', function () {
return createTestIframe()
.then(function (iframe) {
iframe.style.width = '300px';
iframe.style.height = '100px';
iframe.style.border = '5px solid black';
iframe.style.padding = '20px';
iframe.style.backgroundColor = 'yellow';

var div = document.createElement('div');

div.style.display = 'inline-block';

div.appendChild(iframe);
document.body.appendChild(div);

var actualEvents = [];

var simulatorMethods = [
'mousedown',
'mouseup',
'mousemove',
'mouseover',
'mouseenter',
'click',
'dblclick',
'contextmenu'
];

var allEvents = [
'pointerdown',
'mousedown',
'pointerup',
'mouseup',
'pointermove',
'mousemove',
'pointerover',
'mouseover',
'mouseenter',
'click',
'dblclick',
'contextmenu'
];

var eventsInsideFrame = ['pointerover', 'mouseover', 'mouseenter'];

if (!eventUtils.hasPointerEvents) {
const pointerRegExp = /pointer(down|up|move|over)/;

allEvents = allEvents.filter(function (eventName) {
return !pointerRegExp.test(eventName);
});

eventsInsideFrame = eventsInsideFrame.filter(function (eventName) {
return !pointerRegExp.test(eventName);
});
}

var getHandler = function (i) {
return function () {
actualEvents.push(allEvents[i]);
};
};

for (var i = 0; i < allEvents.length; i++)
iframe.addEventListener(allEvents[i], getHandler(i));

for (i = 0; i < simulatorMethods.length; i++)
eventSimulator[simulatorMethods[i]](iframe, { clientX: 190, clientY: 130 });

deepEqual(actualEvents, eventsInsideFrame);

actualEvents = [];

for (i = 0; i < simulatorMethods.length; i++)
eventSimulator[simulatorMethods[i]](iframe, { clientX: 190, clientY: 70 });

deepEqual(actualEvents, allEvents);

document.body.removeChild(div);

start();
});

});

asyncTest('hover style in iframe', function () {
return createTestIframe()
.then(function (iframe) {
var style = document.createElement('style');
var div = document.createElement('div');

div.style.display = 'inline-block';
style.innerHTML = 'iframe: hover { background-color: blue!important; }';

document.body.appendChild(style);
document.body.appendChild(div);

iframe.style.width = '300px';
iframe.style.height = '100px';
iframe.style.border = '5px solid black';
iframe.style.padding = '20px';
iframe.style.backgroundColor = 'yellow';

div.appendChild(iframe);

var initialBackgroundColor = window.getComputedStyle(iframe).backgroundColor;

eventSimulator.mouseover(iframe, { clientX: 190, clientY: 130 });

if (browserUtils.isIE)
equal(window.getComputedStyle(iframe).backgroundColor, initialBackgroundColor);
else
notEqual(window.getComputedStyle(iframe).backgroundColor, initialBackgroundColor);

eventSimulator.mouseover(div, { clientX: 0, clientY: 0 });
equal(window.getComputedStyle(iframe).backgroundColor, initialBackgroundColor);

eventSimulator.mouseover(iframe, { clientX: 190, clientY: 70 });
notEqual(window.getComputedStyle(iframe).backgroundColor, initialBackgroundColor);

document.body.removeChild(div);
document.body.removeChild(style);

start();
});

});
51 changes: 50 additions & 1 deletion test/client/fixtures/utils/position-test.js
@@ -1,6 +1,6 @@
var positionUtils = hammerhead.utils.position;

var TEST_DIV_SELECTOR = '#testDiv';
var TEST_DIV_SELECTOR = '#testDiv';

QUnit.testStart(function () {
if (!$(TEST_DIV_SELECTOR).length)
Expand Down Expand Up @@ -39,3 +39,52 @@ test('`getOffsetPosition` with `roundFn` arg', function () {
notDeepEqual(offsetPositionCeil, offsetPositionRound);
strictEqual(elementInPoint, child);
});

asyncTest('`shouldIgnoreMouseEventInsideIframe`', function () {
return createTestIframe().then(function (iframe) {
var testDiv = document.querySelector(TEST_DIV_SELECTOR);

iframe.style.width = '300px';
iframe.style.height = '100px';

var borderWidth = 5;
var paddingWidth = 10;

iframe.style.border = borderWidth + 'px solid black';
iframe.style.padding = paddingWidth + 'px';

testDiv.appendChild(iframe);

var rect = positionUtils.getElementRectangle(iframe);

rect.right = rect.left + rect.width;
rect.bottom = rect.top + rect.height;

notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.top));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.bottom));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.top));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.bottom));

rect.left += borderWidth + paddingWidth;
rect.right -= borderWidth + paddingWidth;
rect.top += borderWidth + paddingWidth;
rect.bottom -= borderWidth + paddingWidth;

ok(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.top));
ok(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.bottom));
ok(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.top));
ok(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.bottom));

rect.left -= 1;
rect.right += 1;
rect.top -= 1;
rect.bottom += 1;

notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.top));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.left, rect.bottom));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.top));
notOk(positionUtils.shouldIgnoreMouseEventInsideIframe(iframe, rect.right, rect.bottom));

start();
});
});