910e62b5创建于 1月15日历史提交
// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/common/input/actions_parser.h"

#include <utility>

#include "base/containers/contains.h"
#include "base/format_macros.h"
#include "base/strings/string_split.h"
#include "base/strings/stringprintf.h"
#include "base/values.h"
#include "components/viz/common/frame_sinks/begin_frame_args.h"
#include "content/common/input/input_injector.mojom.h"
#include "ui/events/types/scroll_types.h"

using Button = content::SyntheticPointerActionParams::Button;
using PointerActionType =
    content::SyntheticPointerActionParams::PointerActionType;

namespace content {

namespace {

PointerActionType ToSyntheticPointerActionType(const std::string& action_type) {
  if (action_type == "pointerDown")
    return PointerActionType::PRESS;
  if (action_type == "pointerMove")
    return PointerActionType::MOVE;
  if (action_type == "pointerUp")
    return PointerActionType::RELEASE;
  if (action_type == "pointerLeave")
    return PointerActionType::LEAVE;
  if (action_type == "pause")
    return PointerActionType::IDLE;
  return PointerActionType::NOT_INITIALIZED;
}

content::mojom::GestureSourceType ToSyntheticGestureSourceType(
    const std::string& pointer_type) {
  if (pointer_type == "touch")
    return content::mojom::GestureSourceType::kTouchInput;
  else if (pointer_type == "mouse")
    return content::mojom::GestureSourceType::kMouseInput;
  else if (pointer_type == "pen")
    return content::mojom::GestureSourceType::kPenInput;
  return content::mojom::GestureSourceType::kDefaultInput;
}

Button ToSyntheticMouseButton(int button) {
  if (button == 0)
    return Button::LEFT;
  if (button == 1)
    return Button::MIDDLE;
  if (button == 2)
    return Button::RIGHT;
  if (button == 3)
    return Button::BACK;
  if (button == 4)
    return Button::FORWARD;
  NOTREACHED() << "Unexpected button";
}

int ToKeyModifiers(const std::string& key) {
  if (key == "Alt")
    return blink::WebInputEvent::kAltKey;
  if (key == "Control")
    return blink::WebInputEvent::kControlKey;
  if (key == "Meta")
    return blink::WebInputEvent::kMetaKey;
  if (key == "Shift")
    return blink::WebInputEvent::kShiftKey;
  if (key == "CapsLock")
    return blink::WebInputEvent::kCapsLockOn;
  if (key == "NumLock")
    return blink::WebInputEvent::kNumLockOn;
  if (key == "AltGraph")
    return blink::WebInputEvent::kAltGrKey;
  return 0;
}

}  // namespace

ActionsParser::ActionsParser(base::Value action_sequence_list)
    : action_sequence_list_(std::move(action_sequence_list)) {
  // We have two different JSON formats from testdriver Action API or
  // gpuBenchmarking.pointerActionSequence. Below we are deciding where the
  // action sequence list comes from.
  if (action_sequence_list_.is_list() &&
      action_sequence_list_.GetList().size() > 0 &&
      action_sequence_list_.GetList()[0].is_dict()) {
    use_testdriver_api_ = ActionsDictionaryUsesTestDriverApi(
        action_sequence_list_.GetList()[0].GetDict());
  }
}

ActionsParser::~ActionsParser() {}

bool ActionsParser::Parse() {
  if (!action_sequence_list_.is_list() ||
      action_sequence_list_.GetList().size() == 0) {
    error_message_ =
        std::string("provided action sequence list is not a list or is empty");
    return false;
  }

  for (const auto& action_sequence : action_sequence_list_.GetList()) {
    if (!action_sequence.is_dict()) {
      error_message_ =
          std::string("Expected ActionSequence is not a dictionary");
      return false;
    }

    if (use_testdriver_api_) {
      if (!ParseTestDriverActionSequence(action_sequence.GetDict())) {
        return false;
      }
    } else {
      if (!ParseGpuBenchmarkingActionSequence(action_sequence.GetDict())) {
        return false;
      }
    }
  }

  if (source_type_ == "wheel")
    return true;

  gesture_params_ = std::make_unique<SyntheticPointerActionListParams>();
  SyntheticPointerActionListParams* pointer_actions =
      static_cast<SyntheticPointerActionListParams*>(gesture_params_.get());
  pointer_actions->gesture_source_type =
      ToSyntheticGestureSourceType(pointer_type_);
  // Group a list of actions from all pointers into a
  // SyntheticPointerActionListParams object, which is a list of actions, which
  // will be dispatched together.
  for (size_t index = 0; index < longest_action_sequence_; ++index) {
    SyntheticPointerActionListParams::ParamList param_list;
    size_t longest_pause_frame = 0;
    for (const auto& pointer_action_list : pointer_actions_lists_) {
      if (index < pointer_action_list.size()) {
        param_list.push_back(pointer_action_list[index]);
        if (pointer_action_list[index].pointer_action_type() ==
            PointerActionType::IDLE) {
          size_t num_pause_frame = static_cast<size_t>(std::ceil(
              pointer_action_list[index].duration().InMilliseconds() /
              viz::BeginFrameArgs::DefaultInterval().InMilliseconds()));
          longest_pause_frame = std::max(longest_pause_frame, num_pause_frame);
        }
      }
    }
    pointer_actions->PushPointerActionParamsList(param_list);

    for (size_t pause_index = 1; pause_index < longest_pause_frame;
         ++pause_index) {
      SyntheticPointerActionListParams::ParamList pause_param_list;
      SyntheticPointerActionParams pause_action_param(PointerActionType::IDLE);
      for (size_t i = 0; i < param_list.size(); ++i) {
        pause_param_list.push_back(pause_action_param);
      }
      pointer_actions->PushPointerActionParamsList(pause_param_list);
    }
  }
  return true;
}

bool ActionsParser::ActionsDictionaryUsesTestDriverApi(
    const base::Value::Dict& action_sequence) {
  // If the JSON format of each action_sequence has "type" element, it is from
  // the new Action API, otherwise it is from
  // gpuBenchmarking.pointerActionSequence API. We have to keep both formats
  // for now, but later on once we switch to the new Action API in all tests,
  // we will remove the old format.
  if (action_sequence.contains("type")) {
    return true;
  }
  return false;
}

bool ActionsParser::ParseGpuBenchmarkingActionSequence(
    const base::Value::Dict& action_sequence) {
  // The GpuBenchmarking format is implicitly for pointers only and for
  // historic reasons, the "source" key refers to what TestDriver calls the
  // pointer_type_.
  source_type_ = "pointer";
  if (use_testdriver_api_ !=
      ActionsDictionaryUsesTestDriverApi(action_sequence)) {
    error_message_ = std::string(
        "all action sequences must be of the same gpuBenchmarking format");
    return false;
  }

  const std::string* pointer_type = action_sequence.FindString("source");
  if (!pointer_type) {
    error_message_ = std::string("source type is not defined or not a string");
    return false;
  }

  if (*pointer_type != "touch" && *pointer_type != "mouse" &&
      *pointer_type != "pen") {
    error_message_ = base::StringPrintf(
        "source type %s is an unsupported input type", (*pointer_type).c_str());
    return false;
  }

  if (pointer_type_.empty()) {
    pointer_type_ = *pointer_type;
  } else if (pointer_type_ != *pointer_type) {
    error_message_ =
        std::string("currently multiple input types are not not supported");
    return false;
  }

  if (*pointer_type != "touch" && input_source_count_ > 0) {
    error_message_ = std::string(
        "for source type of mouse and pen, we only support one device "
        "in one sequence");
    return false;
  }

  const base::Value::List* actions = action_sequence.FindList("actions");
  if (!actions) {
    error_message_ = base::StringPrintf(
        "action_sequence[%zu].actions is not defined or not a list",
        action_index_);
    return false;
  } else if (actions->size() == 0) {
    error_message_ = base::StringPrintf(
        "action_sequence[%zu].actions is an empty list", action_index_);
    return false;
  }

  if (!ParseActionItemList(*actions, "pointer"))
    return false;

  input_source_count_++;
  return true;
}

bool ActionsParser::ParseTestDriverActionSequence(
    const base::Value::Dict& action_sequence) {
  if (use_testdriver_api_ !=
      ActionsDictionaryUsesTestDriverApi(action_sequence)) {
    error_message_ = std::string(
        "all action sequences must be of the same TestDriver format");
    return false;
  }

  const std::string* source_type = action_sequence.FindString("type");
  if (!source_type) {
    error_message_ =
        std::string("input source type is not defined or not a string");
    return false;
  }

  if (*source_type != "none") {
    if (source_type_.empty()) {
      source_type_ = *source_type;
    } else if (source_type_ != *source_type) {
      error_message_ = std::string(
          "currently multiple input source types are not supported");
      return false;
    }
  }

  if (*source_type == "pointer") {
    if (!ParsePointerParameters(action_sequence))
      return false;
  } else if (*source_type == "key") {
    error_message_ =
        std::string("we do not support action sequence type of key");
    return false;
  } else if (*source_type == "wheel") {
    // do nothing
  } else if (*source_type == "none") {
    // do nothing
  } else {
    error_message_ = base::StringPrintf("the input source type %s is invalid",
                                        (*source_type).c_str());
    return false;
  }

  const base::Value::List* actions = action_sequence.FindList("actions");
  if (!actions) {
    error_message_ = base::StringPrintf(
        "action_sequence[%zu].actions is not defined or not a list",
        action_index_);
    return false;
  } else if (actions->size() == 0) {
    error_message_ = base::StringPrintf(
        "action_sequence[%zu].actions is an empty list", action_index_);
    return false;
  } else if (*source_type == "wheel" && actions->size() > 1) {
    error_message_ = base::StringPrintf(
        "action_sequence[%zu].actions should only have one action for the "
        "wheel input source",
        action_index_);
    return false;
  }

  if (!ParseActionItemList(*actions, *source_type))
    return false;

  if (*source_type != "none")
    input_source_count_++;

  return true;
}

bool ActionsParser::ParsePointerParameters(
    const base::Value::Dict& action_sequence) {
  const base::Value* parameters = action_sequence.Find("parameters");
  // The default pointer type is mouse.
  std::string pointer_type = "mouse";
  if (parameters) {
    if (!parameters->is_dict()) {
      error_message_ =
          std::string("action sequence parameters is not a dictionary");
      return false;
    }

    const std::string* pointer_type_value =
        parameters->GetDict().FindString("pointerType");
    if (!pointer_type_value) {
      error_message_ = std::string(
          "action sequence pointer type is not defined or not a string");
      return false;
    }

    if (*pointer_type_value != "touch" && *pointer_type_value != "mouse" &&
        *pointer_type_value != "pen") {
      error_message_ = base::StringPrintf(
          "action sequence pointer type %s is an unsupported input type",
          (*pointer_type_value).c_str());
      return false;
    }
    pointer_type = *pointer_type_value;
  }

  if (pointer_type_.empty()) {
    pointer_type_ = pointer_type;
  } else if (pointer_type_ != pointer_type) {
    error_message_ = std::string(
        "currently multiple action sequence pointer type are not "
        "supported");
    return false;
  }

  if (pointer_type != "touch" && input_source_count_ > 0) {
    error_message_ = std::string(
        "for input type of mouse and pen, we only support one device");
    return false;
  }

  // TODO(lanwei): according to the Webdriver spec, "Let id be the result of
  // getting the property id from action sequence.", we should move "id" from
  // parameters dictionary to action sequence.
  const std::string* pointer_name = action_sequence.FindString("id");
  if (!pointer_name) {
    error_message_ = std::string("pointer name is not defined or not a string");
    return false;
  }

  if (base::Contains(pointer_name_set_, *pointer_name)) {
    error_message_ = std::string("pointer name already exists");
    return false;
  }

  pointer_name_set_.insert(*pointer_name);
  return true;
}

bool ActionsParser::ParseActionItemList(const base::Value::List& actions,
                                        std::string source_type) {
  DCHECK(source_type == "none" || source_type == source_type_);
  SyntheticPointerActionListParams::ParamList param_list;
  for (const auto& action : actions) {
    if (!action.is_dict()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions is not defined or not a dictionary",
          action_index_);
      return false;
    } else if (!ParseAction(action.GetDict(), param_list, source_type)) {
      return false;
    }
  }

