JSEventHelper.cpp 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  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. }
  89. void JSEventHelper::AddEventHandler(StringHash eventType)
  90. {
  91. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  92. // subscribe using object, so unsubscribing from object and not the event helper works
  93. object_->SubscribeToEvent(eventType, HANDLER(JSEventHelper, HandleEvent));
  94. }
  95. void JSEventHelper::AddEventHandler(Object* sender, StringHash eventType)
  96. {
  97. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  98. // subscribe using object, so unsubscribing from object and not the event helper works
  99. object_->SubscribeToEvent(sender, eventType, HANDLER(JSEventHelper, HandleEvent));
  100. }
  101. void JSEventHelper::HandleEvent(StringHash eventType, VariantMap& eventData)
  102. {
  103. if (object_.Null())
  104. return;
  105. JSVM* vm = JSVM::GetJSVM(0);
  106. duk_context* ctx = vm->GetJSContext();
  107. duk_idx_t top = duk_get_top(ctx);
  108. js_push_class_object_instance(ctx, this);
  109. duk_get_prop_string(ctx, -1, "__eventHelperFunctions");
  110. assert(duk_is_object(ctx, -1));
  111. duk_get_prop_string(ctx, -1, eventType.ToString().CString());
  112. if (duk_is_function(ctx, -1))
  113. {
  114. // look in the variant map cache
  115. duk_push_global_stash(ctx);
  116. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_VARIANTMAP_CACHE);
  117. duk_push_pointer(ctx, (void*) &eventData);
  118. duk_get_prop(ctx, -2);
  119. if (!duk_is_object(ctx, -1))
  120. {
  121. // pop result
  122. duk_pop(ctx);
  123. // we need to push a new variant map and store to cache
  124. // the cache object will be cleared at the send end in the
  125. // global listener above
  126. js_push_variantmap(ctx, eventData);
  127. duk_push_pointer(ctx, (void*) &eventData);
  128. duk_dup(ctx, -2);
  129. duk_put_prop(ctx, -4);
  130. }
  131. duk_remove(ctx, -2); // vmap cache
  132. duk_remove(ctx, -2); // global stash
  133. if (duk_pcall(ctx, 1) != 0)
  134. {
  135. vm->SendJSErrorEvent();
  136. }
  137. else
  138. {
  139. // For widget events, need to check return value
  140. // and set whether handled
  141. if (eventType == E_WIDGETEVENT)
  142. {
  143. if (duk_is_boolean(ctx, -1))
  144. {
  145. if (duk_to_boolean(ctx, -1))
  146. {
  147. eventData[WidgetEvent::P_HANDLED] = true;
  148. }
  149. }
  150. }
  151. }
  152. }
  153. duk_set_top(ctx, top);
  154. }
  155. }