chromium/ui/aura/test/ui_controls_ozone.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "ui/aura/test/ui_controls_ozone.h"

#include <tuple>

#include "base/functional/callback.h"
#include "base/task/single_thread_task_runner.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "ui/events/event_utils.h"
#include "ui/events/ozone/events_ozone.h"

namespace aura {
namespace test {

// static
unsigned UIControlsOzone::button_down_mask_ = 0;

UIControlsOzone::UIControlsOzone(WindowTreeHost* host) : host_(host) {}

UIControlsOzone::~UIControlsOzone() = default;

bool UIControlsOzone::SendKeyEvents(gfx::NativeWindow window,
                                    ui::KeyboardCode key,
                                    int key_event_types,
                                    int accelerator_state) {
  return SendKeyEventsNotifyWhenDone(window, key, key_event_types,
                                     base::OnceClosure(), accelerator_state,
                                     ui_controls::KeyEventType::kKeyRelease);
}

bool UIControlsOzone::SendKeyEventsNotifyWhenDone(
    gfx::NativeWindow window,
    ui::KeyboardCode key,
    int key_event_types,
    base::OnceClosure closure,
    int accelerator_state,
    ui_controls::KeyEventType wait_for) {
  CHECK(wait_for == ui_controls::KeyEventType::kKeyPress ||
        wait_for == ui_controls::KeyEventType::kKeyRelease);
  // This doesn't time out if `window` is deleted before the key release events
  // are dispatched, so it's fine to ignore `wait_for` and always wait for key
  // release events.
  WindowTreeHost* optional_host = nullptr;
  // Send the key event to the window's host, which may not match |host_|.
  // This logic should probably exist for the non-aura path as well.
  // TODO(crbug.com/40144825) Support non-aura path.
#if defined(USE_AURA)
  if (window != nullptr && window->GetHost() != nullptr &&
      window->GetHost() != host_)
    optional_host = window->GetHost();
#endif

  bool has_press = key_event_types & ui_controls::kKeyPress;
  bool has_release = key_event_types & ui_controls::kKeyRelease;

  bool has_control = accelerator_state & ui_controls::kControl;
  bool has_shift = accelerator_state & ui_controls::kShift;
  bool has_command = accelerator_state & ui_controls::kCommand;
  bool has_alt = accelerator_state & ui_controls::kAlt;

  int flags = button_down_mask_;
  int64_t display_id =
      display::Screen::GetScreen()->GetDisplayNearestWindow(window).id();

  if (has_press) {
    if (has_control) {
      flags |= ui::EF_CONTROL_DOWN;
      PostKeyEvent(ui::EventType::kKeyPressed, ui::VKEY_CONTROL, flags,
                   display_id, base::OnceClosure(), optional_host);
    }

    if (has_shift) {
      flags |= ui::EF_SHIFT_DOWN;
      PostKeyEvent(ui::EventType::kKeyPressed, ui::VKEY_SHIFT, flags,
                   display_id, base::OnceClosure(), optional_host);
    }

    if (has_alt) {
      flags |= ui::EF_ALT_DOWN;
      PostKeyEvent(ui::EventType::kKeyPressed, ui::VKEY_MENU, flags, display_id,
                   base::OnceClosure(), optional_host);
    }

    if (has_command) {
      flags |= ui::EF_COMMAND_DOWN;
      PostKeyEvent(ui::EventType::kKeyPressed, ui::VKEY_LWIN, flags, display_id,
                   base::OnceClosure(), optional_host);
    }

    PostKeyEvent(ui::EventType::kKeyPressed, key, flags, display_id,
                 has_release ? base::OnceClosure() : std::move(closure),
                 optional_host);
  }

  if (has_release) {
    PostKeyEvent(ui::EventType::kKeyReleased, key, flags, display_id,
                 (has_control || has_shift || has_alt || has_command)
                     ? base::OnceClosure()
                     : std::move(closure),
                 optional_host);

    if (has_alt) {
      flags &= ~ui::EF_ALT_DOWN;
      PostKeyEvent(
          ui::EventType::kKeyReleased, ui::VKEY_MENU, flags, display_id,
          (has_shift || has_control || has_command) ? base::OnceClosure()
                                                    : std::move(closure),
          optional_host);
    }

    if (has_shift) {
      flags &= ~ui::EF_SHIFT_DOWN;
      PostKeyEvent(ui::EventType::kKeyReleased, ui::VKEY_SHIFT, flags,
                   display_id,
                   (has_control || has_command) ? base::OnceClosure()
                                                : std::move(closure),
                   optional_host);
    }

    if (has_control) {
      flags &= ~ui::EF_CONTROL_DOWN;
      PostKeyEvent(ui::EventType::kKeyReleased, ui::VKEY_CONTROL, flags,
                   display_id,
                   has_command ? base::OnceClosure() : std::move(closure),
                   optional_host);
    }

    if (has_command) {
      flags &= ~ui::EF_COMMAND_DOWN;
      PostKeyEvent(ui::EventType::kKeyReleased, ui::VKEY_LWIN, flags,
                   display_id, std::move(closure), optional_host);
    }
  }

  return true;
}

bool UIControlsOzone::SendMouseMove(int screen_x, int screen_y) {
  return SendMouseMoveNotifyWhenDone(screen_x, screen_y, base::OnceClosure());
}

bool UIControlsOzone::SendMouseMoveNotifyWhenDone(int screen_x,
                                                  int screen_y,
                                                  base::OnceClosure closure) {
  gfx::PointF host_location(screen_x, screen_y);
  int64_t display_id = display::kInvalidDisplayId;
  if (!ScreenDIPToHostPixels(&host_location, &display_id))
    return false;
  ui::EventType event_type;

  if (button_down_mask_)
    event_type = ui::EventType::kMouseDragged;
  else
    event_type = ui::EventType::kMouseMoved;

  PostMouseEvent(event_type, host_location, button_down_mask_, 0, display_id,
                 std::move(closure));

  return true;
}

bool UIControlsOzone::SendMouseEvents(ui_controls::MouseButton type,
                                      int button_state,
                                      int accelerator_state) {
  return SendMouseEventsNotifyWhenDone(type, button_state, base::OnceClosure(),
                                       accelerator_state);
}

bool UIControlsOzone::SendMouseEventsNotifyWhenDone(
    ui_controls::MouseButton type,
    int button_state,
    base::OnceClosure closure,
    int accelerator_state) {
  gfx::PointF host_location(Env::GetInstance()->last_mouse_location());
  int64_t display_id = display::kInvalidDisplayId;
  if (!ScreenDIPToHostPixels(&host_location, &display_id))
    return false;

  int changed_button_flag = 0;

  switch (type) {
    case ui_controls::LEFT:
      changed_button_flag = ui::EF_LEFT_MOUSE_BUTTON;
      break;
    case ui_controls::MIDDLE:
      changed_button_flag = ui::EF_MIDDLE_MOUSE_BUTTON;
      break;
    case ui_controls::RIGHT:
      changed_button_flag = ui::EF_RIGHT_MOUSE_BUTTON;
      break;
    default:
      NOTREACHED_IN_MIGRATION();
      break;
  }

  // Process the accelerator key state.
  int flag = changed_button_flag;
  if (accelerator_state & ui_controls::kShift)
    flag |= ui::EF_SHIFT_DOWN;
  if (accelerator_state & ui_controls::kControl)
    flag |= ui::EF_CONTROL_DOWN;
  if (accelerator_state & ui_controls::kAlt)
    flag |= ui::EF_ALT_DOWN;
  if (accelerator_state & ui_controls::kCommand)
    flag |= ui::EF_COMMAND_DOWN;

  if (button_state & ui_controls::DOWN) {
    button_down_mask_ |= flag;
    // Pass the real closure to the last generated MouseEvent.
    PostMouseEvent(ui::EventType::kMousePressed, host_location,
                   button_down_mask_ | flag, changed_button_flag, display_id,
                   (button_state & ui_controls::UP) ? base::OnceClosure()
                                                    : std::move(closure));
  }
  if (button_state & ui_controls::UP) {
    button_down_mask_ &= ~flag;
    PostMouseEvent(ui::EventType::kMouseReleased, host_location,
                   button_down_mask_ | flag, changed_button_flag, display_id,
                   std::move(closure));
  }

  return true;
}

bool UIControlsOzone::SendMouseClick(ui_controls::MouseButton type) {
  return SendMouseEvents(type, ui_controls::UP | ui_controls::DOWN,
                         ui_controls::kNoAccelerator);
}

#if BUILDFLAG(IS_CHROMEOS)
bool UIControlsOzone::SendTouchEvents(int action, int id, int x, int y) {
  return SendTouchEventsNotifyWhenDone(action, id, x, y, base::OnceClosure());
}

bool UIControlsOzone::SendTouchEventsNotifyWhenDone(int action,
                                                    int id,
                                                    int x,
                                                    int y,
                                                    base::OnceClosure task) {
  DCHECK_NE(0, action);
  gfx::PointF host_location(x, y);
  int64_t display_id = display::kInvalidDisplayId;
  if (!ScreenDIPToHostPixels(&host_location, &display_id))
    return false;
  bool has_move = action & ui_controls::kTouchMove;
  bool has_release = action & ui_controls::kTouchRelease;
  if (action & ui_controls::kTouchPress) {
    PostTouchEvent(
        ui::EventType::kTouchPressed, host_location, id, display_id,
        (has_move || has_release) ? base::OnceClosure() : std::move(task));
  }
  if (has_move) {
    PostTouchEvent(ui::EventType::kTouchMoved, host_location, id, display_id,
                   has_release ? base::OnceClosure() : std::move(task));
  }
  if (has_release) {
    PostTouchEvent(ui::EventType::kTouchReleased, host_location, id, display_id,
                   std::move(task));
  }
  return true;
}
#endif

void UIControlsOzone::SendEventToSink(ui::Event* event,
                                      int64_t display_id,
                                      base::OnceClosure closure,
                                      WindowTreeHost* optional_host,
                                      bool post_task_after_dispatch) {
  // Post the task before processing the event. This is usually necessary in
  // case processing the event results in a nested message loop.
  if (closure && !post_task_after_dispatch) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(closure));
  }

