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

Tap doesn't set hoverPseudoClass on an element in Android (close #294) #309

Merged
merged 1 commit into from
Dec 2, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ class Hammerhead {

this.eventSandbox = {
listeners: this.sandbox.event.listeners,
hover: this.sandbox.event.hover,
focusBlur: this.sandbox.event.focusBlur,
elementEditingWatcher: this.sandbox.event.elementEditingWatcher,
eventSimulator: this.sandbox.event.eventSimulator,
Expand Down
69 changes: 0 additions & 69 deletions src/client/sandbox/event/focus-blur.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@ export default class FocusBlurSandbox extends SandboxBase {

this.shouldDisableOuterFocusHandlers = false;
this.topWindow = null;
this.hoverElementFixed = false;
this.lastHoveredElement = null;
this.lastFocusedElement = null;

this.eventSimulator = eventSimulator;
Expand Down Expand Up @@ -48,63 +46,6 @@ export default class FocusBlurSandbox extends SandboxBase {
styleUtils.setScrollTop(el, scroll.top);
}

_onMouseOverHandler (e) {
if (this.hoverElementFixed || domUtils.isShadowUIElement(e.target))
return;

// NOTE: In this method, we go up to the tree of elements and look for a joint parent for the
// previous and new hovered elements. Processing is needed only until that parent is found.
// In this case, we’ll reduce the number of dom calls.
var clearHoverMarkerUntilJointParent = newHoveredElement => {
var jointParent = null;

if (this.lastHoveredElement) {
var el = this.lastHoveredElement;

while (el && el.tagName) {
// NOTE: Check that the current element is a joint parent for the hovered elements.
if (el.contains && !el.contains(newHoveredElement)) {
nativeMethods.removeAttribute.call(el, INTERNAL_ATTRS.hoverPseudoClass);
el = el.parentNode;
}
else
break;
}

jointParent = el;

if (jointParent)
nativeMethods.removeAttribute.call(jointParent, INTERNAL_ATTRS.hoverPseudoClass);
}

return jointParent;
};

var setHoverMarker = (newHoveredElement, jointParent) => {
if (jointParent)
nativeMethods.setAttribute.call(jointParent, INTERNAL_ATTRS.hoverPseudoClass, '');

while (newHoveredElement && newHoveredElement.tagName) {
// NOTE: Assign a pseudo-class marker to the elements until the joint parent is found.
if (newHoveredElement !== jointParent) {
nativeMethods.setAttribute.call(newHoveredElement, INTERNAL_ATTRS.hoverPseudoClass, '');
newHoveredElement = newHoveredElement.parentNode;
}
else
break;
}
};

var jointParent = clearHoverMarkerUntilJointParent(e.target);

setHoverMarker(e.target, jointParent);
}

_onMouseOut (e) {
if (!domUtils.isShadowUIElement(e.target))
this.lastHoveredElement = e.target;
}

_onChangeActiveElement (activeElement) {
if (this.lastFocusedElement === activeElement)
return;
Expand Down Expand Up @@ -242,8 +183,6 @@ export default class FocusBlurSandbox extends SandboxBase {
this.activeWindowTracker.attach(window);
this.topWindow = domUtils.isCrossDomainWindows(window, window.top) ? window : window.top;

this.listeners.addInternalEventListener(window, ['mouseover'], e => this._onMouseOverHandler(e));
this.listeners.addInternalEventListener(window, ['mouseout'], e => this._onMouseOut(e));
this.listeners.addInternalEventListener(window, ['focus', 'blur'], () => this._onChangeActiveElement(this.document.activeElement));
}

Expand Down Expand Up @@ -375,14 +314,6 @@ export default class FocusBlurSandbox extends SandboxBase {
this.shouldDisableOuterFocusHandlers = false;
}

fixHoveredElement () {
this.hoverElementFixed = true;
}

freeHoveredElement () {
this.hoverElementFixed = false;
}

blur (el, callback, withoutHandlers, isNativeBlur) {
var activeElement = domUtils.getActiveElement(domUtils.findDocument(el));
// NOTE: In IE, if you call the focus() or blur() method from script, an active element is changed
Expand Down
82 changes: 82 additions & 0 deletions src/client/sandbox/event/hover.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import INTERNAL_ATTRS from '../../../processing/dom/internal-attributes';
import SandboxBase from '../base';
import nativeMethods from '../native-methods';
import * as domUtils from '../../utils/dom';

export default class HoverSandbox extends SandboxBase {
constructor (listeners) {
super();

this.listeners = listeners;

this.hoverElementFixed = false;
this.lastHoveredElement = null;
}

static _setHoverMarker (newHoveredElement, jointParent) {
if (jointParent)
nativeMethods.setAttribute.call(jointParent, INTERNAL_ATTRS.hoverPseudoClass, '');

while (newHoveredElement && newHoveredElement.tagName) {
// NOTE: Assign a pseudo-class marker to the elements until the joint parent is found.
if (newHoveredElement !== jointParent) {
nativeMethods.setAttribute.call(newHoveredElement, INTERNAL_ATTRS.hoverPseudoClass, '');
newHoveredElement = newHoveredElement.parentNode;
}
else
break;
}
}

// NOTE: In this method, we go up to the tree of elements and look for a joint parent for the
// previous and new hovered elements. Processing is needed only until that parent is found.
// In this case, we�ll reduce the number of dom calls.
_clearHoverMarkerUntilJointParent (newHoveredElement) {
var jointParent = null;

if (this.lastHoveredElement) {
var el = this.lastHoveredElement;

while (el && el.tagName) {
// NOTE: Check that the current element is a joint parent for the hovered elements.
if (el.contains && !el.contains(newHoveredElement)) {
nativeMethods.removeAttribute.call(el, INTERNAL_ATTRS.hoverPseudoClass);
el = el.parentNode;
}
else
break;
}

jointParent = el;

if (jointParent)
nativeMethods.removeAttribute.call(jointParent, INTERNAL_ATTRS.hoverPseudoClass);
}

return jointParent;
}

_hover (el) {
if (!this.hoverElementFixed && !domUtils.isShadowUIElement(el)) {
var jointParent = this._clearHoverMarkerUntilJointParent(el);

HoverSandbox._setHoverMarker(el, jointParent);

this.lastHoveredElement = el;
}
}

fixHoveredElement () {
this.hoverElementFixed = true;
}

freeHoveredElement () {
this.hoverElementFixed = false;
}

attach (window) {
super.attach(window);

this.listeners.addInternalEventListener(window, ['mouseover', 'touchstart'], e => this._hover(e.target));
}
}
3 changes: 3 additions & 0 deletions src/client/sandbox/event/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import FocusBlurSandbox from './focus-blur';
import HoverSandbox from './hover';
import Listeners from './listeners';
import Selection from './selection';
import SandboxBase from '../base';
Expand All @@ -22,6 +23,7 @@ export default class EventSandbox extends SandboxBase {
this.eventSimulator = eventSimulator;
this.focusBlur = new FocusBlurSandbox(listeners, eventSimulator, messageSandbox, shadowUI, timerSandbox, elementEditingWatcher);
this.selection = new Selection(this);
this.hover = new HoverSandbox(listeners);
this.shadowUI = shadowUI;
this.message = messageSandbox;

Expand Down Expand Up @@ -189,6 +191,7 @@ export default class EventSandbox extends SandboxBase {
this.message.attach(window);
this.timers.attach(window);
this.focusBlur.attach(window);
this.hover.attach(window);
}

overrideElement (el, overridePrototypeMeths) {
Expand Down
104 changes: 0 additions & 104 deletions test/client/fixtures/sandbox/event/event-test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
var INTERNAL_ATTRS = hammerhead.get('../processing/dom/internal-attributes');

var browserUtils = hammerhead.utils.browser;
var nativeMethods = hammerhead.nativeMethods;
var iframeSandbox = hammerhead.sandbox.iframe;
Expand All @@ -17,79 +15,6 @@ QUnit.testDone(function () {
iframeSandbox.off(iframeSandbox.IFRAME_READY_TO_INIT_EVENT, initIframeTestHandler);
});

var lastHovered = null;

function hoverElement (el) {
if (lastHovered)
dispatchMouseEvent(lastHovered, 'mouseout');
dispatchMouseEvent(el, 'mouseover');
lastHovered = el;
}

function dispatchMouseEvent (el, type) {
var evt = null;
var e = {
bubbles: true,
cancelable: true,
view: window,
detail: 0,
ctrlKey: false,
altKey: false,
shiftKey: false,
metaKey: false,
button: 0,
relatedTarget: void 0
};

if (document.createEvent) {
evt = document.createEvent('MouseEvents');
evt.initMouseEvent(type, e.bubbles, e.cancelable, e.view, e.detail, e.screenX, e.screenY,
e.clientX, e.clientY, e.ctrlKey, e.altKey, e.shiftKey, e.metaKey, e.button, document.body.parentNode);
}
else if (document.createEventObject) {
evt = document.createEventObject();
for (var prop in e)
evt[prop] = e[prop];
evt.button = { 0: 1, 1: 4, 2: 2 }[evt.button] || evt.button;
}

if (el.dispatchEvent)
nativeMethods.dispatchEvent.call(el, evt);
else if (el.fireEvent)
nativeMethods.fireEvent.call(el, 'on' + type, evt);

lastHovered = el;
}

function isHovered (el) {
return el.getAttribute(INTERNAL_ATTRS.hoverPseudoClass) === '';
}

if (!browserUtils.hasTouchEvents) {
test('hover pseudo class', function () {
var $parent = $('<div style="width:100px; height:100px; background-color: Red" class="parent">').appendTo($('body'));
var $child = $('<div style="width:50px; height:50px; background-color: Blue" class="child">').appendTo($parent);

ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

hoverElement($parent[0]);
ok(isHovered($parent[0]));
ok(!isHovered($child[0]));

hoverElement($child[0]);
ok(isHovered($parent[0]));
ok(isHovered($child[0]));

hoverElement($('body')[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

$parent.remove();
$child.remove();
});
}

if (!browserUtils.isIE9) {
asyncTest('override setTimeout error (T203986)', function () {
var str = 'success';
Expand Down Expand Up @@ -248,35 +173,6 @@ test('firing and dispatching the events created in different ways (Q532574)', fu
$div.remove();
});

if (!browserUtils.hasTouchEvents) {
test('focusBlur.fixHoveredElement, focusBlur.freeHoveredElement (B254111)', function () {
var $parent = $('<div style="width:100px; height:100px; background-color: Red" class="parent">').appendTo($('body'));
var $child = $('<div style="width:50px; height:50px; background-color: Blue" class="child">').appendTo($parent);

ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

focusBlur.fixHoveredElement();

hoverElement($parent[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

focusBlur.freeHoveredElement();

hoverElement($child[0]);
ok(isHovered($parent[0]));
ok(isHovered($child[0]));

hoverElement($('body')[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

$parent.remove();
$child.remove();
});
}

test('attachEvent, fireEvent, detachEvent must be overriden (T239606)', function () {
var el = nativeMethods.createElement.call(document, 'A');

Expand Down
66 changes: 66 additions & 0 deletions test/client/fixtures/sandbox/event/hover-test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
var INTERNAL_ATTRS = hammerhead.get('../processing/dom/internal-attributes');

var hover = hammerhead.sandbox.event.hover;
var browserUtils = hammerhead.utils.browser;
var eventSimulator = hammerhead.sandbox.event.eventSimulator;

function hoverElement (el) {
if (browserUtils.hasTouchEvents)
eventSimulator.touchstart(el, {});
else
eventSimulator.mouseover(el, {});
}

function isHovered (el) {
return el.getAttribute(INTERNAL_ATTRS.hoverPseudoClass) === '';
}

test('hover pseudo class', function () {
var $parent = $('<div style="width:100px; height:100px; background-color: Red" class="parent">').appendTo($('body'));
var $child = $('<div style="width:50px; height:50px; background-color: Blue" class="child">').appendTo($parent);

ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

hoverElement($parent[0]);
ok(isHovered($parent[0]));
ok(!isHovered($child[0]));

hoverElement($child[0]);
ok(isHovered($parent[0]));
ok(isHovered($child[0]));

hoverElement($('body')[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

$parent.remove();
$child.remove();
});

test('hover.fixHoveredElement, hover.freeHoveredElement (B254111)', function () {
var $parent = $('<div style="width:100px; height:100px; background-color: Red" class="parent">').appendTo($('body'));
var $child = $('<div style="width:50px; height:50px; background-color: Blue" class="child">').appendTo($parent);

ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

hover.fixHoveredElement();

hoverElement($parent[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

hover.freeHoveredElement();

hoverElement($child[0]);
ok(isHovered($parent[0]));
ok(isHovered($child[0]));

hoverElement($('body')[0]);
ok(!isHovered($parent[0]));
ok(!isHovered($child[0]));

$parent.remove();
$child.remove();
});