blob: e2a8edc402ab512e6bc2a92a9088581722b15bc7 [file] [log] [blame]
// Copyright 2017 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "topaz/runtime/web_view/web_view_impl.h"
#include <hid/hid.h>
#include <hid/usages.h>
#include <lib/async/cpp/task.h>
#include <lib/async/default.h>
#include <lib/fdio/io.h>
#include <zircon/pixelformat.h>
#include <zircon/syscalls.h>
#include <zircon/types.h>
#include "topaz/runtime/web_view/schema_org_context.h"
using namespace WebCore;
TouchTracker::TouchTracker(int x, int y)
: start_x_(x), start_y_(y), last_x_(0), last_y_(0), is_drag_(false) {}
void TouchTracker::HandleEvent(const fuchsia::ui::input::PointerEvent& pointer,
const fuchsia::ui::gfx::Metrics& metrics,
WebView& web_view) {
const auto x = pointer.x * metrics.scale_x;
const auto y = pointer.y * metrics.scale_y;
const int kDragThreshhold = 50 * metrics.scale_x;
auto delta_x = last_x_ - x;
auto delta_y = last_y_ - y;
auto distance_x = abs(start_x_ - x);
auto distance_y = abs(start_y_ - y);
last_y_ = y;
last_x_ = x;
if (distance_x > kDragThreshhold || distance_y > kDragThreshhold) {
is_drag_ = true;
}
if (is_drag_) {
switch (pointer.phase) {
case fuchsia::ui::input::PointerEventPhase::MOVE:
web_view.scrollPixels(delta_x, delta_y);
break;
case fuchsia::ui::input::PointerEventPhase::UP:
web_view.scrollPixels(delta_x, delta_y);
break;
default:
break;
}
} else {
switch (pointer.phase) {
case fuchsia::ui::input::PointerEventPhase::UP:
web_view.handleMouseEvent(start_x_, start_y_, WebView::kMouseDown);
web_view.handleMouseEvent(start_x_, start_y_, WebView::kMouseUp);
break;
default:
break;
}
}
}
WebViewImpl::WebViewImpl(scenic::ViewContext view_context,
fuchsia::ui::input::ImeServicePtr ime_service,
const std::string& url)
: V1BaseView(std::move(view_context), "WebView"),
ime_service_(std::move(ime_service)),
ime_client_binding_(this),
weak_factory_(this),
url_(url),
#ifdef EXPERIMENTAL_WEB_ENTITY_EXTRACTION
schema_org_(web_view_),
#endif
image_cycler_(session()) {
FXL_LOG(INFO) << "WebViewImpl constructor";
web_view_.setup_once();
std::function<void(bool)> focusDelegate = [=](bool focused) {
this->HandleWebRequestsFocusEvent(focused);
};
web_view_.setInputFocusDelegate(focusDelegate);
SetNeedSquareMetrics(true);
parent_node().AddChild(image_cycler_);
// Expose |WebView| interface to caller.
outgoing_services().AddService<fuchsia::webview::WebView>(
[this](fidl::InterfaceRequest<fuchsia::webview::WebView> request) {
FXL_LOG(INFO) << "web view service request";
web_view_interface_bindings_.AddBinding(this, std::move(request));
});
async::PostTask(async_get_default_dispatcher(),
([weak = weak_factory_.GetWeakPtr()]() {
if (weak)
weak->CallIdle();
}));
}
void WebViewImpl::HandleWebRequestsFocusEvent(bool focused) {
FXL_LOG(INFO) << "WebView: web requests input focus:"
<< (focused ? "focused" : "unfocused");
web_requests_input_ = focused;
UpdateInputConnection();
}
void WebViewImpl::UpdateInputConnection() {
if (web_requests_input_) {
ime_service_->ShowKeyboard();
fuchsia::ui::input::InputMethodEditorClientPtr client_ptr;
ime_client_binding_.Bind(client_ptr.NewRequest());
auto state = fuchsia::ui::input::TextInputState{};
state.text = "";
ime_service_->GetInputMethodEditor(
fuchsia::ui::input::KeyboardType::TEXT,
fuchsia::ui::input::InputMethodAction::SEND, std::move(state),
std::move(client_ptr), ime_.NewRequest());
} else if (ime_client_binding_.is_bound()) {
ime_service_->HideKeyboard();
ime_client_binding_.Unbind();
}
}
// |fuchsia::ui::input::InputMethodEditorClient|
void WebViewImpl::DidUpdateState(fuchsia::ui::input::TextInputState state,
fuchsia::ui::input::InputEventPtr event) {
if (event != nullptr && event->is_keyboard() && has_scenic_focus_) {
HandleKeyboardEvent(*event);
}
}
// |fuchsia::ui::input::InputMethodEditorClient|
void WebViewImpl::OnAction(fuchsia::ui::input::InputMethodAction action) {
if (fuchsia::ui::input::InputMethodAction::SEND == action) {
// treat send action as return, simulate press and release.
fuchsia::ui::input::InputEvent event;
fuchsia::ui::input::KeyboardEvent keyboard;
keyboard.phase = fuchsia::ui::input::KeyboardEventPhase::PRESSED;
keyboard.hid_usage = HID_USAGE_KEY_ENTER;
event.set_keyboard(keyboard);
HandleKeyboardEvent(event);
keyboard.phase = fuchsia::ui::input::KeyboardEventPhase::RELEASED;
HandleKeyboardEvent(event);
}
}
// |WebView|:
void WebViewImpl::SetUrl(fidl::StringPtr url) {
url_ = url;
// Reset url_set_ so that the next OnDraw() knows to call
// web_view_.setURL()
url_set_ = false;
InvalidateScene();
}
// |WebView|:
void WebViewImpl::ClearCookies() { web_view_.deleteAllCookies(); }
void WebViewImpl::SetWebRequestDelegate(
::fidl::InterfaceHandle<fuchsia::webview::WebRequestDelegate> delegate) {
webRequestDelegate_ = delegate.Bind();
}
bool WebViewImpl::HandleKeyboardEvent(
const fuchsia::ui::input::InputEvent& event) {
bool handled = true;
const fuchsia::ui::input::KeyboardEvent& keyboard = event.keyboard();
bool pressed =
keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::PRESSED;
bool repeating =
keyboard.phase == fuchsia::ui::input::KeyboardEventPhase::REPEAT;
if (pressed && keyboard.code_point == 'c' &&
keyboard.modifiers & fuchsia::ui::input::kModifierControl) {
exit(0);
} else if (pressed && keyboard.code_point == '[' &&
keyboard.modifiers & fuchsia::ui::input::kModifierControl) {
web_view_.goBack();
} else if (pressed && keyboard.code_point == ']' &&
keyboard.modifiers & fuchsia::ui::input::kModifierControl) {
web_view_.goForward();
} else if (pressed && keyboard.code_point == 'r' &&
keyboard.modifiers & fuchsia::ui::input::kModifierControl) {
web_view_.reload();
} else {
bool handled =
web_view_.handleKeyEvent(keyboard.hid_usage, keyboard.code_point,
pressed || repeating, repeating);
if (!handled) {
if (pressed || repeating) {
if (keyboard.hid_usage == HID_USAGE_KEY_DOWN) {
} else if (keyboard.hid_usage == HID_USAGE_KEY_UP) {
web_view_.scrollUpOneLine();
} else if (keyboard.hid_usage == HID_USAGE_KEY_RIGHT) {
web_view_.scrollRightOneLine();
} else if (keyboard.hid_usage == HID_USAGE_KEY_LEFT) {
web_view_.scrollLeftOneLine();
}
}
}
}
return handled;
}
void WebViewImpl::HandleMouseEvent(
const fuchsia::ui::input::PointerEvent& pointer) {
if (pointer.buttons & fuchsia::ui::input::kMousePrimaryButton) {
switch (pointer.phase) {
case fuchsia::ui::input::PointerEventPhase::DOWN:
case fuchsia::ui::input::PointerEventPhase::MOVE:
web_view_.handleMouseEvent(
pointer.x * metrics().scale_x, pointer.y * metrics().scale_y,
pointer.phase == fuchsia::ui::input::PointerEventPhase::DOWN
? ::WebView::kMouseDown
: ::WebView::kMouseMoved);
break;
case fuchsia::ui::input::PointerEventPhase::UP:
web_view_.handleMouseEvent(pointer.x * metrics().scale_x,
pointer.y * metrics().scale_y,
::WebView::kMouseUp);
break;
default:
break;
}
}
}
void WebViewImpl::HandleTouchDown(
const fuchsia::ui::input::PointerEvent& pointer) {
const auto x = pointer.x * metrics().scale_x;
const auto y = pointer.y * metrics().scale_y;
touch_trackers_[pointer.pointer_id] = TouchTracker(x, y);
}
void WebViewImpl::HandleTouchEvent(
const fuchsia::ui::input::PointerEvent& pointer) {
auto pointer_id = pointer.pointer_id;
switch (pointer.phase) {
case fuchsia::ui::input::PointerEventPhase::DOWN:
HandleTouchDown(pointer);
break;
case fuchsia::ui::input::PointerEventPhase::MOVE:
touch_trackers_[pointer_id].HandleEvent(pointer, metrics(), web_view_);
break;
case fuchsia::ui::input::PointerEventPhase::UP:
touch_trackers_[pointer_id].HandleEvent(pointer, metrics(), web_view_);
touch_trackers_.erase(pointer_id);
break;
default:
break;
}
}
void WebViewImpl::HandleFocusEvent(
const fuchsia::ui::input::FocusEvent& focus) {
has_scenic_focus_ = focus.focused;
web_view_.setFocused(focus.focused);
UpdateInputConnection();
}
// |scenic::V1BaseView|
bool WebViewImpl::OnInputEvent(fuchsia::ui::input::InputEvent event) {
bool handled = false;
web_view_.setVisible(true);
if (event.is_pointer()) {
handled = true;
const fuchsia::ui::input::PointerEvent& pointer = event.pointer();
if (pointer.type == fuchsia::ui::input::PointerEventType::TOUCH) {
HandleTouchEvent(pointer);
} else if (pointer.type == fuchsia::ui::input::PointerEventType::MOUSE) {
HandleMouseEvent(pointer);
}
} else if (event.is_keyboard()) {
handled = true;
HandleKeyboardEvent(event);
} else if (event.is_focus()) {
handled = true;
HandleFocusEvent(event.focus());
}
InvalidateScene();
return handled;
}
// |scenic::V1BaseView|
void WebViewImpl::OnSceneInvalidated(
fuchsia::images::PresentationInfo presentation_info) {
if (!has_physical_size())
return;
// Update the image.
const scenic::HostImage* image = image_cycler_.AcquireImage(
physical_size().width, physical_size().height, physical_size().width * 4u,
fuchsia::images::PixelFormat::BGRA_8, fuchsia::images::ColorSpace::SRGB);
FXL_DCHECK(image);
// Paint the webview.
web_view_.setup(reinterpret_cast<unsigned char*>(image->image_ptr()),
ZX_PIXEL_FORMAT_ARGB_8888, physical_size().width,
physical_size().height, physical_size().width * 4u);
if (!url_set_) {
const char* urlToOpen = url_.c_str();
web_view_.setURL(urlToOpen);
url_set_ = true;
FXL_DCHECK(metrics().scale_x ==
metrics().scale_y); // we asked for square metrics
auto requestCallback = [this](std::string url) {
if (webRequestDelegate_) {
webRequestDelegate_->WillSendRequest(url);
}
return url;
};
web_view_.setWebRequestDelegate(requestCallback);
}
if (page_scale_factor_ != metrics().scale_x) {
page_scale_factor_ = metrics().scale_x;
web_view_.setPageAndTextZoomFactors(page_scale_factor_, 1.0);
}
web_view_.iterateEventLoop();
web_view_.layoutAndPaint();
image_cycler_.ReleaseAndSwapImage();
image_cycler_.SetScale(1.f / metrics().scale_x, 1.f / metrics().scale_y, 1.f);
image_cycler_.SetTranslation(logical_size().width * .5f,
logical_size().height * .5f, 0.f);
}
void WebViewImpl::CallIdle() {
web_view_.iterateEventLoop();
InvalidateScene();
async::PostTask(async_get_default_dispatcher(),
([weak = weak_factory_.GetWeakPtr()]() {
if (weak)
weak->CallIdle();
}));
}