blob: e7243af9dcbec19ed5c8fcab30cb2535b72f383b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#import "WXBridgeContext.h"
#import "WXJSCoreBridge.h"
#import "WXLog.h"
#import "WXUtility.h"
#import "WXModuleFactory.h"
#import "WXModuleProtocol.h"
#import "WXUtility.h"
#import "WXSDKError.h"
#import "WXMonitor.h"
#import "WXAssert.h"
#import "WXSDKManager.h"
#import "WXDebugTool.h"
#import "WXSDKInstance_private.h"
#import "WXThreadSafeMutableArray.h"
#import "WXAppConfiguration.h"
#import "WXInvocationConfig.h"
#import "WXComponentMethod.h"
#import "WXModuleMethod.h"
#import "WXCallJSMethod.h"
#import "WXSDKInstance_private.h"
#import "WXPrerenderManager.h"
#import "WXTracingManager.h"
#import "WXExceptionUtils.h"
#import "WXSDKEngine.h"
#import "WXPolyfillSet.h"
#import "WXJSExceptionProtocol.h"
#import "WXMonitor.h"
#import "WXAppMonitorProtocol.h"
#import "WXConfigCenterProtocol.h"
#import "WXSDKInstance_performance.h"
#import "JSContext+Weex.h"
#define SuppressPerformSelectorLeakWarning(Stuff) \
do { \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Warc-performSelector-leaks\"") \
Stuff; \
_Pragma("clang diagnostic pop") \
} while (0)
@interface WXBridgeContext ()
@property (nonatomic, strong) id<WXBridgeProtocol> jsBridge;
@property (nonatomic, strong) id<WXBridgeProtocol> devToolSocketBridge;
@property (nonatomic, assign) BOOL debugJS;
//store the methods which will be executed from native to js
@property (nonatomic, strong) NSMutableDictionary *sendQueue;
//the instance stack
@property (nonatomic, strong) WXThreadSafeMutableArray *insStack;
//identify if the JSFramework has been loaded
@property (nonatomic) BOOL frameworkLoadFinished;
//store some methods temporarily before JSFramework is loaded
@property (nonatomic, strong) NSMutableArray *methodQueue;
// store service
@property (nonatomic, strong) NSMutableArray *jsServiceQueue;
@end
@implementation WXBridgeContext
- (instancetype) init
{
self = [super init];
if (self) {
_methodQueue = [NSMutableArray new];
_frameworkLoadFinished = NO;
_jsServiceQueue = [NSMutableArray new];
}
return self;
}
- (id<WXBridgeProtocol>)jsBridge
{
WXAssertBridgeThread();
_debugJS = [WXDebugTool isDevToolDebug];
Class bridgeClass = _debugJS ? NSClassFromString(@"WXDebugger") : [WXJSCoreBridge class];
if (_jsBridge && [_jsBridge isKindOfClass:bridgeClass]) {
return _jsBridge;
}
if (_jsBridge) {
[_methodQueue removeAllObjects];
_frameworkLoadFinished = NO;
}
_jsBridge = _debugJS ? [NSClassFromString(@"WXDebugger") alloc] : [[WXJSCoreBridge alloc] init];
[self registerGlobalFunctions];
return _jsBridge;
}
- (NSInteger)checkInstance:(WXSDKInstance *)instance
{
if (!instance) {
WXLogInfo(@"instance not found, maybe already destroyed");
return FALSE;
}
return TRUE;
}
- (void)registerGlobalFunctions
{
__weak typeof(self) weakSelf = self;
[_jsBridge registerCallNative:^NSInteger(NSString *instance, NSArray *tasks, NSString *callback) {
return [weakSelf invokeNative:instance tasks:tasks callback:callback];
}];
[_jsBridge registerCallAddElement:^NSInteger(NSString *instanceId, NSString *parentRef, NSDictionary *elementData, NSInteger index) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:elementData[@"ref"] className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"addElement" options:nil];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: addElement : %@",elementData[@"type"]);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:elementData[@"ref"] className:nil name:WXTDomCall phase:WXTracingBegin functionName:@"addElement" options:@{@"threadName":WXTDOMThread}];
[manager startComponentTasks];
[manager addComponent:elementData toSupercomponent:parentRef atIndex:index appendingInTree:NO];
[WXTracingManager startTracingWithInstanceId:instanceId ref:elementData[@"ref"] className:nil name:WXTDomCall phase:WXTracingEnd functionName:@"addElement" options:@{@"threadName":WXTDOMThread}];
});
return 0;
}];
[_jsBridge registerCallCreateBody:^NSInteger(NSString *instanceId, NSDictionary *bodyData) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:bodyData[@"ref"] className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"createBody" options:@{@"threadName":WXTJSBridgeThread}];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: createBody %@ ref:%@",bodyData[@"type"],bodyData[@"ref"]);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:bodyData[@"ref"] className:nil name:WXTDomCall phase:WXTracingBegin functionName:@"createBody" options:@{@"threadName":WXTDOMThread}];
[manager startComponentTasks];
[manager createRoot:bodyData];
[WXTracingManager startTracingWithInstanceId:instanceId ref:bodyData[@"ref"] className:nil name:WXTDomCall phase:WXTracingEnd functionName:@"createBody" options:@{@"threadName":WXTDOMThread}];
});
return 0;
}];
[_jsBridge registerCallRemoveElement:^NSInteger(NSString *instanceId, NSString *ref) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"removeElement" options:nil];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: removeElement ref:%@",ref);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTDomCall phase:WXTracingBegin functionName:@"removeElement" options:@{@"threadName":WXTDOMThread}];
[manager startComponentTasks];
[manager removeComponent:ref];
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTDomCall phase:WXTracingEnd functionName:@"removeElement" options:@{@"threadName":WXTDOMThread}];
});
return 0;
}];
[_jsBridge registerCallMoveElement:^NSInteger(NSString *instanceId,NSString *ref,NSString *parentRef,NSInteger index) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"moveElement" options:nil];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: moveElement ,ref:%@ to ref:%@",ref,parentRef);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[manager startComponentTasks];
[manager moveComponent:ref toSuper:parentRef atIndex:index];
});
return 0;
}];
[_jsBridge registerCallUpdateAttrs:^NSInteger(NSString *instanceId,NSString *ref,NSDictionary *attrsData) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"updateAttrs" options:@{@"threadName":WXTJSBridgeThread}];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: updateAttrs ref:%@,attr:%@",ref,attrsData);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTDomCall phase:WXTracingBegin functionName:@"updateAttrs" options:@{@"threadName":WXTDOMThread}];
[manager startComponentTasks];
[manager updateAttributes:attrsData forComponent:ref];
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTDomCall phase:WXTracingEnd functionName:@"updateAttrs" options:@{@"threadName":WXTDOMThread}];
});
return 0;
}];
[_jsBridge registerCallUpdateStyle:^NSInteger(NSString *instanceId,NSString *ref,NSDictionary *stylesData) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"updateStyles" options:@{@"threadName":WXTJSBridgeThread}];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: updateStyles ref:%@,styles:%@",ref,stylesData);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[manager startComponentTasks];
[manager updateStyles:stylesData forComponent:ref];
});
return 0;
}];
[_jsBridge registerCallAddEvent:^NSInteger(NSString *instanceId,NSString *ref,NSString *event) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: addEvent ref:%@",ref);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[manager startComponentTasks];
[manager addEvent:event toComponent:ref];
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"addEvent" options:nil];
});
return 0;
}];
[_jsBridge registerCallRemoveEvent:^NSInteger(NSString *instanceId,NSString *ref,NSString *event) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action :removeEvent ref:%@",ref);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[manager startComponentTasks];
[manager removeEvent:event fromComponent:ref];
[WXTracingManager startTracingWithInstanceId:instanceId ref:ref className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"removeEvent" options:nil];
});
return 0;
}];
[_jsBridge registerCallCreateFinish:^NSInteger(NSString *instanceId) {
// Temporary here , in order to improve performance, will be refactored next version.
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if(![weakSelf checkInstance:instance]) {
return -1;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:nil className:nil name:WXTJSCall phase:WXTracingEnd functionName:@"createFinish" options:@{@"threadName":WXTJSBridgeThread}];
WXPerformBlockOnComponentThread(^{
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: createFinish :%@",instanceId);
#endif
WXComponentManager *manager = instance.componentManager;
if (!manager.isValid) {
return;
}
[WXTracingManager startTracingWithInstanceId:instanceId ref:nil className:nil name:WXTDomCall phase:WXTracingBegin functionName:@"createFinish" options:@{@"threadName":WXTDOMThread}];
[manager startComponentTasks];
[manager createFinish];
[WXTracingManager startTracingWithInstanceId:instanceId ref:nil className:nil name:WXTDomCall phase:WXTracingEnd functionName:@"createFinish" options:@{@"threadName":WXTDOMThread}];
});
return 0;
}];
[_jsBridge registerCallNativeModule:^NSInvocation*(NSString *instanceId, NSString *moduleName, NSString *methodName, NSArray *arguments, NSDictionary *options) {
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if (!instance) {
WXLogInfo(@"instance not found for callNativeModule:%@.%@, maybe already destroyed", moduleName, methodName);
return nil;
}
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: callNativeModule : %@ . %@",moduleName,methodName);
#endif
NSMutableDictionary * newOptions = options ? [options mutableCopy] : [NSMutableDictionary new];
NSMutableArray * newArguments = [arguments mutableCopy];
if ([WXSDKManager sharedInstance].multiContext && [instance.bundleType.lowercaseString isEqualToString:@"rax"]) {
// we need to adjust __weex_options__ params in arguments to options compatible with rax javaScript framework.
NSDictionary * weexOptions = nil;
for(int i = 0;i < [arguments count]; i ++) {
if ([arguments[i] isKindOfClass:[NSDictionary class]]) {
NSDictionary * dict = (NSDictionary*)arguments[i];
if (dict[@"__weex_options__"] && [dict[@"__weex_options__"] isKindOfClass:[NSDictionary class]]) {
weexOptions = dict;
[newOptions addEntriesFromDictionary:(NSDictionary*)(weexOptions[@"__weex_options__"])];
}
}
}
if (weexOptions) {
[newArguments removeObject:weexOptions];
}
}
WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:[newArguments copy] options:[newOptions copy] instance:instance];
if(![moduleName isEqualToString:@"dom"] && instance.needPrerender){
[WXPrerenderManager storePrerenderModuleTasks:method forUrl:instance.scriptURL.absoluteString];
return nil;
}
return [method invoke];
}];
[_jsBridge registerCallNativeComponent:^void(NSString *instanceId, NSString *componentRef, NSString *methodName, NSArray *args, NSDictionary *options) {
#ifdef DEBUG
WXLogDebug(@"flexLayout -> action: callNativeComponent ref:%@",componentRef);
#endif
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
WXComponentMethod *method = [[WXComponentMethod alloc] initWithComponentRef:componentRef methodName:methodName arguments:args instance:instance];
[method invoke];
}];
}
- (NSMutableArray *)insStack
{
if (_insStack) return _insStack;
_insStack = [WXThreadSafeMutableArray array];
return _insStack;
}
- (WXSDKInstance *)topInstance
{
if (self.insStack.count > 0) {
WXSDKInstance *topInstance = [WXSDKManager instanceForID:[self.insStack firstObject]];
return topInstance;
}
return nil;
}
- (NSMutableDictionary *)sendQueue
{
WXAssertBridgeThread();
if (_sendQueue) return _sendQueue;
_sendQueue = [NSMutableDictionary dictionary];
return _sendQueue;
}
#pragma mark JS Bridge Management
- (NSInteger)invokeNative:(NSString *)instanceId tasks:(NSArray *)tasks callback:(NSString __unused*)callback
{
WXAssertBridgeThread();
if (!instanceId || !tasks) {
WX_MONITOR_FAIL(WXMTNativeRender, WX_ERR_JSFUNC_PARAM, @"JS call Native params error!");
return 0;
}
WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId];
if (!instance) {
WXLogInfo(@"instance already destroyed, task ignored");
return -1;
}
NSTimeInterval startTime = CACurrentMediaTime()*1000;
for (NSDictionary *task in tasks) {
NSString *methodName = task[@"method"];
NSArray *arguments = task[@"args"];
if (task[@"component"]) {
NSString *ref = task[@"ref"];
WXComponentMethod *method = [[WXComponentMethod alloc] initWithComponentRef:ref methodName:methodName arguments:arguments instance:instance];
[method invoke];
[WXTracingManager startTracingWithInstanceId:instanceId ref:task[@"ref"] className:nil name:task[@"component"] phase:WXTracingBegin functionName:task[@"method"] options:nil];
} else {
NSString *moduleName = task[@"module"];
NSDictionary *options = task[@"options"];
WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments options:options instance:instance];
[WXTracingManager startTracingWithInstanceId:instanceId ref:nil className:nil name:task[@"module"] phase:WXTracingBegin functionName:task[@"method"] options:@{@"threadName":WXTJSBridgeThread}];
[method invoke];
}
}
[self performSelector:@selector(_sendQueueLoop) withObject:nil];
if (!instance.isJSCreateFinish) {
NSTimeInterval diff = CACurrentMediaTime()*1000-startTime;
instance.performance.fsCallNativeNum++;
instance.performance.fsCallNativeTime = instance.performance.fsCallNativeTime + diff;
}
return 1;
}
- (void)createInstance:(NSString *)instanceIdString
template:(NSString *)jsBundleString
options:(NSDictionary *)options
data:(id)data
{
WXAssertBridgeThread();
WXAssertParam(instanceIdString);
if (![self.insStack containsObject:instanceIdString]) {
if ([options[@"RENDER_IN_ORDER"] boolValue]) {
[self.insStack addObject:instanceIdString];
} else {
[self.insStack insertObject:instanceIdString atIndex:0];
}
}
//create a sendQueue bind to the current instance
NSMutableArray *sendQueue = [NSMutableArray array];
[self.sendQueue setValue:sendQueue forKey:instanceIdString];
NSArray *args = nil;
WX_MONITOR_INSTANCE_PERF_START(WXFirstScreenJSFExecuteTime, [WXSDKManager instanceForID:instanceIdString]);
WX_MONITOR_INSTANCE_PERF_START(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]);
BOOL shoudMultiContext = [WXSDKManager sharedInstance].multiContext;
__weak typeof(self) weakSelf = self;
NSString * bundleType = nil;
if (shoudMultiContext) {
bundleType = [self _pareJSBundleType:instanceIdString jsBundleString:jsBundleString]; // bundleType can be Vue, Rax and the new framework.
}
if (bundleType&&shoudMultiContext) {
NSMutableDictionary *newOptions = [options mutableCopy];
if (!options) {
newOptions = [NSMutableDictionary new];
}
[newOptions addEntriesFromDictionary:@{@"env":[WXUtility getEnvironment]}];
newOptions[@"bundleType"] = bundleType;
NSString *raxAPIScript = nil;
NSString *raxAPIScriptPath = nil;
WXSDKInstance *sdkInstance = [WXSDKManager instanceForID:instanceIdString];
sdkInstance.bundleType = bundleType;
if ([bundleType.lowercaseString isEqualToString:@"rax"]) {
raxAPIScriptPath = [[NSBundle bundleForClass:[weakSelf class]] pathForResource:@"weex-rax-api" ofType:@"js"];
raxAPIScript = [NSString stringWithContentsOfFile:raxAPIScriptPath encoding:NSUTF8StringEncoding error:nil];
if (!raxAPIScript) {
WXLogError(@"weex-rax-api can not found");
}
}
if ([WXDebugTool isDevToolDebug]) {
[self callJSMethod:@"createInstanceContext" args:@[instanceIdString, newOptions, data?:@[],raxAPIScript?:@""]];
if ([NSURL URLWithString:sdkInstance.pageName]) {
[sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString withSourceURL:[NSURL URLWithString:sdkInstance.pageName]];
} else {
[sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString];
}
} else {
sdkInstance.callCreateInstanceContext = [NSString stringWithFormat:@"instanceId:%@\noptions:%@\ndata:%@",instanceIdString, newOptions,data];
[self callJSMethod:@"createInstanceContext" args:@[instanceIdString, newOptions, data?:@[]] onContext:nil completion:^(JSValue *instanceContextEnvironment) {
if (sdkInstance.pageName) {
if (@available(iOS 8.0, *)) {
[sdkInstance.instanceJavaScriptContext.javaScriptContext setName:sdkInstance.pageName];
} else {
// Fallback
}
}
sdkInstance.createInstanceContextResult = [NSString stringWithFormat:@"%@", [[instanceContextEnvironment toDictionary] allKeys]];
JSGlobalContextRef instanceContextRef = sdkInstance.instanceJavaScriptContext.javaScriptContext.JSGlobalContextRef;
JSObjectRef instanceGlobalObject = JSContextGetGlobalObject(instanceContextRef);
for (NSString * key in [[instanceContextEnvironment toDictionary] allKeys]) {
JSStringRef propertyName = JSStringCreateWithUTF8CString([key cStringUsingEncoding:NSUTF8StringEncoding]);
if ([key isEqualToString:@"Vue"]) {
JSObjectSetPrototype(instanceContextRef, JSValueToObject(instanceContextRef, [instanceContextEnvironment valueForProperty:key].JSValueRef, NULL), JSObjectGetPrototype(instanceContextRef, instanceGlobalObject));
}
JSObjectSetProperty(instanceContextRef, instanceGlobalObject, propertyName, [instanceContextEnvironment valueForProperty:key].JSValueRef, 0, NULL);
}
if (WX_SYS_VERSION_LESS_THAN(@"10.2")) {
NSString *filePath = [[NSBundle bundleForClass:[weakSelf class]] pathForResource:@"weex-polyfill" ofType:@"js"];
NSString *script = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
if (script) {
[sdkInstance.instanceJavaScriptContext executeJavascript:script withSourceURL:[NSURL URLWithString:filePath]];
} else {
WXLogError(@"weex-pollyfill can not found");
}
}
if (raxAPIScript) {
[sdkInstance.instanceJavaScriptContext executeJavascript:raxAPIScript withSourceURL:[NSURL URLWithString:raxAPIScriptPath]];
sdkInstance.executeRaxApiResult = [NSString stringWithFormat:@"%@", [[sdkInstance.instanceJavaScriptContext.javaScriptContext.globalObject toDictionary] allKeys]];
}
if ([NSURL URLWithString:sdkInstance.pageName] || sdkInstance.scriptURL) {
[sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString withSourceURL:[NSURL URLWithString:sdkInstance.pageName]?:sdkInstance.scriptURL];
} else {
[sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString];
}
}];
}
} else {
if (data){
args = @[instanceIdString, jsBundleString, options ?: @{}, data];
} else {
args = @[instanceIdString, jsBundleString, options ?: @{}];
}
[self callJSMethod:@"createInstance" args:args];
}
WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]);
}
- (NSString *)_pareJSBundleType:(NSString*)instanceIdString jsBundleString:(NSString*)jsBundleString
{
NSString * bundleType = nil;
WXSDKInstance * instance = [WXSDKManager instanceForID:instanceIdString];
NSURLComponents * urlComponent = [NSURLComponents componentsWithString:instance.pageName?:@""];
if (@available(iOS 8.0, *)) {
for (NSURLQueryItem * queryItem in urlComponent.queryItems) {
if ([queryItem.name isEqualToString:@"bundleType"] && [@[@"vue", @"rax"] containsObject:queryItem.value]) {
bundleType = queryItem.value;
return bundleType;
}
}
} else {
// Fallback on earlier versions
return bundleType;
}
// trim like whiteSpace and newline charset
jsBundleString = [jsBundleString stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
// use the top 100 characters match the bundleType
if (jsBundleString.length > 100) {
jsBundleString = [jsBundleString substringWithRange:NSMakeRange(0, 100)];
}
if (!jsBundleString ) {
return bundleType;
}
if ([jsBundleString hasPrefix:@"// { \"framework\": \"Vue\""] || [jsBundleString hasPrefix:@"// { \"framework\": \"vue\""]) {
bundleType = @"Vue";
} else if ([jsBundleString hasPrefix:@"// { \"framework\": \"Rax\""] || [jsBundleString hasPrefix:@"// { \"framework\": \"rax\""] || [jsBundleString hasPrefix:@"// {\"framework\" : \"Rax\"}"] || [jsBundleString hasPrefix:@"// {\"framework\" : \"rax\"}"]) {
bundleType = @"Rax";
}else {
NSRegularExpression * regEx = [NSRegularExpression regularExpressionWithPattern:@"(use)(\\s+)(weex:vue)" options:NSRegularExpressionCaseInsensitive error:NULL];
NSTextCheckingResult *match = [regEx firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)];
if (match) {
bundleType = [jsBundleString substringWithRange:match.range];
return bundleType;
}
regEx = [NSRegularExpression regularExpressionWithPattern:@"(use)(\\s+)(weex:rax)" options:NSRegularExpressionCaseInsensitive error:NULL];
match = [regEx firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)];
if (match) {
bundleType = [jsBundleString substringWithRange:match.range];
}
}
return bundleType;
}
- (void)destroyInstance:(NSString *)instance
{
WXAssertBridgeThread();
WXAssertParam(instance);
//remove instance from stack
if([self.insStack containsObject:instance]){
[self.insStack removeObject:instance];
}
if(_jsBridge && [_jsBridge respondsToSelector:@selector(removeTimers:)]){
[_jsBridge removeTimers:instance];
}
if(self.sendQueue[instance]){
[self.sendQueue removeObjectForKey:instance];
}
[self callJSMethod:@"destroyInstance" args:@[instance]];
}
- (void)forceGarbageCollection
{
if ([self.jsBridge respondsToSelector:@selector(garbageCollect)]) {
[self.jsBridge garbageCollect];
}
}
- (void)refreshInstance:(NSString *)instance
data:(id)data
{
WXAssertBridgeThread();
WXAssertParam(instance);
if (!data) return;
[self callJSMethod:@"refreshInstance" args:@[instance, data]];
}
- (void)updateState:(NSString *)instance data:(id)data
{
WXAssertBridgeThread();
WXAssertParam(instance);
//[self.jsBridge callJSMethod:@"updateState" args:@[instance, data]];
}
- (void)executeJsFramework:(NSString *)script
{
WXAssertBridgeThread();
WXAssertParam(script);
WX_MONITOR_PERF_START(WXPTFrameworkExecute);
[self.jsBridge executeJSFramework:script];
WX_MONITOR_PERF_END(WXPTFrameworkExecute);
if ([self.jsBridge exception]) {
NSString *exception = [[self.jsBridge exception] toString];
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_SDK_INIT_JSFM_INIT_FAILED] %@",exception];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_SDK_INIT" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_SDK_INIT] function:@"" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSFramework, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
} else {
WX_MONITOR_SUCCESS(WXMTJSFramework);
//the JSFramework has been load successfully.
self.frameworkLoadFinished = YES;
[self executeAllJsService];
JSValue *frameworkVersion = [self.jsBridge callJSMethod:@"getJSFMVersion" args:nil];
if (frameworkVersion && [frameworkVersion isString]) {
[WXAppConfiguration setJSFrameworkVersion:[frameworkVersion toString]];
}
if (script) {
[WXAppConfiguration setJSFrameworkLibSize:[script lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
}
//execute methods which has been stored in methodQueue temporarily.
for (NSDictionary *method in _methodQueue) {
[self callJSMethod:method[@"method"] args:method[@"args"]];
}
[_methodQueue removeAllObjects];
WX_MONITOR_PERF_END(WXPTInitalize);
};
}
- (void)executeJsMethod:(WXCallJSMethod *)method
{
WXAssertBridgeThread();
if (!method.instance) {
WXLogError(@"Instance doesn't exist!");
return;
}
NSMutableArray *sendQueue = self.sendQueue[method.instance.instanceId];
if (!sendQueue) {
WXLogInfo(@"No send queue for instance:%@, may it has been destroyed so method:%@ is ignored", method.instance, method.methodName);
return;
}
[sendQueue addObject:method];
[self performSelector:@selector(_sendQueueLoop) withObject:nil];
}
- (void)callJSMethod:(NSString *)method args:(NSArray *)args onContext:(id<WXBridgeProtocol>)bridge completion:(void (^)(JSValue * value))complection
{
NSMutableArray *newArg = nil;
if (!bridge) {
bridge = self.jsBridge;
}
if (self.frameworkLoadFinished) {
newArg = [args mutableCopy];
if ([newArg containsObject:complection]) {
[newArg removeObject:complection];
}
WXLogDebug(@"Calling JS... method:%@, args:%@", method, args);
if ([bridge isKindOfClass:[WXJSCoreBridge class]]) {
JSValue *value = [bridge callJSMethod:method args:args];
if (complection) {
complection(value);
}
} else {
[bridge callJSMethod:method args:args];
}
} else {
newArg = [args mutableCopy];
if (complection) {
[newArg addObject:complection];
}
[_methodQueue addObject:@{@"method":method, @"args":[newArg copy]}];
}
}
- (JSValue *)excuteJSMethodWithResult:(WXCallJSMethod *)method
{
WXAssertBridgeThread();
return [self.jsBridge callJSMethod:@"callJS" args:@[method.instance.instanceId, @[[method callJSTask]]]];
}
- (void)executeAllJsService
{
for(NSDictionary *service in _jsServiceQueue) {
NSString *script = [service valueForKey:@"script"];
NSString *name = [service valueForKey:@"name"];
[self executeJsService:script withName:name];
}
[_jsServiceQueue removeAllObjects];
}
- (void)executeJsService:(NSString *)script withName:(NSString *)name
{
if(self.frameworkLoadFinished) {
WXAssert(script, @"param script required!");
[self.jsBridge executeJavascript:script];
if ([self.jsBridge exception]) {
NSString *exception = [[self.jsBridge exception] toString];
NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_INVOKE_JSSERVICE_EXECUTE] %@",exception];
[WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_INVOKE" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_INVOKE] function:@"" exception:errMsg extParams:nil];
WX_MONITOR_FAIL(WXMTJSService, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg);
} else {
// success
}
}else {
[_jsServiceQueue addObject:@{
@"name": name,
@"script": script
}];
}
}
- (void)registerModules:(NSDictionary *)modules
{
WXAssertBridgeThread();
if(!modules) return;
[self callJSMethod:@"registerModules" args:@[modules]];
}
- (void)registerComponents:(NSArray *)components
{
WXAssertBridgeThread();
if(!components) return;
[self callJSMethod:@"registerComponents" args:@[components]];
}
- (void)callJSMethod:(NSString *)method args:(NSArray *)args
{
if (self.frameworkLoadFinished) {
[self.jsBridge callJSMethod:method args:args];
} else {
[_methodQueue addObject:@{@"method":method, @"args":args}];
}
}
- (void)resetEnvironment
{
[_jsBridge resetEnvironment];
}
#pragma mark JS Debug Management
- (void)connectToDevToolWithUrl:(NSURL *)url
{
id webSocketBridge = [NSClassFromString(@"WXDebugger") alloc];
if(!webSocketBridge || ![webSocketBridge respondsToSelector:NSSelectorFromString(@"connectToURL:")]) {
return;
} else {
SuppressPerformSelectorLeakWarning(
[webSocketBridge performSelector:NSSelectorFromString(@"connectToURL:") withObject:url]
);
}
}
- (void)connectToWebSocket:(NSURL *)url
{
if (NSClassFromString(@"WXDebugLoggerBridge")) {
_devToolSocketBridge = [[NSClassFromString(@"WXDebugLoggerBridge") alloc] initWithURL:url];
}
}
- (void)logToWebSocket:(NSString *)flag message:(NSString *)message
{
[_devToolSocketBridge callJSMethod:@"__logger" args:@[flag, message]];
}
#pragma mark Private Mehtods
- (void)_sendQueueLoop
{
WXAssertBridgeThread();
BOOL hasTask = NO;
NSMutableArray *tasks = [NSMutableArray array];
NSString *execIns = nil;
for (NSString *instance in self.insStack) {
NSMutableArray *sendQueue = self.sendQueue[instance];
if(sendQueue.count > 0){
hasTask = YES;
for(WXCallJSMethod *method in sendQueue){
[tasks addObject:[method callJSTask]];
}
[sendQueue removeAllObjects];
execIns = instance;
break;
}
}
if ([tasks count] > 0 && execIns) {
WXSDKInstance * execInstance = [WXSDKManager instanceForID:execIns];
NSTimeInterval start = -1;
if (execInstance && !(execInstance.isJSCreateFinish)) {
start = CACurrentMediaTime()*1000;
}
if (execInstance.instanceJavaScriptContext && execInstance.bundleType) {
[self callJSMethod:@"__WEEX_CALL_JAVASCRIPT__" args:@[execIns, tasks] onContext:execInstance.instanceJavaScriptContext completion:nil];
} else {
[self callJSMethod:@"callJS" args:@[execIns, tasks]];
}
if (execInstance && !(execInstance.isJSCreateFinish)) {
NSTimeInterval diff = CACurrentMediaTime()*1000 - start;
execInstance.performance.fsCallJsNum++;
execInstance.performance.fsCallJsTime = execInstance.performance.fsCallJsTime+ diff;
}
}
if (hasTask) {
[self performSelector:@selector(_sendQueueLoop) withObject:nil];
}
}
+ (void)mountContextEnvironment:(JSContext*)context
{
NSDictionary *data = [WXUtility getEnvironment];
context[@"WXEnvironment"] = data;
// use latin1 encode also named ISO/IEC_8859-1 more: https://en.wikipedia.org/wiki/ISO/IEC_8859-1
// 8-bit single-byte coded graphic character sets as webkit does
// https://github.com/WebKit/webkit/blob/master/Source/WebCore/page/Base64Utilities.cpp
context[@"btoa"] = ^(JSValue *value ) {
NSData *nsdata = [[value toString]
dataUsingEncoding:NSISOLatin1StringEncoding];
NSString *base64Encoded = [nsdata base64EncodedStringWithOptions:0];
return base64Encoded;
};
context[@"atob"] = ^(JSValue *value ) {
NSData *nsdataFromBase64String = [[NSData alloc]
initWithBase64EncodedString:[value toString] options:0];
NSString *base64Decoded = [[NSString alloc]
initWithData:nsdataFromBase64String encoding:NSISOLatin1StringEncoding];
return base64Decoded;
};
context.exceptionHandler = ^(JSContext *context, JSValue *exception){
context.exception = exception;
NSString *errorCode = [NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_WXBRIDGE];;
NSString *bundleUrl = nil;
NSString *message = nil;
NSDictionary *userInfo = nil;
BOOL commitException = YES;
WXSDKInstance * instance = nil;
if ([WXSDKManager sharedInstance].multiContext) {
if (context.instanceId) {
// instance page javaScript runtime exception
instance = [WXSDKManager instanceForID:context.instanceId];
if (instance) {
// instance already existed
commitException = YES;
} else {
// instance already destroyed
commitException = NO;
}
} else {
// weex-main-jsfm.js runtime exception throws
message = [NSString stringWithFormat:@"[WX_KEY_EXCEPTION_WXBRIDGE] [%@:%@:%@] %@\n%@", exception[@"sourceURL"], exception[@"line"], exception[@"column"], [exception toString], [exception[@"stack"] toObject]];
if (!JSValueIsUndefined(context.JSGlobalContextRef, exception[@"sourceURL"].JSValueRef)) {
bundleUrl = exception[@"sourceURL"].toString;
} else {
bundleUrl = @"weex-main-jsfm";
}
userInfo = [NSDictionary dictionary];
}
} else {
instance = [WXSDKEngine topInstance];
}
if (instance) {
bundleUrl = instance.pageName?:([instance.scriptURL absoluteString]?:@"WX_KEY_EXCEPTION_WXBRIDGE");
message = [NSString stringWithFormat:@"[WX_KEY_EXCEPTION_WXBRIDGE] [%@:%@:%@] %@\n%@\n%@\n%@\n%@\n%@", exception[@"sourceURL"], exception[@"line"], exception[@"column"], [exception toString], [exception[@"stack"] toObject], instance.scriptURL.absoluteString, instance.callCreateInstanceContext?:@"", instance.createInstanceContextResult?:@"", instance.executeRaxApiResult?:@""];
userInfo = @{@"jsMainBundleStringContentLength":instance.userInfo[@"jsMainBundleStringContentLength"]?:@"",
@"jsMainBundleStringContentMd5":instance.userInfo[@"jsMainBundleStringContentMd5"]?:@""};
if ([self checkEmptyScreen:instance]) {
errorCode = [NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_EMPTY_SCREEN_JS];
}
}
if (commitException) {
WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:instance.instanceId bundleUrl:bundleUrl errorCode:errorCode functionName:@"" exception:message userInfo:[userInfo mutableCopy]];
[WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo];
WX_MONITOR_FAIL(WXMTJSBridge, WX_ERR_JS_EXECUTE, message);
if (instance.onJSRuntimeException) {
instance.onJSRuntimeException(jsExceptionInfo);
}
}
};
if (WX_SYS_VERSION_LESS_THAN(@"8.0")) {
// solve iOS7 memory problem
context[@"nativeSet"] = [WXPolyfillSet class];
}
context[@"console"][@"error"] = ^(){
[WXBridgeContext handleConsoleOutputWithArgument:[JSContext currentArguments] logLevel:WXLogFlagError];
};
context[@"console"][@"warn"] = ^(){
[WXBridgeContext handleConsoleOutputWithArgument:[JSContext currentArguments] logLevel:WXLogFlagWarning];
};
context[@"console"][@"info"] = ^(){
[WXBridgeContext handleConsoleOutputWithArgument:[JSContext currentArguments] logLevel:WXLogFlagInfo];
};
context[@"console"][@"debug"] = ^(){
[WXBridgeContext handleConsoleOutputWithArgument:[JSContext currentArguments] logLevel:WXLogFlagDebug];
};
context[@"console"][@"log"] = ^(){
[WXBridgeContext handleConsoleOutputWithArgument:[JSContext currentArguments] logLevel:WXLogFlagLog];
};
context[@"nativeLog"] = ^() {
static NSDictionary *levelMap;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
levelMap = @{
@"__ERROR": @(WXLogFlagError),
@"__WARN": @(WXLogFlagWarning),
@"__INFO": @(WXLogFlagInfo),
@"__DEBUG": @(WXLogFlagDebug),
@"__LOG": @(WXLogFlagLog)
};
});
NSArray * args = [JSContext currentArguments];
NSString * levelStr = [[args lastObject] toString];
[WXBridgeContext handleConsoleOutputWithArgument:args logLevel:(WXLogFlag)levelMap[levelStr]];
};
}
+ (void)handleConsoleOutputWithArgument:(NSArray*)arguments logLevel:(WXLogFlag)logLevel
{
NSMutableString *string = [NSMutableString string];
[string appendString:@"jsLog: "];
[arguments enumerateObjectsUsingBlock:^(JSValue *jsVal, NSUInteger idx, BOOL *stop) {
[string appendFormat:@"%@ ", jsVal];
if (idx == arguments.count - 1) {
if (logLevel) {
if (WXLogFlagWarning == logLevel) {
id<WXAppMonitorProtocol> appMonitorHandler = [WXSDKEngine handlerForProtocol:@protocol(WXAppMonitorProtocol)];
if ([appMonitorHandler respondsToSelector:@selector(commitAppMonitorAlarm:monitorPoint:success:errorCode:errorMsg:arg:)]) {
[appMonitorHandler commitAppMonitorAlarm:@"weex" monitorPoint:@"jswarning" success:FALSE errorCode:@"99999" errorMsg:string arg:[WXSDKEngine topInstance].pageName];
}
}
WX_LOG(logLevel, @"%@", string);
} else {
[string appendFormat:@"%@ ", jsVal] ;
WXLogInfo(@"%@", string);
}
}
}];
}
+ (BOOL) checkEmptyScreen:(WXSDKInstance *) instance
{
if(!instance){
return false;
}
if (!(instance.rootView) ) {
return true;
}
CGRect rootFrame = instance.rootView.frame;
if (rootFrame.size.height <=0 || rootFrame.size.width <=0) {
return true;
}
if (!(instance.rootView.subviews) || instance.rootView.subviews.count <=0) {
return true;
}
return false;
}
@end