Skip to content

Commit 2a57b23

Browse files
authoredFeb 1, 2021
fix(client): fix a false positive page reload error in Safari (#3643)
Previous code was susceptible to the race condition in Safari browser. Specifically below piece: ``` iframe.src = policy.createURL(url) karmaNavigating = false ``` There is no guarantee that onbeforeunload event will be triggered synchronously after setting `iframe.src`. It was the case in Chrome and Firefox, but not in Safari, where this caused an error every test run. New approach resets the onbeforeunload handler before navigation is triggered by Karma itself to avoid the need for synchronization and this race condition altogether. The handler will be restored by the new context once it is loaded. PS Code is a bit fragile as there is an implicit dependency on `onbeforeunload` from the context, but I can't think of a cleaner approach.
1 parent 9c755e0 commit 2a57b23

File tree

5 files changed

+19
-31
lines changed

5 files changed

+19
-31
lines changed
 

‎client/karma.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ var util = require('../common/util')
55
function Karma (updater, socket, iframe, opener, navigator, location, document) {
66
this.updater = updater
77
var startEmitted = false
8-
var karmaNavigating = false
98
var self = this
109
var queryParams = util.parseQueryParams(location.search)
1110
var browserId = queryParams.id || util.generateId('manual-')
@@ -83,21 +82,21 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
8382

8483
var childWindow = null
8584
function navigateContextTo (url) {
86-
karmaNavigating = true
8785
if (self.config.useIframe === false) {
8886
// run in new window
8987
if (self.config.runInParent === false) {
9088
// If there is a window already open, then close it
9189
// DEV: In some environments (e.g. Electron), we don't have setter access for location
9290
if (childWindow !== null && childWindow.closed !== true) {
91+
// The onbeforeunload listener was added by context to catch
92+
// unexpected navigations while running tests.
93+
childWindow.onbeforeunload = undefined
9394
childWindow.close()
9495
}
9596
childWindow = opener(url)
96-
karmaNavigating = false
9797
// run context on parent element (client_with_context)
9898
// using window.__karma__.scriptUrls to get the html element strings and load them dynamically
9999
} else if (url !== 'about:blank') {
100-
karmaNavigating = false
101100
var loadScript = function (idx) {
102101
if (idx < window.__karma__.scriptUrls.length) {
103102
var parser = new DOMParser()
@@ -128,15 +127,10 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
128127
}
129128
// run in iframe
130129
} else {
130+
// The onbeforeunload listener was added by the context to catch
131+
// unexpected navigations while running tests.
132+
iframe.contentWindow.onbeforeunload = undefined
131133
iframe.src = policy.createURL(url)
132-
karmaNavigating = false
133-
}
134-
}
135-
136-
this.onbeforeunload = function () {
137-
if (!karmaNavigating) {
138-
// TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL)
139-
self.error('Some of your tests did a full page reload!')
140134
}
141135
}
142136

‎context/karma.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@ function ContextKarma (callParentKarmaMethod) {
7777
contextWindow.onerror = function () {
7878
return self.error.apply(self, arguments)
7979
}
80-
// DEV: We must defined a function since we don't want to pass the event object
81-
contextWindow.onbeforeunload = function (e, b) {
82-
callParentKarmaMethod('onbeforeunload', [])
80+
81+
contextWindow.onbeforeunload = function () {
82+
return self.error('Some of your tests did a full page reload!')
8383
}
8484

8585
contextWindow.dump = function () {

‎static/context.js

+3-3
Original file line numberDiff line numberDiff line change
@@ -214,9 +214,9 @@ function ContextKarma (callParentKarmaMethod) {
214214
contextWindow.onerror = function () {
215215
return self.error.apply(self, arguments)
216216
}
217-
// DEV: We must defined a function since we don't want to pass the event object
218-
contextWindow.onbeforeunload = function (e, b) {
219-
callParentKarmaMethod('onbeforeunload', [])
217+
218+
contextWindow.onbeforeunload = function () {
219+
return self.error('Some of your tests did a full page reload!')
220220
}
221221

222222
contextWindow.dump = function () {

‎static/karma.js

+6-12
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@ var util = require('../common/util')
1515
function Karma (updater, socket, iframe, opener, navigator, location, document) {
1616
this.updater = updater
1717
var startEmitted = false
18-
var karmaNavigating = false
1918
var self = this
2019
var queryParams = util.parseQueryParams(location.search)
2120
var browserId = queryParams.id || util.generateId('manual-')
@@ -93,21 +92,21 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
9392

9493
var childWindow = null
9594
function navigateContextTo (url) {
96-
karmaNavigating = true
9795
if (self.config.useIframe === false) {
9896
// run in new window
9997
if (self.config.runInParent === false) {
10098
// If there is a window already open, then close it
10199
// DEV: In some environments (e.g. Electron), we don't have setter access for location
102100
if (childWindow !== null && childWindow.closed !== true) {
101+
// The onbeforeunload listener was added by context to catch
102+
// unexpected navigations while running tests.
103+
childWindow.onbeforeunload = undefined
103104
childWindow.close()
104105
}
105106
childWindow = opener(url)
106-
karmaNavigating = false
107107
// run context on parent element (client_with_context)
108108
// using window.__karma__.scriptUrls to get the html element strings and load them dynamically
109109
} else if (url !== 'about:blank') {
110-
karmaNavigating = false
111110
var loadScript = function (idx) {
112111
if (idx < window.__karma__.scriptUrls.length) {
113112
var parser = new DOMParser()
@@ -138,15 +137,10 @@ function Karma (updater, socket, iframe, opener, navigator, location, document)
138137
}
139138
// run in iframe
140139
} else {
140+
// The onbeforeunload listener was added by the context to catch
141+
// unexpected navigations while running tests.
142+
iframe.contentWindow.onbeforeunload = undefined
141143
iframe.src = policy.createURL(url)
142-
karmaNavigating = false
143-
}
144-
}
145-
146-
this.onbeforeunload = function () {
147-
if (!karmaNavigating) {
148-
// TODO(vojta): show what test (with explanation about jasmine.UPDATE_INTERVAL)
149-
self.error('Some of your tests did a full page reload!')
150144
}
151145
}
152146

‎test/client/karma.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ describe('Karma', function () {
2222
}
2323
}
2424
socket = new MockSocket()
25-
iframe = {}
25+
iframe = { contentWindow: {} }
2626
windowNavigator = {}
2727
windowLocation = { search: '' }
2828
windowStub = sinon.stub().returns({})

0 commit comments

Comments
 (0)
Please sign in to comment.