refactor(weex): sync recent changes of Weex (#6028)

* compile bundle on native side if 'compileBundle()' is available on
native side.

* refactor sendTasks

* reset renderer.compileBundle

* v2.2.2-weex.1

* v2.2.2-weex.2 && fixed memory leak

* call C++ timer instead of WxTimerModule in weex-vue-framwork

* v2.2.2-weex.4

* v2.2.2-weex.5

* v2.2.6-weex.1

* style(weex): fix eslint

* test(weex): fix test case for weex callback manager
This commit is contained in:
Hanks 2017-07-07 11:42:47 +08:00 committed by Evan You
parent 306997eaf4
commit 0d6ad12a48
2 changed files with 125 additions and 107 deletions

View File

@ -22,7 +22,7 @@ export function init (cfg) {
renderer.Document = cfg.Document
renderer.Element = cfg.Element
renderer.Comment = cfg.Comment
renderer.sendTasks = cfg.sendTasks
renderer.compileBundle = cfg.compileBundle
}
/**
@ -35,7 +35,7 @@ export function reset () {
delete renderer.Document
delete renderer.Element
delete renderer.Comment
delete renderer.sendTasks
delete renderer.compileBundle
}
/**
@ -66,18 +66,9 @@ export function createInstance (
// Virtual-DOM object.
const document = new renderer.Document(instanceId, config.bundleUrl)
// All function/callback of parameters before sent to native
// will be converted as an id. So `callbacks` is used to store
// these real functions. When a callback invoked and won't be
// called again, it should be removed from here automatically.
const callbacks = []
// The latest callback id, incremental.
const callbackId = 1
const instance = instances[instanceId] = {
instanceId, config, data,
document, callbacks, callbackId
document
}
// Prepare native module getter and HTML5 Timer APIs.
@ -102,11 +93,16 @@ export function createInstance (
weex: weexInstanceVar,
// deprecated
__weex_require_module__: weexInstanceVar.requireModule // eslint-disable-line
}, timerAPIs)
callFunction(instanceVars, appCode)
}, timerAPIs, env.services)
if (!callFunctionNative(instanceVars, appCode)) {
// If failed to compile functionBody on native side,
// fallback to 'callFunction()'.
callFunction(instanceVars, appCode)
}
// Send `createFinish` signal to native.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'createFinish', args: [] }], -1)
instance.document.taskCenter.send('dom', { action: 'createFinish' }, [])
}
/**
@ -117,6 +113,7 @@ export function createInstance (
export function destroyInstance (instanceId) {
const instance = instances[instanceId]
if (instance && instance.app instanceof instance.Vue) {
instance.document.destroy()
instance.app.$destroy()
}
delete instances[instanceId]
@ -138,7 +135,7 @@ export function refreshInstance (instanceId, data) {
instance.Vue.set(instance.app, key, data[key])
}
// Finally `refreshFinish` signal needed.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'refreshFinish', args: [] }], -1)
instance.document.taskCenter.send('dom', { action: 'refreshFinish' }, [])
}
/**
@ -153,41 +150,51 @@ export function getRoot (instanceId) {
return instance.app.$el.toJSON()
}
/**
* Receive tasks from native. Generally there are two types of tasks:
* 1. `fireEvent`: an device actions or user actions from native.
* 2. `callback`: invoke function which sent to native as a parameter before.
* @param {string} instanceId
* @param {array} tasks
*/
export function receiveTasks (instanceId, tasks) {
const instance = instances[instanceId]
if (!instance || !(instance.app instanceof instance.Vue)) {
return new Error(`receiveTasks: instance ${instanceId} not found!`)
const jsHandlers = {
fireEvent: (id, ...args) => {
return fireEvent(instances[id], ...args)
},
callback: (id, ...args) => {
return callback(instances[id], ...args)
}
const { callbacks, document } = instance
tasks.forEach(task => {
// `fireEvent` case: find the event target and fire.
if (task.method === 'fireEvent') {
const [nodeId, type, e, domChanges] = task.args
const el = document.getRef(nodeId)
document.fireEvent(el, type, e, domChanges)
}
// `callback` case: find the callback by id and call it.
if (task.method === 'callback') {
const [callbackId, data, ifKeepAlive] = task.args
const callback = callbacks[callbackId]
if (typeof callback === 'function') {
callback(data)
// Remove the callback from `callbacks` if it won't called again.
if (typeof ifKeepAlive === 'undefined' || ifKeepAlive === false) {
callbacks[callbackId] = undefined
}
}
function fireEvent (instance, nodeId, type, e, domChanges) {
const el = instance.document.getRef(nodeId)
if (el) {
return instance.document.fireEvent(el, type, e, domChanges)
}
return new Error(`invalid element reference "${nodeId}"`)
}
function callback (instance, callbackId, data, ifKeepAlive) {
const result = instance.document.taskCenter.callback(callbackId, data, ifKeepAlive)
instance.document.taskCenter.send('dom', { action: 'updateFinish' }, [])
return result
}
/**
* Accept calls from native (event or callback).
*
* @param {string} id
* @param {array} tasks list with `method` and `args`
*/
export function receiveTasks (id, tasks) {
const instance = instances[id]
if (instance && Array.isArray(tasks)) {
const results = []
tasks.forEach((task) => {
const handler = jsHandlers[task.method]
const args = [...task.args]
/* istanbul ignore else */
if (typeof handler === 'function') {
args.unshift(id)
results.push(handler(...args))
}
}
})
// Finally `updateFinish` signal needed.
renderer.sendTasks(instanceId + '', [{ module: 'dom', method: 'updateFinish', args: [] }], -1)
})
return results
}
return new Error(`invalid instance id "${id}" or tasks`)
}
/**
@ -288,9 +295,7 @@ function createVueModuleInstance (instanceId, moduleGetter) {
* Generate native module getter. Each native module has several
* methods to call. And all the behaviors is instance-related. So
* this getter will return a set of methods which additionally
* send current instance id to native when called. Also the args
* will be normalized into "safe" value. For example function arg
* will be converted into a callback id.
* send current instance id to native when called.
* @param {string} instanceId
* @return {function}
*/
@ -300,12 +305,20 @@ function genModuleGetter (instanceId) {
const nativeModule = modules[name] || []
const output = {}
for (const methodName in nativeModule) {
output[methodName] = (...args) => {
const finalArgs = args.map(value => {
return normalize(value, instance)
})
renderer.sendTasks(instanceId + '', [{ module: name, method: methodName, args: finalArgs }], -1)
}
Object.defineProperty(output, methodName, {
enumerable: true,
configurable: true,
get: function proxyGetter () {
return (...args) => {
return instance.document.taskCenter.send('module', { module: name, method: methodName }, args)
}
},
set: function proxySetter (val) {
if (typeof val === 'function') {
return instance.document.taskCenter.send('module', { module: name, method: methodName }, [val])
}
}
})
}
return output
}
@ -328,15 +341,17 @@ function getInstanceTimer (instanceId, moduleGetter) {
const handler = function () {
args[0](...args.slice(2))
}
timer.setTimeout(handler, args[1])
return instance.callbackId.toString()
return instance.document.taskCenter.callbackManager.lastCallbackId.toString()
},
setInterval: (...args) => {
const handler = function () {
args[0](...args.slice(2))
}
timer.setInterval(handler, args[1])
return instance.callbackId.toString()
return instance.document.taskCenter.callbackManager.lastCallbackId.toString()
},
clearTimeout: (n) => {
timer.clearTimeout(n)
@ -368,50 +383,53 @@ function callFunction (globalObjects, body) {
}
/**
* Convert all type of values into "safe" format to send to native.
* 1. A `function` will be converted into callback id.
* 2. An `Element` object will be converted into `ref`.
* The `instance` param is used to generate callback id and store
* function if necessary.
* @param {any} v
* @param {object} instance
* @return {any}
* Call a new function generated on the V8 native side.
*
* This function helps speed up bundle compiling. Normally, the V8
* engine needs to download, parse, and compile a bundle on every
* visit. If 'compileBundle()' is available on native side,
* the downloding, parsing, and compiling steps would be skipped.
* @param {object} globalObjects
* @param {string} body
* @return {boolean}
*/
function normalize (v, instance) {
const type = typof(v)
switch (type) {
case 'undefined':
case 'null':
return ''
case 'regexp':
return v.toString()
case 'date':
return v.toISOString()
case 'number':
case 'string':
case 'boolean':
case 'array':
case 'object':
if (v instanceof renderer.Element) {
return v.ref
}
return v
case 'function':
instance.callbacks[++instance.callbackId] = v
return instance.callbackId.toString()
default:
return JSON.stringify(v)
function callFunctionNative (globalObjects, body) {
if (typeof renderer.compileBundle !== 'function') {
return false
}
}
/**
* Get the exact type of an object by `toString()`. For example call
* `toString()` on an array will be returned `[object Array]`.
* @param {any} v
* @return {string}
*/
function typof (v) {
const s = Object.prototype.toString.call(v)
return s.substring(8, s.length - 1).toLowerCase()
let fn = void 0
let isNativeCompileOk = false
let script = '(function ('
const globalKeys = []
const globalValues = []
for (const key in globalObjects) {
globalKeys.push(key)
globalValues.push(globalObjects[key])
}
for (let i = 0; i < globalKeys.length - 1; ++i) {
script += globalKeys[i]
script += ','
}
script += globalKeys[globalKeys.length - 1]
script += ') {'
script += body
script += '} )'
try {
const weex = globalObjects.weex || {}
const config = weex.config || {}
fn = renderer.compileBundle(script,
config.bundleUrl,
config.bundleDigest,
config.codeCachePath)
if (fn && typeof fn === 'function') {
fn(...globalValues)
isNativeCompileOk = true
}
} catch (e) {
console.error(e)
}
return isNativeCompileOk
}

View File

@ -248,8 +248,8 @@ describe('framework APIs', () => {
{ method: 'fireEvent', args: [textRef, 'click'] }
])
expect(result instanceof Error).toBe(true)
expect(result).toMatch(/receiveTasks/)
expect(result).toMatch(/not found/)
expect(result).toMatch(/invalid\sinstance\sid/)
expect(result).toMatch(instance.id)
done()
})
})
@ -341,8 +341,8 @@ describe('framework APIs', () => {
{ method: 'callback', args: [callbackId] }
])
expect(result instanceof Error).toBe(true)
expect(result).toMatch(/receiveTasks/)
expect(result).toMatch(/not found/)
expect(result).toMatch(/invalid\sinstance\sid/)
expect(result).toMatch(instance.id)
done()
})
})