iportal
Version:
web-portal
207 lines (192 loc) • 6.56 kB
text/typescript
import { ModuleState } from './state'
import { ModuleManifest, Application } from '../types'
class ModuleMount extends ModuleState {
private mutationObserver!: MutationObserver
constructor (id: string, model: ModuleManifest, application: Application) {
super(id, model, application)
}
public timeTick () {
if (Date.now() - this.createTime > (this.config.timeout || 3600000)) {
if (this.view) this.destroy()
}
}
public show () {
if (this.viewType === 'portal') {
(this.view as HTMLPortalElement)?.activate()
return
}
if (this.viewType !== 'iframe') return
for (const task of this.darkTask) {
task()
}
this.darkTask = []
this.visibility = true
this.trigger('visible')
this.triggerWindow('module-visible', 'moduleVisibilityState', 'visible')
}
public hide () {
this.visibility = false
this.trigger('hidden')
this.triggerWindow('module-hidden', 'moduleVisibilityState', 'hidden')
}
public willShow () {
this.trigger('willShow')
this.triggerWindow('module-will-show', 'moduleVisibilityState', 'willVisible')
}
public willHide () {
this.trigger('willHidden')
this.triggerWindow('module-will-hidden', 'moduleVisibilityState', 'willHidden')
}
public triggerWindow (type: string, attributeName?: string, attributeValue?: string | object | boolean): void {
if (this.viewType !== 'iframe') return
const sandbox = this.view as HTMLIFrameElement
const contentWindow = sandbox?.contentWindow?.window
if (contentWindow) {
contentWindow.postMessage({
type,
historyDirection: this.application.transform.historyDirection
}, '*')
if (this.sameOrigin && attributeName) {
contentWindow[attributeName] = attributeValue
}
}
}
public mediaGuard () {
return new Promise<void>((resolve, reject) => {
try {
if (this.viewType !== 'iframe') return resolve()
if (this.sandbox === undefined) return resolve()
const view = this.view as HTMLIFrameElement
const contentDocumentElement = view.contentDocument?.documentElement
if (!contentDocumentElement) return reject()
if (contentDocumentElement.getElementsByTagName('video')[0]) {
const videos = contentDocumentElement.querySelectorAll('video')
for (const index in videos) {
const video = videos[index]
if (!video?.paused) {
video.pause()
this.darkTask.push(() => {
video.play()
})
}
}
}
if (contentDocumentElement.getElementsByTagName('audio')[0]) {
const audios = contentDocumentElement.querySelectorAll('audio')
for (const index in audios) {
const audio = audios[index]
if (!audio?.paused) {
audio.pause()
this.darkTask.push(() => {
audio.play()
})
}
}
}
} catch (error) {
reject()
}
})
}
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.view = null
this.status.init = false
this.darkTask = []
this.events?.destroy.bind(this)()
this.trigger('destroy')
resolve()
})
}
public observer (change: (record: MutationRecord[]) => void) {
const target = this.sandbox ? this.sandbox.contentDocument?.documentElement : this.view
if (!target) return
const observer = new MutationObserver((record: MutationRecord[]) => {
change(record)
})
observer.observe(target, {
subtree: true,
attributes: true,
childList: true,
characterData: true,
attributeOldValue: true,
characterDataOldValue: true
})
return observer
}
public fate () {
return new Promise<void>((resolve, reject) => {
if (this.rel !== 'module') return reject()
if (this.config.background === true) return reject()
if (this.config.background === false) return resolve()
if (this.viewType !== 'iframe') return reject()
if (this.sandbox === undefined) return reject()
if (this.sameOrigin === false) return resolve()
const view = this.view as HTMLIFrameElement
try {
const contentDocumentElement = view.contentDocument?.documentElement
if (!contentDocumentElement) return reject()
if (this.config.mediaGuard !== false) {
this.mediaGuard().catch(resolve)
}
if (contentDocumentElement.getElementsByTagName('object')[0]) return resolve()
if (contentDocumentElement.getElementsByTagName('embed')[0]) return resolve()
if (contentDocumentElement.getElementsByTagName('applet')[0]) return resolve()
if (contentDocumentElement.getElementsByTagName('iframe')[0]) return resolve()
if (contentDocumentElement) {
const counter = {
times: 0
}
const observer = this.observer(() => {
counter.times++
if (counter.times > 1000) {
resolve()
this.mutationObserver.disconnect()
}
})
if (!observer) return
this.mutationObserver = observer
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.view 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
}