| /* |
| * 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 "WXAppConfiguration.h" |
| #import "WXInvocationConfig.h" |
| #import "WXComponentMethod.h" |
| #import "WXModuleMethod.h" |
| #import "WXCallJSMethod.h" |
| #import "WXSDKInstance_private.h" |
| #import "WXPrerenderManager.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" |
| #import "WXCoreBridge.h" |
| #import "WXJSFrameworkLoadProtocol.h" |
| #import "WXJSFrameworkLoadDefaultImpl.h" |
| #import "WXHandlerFactory.h" |
| #import "WXExtendCallNativeManager.h" |
| #import "WXDataRenderHandler.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) NSMutableArray *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; |
| |
| @property (nonatomic, readonly) id<WXDataRenderHandler> dataRenderHandler; |
| |
| @end |
| |
| @implementation WXBridgeContext |
| |
| @synthesize dataRenderHandler = _dataRenderHandler; |
| |
| - (instancetype) init |
| { |
| self = [super init]; |
| if (self) { |
| _methodQueue = [NSMutableArray new]; |
| _frameworkLoadFinished = NO; |
| _jsServiceQueue = [NSMutableArray new]; |
| _dataRenderHandler = [WXHandlerFactory handlerForProtocol:@protocol(WXDataRenderHandler)]; |
| if (!_dataRenderHandler) { |
| Class handlerClass = NSClassFromString(@"WXEagleHandler"); |
| if (handlerClass) { |
| _dataRenderHandler = [[handlerClass alloc] init]; |
| [WXSDKEngine registerHandler:_dataRenderHandler withProtocol:@protocol(WXDataRenderHandler)]; |
| } |
| } |
| } |
| 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; |
| } |
| |
| // WXDebugger is a singleton actually and should not call its init twice. |
| _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 registerCallUpdateComponentData:^NSInteger(NSString *instanceId, NSString *componentId, NSString *jsonData) { |
| if (weakSelf.dataRenderHandler) { |
| WXPerformBlockOnComponentThread(^{ |
| long start = [WXUtility getUnixFixTimeMillis]; |
| WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId]; |
| [instance.apmInstance addUpdateComponentDataTimestamp:start]; |
| [weakSelf.dataRenderHandler callUpdateComponentData:instanceId componentId:componentId jsonData:jsonData]; |
| [instance.apmInstance addUpdateComponentDataTime:[WXUtility getUnixFixTimeMillis] - start]; |
| }); |
| } |
| else { |
| WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId]; |
| WXComponentManager *manager = instance.componentManager; |
| if (manager.isValid) { |
| WXSDKErrCode errorCode = WX_KEY_EXCEPTION_DEGRADE_EAGLE_RENDER_ERROR; |
| NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:errorCode userInfo:@{@"message":@"No data render handler found!"}]; |
| WXPerformBlockOnComponentThread(^{ |
| [manager renderFailed:error]; |
| }); |
| } |
| } |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallAddElement:^NSInteger(NSString *instanceId, NSString *parentRef, NSDictionary *elementData, NSInteger index) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callAddElement:instanceId parentRef:parentRef data:elementData index:(int)index]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callAddElement:instanceId parentRef:parentRef data:elementData index:(int)index]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallCreateBody:^NSInteger(NSString *instanceId, NSDictionary *bodyData) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callCreateBody:instanceId data:bodyData]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callCreateBody:instanceId data:bodyData]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallRemoveElement:^NSInteger(NSString *instanceId, NSString *ref) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callRemoveElement:instanceId ref:ref]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callRemoveElement:instanceId ref:ref]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallMoveElement:^NSInteger(NSString *instanceId, NSString *ref, NSString *parentRef, NSInteger index) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callMoveElement:instanceId ref:ref parentRef:parentRef index:(int)index]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callMoveElement:instanceId ref:ref parentRef:parentRef index:(int)index]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallUpdateAttrs:^NSInteger(NSString *instanceId, NSString *ref, NSDictionary *attrsData) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callUpdateAttrs:instanceId ref:ref data:attrsData]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callUpdateAttrs:instanceId ref:ref data:attrsData]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallUpdateStyle:^NSInteger(NSString *instanceId, NSString *ref, NSDictionary *stylesData) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callUpdateStyle:instanceId ref:ref data:stylesData]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callUpdateStyle:instanceId ref:ref data:stylesData]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallAddEvent:^NSInteger(NSString *instanceId, NSString *ref, NSString *event) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callAddEvent:instanceId ref:ref event:event]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callAddEvent:instanceId ref:ref event:event]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallRemoveEvent:^NSInteger(NSString *instanceId, NSString *ref, NSString *event) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callRemoveEvent:instanceId ref:ref event:event]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callRemoveEvent:instanceId ref:ref event:event]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| [_jsBridge registerCallCreateFinish:^NSInteger(NSString *instanceId) { |
| |
| WXSDKInstance *instance = [WXSDKManager instanceForID:instanceId]; |
| [instance.apmInstance onStage:KEY_PAGE_STAGES_CREATE_FINISH]; |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callCreateFinish:instanceId]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callCreateFinish:instanceId]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| |
| if ([_jsBridge respondsToSelector:@selector(registerCallRefreshFinish:)]) { |
| [_jsBridge registerCallRefreshFinish:^NSInteger(NSString *instanceId) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callRefreshFinish:instanceId]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callRefreshFinish:instanceId]; |
| }); |
| } |
| |
| return 0; |
| }]; |
| } |
| |
| if ([_jsBridge respondsToSelector:@selector(registerCallUpdateFinish:)]) { |
| [_jsBridge registerCallUpdateFinish:^NSInteger(NSString *instanceId) { |
| |
| if ([WXCustomPageBridge isCustomPage:instanceId]) { |
| [[WXCustomPageBridge sharedInstance] callUpdateFinish:instanceId]; |
| } |
| else { |
| WXPerformBlockOnComponentThread(^{ |
| [WXCoreBridge callUpdateFinish:instanceId]; |
| }); |
| } |
| |
| 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 ([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; |
| } |
| |
| BOOL intercepted = [instance moduleInterceptWithModuleName:moduleName methodName:methodName arguments:[newArguments copy] options:[newOptions copy]]; |
| if (intercepted) { |
| 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 = [NSMutableArray array]; |
| |
| return _insStack; |
| } |
| |
| - (WXSDKInstance *)topInstance |
| { |
| WXSDKInstance *topInstance = nil; |
| @synchronized(self) { |
| if (self.insStack.count > 0) { |
| topInstance = [WXSDKManager instanceForID:[self.insStack firstObject]]; |
| } |
| } |
| return topInstance; |
| } |
| |
| - (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]; |
| } else { |
| NSString *moduleName = task[@"module"]; |
| NSDictionary *options = task[@"options"]; |
| WXModuleMethod *method = [[WXModuleMethod alloc] initWithModuleName:moduleName methodName:methodName arguments:arguments options:options instance:instance]; |
| [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; |
| } |
| if (instance && !instance.apmInstance.isFSEnd) { |
| NSTimeInterval diff = CACurrentMediaTime()*1000 - startTime; |
| [instance.apmInstance updateFSDiffStats:KEY_PAGE_STATS_FS_CALL_NATIVE_NUM withDiffValue:1]; |
| [instance.apmInstance updateFSDiffStats:KEY_PAGE_STATS_FS_CALL_NATIVE_TIME withDiffValue:diff]; |
| } |
| return 1; |
| } |
| |
| - (void)createInstance:(NSString *)instanceIdString |
| template:(NSString *)jsBundleString |
| options:(NSDictionary *)options |
| data:(id)data |
| { |
| WXAssertBridgeThread(); |
| WXAssertParam(instanceIdString); |
| |
| @synchronized(self) { |
| if (![self.insStack containsObject:instanceIdString]) { |
| if ([options[@"RENDER_IN_ORDER"] boolValue]) { |
| [self.insStack addObject:instanceIdString]; |
| } else { |
| [self.insStack insertObject:instanceIdString atIndex:0]; |
| } |
| } |
| } |
| WXSDKInstance *sdkInstance = [WXSDKManager instanceForID:instanceIdString]; |
| if (!sdkInstance) { |
| return; |
| } |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_START]; |
| |
| //create a sendQueue bind to the current instance |
| NSMutableArray *sendQueue = [NSMutableArray array]; |
| [self.sendQueue setValue:sendQueue forKey:instanceIdString]; |
| |
| if (sdkInstance.dataRender && ![options[@"EXEC_JS"] boolValue]) { |
| if (_dataRenderHandler) { |
| WXPerformBlockOnComponentThread(^{ |
| [_dataRenderHandler createPage:instanceIdString template:jsBundleString options:options data:data]; |
| }); |
| } |
| else { |
| WXComponentManager *manager = sdkInstance.componentManager; |
| if (manager.isValid) { |
| WXSDKErrCode errorCode = WX_KEY_EXCEPTION_DEGRADE_EAGLE_RENDER_ERROR; |
| NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:errorCode userInfo:@{@"message":@"No data render handler found!"}]; |
| WXPerformBlockOnComponentThread(^{ |
| [manager renderFailed:error]; |
| }); |
| } |
| } |
| return; |
| } |
| |
| NSArray *args = nil; |
| WX_MONITOR_INSTANCE_PERF_START(WXFirstScreenJSFExecuteTime, [WXSDKManager instanceForID:instanceIdString]); |
| WX_MONITOR_INSTANCE_PERF_START(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]); |
| |
| NSString * bundleType = [self _pareJSBundleType:instanceIdString jsBundleString:jsBundleString]; // bundleType can be Vue, Rax and the new framework. |
| if (bundleType) { |
| [sdkInstance.apmInstance setProperty:KEY_PAGE_PROPERTIES_BUNDLE_TYPE withValue:bundleType]; |
| NSMutableDictionary *newOptions = [options mutableCopy]; |
| if (!options) { |
| newOptions = [NSMutableDictionary new]; |
| } |
| NSDictionary* immutableEnvDict = [[WXUtility getEnvironment] copy]; |
| if (immutableEnvDict) { |
| [newOptions addEntriesFromDictionary:@{@"env":immutableEnvDict}]; |
| } |
| newOptions[@"bundleType"] = bundleType; |
| __block NSString *raxAPIScript = nil; |
| __block NSString *raxAPIScriptPath = nil; |
| id<WXJSFrameworkLoadProtocol> handler = [WXHandlerFactory handlerForProtocol:@protocol(WXJSFrameworkLoadProtocol)]; |
| sdkInstance.bundleType = bundleType; |
| if ([bundleType.lowercaseString isEqualToString:@"rax"]) { |
| if (handler && [handler respondsToSelector:@selector(loadRaxFramework:)]) { |
| [handler loadRaxFramework:^(NSString *path, NSString *script) { |
| raxAPIScriptPath = path; |
| raxAPIScript = script; |
| }]; |
| } |
| } |
| 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 { |
| NSDictionary* immutableOptions = [newOptions copy]; |
| sdkInstance.callCreateInstanceContext = [NSString stringWithFormat:@"instanceId:%@\noptions:%@\ndata:%@", instanceIdString, immutableOptions, data]; |
| //add instanceId to weexContext ,if fucn createInstanceContext failure ,then we will know which instance has problem (exceptionhandler) |
| self.jsBridge.javaScriptContext[@"wxExtFuncInfo"]= @{ |
| @"func":@"createInstanceContext", |
| @"arg":@"start", |
| @"instanceId":sdkInstance.instanceId?:@"unknownId" |
| }; |
| __weak typeof(self) weakSelf = self; |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_END]; |
| [self callJSMethod:@"createInstanceContext" args:@[instanceIdString, immutableOptions, data?:@[]] onContext:nil completion:^(JSValue *instanceContextEnvironment) { |
| if (sdkInstance.pageName) { |
| [sdkInstance.instanceJavaScriptContext.javaScriptContext setName:sdkInstance.pageName]; |
| } |
| weakSelf.jsBridge.javaScriptContext[@"wxExtFuncInfo"]= nil; |
| |
| NSArray* allKeys = [WXUtility extractPropertyNamesOfJSValueObject:instanceContextEnvironment]; |
| sdkInstance.createInstanceContextResult = [NSString stringWithFormat:@"%@", allKeys]; |
| JSGlobalContextRef instanceContextRef = sdkInstance.instanceJavaScriptContext.javaScriptContext.JSGlobalContextRef; |
| JSObjectRef instanceGlobalObject = JSContextGetGlobalObject(instanceContextRef); |
| |
| for (NSString * key in 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); |
| JSStringRelease(propertyName); |
| } |
| |
| if (WX_SYS_VERSION_LESS_THAN(@"10.2")) { |
| if (handler && [handler respondsToSelector:@selector(loadPolyfillFramework:)]) { |
| [handler loadPolyfillFramework:^(NSString *path, NSString *script) { |
| if (script) { |
| [sdkInstance.instanceJavaScriptContext executeJavascript:script withSourceURL:[NSURL URLWithString:path]]; |
| } else { |
| WXLogError(@"weex-pollyfill can not found"); |
| } |
| }]; |
| } |
| } |
| |
| if (raxAPIScript) { |
| [sdkInstance.instanceJavaScriptContext executeJavascript:raxAPIScript withSourceURL:[NSURL URLWithString:raxAPIScriptPath]]; |
| NSArray* allKeys = [WXUtility extractPropertyNamesOfJSValueObject:sdkInstance.instanceJavaScriptContext.javaScriptContext.globalObject]; |
| sdkInstance.executeRaxApiResult = [NSString stringWithFormat:@"%@", allKeys]; |
| } |
| |
| NSDictionary* funcInfo = @{ |
| @"func":@"createInstance", |
| @"arg":@"start", |
| @"instanceId":sdkInstance.instanceId?:@"unknownId" |
| }; |
| sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"]= funcInfo; |
| if ([NSURL URLWithString:sdkInstance.pageName] || sdkInstance.scriptURL) { |
| [sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString withSourceURL:[NSURL URLWithString:sdkInstance.pageName]?:sdkInstance.scriptURL]; |
| } else { |
| [sdkInstance.instanceJavaScriptContext executeJavascript:jsBundleString]; |
| } |
| sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"] = nil; |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_EXECUTE_BUNDLE_END]; |
| WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]); |
| }]; |
| } |
| |
| } else { |
| [sdkInstance.apmInstance setProperty:KEY_PAGE_PROPERTIES_BUNDLE_TYPE withValue:@"other"]; |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_END]; |
| if (data){ |
| args = @[instanceIdString, jsBundleString, options ?: @{}, data]; |
| } else { |
| args = @[instanceIdString, jsBundleString, options ?: @{}]; |
| } |
| NSDictionary* funcInfo = @{ |
| @"func":@"createInstance", |
| @"arg":@"start", |
| @"instanceId":sdkInstance.instanceId?:@"unknownId" |
| }; |
| sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"] = funcInfo; |
| [self callJSMethod:@"createInstance" args:args]; |
| sdkInstance.instanceJavaScriptContext.javaScriptContext[@"wxExtFuncInfo"] = nil; |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_EXECUTE_BUNDLE_END]; |
| WX_MONITOR_INSTANCE_PERF_END(WXPTJSCreateInstance, [WXSDKManager instanceForID:instanceIdString]); |
| } |
| } |
| |
| - (BOOL) _shouldMountExtInfoToInstanceContxt |
| { |
| id configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)]; |
| BOOL shouldMountInstanceContextExtInfo = YES; |
| if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) { |
| shouldMountInstanceContextExtInfo = [[configCenter configForKey:@"iOS_weex_ext_config.shouldMountInstanceContextExtInfo" defaultValue:@YES isDefault:NULL] boolValue]; |
| } |
| return shouldMountInstanceContextExtInfo; |
| } |
| |
| - (void)createInstance:(NSString *)instanceIdString |
| contents:(NSData *)contents |
| options:(NSDictionary *)options |
| data:(id)data |
| { |
| WXAssertBridgeThread(); |
| WXAssertParam(instanceIdString); |
| |
| @synchronized(self) { |
| if (![self.insStack containsObject:instanceIdString]) { |
| if ([options[@"RENDER_IN_ORDER"] boolValue]) { |
| [self.insStack addObject:instanceIdString]; |
| } else { |
| [self.insStack insertObject:instanceIdString atIndex:0]; |
| } |
| } |
| } |
| WXSDKInstance *sdkInstance = [WXSDKManager instanceForID:instanceIdString]; |
| [sdkInstance.apmInstance onStage:KEY_PAGE_STAGES_LOAD_BUNDLE_START]; |
| |
| //create a sendQueue bind to the current instance |
| NSMutableArray *sendQueue = [NSMutableArray array]; |
| [self.sendQueue setValue:sendQueue forKey:instanceIdString]; |
| |
| if (sdkInstance.dataRender) { |
| if (_dataRenderHandler) { |
| WXPerformBlockOnComponentThread(^{ |
| [_dataRenderHandler createPage:instanceIdString contents:contents options:options data:data]; |
| }); |
| } |
| else { |
| WXComponentManager *manager = sdkInstance.componentManager; |
| if (manager.isValid) { |
| WXSDKErrCode errorCode = WX_KEY_EXCEPTION_DEGRADE_EAGLE_RENDER_ERROR; |
| NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:errorCode userInfo:@{@"message":@"No data render handler found!"}]; |
| WXPerformBlockOnComponentThread(^{ |
| [manager renderFailed:error]; |
| }); |
| } |
| } |
| return; |
| } |
| } |
| |
| - (NSString *)_pareJSBundleType:(NSString*)instanceIdString jsBundleString:(NSString*)jsBundleString |
| { |
| NSString * bundleType = nil; |
| WXSDKInstance * instance = [WXSDKManager instanceForID:instanceIdString]; |
| NSURLComponents * urlComponent = [NSURLComponents componentsWithString:instance.pageName?:@""]; |
| for (NSURLQueryItem * queryItem in urlComponent.queryItems) { |
| if ([queryItem.name isEqualToString:@"bundleType"] && [@[@"Vue", @"Rax", @"vue", @"rax"] containsObject:queryItem.value]) { |
| bundleType = queryItem.value; |
| return bundleType; |
| } |
| } |
| |
| // find first character that is not space or new line character |
| NSCharacterSet* voidCharSet = [NSCharacterSet whitespaceAndNewlineCharacterSet]; |
| NSUInteger length = [jsBundleString length]; |
| NSUInteger validCharacter = 0; |
| while (validCharacter < length && [voidCharSet characterIsMember:[jsBundleString characterAtIndex:validCharacter]]) { |
| validCharacter ++; |
| } |
| if (validCharacter >= length) { |
| return bundleType; |
| } |
| @try { |
| jsBundleString = [jsBundleString substringWithRange:NSMakeRange(validCharacter, MIN(100, length - validCharacter))]; |
| } |
| @catch (NSException* e) {//!OCLint |
| } |
| if ([jsBundleString length] == 0) { |
| return bundleType; |
| } |
| |
| static NSRegularExpression* headerExp = nil; |
| static NSRegularExpression* vueExp = nil; |
| static NSRegularExpression* raxExp = nil; |
| static dispatch_once_t onceToken; |
| dispatch_once(&onceToken, ^{ |
| headerExp = [NSRegularExpression regularExpressionWithPattern:@"^\\s*\\/\\/ *(\\{[^}]*\\}) *\\r?\\n" options:NSRegularExpressionCaseInsensitive error:NULL]; |
| vueExp = [NSRegularExpression regularExpressionWithPattern:@"(use)(\\s+)(weex:vue)" options:NSRegularExpressionCaseInsensitive error:NULL]; |
| raxExp = [NSRegularExpression regularExpressionWithPattern:@"(use)(\\s+)(weex:rax)" options:NSRegularExpressionCaseInsensitive error:NULL]; |
| }); |
| |
| if ( [self _isParserByRegEx]) { |
| NSTextCheckingResult *match = [headerExp firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)]; |
| if (match) { |
| NSString* bundleTypeStr = [jsBundleString substringWithRange:match.range]; |
| bundleTypeStr = [bundleTypeStr stringByReplacingOccurrencesOfString:@"//" withString:@""]; |
| id vale = [WXUtility objectFromJSON:bundleTypeStr]; |
| bundleType = [vale objectForKey:@"framework"]; |
| }else{ |
| NSTextCheckingResult *match = [vueExp firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)]; |
| if (match) { |
| bundleType = [jsBundleString substringWithRange:match.range]; |
| return bundleType; |
| } |
| match = [raxExp firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)]; |
| if (match) { |
| bundleType = [jsBundleString substringWithRange:match.range]; |
| } |
| } |
| }else{ |
| 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 { |
| NSTextCheckingResult *match = [vueExp firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)]; |
| if (match) { |
| bundleType = [jsBundleString substringWithRange:match.range]; |
| return bundleType; |
| } |
| match = [raxExp firstMatchInString:jsBundleString options:0 range:NSMakeRange(0, jsBundleString.length)]; |
| if (match) { |
| bundleType = [jsBundleString substringWithRange:match.range]; |
| } |
| } |
| } |
| return bundleType; |
| } |
| |
| - (bool)_isParserByRegEx |
| { |
| return false; |
| // bool useRegEx = true; |
| // id<WXConfigCenterProtocol> configCenter = [WXSDKEngine handlerForProtocol:@protocol(WXConfigCenterProtocol)]; |
| // |
| // if ([configCenter respondsToSelector:@selector(configForKey:defaultValue:isDefault:)]) { |
| // useRegEx = [[configCenter configForKey:@"iOS_weex_ext_config.parserTypeByRegEx" defaultValue:@(YES) isDefault:NULL] boolValue]; |
| // } |
| // return useRegEx; |
| } |
| |
| - (void)destroyInstance:(NSString *)instance |
| { |
| WXAssertBridgeThread(); |
| WXAssertParam(instance); |
| |
| //remove this instance exception history |
| [WXExceptionUtils removeExceptionHistory:instance]; |
| |
| //remove instance from stack |
| @synchronized(self) { |
| [self.insStack removeObject:instance]; |
| } |
| |
| if(_jsBridge && [_jsBridge respondsToSelector:@selector(removeTimers:)]){ |
| [_jsBridge removeTimers:instance]; |
| } |
| |
| if(self.sendQueue[instance]){ |
| [self.sendQueue removeObjectForKey:instance]; |
| } |
| |
| WXSDKInstance *sdkInstance = [WXSDKManager instanceForID:instance]; |
| if (!sdkInstance.wlasmRender) { |
| [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; |
| |
| WXSDKInstance *sdkInstance = [WXSDKManager instanceForID:instance]; |
| if (sdkInstance.dataRender) { |
| if (!_dataRenderHandler) { |
| WXComponentManager *manager = sdkInstance.componentManager; |
| if (manager.isValid) { |
| WXSDKErrCode errorCode = WX_KEY_EXCEPTION_DEGRADE_EAGLE_RENDER_ERROR; |
| NSError *error = [NSError errorWithDomain:WX_ERROR_DOMAIN code:errorCode userInfo:@{@"message":@"No data render handler found!"}]; |
| WXPerformBlockOnComponentThread(^{ |
| [manager renderFailed:error]; |
| }); |
| } |
| return; |
| } |
| WXPerformBlockOnComponentThread(^{ |
| if ([data isKindOfClass:[NSDictionary class]]) { |
| [_dataRenderHandler refreshDataRenderInstance:instance data:[WXUtility JSONString:data]]; |
| } else if ([data isKindOfClass:[NSString class]]) { |
| [_dataRenderHandler refreshDataRenderInstance:instance data:data]; |
| } |
| }); |
| [[WXSDKManager bridgeMgr] callJSMethod:@"callJS" args:@[instance, @[@{@"method":@"fireEvent", @"args":@[@"", @"refresh", [WXUtility objectFromJSON:@"[]"], @"", @{@"params":@[@{@"data":data}]}]}]]]; |
| } else { |
| [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))completion |
| { |
| NSMutableArray *newArg = nil; |
| if (!bridge) { |
| bridge = self.jsBridge; |
| } |
| if (self.frameworkLoadFinished) { |
| WXLogDebug(@"Calling JS... method:%@, args:%@", method, args); |
| if (([bridge isKindOfClass:[WXJSCoreBridge class]]) || |
| ([bridge isKindOfClass:NSClassFromString(@"WXDebugger") ]) ) { |
| JSValue *value = [bridge callJSMethod:method args:args]; |
| if (completion) { |
| completion(value); |
| } |
| } else { |
| [bridge callJSMethod:method args:args]; |
| } |
| } else { |
| newArg = [args mutableCopy]; |
| if (completion) { |
| [newArg addObject:completion]; |
| } |
| [_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!"); |
| if ([self.jsBridge respondsToSelector:@selector(javaScriptContext)]) { |
| NSDictionary* funcInfo = @{ |
| @"func":@"executeJsService", |
| @"arg":name?:@"unsetScriptName" |
| }; |
| self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = funcInfo; |
| } |
| [self.jsBridge executeJavascript:script]; |
| if ([self.jsBridge respondsToSelector:@selector(javaScriptContext)]) { |
| self.jsBridge.javaScriptContext[@"wxExtFuncInfo"] = nil; |
| } |
| |
| if ([self.jsBridge exception]) { |
| NSString *exception = [[self.jsBridge exception] toString]; |
| NSMutableString *errMsg = [NSMutableString stringWithFormat:@"[WX_KEY_EXCEPTION_INVOKE_JSSERVICE_EXECUTE] name:%@,arg:%@,exception :$@",name,exception]; |
| [WXExceptionUtils commitCriticalExceptionRT:@"WX_KEY_EXCEPTION_INVOKE" errCode:[NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_INVOKE] function:@"executeJsService" exception:errMsg extParams:nil]; |
| WX_MONITOR_FAIL(WXMTJSService, WX_ERR_JSFRAMEWORK_EXECUTE, errMsg); |
| } |
| }else { |
| [_jsServiceQueue addObject:@{ |
| @"name": name, |
| @"script": script |
| }]; |
| } |
| } |
| |
| - (void)registerModules:(NSDictionary *)modules |
| { |
| WXAssertBridgeThread(); |
| |
| if(!modules) return; |
| |
| [self callJSMethod:@"registerModules" args:@[modules]]; |
| if (_dataRenderHandler) { |
| WXPerformBlockOnComponentThread(^{ |
| [_dataRenderHandler registerModules:modules]; |
| }); |
| } |
| } |
| |
| - (void)registerComponents:(NSArray *)components |
| { |
| WXAssertBridgeThread(); |
| |
| if(!components) return; |
| |
| [self callJSMethod:@"registerComponents" args:@[components]]; |
| if (_dataRenderHandler) { |
| WXPerformBlockOnComponentThread(^{ |
| [_dataRenderHandler registerComponents: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; |
| |
| @synchronized(self) { |
| 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 = CACurrentMediaTime()*1000; |
| |
| if (execInstance.instanceJavaScriptContext && execInstance.bundleType) { |
| [self callJSMethod:@"__WEEX_CALL_JAVASCRIPT__" args:@[execIns, [tasks copy]] onContext:execInstance.instanceJavaScriptContext completion:nil]; |
| } else { |
| [self callJSMethod:@"callJS" args:@[execIns, [tasks copy]]]; |
| } |
| if (execInstance) { |
| NSTimeInterval diff = CACurrentMediaTime()*1000 - start; |
| execInstance.performance.callJsNum++; |
| execInstance.performance.callJsTime = execInstance.performance.callJsTime+ diff; |
| } |
| if (execInstance && !execInstance.apmInstance.isFSEnd) { |
| NSTimeInterval diff = CACurrentMediaTime()*1000 - start; |
| [execInstance.apmInstance updateFSDiffStats:KEY_PAGE_STATS_FS_CALL_JS_NUM withDiffValue:1]; |
| [execInstance.apmInstance updateFSDiffStats:KEY_PAGE_STATS_FS_CALL_JS_TIME withDiffValue: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; |
| }; |
| |
| // Avoid exceptionHandler be recursively invoked and finally cause stack overflow. |
| static BOOL gInExceptionHandler = NO; |
| context.exceptionHandler = ^(JSContext *context, JSValue *exception){ |
| if (gInExceptionHandler) { |
| return; |
| } |
| gInExceptionHandler = YES; |
| |
| @try { |
| BOOL tryFindInstanceInfoInGlobalContext = NO; |
| 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 (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] [%@:%@:%@] %@ js stack: %@", 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]; |
| } |
| |
| NSDictionary* wxExtFuncInfo = [context[@"wxExtFuncInfo"] toDictionary]; |
| NSString* recordFunc = [wxExtFuncInfo objectForKey:@"func"]; |
| NSString* recordArg = [wxExtFuncInfo objectForKey:@"arg"]; |
| NSString* recordInsstanceId = [wxExtFuncInfo objectForKey:@"instanceId"]; |
| if (nil == instance) { |
| instance = [WXSDKManager instanceForID:recordInsstanceId]; |
| tryFindInstanceInfoInGlobalContext = nil!= instance; |
| } |
| |
| if(nil != instance && [recordFunc isEqualToString:@"createInstance"] && !instance.apmInstance.hasAddView){ |
| errorCode = [NSString stringWithFormat:@"%d", WX_KEY_EXCEPTION_EMPTY_SCREEN_JS]; |
| } |
| |
| if (instance) { |
| bundleUrl = instance.pageName?:([instance.scriptURL absoluteString]?:@"WX_KEY_EXCEPTION_WXBRIDGE"); |
| message = [NSString stringWithFormat:@"[WX_KEY_EXCEPTION_WXBRIDGE] exception: %@\n stack:%@",[exception toString],[exception[@"stack"] toObject]]; |
| userInfo = @{@"jsMainBundleStringContentLength":instance.userInfo[@"jsMainBundleStringContentLength"]?:@"", |
| @"jsMainBundleStringContentMd5":instance.userInfo[@"jsMainBundleStringContentMd5"]?:@"", |
| @"sourceURL":[NSString stringWithFormat:@"%@:%@:%@",exception[@"sourceURL"],exception[@"line"],exception[@"column"]], |
| @"callCreateInstanceContext":instance.callCreateInstanceContext?:@"", |
| @"createInstanceContextResult": instance.createInstanceContextResult?:@"", |
| @"executeRaxApiResult":instance.executeRaxApiResult?:@"" |
| }; |
| } |
| |
| if (commitException) { |
| NSMutableDictionary* reportInfo = [[NSMutableDictionary alloc] initWithDictionary:[userInfo mutableCopy]]; |
| [reportInfo setObject:context.name?:@"unknownContextName" forKey:@"wxContextName"]; |
| |
| WXJSExceptionInfo * jsExceptionInfo = [[WXJSExceptionInfo alloc] initWithInstanceId:instance.instanceId bundleUrl:bundleUrl errorCode:errorCode functionName:[NSString stringWithFormat:@"func: %@ arg:%@",recordFunc,recordArg]?:@"exceptionHandler" exception:message userInfo:reportInfo]; |
| |
| if (nil == instance) { |
| [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo]; |
| }else{ |
| [WXExceptionUtils commitCriticalExceptionRT:jsExceptionInfo.instanceId errCode:jsExceptionInfo.errorCode function:jsExceptionInfo.functionName exception:jsExceptionInfo.exception extParams:jsExceptionInfo.userInfo]; |
| } |
| |
| WX_MONITOR_FAIL(WXMTJSBridge, WX_ERR_JS_EXECUTE, message); |
| if (instance.onJSRuntimeException && !tryFindInstanceInfoInGlobalContext) { |
| instance.onJSRuntimeException(jsExceptionInfo); |
| } |
| } |
| } |
| @finally { |
| gInExceptionHandler = NO; |
| } |
| }; |
| |
| 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] integerValue]]; |
| }; |
| |
| context[@"extendCallNative"] = ^(JSValue *value ) { |
| return [WXBridgeContext extendCallNative:[value toDictionary]]; |
| }; |
| } |
| |
| + (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 || WXLogFlagError == logLevel) { |
| id<WXAppMonitorProtocol> appMonitorHandler = [WXSDKEngine handlerForProtocol:@protocol(WXAppMonitorProtocol)]; |
| if ([appMonitorHandler respondsToSelector:@selector(commitAppMonitorAlarm:monitorPoint:success:errorCode:errorMsg:arg:)]) { |
| [appMonitorHandler commitAppMonitorAlarm:@"weex" monitorPoint:@"jswarning" success:NO errorCode:@"99999" errorMsg:string arg:[WXSDKEngine topInstance].pageName]; |
| } |
| } |
| WX_LOG(logLevel, @"%@", string); |
| } else { |
| [string appendFormat:@"%@ ", jsVal]; |
| WXLogInfo(@"%@", string); |
| } |
| } |
| }]; |
| } |
| |
| +(id)extendCallNative:(NSDictionary *)dict |
| { |
| if(dict){ |
| return [WXExtendCallNativeManager sendExtendCallNativeEvent:dict]; |
| } |
| return @(-1); |
| } |
| @end |