web-portals
Version:
web-portals
137 lines (127 loc) • 4.41 kB
text/typescript
import { ModuleState } from './state'
import { ModuleManifest, Application } from '../types'
class ModuleMount extends ModuleState {
constructor (id: string, model: ModuleManifest, application: Application) {
super(id, model, application)
}
public timeTick () {
if (Date.now() - this.createTime > (this.config.timeout || 3600000)) {
if (this.shadowView) this.destroy()
}
}
public show () {
if (this.viewType !== 'iframe') return
const sandbox = this.shadowView as HTMLIFrameElement
const contentWindow = sandbox?.contentWindow
if (contentWindow) {
contentWindow.window.postMessage({
type: 'module-visible',
historyDirection: this.application.transform.historyDirection
}, '*')
if (this.sameOrigin) {
contentWindow.window['moduleVisibilityState'] = 'visible'
}
}
}
public hide () {
if (this.viewType !== 'iframe') return
const sandbox = this.shadowView as HTMLIFrameElement
const contentWindow = sandbox?.contentWindow?.window
if (contentWindow) {
contentWindow.postMessage({
type: 'module-hidden',
historyDirection: this.application.transform.historyDirection
}, '*')
if (this.sameOrigin) {
contentWindow['moduleVisibilityState'] = 'visible'
}
}
}
public destroy () {
return new Promise<void>((resolve, reject) => {
if (this.rel !== 'module') return reject()
if (this.application.transform?.id === this.id) return reject()
if (this.viewType === 'iframe') this.unload().catch(reject)
this.elements.container.parentElement?.removeChild(this.elements.container)
this.status.prefetch = this.status.preload = this.status.prerender = false
this.shadowView = null
this.status.init = false
this.events?.destroy.bind(this)()
resolve()
})
}
public fate () {
return new Promise<void>((resolve, reject) => {
if (this.rel !== 'module') return reject()
if (this.config.background === false) return resolve()
if (this.viewType !== 'iframe') return resolve()
if (this.sameOrigin === false) return resolve()
const sandbox = this.shadowView as HTMLIFrameElement
if (!sandbox) return
try {
const contentDocumentElement = sandbox.contentDocument?.documentElement
if (document.getElementsByTagName('video')[0]) return resolve()
if (document.getElementsByTagName('source')[0]) return resolve()
if (document.getElementsByTagName('object')[0]) return resolve()
if (document.getElementsByTagName('audio')[0]) return resolve()
if (document.getElementsByTagName('embed')[0]) return resolve()
if (document.getElementsByTagName('applet')[0]) return resolve()
if (document.getElementsByTagName('iframe')[0]) return resolve()
if (contentDocumentElement) {
const counter = {
times: 0
}
this.mutationObserver = new MutationObserver(() => {
counter.times++
if (counter.times > 1000) {
resolve()
this.mutationObserver.disconnect()
}
})
this.mutationObserver.observe(contentDocumentElement, {
subtree: true,
attributes: true,
childList: true,
characterData: true,
attributeOldValue: true,
characterDataOldValue: true
})
setTimeout(() => {
if (counter.times > 10) resolve()
}, 3000)
} else {
reject()
}
} catch (error) {
reject()
}
})
}
public unfate () {
this.mutationObserver?.disconnect()
}
private unload () {
return new Promise<void>((resolve, reject) => {
this.unfate()
const sandbox = this.shadowView as HTMLIFrameElement
if (!sandbox) return resolve()
sandbox.style.display = 'none'
sandbox.src = 'about:blank'
try {
const contentWindow = sandbox.contentWindow?.window
const contentDocument = sandbox.contentDocument
contentWindow?.location.reload()
contentDocument?.open()
contentDocument?.write('')
contentDocument?.close()
} catch (error) {
reject()
}
sandbox.parentElement?.removeChild(sandbox)
resolve()
})
}
}
export {
ModuleMount
}