146 lines
5.2 KiB
JavaScript
146 lines
5.2 KiB
JavaScript
// Keep the native Page lifecycle, but hydrate the correct account before any
|
|
// story page reads/writes storage. This also covers cold starts from shares.
|
|
const bridge = require('./platformBridge')
|
|
const LIFECYCLE_METHODS = new Set(['onLoad', 'onShow', 'onReady', 'onHide', 'onUnload'])
|
|
const CLEANUP_METHOD = /^(?:destroy|clear|unbind|pause|invalidate|beginPauseLock)/
|
|
module.exports = function registerTangPage(options) {
|
|
function isCurrent(page) {
|
|
return !page.__tangDead && !page.__tangOriginalUnloaded
|
|
&& page.__tangVisible && page.__tangScope === bridge.getScope()
|
|
}
|
|
function returnHome(page) {
|
|
if (page.__tangRedirected || page.__tangDead) return
|
|
page.__tangRedirected = true
|
|
wx.reLaunch({ url: '/tang-detective/pages/home/home' })
|
|
}
|
|
function cleanUp(page, name) {
|
|
if (!page.__tangLoaded || typeof options[name] !== 'function') return
|
|
if (name !== 'onUnload' && page.__tangOriginalUnloaded) return
|
|
if (name === 'onUnload') {
|
|
if (page.__tangOriginalUnloaded) return
|
|
page.__tangOriginalUnloaded = true
|
|
}
|
|
page.__tangCleaning = true
|
|
try {
|
|
options[name].call(page)
|
|
} finally {
|
|
// This exception is synchronous only; pending timers and audio callbacks
|
|
// regain the normal visible/account/dead guards as soon as cleanup ends.
|
|
page.__tangCleaning = false
|
|
}
|
|
}
|
|
function deliverReady(page) {
|
|
if (!isCurrent(page) || !page.__tangLoaded || !page.__tangHasShown
|
|
|| !page.__tangReadyRequested || page.__tangReadyDelivered) return
|
|
page.__tangReadyDelivered = true
|
|
if (typeof options.onReady === 'function') options.onReady.call(page)
|
|
}
|
|
const guarded = { ...options }
|
|
Object.keys(options).forEach(key => {
|
|
// Event handlers such as onAudioTimeUpdate are ordinary guarded methods,
|
|
// even though their names begin with "on".
|
|
if (typeof options[key] !== 'function' || LIFECYCLE_METHODS.has(key)) return
|
|
guarded[key] = function (...args) {
|
|
if (this.__tangCleaning) {
|
|
// Release resources across an account change, but do not run helpers
|
|
// such as saveCurrentAudioTime against the newly selected account.
|
|
if (this.__tangScope === bridge.getScope() || CLEANUP_METHOD.test(key)) {
|
|
return options[key].apply(this, args)
|
|
}
|
|
return
|
|
}
|
|
if (this.__tangDead || this.__tangOriginalUnloaded || !this.__tangLoaded || !this.__tangVisible) return
|
|
if (this.__tangScope && this.__tangScope !== bridge.getScope()) {
|
|
returnHome(this)
|
|
return
|
|
}
|
|
return options[key].apply(this, args)
|
|
}
|
|
})
|
|
Page({
|
|
...guarded,
|
|
data: { ...(options.data || {}), tangBootPending: true },
|
|
tangIgnoreBootTap() {},
|
|
onLoad(query) {
|
|
this.__tangQuery = query
|
|
this.__tangScope = bridge.getScope()
|
|
this.__tangDead = false
|
|
this.__tangVisible = false
|
|
this.__tangLoaded = false
|
|
this.__tangCleaning = false
|
|
this.__tangOriginalUnloaded = false
|
|
this.__tangRedirected = false
|
|
this.__tangShowGeneration = 0
|
|
this.__tangReadyRequested = false
|
|
this.__tangReadyDelivered = false
|
|
this.__tangHasShown = false
|
|
const nativeSetData = this.setData
|
|
this.setData = function (...args) {
|
|
if (this.__tangScope !== bridge.getScope()) return
|
|
if (!this.__tangCleaning && !isCurrent(this)) return
|
|
return nativeSetData.apply(this, args)
|
|
}
|
|
this.__tangBoot = bridge.open()
|
|
},
|
|
onShow() {
|
|
if (this.__tangDead) return
|
|
this.__tangVisible = true
|
|
const generation = ++this.__tangShowGeneration
|
|
const scope = bridge.getScope()
|
|
if (this.__tangOriginalUnloaded || scope !== this.__tangScope) {
|
|
returnHome(this)
|
|
return
|
|
}
|
|
Promise.resolve(this.__tangBoot).then(() => {
|
|
if (this.__tangDead || !this.__tangVisible || generation !== this.__tangShowGeneration) return
|
|
if (bridge.getScope() !== scope) {
|
|
returnHome(this)
|
|
return
|
|
}
|
|
if (!this.__tangLoaded) {
|
|
this.__tangLoaded = true
|
|
if (options.onLoad) options.onLoad.call(this, this.__tangQuery)
|
|
if (!isCurrent(this)) return
|
|
this.setData({ tangBootPending: false })
|
|
}
|
|
if (options.onShow) options.onShow.call(this)
|
|
this.__tangHasShown = true
|
|
deliverReady(this)
|
|
}).catch(() => {
|
|
if (isCurrent(this) && generation === this.__tangShowGeneration) {
|
|
wx.showToast({ title: '故事暂时未能打开,请返回重试', icon: 'none' })
|
|
}
|
|
})
|
|
},
|
|
onReady() {
|
|
this.__tangReadyRequested = true
|
|
deliverReady(this)
|
|
},
|
|
onHide() {
|
|
this.__tangVisible = false
|
|
++this.__tangShowGeneration
|
|
try {
|
|
cleanUp(this, 'onHide')
|
|
} finally {
|
|
// A hidden page from an obsolete account must not retain contexts with
|
|
// callbacks that directly reference storage instead of guarded methods.
|
|
try {
|
|
if (this.__tangScope !== bridge.getScope()) cleanUp(this, 'onUnload')
|
|
} finally {
|
|
bridge.flush()
|
|
}
|
|
}
|
|
},
|
|
onUnload() {
|
|
this.__tangDead = true
|
|
this.__tangVisible = false
|
|
++this.__tangShowGeneration
|
|
try {
|
|
cleanUp(this, 'onUnload')
|
|
} finally {
|
|
bridge.flush()
|
|
}
|
|
},
|
|
})
|
|
}
|