JSEventHelper.cpp 4.7 KB

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