Skip to content
This repository was archived by the owner on Feb 26, 2024. It is now read-only.

Commit e02960d

Browse files
JiaLiPassionmhevery
authored andcommitted
fix(listener): fix #616, webdriver removeEventListener throw permission denied error (#699)
* fix(listener): fix #616, webdriver removeEventListener will throw permission denied error * add test cases of webdriver cross site context
1 parent 0d0ee53 commit e02960d

File tree

3 files changed

+95
-9
lines changed

3 files changed

+95
-9
lines changed

Diff for: lib/common/utils.ts

+59-9
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@ export interface ListenerTaskMeta extends TaskData {
164164
handler: NestedEventListenerOrEventListenerObject;
165165
target: any;
166166
name: string;
167+
crossContext: boolean;
167168
invokeAddFunc: (addFnSymbol: any, delegate: Task|NestedEventListenerOrEventListenerObject) => any;
168169
invokeRemoveFunc:
169170
(removeFnSymbol: any, delegate: Task|NestedEventListenerOrEventListenerObject) => any;
@@ -228,21 +229,47 @@ const defaultListenerMetaCreator = (self: any, args: any[]) => {
228229
handler: args[1],
229230
target: self || _global,
230231
name: args[0],
232+
crossContext: false,
231233
invokeAddFunc: function(
232234
addFnSymbol: any, delegate: Task|NestedEventListenerOrEventListenerObject) {
233-
if (delegate && (<Task>delegate).invoke) {
234-
return this.target[addFnSymbol](this.eventName, (<Task>delegate).invoke, this.useCapturing);
235+
// check if the data is cross site context, if it is, fallback to
236+
// remove the delegate directly and try catch error
237+
if (!this.crossContext) {
238+
if (delegate && (<Task>delegate).invoke) {
239+
return this.target[addFnSymbol](
240+
this.eventName, (<Task>delegate).invoke, this.useCapturing);
241+
} else {
242+
return this.target[addFnSymbol](this.eventName, delegate, this.useCapturing);
243+
}
235244
} else {
236-
return this.target[addFnSymbol](this.eventName, delegate, this.useCapturing);
245+
// add a if/else branch here for performance concern, for most times
246+
// cross site context is false, so we don't need to try/catch
247+
try {
248+
return this.target[addFnSymbol](this.eventName, delegate, this.useCapturing);
249+
} catch (err) {
250+
// do nothing here is fine, because objects in a cross-site context are unusable
251+
}
237252
}
238253
},
239254
invokeRemoveFunc: function(
240255
removeFnSymbol: any, delegate: Task|NestedEventListenerOrEventListenerObject) {
241-
if (delegate && (<Task>delegate).invoke) {
242-
return this.target[removeFnSymbol](
243-
this.eventName, (<Task>delegate).invoke, this.useCapturing);
256+
// check if the data is cross site context, if it is, fallback to
257+
// remove the delegate directly and try catch error
258+
if (!this.crossContext) {
259+
if (delegate && (<Task>delegate).invoke) {
260+
return this.target[removeFnSymbol](
261+
this.eventName, (<Task>delegate).invoke, this.useCapturing);
262+
} else {
263+
return this.target[removeFnSymbol](this.eventName, delegate, this.useCapturing);
264+
}
244265
} else {
245-
return this.target[removeFnSymbol](this.eventName, delegate, this.useCapturing);
266+
// add a if/else branch here for performance concern, for most times
267+
// cross site context is false, so we don't need to try/catch
268+
try {
269+
return this.target[removeFnSymbol](this.eventName, delegate, this.useCapturing);
270+
} catch (err) {
271+
// do nothing here is fine, because objects in a cross-site context are unusable
272+
}
246273
}
247274
}
248275
};
@@ -289,8 +316,9 @@ export function makeZoneAwareAddListener(
289316
// will fail tests prematurely.
290317
validZoneHandler = data.handler && data.handler.toString() === '[object FunctionWrapper]';
291318
} catch (error) {
292-
// Returning nothing here is fine, because objects in a cross-site context are unusable
293-
return;
319+
// we can still try to add the data.handler even we are in cross site context
320+
data.crossContext = true;
321+
return data.invokeAddFunc(addFnSymbol, data.handler);
294322
}
295323
// Ignore special listeners of IE11 & Edge dev tools, see
296324
// https://github.com/angular/zone.js/issues/150
@@ -322,10 +350,32 @@ export function makeZoneAwareRemoveListener(
322350

323351
return function zoneAwareRemoveListener(self: any, args: any[]) {
324352
const data = metaCreator(self, args);
353+
325354
data.useCapturing = data.useCapturing || defaultUseCapturing;
326355
// - Inside a Web Worker, `this` is undefined, the context is `global`
327356
// - When `addEventListener` is called on the global context in strict mode, `this` is undefined
328357
// see https://github.com/angular/zone.js/issues/190
358+
let delegate: EventListener = null;
359+
if (typeof data.handler == 'function') {
360+
delegate = <EventListener>data.handler;
361+
} else if (data.handler && (<EventListenerObject>data.handler).handleEvent) {
362+
delegate = (event) => (<EventListenerObject>data.handler).handleEvent(event);
363+
}
364+
let validZoneHandler = false;
365+
try {
366+
// In cross site contexts (such as WebDriver frameworks like Selenium),
367+
// accessing the handler object here will cause an exception to be thrown which
368+
// will fail tests prematurely.
369+
validZoneHandler = data.handler && data.handler.toString() === '[object FunctionWrapper]';
370+
} catch (error) {
371+
data.crossContext = true;
372+
return data.invokeRemoveFunc(symbol, data.handler);
373+
}
374+
// Ignore special listeners of IE11 & Edge dev tools, see
375+
// https://github.com/angular/zone.js/issues/150
376+
if (!delegate || validZoneHandler) {
377+
return data.invokeRemoveFunc(symbol, data.handler);
378+
}
329379
const eventTask = findExistingRegisteredTask(
330380
data.target, data.handler, data.eventName, data.useCapturing, true);
331381
if (eventTask) {

Diff for: test/webdriver/test.html

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script src='../../dist/zone.js'></script>
5+
</head>
6+
<body>
7+
<div id="thetext">Hello Zones!</div>
8+
</body>
9+
</html>

Diff for: test/webdriver/test.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
// TODO: @JiaLiPassion, try to add it into travis/saucelabs test after saucelabs support Firefox 52+
10+
// requirement, Firefox 52+, webdriver-manager 12.0.4+, selenium-webdriver 3.3.0+
11+
// test step,
12+
// webdriver-manager update
13+
// webdriver-manager start
14+
// http-server test/webdriver
15+
// node test/webdriver/test.js
16+
17+
// testcase1: removeEventHandler in firefox cross site context
18+
const webdriver = require('selenium-webdriver');
19+
const capabilities = webdriver.Capabilities.firefox();
20+
const driver = new webdriver.Builder().usingServer('http://localhost:4444/wd/hub').withCapabilities(capabilities).build();
21+
driver.get("http://localhost:8080/test.html");
22+
driver.executeAsyncScript((cb) => { window.setTimeout(cb,1000) });
23+
24+
// test case2 addEventHandler in firefox cross site context
25+
driver.findElement(webdriver.By.css('#thetext')).getText().then(function(text) {
26+
console.log(text);
27+
});

0 commit comments

Comments
 (0)