// Copyright 2014 The Chromium 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 "sky/viewer/document_view.h" #include "base/bind.h" #include "base/location.h" #include "base/single_thread_task_runner.h" #include "base/strings/string_util.h" #include "base/thread_task_runner_handle.h" #include "mojo/converters/geometry/geometry_type_converters.h" #include "mojo/converters/input_events/input_events_type_converters.h" #include "mojo/public/cpp/application/connect.h" #include "mojo/public/cpp/system/data_pipe.h" #include "mojo/public/interfaces/application/shell.mojom.h" #include "mojo/services/view_manager/public/cpp/view.h" #include "mojo/services/view_manager/public/cpp/view_manager.h" #include "mojo/services/view_manager/public/interfaces/view_manager.mojom.h" #include "skia/ext/refptr.h" #include "sky/compositor/layer.h" #include "sky/compositor/layer_host.h" #include "sky/compositor/rasterizer_bitmap.h" #include "sky/compositor/rasterizer_ganesh.h" #include "sky/engine/public/platform/Platform.h" #include "sky/engine/public/platform/WebHTTPHeaderVisitor.h" #include "sky/engine/public/platform/WebInputEvent.h" #include "sky/engine/public/platform/WebScreenInfo.h" #include "sky/engine/public/web/Sky.h" #include "sky/engine/public/web/WebConsoleMessage.h" #include "sky/engine/public/web/WebDocument.h" #include "sky/engine/public/web/WebElement.h" #include "sky/engine/public/web/WebLocalFrame.h" #include "sky/engine/public/web/WebScriptSource.h" #include "sky/engine/public/web/WebSettings.h" #include "sky/engine/public/web/WebView.h" #include "sky/services/platform/url_request_types.h" #include "sky/viewer/converters/input_event_types.h" #include "sky/viewer/internals.h" #include "sky/viewer/runtime_flags.h" #include "third_party/skia/include/core/SkCanvas.h" #include "third_party/skia/include/core/SkColor.h" #include "third_party/skia/include/core/SkDevice.h" #include "ui/events/gestures/gesture_recognizer.h" #include "v8/include/v8.h" namespace sky { namespace { void ConfigureSettings(blink::WebSettings* settings) { settings->setDefaultFixedFontSize(13); settings->setDefaultFontSize(16); settings->setLoadsImagesAutomatically(true); } mojo::Target WebNavigationPolicyToNavigationTarget( blink::WebNavigationPolicy policy) { switch (policy) { case blink::WebNavigationPolicyCurrentTab: return mojo::TARGET_SOURCE_NODE; case blink::WebNavigationPolicyNewBackgroundTab: case blink::WebNavigationPolicyNewForegroundTab: case blink::WebNavigationPolicyNewWindow: case blink::WebNavigationPolicyNewPopup: return mojo::TARGET_NEW_NODE; default: return mojo::TARGET_DEFAULT; } } ui::EventType ConvertEventTypeToUIEventType(blink::WebInputEvent::Type type) { if (type == blink::WebInputEvent::PointerDown) return ui::ET_TOUCH_PRESSED; if (type == blink::WebInputEvent::PointerUp) return ui::ET_TOUCH_RELEASED; if (type == blink::WebInputEvent::PointerMove) return ui::ET_TOUCH_MOVED; DCHECK(type == blink::WebInputEvent::PointerCancel); return ui::ET_TOUCH_CANCELLED; } scoped_ptr ConvertToUITouchEvent( const blink::WebInputEvent& event, float device_pixel_ratio) { if (!blink::WebInputEvent::isPointerEventType(event.type)) return nullptr; const blink::WebPointerEvent& pointer_event = static_cast(event); return make_scoped_ptr(new ui::TouchEvent( ConvertEventTypeToUIEventType(event.type), gfx::PointF(pointer_event.x * device_pixel_ratio, pointer_event.y * device_pixel_ratio), pointer_event.pointer, base::TimeDelta::FromMillisecondsD(pointer_event.timeStampMS))); } } // namespace DocumentView::DocumentView( mojo::InterfaceRequest services, mojo::ServiceProviderPtr exported_services, mojo::URLResponsePtr response, mojo::Shell* shell) : response_(response.Pass()), exported_services_(services.Pass()), imported_services_(exported_services.Pass()), shell_(shell), web_view_(nullptr), root_(nullptr), view_manager_client_factory_(shell_, this), bitmap_rasterizer_(nullptr), weak_factory_(this) { exported_services_.AddService(&view_manager_client_factory_); InitServiceRegistry(); } DocumentView::~DocumentView() { if (web_view_) web_view_->close(); if (root_) root_->RemoveObserver(this); ui::GestureRecognizer::Get()->CleanupStateForConsumer(this); } base::WeakPtr DocumentView::GetWeakPtr() { return weak_factory_.GetWeakPtr(); } void DocumentView::OnEmbed( mojo::View* root, mojo::InterfaceRequest services_provided_to_embedder, mojo::ServiceProviderPtr services_provided_by_embedder) { root_ = root; if (services_provided_by_embedder.get()) { mojo::ConnectToService(services_provided_by_embedder.get(), &navigator_host_); if (RuntimeFlags::Get().testing()) mojo::ConnectToService(services_provided_by_embedder.get(), &test_harness_); } services_provided_to_embedder_ = services_provided_to_embedder.Pass(); services_provided_by_embedder_ = services_provided_by_embedder.Pass(); Load(response_.Pass()); UpdateRootSizeAndViewportMetrics(root_->bounds()); // TODO(abarth): We should ask the view whether it is focused instead of // assuming that we're focused. if (web_view_) web_view_->setFocus(true); root_->AddObserver(this); } void DocumentView::OnViewManagerDisconnected(mojo::ViewManager* view_manager) { // TODO(aa): Need to figure out how shutdown works. } void DocumentView::Load(mojo::URLResponsePtr response) { GURL responseURL(response->url); if (!blink::WebView::shouldUseWebView(responseURL)) { sky_view_ = blink::SkyView::Create(this); initializeLayerTreeView(); sky_view_->Load(responseURL, response.Pass()); return; } if (!RuntimeFlags::Get().testing()) LOG(WARNING) << ".sky support is deprecated, please use .dart for main()"; web_view_ = blink::WebView::create(this); ConfigureSettings(web_view_->settings()); web_view_->setMainFrame(blink::WebLocalFrame::create(this)); web_view_->mainFrame()->loadFromDataPipeWithURL( response->body.Pass(), responseURL); } void DocumentView::initializeLayerTreeView() { layer_host_.reset(new LayerHost(this)); root_layer_ = make_scoped_refptr(new Layer(this)); root_layer_->set_rasterizer(CreateRasterizer()); layer_host_->SetRootLayer(root_layer_); } scoped_ptr DocumentView::CreateRasterizer() { if (!RuntimeFlags::Get().testing()) return make_scoped_ptr(new RasterizerGanesh(layer_host_.get())); // TODO(abarth): If we have more than one layer, we'll need to re-think how // we capture pixels for testing; DCHECK(!bitmap_rasterizer_); bitmap_rasterizer_ = new RasterizerBitmap(layer_host_.get()); return make_scoped_ptr(bitmap_rasterizer_); } void DocumentView::GetPixelsForTesting(std::vector* pixels) { DCHECK(RuntimeFlags::Get().testing()) << "Requires testing runtime flag"; DCHECK(root_layer_) << "The root layer owns the rasterizer"; return bitmap_rasterizer_->GetPixelsForTesting(pixels); } TestHarnessPtr DocumentView::TakeTestHarness() { return test_harness_.Pass(); } mojo::ScopedMessagePipeHandle DocumentView::TakeServicesProvidedToEmbedder() { return services_provided_to_embedder_.PassMessagePipe(); } mojo::ScopedMessagePipeHandle DocumentView::TakeServicesProvidedByEmbedder() { return services_provided_by_embedder_.PassInterface().PassHandle(); } mojo::ScopedMessagePipeHandle DocumentView::TakeServiceRegistry() { return service_registry_.PassInterface().PassHandle(); } mojo::Shell* DocumentView::GetShell() { return shell_; } void DocumentView::BeginFrame(base::TimeTicks frame_time) { if (sky_view_) { sky_view_->BeginFrame(frame_time); root_layer_->SetSize(sky_view_->display_metrics().physical_size); } if (web_view_) { double frame_time_sec = (frame_time - base::TimeTicks()).InSecondsF(); double deadline_sec = frame_time_sec; double interval_sec = 1.0/60; blink::WebBeginFrameArgs web_begin_frame_args( frame_time_sec, deadline_sec, interval_sec); web_view_->beginFrame(web_begin_frame_args); web_view_->layout(); blink::WebSize size = web_view_->size(); float device_pixel_ratio = GetDevicePixelRatio(); root_layer_->SetSize(gfx::Size(size.width * device_pixel_ratio, size.height * device_pixel_ratio)); } } void DocumentView::OnSurfaceIdAvailable(mojo::SurfaceIdPtr surface_id) { if (root_) root_->SetSurfaceId(surface_id.Pass()); } void DocumentView::PaintContents(SkCanvas* canvas, const gfx::Rect& clip) { blink::WebRect rect(clip.x(), clip.y(), clip.width(), clip.height()); if (sky_view_) { skia::RefPtr picture = sky_view_->Paint(); canvas->clear(SK_ColorBLACK); canvas->scale(GetDevicePixelRatio(), GetDevicePixelRatio()); if (picture) canvas->drawPicture(picture.get()); } if (web_view_) web_view_->paint(canvas, rect); } void DocumentView::scheduleVisualUpdate() { layer_host_->SetNeedsAnimate(); } blink::WebScreenInfo DocumentView::screenInfo() { DCHECK(root_); auto& metrics = root_->viewport_metrics(); blink::WebScreenInfo screen; screen.rect = blink::WebRect(0, 0, metrics.size->width, metrics.size->height); screen.availableRect = screen.rect; screen.deviceScaleFactor = metrics.device_pixel_ratio; return screen; } mojo::View* DocumentView::createChildFrame() { if (!root_) return nullptr; mojo::View* child = root_->view_manager()->CreateView(); child->SetVisible(true); root_->AddChild(child); return child; } void DocumentView::frameDetached(blink::WebFrame* frame) { // |frame| is invalid after here. frame->close(); } float DocumentView::GetDevicePixelRatio() const { if (root_) return root_->viewport_metrics().device_pixel_ratio; return 1.f; } blink::WebNavigationPolicy DocumentView::decidePolicyForNavigation( const blink::WebFrameClient::NavigationPolicyInfo& info) { if (navigator_host_.get()) { navigator_host_->RequestNavigate( WebNavigationPolicyToNavigationTarget(info.defaultPolicy), mojo::URLRequest::From(info.urlRequest).Pass()); } return blink::WebNavigationPolicyIgnore; } void DocumentView::didAddMessageToConsole( const blink::WebConsoleMessage& message, const blink::WebString& source_name, unsigned source_line, const blink::WebString& stack_trace) { } void DocumentView::didCreateIsolate(blink::WebLocalFrame* frame, Dart_Isolate isolate) { Internals::Create(isolate, this); } void DocumentView::DidCreateIsolate(Dart_Isolate isolate) { Internals::Create(isolate, this); } blink::ServiceProvider* DocumentView::services() { return this; } mojo::NavigatorHost* DocumentView::NavigatorHost() { return navigator_host_.get(); } void DocumentView::OnViewBoundsChanged(mojo::View* view, const mojo::Rect& old_bounds, const mojo::Rect& new_bounds) { DCHECK_EQ(view, root_); UpdateRootSizeAndViewportMetrics(new_bounds); } void DocumentView::OnViewViewportMetricsChanged( mojo::View* view, const mojo::ViewportMetrics& old_metrics, const mojo::ViewportMetrics& new_metrics) { DCHECK_EQ(view, root_); if (web_view_) { web_view_->setDeviceScaleFactor(GetDevicePixelRatio()); } UpdateRootSizeAndViewportMetrics(root_->bounds()); } void DocumentView::UpdateRootSizeAndViewportMetrics( const mojo::Rect& new_bounds) { float device_pixel_ratio = GetDevicePixelRatio(); if (sky_view_) { blink::SkyDisplayMetrics metrics; mojo::Rect bounds = root_->bounds(); metrics.physical_size = blink::WebSize(bounds.width, bounds.height); metrics.device_pixel_ratio = device_pixel_ratio; sky_view_->SetDisplayMetrics(metrics); return; } web_view_->resize(blink::WebSize(new_bounds.width / device_pixel_ratio, new_bounds.height / device_pixel_ratio)); } void DocumentView::OnViewFocusChanged(mojo::View* gained_focus, mojo::View* lost_focus) { if (sky_view_) return; if (root_ == lost_focus) { web_view_->setFocus(false); } else if (root_ == gained_focus) { web_view_->setFocus(true); } } void DocumentView::OnViewDestroyed(mojo::View* view) { DCHECK_EQ(view, root_); root_ = nullptr; } void DocumentView::OnViewInputEvent( mojo::View* view, const mojo::EventPtr& event) { float device_pixel_ratio = GetDevicePixelRatio(); scoped_ptr web_event = ConvertEvent(event, device_pixel_ratio); if (!web_event) return; ui::GestureRecognizer* recognizer = ui::GestureRecognizer::Get(); scoped_ptr touch_event = ConvertToUITouchEvent(*web_event, device_pixel_ratio); if (touch_event) recognizer->ProcessTouchEventPreDispatch(*touch_event, this); bool handled = false; if (web_view_) handled = web_view_->handleInputEvent(*web_event); if (sky_view_) sky_view_->HandleInputEvent(*web_event); if (touch_event) { ui::EventResult result = handled ? ui::ER_UNHANDLED : ui::ER_UNHANDLED; if (auto gestures = recognizer->ProcessTouchEventPostDispatch( *touch_event, result, this)) { for (auto& gesture : *gestures) { scoped_ptr gesture_event = ConvertEvent(*gesture, device_pixel_ratio); if (gesture_event) { if (web_view_) web_view_->handleInputEvent(*gesture_event); if (sky_view_) sky_view_->HandleInputEvent(*gesture_event); } } } } } void DocumentView::StartDebuggerInspectorBackend() { // FIXME: Do we need this for dart? } void DocumentView::InitServiceRegistry() { mojo::ConnectToService(imported_services_.get(), &service_registry_); mojo::Array interface_names(1); interface_names[0] = mojo::ViewManagerClient::Name_; mojo::ServiceProviderImpl* sp_impl(new mojo::ServiceProviderImpl()); sp_impl->AddService(&view_manager_client_factory_); mojo::ServiceProviderPtr sp; service_registry_service_provider_binding_.reset( new mojo::StrongBinding(sp_impl, &sp)); service_registry_->AddServices(interface_names.Pass(), sp.Pass()); } void DocumentView::ScheduleFrame() { DCHECK(sky_view_); layer_host_->SetNeedsAnimate(); } } // namespace sky