  WindowTreeHost* host = optional_host ? optional_host : host_.get();
  ui::EventSourceTestApi event_source_test(host->GetEventSource());
  std::ignore = event_source_test.SendEventToSink(event);

  // It is sometimes necessary to post the task after processing the event.
  // This should only occur if it is known that the event does not enter any
  // nested message loops.
  if (closure && post_task_after_dispatch) {
    base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
        FROM_HERE, std::move(closure));
  }
}

void UIControlsOzone::PostKeyEvent(ui::EventType type,
                                   ui::KeyboardCode key_code,
                                   int flags,
                                   int64_t display_id,
                                   base::OnceClosure closure,
                                   WindowTreeHost* optional_host) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&UIControlsOzone::PostKeyEventTask,
                                base::Unretained(this), type, key_code, flags,
                                display_id, std::move(closure), optional_host));
}

void UIControlsOzone::PostKeyEventTask(ui::EventType type,
                                       ui::KeyboardCode key_code,
                                       int flags,
                                       int64_t display_id,
                                       base::OnceClosure closure,
                                       WindowTreeHost* optional_host) {
  // Do not rewrite injected events. See crbug.com/136465.
  flags |= ui::EF_FINAL;

  ui::KeyEvent key_event(type, key_code, flags);
  if (type == ui::EventType::kKeyPressed) {
    // Set a property as if this is a key event not consumed by IME.
    // Ozone/X11+GTK IME works so already. Ozone/wayland IME relies on this
    // flag to work properly.
    ui::SetKeyboardImeFlags(&key_event, ui::kPropertyKeyboardImeIgnoredFlag);
  }
  SendEventToSink(&key_event, display_id, std::move(closure), optional_host);
}