  if (param_list.size() > longest_action_sequence_)
    longest_action_sequence_ = param_list.size();

  pointer_actions_lists_.push_back(param_list);
  return true;
}

bool ActionsParser::ParseAction(
    const base::Value::Dict& action,
    SyntheticPointerActionListParams::ParamList& param_list,
    std::string source_type) {
  std::string subtype;
  if (use_testdriver_api_) {
    const std::string* type_value = action.FindString("type");
    if (!type_value) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.type is not defined or not a string",
          action_index_);
      return false;
    }
    subtype = *type_value;
  } else {
    const std::string* name_value = action.FindString("name");
    if (!name_value) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.name is not defined or not a string",
          action_index_);
      return false;
    }
    subtype = *name_value;
  }

  if (source_type == "wheel") {
    return ParseWheelAction(action, subtype);
  } else if (source_type == "pointer") {
    return ParsePointerAction(action, subtype, param_list);
  } else if (source_type == "none") {
    return ParseNullAction(action, subtype, param_list);
  } else {
    NOTREACHED();
  }
}

bool ActionsParser::ParseWheelAction(const base::Value::Dict& action,
                                     std::string subtype) {
  if (subtype == "pause") {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.type of pause is not supported now",
        action_index_);
    return false;
  } else if (subtype != "scroll") {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.type is not scroll or pause when source type is "
        "wheel",
        action_index_);
    return false;
  }

  double position_x = 0;
  double position_y = 0;
  if (!GetPosition(action, position_x, position_y))
    return false;

  int delta_x = 0;
  int delta_y = 0;
  if (!GetScrollDelta(action, delta_x, delta_y))
    return false;

  gesture_params_ = std::make_unique<SyntheticSmoothScrollGestureParams>();
  SyntheticSmoothScrollGestureParams* scroll =
      static_cast<SyntheticSmoothScrollGestureParams*>(gesture_params_.get());
  scroll->gesture_source_type = content::mojom::GestureSourceType::kMouseInput;
  scroll->speed_in_pixels_s = 8000;
  scroll->prevent_fling = true;
  scroll->granularity = ui::ScrollGranularity::kScrollByPrecisePixel;
  scroll->anchor.SetPoint(position_x, position_y);
  scroll->fling_velocity_x = 0;
  scroll->fling_velocity_y = 0;
  scroll->distances.push_back(-gfx::Vector2dF(delta_x, delta_y));
  scroll->modifiers = 0;
  return true;
}

