|
|
@ -34,7 +34,7 @@ function init (cfg) { |
|
|
|
renderer.Document = cfg.Document; |
|
|
|
renderer.Element = cfg.Element; |
|
|
|
renderer.Comment = cfg.Comment; |
|
|
|
renderer.sendTasks = cfg.sendTasks; |
|
|
|
renderer.compileBundle = cfg.compileBundle; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -47,7 +47,7 @@ function reset () { |
|
|
|
delete renderer.Document; |
|
|
|
delete renderer.Element; |
|
|
|
delete renderer.Comment; |
|
|
|
delete renderer.sendTasks; |
|
|
|
delete renderer.compileBundle; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -82,18 +82,9 @@ function createInstance ( |
|
|
|
// Virtual-DOM object.
|
|
|
|
var 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.
|
|
|
|
var callbacks = []; |
|
|
|
|
|
|
|
// The latest callback id, incremental.
|
|
|
|
var callbackId = 1; |
|
|
|
|
|
|
|
var instance = instances[instanceId] = { |
|
|
|
instanceId: instanceId, config: config, data: data, |
|
|
|
document: document, callbacks: callbacks, callbackId: callbackId |
|
|
|
document: document |
|
|
|
}; |
|
|
|
|
|
|
|
// Prepare native module getter and HTML5 Timer APIs.
|
|
|
@ -104,6 +95,7 @@ function createInstance ( |
|
|
|
var weexInstanceVar = { |
|
|
|
config: config, |
|
|
|
document: document, |
|
|
|
supports: supports, |
|
|
|
requireModule: moduleGetter |
|
|
|
}; |
|
|
|
Object.freeze(weexInstanceVar); |
|
|
@ -118,11 +110,16 @@ 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' }, []); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -133,6 +130,7 @@ function createInstance ( |
|
|
|
function destroyInstance (instanceId) { |
|
|
|
var instance = instances[instanceId]; |
|
|
|
if (instance && instance.app instanceof instance.Vue) { |
|
|
|
instance.document.destroy(); |
|
|
|
instance.app.$destroy(); |
|
|
|
} |
|
|
|
delete instances[instanceId]; |
|
|
@ -154,7 +152,7 @@ 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' }, []); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -169,49 +167,57 @@ function getRoot (instanceId) { |
|
|
|
return instance.app.$el.toJSON() |
|
|
|
} |
|
|
|
|
|
|
|
var jsHandlers = { |
|
|
|
fireEvent: function (id) { |
|
|
|
var args = [], len = arguments.length - 1; |
|
|
|
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; |
|
|
|
|
|
|
|
return fireEvent.apply(void 0, [ instances[id] ].concat( args )) |
|
|
|
}, |
|
|
|
callback: function (id) { |
|
|
|
var args = [], len = arguments.length - 1; |
|
|
|
while ( len-- > 0 ) args[ len ] = arguments[ len + 1 ]; |
|
|
|
|
|
|
|
return callback.apply(void 0, [ instances[id] ].concat( args )) |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
function fireEvent (instance, nodeId, type, e, domChanges) { |
|
|
|
var 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) { |
|
|
|
var result = instance.document.taskCenter.callback(callbackId, data, ifKeepAlive); |
|
|
|
instance.document.taskCenter.send('dom', { action: 'updateFinish' }, []); |
|
|
|
return result |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 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 |
|
|
|
* Accept calls from native (event or callback). |
|
|
|
* |
|
|
|
* @param {string} id |
|
|
|
* @param {array} tasks list with `method` and `args` |
|
|
|
*/ |
|
|
|
function receiveTasks (instanceId, tasks) { |
|
|
|
var instance = instances[instanceId]; |
|
|
|
if (!instance || !(instance.app instanceof instance.Vue)) { |
|
|
|
return new Error(("receiveTasks: instance " + instanceId + " not found!")) |
|
|
|
} |
|
|
|
var callbacks = instance.callbacks; |
|
|
|
var document = instance.document; |
|
|
|
tasks.forEach(function (task) { |
|
|
|
// `fireEvent` case: find the event target and fire.
|
|
|
|
if (task.method === 'fireEvent') { |
|
|
|
var ref = task.args; |
|
|
|
var nodeId = ref[0]; |
|
|
|
var type = ref[1]; |
|
|
|
var e = ref[2]; |
|
|
|
var domChanges = ref[3]; |
|
|
|
var el = document.getRef(nodeId); |
|
|
|
document.fireEvent(el, type, e, domChanges); |
|
|
|
} |
|
|
|
// `callback` case: find the callback by id and call it.
|
|
|
|
if (task.method === 'callback') { |
|
|
|
var ref$1 = task.args; |
|
|
|
var callbackId = ref$1[0]; |
|
|
|
var data = ref$1[1]; |
|
|
|
var ifKeepAlive = ref$1[2]; |
|
|
|
var 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 receiveTasks (id, tasks) { |
|
|
|
var instance = instances[id]; |
|
|
|
if (instance && Array.isArray(tasks)) { |
|
|
|
var results = []; |
|
|
|
tasks.forEach(function (task) { |
|
|
|
var handler = jsHandlers[task.method]; |
|
|
|
var args = [].concat( task.args ); |
|
|
|
/* istanbul ignore else */ |
|
|
|
if (typeof handler === 'function') { |
|
|
|
args.unshift(id); |
|
|
|
results.push(handler.apply(void 0, 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")) |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
@ -235,6 +241,18 @@ function registerModules (newModules) { |
|
|
|
for (var name in newModules) loop( name ); |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Check whether the module or the method has been registered. |
|
|
|
* @param {String} module name |
|
|
|
* @param {String} method name (optional) |
|
|
|
*/ |
|
|
|
function isRegisteredModule (name, method) { |
|
|
|
if (typeof method === 'string') { |
|
|
|
return !!(modules[name] && modules[name][method]) |
|
|
|
} |
|
|
|
return !!modules[name] |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Register native components information. |
|
|
|
* @param {array} newComponents |
|
|
@ -254,6 +272,35 @@ function registerComponents (newComponents) { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Check whether the component has been registered. |
|
|
|
* @param {String} component name |
|
|
|
*/ |
|
|
|
function isRegisteredComponent (name) { |
|
|
|
return !!components[name] |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Detects whether Weex supports specific features. |
|
|
|
* @param {String} condition |
|
|
|
*/ |
|
|
|
function supports (condition) { |
|
|
|
if (typeof condition !== 'string') { return null } |
|
|
|
|
|
|
|
var res = condition.match(/^@(\w+)\/(\w+)(\.(\w+))?$/i); |
|
|
|
if (res) { |
|
|
|
var type = res[1]; |
|
|
|
var name = res[2]; |
|
|
|
var method = res[4]; |
|
|
|
switch (type) { |
|
|
|
case 'module': return isRegisteredModule(name, method) |
|
|
|
case 'component': return isRegisteredComponent(name) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
return null |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* Create a fresh instance of Vue for each Weex instance. |
|
|
|
*/ |
|
|
@ -314,9 +361,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} |
|
|
|
*/ |
|
|
@ -326,15 +371,23 @@ function genModuleGetter (instanceId) { |
|
|
|
var nativeModule = modules[name] || []; |
|
|
|
var output = {}; |
|
|
|
var loop = function ( methodName ) { |
|
|
|
output[methodName] = function () { |
|
|
|
var args = [], len = arguments.length; |
|
|
|
while ( len-- ) args[ len ] = arguments[ len ]; |
|
|
|
|
|
|
|
var finalArgs = args.map(function (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 function () { |
|
|
|
var args = [], len = arguments.length; |
|
|
|
while ( len-- ) args[ len ] = arguments[ len ]; |
|
|
|
|
|
|
|
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]) |
|
|
|
} |
|
|
|
} |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
for (var methodName in nativeModule) loop( methodName ); |
|
|
@ -362,8 +415,9 @@ function getInstanceTimer (instanceId, moduleGetter) { |
|
|
|
var handler = function () { |
|
|
|
args[0].apply(args, args.slice(2)); |
|
|
|
}; |
|
|
|
|
|
|
|
timer.setTimeout(handler, args[1]); |
|
|
|
return instance.callbackId.toString() |
|
|
|
return instance.document.taskCenter.callbackManager.lastCallbackId.toString() |
|
|
|
}, |
|
|
|
setInterval: function () { |
|
|
|
var args = [], len = arguments.length; |
|
|
@ -372,8 +426,9 @@ function getInstanceTimer (instanceId, moduleGetter) { |
|
|
|
var handler = function () { |
|
|
|
args[0].apply(args, args.slice(2)); |
|
|
|
}; |
|
|
|
|
|
|
|
timer.setInterval(handler, args[1]); |
|
|
|
return instance.callbackId.toString() |
|
|
|
return instance.document.taskCenter.callbackManager.lastCallbackId.toString() |
|
|
|
}, |
|
|
|
clearTimeout: function (n) { |
|
|
|
timer.clearTimeout(n); |
|
|
@ -405,52 +460,55 @@ 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) { |
|
|
|
var 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) { |
|
|
|
var s = Object.prototype.toString.call(v); |
|
|
|
return s.substring(8, s.length - 1).toLowerCase() |
|
|
|
var fn = void 0; |
|
|
|
var isNativeCompileOk = false; |
|
|
|
var script = '(function ('; |
|
|
|
var globalKeys = []; |
|
|
|
var globalValues = []; |
|
|
|
for (var key in globalObjects) { |
|
|
|
globalKeys.push(key); |
|
|
|
globalValues.push(globalObjects[key]); |
|
|
|
} |
|
|
|
for (var i = 0; i < globalKeys.length - 1; ++i) { |
|
|
|
script += globalKeys[i]; |
|
|
|
script += ','; |
|
|
|
} |
|
|
|
script += globalKeys[globalKeys.length - 1]; |
|
|
|
script += ') {'; |
|
|
|
script += body; |
|
|
|
script += '} )'; |
|
|
|
|
|
|
|
try { |
|
|
|
var weex = globalObjects.weex || {}; |
|
|
|
var config = weex.config || {}; |
|
|
|
fn = renderer.compileBundle(script, |
|
|
|
config.bundleUrl, |
|
|
|
config.bundleDigest, |
|
|
|
config.codeCachePath); |
|
|
|
if (fn && typeof fn === 'function') { |
|
|
|
fn.apply(void 0, globalValues); |
|
|
|
isNativeCompileOk = true; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.error(e); |
|
|
|
} |
|
|
|
|
|
|
|
return isNativeCompileOk |
|
|
|
} |
|
|
|
|
|
|
|
exports.init = init; |
|
|
@ -461,4 +519,7 @@ exports.refreshInstance = refreshInstance; |
|
|
|
exports.getRoot = getRoot; |
|
|
|
exports.receiveTasks = receiveTasks; |
|
|
|
exports.registerModules = registerModules; |
|
|
|
exports.isRegisteredModule = isRegisteredModule; |
|
|
|
exports.registerComponents = registerComponents; |
|
|
|
exports.isRegisteredComponent = isRegisteredComponent; |
|
|
|
exports.supports = supports; |
|
|
|