void UIControlsOzone::PostMouseEvent(ui::EventType type,
                                     const gfx::PointF& host_location,
                                     int flags,
                                     int changed_button_flags,
                                     int64_t display_id,
                                     base::OnceClosure closure) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE,
      base::BindOnce(&UIControlsOzone::PostMouseEventTask,
                     base::Unretained(this), type, host_location, flags,
                     changed_button_flags, display_id, std::move(closure)));
}

void UIControlsOzone::PostMouseEventTask(ui::EventType type,
                                         const gfx::PointF& host_location,
                                         int flags,
                                         int changed_button_flags,
                                         int64_t display_id,
                                         base::OnceClosure closure) {
  ui::MouseEvent mouse_event(type, host_location, host_location,
                             ui::EventTimeForNow(), flags,
                             changed_button_flags);

  // This hack is necessary to set the repeat count for clicks.
  ui::MouseEvent mouse_event2(&mouse_event);

  // For drag-ending left-mouse-button release events, the closure must be
  // posted after the event is processed to ensure zcr_ui_controls::
  // request_processed is sent after wl_data_source::dnd_finished.
  // TODO(crbug.com/41489982): Desired synchronization semantics should
  // be declared explicitly, not decided by test framework heuristics.
  bool post_task_after_dispatch =
      changed_button_flags == ui::EF_LEFT_MOUSE_BUTTON &&
      type == ui::EventType::kMouseReleased;
  SendEventToSink(&mouse_event2, display_id, std::move(closure), nullptr,
                  post_task_after_dispatch);
}

void UIControlsOzone::PostTouchEvent(ui::EventType type,
                                     const gfx::PointF& host_location,
                                     int id,
                                     int64_t display_id,
                                     base::OnceClosure closure) {
  base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
      FROM_HERE, base::BindOnce(&UIControlsOzone::PostTouchEventTask,
                                base::Unretained(this), type, host_location, id,
                                display_id, std::move(closure)));
}

void UIControlsOzone::PostTouchEventTask(ui::EventType type,
                                         const gfx::PointF& host_location,
                                         int id,
                                         int64_t display_id,
                                         base::OnceClosure closure) {
  ui::PointerDetails details(ui::EventPointerType::kTouch, id, 1.0f, 1.0f,
                             0.0f);
  ui::TouchEvent touch_event(type, host_location, host_location,
                             ui::EventTimeForNow(), details);
  SendEventToSink(&touch_event, display_id, std::move(closure));
}

bool UIControlsOzone::ScreenDIPToHostPixels(gfx::PointF* location,
                                            int64_t* display_id) {
  // The location needs to be in display's coordinate.
  display::Display display =
      display::Screen::GetScreen()->GetDisplayNearestPoint(
          gfx::ToFlooredPoint(*location));
  if (!display.is_valid()) {
    LOG(ERROR) << "Failed to find the display for " << location->ToString();
    return false;
  }
  *display_id = display.id();
  *location -= display.bounds().OffsetFromOrigin();
  location->Scale(display.device_scale_factor());
  return true;
}

ui_controls::UIControlsAura* CreateUIControlsAura(WindowTreeHost* host) {
  return new UIControlsOzone(host);
}

}  // namespace test
}  // namespace aura