/* * Created by Rolando Abarca on 3/14/12. * Copyright (c) 2012 Zynga Inc. All rights reserved. * Copyright (c) 2013-2017 Chukong Technologies Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #include "scripting/js-bindings/manual/ScriptingCore.h" // Removed in Firefox v27, use 'js/OldDebugAPI.h' instead //#include "jsdbgapi.h" #include "js/OldDebugAPI.h" #include "storage/local-storage/LocalStorage.h" #include "scripting/js-bindings/manual/cocos2d_specifics.hpp" #include "scripting/js-bindings/auto/jsb_cocos2dx_auto.hpp" #include "scripting/js-bindings/manual/js_bindings_config.h" #include "cocos2d.h" // we used cocos2dVersion() ... // for debug socket #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) #include #include #else #include #include #include #endif #include #include #include #include #include #include #include #include #ifdef ANDROID #include #include #include #endif #ifdef ANDROID #define LOG_TAG "ScriptingCore.cpp" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG,LOG_TAG,__VA_ARGS__) #else #define LOGD(...) js_log(__VA_ARGS__) #endif #if COCOS2D_DEBUG #define TRACE_DEBUGGER_SERVER(...) CCLOG(__VA_ARGS__) #else #define TRACE_DEBUGGER_SERVER(...) #endif // #if COCOS2D_DEBUG #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS #error "The functionality of JS controls C++ object's lifecycle isn't stable enough, please don't enable it now." #endif #define BYTE_CODE_FILE_EXT ".jsc" using namespace cocos2d; static std::string inData; static std::string outData; static std::vector g_queue; static std::mutex g_qMutex; static std::mutex g_rwMutex; static int clientSocket = -1; static uint32_t s_nestedLoopLevel = 0; // server entry point for the bg thread static void serverEntryPoint(unsigned int port); std::unordered_map _js_global_type_map; static std::unordered_map _native_js_global_map; static std::unordered_map _js_native_global_map; static std::unordered_map _js_hook_owner_map; static char *_js_log_buf = NULL; static std::vector registrationList; // name ~> JSScript map static std::unordered_map filename_script; // port ~> socket map static std::unordered_map ports_sockets; static void cc_closesocket(int fd) { #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) closesocket(fd); #else close(fd); #endif } static void ReportException(JSContext *cx) { if (JS_IsExceptionPending(cx)) { if (!JS_ReportPendingException(cx)) { JS_ClearPendingException(cx); } } } static void onGarbageCollect(JSRuntime* rt, JSGCStatus status, void* data) { /* We finalize any pending toggle refs before doing any garbage collection, * so that we can collect the JS wrapper objects, and in order to minimize * the chances of objects having a pending toggle up queued when they are * garbage collected. */ if (status == JSGC_BEGIN) { CCLOGINFO("onGarbageCollect begin, native->js map: %d, js->native map: %d", (int)_native_js_global_map.size(), (int)_js_native_global_map.size()); } else if (status == JSGC_END) { CCLOGINFO("onGarbageCollect end, native->js map: %d, js->native map: %d", (int)_native_js_global_map.size(), (int)_js_native_global_map.size()); } } static void executeJSFunctionFromReservedSpot(JSContext *cx, JS::HandleObject obj, const JS::HandleValueArray& dataVal, JS::MutableHandleValue retval) { JS::RootedValue func(cx, JS_GetReservedSlot(obj, 0)); if (func.isNullOrUndefined()) { return; } JS::RootedValue thisObj(cx, JS_GetReservedSlot(obj, 1)); if (thisObj.isNullOrUndefined()) { JS_CallFunctionValue(cx, obj, func, dataVal, retval); } else { assert(!thisObj.isPrimitive()); JS::RootedObject jsthis(cx, thisObj.toObjectOrNull()); JS_CallFunctionValue(cx, jsthis, func, dataVal, retval); } } static std::string getTouchesFuncName(EventTouch::EventCode eventCode) { std::string funcName; switch(eventCode) { case EventTouch::EventCode::BEGAN: funcName = "onTouchesBegan"; break; case EventTouch::EventCode::ENDED: funcName = "onTouchesEnded"; break; case EventTouch::EventCode::MOVED: funcName = "onTouchesMoved"; break; case EventTouch::EventCode::CANCELLED: funcName = "onTouchesCancelled"; break; default: CCASSERT(false, "Invalid event code!"); break; } return funcName; } static std::string getTouchFuncName(EventTouch::EventCode eventCode) { std::string funcName; switch(eventCode) { case EventTouch::EventCode::BEGAN: funcName = "onTouchBegan"; break; case EventTouch::EventCode::ENDED: funcName = "onTouchEnded"; break; case EventTouch::EventCode::MOVED: funcName = "onTouchMoved"; break; case EventTouch::EventCode::CANCELLED: funcName = "onTouchCancelled"; break; default: CCASSERT(false, "Invalid event code!"); } return funcName; } static std::string getMouseFuncName(EventMouse::MouseEventType eventType) { std::string funcName; switch(eventType) { case EventMouse::MouseEventType::MOUSE_DOWN: funcName = "onMouseDown"; break; case EventMouse::MouseEventType::MOUSE_UP: funcName = "onMouseUp"; break; case EventMouse::MouseEventType::MOUSE_MOVE: funcName = "onMouseMove"; break; case EventMouse::MouseEventType::MOUSE_SCROLL: funcName = "onMouseScroll"; break; default: CCASSERT(false, "Invalid event code!"); } return funcName; } void removeJSObject(JSContext* cx, cocos2d::Ref* nativeObj) { auto proxy = jsb_get_native_proxy(nativeObj); if (proxy) { #if ! CC_ENABLE_GC_FOR_NATIVE_OBJECTS JS::RemoveObjectRoot(cx, &proxy->obj); #endif // remove the proxy here, since this was a "stack" object, not heap // when js_finalize will be called, it will fail, but // the correct solution is to have a new finalize for event jsb_remove_proxy(proxy); } else CCLOG("removeJSObject: BUG: cannot find native object = %p", nativeObj); } void ScriptingCore::executeJSFunctionWithThisObj(JS::HandleValue thisObj, JS::HandleValue callback) { JS::RootedValue retVal(_cx); executeJSFunctionWithThisObj(thisObj, callback, JS::HandleValueArray::empty(), &retVal); } void ScriptingCore::executeJSFunctionWithThisObj(JS::HandleValue thisObj, JS::HandleValue callback, const JS::HandleValueArray& vp, JS::MutableHandleValue retVal) { if (!callback.isNullOrUndefined() || !thisObj.isNullOrUndefined()) { JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET // Very important: The last parameter 'retVal' passed to 'JS_CallFunctionValue' should not be a NULL pointer. // If it's a NULL pointer, crash will be triggered in 'JS_CallFunctionValue'. To find out the reason of this crash is very difficult. // So we have to check the availability of 'retVal'. JS::RootedObject jsthis(_cx, thisObj.toObjectOrNull()); JS_CallFunctionValue(_cx, jsthis, callback, vp, retVal); } } void js_log(const char *format, ...) { if (_js_log_buf == NULL) { _js_log_buf = (char *)calloc(sizeof(char), MAX_LOG_LENGTH+1); _js_log_buf[MAX_LOG_LENGTH] = '\0'; } va_list vl; va_start(vl, format); int len = vsnprintf(_js_log_buf, MAX_LOG_LENGTH, format, vl); va_end(vl); if (len > 0) { CCLOG("JS: %s", _js_log_buf); } } bool JSBCore_platform(JSContext *cx, uint32_t argc, jsval *vp) { if (argc!=0) { JS_ReportError(cx, "Invalid number of arguments in __getPlatform"); return false; } JS::CallArgs args = JS::CallArgsFromVp(argc, vp); Application::Platform platform; // config.deviceType: Device Type // 'mobile' for any kind of mobile devices, 'desktop' for PCs, 'browser' for Web Browsers // #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) || (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) || (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) // platform = JS_InternString(_cx, "desktop"); // #else platform = Application::getInstance()->getTargetPlatform(); // #endif args.rval().set(INT_TO_JSVAL((int)platform)); return true; }; bool JSBCore_version(JSContext *cx, uint32_t argc, jsval *vp) { if (argc!=0) { JS_ReportError(cx, "Invalid number of arguments in __getVersion"); return false; } JS::CallArgs args = JS::CallArgsFromVp(argc, vp); char version[256]; snprintf(version, sizeof(version)-1, "%s", cocos2dVersion()); JSString * js_version = JS_InternString(cx, version); args.rval().set(STRING_TO_JSVAL(js_version)); return true; }; bool JSBCore_os(JSContext *cx, uint32_t argc, jsval *vp) { if (argc!=0) { JS_ReportError(cx, "Invalid number of arguments in __getOS"); return false; } JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSString * os; // osx, ios, android, windows, linux, etc.. #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) os = JS_InternString(cx, "iOS"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) os = JS_InternString(cx, "Android"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32) os = JS_InternString(cx, "Windows"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_MARMALADE) os = JS_InternString(cx, "Marmalade"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_LINUX) os = JS_InternString(cx, "Linux"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_BADA) os = JS_InternString(cx, "Bada"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_BLACKBERRY) os = JS_InternString(cx, "Blackberry"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_MAC) os = JS_InternString(cx, "OS X"); #elif (CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) os = JS_InternString(cx, "WINRT"); #else os = JS_InternString(cx, "Unknown"); #endif args.rval().set(STRING_TO_JSVAL(os)); return true; }; bool JSB_cleanScript(JSContext *cx, uint32_t argc, jsval *vp) { if (argc != 1) { JS_ReportError(cx, "Invalid number of arguments in JSB_cleanScript"); return false; } JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSString *jsPath = args.get(0).toString(); JSB_PRECONDITION2(jsPath, cx, false, "Error js file in clean script"); JSStringWrapper wrapper(jsPath); ScriptingCore::getInstance()->cleanScript(wrapper.get()); args.rval().setUndefined(); return true; }; bool JSB_core_restartVM(JSContext *cx, uint32_t argc, jsval *vp) { JSB_PRECONDITION2(argc==0, cx, false, "Invalid number of arguments in executeScript"); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); ScriptingCore::getInstance()->reset(); args.rval().setUndefined(); return true; }; bool JSB_closeWindow(JSContext *cx, uint32_t argc, jsval *vp) { EventListenerCustom* _event = Director::getInstance()->getEventDispatcher()->addCustomEventListener(Director::EVENT_AFTER_DRAW, [&](EventCustom *event) { Director::getInstance()->getEventDispatcher()->removeEventListener(_event); CC_SAFE_RELEASE(_event); ScriptingCore::getInstance()->cleanup(); }); _event->retain(); Director::getInstance()->end(); #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) exit(0); #endif return true; }; void registerDefaultClasses(JSContext* cx, JS::HandleObject global) { // first, try to get the ns JS::RootedValue nsval(cx); JS::RootedObject ns(cx); JS_GetProperty(cx, global, "cc", &nsval); // Not exist, create it if (nsval == JSVAL_VOID) { ns.set(JS_NewObject(cx, NULL, JS::NullPtr(), JS::NullPtr())); nsval = OBJECT_TO_JSVAL(ns); JS_SetProperty(cx, global, "cc", nsval); } else { ns.set(nsval.toObjectOrNull()); } // // Javascript controller (__jsc__) // JS::RootedObject proto(cx); JS::RootedObject parent(cx); JS::RootedObject jsc(cx, JS_NewObject(cx, NULL, proto, parent)); JS::RootedValue jscVal(cx); jscVal = OBJECT_TO_JSVAL(jsc); JS_SetProperty(cx, global, "__jsc__", jscVal); JS_DefineFunction(cx, jsc, "garbageCollect", ScriptingCore::forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE ); JS_DefineFunction(cx, jsc, "dumpRoot", ScriptingCore::dumpRoot, 0, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE ); JS_DefineFunction(cx, jsc, "executeScript", ScriptingCore::executeScript, 1, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE ); // register some global functions JS_DefineFunction(cx, global, "require", ScriptingCore::executeScript, 1, JSPROP_PERMANENT); JS_DefineFunction(cx, global, "log", ScriptingCore::log, 0, JSPROP_PERMANENT); JS_DefineFunction(cx, global, "executeScript", ScriptingCore::executeScript, 1, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "forceGC", ScriptingCore::forceGC, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "__getPlatform", JSBCore_platform, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "__getOS", JSBCore_os, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "__getVersion", JSBCore_version, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "__restartVM", JSB_core_restartVM, 0, JSPROP_READONLY | JSPROP_PERMANENT | JSPROP_ENUMERATE ); JS_DefineFunction(cx, global, "__cleanScript", JSB_cleanScript, 1, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "__isObjectValid", ScriptingCore::isObjectValid, 1, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(cx, global, "close", JSB_closeWindow, 0, JSPROP_READONLY | JSPROP_PERMANENT); } static void sc_finalize(JSFreeOp *freeOp, JSObject *obj) { CCLOGINFO("jsbindings: finalizing JS object %p (global class)", obj); } //static JSClass global_class = { // "global", JSCLASS_GLOBAL_FLAGS, // JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, // JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, sc_finalize, // JSCLASS_NO_OPTIONAL_MEMBERS //}; static const JSClass global_class = { "global", JSCLASS_GLOBAL_FLAGS, JS_PropertyStub, JS_DeletePropertyStub, JS_PropertyStub, JS_StrictPropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, sc_finalize, nullptr, nullptr, nullptr, JS_GlobalObjectTraceHook }; ScriptingCore* ScriptingCore::getInstance() { static ScriptingCore* instance = nullptr; if (instance == nullptr) instance = new (std::nothrow) ScriptingCore(); return instance; } ScriptingCore::ScriptingCore() : _rt(nullptr) , _cx(nullptr) , _jsInited(false) , _needCleanup(false) , _global(nullptr) , _debugGlobal(nullptr) , _oldCompartment(nullptr) , _callFromScript(false) , _finalizing(nullptr) { // set utf8 strings internally (we don't need utf16) // XXX: Removed in SpiderMonkey 19.0 //JS_SetCStringsAreUTF8(); initRegister(); } void ScriptingCore::initRegister() { this->addRegisterCallback(registerDefaultClasses); this->_runLoop = new (std::nothrow) SimpleRunLoop(); } void ScriptingCore::string_report(JS::HandleValue val) { if (val.isNull()) { LOGD("val : (JSVAL_IS_NULL(val)"); // return 1; } else if (val.isBoolean() && false == val.toBoolean()) { LOGD("val : (return value is false"); // return 1; } else if (val.isString()) { JSContext* cx = this->getGlobalContext(); JS::RootedString str(cx, val.toString()); if (str.get()) { LOGD("val : return string is NULL"); } else { JSStringWrapper wrapper(str); LOGD("val : return string =\n%s\n", wrapper.get()); } } else if (val.isNumber()) { double number = val.toNumber(); LOGD("val : return number =\n%f", number); } } bool ScriptingCore::evalString(const char *string, JS::MutableHandleValue outVal, const char *filename, JSContext* cx, JS::HandleObject global) { JS::PersistentRootedScript script(cx); JS::CompileOptions op(cx); op.setUTF8(true); std::string content = string; bool ok = false; bool evaluatedOK = false; if (!content.empty()) { ok = JS::Compile(cx, global, op, content.c_str(), content.size(), &(script) ); } if (ok) { evaluatedOK = JS_ExecuteScript(cx, global, script, outVal); if (false == evaluatedOK) { cocos2d::log("Evaluating %s failed (evaluatedOK == JS_FALSE)", content.c_str()); JS_ReportPendingException(cx); } } else { cocos2d::log("ScriptingCore:: evaluateScript fail: %s", content.c_str()); } return evaluatedOK; } bool ScriptingCore::evalString(const char *string, JS::MutableHandleValue outVal) { JS::RootedObject global(_cx, _global->get()); return evalString(string, outVal, nullptr, _cx, global); } bool ScriptingCore::evalString(const char *string) { JS::RootedValue retVal(_cx); return evalString(string, &retVal); } void ScriptingCore::start() { _engineStartTime = std::chrono::steady_clock::now(); // for now just this createGlobalContext(); } void ScriptingCore::addRegisterCallback(sc_register_sth callback) { registrationList.push_back(callback); } void ScriptingCore::removeAllRoots(JSContext *cx) { // Native -> JS. No need to free "second" _native_js_global_map.clear(); // JS -> Native: free "second" and "unroot" it. auto it_js = _js_native_global_map.begin(); while (it_js != _js_native_global_map.end()) { JS::RemoveObjectRoot(cx, &it_js->second->obj); free(it_js->second); it_js = _js_native_global_map.erase(it_js); } _js_native_global_map.clear(); } // Just a wrapper around JSPrincipals that allows static construction. class CCJSPrincipals : public JSPrincipals { public: explicit CCJSPrincipals(int rc = 0) : JSPrincipals() { refcount = rc; } }; static CCJSPrincipals shellTrustedPrincipals(1); static bool CheckObjectAccess(JSContext *cx) { return true; } static JSSecurityCallbacks securityCallbacks = { CheckObjectAccess, NULL }; void ScriptingCore::createGlobalContext() { if (_cx && _rt) { ScriptingCore::removeAllRoots(_cx); JS_DestroyContext(_cx); JS_DestroyRuntime(_rt); _cx = NULL; _rt = NULL; } // Start the engine. Added in SpiderMonkey v25 if (!_jsInited && !JS_Init()) { return; } else { _jsInited = true; } _rt = JS_NewRuntime(32L * 1024L * 1024L); JS_SetGCParameter(_rt, JSGCParamKey::JSGC_MAX_BYTES, 0xffffffff); JS_SetGCParameter(_rt, JSGCParamKey::JSGC_MODE, JSGC_MODE_COMPARTMENT); JS_SetTrustedPrincipals(_rt, &shellTrustedPrincipals); JS_SetSecurityCallbacks(_rt, &securityCallbacks); JS_SetNativeStackQuota(_rt, JSB_MAX_STACK_QUOTA); _cx = JS_NewContext(_rt, 32 * 1024); JS::RuntimeOptionsRef(_rt).setIon(true); JS::RuntimeOptionsRef(_rt).setBaseline(true); JS_SetErrorReporter(_cx, ScriptingCore::reportError); #if defined(JS_GC_ZEAL) && defined(DEBUG) JS_SetGCZeal(this->_cx, 2, JS_DEFAULT_ZEAL_FREQ); #endif JS_SetGCCallback(_rt, onGarbageCollect, nullptr); _global = new (std::nothrow) JS::PersistentRootedObject(_rt, newGlobalObject(_cx, false)); JS::RootedObject global(_cx, _global->get()); // Removed in Firefox v34 js::SetDefaultObjectForContext(_cx, global); runScript("script/jsb_prepare.js"); for (auto& callback : registrationList) { callback(_cx, global); } _needCleanup = true; } static std::string RemoveFileExt(const std::string& filePath) { size_t pos = filePath.rfind('.'); if (0 < pos) { return filePath.substr(0, pos); } else { return filePath; } } JS::PersistentRootedScript* ScriptingCore::getScript(const std::string& path) { // a) check jsc file first std::string byteCodePath = RemoveFileExt(std::string(path)) + BYTE_CODE_FILE_EXT; if (filename_script.find(byteCodePath) != filename_script.end()) return filename_script[byteCodePath]; // b) no jsc file, check js file std::string fullPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(path); if (filename_script.find(fullPath) != filename_script.end()) return filename_script[fullPath]; return nullptr; } JS::PersistentRootedScript* ScriptingCore::compileScript(const std::string& path, JS::HandleObject global, JSContext* cx) { if (path.empty()) { return nullptr; } JS::PersistentRootedScript* script = getScript(path); if (script != nullptr) { return script; } if (cx == nullptr) { cx = _cx; } cocos2d::FileUtils *futil = cocos2d::FileUtils::getInstance(); script = new (std::nothrow) JS::PersistentRootedScript(cx); if (script == nullptr) { return nullptr; } JS::RootedObject obj(cx, global); bool compileSucceed = false; // a) check jsc file first std::string byteCodePath = RemoveFileExt(std::string(path)) + BYTE_CODE_FILE_EXT; // Check whether '.jsc' files exist to avoid outputting log which says 'couldn't find .jsc file'. if (futil->isFileExist(byteCodePath)) { Data data = futil->getDataFromFile(byteCodePath); if (!data.isNull()) { *script = JS_DecodeScript(cx, data.getBytes(), static_cast(data.getSize()), nullptr); } if (*script) { compileSucceed = true; filename_script[byteCodePath] = script; } } // b) no jsc file, check js file if (!(*script)) { /* Clear any pending exception from previous failed decoding. */ ReportException(cx); JS::CompileOptions op(cx); op.setUTF8(true); std::string fullPath = futil->fullPathForFilename(path); op.setFileAndLine(fullPath.c_str(), 1); bool ok = false; #if (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID) std::string jsFileContent = futil->getStringFromFile(fullPath); if (!jsFileContent.empty()) { ok = JS::Compile(cx, obj, op, jsFileContent.c_str(), jsFileContent.size(), &(*script)); } #else ok = JS::Compile(cx, obj, op, fullPath.c_str(), &(*script)); #endif if (ok) { compileSucceed = true; filename_script[fullPath] = script; } } else { filename_script[byteCodePath] = script; } if (compileSucceed) { return script; } else { LOGD("ScriptingCore:: compileScript fail:%s", path.c_str()); CC_SAFE_DELETE(script); return nullptr; } } void ScriptingCore::cleanScript(const char *path) { std::string byteCodePath = RemoveFileExt(std::string(path)) + BYTE_CODE_FILE_EXT; auto it = filename_script.find(byteCodePath); if (it != filename_script.end()) { delete it->second; filename_script.erase(it); } std::string fullPath = cocos2d::FileUtils::getInstance()->fullPathForFilename(path); it = filename_script.find(fullPath); if (it != filename_script.end()) { delete it->second; filename_script.erase(it); } } std::unordered_map& ScriptingCore::getFileScript() { return filename_script; } void ScriptingCore::cleanAllScript() { filename_script.clear(); } bool ScriptingCore::runScript(const std::string& path) { JS::RootedObject global(_cx, _global->get()); return runScript(path, global, _cx); } bool ScriptingCore::runScript(const std::string& path, JS::HandleObject global, JSContext* cx) { if (cx == nullptr) { cx = _cx; } auto script = compileScript(path, global, cx); if (script == nullptr) { return false; } bool evaluatedOK = false; if (script) { JS::RootedValue rval(cx); evaluatedOK = JS_ExecuteScript(cx, global, *script, &rval); if (false == evaluatedOK) { cocos2d::log("Evaluating %s failed (evaluatedOK == JS_FALSE)", path.c_str()); JS_ReportPendingException(cx); } } return evaluatedOK; } bool ScriptingCore::requireScript(const char *path, JS::MutableHandleValue jsvalRet) { JS::RootedObject global(_cx, _global->get()); return requireScript(path, global, _cx, jsvalRet); } bool ScriptingCore::requireScript(const char *path, JS::HandleObject global, JSContext* cx, JS::MutableHandleValue jsvalRet) { if (cx == nullptr) { cx = _cx; } auto script = compileScript(path, global, cx); bool evaluatedOK = false; if (script) { evaluatedOK = JS_ExecuteScript(cx, global, (*script), jsvalRet); if (false == evaluatedOK) { cocos2d::log("(evaluatedOK == JS_FALSE)"); JS_ReportPendingException(cx); } } return evaluatedOK; } void ScriptingCore::reset() { Director::getInstance()->restart(); } void ScriptingCore::restartVM() { cleanup(); initRegister(); Application::getInstance()->applicationDidFinishLaunching(); } ScriptingCore::~ScriptingCore() { cleanup(); JS_ShutDown(); } void ScriptingCore::cleanup() { if (!_needCleanup) { return; } localStorageFree(); removeAllRoots(_cx); garbageCollect(); PoolManager::getInstance()->getCurrentPool()->clear(); if (_js_log_buf) { free(_js_log_buf); _js_log_buf = NULL; } for (auto& s : filename_script) { CC_SAFE_DELETE(s.second); } filename_script.clear(); registrationList.clear(); for (auto iter = _js_global_type_map.begin(); iter != _js_global_type_map.end(); ++iter) { delete iter->second->parentProto.ptr(); delete iter->second->proto.ptr(); } CC_SAFE_DELETE(_global); CC_SAFE_DELETE(_debugGlobal); JS_LeaveCompartment(_cx, _oldCompartment); _oldCompartment = nullptr; if (_cx) { JS_DestroyContext(_cx); _cx = NULL; } if (_rt) { JS_DestroyRuntime(_rt); _rt = NULL; } for (auto iter = _js_global_type_map.begin(); iter != _js_global_type_map.end(); ++iter) { free(iter->second->jsclass); free(iter->second); } _js_global_type_map.clear(); _needCleanup = false; } void ScriptingCore::reportError(JSContext *cx, const char *message, JSErrorReport *report) { js_log("%s:%u:%s\n", report->filename ? report->filename : "", (unsigned int) report->lineno, message); }; bool ScriptingCore::log(JSContext* cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (argc > 0) { JSString *string = JS::ToString(cx, args.get(0)); if (string) { JSStringWrapper wrapper(string); js_log("%s", wrapper.get()); } } args.rval().setUndefined(); return true; } void ScriptingCore::retainScriptObject(cocos2d::Ref* owner, cocos2d::Ref* target) { JS::RootedObject global(_cx, _global->get()); JS::RootedObject jsbObj(_cx); get_or_create_js_obj(_cx, global, "jsb", &jsbObj); JS::RootedValue jsbVal(_cx, OBJECT_TO_JSVAL(jsbObj)); if (jsbVal.isNullOrUndefined()) { return; } js_proxy_t *pOwner = jsb_get_native_proxy(owner); js_proxy_t *pTarget = jsb_get_native_proxy(target); if (!pOwner || !pTarget) { return; } JS::RootedValue valOwner(_cx, OBJECT_TO_JSVAL(pOwner->obj)); JS::RootedValue valTarget(_cx, OBJECT_TO_JSVAL(pTarget->obj)); if (valTarget.isPrimitive()) { return; } JS::RootedValue retval(_cx); jsval valArr[2]; valArr[0] = valOwner; valArr[1] = valTarget; JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(2, valArr); executeFunctionWithOwner(jsbVal, "registerNativeRef", args, &retval); } void ScriptingCore::rootScriptObject(cocos2d::Ref* target) { JS::RootedObject global(_cx, _global->get()); JS::RootedObject jsbObj(_cx); get_or_create_js_obj(_cx, global, "jsb", &jsbObj); JS::RootedValue jsbVal(_cx, OBJECT_TO_JSVAL(jsbObj)); if (jsbVal.isNullOrUndefined()) { return; } js_proxy_t *pTarget = jsb_get_native_proxy(target); if (!pTarget) { return; } JS::RootedValue valTarget(_cx, OBJECT_TO_JSVAL(pTarget->obj)); if (valTarget.isPrimitive()) { return; } JS::RootedObject root(_cx); get_or_create_js_obj(_cx, jsbObj, "_root", &root); JS::RootedValue valRoot(_cx, OBJECT_TO_JSVAL(root)); JS::RootedValue retval(_cx); jsval valArr[2]; valArr[0] = valRoot; valArr[1] = valTarget; JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(2, valArr); executeFunctionWithOwner(jsbVal, "registerNativeRef", args, &retval); } void ScriptingCore::releaseScriptObject(cocos2d::Ref* owner, cocos2d::Ref* target) { JS::RootedObject global(_cx, _global->get()); JS::RootedObject jsbObj(_cx); get_or_create_js_obj(_cx, global, "jsb", &jsbObj); JS::RootedValue jsbVal(_cx, OBJECT_TO_JSVAL(jsbObj)); if (jsbVal.isNullOrUndefined()) { return; } js_proxy_t *pOwner = jsb_get_native_proxy(owner); js_proxy_t *pTarget = jsb_get_native_proxy(target); if (!pOwner || !pTarget) { return; } JS::RootedValue valOwner(_cx, OBJECT_TO_JSVAL(pOwner->obj)); JS::RootedValue valTarget(_cx, OBJECT_TO_JSVAL(pTarget->obj)); if (valTarget.isPrimitive()) { return; } JS::RootedValue retval(_cx); jsval valArr[2]; valArr[0] = valOwner; valArr[1] = valTarget; JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(2, valArr); executeFunctionWithOwner(jsbVal, "unregisterNativeRef", args, &retval); } void ScriptingCore::unrootScriptObject(cocos2d::Ref* target) { JS::RootedObject global(_cx, _global->get()); JS::RootedObject jsbObj(_cx); get_or_create_js_obj(_cx, global, "jsb", &jsbObj); JS::RootedValue jsbVal(_cx, OBJECT_TO_JSVAL(jsbObj)); if (jsbVal.isNullOrUndefined()) { return; } js_proxy_t *pTarget = jsb_get_native_proxy(target); if (!pTarget) { return; } JS::RootedValue valTarget(_cx, OBJECT_TO_JSVAL(pTarget->obj)); if (valTarget.isPrimitive()) { return; } JS::RootedObject root(_cx); get_or_create_js_obj(_cx, jsbObj, "_root", &root); JS::RootedValue valRoot(_cx, OBJECT_TO_JSVAL(root)); JS::RootedValue retval(_cx); jsval valArr[2]; valArr[0] = valRoot; valArr[1] = valTarget; JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(2, valArr); executeFunctionWithOwner(jsbVal, "unregisterNativeRef", args, &retval); } void ScriptingCore::releaseAllChildrenRecursive(cocos2d::Node *node) { const Vector& children = node->getChildren(); for (auto child : children) { releaseScriptObject(node, child); releaseAllChildrenRecursive(child); } } void ScriptingCore::releaseAllNativeRefs(cocos2d::Ref* owner) { JS::RootedObject global(_cx, _global->get()); JS::RootedObject jsbObj(_cx); get_or_create_js_obj(_cx, global, "jsb", &jsbObj); JS::RootedValue jsbVal(_cx, OBJECT_TO_JSVAL(jsbObj)); if (jsbVal.isNullOrUndefined()) { return; } js_proxy_t *pOwner = jsb_get_native_proxy(owner); if (!pOwner) { return; } JS::RootedValue valOwner(_cx, OBJECT_TO_JSVAL(pOwner->obj)); JS::RootedValue retval(_cx); jsval valArr[1]; valArr[0] = valOwner; JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(1, valArr); executeFunctionWithOwner(jsbVal, "unregisterAllNativeRefs", args, &retval); } void ScriptingCore::removeScriptObjectByObject(Ref* pObj) { auto proxy = jsb_get_native_proxy(pObj); if (proxy) { JSContext *cx = getGlobalContext(); JS::RemoveObjectRoot(cx, &proxy->obj); jsb_remove_proxy(proxy); } } bool ScriptingCore::setReservedSpot(uint32_t i, JSObject *obj, jsval value) { JS_SetReservedSlot(obj, i, value); return true; } bool ScriptingCore::executeScript(JSContext *cx, uint32_t argc, jsval *vp) { ScriptingCore *engine = ScriptingCore::getInstance(); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (argc >= 1) { JS::RootedValue jsstr(cx, args.get(0)); JSString* str = JS::ToString(cx, jsstr); JSStringWrapper path(str); bool res = false; JS::RootedValue jsret(cx); if (argc == 2 && args.get(1).isString()) { JSString* globalName = args.get(1).toString(); JSStringWrapper name(globalName); JS::RootedObject debugObj(cx, engine->getDebugGlobal()); if (debugObj) { res = engine->requireScript(path.get(), debugObj, cx, &jsret); } else { JS_ReportError(cx, "Invalid global object: %s", name.get()); args.rval().setUndefined(); return false; } } else { JS::RootedObject glob(cx, JS::CurrentGlobalOrNull(cx)); res = engine->requireScript(path.get(), glob, cx, &jsret); } args.rval().set(jsret); return res; } args.rval().setUndefined(); return true; } bool ScriptingCore::forceGC(JSContext *cx, uint32_t argc, jsval *vp) { JSRuntime *rt = JS_GetRuntime(cx); JS_GC(rt); return true; } bool ScriptingCore::dumpRoot(JSContext *cx, uint32_t argc, jsval *vp) { // JS_DumpNamedRoots is only available on DEBUG versions of SpiderMonkey. // Mac and Simulator versions were compiled with DEBUG. #if COCOS2D_DEBUG // JSContext *_cx = ScriptingCore::getInstance()->getGlobalContext(); // JSRuntime *rt = JS_GetRuntime(_cx); // JS_DumpNamedRoots(rt, dumpNamedRoot, NULL); // JS_DumpHeap(rt, stdout, NULL, JSTRACE_OBJECT, NULL, 2, NULL); #endif return true; } void ScriptingCore::pauseSchedulesAndActions(js_proxy_t* p) { JS::RootedObject obj(_cx, p->obj.get()); auto arr = JSScheduleWrapper::getTargetForJSObject(obj); if (! arr) return; Node* node = (Node*)p->ptr; for(ssize_t i = 0; i < arr->size(); ++i) { if (arr->at(i)) { node->getScheduler()->pauseTarget(arr->at(i)); } } } void ScriptingCore::resumeSchedulesAndActions(js_proxy_t* p) { JS::RootedObject obj(_cx, p->obj.get()); auto arr = JSScheduleWrapper::getTargetForJSObject(obj); if (!arr) return; Node* node = (Node*)p->ptr; for(ssize_t i = 0; i < arr->size(); ++i) { if (!arr->at(i)) continue; node->getScheduler()->resumeTarget(arr->at(i)); } } void ScriptingCore::cleanupSchedulesAndActions(js_proxy_t* p) { JS::RootedObject obj(_cx, p->obj.get()); auto targetArray = JSScheduleWrapper::getTargetForJSObject(obj); if (targetArray) { Node* node = (Node*)p->ptr; auto scheduler = node->getScheduler(); for (auto&& target : *targetArray) { scheduler->unscheduleAllForTarget(target); } JSScheduleWrapper::removeAllTargetsForJSObject(obj); } } bool ScriptingCore::isFunctionOverridedInJS(JS::HandleObject obj, const std::string& name, JSNative native) { JS::RootedObject jsobj(_cx, obj); JS::RootedValue value(_cx); bool ok = JS_GetProperty(_cx, jsobj, name.c_str(), &value); if (ok && !value.isNullOrUndefined() && !JS_IsNativeFunction(value.toObjectOrNull(), native)) { return true; } return false; } int ScriptingCore::handleActionEvent(void* data) { if (NULL == data) return 0; ActionObjectScriptData* actionObjectScriptData = static_cast(data); if (NULL == actionObjectScriptData->nativeObject || NULL == actionObjectScriptData->eventType) return 0; Action* actionObject = static_cast(actionObjectScriptData->nativeObject); int eventType = *((int*)(actionObjectScriptData->eventType)); js_proxy_t * p = jsb_get_native_proxy(actionObject); if (!p) return 0; int ret = 0; JS::RootedValue retval(_cx); if (eventType == kActionUpdate) { JS::RootedObject jstarget(_cx, p->obj); if (isFunctionOverridedInJS(jstarget, "update", js_cocos2dx_Action_update)) { jsval dataVal = DOUBLE_TO_JSVAL(*((float *)actionObjectScriptData->param)); ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "update", 1, &dataVal, &retval); } } return ret; } int ScriptingCore::handleNodeEvent(void* data) { if (NULL == data) return 0; BasicScriptData* basicScriptData = static_cast(data); if (NULL == basicScriptData->nativeObject || NULL == basicScriptData->value) return 0; Node* node = static_cast(basicScriptData->nativeObject); int action = *((int*)(basicScriptData->value)); js_proxy_t * p = jsb_get_native_proxy(node); if (!p) return 0; int ret = 0; JS::RootedValue retval(_cx); jsval dataVal = INT_TO_JSVAL(1); JS::RootedObject jstarget(_cx, p->obj); if (action == kNodeOnEnter) { if (isFunctionOverridedInJS(jstarget, "onEnter", js_cocos2dx_Node_onEnter)) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "onEnter", 1, &dataVal, &retval); } resumeSchedulesAndActions(p); } else if (action == kNodeOnExit) { if (isFunctionOverridedInJS(jstarget, "onExit", js_cocos2dx_Node_onExit)) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "onExit", 1, &dataVal, &retval); } pauseSchedulesAndActions(p); } else if (action == kNodeOnEnterTransitionDidFinish) { if (isFunctionOverridedInJS(jstarget, "onEnterTransitionDidFinish", js_cocos2dx_Node_onEnterTransitionDidFinish)) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "onEnterTransitionDidFinish", 1, &dataVal, &retval); } } else if (action == kNodeOnExitTransitionDidStart) { if (isFunctionOverridedInJS(jstarget, "onExitTransitionDidStart", js_cocos2dx_Node_onExitTransitionDidStart)) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "onExitTransitionDidStart", 1, &dataVal, &retval); } } else if (action == kNodeOnCleanup) { cleanupSchedulesAndActions(p); if (isFunctionOverridedInJS(jstarget, "cleanup", js_cocos2dx_Node_cleanup)) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "cleanup", 1, &dataVal, &retval); } } return ret; } int ScriptingCore::handleComponentEvent(void* data) { if (NULL == data) return 0; BasicScriptData* basicScriptData = static_cast(data); if (NULL == basicScriptData->nativeObject || NULL == basicScriptData->value) return 0; Component* node = static_cast(basicScriptData->nativeObject); int action = *((int*)(basicScriptData->value)); js_proxy_t * p = jsb_get_native_proxy(node); if (!p) return 0; int ret = 0; JS::RootedValue retval(_cx); jsval dataVal = INT_TO_JSVAL(1); JS::RootedValue nodeValue(_cx, OBJECT_TO_JSVAL(p->obj)); if (action == kComponentOnAdd) { ret = executeFunctionWithOwner(nodeValue, "onAdd", 1, &dataVal, &retval); } else if (action == kComponentOnRemove) { ret = executeFunctionWithOwner(nodeValue, "onRemove", 1, &dataVal, &retval); } else if (action == kComponentOnEnter) { ret = executeFunctionWithOwner(nodeValue, "onEnter", 1, &dataVal, &retval); resumeSchedulesAndActions(p); } else if (action == kComponentOnExit) { ret = executeFunctionWithOwner(nodeValue, "onExit", 1, &dataVal, &retval); pauseSchedulesAndActions(p); } else if (action == kComponentOnUpdate) { ret = executeFunctionWithOwner(nodeValue, "update", 1, &dataVal, &retval); } return ret; } bool ScriptingCore::handleTouchesEvent(void* nativeObj, cocos2d::EventTouch::EventCode eventCode, const std::vector& touches, cocos2d::Event* event) { JS::RootedValue ret(_cx); return handleTouchesEvent(nativeObj, eventCode, touches, event, &ret); } bool ScriptingCore::handleTouchesEvent(void* nativeObj, cocos2d::EventTouch::EventCode eventCode, const std::vector& touches, cocos2d::Event* event, JS::MutableHandleValue jsvalRet) { bool ret = false; std::string funcName = getTouchesFuncName(eventCode); JS::RootedObject jsretArr(_cx, JS_NewArrayObject(_cx, 0)); int count = 0; js_type_class_t *typeClassEvent = nullptr; js_type_class_t *typeClassTouch = nullptr; if (touches.size()>0) typeClassTouch = js_get_type_from_native(touches[0]); typeClassEvent = js_get_type_from_native((cocos2d::EventTouch*)event); for (const auto& touch : touches) { JS::RootedValue jsret(_cx, OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, touch, typeClassTouch))); if (!JS_SetElement(_cx, jsretArr, count, jsret)) { break; } ++count; } js_proxy_t* p = jsb_get_native_proxy(nativeObj); if (p) { jsval dataVal[2]; dataVal[0] = OBJECT_TO_JSVAL(jsretArr); dataVal[1] = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, event, typeClassEvent)); ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), funcName.c_str(), 2, dataVal, jsvalRet); // event is created on the heap and its destructor won't be invoked, so we need to remove JS object manually removeJSObject(_cx, event); } return ret; } bool ScriptingCore::handleTouchEvent(void* nativeObj, cocos2d::EventTouch::EventCode eventCode, cocos2d::Touch* touch, cocos2d::Event* event) { JS::RootedValue ret(_cx); return handleTouchEvent(nativeObj, eventCode, touch, event, &ret); } bool ScriptingCore::handleTouchEvent(void* nativeObj, cocos2d::EventTouch::EventCode eventCode, cocos2d::Touch* touch, cocos2d::Event* event, JS::MutableHandleValue jsvalRet) { std::string funcName = getTouchFuncName(eventCode); bool ret = false; js_proxy_t * p = jsb_get_native_proxy(nativeObj); if (p) { js_type_class_t *typeClassTouch = js_get_type_from_native(touch); js_type_class_t *typeClassEvent = js_get_type_from_native((cocos2d::EventTouch*)event); jsval dataVal[2]; dataVal[0] = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, touch, typeClassTouch)); dataVal[1] = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, event, typeClassEvent)); ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), funcName.c_str(), 2, dataVal, jsvalRet); // event is created on the heap and its destructor won't be invoked, so we need to remove JS object manually removeJSObject(_cx, event); } return ret; } bool ScriptingCore::handleMouseEvent(void* nativeObj, cocos2d::EventMouse::MouseEventType eventType, cocos2d::Event* event) { JS::RootedValue ret(_cx); return handleMouseEvent(nativeObj, eventType, event, &ret); } bool ScriptingCore::handleMouseEvent(void* nativeObj, cocos2d::EventMouse::MouseEventType eventType, cocos2d::Event* event, JS::MutableHandleValue jsvalRet) { std::string funcName = getMouseFuncName(eventType); bool ret = false; js_proxy_t * p = jsb_get_native_proxy(nativeObj); if (p) { js_type_class_t *typeClass = js_get_type_from_native((cocos2d::EventMouse*)event); jsval dataVal = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, event, typeClass)); ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), funcName.c_str(), 1, &dataVal, jsvalRet); // event is created on the heap and its destructor won't be invoked, so we need to remove JS object manually removeJSObject(_cx, event); } else CCLOG("ScriptingCore::handleMouseEvent native proxy NOT found"); return ret; } bool ScriptingCore::executeFunctionWithObjectData(void* nativeObj, const char *name, JSObject *obj) { js_proxy_t * p = jsb_get_native_proxy(nativeObj); if (!p) return false; JS::RootedValue retval(_cx); jsval dataVal = OBJECT_TO_JSVAL(obj); executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), name, 1, &dataVal, &retval); if (retval.isNull()) { return false; } else if (retval.isBoolean()) { return retval.toBoolean(); } return false; } bool ScriptingCore::executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc, jsval *vp) { JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(argc, vp); JS::RootedValue rval(_cx); return executeFunctionWithOwner(owner, name, args, &rval); } bool ScriptingCore::executeFunctionWithOwner(jsval owner, const char *name, uint32_t argc, jsval *vp, JS::MutableHandleValue retVal) { //should not use CallArgs here, use HandleValueArray instead !! //because the "jsval* vp" is not the standard format from JSNative, the array doesn't contain this and retval //JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(argc, vp); return executeFunctionWithOwner(owner, name, args, retVal); } bool ScriptingCore::executeFunctionWithOwner(jsval owner, const char *name, const JS::HandleValueArray& args) { JS::RootedValue ret(_cx); return executeFunctionWithOwner(owner, name, args, &ret); } bool ScriptingCore::executeFunctionWithOwner(jsval owner, const char *name, const JS::HandleValueArray& args, JS::MutableHandleValue retVal) { bool bRet = false; bool hasFunc; JSContext* cx = this->_cx; JS::RootedValue funcVal(cx); JS::RootedValue ownerval(cx, owner); JS::RootedObject obj(cx, ownerval.toObjectOrNull()); do { if (JS_HasProperty(cx, obj, name, &hasFunc) && hasFunc) { if (!JS_GetProperty(cx, obj, name, &funcVal)) { break; } if (funcVal == JSVAL_VOID) { break; } bRet = JS_CallFunctionValue(cx, obj, funcVal, args, retVal); } }while(0); return bRet; } std::chrono::steady_clock::time_point ScriptingCore::getEngineStartTime() const { return _engineStartTime; } bool ScriptingCore::handleKeyboardEvent(void* nativeObj, cocos2d::EventKeyboard::KeyCode keyCode, bool isPressed, cocos2d::Event* event) { js_proxy_t * p = jsb_get_native_proxy(nativeObj); if (nullptr == p) return false; bool ret = false; js_type_class_t *typeClass = js_get_type_from_native((cocos2d::EventKeyboard*)event); jsval args[2] = { int32_to_jsval(_cx, (int32_t)keyCode), OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, event, typeClass)) }; if (isPressed) { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "_onKeyPressed", 2, args); } else { ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "_onKeyReleased", 2, args); } removeJSObject(_cx, event); return ret; } bool ScriptingCore::handleFocusEvent(void* nativeObj, cocos2d::ui::Widget* widgetLoseFocus, cocos2d::ui::Widget* widgetGetFocus) { js_proxy_t * p = jsb_get_native_proxy(nativeObj); if (nullptr == p) return false; js_type_class_t *typeClass = js_get_type_from_native(widgetLoseFocus); jsval args[2] = { OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, widgetLoseFocus, typeClass, "cocos2d::ui::Widget")), OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(_cx, widgetGetFocus, typeClass, "cocos2d::ui::Widget")) }; bool ret = executeFunctionWithOwner(OBJECT_TO_JSVAL(p->obj), "onFocusChanged", 2, args); return ret; } int ScriptingCore::executeCustomTouchesEvent(EventTouch::EventCode eventType, const std::vector& touches, JSObject *obj) { std::string funcName = getTouchesFuncName(eventType); JS::RootedObject jsretArr(_cx, JS_NewArrayObject(this->_cx, 0)); int count = 0; for (auto& touch : touches) { js_type_class_t *typeClass = js_get_type_from_native(touch); jsval jsret = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(this->_cx, touch, typeClass)); JS::RootedValue jsval(_cx, jsret); if (!JS_SetElement(this->_cx, jsretArr, count, jsval)) { break; } ++count; } jsval jsretArrVal = OBJECT_TO_JSVAL(jsretArr); executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), funcName.c_str(), 1, &jsretArrVal); return 1; } int ScriptingCore::executeCustomTouchEvent(EventTouch::EventCode eventType, Touch *touch, JSObject *obj) { JS::RootedValue retval(_cx); std::string funcName = getTouchFuncName(eventType); js_type_class_t *typeClass = js_get_type_from_native(touch); jsval jsTouch = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(this->_cx, touch, typeClass)); executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), funcName.c_str(), 1, &jsTouch, &retval); return 1; } int ScriptingCore::executeCustomTouchEvent(EventTouch::EventCode eventType, Touch *touch, JSObject *obj, JS::MutableHandleValue retval) { JSB_AUTOCOMPARTMENT_WITH_GLOBAL_OBJCET std::string funcName = getTouchFuncName(eventType); js_type_class_t *typeClass = js_get_type_from_native(touch); jsval jsTouch = OBJECT_TO_JSVAL(jsb_get_or_create_weak_jsobject(this->_cx, touch, typeClass)); executeFunctionWithOwner(OBJECT_TO_JSVAL(obj), funcName.c_str(), 1, &jsTouch, retval); return 1; } int ScriptingCore::executeGlobalFunction(const char* functionName) { std::string evalStr = functionName; JS::RootedValue globalVal(_cx, OBJECT_TO_JSVAL(_global->get())); return executeFunctionWithOwner(globalVal, functionName, 0, NULL); } int ScriptingCore::sendEvent(ScriptEvent* evt) { if (NULL == evt) return 0; // special type, can't use this code after JSAutoCompartment if (evt->type == kRestartGame) { restartVM(); return 0; } switch (evt->type) { case kNodeEvent: { return handleNodeEvent(evt->data); } break; case kScriptActionEvent: { return handleActionEvent(evt->data); } break; case kMenuClickedEvent: break; case kTouchEvent: { TouchScriptData* data = (TouchScriptData*)evt->data; return handleTouchEvent(data->nativeObject, data->actionType, data->touch, data->event); } break; case kTouchesEvent: { TouchesScriptData* data = (TouchesScriptData*)evt->data; return handleTouchesEvent(data->nativeObject, data->actionType, data->touches, data->event); } break; case kComponentEvent: { return handleComponentEvent(evt->data); } break; default: CCASSERT(false, "Invalid script event."); break; } return 0; } bool ScriptingCore::parseConfig(ConfigType type, const std::string &str) { jsval args[2]; args[0] = int32_to_jsval(_cx, static_cast(type)); args[1] = std_string_to_jsval(_cx, str); JS::RootedValue globalVal(_cx, OBJECT_TO_JSVAL(_global->get())); return (true == executeFunctionWithOwner(globalVal, "__onParseConfig", 2, args)); } bool ScriptingCore::isObjectValid(JSContext *cx, uint32_t argc, jsval *vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (argc == 1) { if (!args[0].isObject()) { args.rval().setBoolean(false); return true; } JS::RootedObject tmpObj(cx, args.get(0).toObjectOrNull()); js_proxy_t *proxy = jsb_get_js_proxy(tmpObj); if (proxy && proxy->ptr) { args.rval().set(JSVAL_TRUE); } else { args.rval().set(JSVAL_FALSE); } return true; } else { JS_ReportError(cx, "Invalid number of arguments: %d. Expecting: 1", argc); return false; } } void ScriptingCore::rootObject(Ref* ref) { auto proxy = jsb_get_native_proxy(ref); if (proxy) { JSContext *cx = getGlobalContext(); JS::AddNamedObjectRoot(cx, &proxy->obj, typeid(*ref).name()); ref->_rooted = true; } else CCLOG("rootObject: BUG. native not found: %p (%s)", ref, typeid(*ref).name()); } void ScriptingCore::unrootObject(Ref* ref) { auto proxy = jsb_get_native_proxy(ref); if (proxy) { JSContext *cx = getGlobalContext(); JS::RemoveObjectRoot(cx, &proxy->obj); ref->_rooted = false; } else CCLOG("unrootObject: BUG. native not found: %p (%s)", ref, typeid(*ref).name()); } void ScriptingCore::removeObjectProxy(Ref* obj) { auto proxy = jsb_get_native_proxy(obj); if (proxy) { #if ! CC_ENABLE_GC_FOR_NATIVE_OBJECTS JS::RemoveObjectRoot(_cx, &proxy->obj); #endif // remove the proxy here, since this was a "stack" object, not heap // when js_finalize will be called, it will fail, but // the correct solution is to have a new finalize for event jsb_remove_proxy(proxy); } } void ScriptingCore::garbageCollect() { #if CC_TARGET_PLATFORM != CC_PLATFORM_WIN32 auto runtime = JS_GetRuntime(_cx); // twice: yep, call it twice since this is a generational GC // and we want to collect as much as possible when this is being called // from replaceScene(). JS_GC(runtime); JS_GC(runtime); #endif } #pragma mark - Debug void SimpleRunLoop::update(float dt) { std::string message; size_t messageCount = 0; while (true) { g_qMutex.lock(); messageCount = g_queue.size(); if (messageCount > 0) { auto first = g_queue.begin(); message = *first; g_queue.erase(first); --messageCount; } g_qMutex.unlock(); if (!message.empty()) ScriptingCore::getInstance()->debugProcessInput(message); if (messageCount == 0) break; } } void ScriptingCore::debugProcessInput(const std::string& str) { JSString* jsstr = JS_NewStringCopyZ(_cx, str.c_str()); jsval argv = STRING_TO_JSVAL(jsstr); JS::RootedValue outval(_cx); JS::RootedObject debugGlobal(_cx, _debugGlobal->get()); JS_CallFunctionName(_cx, debugGlobal, "processInput", JS::HandleValueArray::fromMarkedLocation(1, &argv), &outval); } static bool NS_ProcessNextEvent() { std::string message; size_t messageCount = 0; while (true) { g_qMutex.lock(); messageCount = g_queue.size(); if (messageCount > 0) { auto first = g_queue.begin(); message = *first; g_queue.erase(first); --messageCount; } g_qMutex.unlock(); if (!message.empty()) ScriptingCore::getInstance()->debugProcessInput(message); if (messageCount == 0) break; } // std::this_thread::yield(); std::this_thread::sleep_for(std::chrono::milliseconds(10)); return true; } bool JSBDebug_enterNestedEventLoop(JSContext* cx, unsigned argc, jsval* vp) { enum { NS_OK = 0, NS_ERROR_UNEXPECTED }; #define NS_SUCCEEDED(v) ((v) == NS_OK) int rv = NS_OK; uint32_t nestLevel = ++s_nestedLoopLevel; while (NS_SUCCEEDED(rv) && s_nestedLoopLevel >= nestLevel) { if (!NS_ProcessNextEvent()) rv = NS_ERROR_UNEXPECTED; } CCASSERT(s_nestedLoopLevel <= nestLevel, "nested event didn't unwind properly"); JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(UINT_TO_JSVAL(s_nestedLoopLevel)); return true; } bool JSBDebug_exitNestedEventLoop(JSContext* cx, unsigned argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); if (s_nestedLoopLevel > 0) { --s_nestedLoopLevel; } else { args.rval().set(UINT_TO_JSVAL(0)); return true; } args.rval().setUndefined(); return true; } bool JSBDebug_getEventLoopNestLevel(JSContext* cx, unsigned argc, jsval* vp) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); args.rval().set(UINT_TO_JSVAL(s_nestedLoopLevel)); return true; } //#pragma mark - Debugger static void _clientSocketWriteAndClearString(std::string& s) { ::send(clientSocket, s.c_str(), s.length(), 0); s.clear(); } static void processInput(const std::string& data) { std::lock_guard lk(g_qMutex); g_queue.push_back(data); } static void clearBuffers() { std::lock_guard lk(g_rwMutex); // only process input if there's something and we're not locked if (!inData.empty()) { processInput(inData); inData.clear(); } if (!outData.empty()) { _clientSocketWriteAndClearString(outData); } } static void serverEntryPoint(unsigned int port) { // start a server, accept the connection and keep reading data from it struct addrinfo hints, *result = nullptr, *rp = nullptr; int s = 0; memset(&hints, 0, sizeof(struct addrinfo)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; // TCP stream sockets hints.ai_flags = AI_PASSIVE; // fill in my IP for me std::stringstream portstr; portstr << port; int err = 0; #if (CC_TARGET_PLATFORM == CC_PLATFORM_WIN32 || CC_TARGET_PLATFORM == CC_PLATFORM_WINRT) WSADATA wsaData; err = WSAStartup(MAKEWORD(2, 2),&wsaData); #endif if ((err = getaddrinfo(NULL, portstr.str().c_str(), &hints, &result)) != 0) { LOGD("getaddrinfo error : %s\n", gai_strerror(err)); } for (rp = result; rp != NULL; rp = rp->ai_next) { if ((s = socket(rp->ai_family, rp->ai_socktype, 0)) < 0) { continue; } int optval = 1; if ((setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&optval, sizeof(optval))) < 0) { cc_closesocket(s); TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_REUSEADDR"); return; } #if (CC_TARGET_PLATFORM == CC_PLATFORM_IOS) if ((setsockopt(s, SOL_SOCKET, SO_NOSIGPIPE, &optval, sizeof(optval))) < 0) { close(s); TRACE_DEBUGGER_SERVER("debug server : error setting socket option SO_NOSIGPIPE"); return; } #endif //(CC_TARGET_PLATFORM == CC_PLATFORM_IOS) if ((::bind(s, rp->ai_addr, rp->ai_addrlen)) == 0) { break; } cc_closesocket(s); s = -1; } if (s < 0 || rp == NULL) { TRACE_DEBUGGER_SERVER("debug server : error creating/binding socket"); return; } freeaddrinfo(result); listen(s, 1); #define MAX_RECEIVED_SIZE 1024 #define BUF_SIZE MAX_RECEIVED_SIZE + 1 char buf[BUF_SIZE] = {0}; int readBytes = 0; while (true) { clientSocket = accept(s, NULL, NULL); if (clientSocket < 0) { TRACE_DEBUGGER_SERVER("debug server : error on accept"); return; } else { // read/write data TRACE_DEBUGGER_SERVER("debug server : client connected"); inData = "connected"; // process any input, send any output clearBuffers(); while ((readBytes = (int)::recv(clientSocket, buf, MAX_RECEIVED_SIZE, 0)) > 0) { buf[readBytes] = '\0'; // TRACE_DEBUGGER_SERVER("debug server : received command >%s", buf); // no other thread is using this inData.append(buf); // process any input, send any output clearBuffers(); } // while(read) cc_closesocket(clientSocket); } } // while(true) #undef BUF_SIZE #undef MAX_RECEIVED_SIZE } bool JSBDebug_BufferWrite(JSContext* cx, unsigned argc, jsval* vp) { if (argc == 1) { JS::CallArgs args = JS::CallArgsFromVp(argc, vp); JSStringWrapper strWrapper(args.get(0)); // this is safe because we're already inside a lock (from clearBuffers) outData.append(strWrapper.get()); _clientSocketWriteAndClearString(outData); } return true; } void ScriptingCore::enableDebugger(unsigned int port) { if (!_debugGlobal) { JS_SetDebugMode(_cx, true); _debugGlobal = new (std::nothrow) JS::PersistentRootedObject(_cx, newGlobalObject(_cx, true)); // Adds the debugger object to root, otherwise it may be collected by GC. //AddObjectRoot(_cx, &_debugGlobal.ref()); no need, it's persistent rooted now //JS_WrapObject(_cx, &_debugGlobal.ref()); Not really needed, JS_WrapObject makes a cross-compartment wrapper for the given JS object JS::RootedObject rootedDebugObj(_cx, _debugGlobal->get()); JSAutoCompartment acDebug(_cx, rootedDebugObj); // these are used in the debug program JS_DefineFunction(_cx, rootedDebugObj, "log", ScriptingCore::log, 0, JSPROP_READONLY | JSPROP_ENUMERATE | JSPROP_PERMANENT); JS_DefineFunction(_cx, rootedDebugObj, "require", ScriptingCore::executeScript, 2, JSPROP_ENUMERATE | JSPROP_PERMANENT); JS_DefineFunction(_cx, rootedDebugObj, "_bufferWrite", JSBDebug_BufferWrite, 1, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(_cx, rootedDebugObj, "_enterNestedEventLoop", JSBDebug_enterNestedEventLoop, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(_cx, rootedDebugObj, "_exitNestedEventLoop", JSBDebug_exitNestedEventLoop, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS_DefineFunction(_cx, rootedDebugObj, "_getEventLoopNestLevel", JSBDebug_getEventLoopNestLevel, 0, JSPROP_READONLY | JSPROP_PERMANENT); JS::RootedObject globalObj(_cx, _global->get()); JS_WrapObject(_cx, &globalObj); runScript("script/jsb_debugger.js", rootedDebugObj); // prepare the debugger jsval argv = OBJECT_TO_JSVAL(globalObj); JS::RootedValue outval(_cx); JS::HandleValueArray args = JS::HandleValueArray::fromMarkedLocation(1, &argv); bool ok = JS_CallFunctionName(_cx, rootedDebugObj, "_prepareDebugger", args, &outval); if (!ok) { JS_ReportPendingException(_cx); } // start bg thread auto t = std::thread(&serverEntryPoint,port); t.detach(); Scheduler* scheduler = Director::getInstance()->getScheduler(); scheduler->scheduleUpdate(this->_runLoop, 0, false); } } JSObject* ScriptingCore::newGlobalObject(JSContext* cx, bool debug) { JS::CompartmentOptions options; options.setVersion(JSVERSION_LATEST); JS::RootedObject glob(cx, JS_NewGlobalObject(cx, &global_class, &shellTrustedPrincipals, JS::DontFireOnNewGlobalHook, options)); if (!glob) { return nullptr; } JSCompartment* oldDebugCompartment = nullptr; if (!debug) { _oldCompartment = JS_EnterCompartment(cx, glob); } else { oldDebugCompartment = JS_EnterCompartment(cx, glob); } bool ok = true; ok = JS_InitStandardClasses(cx, glob); if (ok) JS_InitReflect(cx, glob); if (ok && debug) ok = JS_DefineDebuggerObject(cx, glob); if (!ok) return nullptr; JS_FireOnNewGlobalObject(cx, glob); if (debug) { JS_LeaveCompartment(cx, oldDebugCompartment); } return glob; } bool jsb_set_reserved_slot(JSObject *obj, uint32_t idx, jsval value) { const JSClass *klass = JS_GetClass(obj); unsigned int slots = JSCLASS_RESERVED_SLOTS(klass); if ( idx >= slots ) return false; JS_SetReservedSlot(obj, idx, value); return true; } bool jsb_get_reserved_slot(JSObject *obj, uint32_t idx, jsval& ret) { const JSClass *klass = JS_GetClass(obj); unsigned int slots = JSCLASS_RESERVED_SLOTS(klass); if ( idx >= slots ) return false; ret = JS_GetReservedSlot(obj, idx); return true; } js_proxy_t* jsb_new_proxy(void* nativeObj, JS::HandleObject jsHandle) { js_proxy_t* proxy = nullptr; JSObject* jsObj = jsHandle.get(); if (nativeObj && jsObj) { // native to JS index proxy = new (std::nothrow) js_proxy_t(); CC_ASSERT(proxy && "not enough memory"); #if 0 if (_js_native_global_map.find(jsObj) != _js_native_global_map.end()) { CCLOG("BUG: old:%s new:%s", JS_GetClass(_js_native_global_map.at(jsObj)->_jsobj)->name, JS_GetClass(jsObj)->name); } #endif CC_ASSERT(_native_js_global_map.find(nativeObj) == _native_js_global_map.end() && "Native Key should not be present"); // If native proxy doesn't exist, and js proxy exist, means previous js object in this location have already been released. // In some circumstances, js object may be released without calling its finalizer, so the proxy haven't been removed. // For ex: var seq = cc.sequence(moveBy, cc.callFunc(this.callback, this)); // In this code, cc.callFunc is created in parameter, and directly released after the native function call, the finalizer won't be triggered. // The current solution keep the game running with a warning because it may cause memory leak as the native object may have been retained. auto existJSProxy = _js_native_global_map.find(jsObj); if (existJSProxy != _js_native_global_map.end()) { #if COCOS2D_DEBUG > 1 CCLOG("jsbindings: Failed to remove proxy for native object: %p, force removing it, but it may cause memory leak", existJSProxy->second->ptr); #endif jsb_remove_proxy(existJSProxy->second); } proxy->ptr = nativeObj; proxy->obj = jsObj; proxy->_jsobj = jsObj; // One Proxy in two entries _native_js_global_map[nativeObj] = proxy; _js_native_global_map[jsObj] = proxy; } else CCLOG("jsb_new_proxy: Invalid keys"); return proxy; } js_proxy_t* jsb_get_native_proxy(void* nativeObj) { auto search = _native_js_global_map.find(nativeObj); if(search != _native_js_global_map.end()) return search->second; return nullptr; } js_proxy_t* jsb_get_js_proxy(JS::HandleObject jsObj) { auto search = _js_native_global_map.find(jsObj); if(search != _js_native_global_map.end()) return search->second; return nullptr; } void jsb_remove_proxy(js_proxy_t* nativeProxy, js_proxy_t* jsProxy) { js_proxy_t* proxy = nativeProxy ? nativeProxy : jsProxy; jsb_remove_proxy(proxy); } void jsb_remove_proxy(js_proxy_t* proxy) { void* nativeKey = proxy->ptr; JSObject* jsKey = proxy->_jsobj; CC_ASSERT(nativeKey && "Invalid nativeKey"); CC_ASSERT(jsKey && "Invalid JSKey"); auto it_nat = _native_js_global_map.find(nativeKey); auto it_js = _js_native_global_map.find(jsKey); if (it_nat != _native_js_global_map.end()) { _native_js_global_map.erase(it_nat); } else CCLOG("jsb_remove_proxy: failed. Native key not found"); if (it_js != _js_native_global_map.end()) { // Free it once, since we only have one proxy alloced entry delete it_js->second; _js_native_global_map.erase(it_js); } else CCLOG("jsb_remove_proxy: failed. JS key not found"); } // // Ref functions // // ref_create JSObject* jsb_ref_create_jsobject(JSContext *cx, cocos2d::Ref *ref, js_type_class_t *typeClass, const char* debug) { JS::RootedObject proto(cx, typeClass->proto.ref()); JS::RootedObject parent(cx, typeClass->parentProto.ref()); JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent)); js_proxy_t* newproxy = jsb_new_proxy(ref, jsObj); jsb_ref_init(cx, &newproxy->obj, ref, debug); return jsObj; } JSObject* jsb_ref_autoreleased_create_jsobject(JSContext *cx, cocos2d::Ref *ref, js_type_class_t *typeClass, const char* debug) { JS::RootedObject proto(cx, typeClass->proto.ref()); JS::RootedObject parent(cx, typeClass->parentProto.ref()); JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent)); js_proxy_t* newproxy = jsb_new_proxy(ref, jsObj); jsb_ref_autoreleased_init(cx, &newproxy->obj, ref, debug); return jsObj; } JSObject* jsb_create_weak_jsobject(JSContext *cx, void *native, js_type_class_t *typeClass, const char* debug) { JS::RootedObject proto(cx, typeClass->proto.ref()); JS::RootedObject parent(cx, typeClass->parentProto.ref()); JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent)); auto proxy = jsb_new_proxy(native, jsObj); js_add_FinalizeHook(cx, jsObj, false); #if ! CC_ENABLE_GC_FOR_NATIVE_OBJECTS JS::AddNamedObjectRoot(cx, &proxy->obj, debug); #else CC_UNUSED_PARAM(proxy); #if COCOS2D_DEBUG > 1 if (debug != nullptr) { CCLOG("++++++WEAK_REF++++++ Cpp(%s): %p - JS: %p", debug, native, jsObj.get()); } #endif // COCOS2D_DEBUG #endif // CC_ENABLE_GC_FOR_NATIVE_OBJECTS return jsObj; } // get_or_create JSObject* jsb_ref_get_or_create_jsobject(JSContext *cx, cocos2d::Ref *ref, js_type_class_t *typeClass, const char* debug) { auto proxy = jsb_get_native_proxy(ref); if (proxy) { JS::RootedObject obj(cx, proxy->obj); return obj; } JS::RootedObject proto(cx, typeClass->proto.ref()); JS::RootedObject parent(cx, typeClass->parentProto.ref()); JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent)); js_proxy_t* newproxy = jsb_new_proxy(ref, jsObj); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS CC_UNUSED_PARAM(newproxy); ref->retain(); js_add_FinalizeHook(cx, jsObj, true); #if COCOS2D_DEBUG > 1 CCLOG("++++++RETAINED++++++ Cpp(%s): %p - JS: %p", debug, ref, jsObj.get()); #endif // COCOS2D_DEBUG #else // don't auto-release, don't retain. JS::AddNamedObjectRoot(cx, &newproxy->obj, debug); #endif // CC_ENABLE_GC_FOR_NATIVE_OBJECTS return jsObj; } // get_or_create: Ref is already autoreleased (or created) JSObject* jsb_ref_autoreleased_get_or_create_jsobject(JSContext *cx, cocos2d::Ref *ref, js_type_class_t *typeClass, const char* debug) { auto proxy = jsb_get_native_proxy(ref); if (proxy) { JS::RootedObject obj(cx, proxy->obj); return obj; } // else return jsb_ref_autoreleased_create_jsobject(cx, ref, typeClass, debug); } // get_or_create: when native object isn't a ref object or when the native object life cycle don't need to be managed by js object JSObject* jsb_get_or_create_weak_jsobject(JSContext *cx, void *native, js_type_class_t *typeClass, const char* debug) { auto proxy = jsb_get_native_proxy(native); if (proxy) { JS::RootedObject obj(cx, proxy->obj); return obj; } // don't auto-release, don't retain. JS::RootedObject proto(cx, typeClass->proto.ref()); JS::RootedObject parent(cx, typeClass->parentProto.ref()); JS::RootedObject jsObj(cx, JS_NewObject(cx, typeClass->jsclass, proto, parent)); proxy = jsb_new_proxy(native, jsObj); JS::RootedObject flag(cx, JS_NewObject(cx, nullptr, JS::NullPtr(), JS::NullPtr())); JS::RootedValue flagVal(cx, OBJECT_TO_JSVAL(flag)); JS_SetProperty(cx, jsObj, "__cppCreated", flagVal); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS js_add_FinalizeHook(cx, jsObj, false); #if COCOS2D_DEBUG > 1 if (debug != nullptr) { CCLOG("++++++WEAK_REF++++++ Cpp(%s): %p - JS: %p", debug, native, jsObj.get()); } #endif // COCOS2D_DEBUG #endif // CC_ENABLE_GC_FOR_NATIVE_OBJECTS return jsObj; } // ref_init void jsb_ref_init(JSContext* cx, JS::Heap *obj, Ref* ref, const char* debug) { // CCLOG("jsb_ref_init: JSObject address = %p. %s", obj->get(), debug); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS (void)ref; JS::RootedObject jsObj(cx, *obj); js_add_FinalizeHook(cx, jsObj, true); // don't retain it, already retained #if COCOS2D_DEBUG > 1 CCLOG("++++++RETAINED++++++ Cpp(%s): %p - JS: %p", debug, ref, jsObj.get()); #endif // COCOS2D_DEBUG #else // autorelease it ref->autorelease(); JS::AddNamedObjectRoot(cx, obj, debug); #endif } void jsb_ref_autoreleased_init(JSContext* cx, JS::Heap *obj, Ref* ref, const char* debug) { // CCLOG("jsb_ref_init: JSObject address = %p. %s", obj->get(), debug); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS (void)cx; (void)obj; ref->retain(); JS::RootedObject jsObj(cx, *obj); js_add_FinalizeHook(cx, jsObj, true); #if COCOS2D_DEBUG > 1 CCLOG("++++++RETAINED++++++ Cpp(%s): %p - JS: %p", debug, ref, jsObj.get()); #endif // COCOS2D_DEBUG #else // don't autorelease it, since it is already autoreleased JS::AddNamedObjectRoot(cx, obj, debug); #endif } // rebind void jsb_ref_rebind(JSContext* cx, JS::HandleObject jsobj, js_proxy_t *proxy, cocos2d::Ref* oldRef, cocos2d::Ref* newRef, const char* debug) { #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS // Release the old reference as it have been retained by jsobj previously, // and the jsobj won't have any chance to release it in the future oldRef->release(); #else JS::RemoveObjectRoot(cx, &proxy->obj); #endif jsb_remove_proxy(proxy); // Rebind js obj with new action js_proxy_t* newProxy = jsb_new_proxy(newRef, jsobj); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS CC_UNUSED_PARAM(newProxy); #else JS::AddNamedObjectRoot(cx, &newProxy->obj, debug); #endif } void jsb_non_ref_init(JSContext* cx, JS::Heap *obj, void* native, const char* debug) { // CCLOG("jsb_non_ref_init: JSObject address = %p. %s", obj->get(), debug); #if CC_ENABLE_GC_FOR_NATIVE_OBJECTS JS::RootedObject jsObj(cx, *obj); js_add_FinalizeHook(cx, jsObj, false); // don't retain it, already retained #if COCOS2D_DEBUG > 1 CCLOG("++++++RETAINED++++++ Cpp(%s): %p - JS: %p", debug, native, jsObj.get()); #endif // COCOS2D_DEBUG #endif } // Register finalize hook void jsb_register_finalize_hook(JSObject *hook, JSObject *owner) { _js_hook_owner_map.insert(std::make_pair(hook, owner)); } // Remove hook owner entry in _js_hook_owner_map JSObject *jsb_get_and_remove_hook_owner(JSObject *hook) { auto ownerIt = _js_hook_owner_map.find(hook); // Found if (ownerIt != _js_hook_owner_map.cend()) { JSObject *result = ownerIt->second; _js_hook_owner_map.erase(ownerIt); return result; } return nullptr; }