bool ActionsParser::ParsePointerAction(
    const base::Value::Dict& action,
    std::string subtype,
    SyntheticPointerActionListParams::ParamList& param_list) {
  double position_x = 0;
  double position_y = 0;
  if ((subtype == "pointerDown" || subtype == "pointerMove") &&
      !GetPosition(action, position_x, position_y)) {
    return false;
  }

  PointerActionType pointer_action_type = PointerActionType::NOT_INITIALIZED;
  pointer_action_type = ToSyntheticPointerActionType(subtype);
  if (pointer_action_type == PointerActionType::NOT_INITIALIZED) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.name is an unsupported action name",
        action_index_);
    return false;
  }

  Button button = pointer_action_type == PointerActionType::MOVE
                      ? Button::NO_BUTTON
                      : Button::LEFT;
  const base::Value* button_id_value = action.Find("button");
  if (button_id_value) {
    if (!button_id_value->is_int()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.button is not an integer", action_index_);
      return false;
    }
    int button_id = button_id_value->GetInt();
    if (button_id < 0 || button_id > 4) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.button is an unsupported button",
          action_index_);
      return false;
    }
    button = ToSyntheticMouseButton(button_id);
  }

  std::string keys;
  const base::Value* keys_value = action.Find("keys");
  if (keys_value) {
    if (!keys_value->is_string()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.key is not a string", action_index_);
      return false;
    }
    keys = keys_value->GetString();
  }

  int key_modifiers = 0;
  std::vector<std::string> key_list =
      base::SplitString(keys, ",", base::TRIM_WHITESPACE, base::SPLIT_WANT_ALL);
  for (std::string& key : key_list) {
    int key_modifier = ToKeyModifiers(key);
    if (key_modifier == 0) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.key is not a valid key", action_index_);
      return false;
    }
    key_modifiers |= key_modifier;
  }

  const std::optional<double> width_optional = action.FindDouble("width");
  double width = width_optional.value_or(40);
  if (width < 0) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.width should not be negative", action_index_);
    return false;
  }

  const std::optional<double> height_optional = action.FindDouble("height");
  double height = height_optional.value_or(40);
  if (height < 0) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.height should not be negative", action_index_);
    return false;
  }

  const std::optional<double> pressure_optional = action.FindDouble("pressure");
  double pressure = pressure_optional.value_or(0.5);
  if (pressure < 0 || pressure > 1) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.pressure must be a non-negative number in the "
        "range of [0,1]",
        action_index_);
    return false;
  }

  const std::optional<double> tangential_pressure_optional =
      action.FindDouble("tangentialPressure");
  double tangential_pressure = tangential_pressure_optional.value_or(0);
  if (tangential_pressure < -1 || tangential_pressure > 1) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.tangentialPressure must be a non-negative "
        "number in the range of [-1,1]",
        action_index_);
    return false;
  }

  int tilt_x = 0;
  const base::Value* tilt_x_value = action.Find("tiltX");
  if (tilt_x_value) {
    if (!tilt_x_value->is_int()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.tiltX is not an integer", action_index_);
      return false;
    }
    tilt_x = tilt_x_value->GetInt();
    if (tilt_x < -90 || tilt_x > 90) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.tiltX must be an integer in the range of "
          "[-90,90]",
          action_index_);
      return false;
    }
  }

  int tilt_y = 0;
  const base::Value* tilt_y_value = action.Find("tiltY");
  if (tilt_y_value) {
    if (!tilt_y_value->is_int()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.tiltY is not an integer", action_index_);
      return false;
    }
    tilt_y = tilt_y_value->GetInt();
    if (tilt_y < -90 || tilt_y > 90) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.tiltY must be an integer in the range of "
          "[-90,90]",
          action_index_);
      return false;
    }
  }

  int twist = 0;
  const base::Value* twist_value = action.Find("twist");
  if (twist_value) {
    if (!twist_value->is_int()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.twist is not an integer", action_index_);
      return false;
    }
    twist = twist_value->GetInt();
    if (twist < 0 || twist > 359) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.twist must be an integer in the range of "
          "[0,359]",
          action_index_);
      return false;
    }
  }

  int duration = viz::BeginFrameArgs::DefaultInterval().InMilliseconds();
  if (pointer_action_type == PointerActionType::IDLE &&
      !GetPauseDuration(action, duration)) {
    return false;
  }

  SyntheticPointerActionParams action_param(pointer_action_type);
  action_param.set_pointer_id(input_source_count_);
  switch (pointer_action_type) {
    case PointerActionType::PRESS:
      action_param.set_position(gfx::PointF(position_x, position_y));
      action_param.set_button(button);
      action_param.set_key_modifiers(key_modifiers);
      action_param.set_width(width);
      action_param.set_height(height);
      action_param.set_force(pressure);
      action_param.set_tangential_pressure(tangential_pressure);
      action_param.set_tilt_x(tilt_x);
      action_param.set_tilt_y(tilt_y);
      action_param.set_rotation_angle(twist);
      break;
    case PointerActionType::MOVE:
      action_param.set_position(gfx::PointF(position_x, position_y));
      action_param.set_key_modifiers(key_modifiers);
      action_param.set_width(width);
      action_param.set_height(height);
      action_param.set_force(pressure);
      action_param.set_tangential_pressure(tangential_pressure);
      action_param.set_tilt_x(tilt_x);
      action_param.set_tilt_y(tilt_y);
      action_param.set_rotation_angle(twist);
      action_param.set_button(button);
      break;
    case PointerActionType::RELEASE:
      action_param.set_button(button);
      action_param.set_key_modifiers(key_modifiers);
      break;
    case PointerActionType::IDLE:
      action_param.set_duration(base::Milliseconds(duration));
      break;
    case PointerActionType::CANCEL:
    case PointerActionType::LEAVE:
    case PointerActionType::NOT_INITIALIZED:
      break;
  }
  param_list.push_back(action_param);
  return true;
}

