Skip to content

Commit 0f154b6

Browse files
committed
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.
1 parent 5132f1d commit 0f154b6

File tree

4 files changed

+44
-70
lines changed

4 files changed

+44
-70
lines changed

packages/driver/src/cypress.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ import ProxyLogging from './cypress/proxy-logging'
3737
import * as $Events from './cypress/events'
3838
import $Keyboard from './cy/keyboard'
3939
import * as resolvers from './cypress/resolvers'
40-
import { PrimaryDomainCommunicator, PrimaryDomainCommunicatorFactory, SpecBridgeDomainCommunicator } from './multi-domain/communicator'
40+
import { PrimaryDomainCommunicator, SpecBridgeDomainCommunicator } from './multi-domain/communicator'
4141

4242
const debug = debugFn('cypress:driver:cypress')
4343

@@ -153,7 +153,7 @@ class $Cypress {
153153
this.Commands = null
154154
this.$autIframe = null
155155
this.onSpecReady = null
156-
this.multiDomainCommunicator = PrimaryDomainCommunicatorFactory.create()
156+
this.multiDomainCommunicator = new PrimaryDomainCommunicator()
157157
this.specBridgeCommunicator = new SpecBridgeDomainCommunicator()
158158
this.isMultiDomain = false
159159

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

Lines changed: 31 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -6,25 +6,6 @@ import { preprocessForSerialization, reifyCrossDomainError } from '../util/seria
66
const debug = debugFn('cypress:driver:multi-domain')
77

88
const CROSS_DOMAIN_PREFIX = 'cross:domain:'
9-
let primaryDomainCommunicatorSingleton: PrimaryDomainCommunicator | null = null
10-
11-
export class PrimaryDomainCommunicatorFactory {
12-
static create () {
13-
// There should only be one PrimaryDomainCommunicator per Cypress instance (Ex: primary domain, specbridge1, specbridge2, ...).
14-
// Spec Bridges will create a PrimaryDomainCommunicator instance, but will never initialize the window.top message listener
15-
// this means that methods that communicate through postMessage can send, but spec bridge PrimaryDomainCommunicator
16-
// will never receive any events.
17-
if (!primaryDomainCommunicatorSingleton) {
18-
primaryDomainCommunicatorSingleton = new PrimaryDomainCommunicator()
19-
}
20-
21-
// for the primary domain, the PrimaryDomainCommunicator needs to be a singleton.
22-
// multiple PrimaryDomainCommunicator instances recreated on test refreshes/reloads cause dangling instances
23-
// of the window.top message listener, stacking message events multiple times.
24-
// The window.top instance does not change between test reloads, and we only need to bind it once
25-
return primaryDomainCommunicatorSingleton
26-
}
27-
}
289

2910
/**
3011
* Primary domain communicator. Responsible for sending/receiving events throughout
@@ -38,47 +19,41 @@ export class PrimaryDomainCommunicatorFactory {
3819
* @extends EventEmitter
3920
*/
4021
export class PrimaryDomainCommunicator extends EventEmitter {
41-
private windowReference: Window | undefined
4222
private crossDomainDriverWindows: {[key: string]: Window} = {}
4323
userInvocationStack?: string
4424

4525
/**
46-
* Initializes the event handler to receive messages from the spec bridge.
47-
* @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
4829
* @returns {Void}
4930
*/
50-
initialize (win: Window) {
51-
if (this.windowReference) return
52-
53-
this.windowReference = win
54-
55-
this.windowReference.top?.addEventListener('message', ({ data, source }) => {
56-
// currently used for tests, can be removed later
57-
if (data?.actual) return
58-
59-
// check if message is cross domain and if so, feed the message into
60-
// the cross domain bus with args and strip prefix
61-
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
62-
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')
63-
64-
// NOTE: need a special case here for 'bridge:ready'
65-
// where we need to set the crossDomainDriverWindow to source to
66-
// communicate back to the iframe
67-
if (messageName === 'bridge:ready' && source) {
68-
this.crossDomainDriverWindows[data.domain] = source as Window
69-
}
31+
onMessage ({ data, source }) {
32+
// currently used for tests, can be removed later
33+
if (data?.actual) return
34+
35+
// check if message is cross domain and if so, feed the message into
36+
// the cross domain bus with args and strip prefix
37+
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
38+
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')
39+
40+
// NOTE: need a special case here for 'bridge:ready'
41+
// where we need to set the crossDomainDriverWindow to source to
42+
// communicate back to the iframe
43+
if (messageName === 'bridge:ready' && source) {
44+
this.crossDomainDriverWindows[data.domain] = source as Window
45+
}
7046

71-
if (data?.data?.err) {
72-
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
73-
}
47+
if (data?.data?.err) {
48+
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
49+
}
7450

75-
this.emit(messageName, data.data, data.domain)
51+
this.emit(messageName, data.data, data.domain)
7652

77-
return
78-
}
53+
return
54+
}
7955

80-
debug('Unexpected postMessage:', data)
81-
}, false)
56+
debug('Unexpected postMessage:', data)
8257
}
8358

8459
/**
@@ -134,8 +109,6 @@ export class PrimaryDomainCommunicator extends EventEmitter {
134109
* @extends EventEmitter
135110
*/
136111
export class SpecBridgeDomainCommunicator extends EventEmitter {
137-
private windowReference
138-
139112
private handleSubjectAndErr = (data: Cypress.ObjectLike = {}, send: (data: Cypress.ObjectLike) => void) => {
140113
let { subject, err, ...rest } = data
141114

@@ -183,20 +156,14 @@ export class SpecBridgeDomainCommunicator extends EventEmitter {
183156
}
184157

185158
/**
186-
* Initializes the event handler to receive messages from the primary domain.
187-
* @param {Window} win - a reference to the window object in the spec bridge/iframe.
159+
* The callback handler that receives messages from the primary domain.
160+
* @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
188161
* @returns {Void}
189162
*/
190-
initialize (win) {
191-
if (this.windowReference) return
192-
193-
this.windowReference = win
194-
195-
this.windowReference.addEventListener('message', ({ data }) => {
196-
if (!data) return
163+
onMessage ({ data }) {
164+
if (!data) return
197165

198-
this.emit(data.event, data.data)
199-
}, false)
166+
this.emit(data.event, data.data)
200167
}
201168

202169
/**
@@ -209,7 +176,7 @@ export class SpecBridgeDomainCommunicator extends EventEmitter {
209176
if (options.syncGlobals) this.syncGlobalsToPrimary()
210177

211178
this.handleSubjectAndErr(data, (data: Cypress.ObjectLike) => {
212-
this.windowReference.top.postMessage({
179+
window.top?.postMessage({
213180
event: `${CROSS_DOMAIN_PREFIX}${event}`,
214181
data,
215182
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)
@@ -166,4 +164,9 @@ const onBeforeAppWindowLoad = (Cypress: Cypress.Cypress, cy: $Cy) => (autWindow:
166164
})
167165
}
168166

167+
// only bind the message handler one time when the spec bridge is created
168+
window.addEventListener('message', ({ data }) => {
169+
Cypress?.specBridgeCommunicator.onMessage({ data })
170+
}, false)
171+
169172
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)