Skip to content

Commit 432b3d5

Browse files
authored
fix: make the primary domain communicator a singleton per cypress instance (#20653)
* fix: make the primary domain communicator a singleton per cypress instance * leverage a factory to make typescript happy with their super() constructor rules * remove singleton instance from communicator and only bind message events once in the primary and per spec bridge. Forward all message events to the respective communicator instance, if available. * remove data.actual reference in primary communicator onMessage as it is no longer needed
1 parent ebb054e commit 432b3d5

File tree

3 files changed

+39
-49
lines changed

3 files changed

+39
-49
lines changed

packages/driver/src/multi-domain/communicator.ts

Lines changed: 28 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,38 @@ const CROSS_DOMAIN_PREFIX = 'cross:domain:'
1919
* @extends EventEmitter
2020
*/
2121
export class PrimaryDomainCommunicator extends EventEmitter {
22-
private windowReference: Window | undefined
2322
private crossDomainDriverWindows: {[key: string]: Window} = {}
2423
userInvocationStack?: string
2524

2625
/**
27-
* Initializes the event handler to receive messages from the spec bridge.
28-
* @param {Window} win - a reference to the window object in the primary domain.
26+
* The callback handler that receives messages from secondary domains.
27+
* @param {MessageEvent.data} data - a reference to the MessageEvent.data sent through the postMessage event. See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/data
28+
* @param {MessageEvent.source} source - a reference to the MessageEvent.source sent through the postMessage event. See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/source
2929
* @returns {Void}
3030
*/
31-
initialize (win: Window) {
32-
if (this.windowReference) return
33-
34-
this.windowReference = win
35-
36-
this.windowReference.top?.addEventListener('message', ({ data, source }) => {
37-
// currently used for tests, can be removed later
38-
if (data?.actual) return
39-
40-
// check if message is cross domain and if so, feed the message into
41-
// the cross domain bus with args and strip prefix
42-
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
43-
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')
44-
45-
// NOTE: need a special case here for 'bridge:ready'
46-
// where we need to set the crossDomainDriverWindow to source to
47-
// communicate back to the iframe
48-
if (messageName === 'bridge:ready' && source) {
49-
this.crossDomainDriverWindows[data.domain] = source as Window
50-
}
31+
onMessage ({ data, source }) {
32+
// check if message is cross domain and if so, feed the message into
33+
// the cross domain bus with args and strip prefix
34+
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
35+
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')
36+
37+
// NOTE: need a special case here for 'bridge:ready'
38+
// where we need to set the crossDomainDriverWindow to source to
39+
// communicate back to the iframe
40+
if (messageName === 'bridge:ready' && source) {
41+
this.crossDomainDriverWindows[data.domain] = source as Window
42+
}
5143

52-
if (data?.data?.err) {
53-
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
54-
}
44+
if (data?.data?.err) {
45+
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
46+
}
5547

56-
this.emit(messageName, data.data, data.domain)
48+
this.emit(messageName, data.data, data.domain)
5749

58-
return
59-
}
50+
return
51+
}
6052

61-
debug('Unexpected postMessage:', data)
62-
}, false)
53+
debug('Unexpected postMessage:', data)
6354
}
6455

6556
/**
@@ -115,8 +106,6 @@ export class PrimaryDomainCommunicator extends EventEmitter {
115106
* @extends EventEmitter
116107
*/
117108
export class SpecBridgeDomainCommunicator extends EventEmitter {
118-
private windowReference
119-
120109
private handleSubjectAndErr = (data: Cypress.ObjectLike = {}, send: (data: Cypress.ObjectLike) => void) => {
121110
let { subject, err, ...rest } = data
122111

@@ -164,20 +153,14 @@ export class SpecBridgeDomainCommunicator extends EventEmitter {
164153
}
165154

166155
/**
167-
* Initializes the event handler to receive messages from the primary domain.
168-
* @param {Window} win - a reference to the window object in the spec bridge/iframe.
156+
* The callback handler that receives messages from the primary domain.
157+
* @param {MessageEvent.data} data - a reference to the MessageEvent.data sent through the postMessage event. See https://developer.mozilla.org/en-US/docs/Web/API/MessageEvent/data
169158
* @returns {Void}
170159
*/
171-
initialize (win) {
172-
if (this.windowReference) return
173-
174-
this.windowReference = win
175-
176-
this.windowReference.addEventListener('message', ({ data }) => {
177-
if (!data) return
160+
onMessage ({ data }) {
161+
if (!data) return
178162

179-
this.emit(data.event, data.data)
180-
}, false)
163+
this.emit(data.event, data.data)
181164
}
182165

183166
/**
@@ -190,7 +173,7 @@ export class SpecBridgeDomainCommunicator extends EventEmitter {
190173
if (options.syncGlobals) this.syncGlobalsToPrimary()
191174

192175
this.handleSubjectAndErr(data, (data: Cypress.ObjectLike) => {
193-
this.windowReference.top.postMessage({
176+
window.top?.postMessage({
194177
event: `${CROSS_DOMAIN_PREFIX}${event}`,
195178
data,
196179
domain: document.domain,

packages/driver/src/multi-domain/cypress.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,6 @@ const createCypress = () => {
2424
// @ts-ignore
2525
const Cypress = window.Cypress = new $Cypress() as Cypress.Cypress
2626

27-
Cypress.specBridgeCommunicator.initialize(window)
28-
2927
Cypress.specBridgeCommunicator.once('initialize:cypress', ({ config, env }) => {
3028
// eventually, setup will get called again on rerun and cy will get re-created
3129
setup(config, env)
@@ -165,4 +163,9 @@ const onBeforeAppWindowLoad = (Cypress: Cypress.Cypress, cy: $Cy) => (autWindow:
165163
})
166164
}
167165

166+
// only bind the message handler one time when the spec bridge is created
167+
window.addEventListener('message', ({ data }) => {
168+
Cypress?.specBridgeCommunicator.onMessage({ data })
169+
}, false)
170+
168171
createCypress()

packages/runner-shared/src/event-manager.js

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,12 @@ export const eventManager = {
320320
this._clearAllCookies()
321321
this._setUnload()
322322
})
323+
324+
// The window.top should not change between test reloads, and we only need to bind the message event once
325+
// Forward all message events to the current instance of the multi-domain communicator
326+
window.top?.addEventListener('message', ({ data, source }) => {
327+
Cypress?.multiDomainCommunicator.onMessage({ data, source })
328+
}, false)
323329
},
324330

325331
start (config) {
@@ -512,8 +518,6 @@ export const eventManager = {
512518
}
513519
})
514520

515-
Cypress.multiDomainCommunicator.initialize(window)
516-
517521
Cypress.on('test:before:run', (...args) => {
518522
Cypress.multiDomainCommunicator.toAllSpecBridges('test:before:run', ...args)
519523
})

0 commit comments

Comments
 (0)