bool ActionsParser::ParseNullAction(
    const base::Value::Dict& action,
    std::string subtype,
    SyntheticPointerActionListParams::ParamList& param_list) {
  PointerActionType pointer_action_type = PointerActionType::NOT_INITIALIZED;
  pointer_action_type = ToSyntheticPointerActionType(subtype);
  if (pointer_action_type != PointerActionType::IDLE) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.name should only be pause", action_index_);
    return false;
  }

  int duration = viz::BeginFrameArgs::DefaultInterval().InMilliseconds();
  if (!GetPauseDuration(action, duration))
    return false;

  SyntheticPointerActionParams action_param(pointer_action_type);
  action_param.set_pointer_id(0);
  action_param.set_duration(base::Milliseconds(duration));
  param_list.push_back(action_param);
  return true;
}

bool ActionsParser::GetPosition(const base::Value::Dict& action,
                                double& position_x,
                                double& position_y) {
  const std::optional<double> position_x_optional = action.FindDouble("x");
  const std::optional<double> position_y_optional = action.FindDouble("y");
  // TODO(lanwei): we should clarify the case when x or y is undefined in the
  // WebDriver spec.
  // https://www.w3.org/TR/webdriver/#dfn-process-a-pointer-move-action.
  if (!position_x_optional) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.x is not defined or not a number", action_index_);
    return false;
  }
  position_x = position_x_optional.value();

  if (!position_y_optional) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.y is not defined or not a number", action_index_);
    return false;
  }
  position_y = position_y_optional.value();
  return true;
}

