Skip to content

Commit

Permalink
do not open new window if preventDefault was called in later handler …
Browse files Browse the repository at this point in the history
…(tc #5621) (#2648)

* do not open new window if preventDefault was called in later handler (tc #5621)

* ie

* review remarks
  • Loading branch information
AlexKamaev authored Jun 10, 2021
1 parent 8580411 commit e985143
Show file tree
Hide file tree
Showing 5 changed files with 120 additions and 12 deletions.
34 changes: 33 additions & 1 deletion src/client/sandbox/child-window/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Listeners from '../event/listeners';
import INTERNAL_PROPS from '../../../processing/dom/internal-properties';
import getTopOpenerWindow from '../../utils/get-top-opener-window';
import { isIframeWindow } from '../../utils/dom';
import nextTick from '../../utils/next-tick';

const DEFAULT_WINDOW_PARAMETERS = 'width=500px, height=500px';
const STORE_CHILD_WINDOW_CMD = 'hammerhead|command|store-child-window';
Expand Down Expand Up @@ -88,7 +89,38 @@ export default class ChildWindowSandbox extends SandboxBase {

e.preventDefault();

this._openUrlInNewWindow(url);
this._openUrlInNewWindowIfNotPrevented(url, e);
});
}

private _openUrlInNewWindowIfNotPrevented (url, e) {
let eventBubbledToTop = false;
let isDefaultPrevented = false;

const openUrlInNewWindowIfNotPreventedHandler = () => {
eventBubbledToTop = true;

Listeners.getNativeRemoveEventListener(window).call(window, 'click', openUrlInNewWindowIfNotPreventedHandler);

if (!isDefaultPrevented)
this._openUrlInNewWindow(url);
};

Listeners.getNativeAddEventListener(window).call(window, 'click', openUrlInNewWindowIfNotPreventedHandler);

// NOTE: additional attempt to open a new window if window.handler was prevented by
// `stopPropagation` or `stopImmediatePropagation` methods
const origPreventDefault = e.preventDefault;

e.preventDefault = () => {
isDefaultPrevented = true;

return origPreventDefault.call(e);
};

nextTick().then(() => {
if (!eventBubbledToTop)
openUrlInNewWindowIfNotPreventedHandler();
});
}

Expand Down
12 changes: 6 additions & 6 deletions src/client/sandbox/event/listeners.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export default class Listeners extends EventEmitter {
this.removeInternalEventBeforeListener = this.listeningCtx.removeInternalBeforeHandler;
}

