/*! * iOS SDK * * Tencent is pleased to support the open source community by making * Hippy available. * * Copyright (C) 2019 THL A29 Limited, a Tencent company. * All rights reserved. * * Licensed 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 "HippyJSExecutor.h" #import "HippyJSExecutor+Internal.h" #import "VFSUriHandler.h" #import "HippyAssert.h" #import "HippyBundleURLProvider.h" #import "HippyDefines.h" #import "HippyDevInfo.h" #import "HippyDevMenu.h" #import "HippyJSEnginesMapper.h" #import "HippyOCTurboModule+Inner.h" #import "HippyRedBox.h" #import "HippyUtils.h" #import "HippyTurboModuleManager.h" #import "HippyLog.h" #import "HippyUtils.h" #import "HippyFootstoneUtils.h" #import "NSObject+CtxValue.h" #import "TypeConverter.h" #import "HippyBridge+Private.h" #import "HippyBridge+ModuleManage.h" #include #include #include #include #include #include "driver/engine.h" #include "driver/napi/js_ctx.h" #include "driver/napi/js_ctx_value.h" #include "driver/napi/js_try_catch.h" #include "driver/napi/callback_info.h" #include "driver/vm/js_vm.h" #include "driver/scope.h" #include "driver/js_driver_utils.h" #include "footstone/string_view.h" #include "footstone/string_view_utils.h" #include "footstone/task_runner.h" #include "footstone/task.h" #include "vfs/handler/uri_handler.h" #ifdef ENABLE_INSPECTOR #include "devtools/devtools_data_source.h" #endif #ifdef JS_JSC #include "driver/napi/jsc/jsc_ctx.h" #include "driver/napi/jsc/jsc_ctx_value.h" #endif #ifdef JS_HERMES #include "driver/napi/hermes/hermes_ctx.h" #include "driver/napi/hermes/hermes_ctx_value.h" #endif using string_view = footstone::stringview::string_view; using StringViewUtils = footstone::stringview::StringViewUtils; using SharedCtxPtr = std::shared_ptr; using WeakCtxPtr = std::weak_ptr; using SharedCtxValuePtr = std::shared_ptr; using WeakCtxValuePtr = std::weak_ptr; constexpr char kGlobalKey[] = "global"; constexpr char kHippyKey[] = "Hippy"; constexpr char kHippyNativeGlobalKey[] = "__HIPPYNATIVEGLOBAL__"; constexpr char kHippyRequireModuleConfigFuncKey[] = "nativeRequireModuleConfig"; constexpr char kHippyFlushQueueImmediateFuncKey[] = "nativeFlushQueueImmediate"; constexpr char kHippyGetTurboModule[] = "getTurboModule"; @interface HippyJSExecutor () { // The hippy scope std::shared_ptr _pScope; // Whether js engine is inspectable, currently for jsc BOOL _isInspectable; } /// Whether JSExecutor has done setup. @property (nonatomic, assign) BOOL ready; /// Pending blocks to be executed on JS queue. @property (nonatomic, strong) NSMutableArray *pendingCalls;; @end @implementation HippyJSExecutor @synthesize pScope = _pScope; - (void)setup { HippyBridge *bridge = self.bridge; const std::string engineType = bridge.usingHermesEngine ? hippy::VM::kJSEngineHermes : hippy::VM::kJSEngineJSC; auto engine = [[HippyJSEnginesMapper defaultInstance] createJSEngineResourceForKey:self.enginekey engineType:engineType isDebug:bridge.debugMode]; const char *pName = [self.enginekey UTF8String] ?: ""; auto scope = engine->GetEngine()->CreateScope(pName); // Assign pScope first, then register the callback _pScope = scope; __weak __typeof(self)weakSelf = self; if (!bridge.usingHermesEngine) { hippy::base::RegisterFunction taskEndCB = [weakSelf](void *) { __strong __typeof(weakSelf)strongSelf = weakSelf; if (strongSelf) { handleJsExcepiton(strongSelf.pScope); } }; scope->RegisterExtraCallback(hippy::kAsyncTaskEndKey, taskEndCB); } dispatch_semaphore_t scopeSemaphore = dispatch_semaphore_create(0); footstone::TimePoint startPoint = footstone::TimePoint::SystemNow(); engine->GetEngine()->GetJsTaskRunner()->PostTask([weakSelf, scopeSemaphore, startPoint](){ __strong __typeof(weakSelf)strongSelf = weakSelf; HippyBridge *bridge = strongSelf.bridge; if (!bridge) { return; } dispatch_semaphore_wait(scopeSemaphore, DISPATCH_TIME_FOREVER); auto scope = strongSelf.pScope; scope->CreateContext(); auto context = scope->GetContext(); auto global_object = context->GetGlobalObject(); #if defined(ENABLE_INSPECTOR) && defined(JS_HERMES) // setup debugger agent for hermes setupDebuggerAgent(bridge, context, weakSelf); #endif /* defined(ENABLE_INSPECTOR) && defined(JS_HERMES) */ // add `global` property to global object auto user_global_object_key = context->CreateString(kGlobalKey); context->SetProperty(global_object, user_global_object_key, global_object); // add `Hippy` property to global object auto hippy_key = context->CreateString(kHippyKey); context->SetProperty(global_object, hippy_key, context->CreateObject()); // inject device info to `__HIPPYNATIVEGLOBAL__` [strongSelf injectDeviceInfoAsHippyNativeGlobal:bridge context:context globalObject:global_object]; // register `nativeRequireModuleConfig` function [strongSelf registerRequiredModuleConfigFuncToJS:context globalObject:global_object scope:scope]; // register `nativeFlushQueueImmediate` function [strongSelf registerFlushQueueImmediateFuncToJS:context globalObject:global_object scope:scope]; // register `getTurboModule` function [strongSelf registerGetTurboModuleFuncToJS:context globalObject:global_object scope:scope]; // call finish block if (strongSelf.contextCreatedBlock) { strongSelf.contextCreatedBlock(); } scope->SyncInitialize(); // performance record footstone::TimePoint endTime = footstone::TimePoint::SystemNow(); auto entry = scope->GetPerformance()->PerformanceNavigation(hippy::kPerfNavigationHippyInit); if (entry) { entry->SetHippyJsEngineInitStart(startPoint); entry->SetHippyJsEngineInitEnd(endTime); entry->SetHippyNativeInitStart(strongSelf.bridge.startTime); entry->SetHippyNativeInitEnd(endTime); } // the last, execute pending blocks NSArray *pendingCalls; @synchronized (strongSelf) { strongSelf.ready = YES; pendingCalls = [strongSelf.pendingCalls copy]; [strongSelf.pendingCalls removeAllObjects]; } [pendingCalls enumerateObjectsUsingBlock:^(dispatch_block_t _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { [strongSelf executeBlockOnJavaScriptQueue:obj]; }]; }); dispatch_semaphore_signal(scopeSemaphore); #ifdef ENABLE_INSPECTOR [self executeAsyncBlockOnJavaScriptQueue:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; HippyBridge *bridge = strongSelf.bridge; if (!bridge) { return; } if (bridge && bridge.debugMode) { NSString *wsURL = [strongSelf completeWSURLWithBridge:bridge]; auto scope = strongSelf->_pScope; if (wsURL.length > 0 && scope) { auto engine = scope->GetEngine().lock(); if (!engine) { return; } auto workerManager = std::make_shared(1); auto devtools_data_source = std::make_shared(); devtools_data_source->CreateDevtoolsService([wsURL UTF8String], workerManager); if (bridge.usingHermesEngine) { // InitDevTools will bind devtools_data_source to scope hippy::JsDriverUtils::InitDevTools(scope, engine->GetVM(), devtools_data_source); } else { scope->SetDevtoolsDataSource(devtools_data_source); } } } }]; #endif } - (instancetype)initWithEngineKey:(NSString *)engineKey bridge:(HippyBridge *)bridge { NSParameterAssert(engineKey.length > 0); if (self = [super init]) { _valid = YES; _ready = NO; _enginekey = engineKey; _bridge = bridge; _pendingCalls = [NSMutableArray array]; HippyLogInfo(@"[Hippy_OC_Log][Life_Circle],HippyJSCExecutor Init %p, engineKey:%@", self, engineKey); } return self; } - (void)dealloc { HippyLogInfo(@"[Hippy_OC_Log][Life_Circle],HippyJSCExecutor dealloc %p", self); [self invalidate]; } - (void)invalidate { if (!self.isValid) { return; } _valid = NO; HippyLogInfo(@"[Hippy_OC_Log][Life_Circle],HippyJSCExecutor invalide %p", self); #ifdef ENABLE_INSPECTOR auto devtools_data_source = self.pScope->GetDevtoolsDataSource(); if (devtools_data_source) { HippyBridge *bridge = self.bridge; // Note: bridge may be nil bool reload = (bridge && bridge.invalidateReason == HippyInvalidateReasonReload) ? true : false; devtools_data_source->Destroy(reload); } #endif /* ENABLE_INSPECTOR */ self.pScope->WillExit(); _pScope = nullptr; NSString *enginekey = self.enginekey; if (!enginekey) { return; } dispatch_async(dispatch_get_main_queue(), ^{ auto engineRsc = [[HippyJSEnginesMapper defaultInstance] JSEngineResourceForKey:enginekey]; [[HippyJSEnginesMapper defaultInstance] removeEngineResourceForKey:enginekey]; dispatch_async(dispatch_get_main_queue(), ^{ // Make a tiny delay to ensure the engine resource is released on the main thread HippyLogInfo(@"Remove EngineRsc, UseCount:%ld", engineRsc.use_count()); }); }); } #pragma mark - Subprocedures of Setup #if defined(ENABLE_INSPECTOR) && defined(JS_HERMES) static void setupDebuggerAgent(HippyBridge *bridge, const std::shared_ptr &context, HippyJSExecutor *const __weak weakSelf) { if (bridge.debugMode && bridge.usingHermesEngine) { // Create cdp agent for hermes auto hermesCtx = std::static_pointer_cast(context); hermesCtx->SetupDebugAgent([weakSelf](facebook::hermes::debugger::RuntimeTask fn) { __strong __typeof(weakSelf)strongSelf = weakSelf; auto scope = strongSelf.pScope; if (scope && scope->GetTaskRunner() && scope->GetContext()) { auto hermes_ctx = std::static_pointer_cast(scope->GetContext()); scope->GetTaskRunner()->PostTask(^{ fn(*hermes_ctx->GetRuntime()); }); } }, [weakSelf](const std::string &message) { // Process CDP response or event and send message back to the Chrome debugger // HippyLogTrace(@"To Debugger: %s\n", message.c_str()); __strong __typeof(weakSelf)strongSelf = weakSelf; auto scope = strongSelf.pScope; if (scope) { auto devtoolsDataSource = scope->GetDevtoolsDataSource(); if (devtoolsDataSource) { devtoolsDataSource->GetNotificationCenter()->vm_response_notification->ResponseToFrontend(message); }; } }); } } #endif /* defined(ENABLE_INSPECTOR) && defined(JS_HERMES) */ - (void)injectDeviceInfoAsHippyNativeGlobal:(HippyBridge *)bridge context:(const std::shared_ptr &)context globalObject:(const std::shared_ptr &)globalObject { NSMutableDictionary *deviceInfo = [NSMutableDictionary dictionaryWithDictionary:[bridge deviceInfo]]; NSDictionary *debugInfo = @{@"Debug" : @{@"debugClientId" : [self getClientID]}}; [deviceInfo addEntriesFromDictionary:debugInfo]; auto key = context->CreateString(kHippyNativeGlobalKey); auto value = [deviceInfo convertToCtxValue:context]; if (key && value) { context->SetProperty(globalObject, key, value); } } - (void)registerRequiredModuleConfigFuncToJS:(const std::shared_ptr &)context globalObject:(const std::shared_ptr &)globalObject scope:(const std::shared_ptr &)scope { __weak __typeof(self)weakSelf = self; auto requireModuleConfigFunWrapper = std::make_unique([](hippy::CallbackInfo& info, void* data) { HippyJSExecutor *strongSelf = (__bridge HippyJSExecutor*)data; HippyBridge *bridge = strongSelf.bridge; if (!strongSelf.valid || !bridge || !strongSelf.pScope) { return; } const auto &context = strongSelf.pScope->GetContext(); if (context->IsString(info[0])) { NSString *moduleName = ObjectFromCtxValue(context, info[0]); if (moduleName) { NSArray *result = [bridge configForModuleName:moduleName]; info.GetReturnValue()->Set([HippyNullIfNil(result) convertToCtxValue:context]); } } }, (__bridge void*)weakSelf); auto requireModuleConfigFunction = context->CreateFunction(requireModuleConfigFunWrapper); scope->SaveFunctionWrapper(std::move(requireModuleConfigFunWrapper)); context->SetProperty(globalObject, context->CreateString(kHippyRequireModuleConfigFuncKey), requireModuleConfigFunction); } - (void)registerFlushQueueImmediateFuncToJS:(const std::shared_ptr &)context globalObject:(const std::shared_ptr &)globalObject scope:(const std::shared_ptr &)scope { __weak __typeof(self)weakSelf = self; auto nativeFlushQueueFunWrapper = std::make_unique([](hippy::CallbackInfo& info, void* data) { HippyJSExecutor *strongSelf = (__bridge HippyJSExecutor*)data; HippyBridge *bridge = strongSelf.bridge; if (!strongSelf.valid || !bridge || !strongSelf.pScope) { return; } const auto &context = strongSelf.pScope->GetContext(); if (context->IsArray(info[0])) { NSArray *calls = ObjectFromCtxValue(context, info[0]); [bridge handleBuffer:calls batchEnded:NO]; } }, (__bridge void*)weakSelf); auto nativeFlushQueueFunction = context->CreateFunction(nativeFlushQueueFunWrapper); scope->SaveFunctionWrapper(std::move(nativeFlushQueueFunWrapper)); context->SetProperty(globalObject, context->CreateString(kHippyFlushQueueImmediateFuncKey), nativeFlushQueueFunction); } - (void)registerGetTurboModuleFuncToJS:(const std::shared_ptr &)context globalObject:(const std::shared_ptr &)globalObject scope:(const std::shared_ptr &)scope { __weak __typeof(self)weakSelf = self; auto turbo_wrapper = std::make_unique([](hippy::CallbackInfo& info, void* data) { HippyJSExecutor *strongSelf = (__bridge HippyJSExecutor*)data; if (!strongSelf || !strongSelf.pScope) { return; } const auto &context = strongSelf.pScope->GetContext(); if (context->IsString(info[0])) { NSString *name = ObjectFromCtxValue(context, info[0]); auto value = [strongSelf JSTurboObjectWithName:name]; info.GetReturnValue()->Set(value); } }, (__bridge void*)weakSelf); auto turbo_function = context->CreateFunction(turbo_wrapper); scope->SaveFunctionWrapper(std::move(turbo_wrapper)); context->SetProperty(globalObject, context->CreateString(kHippyGetTurboModule), turbo_function); } #pragma mark - - (NSString *)getClientID { NSString *deviceName = [[UIDevice currentDevice] name]; NSString *clientId = HippyMD5Hash([NSString stringWithFormat:@"%@%p", deviceName, self]); return clientId; } - (void)setUriLoader:(std::weak_ptr)uriLoader { if (self.pScope->GetUriLoader().lock() != uriLoader.lock()) { self.pScope->SetUriLoader(uriLoader); } } - (void)setSandboxDirectory:(NSString *)directory { if (directory) { __weak HippyJSExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ HippyJSExecutor *strongSelf = weakSelf; if (!strongSelf) { return; } HippyAssert(strongSelf.pScope, @"scope must not be null"); HippyAssert(strongSelf.pScope->GetContext(), @"context must not be null"); auto context = strongSelf.pScope->GetContext(); auto global_object = context->GetGlobalObject(); auto key = context->CreateString("__HIPPYCURDIR__"); auto value = context->CreateString(NSStringToU8StringView(directory)); context->SetProperty(global_object, key, value); }]; } } - (SharedCtxValuePtr)JSTurboObjectWithName:(NSString *)name { // create HostObject by name HippyBridge *bridge = self.bridge; HippyOCTurboModule *turboModule = [bridge turboModuleWithName:name]; auto scope = self.pScope; auto context = scope->GetContext(); if (!turboModule) { return context->CreateNull(); } // create jsProxy std::string turbo_name([name UTF8String]); if (scope->HasTurboInstance(turbo_name)) { return scope->GetTurboInstance(turbo_name); } CFTypeRef retainedTurboModule = CFBridgingRetain(turboModule); auto wrapper = std::make_unique([](hippy::CallbackInfo& info, void* data) { auto name = info[0]; if (!name) { CFRelease(data); return; } HippyOCTurboModule *turbo = (__bridge HippyOCTurboModule*)data; auto turbo_wrapper = std::make_unique(turbo, info[0]); auto func_wrapper = std::make_unique([](hippy::CallbackInfo& info, void* data) { std::vector> argv; for (size_t i = 0; i < info.Length(); ++i) { argv.push_back(info[i]); } std::any slot_any = info.GetSlot(); auto any_pointer = std::any_cast(&slot_any); auto scope_wrapper = reinterpret_cast(static_cast(*any_pointer)); auto scope = scope_wrapper->scope.lock(); FOOTSTONE_CHECK(scope); auto turbo_wrapper = reinterpret_cast(data); HippyOCTurboModule *turbo = turbo_wrapper->module; auto name = turbo_wrapper->name; auto result = [turbo invokeOCMethod:scope->GetContext() this_val:name args:argv.data() count:argv.size()]; info.GetReturnValue()->Set(result); }, turbo_wrapper.get()); [turbo saveTurboWrapper:name turbo:std::move(turbo_wrapper)]; std::any slot_any = info.GetSlot(); auto any_pointer = std::any_cast(&slot_any); auto scope_wrapper = reinterpret_cast(static_cast(*any_pointer)); auto scope = scope_wrapper->scope.lock(); FOOTSTONE_CHECK(scope); auto func = scope->GetContext()->CreateFunction(func_wrapper); scope->SaveFunctionWrapper(std::move(func_wrapper)); info.GetReturnValue()->Set(func); CFRelease(data); }, (void *)retainedTurboModule); #ifdef JS_HERMES // Save function wrapper if (bridge.usingHermesEngine) { auto proxy = scope->GetContext()->DefineProxy(nullptr); auto handler = scope->GetContext()->DefineProxyHandler(wrapper); auto proxy_ctx = std::static_pointer_cast(proxy); auto handler_ctx = std::static_pointer_cast(handler); auto& runtime = std::static_pointer_cast(scope->GetContext())->GetRuntime(); auto constructor = proxy_ctx->GetValue(runtime).asObject(*runtime).asFunction(*runtime); auto instance = constructor.callAsConstructor(*runtime, { handler_ctx->GetValue(runtime) }); auto obj = std::make_shared(*runtime, instance); scope->SaveFunctionWrapper(std::move(wrapper)); scope->SetTurboInstance(turbo_name, obj); return obj; } else { auto obj = scope->GetContext()->DefineProxy(wrapper); scope->SaveFunctionWrapper(std::move(wrapper)); scope->SetTurboInstance(turbo_name, obj); return obj; } #else auto obj = scope->GetContext()->DefineProxy(wrapper); scope->SaveFunctionWrapper(std::move(wrapper)); scope->SetTurboInstance(turbo_name, obj); return obj; #endif /* JS_HERMES */ } - (void)setContextName:(NSString *)contextName { #ifdef JS_JSC #ifdef JS_HERMES // SetContextName not support Hermes now if (self.bridge.usingHermesEngine) { return; } #endif /* JS_HERMES */ if (!contextName) { return; } __weak __typeof(self)weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (!strongSelf || !strongSelf.pScope || !strongSelf.pScope->isValid()) { return; } SharedCtxPtr context = strongSelf.pScope->GetContext(); if (!context) { return; } auto tryCatch = hippy::TryCatch::CreateTryCatchScope(true, context); auto jscContext = std::static_pointer_cast(context); NSString *finalName = [NSString stringWithFormat:@"HippyContext: %@", contextName]; jscContext->SetName((__bridge CFStringRef)finalName); if (tryCatch->HasCaught()) { HippyLogError(@"Exception while setting Context Name!"); } }]; #endif //JS_JSC } - (void)setInspecable:(BOOL)inspectable { #ifdef JS_HERMES // judge js engine type if (self.bridge.usingHermesEngine) { return; } #endif /* JS_HERMES */ #ifdef JS_JSC _isInspectable = inspectable; #if defined(__IPHONE_16_4) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_4 if (@available(iOS 16.4, *)) { WeakCtxPtr weak_ctx = self.pScope->GetContext(); [self executeBlockOnJavaScriptQueue:^{ SharedCtxPtr context = weak_ctx.lock(); if (!context) { return; } auto jsc_context = std::static_pointer_cast(context); JSGlobalContextRef contextRef = jsc_context->context_; JSGlobalContextSetInspectable(contextRef, inspectable); }]; } #endif //defined(__IPHONE_16_4) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_16_4 #endif //JS_JSC } - (void)updateNativeInfoToHippyGlobalObject:(NSDictionary *)updatedInfoDict { if (updatedInfoDict.count <= 0){ return; } __weak __typeof(self)weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid || nullptr == strongSelf.pScope) { return; } [strongSelf addInfoToGlobalObject:updatedInfoDict.copy]; }]; } - (void)addInfoToGlobalObject:(NSDictionary*)addInfoDict{ string_view str(kHippyNativeGlobalKey); auto context = self.pScope->GetContext(); auto global_object = context->GetGlobalObject(); auto hippy_native_object_key = context->CreateString(str); auto hippy_native_object_value = context->GetProperty(global_object, hippy_native_object_key); HippyAssert(hippy_native_object_value, @"__HIPPYNATIVEGLOBAL__ must not be null"); if (!context->IsNullOrUndefined(hippy_native_object_value)) { for (NSString *key in addInfoDict) { id value = addInfoDict[key]; string_view key_string = NSStringToU8StringView(key); auto ctx_value = [value convertToCtxValue:context]; context->SetProperty(hippy_native_object_value, context->CreateString(key_string), ctx_value, hippy::napi::PropertyAttribute::None); } } } - (void)flushedQueue:(HippyJavaScriptCallback)onComplete { [self _executeJSCall:@"flushedQueue" arguments:@[] callback:onComplete]; } - (void)_callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(HippyJavaScriptCallback)onComplete { static NSString *bridgeMethod = @"callFunctionReturnFlushedQueue"; [self _executeJSCall:bridgeMethod arguments:@[module, method, args] callback:onComplete]; } - (void)callFunctionOnModule:(NSString *)module method:(NSString *)method arguments:(NSArray *)args callback:(HippyJavaScriptCallback)onComplete { [self _callFunctionOnModule:module method:method arguments:args callback:onComplete]; } - (void)invokeCallbackID:(NSNumber *)cbID arguments:(NSArray *)args callback:(HippyJavaScriptCallback)onComplete { [self _executeJSCall:@"invokeCallbackAndReturnFlushedQueue" arguments:@[cbID, args] callback:onComplete]; } - (void)_executeJSCall:(NSString *)method arguments:(NSArray *)arguments callback:(HippyJavaScriptCallback)onComplete { HippyAssert(onComplete != nil, @"onComplete block should not be nil"); __weak HippyJSExecutor *weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ HippyJSExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid || nullptr == strongSelf.pScope) { return; } @try { HippyBridge *bridge = [strongSelf bridge]; NSString *moduleName = [bridge moduleName]; NSError *executeError = nil; id objcValue = nil; auto context = strongSelf.pScope->GetContext(); auto global_object = context->GetGlobalObject(); auto bridge_key = context->CreateString("__hpBatchedBridge"); auto batchedbridge_value = context->GetProperty(global_object, bridge_key); SharedCtxValuePtr resultValue = nullptr; string_view exception; if (batchedbridge_value) { string_view methodName = NSStringToU8StringView(method); SharedCtxValuePtr method_value = context->GetProperty(batchedbridge_value, methodName); if (method_value) { if (context->IsFunction(method_value)) { std::vector function_params(arguments.count); for (NSUInteger i = 0; i < arguments.count; i++) { id obj = arguments[i]; function_params[i] = [obj convertToCtxValue:context]; } auto tryCatch = hippy::TryCatch::CreateTryCatchScope(true, context); resultValue = context->CallFunction(method_value, context->GetGlobalObject(), arguments.count, function_params.data()); if (tryCatch->HasCaught()) { exception = tryCatch->GetExceptionMessage(); } } else { NSString *errMsg = [NSString stringWithFormat:@"%@ is not a function", method]; executeError = HippyErrorWithMessageAndModuleName(errMsg, moduleName); } } else { NSString *errMsg = [NSString stringWithFormat:@"property/function %@ not found in __hpBatchedBridge", method]; executeError = HippyErrorWithMessageAndModuleName(errMsg, moduleName); } } else { executeError = HippyErrorWithMessageAndModuleName(@"__hpBatchedBridge not found", moduleName); } if (!StringViewUtils::IsEmpty(exception) || executeError) { if (!StringViewUtils::IsEmpty(exception)) { NSString *string = StringViewToNSString(exception); executeError = HippyErrorWithMessageAndModuleName(string, moduleName); } } else if (resultValue) { objcValue = ObjectFromCtxValue(context, resultValue); } onComplete(objcValue, executeError); } @catch (NSException *exception) { NSString *moduleName = strongSelf.bridge.moduleName?:@"unknown"; NSMutableDictionary *userInfo = [exception.userInfo mutableCopy]?:[NSMutableDictionary dictionary]; [userInfo setObject:moduleName forKey:HippyFatalModuleName]; [userInfo setObject:arguments?:[NSArray array] forKey:@"arguments"]; NSException *reportException = [NSException exceptionWithName:exception.name reason:exception.reason userInfo:userInfo]; HippyHandleException(reportException); } }]; } - (void)executeApplicationScript:(NSData *)script sourceURL:(NSURL *)sourceURL onComplete:(HippyJavaScriptCallback)onComplete { HippyAssertParam(script); HippyAssertParam(sourceURL); __weak HippyJSExecutor* weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ HippyJSExecutor *strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { onComplete(nil, HippyErrorWithMessageAndModuleName(@"jsexecutor is not invalid", strongSelf.bridge.moduleName)); return; } NSError *error = nil; auto entry = strongSelf.pScope->GetPerformance()->PerformanceNavigation(hippy::kPerfNavigationHippyInit); string_view url = [[sourceURL absoluteString] UTF8String]?:""; entry->BundleInfoOfUrl(url).execute_source_start_ = footstone::TimePoint::SystemNow(); id result = executeApplicationScript(script, sourceURL, strongSelf.pScope->GetContext(), &error); entry->BundleInfoOfUrl(url).execute_source_end_ = footstone::TimePoint::SystemNow(); if (onComplete) { onComplete(result, error); } }]; } static NSError *executeApplicationScript(NSData *script, NSURL *sourceURL, SharedCtxPtr context, __strong NSError **error) { const char *scriptBytes = reinterpret_cast([script bytes]); string_view view = string_view::new_from_utf8(scriptBytes, [script length]); string_view fileName = NSStringToU16StringView([sourceURL absoluteString]); string_view errorMsg; auto tryCatch = hippy::TryCatch::CreateTryCatchScope(true, context); SharedCtxValuePtr result = context->RunScript(view, fileName); if (tryCatch->HasCaught()) { errorMsg = std::move(tryCatch->GetExceptionMessage()); } *error = !StringViewUtils::IsEmpty(errorMsg) ? [NSError errorWithDomain:HippyErrorDomain code:2 userInfo:@{ NSLocalizedDescriptionKey: StringViewToNSString(errorMsg)}] : nil; id objcResult = ObjectFromCtxValue(context, result); return objcResult; } - (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block { @synchronized (self) { if (!self.ready) { [self.pendingCalls addObject:block]; return; } } auto engineRsc = [[HippyJSEnginesMapper defaultInstance] JSEngineResourceForKey:self.enginekey]; if (!engineRsc) { return; } auto engine = engineRsc->GetEngine(); if (engine) { auto runner = engine->GetJsTaskRunner(); if (footstone::Worker::IsTaskRunning() && runner == footstone::runner::TaskRunner::GetCurrentTaskRunner()) { block(); } else if (runner) { runner->PostTask(block); } } } - (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block { @synchronized (self) { if (!self.ready) { [self.pendingCalls addObject:block]; return; } } auto engineRsc = [[HippyJSEnginesMapper defaultInstance] JSEngineResourceForKey:self.enginekey]; if (!engineRsc) { return; } auto engine = engineRsc->GetEngine(); if (engine) { auto runner = engine->GetJsTaskRunner(); if (runner) { runner->PostTask(block); } } } - (void)injectObjectSync:(NSObject *)value asGlobalObjectNamed:(NSString *)objectName callback:(HippyJavaScriptCallback)onComplete { if (!objectName || !value) { if (onComplete) { NSError *error = HippyErrorWithMessage(@"Inject param invalid"); onComplete(@(NO), error); } return; } if (!self.isValid || !self.pScope) { return; } auto context = self.pScope->GetContext(); auto tryCatch = hippy::TryCatch::CreateTryCatchScope(true, context); auto globalObject = context->GetGlobalObject(); auto nameKey = context->CreateString(objectName.UTF8String); auto ctxValue = [value convertToCtxValue:context]; if (nameKey && ctxValue) { context->SetProperty(globalObject, nameKey, [value convertToCtxValue:context]); } else { HippyLogError(@"Convert Error while inject:%@ for:%@", value, objectName); } if (tryCatch && tryCatch->HasCaught()) { NSString *errorMsg = StringViewToNSString(tryCatch->GetExceptionMessage()); NSError *error = HippyErrorWithMessage(errorMsg); if (onComplete) { onComplete(@(NO), error); } else { HippyLogError(@"Error(%@) while inject:%@ for:%@", errorMsg, value, objectName); } } else if (onComplete) { onComplete(@(YES), nil); } } - (void)injectObjectAsync:(NSObject *)value asGlobalObjectNamed:(NSString *)objectName callback:(HippyJavaScriptCallback)onComplete { __weak __typeof(self)weakSelf = self; [self executeBlockOnJavaScriptQueue:^{ __strong __typeof(weakSelf)strongSelf = weakSelf; if (!strongSelf || !strongSelf.isValid) { return; } [strongSelf injectObjectSync:value asGlobalObjectNamed:objectName callback:onComplete]; }]; } - (NSString *)completeWSURLWithBridge:(HippyBridge *)bridge { if ([bridge.delegate respondsToSelector:@selector(shouldStartInspector:)] && [bridge.delegate shouldStartInspector:bridge] == NO) { return @""; } HippyDevInfo *devInfo = [[HippyDevInfo alloc] init]; if ([bridge.delegate respondsToSelector:@selector(inspectorSourceURLForBridge:)]) { NSURL *debugURL = [bridge.delegate inspectorSourceURLForBridge:bridge]; devInfo.scheme = [debugURL scheme]; devInfo.ipAddress = [debugURL host]; devInfo.port = [NSString stringWithFormat:@"%@", [debugURL port]]; devInfo.versionId = [HippyBundleURLProvider parseVersionId:[debugURL path]]; [devInfo parseWsURLWithURLQuery:[debugURL query]]; } else { HippyBundleURLProvider *bundleURLProvider = [HippyBundleURLProvider sharedInstance]; devInfo.scheme = bundleURLProvider.scheme; devInfo.ipAddress = bundleURLProvider.localhostIP; devInfo.port = bundleURLProvider.localhostPort; devInfo.versionId = bundleURLProvider.versionId; devInfo.wsURL = bundleURLProvider.wsURL; } return [devInfo assembleFullWSURLWithClientId:[self getClientID] contextName:bridge.contextName isHermesEngine:bridge.usingHermesEngine]; } #pragma mark - Exception Handle static void handleJsExcepiton(std::shared_ptr scope) { if (!scope) { return; } #ifdef JS_JSC auto context = std::dynamic_pointer_cast(scope->GetContext()); if (!context) { HippyLogError(@"[Hippy_OC_Log][HippyJSExecutor], handleJsExcepiton: scope context is not JSCCtx type!"); return; } auto exception = std::dynamic_pointer_cast(context->GetException()); if (exception) { // if native does not handled, rethrow to js if (!context->IsExceptionHandled()) { constexpr char kHippyExceptionEventName[] = "uncaughtException"; hippy::vm::VM::HandleException(context, kHippyExceptionEventName, exception); } string_view exceptionStrView = context->GetExceptionMessage(exception); auto errU8Str = StringViewUtils::ConvertEncoding(exceptionStrView, string_view::Encoding::Utf8).utf8_value(); std::string errStr = StringViewUtils::ToStdString(errU8Str); NSError *error = HippyErrorWithMessage([NSString stringWithUTF8String:errStr.c_str()]); HippyFatal(error); context->SetException(nullptr); context->SetExceptionHandled(true); } #endif /* JS_JSC */ } @end