evas: Strengthen post-event callbacks
authorJean-Philippe Andre <jp.andre@samsung.com>
Thu, 16 Feb 2017 07:47:57 +0000 (16:47 +0900)
committerJean-Philippe Andre <jp.andre@samsung.com>
Thu, 16 Feb 2017 09:10:18 +0000 (18:10 +0900)
See T3144 that I marked as Wontfix.

Bryce in E manually feeds events from a post-event callback
resulting in Evas going insane and leading to frequent crashes.
The ideal solution (for E) would be to ensure that everything works
smoothly, the input event data is valid up until the post-event cb
is called, etc... Unfortunately, with recursive events the exact
order of operations may be messed up: the post-event

I don't want to add yet more complexity to Evas events here (it's
already spaghetti all over the place) so I'm simply blocking any
new event feed when running the post-event callback list.

It's not possible to just freeze the events (I tried, it failed).

**********************
Some more explanation:

post-event callbacks are used to implement reverse-order logic
where the on-hold flag of an input event may be set by an event
listener that does not come first.

Here's a situation to illustrate: scroller A inside scroller B.

As events are propagated from children to parents (assuming the
propagate flag is set), we'd assume the events to go first to A
and then to B, which means a mouse wheel event would make the
inner-most scroller (A) scroll, and the outer-most scroller (B)
wouldn't budge.

But as things are designed, A and B are not simple evas objects,
and the actual event-catching object is a top-most transparent
rectangle (top-most in Z stack order). Since A is inside B, B's
rectangle BR is over A's rectangle AR, thus catches the wheel
event first. But in terms of UX we still want A to scroll, not B.

The solution then is to reverse the event processing order and
post-event callbacks are the way to do that. This comes with the
consequence that the event_info needs to remain valid until the
post-event is called, and stay the same (so that the on-hold flag
set by A can be read by B).

Recursive events (by explicit feed or modifying the canvas so
that mouse,in or mouse,out are triggered) mess with this logic,
and trigger the post-events too early (event is not fully
processed) or too late (event_info is not valid anymore... and
crash!).

Thanks @raster for explaining the goal of post-event callbacks!

src/lib/evas/canvas/evas_callbacks.c
src/lib/evas/canvas/evas_events.c
src/lib/evas/include/evas_private.h

index da5bb48..42dd851 100644 (file)
@@ -227,12 +227,12 @@ _evas_post_event_callback_call(Evas *eo_e, Evas_Public_Data *e)
    Evas_Post_Callback *pc;
    Eina_List *l, *l_next;
    int skip = 0;
-   static int first_run = 1; // FIXME: This is a workaround to prevent this
-                             // function from being called recursively.
 
-   if (e->delete_me || (!first_run)) return;
+   if (e->delete_me || e->running_post_events) return;
+   if (!e->post_events) return;
+
    _evas_walk(e);
-   first_run = 0;
+   e->running_post_events = EINA_TRUE;
    EINA_LIST_FOREACH_SAFE(e->post_events, l, l_next, pc)
      {
         e->post_events = eina_list_remove_list(e->post_events, l);
@@ -242,7 +242,7 @@ _evas_post_event_callback_call(Evas *eo_e, Evas_Public_Data *e)
           }
         EVAS_MEMPOOL_FREE(_mp_pc, pc);
      }
-   first_run = 1;
+   e->running_post_events = EINA_FALSE;
    _evas_unwalk(e);
 }
 
index ff20292..69a1586 100644 (file)
@@ -30,6 +30,17 @@ static void
 _canvas_event_feed_mouse_move_legacy(Evas *eo_e, Evas_Public_Data *e, int x, int y,
                                      unsigned int timestamp, const void *data);
 
