jsb_websocket.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /****************************************************************************
  2. Copyright (c) 2017-2018 Xiamen Yaji Software Co., Ltd.
  3. http://www.cocos.com
  4. Permission is hereby granted, free of charge, to any person obtaining a copy
  5. of this software and associated engine source code (the "Software"), a limited,
  6. worldwide, royalty-free, non-assignable, revocable and non-exclusive license
  7. to use Cocos Creator solely to develop games on your target platforms. You shall
  8. not use Cocos Creator software for developing other software or tools that's
  9. used for developing games. You are not granted to publish, distribute,
  10. sublicense, and/or sell copies of Cocos Creator.
  11. The software or tools in this License Agreement are licensed, not sold.
  12. Xiamen Yaji Software Co., Ltd. reserves all rights not expressly granted to you.
  13. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  14. IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  15. FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  16. AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  17. LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  18. OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  19. THE SOFTWARE.
  20. ****************************************************************************/
  21. #include "base/ccConfig.h"
  22. #include "jsb_websocket.hpp"
  23. #if (USE_SOCKET > 0) && (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)
  24. #include "cocos/scripting/js-bindings/jswrapper/SeApi.h"
  25. #include "cocos/scripting/js-bindings/manual/jsb_conversions.hpp"
  26. #include "cocos/scripting/js-bindings/manual/jsb_global.h"
  27. #include "base/ccUTF8.h"
  28. #include "platform/CCApplication.h"
  29. using namespace cocos2d;
  30. using namespace cocos2d::network;
  31. /*
  32. [Constructor(in DOMString url, in optional DOMString protocols)]
  33. [Constructor(in DOMString url, in optional DOMString[] protocols)]
  34. interface WebSocket {
  35. readonly attribute DOMString url;
  36. // ready state
  37. const unsigned short CONNECTING = 0;
  38. const unsigned short OPEN = 1;
  39. const unsigned short CLOSING = 2;
  40. const unsigned short CLOSED = 3;
  41. readonly attribute unsigned short readyState;
  42. readonly attribute unsigned long bufferedAmount;
  43. // networking
  44. attribute Function onopen;
  45. attribute Function onmessage;
  46. attribute Function onerror;
  47. attribute Function onclose;
  48. readonly attribute DOMString protocol;
  49. void send(in DOMString data);
  50. void close();
  51. };
  52. WebSocket implements EventTarget;
  53. */
  54. se::Class* __jsb_WebSocket_class = nullptr;
  55. JSB_WebSocketDelegate::JSB_WebSocketDelegate()
  56. {
  57. }
  58. JSB_WebSocketDelegate::~JSB_WebSocketDelegate()
  59. {
  60. CCLOGINFO("In the destructor of JSB_WebSocketDelegate(%p)", this);
  61. }
  62. void JSB_WebSocketDelegate::onOpen(WebSocket* ws)
  63. {
  64. se::ScriptEngine::getInstance()->clearException();
  65. se::AutoHandleScope hs;
  66. if (cocos2d::Application::getInstance() == nullptr)
  67. return;
  68. auto iter = se::NativePtrToObjectMap::find(ws);
  69. if (iter == se::NativePtrToObjectMap::end())
  70. return;
  71. se::Object* wsObj = iter->second;
  72. wsObj->setProperty("protocol", se::Value(ws->getProtocol()));
  73. se::HandleObject jsObj(se::Object::createPlainObject());
  74. jsObj->setProperty("type", se::Value("open"));
  75. se::Value target;
  76. native_ptr_to_seval<WebSocket>(ws, &target);
  77. jsObj->setProperty("target", target);
  78. se::Value func;
  79. bool ok = _JSDelegate.toObject()->getProperty("onopen", &func);
  80. if (ok && func.isObject() && func.toObject()->isFunction())
  81. {
  82. se::ValueArray args;
  83. args.push_back(se::Value(jsObj));
  84. func.toObject()->call(args, wsObj);
  85. }
  86. else
  87. {
  88. SE_REPORT_ERROR("Can't get onopen function!");
  89. }
  90. }
  91. void JSB_WebSocketDelegate::onMessage(WebSocket* ws, const WebSocket::Data& data)
  92. {
  93. se::ScriptEngine::getInstance()->clearException();
  94. se::AutoHandleScope hs;
  95. if (cocos2d::Application::getInstance() == nullptr)
  96. return;
  97. auto iter = se::NativePtrToObjectMap::find(ws);
  98. if (iter == se::NativePtrToObjectMap::end())
  99. return;
  100. se::Object* wsObj = iter->second;
  101. se::HandleObject jsObj(se::Object::createPlainObject());
  102. jsObj->setProperty("type", se::Value("message"));
  103. se::Value target;
  104. native_ptr_to_seval<WebSocket>(ws, &target);
  105. jsObj->setProperty("target", target);
  106. se::Value func;
  107. bool ok = _JSDelegate.toObject()->getProperty("onmessage", &func);
  108. if (ok && func.isObject() && func.toObject()->isFunction())
  109. {
  110. se::ValueArray args;
  111. args.push_back(se::Value(jsObj));
  112. if (data.isBinary)
  113. {
  114. se::HandleObject dataObj(se::Object::createArrayBufferObject(data.bytes, data.len));
  115. jsObj->setProperty("data", se::Value(dataObj));
  116. }
  117. else
  118. {
  119. se::Value dataVal;
  120. if (strlen(data.bytes) == 0 && data.len > 0)
  121. {// String with 0x00 prefix
  122. std::string str(data.bytes, data.len);
  123. dataVal.setString(str);
  124. }
  125. else
  126. {// Normal string
  127. dataVal.setString(data.bytes);
  128. }
  129. if (dataVal.isNullOrUndefined())
  130. {
  131. ws->closeAsync();
  132. }
  133. else
  134. {
  135. jsObj->setProperty("data", se::Value(dataVal));
  136. }
  137. }
  138. func.toObject()->call(args, wsObj);
  139. }
  140. else
  141. {
  142. SE_REPORT_ERROR("Can't get onmessage function!");
  143. }
  144. }
  145. void JSB_WebSocketDelegate::onClose(WebSocket* ws)
  146. {
  147. se::ScriptEngine::getInstance()->clearException();
  148. se::AutoHandleScope hs;
  149. if (cocos2d::Application::getInstance() == nullptr)
  150. return;
  151. auto iter = se::NativePtrToObjectMap::find(ws);
  152. do
  153. {
  154. if (iter == se::NativePtrToObjectMap::end())
  155. {
  156. CCLOGINFO("WebSocket js instance was destroyted, don't need to invoke onclose callback!");
  157. break;
  158. }
  159. se::Object* wsObj = iter->second;
  160. se::HandleObject jsObj(se::Object::createPlainObject());
  161. jsObj->setProperty("type", se::Value("close"));
  162. se::Value target;
  163. native_ptr_to_seval<WebSocket>(ws, &target);
  164. jsObj->setProperty("target", target);
  165. se::Value func;
  166. bool ok = _JSDelegate.toObject()->getProperty("onclose", &func);
  167. if (ok && func.isObject() && func.toObject()->isFunction())
  168. {
  169. se::ValueArray args;
  170. args.push_back(se::Value(jsObj));
  171. func.toObject()->call(args, wsObj);
  172. }
  173. else
  174. {
  175. SE_REPORT_ERROR("Can't get onclose function!");
  176. }
  177. //JS Websocket object now can be GC, since the connection is closed.
  178. wsObj->unroot();
  179. // Websocket instance is attached to global object in 'WebSocket_close'
  180. // It's safe to detach it here since JS 'onclose' method has been already invoked.
  181. se::ScriptEngine::getInstance()->getGlobalObject()->detachObject(wsObj);
  182. } while(false);
  183. ws->release();
  184. release(); // Release delegate self at last
  185. }
  186. void JSB_WebSocketDelegate::onError(WebSocket* ws, const WebSocket::ErrorCode& error)
  187. {
  188. se::ScriptEngine::getInstance()->clearException();
  189. se::AutoHandleScope hs;
  190. if (cocos2d::Application::getInstance() == nullptr)
  191. return;
  192. auto iter = se::NativePtrToObjectMap::find(ws);
  193. if (iter == se::NativePtrToObjectMap::end())
  194. return;
  195. se::Object* wsObj = iter->second;
  196. se::HandleObject jsObj(se::Object::createPlainObject());
  197. jsObj->setProperty("type", se::Value("error"));
  198. se::Value target;
  199. native_ptr_to_seval<WebSocket>(ws, &target);
  200. jsObj->setProperty("target", target);
  201. se::Value func;
  202. bool ok = _JSDelegate.toObject()->getProperty("onerror", &func);
  203. if (ok && func.isObject() && func.toObject()->isFunction())
  204. {
  205. se::ValueArray args;
  206. args.push_back(se::Value(jsObj));
  207. func.toObject()->call(args, wsObj);
  208. }
  209. else
  210. {
  211. SE_REPORT_ERROR("Can't get onerror function!");
  212. }
  213. wsObj->unroot();
  214. }
  215. void JSB_WebSocketDelegate::setJSDelegate(const se::Value& jsDelegate)
  216. {
  217. assert(jsDelegate.isObject());
  218. _JSDelegate = jsDelegate;
  219. }
  220. static bool WebSocket_finalize(se::State& s)
  221. {
  222. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  223. CCLOGINFO("jsbindings: finalizing JS object %p (WebSocket)", cobj);
  224. // Manually close if web socket is not closed
  225. if (cobj->getReadyState() != WebSocket::State::CLOSED)
  226. {
  227. CCLOGINFO("WebSocket (%p) isn't closed, try to close it!", cobj);
  228. cobj->closeAsync();
  229. }
  230. static_cast<JSB_WebSocketDelegate*>(cobj->getDelegate())->release();
  231. if (cobj->getReferenceCount() == 1)
  232. cobj->autorelease();
  233. else
  234. cobj->release();
  235. return true;
  236. }
  237. SE_BIND_FINALIZE_FUNC(WebSocket_finalize)
  238. static bool WebSocket_constructor(se::State& s)
  239. {
  240. const auto& args = s.args();
  241. int argc = (int)args.size();
  242. if (argc == 1 || argc == 2 || argc == 3)
  243. {
  244. std::string url;
  245. bool ok = seval_to_std_string(args[0], &url);
  246. SE_PRECONDITION2(ok, false, "Error processing url argument");
  247. se::Object* obj = s.thisObject();
  248. WebSocket* cobj = nullptr;
  249. if (argc >= 2)
  250. {
  251. std::string caFilePath;
  252. std::vector<std::string> protocols;
  253. if (args[1].isString())
  254. {
  255. std::string protocol;
  256. ok = seval_to_std_string(args[1], &protocol);
  257. SE_PRECONDITION2(ok, false, "Error processing protocol string");
  258. protocols.push_back(protocol);
  259. }
  260. else if (args[1].isObject() && args[1].toObject()->isArray())
  261. {
  262. se::Object* protocolArr = args[1].toObject();
  263. uint32_t len = 0;
  264. ok = protocolArr->getArrayLength(&len);
  265. SE_PRECONDITION2(ok, false, "getArrayLength failed!");
  266. se::Value tmp;
  267. for (uint32_t i=0; i < len; ++i)
  268. {
  269. if (!protocolArr->getArrayElement(i, &tmp))
  270. continue;
  271. std::string protocol;
  272. ok = seval_to_std_string(tmp, &protocol);
  273. SE_PRECONDITION2(ok, false, "Error processing protocol object");
  274. protocols.push_back(protocol);
  275. }
  276. }
  277. if (argc > 2)
  278. {
  279. ok = seval_to_std_string(args[2], &caFilePath);
  280. SE_PRECONDITION2(ok, false, "Error processing caFilePath");
  281. }
  282. cobj = new (std::nothrow) WebSocket();
  283. JSB_WebSocketDelegate* delegate = new (std::nothrow) JSB_WebSocketDelegate();
  284. if (cobj->init(*delegate, url, &protocols, caFilePath))
  285. {
  286. delegate->setJSDelegate(se::Value(obj, true));
  287. cobj->retain(); // release in finalize function and onClose delegate method
  288. delegate->retain(); // release in finalize function and onClose delegate method
  289. }
  290. else
  291. {
  292. cobj->release();
  293. delegate->release();
  294. SE_REPORT_ERROR("WebSocket init failed!");
  295. return false;
  296. }
  297. }
  298. else
  299. {
  300. cobj = new (std::nothrow) WebSocket();
  301. JSB_WebSocketDelegate* delegate = new (std::nothrow) JSB_WebSocketDelegate();
  302. if (cobj->init(*delegate, url))
  303. {
  304. delegate->setJSDelegate(se::Value(obj, true));
  305. cobj->retain(); // release in finalize function and onClose delegate method
  306. delegate->retain(); // release in finalize function and onClose delegate method
  307. }
  308. else
  309. {
  310. cobj->release();
  311. delegate->release();
  312. SE_REPORT_ERROR("WebSocket init failed!");
  313. return false;
  314. }
  315. }
  316. obj->setProperty("url", args[0]);
  317. // The websocket draft uses lowercase 'url', so 'URL' need to be deprecated.
  318. obj->setProperty("URL", args[0]);
  319. // Initialize protocol property with an empty string, it will be assigned in onOpen delegate.
  320. obj->setProperty("protocol", se::Value(""));
  321. obj->setPrivateData(cobj);
  322. obj->root();
  323. return true;
  324. }
  325. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1<= and <=3", argc);
  326. return false;
  327. }
  328. SE_BIND_CTOR(WebSocket_constructor, __jsb_WebSocket_class, WebSocket_finalize)
  329. static bool WebSocket_send(se::State& s)
  330. {
  331. const auto& args = s.args();
  332. int argc = (int)args.size();
  333. if (argc == 1)
  334. {
  335. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  336. bool ok = false;
  337. if (args[0].isString())
  338. {
  339. std::string data;
  340. ok = seval_to_std_string(args[0], &data);
  341. SE_PRECONDITION2(ok, false, "Convert string failed");
  342. //IDEA: We didn't find a way to get the JS string length in JSB2.0.
  343. // if (data.empty() && len > 0)
  344. // {
  345. // CCLOGWARN("Text message to send is empty, but its length is greater than 0!");
  346. // //IDEA: Note that this text message contains '0x00' prefix, so its length calcuted by strlen is 0.
  347. // // we need to fix that if there is '0x00' in text message,
  348. // // since javascript language could support '0x00' inserted at the beginning or the middle of text message
  349. // }
  350. cobj->send(data);
  351. }
  352. else if (args[0].isObject())
  353. {
  354. se::Object* dataObj = args[0].toObject();
  355. uint8_t* ptr = nullptr;
  356. size_t length = 0;
  357. if (dataObj->isArrayBuffer())
  358. {
  359. ok = dataObj->getArrayBufferData(&ptr, &length);
  360. SE_PRECONDITION2(ok, false, "getArrayBufferData failed!");
  361. }
  362. else if (dataObj->isTypedArray())
  363. {
  364. ok = dataObj->getTypedArrayData(&ptr, &length);
  365. SE_PRECONDITION2(ok, false, "getTypedArrayData failed!");
  366. }
  367. else
  368. {
  369. assert(false);
  370. }
  371. cobj->send(ptr, (unsigned int)length);
  372. }
  373. else
  374. {
  375. assert(false);
  376. }
  377. return true;
  378. }
  379. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 1", argc);
  380. return false;
  381. }
  382. SE_BIND_FUNC(WebSocket_send)
  383. static bool WebSocket_close(se::State& s)
  384. {
  385. const auto& args = s.args();
  386. int argc = (int)args.size();
  387. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  388. if(argc == 0)
  389. {
  390. cobj->closeAsync();
  391. }
  392. else if (argc == 1)
  393. {
  394. if (args[0].isNumber())
  395. {
  396. int reason;
  397. seval_to_int32(args[0], &reason);
  398. cobj->closeAsync(reason, "no_reason");
  399. }
  400. else if (args[0].isString())
  401. {
  402. std::string reason;
  403. seval_to_std_string(args[0], &reason);
  404. cobj->closeAsync(1005, reason);
  405. }
  406. else
  407. {
  408. assert(false);
  409. }
  410. }
  411. else if (argc == 2)
  412. {
  413. assert(args[0].isNumber());
  414. assert(args[1].isString());
  415. int reasonCode;
  416. std::string reasonString;
  417. seval_to_int32(args[0], &reasonCode);
  418. seval_to_std_string(args[1], &reasonString);
  419. cobj->closeAsync(reasonCode, reasonString);
  420. }
  421. else
  422. {
  423. assert(false);
  424. }
  425. // Attach current WebSocket instance to global object to prevent WebSocket instance
  426. // being garbage collected after "ws.close(); ws = null;"
  427. // There is a state that current WebSocket JS instance is being garbaged but its finalize
  428. // callback has not be invoked. Then in "JSB_WebSocketDelegate::onClose", se::Object is
  429. // still be able to be found and while invoking JS 'onclose' method, crash will happen since
  430. // JS instance is invalid and is going to be collected. This bug is easiler reproduced on iOS
  431. // because JavaScriptCore is more GC sensitive.
  432. // Please note that we need to detach it from global object in "JSB_WebSocketDelegate::onClose".
  433. se::ScriptEngine::getInstance()->getGlobalObject()->attachObject(s.thisObject());
  434. return true;
  435. }
  436. SE_BIND_FUNC(WebSocket_close)
  437. static bool WebSocket_getReadyState(se::State& s)
  438. {
  439. const auto& args = s.args();
  440. int argc = (int)args.size();
  441. if (argc == 0)
  442. {
  443. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  444. s.rval().setInt32((int)cobj->getReadyState());
  445. return true;
  446. }
  447. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc);
  448. return false;
  449. }
  450. SE_BIND_PROP_GET(WebSocket_getReadyState)
  451. static bool WebSocket_getBufferedAmount(se::State& s)
  452. {
  453. const auto& args = s.args();
  454. int argc = (int)args.size();
  455. if (argc == 0)
  456. {
  457. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  458. s.rval().setUint32((uint32_t)cobj->getBufferedAmount());
  459. return true;
  460. }
  461. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc);
  462. return false;
  463. }
  464. SE_BIND_PROP_GET(WebSocket_getBufferedAmount)
  465. static bool WebSocket_getExtensions(se::State& s)
  466. {
  467. const auto& args = s.args();
  468. int argc = (int)args.size();
  469. if (argc == 0)
  470. {
  471. WebSocket* cobj = (WebSocket*)s.nativeThisObject();
  472. s.rval().setString(cobj->getExtensions());
  473. return true;
  474. }
  475. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc);
  476. return false;
  477. }
  478. SE_BIND_PROP_GET(WebSocket_getExtensions)
  479. #define WEBSOCKET_DEFINE_READONLY_INT_FIELD(full_name, value) \
  480. static bool full_name(se::State& s) \
  481. { \
  482. const auto& args = s.args(); \
  483. int argc = (int)args.size(); \
  484. if (argc == 0) \
  485. { \
  486. s.rval().setInt32(value); \
  487. return true; \
  488. } \
  489. SE_REPORT_ERROR("wrong number of arguments: %d, was expecting 0", argc); \
  490. return false; \
  491. } \
  492. SE_BIND_PROP_GET(full_name)
  493. WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CONNECTING, (int)WebSocket::State::CONNECTING)
  494. WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_OPEN, (int)WebSocket::State::OPEN)
  495. WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CLOSING, (int)WebSocket::State::CLOSING)
  496. WEBSOCKET_DEFINE_READONLY_INT_FIELD(Websocket_CLOSED, (int)WebSocket::State::CLOSED)
  497. bool register_all_websocket(se::Object* obj)
  498. {
  499. se::Class* cls = se::Class::create("WebSocket", obj, nullptr, _SE(WebSocket_constructor));
  500. cls->defineFinalizeFunction(_SE(WebSocket_finalize));
  501. cls->defineFunction("send", _SE(WebSocket_send));
  502. cls->defineFunction("close", _SE(WebSocket_close));
  503. cls->defineProperty("readyState", _SE(WebSocket_getReadyState), nullptr);
  504. cls->defineProperty("bufferedAmount", _SE(WebSocket_getBufferedAmount), nullptr);
  505. cls->defineProperty("extensions", _SE(WebSocket_getExtensions), nullptr);
  506. cls->defineProperty("CONNECTING", _SE(Websocket_CONNECTING), nullptr);
  507. cls->defineProperty("CLOSING", _SE(Websocket_CLOSING), nullptr);
  508. cls->defineProperty("OPEN", _SE(Websocket_OPEN), nullptr);
  509. cls->defineProperty("CLOSED", _SE(Websocket_CLOSED), nullptr);
  510. cls->install();
  511. se::Value tmp;
  512. obj->getProperty("WebSocket", &tmp);
  513. tmp.toObject()->defineProperty("CONNECTING", _SE(Websocket_CONNECTING), nullptr);
  514. tmp.toObject()->defineProperty("CLOSING", _SE(Websocket_CLOSING), nullptr);
  515. tmp.toObject()->defineProperty("OPEN", _SE(Websocket_OPEN), nullptr);
  516. tmp.toObject()->defineProperty("CLOSED", _SE(Websocket_CLOSED), nullptr);
  517. JSBClassType::registerClass<WebSocket>(cls);
  518. __jsb_WebSocket_class = cls;
  519. se::ScriptEngine::getInstance()->clearException();
  520. return true;
  521. }
  522. #endif //#if (USE_SOCKET > 0) && (CC_TARGET_PLATFORM == CC_PLATFORM_ANDROID || CC_TARGET_PLATFORM == CC_PLATFORM_IOS || CC_TARGET_PLATFORM == CC_PLATFORM_MAC || CC_TARGET_PLATFORM == CC_PLATFORM_WIN32)