JSEventHelper.cpp 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. //
  2. // Copyright (c) 2014-2015, THUNDERBEAST GAMES LLC All rights reserved
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include <Atomic/UI/UIEvents.h>
  23. #include "JSVM.h"
  24. #include "JSEventHelper.h"
  25. namespace Atomic
  26. {
  27. JSEventDispatcher::JSEventDispatcher(Context* context) :
  28. Object(context)
  29. {
  30. }
  31. JSEventDispatcher::~JSEventDispatcher()
  32. {
  33. }
  34. void JSEventDispatcher::BeginSendEvent(Context* context, Object* sender, StringHash eventType, VariantMap& eventData)
  35. {
  36. }
  37. void JSEventDispatcher::EndSendEvent(Context* context, Object* sender, StringHash eventType, VariantMap& eventData)
  38. {
  39. if (!jsEvents_.Contains(eventType))
  40. return;
  41. JSVM* vm = JSVM::GetJSVM(NULL);
  42. if (!vm)
  43. return;
  44. duk_context* ctx = vm->GetJSContext();
  45. duk_push_global_stash(ctx);
  46. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_VARIANTMAP_CACHE);
  47. duk_push_pointer(ctx, (void*) &eventData);
  48. duk_get_prop(ctx, -2);
  49. // If this issue is addressed, revisit this to simply remove
  50. // the variantmap object from the cache, if the user explicitly
  51. // keeps the event object alive in a local closure, that is allowed
  52. // (though, will keep object properties from being GC'd)
  53. // https://github.com/svaarala/duktape/issues/229
  54. // Ok, this is unfortunate, in an event callback it is possible
  55. // to capture the Proxy object which represents the event VariantMap
  56. // in a function() {} closure, which will keep the event data alive
  57. // until the function happens to be gc'd (it is a member of the eventhandler)
  58. // which will leave things like scenes up if there was a P_SCENE event data
  59. // member, etc
  60. // So, we need to check if we have an object in the variant map cache
  61. // and thense call it's delete property method on the Proxy, which will clear
  62. // all the data (the proxy can still be alive as a captured local, though
  63. // the members won't be held
  64. // This all makes it that much more important that the pointer to eventData
  65. // is consistent across entire event, otherwise references may be held
  66. // see note above about: https://github.com/svaarala/duktape/issues/229
  67. if (duk_is_object(ctx, -1))
  68. {
  69. // deletes all properties, thus freeing references, even if
  70. // the variant map object is held onto by script (it will be invalid, post
  71. // event send)
  72. // see JSAPI.cpp variantmap_property_deleteproperty
  73. duk_del_prop_index(ctx, -1, 0);
  74. duk_push_pointer(ctx, (void*) &eventData);
  75. duk_push_undefined(ctx);
  76. // clear the variant map object from the cache
  77. duk_put_prop(ctx, -4);
  78. }
  79. duk_pop_3(ctx);
  80. }
  81. JSEventHelper::JSEventHelper(Context* context, Object* object) :
  82. Object(context),
  83. object_(object)
  84. {
  85. }
  86. JSEventHelper::~JSEventHelper()
  87. {
  88. Clear();
  89. }
  90. void JSEventHelper::Clear()
  91. {
  92. UnsubscribeFromAllEvents();
  93. if (!object_.Expired())
  94. {
  95. object_->UnsubscribeFromEventReceiver(this);
  96. }
  97. }
  98. void JSEventHelper::AddEventHandler(StringHash eventType)
  99. {
  100. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  101. // subscribe using object, so unsubscribing from object and not the event helper works
  102. object_->SubscribeToEvent(eventType, ATOMIC_HANDLER(JSEventHelper, HandleEvent));
  103. }
  104. void JSEventHelper::AddEventHandler(Object* sender, StringHash eventType)
  105. {
  106. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  107. // subscribe using object, so unsubscribing from object and not the event helper works
  108. object_->SubscribeToEvent(sender, eventType, ATOMIC_HANDLER(JSEventHelper, HandleEvent));
  109. }
  110. void JSEventHelper::HandleEvent(StringHash eventType, VariantMap& eventData)
  111. {
  112. if (object_.Expired())
  113. return;
  114. JSVM* vm = JSVM::GetJSVM(0);
  115. duk_context* ctx = vm->GetJSContext();
  116. duk_idx_t top = duk_get_top(ctx);
  117. js_push_class_object_instance(ctx, this);
  118. duk_get_prop_string(ctx, -1, "__eventHelperFunctions");
  119. assert(duk_is_object(ctx, -1));
  120. duk_get_prop_string(ctx, -1, eventType.ToString().CString());
  121. if (duk_is_function(ctx, -1))
  122. {
  123. // look in the variant map cache
  124. duk_push_global_stash(ctx);
  125. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_VARIANTMAP_CACHE);
  126. duk_push_pointer(ctx, (void*) &eventData);
  127. duk_get_prop(ctx, -2);
  128. if (!duk_is_object(ctx, -1))
  129. {
  130. // pop result
  131. duk_pop(ctx);
  132. // we need to push a new variant map and store to cache
  133. // the cache object will be cleared at the send end in the
  134. // global listener above
  135. js_push_variantmap(ctx, eventData);
  136. duk_push_pointer(ctx, (void*) &eventData);
  137. duk_dup(ctx, -2);
  138. duk_put_prop(ctx, -4);
  139. }
  140. duk_remove(ctx, -2); // vmap cache
  141. duk_remove(ctx, -2); // global stash
  142. if (duk_pcall(ctx, 1) != 0)
  143. {
  144. vm->SendJSErrorEvent();
  145. }
  146. else
  147. {
  148. // For widget events, need to check return value
  149. // and set whether handled
  150. if (eventType == E_WIDGETEVENT)
  151. {
  152. if (duk_is_boolean(ctx, -1))
  153. {
  154. if (duk_to_boolean(ctx, -1))
  155. {
  156. eventData[WidgetEvent::P_HANDLED] = true;
  157. }
  158. }
  159. }
  160. }
  161. }
  162. duk_set_top(ctx, top);
  163. }
  164. }