+static inline Eina_Bool
+_evas_event_feed_allow(Evas_Public_Data *e)
+{
+   if (EINA_LIKELY(!e->running_post_events)) return EINA_TRUE;
+   ERR("Can not feed input events while running post-event callbacks!");
+   return EINA_FALSE;
+}
+
+#define EVAS_EVENT_FEED_SAFETY_CHECK(evas, ...) do { \
+   if (!_evas_event_feed_allow(evas)) return __VA_ARGS__; } while (0)
+
 static void
 _evas_event_havemap_adjust_f(Evas_Object *eo_obj EINA_UNUSED, Evas_Object_Protected_Data *obj, Eina_Vector2 *point, Eina_Bool mouse_grabbed)
 {
@@ -1390,6 +1401,7 @@ _canvas_event_feed_mouse_down_internal(Evas_Public_Data *e, Efl_Input_Pointer_Da
          _efl_input_value_mask(EFL_INPUT_VALUE_BUTTON);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -1509,13 +1521,13 @@ _post_up_handle(Evas_Public_Data *e, Efl_Input_Pointer *parent_ev,
    Evas_Object_Pointer_Data *obj_pdata;
    int event_id;
 
-   event_id = _evas_object_event_new();
-
    /* Duplicating UP event */
    evt = efl_input_dup(parent_ev);
    ev = efl_data_scope_get(evt, EFL_INPUT_POINTER_CLASS);
    if (!ev) return 0;
 
+   event_id = _evas_object_event_new();
+
    /* Actually we want an OUT */
    ev->action = EFL_POINTER_ACTION_OUT;
 
@@ -1633,6 +1645,7 @@ _canvas_event_feed_mouse_up_internal(Evas_Public_Data *e, Efl_Input_Pointer_Data
          _efl_input_value_mask(EFL_INPUT_VALUE_BUTTON);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -1736,6 +1749,7 @@ _canvas_event_feed_mouse_updown(Eo *eo_e, int b, Evas_Button_Flags flags,
 
    e = efl_data_scope_get(eo_e, EVAS_CANVAS_CLASS);
    if (!e) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    evt = efl_input_instance_get(EFL_INPUT_POINTER_CLASS, eo_e, (void **) &ev);
    if (!ev) return;
@@ -1803,6 +1817,7 @@ _canvas_event_feed_mouse_cancel_internal(Evas_Public_Data *e, Efl_Input_Pointer_
 
    if (!e || !ev) return;
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -1851,6 +1866,7 @@ evas_event_feed_mouse_cancel(Eo *eo_e, unsigned int timestamp, const void *data)
 
    evt = efl_input_instance_get(EFL_INPUT_POINTER_CLASS, eo_e, (void **) &ev);
    if (!ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    ev->timestamp = timestamp;
    ev->data = (void *) data;
@@ -1880,6 +1896,7 @@ _canvas_event_feed_mouse_wheel_internal(Eo *eo_e, Efl_Input_Pointer_Data *pe)
          _efl_input_value_mask(EFL_INPUT_VALUE_WHEEL_DIRECTION);
 
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, pe->device);
    if (!pdata) return;
@@ -1981,6 +1998,7 @@ _canvas_event_feed_mouse_move_internal(Evas_Public_Data *e, Efl_Input_Pointer_Da
 
    if (!e || !ev) return;
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -2257,7 +2275,6 @@ nogrep:
 
         // NOTE: was foreach + append without free (smelled bad)
         newin = eina_list_merge(newin, ins);
-
         EINA_LIST_FOREACH(lst, l, eo_obj)
           {
              obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
@@ -2412,6 +2429,7 @@ _canvas_event_feed_mouse_in_internal(Evas *eo_e, Efl_Input_Pointer_Data *ev)
          _efl_input_value_mask(EFL_INPUT_VALUE_TOOL);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -2492,6 +2510,7 @@ _canvas_event_feed_mouse_out_internal(Evas *eo_e, Efl_Input_Pointer_Data *ev)
          _efl_input_value_mask(EFL_INPUT_VALUE_TOOL);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
    pdata->inside = 0;
@@ -2608,6 +2627,7 @@ _canvas_event_feed_multi_down_internal(Evas_Public_Data *e, Efl_Input_Pointer_Da
          _efl_input_value_mask(EFL_INPUT_VALUE_BUTTON);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -2699,6 +2719,7 @@ _canvas_event_feed_multi_up_internal(Evas_Public_Data *e, Efl_Input_Pointer_Data
          _efl_input_value_mask(EFL_INPUT_VALUE_TOOL);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -2776,6 +2797,7 @@ _canvas_event_feed_multi_internal(Evas *eo_e, Evas_Public_Data *e,
 
    evt = efl_input_instance_get(EFL_INPUT_POINTER_CLASS, eo_e, (void **) &ev);
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    if (EINA_DBL_EQ(fx, 0.0)) fx = x;
    if (EINA_DBL_EQ(fy, 0.0)) fy = y;
@@ -2874,6 +2896,7 @@ _canvas_event_feed_multi_move_internal(Evas_Public_Data *e, Efl_Input_Pointer_Da
          _efl_input_value_mask(EFL_INPUT_VALUE_TOOL);
 
    if (!e || !ev) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -3069,6 +3092,7 @@ _canvas_event_feed_key_down_internal(Evas_Public_Data *e, Efl_Input_Key_Data *ev
 
    if (!e || !ev) return;
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
    e->last_timestamp = ev->timestamp;
    _evas_walk(e);
 
@@ -3155,6 +3179,7 @@ _canvas_event_feed_key_up_internal(Evas_Public_Data *e, Efl_Input_Key_Data *ev)
 
    if (!e || !ev) return;
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
    e->last_timestamp = ev->timestamp;
    _evas_walk(e);
 
@@ -3314,6 +3339,7 @@ evas_event_feed_hold(Eo *eo_e, int hold, unsigned int timestamp, const void *dat
    Evas_Pointer_Data *pdata;
 
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
    e->last_timestamp = timestamp;
 
    event_id = _evas_object_event_new();
@@ -3368,6 +3394,7 @@ _canvas_event_feed_axis_update_internal(Evas_Public_Data *e, Efl_Input_Pointer_D
 
    if (!e || !ev) return;
    if (e->is_frozen) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
 
    pdata = _evas_pointer_data_by_device_get(e, ev->device);
    if (!pdata) return;
@@ -3413,6 +3440,9 @@ evas_event_feed_axis_update(Evas *eo_e, unsigned int timestamp, int device, int
    double x = 0, y = 0;
    int n;
 
+   if (!e) return;
+   EVAS_EVENT_FEED_SAFETY_CHECK(e);
+
    evt = efl_input_instance_get(EFL_INPUT_POINTER_CLASS, eo_e, (void **) &ev);
    if (!ev) return;
 
index ff28339..2495dca 100644 (file)
@@ -949,6 +949,7 @@ struct _Evas_Public_Data
    Eina_Bool      rendering : 1;
    Eina_Bool      render2 : 1;
    Eina_Bool      common_init : 1;
+   Eina_Bool      running_post_events : 1;
 };
 
 struct _Evas_Layer