Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 28 additions & 45 deletions packages/driver/src/multi-domain/communicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,47 +19,38 @@ const CROSS_DOMAIN_PREFIX = 'cross:domain:'
* @extends EventEmitter
*/
export class PrimaryDomainCommunicator extends EventEmitter {
private windowReference: Window | undefined
private crossDomainDriverWindows: {[key: string]: Window} = {}
userInvocationStack?: string

/**
* Initializes the event handler to receive messages from the spec bridge.
* @param {Window} win - a reference to the window object in the primary domain.
* The callback handler that receives messages from secondary domains.
* @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
* @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
* @returns {Void}
*/
initialize (win: Window) {
if (this.windowReference) return

this.windowReference = win

this.windowReference.top?.addEventListener('message', ({ data, source }) => {
// currently used for tests, can be removed later
if (data?.actual) return

// check if message is cross domain and if so, feed the message into
// the cross domain bus with args and strip prefix
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')

// NOTE: need a special case here for 'bridge:ready'
// where we need to set the crossDomainDriverWindow to source to
// communicate back to the iframe
if (messageName === 'bridge:ready' && source) {
this.crossDomainDriverWindows[data.domain] = source as Window
}
onMessage ({ data, source }) {
// check if message is cross domain and if so, feed the message into
// the cross domain bus with args and strip prefix
if (data?.event?.includes(CROSS_DOMAIN_PREFIX)) {
const messageName = data.event.replace(CROSS_DOMAIN_PREFIX, '')

// NOTE: need a special case here for 'bridge:ready'
// where we need to set the crossDomainDriverWindow to source to
// communicate back to the iframe
if (messageName === 'bridge:ready' && source) {
this.crossDomainDriverWindows[data.domain] = source as Window
}

if (data?.data?.err) {
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
}
if (data?.data?.err) {
data.data.err = reifyCrossDomainError(data.data.err, this.userInvocationStack as string)
}

this.emit(messageName, data.data, data.domain)
this.emit(messageName, data.data, data.domain)

return
}
return
}

debug('Unexpected postMessage:', data)
}, false)
debug('Unexpected postMessage:', data)
}

/**
Expand Down Expand Up @@ -115,8 +106,6 @@ export class PrimaryDomainCommunicator extends EventEmitter {
* @extends EventEmitter
*/
export class SpecBridgeDomainCommunicator extends EventEmitter {
private windowReference

private handleSubjectAndErr = (data: Cypress.ObjectLike = {}, send: (data: Cypress.ObjectLike) => void) => {
let { subject, err, ...rest } = data

Expand Down Expand Up @@ -164,20 +153,14 @@ export class SpecBridgeDomainCommunicator extends EventEmitter {
}

/**
* Initializes the event handler to receive messages from the primary domain.
* @param {Window} win - a reference to the window object in the spec bridge/iframe.
* The callback handler that receives messages from the primary domain.
* @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
* @returns {Void}
*/
initialize (win) {
if (this.windowReference) return

this.windowReference = win

this.windowReference.addEventListener('message', ({ data }) => {
if (!data) return
onMessage ({ data }) {
if (!data) return

this.emit(data.event, data.data)
}, false)
this.emit(data.event, data.data)
}

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

this.handleSubjectAndErr(data, (data: Cypress.ObjectLike) => {
this.windowReference.top.postMessage({
window.top?.postMessage({
event: `${CROSS_DOMAIN_PREFIX}${event}`,
data,
domain: document.domain,
Expand Down
7 changes: 5 additions & 2 deletions packages/driver/src/multi-domain/cypress.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,6 @@ const createCypress = () => {
// @ts-ignore
const Cypress = window.Cypress = new $Cypress() as Cypress.Cypress

Cypress.specBridgeCommunicator.initialize(window)

Cypress.specBridgeCommunicator.once('initialize:cypress', ({ config, env }) => {
// eventually, setup will get called again on rerun and cy will get re-created
setup(config, env)
Expand Down Expand Up @@ -165,4 +163,9 @@ const onBeforeAppWindowLoad = (Cypress: Cypress.Cypress, cy: $Cy) => (autWindow:
})
}

// only bind the message handler one time when the spec bridge is created
window.addEventListener('message', ({ data }) => {
Cypress?.specBridgeCommunicator.onMessage({ data })
}, false)

createCypress()
8 changes: 6 additions & 2 deletions packages/runner-shared/src/event-manager.js
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,12 @@ export const eventManager = {
this._clearAllCookies()
this._setUnload()
})

// The window.top should not change between test reloads, and we only need to bind the message event once
// Forward all message events to the current instance of the multi-domain communicator
window.top?.addEventListener('message', ({ data, source }) => {
Cypress?.multiDomainCommunicator.onMessage({ data, source })
}, false)
},

start (config) {
Expand Down Expand Up @@ -512,8 +518,6 @@ export const eventManager = {
}
})

Cypress.multiDomainCommunicator.initialize(window)

Cypress.on('test:before:run', (...args) => {
Cypress.multiDomainCommunicator.toAllSpecBridges('test:before:run', ...args)
})
Expand Down