bool ActionsParser::GetScrollDelta(const base::Value::Dict& action,
                                   int& delta_x,
                                   int& delta_y) {
  const std::optional<int> delta_x_optional = action.FindInt("deltaX");
  const std::optional<int> delta_y_optional = action.FindInt("deltaY");
  if (!delta_x_optional) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.delta_x is not defined or not an integer",
        action_index_);
    return false;
  }
  delta_x = delta_x_optional.value();

  if (!delta_y_optional) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.delta_y is not defined or not an integer",
        action_index_);
    return false;
  }
  delta_y = delta_y_optional.value();
  return true;
}

bool ActionsParser::GetPauseDuration(const base::Value::Dict& action,
                                     int& duration) {
  const base::Value* duration_value = action.Find("duration");
  // TODO(lanwei): we should always have a duration value for pause action.
  if (duration_value) {
    if (!duration_value->is_double() && !duration_value->is_int()) {
      error_message_ = base::StringPrintf(
          "actions[%zu].actions.duration is not a number", action_index_);
      return false;
    }
    duration = duration_value->GetDouble();
  }

  if (duration < 0) {
    error_message_ = base::StringPrintf(
        "actions[%zu].actions.duration should not be negative", action_index_);
    return false;
  }
  return true;
}

}  // namespace content