private static _getNativeAddEventListener (el: any) {
static getNativeAddEventListener (el: any) {
if (isIE11) {
if (isWindow(el))
return nativeMethods.windowAddEventListener;
Expand All @@ -51,7 +51,7 @@ export default class Listeners extends EventEmitter {
return nativeMethods.addEventListener;
}

private static _getNativeRemoveEventListener (el) {
static getNativeRemoveEventListener (el) {
if (isIE11) {
if (isWindow(el))
return nativeMethods.windowRemoveEventListener;
Expand Down Expand Up @@ -154,7 +154,7 @@ export default class Listeners extends EventEmitter {
const el = this;
const useCapture = Listeners._getUseCaptureParam(args[2]);
const eventCtx = listeningCtx.getEventCtx(el, eventType);
const nativeAddEventListener = Listeners._getNativeAddEventListener(el);
const nativeAddEventListener = Listeners.getNativeAddEventListener(el);

if (!eventCtx || !isValidEventListener(listener))
return nativeAddEventListener.apply(el, args);
Expand Down Expand Up @@ -183,7 +183,7 @@ export default class Listeners extends EventEmitter {
const [eventType, listener] = args;
const el = this;
const useCapture = Listeners._getUseCaptureParam(args[2]);
const nativeRemoveEventListener = Listeners._getNativeRemoveEventListener(el);
const nativeRemoveEventListener = Listeners.getNativeRemoveEventListener(el);
const eventCtx = listeningCtx.getEventCtx(el, eventType);

if (!eventCtx || !isValidEventListener(listener))
Expand All @@ -203,7 +203,7 @@ export default class Listeners extends EventEmitter {
}

initElementListening (el: HTMLElement|Window|Document, events: string[] = LISTENED_EVENTS) {
const nativeAddEventListener = Listeners._getNativeAddEventListener(el);
const nativeAddEventListener = Listeners.getNativeAddEventListener(el);

for (const event of events) {
if (!this.listeningCtx.getEventCtx(el, event))
Expand Down Expand Up @@ -242,7 +242,7 @@ export default class Listeners extends EventEmitter {
}

restartElementListening (el: HTMLElement) {
const nativeAddEventListener = Listeners._getNativeAddEventListener(el);
const nativeAddEventListener = Listeners.getNativeAddEventListener(el);
const elementCtx = this.listeningCtx.getElementCtx(el);

if (elementCtx) {
Expand Down
6 changes: 3 additions & 3 deletions src/client/sandbox/event/unload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,8 +98,8 @@ export default class UnloadSandbox extends SandboxBase {
}

private _reattachListener (eventProperties: EventProperties) {
const nativeAddEventListener = nativeMethods.windowAddEventListener || nativeMethods.addEventListener;
const nativeRemoveEventListener = nativeMethods.windowRemoveEventListener || nativeMethods.removeEventListener;
const nativeAddEventListener = Listeners.getNativeAddEventListener(this.window);
const nativeRemoveEventListener = Listeners.getNativeRemoveEventListener(this.window);

// NOTE: reattach the Listener, it'll be the last in the queue.
nativeRemoveEventListener.call(this.window, eventProperties.nativeEventName, this);
Expand All @@ -111,7 +111,7 @@ export default class UnloadSandbox extends SandboxBase {
}

private _addEventListener (eventProperties: EventProperties) {
const nativeAddEventListener = nativeMethods.windowAddEventListener || nativeMethods.addEventListener;
const nativeAddEventListener = Listeners.getNativeAddEventListener(window);

nativeAddEventListener.call(window, eventProperties.nativeEventName, this);

Expand Down
4 changes: 2 additions & 2 deletions src/client/sandbox/node/window.ts
Original file line number Diff line number Diff line change
Expand Up @@ -227,8 +227,8 @@ export default class WindowSandbox extends SandboxBase {
}

_reattachHandler (window: Window, eventName: string): void {
const nativeAddEventListener = nativeMethods.windowAddEventListener || nativeMethods.addEventListener;
const nativeRemoveEventListener = nativeMethods.windowRemoveEventListener || nativeMethods.removeEventListener;
const nativeAddEventListener = Listeners.getNativeAddEventListener(window);
const nativeRemoveEventListener = Listeners.getNativeRemoveEventListener(window);

nativeRemoveEventListener.call(window, eventName, this);
nativeAddEventListener.call(window, eventName, this);
Expand Down
76 changes: 76 additions & 0 deletions test/client/fixtures/sandbox/child-window-test.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
var ChildWindowSandbox = hammerhead.sandboxes.ChildWindowSandbox;
var defaultTarget = hammerhead.sandboxUtils.defaultTarget;
var settings = hammerhead.settings;
var Promise = hammerhead.Promise;

var windowSandbox = hammerhead.sandbox.node.win;
var nativeMethods = hammerhead.nativeMethods;

var delay = function (ms) {
return new Promise(function (resolve) {
setTimeout(resolve, ms);
});
};

test('_shouldOpenInNewWindow', function () {
window.name = 'test-window-name';

Expand Down Expand Up @@ -144,6 +151,75 @@ test('Should not open in window if the default behavior was prevented', function
document.body.removeChild(link);
});

test('Should not open in window if the default behavior was prevented in parent element handler', function () {
settings.get().allowMultipleWindows = true;

var windowOpenCounter = 0;
var storedOpenUrlInNewWindow = windowSandbox._childWindowSandbox._openUrlInNewWindow;

hammerhead.sandbox.childWindow._openUrlInNewWindow = function () {
windowOpenCounter++;

return {
windowId: Date.now()
};
};

var link = document.createElement('a');
var div1 = document.createElement('div');
var div2 = document.createElement('div');
var div3 = document.createElement('div');

link.innerText = 'link';
link.href = 'http://example.com';
link.target = '_blank';

div1.addEventListener('click', function (e) {
e.preventDefault();
});

div2.addEventListener('click', function (e) {
e.stopPropagation();

e.preventDefault();
});

div3.addEventListener('click', function (e) {
e.stopPropagation();
});

div1.appendChild(link);
document.body.appendChild(div1);
nativeMethods.click.call(link);

document.body.removeChild(div1);
strictEqual(windowOpenCounter, 0);

div2.appendChild(link);
document.body.appendChild(div2);
nativeMethods.click.call(link);

return delay(10)
.then(function () {
document.body.removeChild(div2);
strictEqual(windowOpenCounter, 0);

div3.appendChild(link);
document.body.appendChild(div3);
nativeMethods.click.call(link);

return delay(10);
})
.then(function () {
document.body.removeChild(div3);
strictEqual(windowOpenCounter, 1);

windowSandbox._childWindowSandbox._openUrlInNewWindow = storedOpenUrlInNewWindow;

settings.get().allowMultipleWindows = false;
});
});

module('regression');

test('should be prevented only default behaviour (GH-2467)', function () {
Expand Down

0 comments on commit e985143

Please sign in to comment.