JSEventHelper.cpp 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183
  1. #include <Atomic/UI/UIEvents.h>
  2. #include "JSVM.h"
  3. #include "JSEventHelper.h"
  4. namespace Atomic
  5. {
  6. JSEventDispatcher::JSEventDispatcher(Context* context) :
  7. Object(context)
  8. {
  9. }
  10. JSEventDispatcher::~JSEventDispatcher()
  11. {
  12. }
  13. void JSEventDispatcher::BeginSendEvent(Context* context, Object* sender, StringHash eventType, VariantMap& eventData)
  14. {
  15. }
  16. void JSEventDispatcher::EndSendEvent(Context* context, Object* sender, StringHash eventType, VariantMap& eventData)
  17. {
  18. if (!jsEvents_.Contains(eventType))
  19. return;
  20. JSVM* vm = JSVM::GetJSVM(NULL);
  21. if (!vm)
  22. return;
  23. duk_context* ctx = vm->GetJSContext();
  24. duk_push_global_stash(ctx);
  25. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_VARIANTMAP_CACHE);
  26. duk_push_pointer(ctx, (void*) &eventData);
  27. duk_get_prop(ctx, -2);
  28. // If this issue is addressed, revisit this to simply remove
  29. // the variantmap object from the cache, if the user explicitly
  30. // keeps the event object alive in a local closure, that is allowed
  31. // (though, will keep object properties from being GC'd)
  32. // https://github.com/svaarala/duktape/issues/229
  33. // Ok, this is unfortunate, in an event callback it is possible
  34. // to capture the Proxy object which represents the event VariantMap
  35. // in a function() {} closure, which will keep the event data alive
  36. // until the function happens to be gc'd (it is a member of the eventhandler)
  37. // which will leave things like scenes up if there was a P_SCENE event data
  38. // member, etc
  39. // So, we need to check if we have an object in the variant map cache
  40. // and thense call it's delete property method on the Proxy, which will clear
  41. // all the data (the proxy can still be alive as a captured local, though
  42. // the members won't be held
  43. // This all makes it that much more important that the pointer to eventData
  44. // is consistent across entire event, otherwise references may be held
  45. // see note above about: https://github.com/svaarala/duktape/issues/229
  46. if (duk_is_object(ctx, -1))
  47. {
  48. // deletes all properties, thus freeing references, even if
  49. // the variant map object is held onto by script (it will be invalid, post
  50. // event send)
  51. // see JSAPI.cpp variantmap_property_deleteproperty
  52. duk_del_prop_index(ctx, -1, 0);
  53. duk_push_pointer(ctx, (void*) &eventData);
  54. duk_push_undefined(ctx);
  55. // clear the variant map object from the cache
  56. duk_put_prop(ctx, -4);
  57. }
  58. duk_pop_3(ctx);
  59. }
  60. JSEventHelper::JSEventHelper(Context* context, Object* object) :
  61. Object(context),
  62. object_(object)
  63. {
  64. }
  65. JSEventHelper::~JSEventHelper()
  66. {
  67. }
  68. void JSEventHelper::AddEventHandler(StringHash eventType)
  69. {
  70. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  71. // subscribe using object, so unsubscribing from object and not the event helper works
  72. object_->SubscribeToEvent(eventType, HANDLER(JSEventHelper, HandleEvent));
  73. }
  74. void JSEventHelper::AddEventHandler(Object* sender, StringHash eventType)
  75. {
  76. GetSubsystem<JSEventDispatcher>()->RegisterJSEvent(eventType);
  77. // subscribe using object, so unsubscribing from object and not the event helper works
  78. object_->SubscribeToEvent(sender, eventType, HANDLER(JSEventHelper, HandleEvent));
  79. }
  80. void JSEventHelper::HandleEvent(StringHash eventType, VariantMap& eventData)
  81. {
  82. if (object_.Null())
  83. return;
  84. JSVM* vm = JSVM::GetJSVM(0);
  85. duk_context* ctx = vm->GetJSContext();
  86. duk_idx_t top = duk_get_top(ctx);
  87. js_push_class_object_instance(ctx, this);
  88. duk_get_prop_string(ctx, -1, "__eventHelperFunctions");
  89. assert(duk_is_object(ctx, -1));
  90. duk_get_prop_string(ctx, -1, eventType.ToString().CString());
  91. if (duk_is_function(ctx, -1))
  92. {
  93. // look in the variant map cache
  94. duk_push_global_stash(ctx);
  95. duk_get_prop_index(ctx, -1, JS_GLOBALSTASH_VARIANTMAP_CACHE);
  96. duk_push_pointer(ctx, (void*) &eventData);
  97. duk_get_prop(ctx, -2);
  98. if (!duk_is_object(ctx, -1))
  99. {
  100. // pop result
  101. duk_pop(ctx);
  102. // we need to push a new variant map and store to cache
  103. // the cache object will be cleared at the send end in the
  104. // global listener above
  105. js_push_variantmap(ctx, eventData);
  106. duk_push_pointer(ctx, (void*) &eventData);
  107. duk_dup(ctx, -2);
  108. duk_put_prop(ctx, -4);
  109. }
  110. duk_remove(ctx, -2); // vmap cache
  111. duk_remove(ctx, -2); // global stash
  112. if (duk_pcall(ctx, 1) != 0)
  113. {
  114. vm->SendJSErrorEvent();
  115. }
  116. else
  117. {
  118. // For widget events, need to check return value
  119. // and set whether handled
  120. if (eventType == E_WIDGETEVENT)
  121. {
  122. if (duk_is_boolean(ctx, -1))
  123. {
  124. if (duk_to_boolean(ctx, -1))
  125. {
  126. eventData[WidgetEvent::P_HANDLED] = true;
  127. }
  128. }
  129. }
  130. }
  131. }
  132. duk_set_top(ctx, top);
  133. }
  134. }