1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "components/html_viewer/html_document.h"
8 #include "base/command_line.h"
9 #include "base/location.h"
10 #include "base/memory/scoped_ptr.h"
11 #include "base/single_thread_task_runner.h"
12 #include "base/stl_util.h"
13 #include "base/strings/string_util.h"
14 #include "base/thread_task_runner_handle.h"
15 #include "components/devtools_service/public/cpp/switches.h"
16 #include "components/html_viewer/blink_input_events_type_converters.h"
17 #include "components/html_viewer/blink_url_request_type_converters.h"
18 #include "components/html_viewer/devtools_agent_impl.h"
19 #include "components/html_viewer/media_factory.h"
20 #include "components/html_viewer/setup.h"
21 #include "components/html_viewer/web_layer_tree_view_impl.h"
22 #include "components/html_viewer/web_storage_namespace_impl.h"
23 #include "components/html_viewer/web_url_loader_impl.h"
24 #include "components/view_manager/public/cpp/view.h"
25 #include "components/view_manager/public/cpp/view_manager.h"
26 #include "components/view_manager/public/cpp/view_property.h"
27 #include "components/view_manager/public/interfaces/surfaces.mojom.h"
28 #include "mojo/application/public/cpp/application_impl.h"
29 #include "mojo/application/public/cpp/connect.h"
30 #include "mojo/application/public/interfaces/shell.mojom.h"
31 #include "mojo/converters/geometry/geometry_type_converters.h"
32 #include "skia/ext/refptr.h"
33 #include "third_party/WebKit/public/platform/Platform.h"
34 #include "third_party/WebKit/public/platform/WebHTTPHeaderVisitor.h"
35 #include "third_party/WebKit/public/platform/WebSize.h"
36 #include "third_party/WebKit/public/web/WebConsoleMessage.h"
37 #include "third_party/WebKit/public/web/WebDocument.h"
38 #include "third_party/WebKit/public/web/WebElement.h"
39 #include "third_party/WebKit/public/web/WebInputEvent.h"
40 #include "third_party/WebKit/public/web/WebLocalFrame.h"
41 #include "third_party/WebKit/public/web/WebRemoteFrame.h"
42 #include "third_party/WebKit/public/web/WebRemoteFrameClient.h"
43 #include "third_party/WebKit/public/web/WebScriptSource.h"
44 #include "third_party/WebKit/public/web/WebSettings.h"
45 #include "third_party/WebKit/public/web/WebView.h"
46 #include "third_party/mojo/src/mojo/public/cpp/system/data_pipe.h"
47 #include "third_party/skia/include/core/SkCanvas.h"
48 #include "third_party/skia/include/core/SkColor.h"
49 #include "third_party/skia/include/core/SkDevice.h"
50 #include "ui/gfx/geometry/dip_util.h"
51 #include "ui/gfx/geometry/size.h"
53 using blink::WebString
;
54 using mojo::AxProvider
;
56 using mojo::ServiceProviderPtr
;
57 using mojo::URLResponsePtr
;
59 using mojo::ViewManager
;
60 using mojo::WeakBindToRequest
;
62 namespace html_viewer
{
65 // Switch to enable out of process iframes.
66 const char kOOPIF
[] = "oopifs";
69 return base::CommandLine::ForCurrentProcess()->HasSwitch(kOOPIF
);
72 bool EnableRemoteDebugging() {
73 return base::CommandLine::ForCurrentProcess()->HasSwitch(
74 devtools_service::kRemoteDebuggingPort
);
77 // WebRemoteFrameClient implementation used for OOPIFs.
78 // TODO(sky): this needs to talk to browser by way of an interface.
79 class RemoteFrameClientImpl
: public blink::WebRemoteFrameClient
{
81 explicit RemoteFrameClientImpl(mojo::View
* view
) : view_(view
) {}
82 ~RemoteFrameClientImpl() {}
84 // WebRemoteFrameClient methods:
85 virtual void postMessageEvent(blink::WebLocalFrame
* source_frame
,
86 blink::WebRemoteFrame
* target_frame
,
87 blink::WebSecurityOrigin target_origin
,
88 blink::WebDOMMessageEvent event
) {}
89 virtual void initializeChildFrame(const blink::WebRect
& frame_rect
,
92 rect
.x
= frame_rect
.x
;
93 rect
.y
= frame_rect
.y
;
94 rect
.width
= frame_rect
.width
;
95 rect
.height
= frame_rect
.height
;
96 view_
->SetBounds(rect
);
98 virtual void navigate(const blink::WebURLRequest
& request
,
99 bool should_replace_current_entry
) {}
100 virtual void reload(bool ignore_cache
, bool is_client_redirect
) {}
102 virtual void forwardInputEvent(const blink::WebInputEvent
* event
) {}
105 mojo::View
* const view_
;
107 DISALLOW_COPY_AND_ASSIGN(RemoteFrameClientImpl
);
110 void ConfigureSettings(blink::WebSettings
* settings
) {
111 settings
->setCookieEnabled(true);
112 settings
->setDefaultFixedFontSize(13);
113 settings
->setDefaultFontSize(16);
114 settings
->setLoadsImagesAutomatically(true);
115 settings
->setJavaScriptEnabled(true);
118 mojo::Target
WebNavigationPolicyToNavigationTarget(
119 blink::WebNavigationPolicy policy
) {
121 case blink::WebNavigationPolicyCurrentTab
:
122 return mojo::TARGET_SOURCE_NODE
;
123 case blink::WebNavigationPolicyNewBackgroundTab
:
124 case blink::WebNavigationPolicyNewForegroundTab
:
125 case blink::WebNavigationPolicyNewWindow
:
126 case blink::WebNavigationPolicyNewPopup
:
127 return mojo::TARGET_NEW_NODE
;
129 return mojo::TARGET_DEFAULT
;
133 bool CanNavigateLocally(blink::WebFrame
* frame
,
134 const blink::WebURLRequest
& request
) {
135 // For now, we just load child frames locally.
136 // TODO(sky): this can be removed once we transition to oopifs.
137 if (!EnableOOPIFs() && frame
->parent())
140 // If we have extraData() it means we already have the url response
141 // (presumably because we are being called via Navigate()). In that case we
142 // can go ahead and navigate locally.
143 if (request
.extraData())
146 // Otherwise we don't know if we're the right app to handle this request. Ask
147 // host to do the navigation for us.
153 HTMLDocument::HTMLDocument(mojo::ApplicationImpl
* html_document_app
,
154 mojo::ApplicationConnection
* connection
,
155 URLResponsePtr response
,
157 const DeleteCallback
& delete_callback
)
159 html_document_app
->app_lifetime_helper()->CreateAppRefCount()),
160 html_document_app_(html_document_app
),
161 response_(response
.Pass()),
162 navigator_host_(connection
->GetServiceProvider()),
165 view_manager_client_factory_(html_document_app
->shell(), this),
167 frame_tree_manager_binding_(&frame_tree_manager_
),
168 delete_callback_(delete_callback
) {
169 connection
->AddService(
170 static_cast<mojo::InterfaceFactory
<mandoline::FrameTreeClient
>*>(this));
171 connection
->AddService(
172 static_cast<InterfaceFactory
<mojo::AxProvider
>*>(this));
173 connection
->AddService(&view_manager_client_factory_
);
175 if (setup_
->did_init())
176 Load(response_
.Pass());
179 void HTMLDocument::Destroy() {
180 // See comment in header for a description of lifetime.
182 // Deleting the ViewManager calls back to OnViewManagerDestroyed() and
183 // triggers deletion.
184 delete root_
->view_manager();
190 HTMLDocument::~HTMLDocument() {
191 delete_callback_
.Run(this);
193 STLDeleteElements(&ax_providers_
);
194 STLDeleteElements(&ax_provider_requests_
);
199 root_
->RemoveObserver(this);
202 void HTMLDocument::OnEmbed(View
* root
) {
203 DCHECK(!setup_
->is_headless());
205 root_
->AddObserver(this);
208 InitSetupAndLoadIfNecessary();
211 void HTMLDocument::OnViewManagerDestroyed(ViewManager
* view_manager
) {
215 void HTMLDocument::Create(mojo::ApplicationConnection
* connection
,
216 mojo::InterfaceRequest
<AxProvider
> request
) {
217 if (!did_finish_load_
) {
218 // Cache AxProvider interface requests until the document finishes loading.
219 auto cached_request
= new mojo::InterfaceRequest
<AxProvider
>();
220 *cached_request
= request
.Pass();
221 ax_provider_requests_
.insert(cached_request
);
223 ax_providers_
.insert(
224 new AxProviderImpl(web_view_
, request
.Pass()));
228 void HTMLDocument::Create(
229 mojo::ApplicationConnection
* connection
,
230 mojo::InterfaceRequest
<mandoline::FrameTreeClient
> request
) {
231 frame_tree_manager_binding_
.Bind(request
.Pass());
234 void HTMLDocument::Load(URLResponsePtr response
) {
236 web_view_
= blink::WebView::create(this);
237 touch_handler_
.reset(new TouchHandler(web_view_
));
238 web_layer_tree_view_impl_
->set_widget(web_view_
);
239 ConfigureSettings(web_view_
->settings());
241 blink::WebLocalFrame
* main_frame
=
242 blink::WebLocalFrame::create(blink::WebTreeScopeType::Document
, this);
243 web_view_
->setMainFrame(main_frame
);
245 // TODO(yzshen): http://crbug.com/498986 Creating DevToolsAgentImpl instances
246 // causes html_viewer_apptests flakiness currently. Before we fix that we
247 // cannot enable remote debugging (which is required by Telemetry tests) on
249 if (EnableRemoteDebugging()) {
250 devtools_agent_
.reset(
251 new DevToolsAgentImpl(main_frame
, html_document_app_
->shell()));
254 GURL
url(response
->url
);
256 WebURLRequestExtraData
* extra_data
= new WebURLRequestExtraData
;
257 extra_data
->synthetic_response
= response
.Pass();
259 blink::WebURLRequest web_request
;
260 web_request
.initialize();
261 web_request
.setURL(url
);
262 web_request
.setExtraData(extra_data
);
264 web_view_
->mainFrame()->loadRequest(web_request
);
268 void HTMLDocument::ConvertLocalFrameToRemoteFrame(blink::WebLocalFrame
* frame
) {
269 mojo::View
* view
= frame_to_view_
[frame
].view
;
270 // TODO(sky): this leaks. Fix it.
271 blink::WebRemoteFrame
* remote_frame
= blink::WebRemoteFrame::create(
272 frame_to_view_
[frame
].scope
, new RemoteFrameClientImpl(view
));
273 remote_frame
->initializeFromFrame(frame
);
274 frame
->swap(remote_frame
);
277 void HTMLDocument::UpdateWebviewSizeFromViewSize() {
278 web_view_
->setDeviceScaleFactor(setup_
->device_pixel_ratio());
279 const gfx::Size
size_in_pixels(root_
->bounds().width
, root_
->bounds().height
);
280 const gfx::Size size_in_dips
= gfx::ConvertSizeToDIP(
281 root_
->viewport_metrics().device_pixel_ratio
, size_in_pixels
);
283 blink::WebSize(size_in_dips
.width(), size_in_dips
.height()));
284 web_layer_tree_view_impl_
->setViewportSize(size_in_pixels
);
287 void HTMLDocument::InitSetupAndLoadIfNecessary() {
289 if (root_
->viewport_metrics().device_pixel_ratio
== 0.f
)
293 setup_
->InitIfNecessary(
294 root_
->viewport_metrics().size_in_pixels
.To
<gfx::Size
>(),
295 root_
->viewport_metrics().device_pixel_ratio
);
296 Load(response_
.Pass());
299 UpdateWebviewSizeFromViewSize();
300 web_layer_tree_view_impl_
->set_view(root_
);
303 blink::WebStorageNamespace
* HTMLDocument::createSessionStorageNamespace() {
304 return new WebStorageNamespaceImpl();
307 void HTMLDocument::initializeLayerTreeView() {
308 if (setup_
->is_headless()) {
309 web_layer_tree_view_impl_
.reset(new WebLayerTreeViewImpl(
310 setup_
->compositor_thread(), nullptr, nullptr, nullptr, nullptr));
314 mojo::URLRequestPtr
request(mojo::URLRequest::New());
315 request
->url
= mojo::String::From("mojo:surfaces_service");
316 mojo::SurfacePtr surface
;
317 html_document_app_
->ConnectToService(request
.Pass(), &surface
);
319 // TODO(jamesr): Should be mojo:gpu_service
320 mojo::URLRequestPtr
request2(mojo::URLRequest::New());
321 request2
->url
= mojo::String::From("mojo:view_manager");
322 mojo::GpuPtr gpu_service
;
323 html_document_app_
->ConnectToService(request2
.Pass(), &gpu_service
);
324 web_layer_tree_view_impl_
.reset(new WebLayerTreeViewImpl(
325 setup_
->compositor_thread(), setup_
->gpu_memory_buffer_manager(),
326 setup_
->raster_thread_helper()->task_graph_runner(), surface
.Pass(),
327 gpu_service
.Pass()));
330 blink::WebLayerTreeView
* HTMLDocument::layerTreeView() {
331 return web_layer_tree_view_impl_
.get();
334 blink::WebMediaPlayer
* HTMLDocument::createMediaPlayer(
335 blink::WebLocalFrame
* frame
,
336 const blink::WebURL
& url
,
337 blink::WebMediaPlayerClient
* client
,
338 blink::WebContentDecryptionModule
* initial_cdm
) {
339 return setup_
->media_factory()->CreateMediaPlayer(
340 frame
, url
, client
, initial_cdm
, html_document_app_
->shell());
343 blink::WebFrame
* HTMLDocument::createChildFrame(
344 blink::WebLocalFrame
* parent
,
345 blink::WebTreeScopeType scope
,
346 const blink::WebString
& frameName
,
347 blink::WebSandboxFlags sandboxFlags
) {
348 blink::WebLocalFrame
* child_frame
= blink::WebLocalFrame::create(scope
, this);
349 parent
->appendChild(child_frame
);
350 if (EnableOOPIFs()) {
351 // Create the view that will house the frame now. We embed only once we know
353 mojo::View
* child_frame_view
= root_
->view_manager()->CreateView();
354 child_frame_view
->SetVisible(true);
355 root_
->AddChild(child_frame_view
);
357 ChildFrameData child_frame_data
;
358 child_frame_data
.view
= child_frame_view
;
359 child_frame_data
.scope
= scope
;
360 frame_to_view_
[child_frame
] = child_frame_data
;
365 void HTMLDocument::frameDetached(blink::WebFrame
* frame
) {
366 frameDetached(frame
, DetachType::Remove
);
369 void HTMLDocument::frameDetached(blink::WebFrame
* frame
, DetachType type
) {
370 DCHECK(type
== DetachType::Remove
);
372 frame
->parent()->removeChild(frame
);
374 if (devtools_agent_
&& frame
== devtools_agent_
->frame())
375 devtools_agent_
.reset();
377 // |frame| is invalid after here.
381 blink::WebCookieJar
* HTMLDocument::cookieJar(blink::WebLocalFrame
* frame
) {
382 // TODO(darin): Blink does not fallback to the Platform provided WebCookieJar.
383 // Either it should, as it once did, or we should find another solution here.
384 return blink::Platform::current()->cookieJar();
387 blink::WebNavigationPolicy
HTMLDocument::decidePolicyForNavigation(
388 const NavigationPolicyInfo
& info
) {
389 // TODO(yzshen): Remove this check once the browser is able to navigate an
390 // existing html_viewer instance and about:blank page support is ready.
391 if (devtools_agent_
&& devtools_agent_
->frame() == info
.frame
&&
392 devtools_agent_
->handling_page_navigate_request()) {
393 return info
.defaultPolicy
;
396 std::string frame_name
= info
.frame
? info
.frame
->assignedName().utf8() : "";
397 if (info
.frame
->parent() && EnableOOPIFs()) {
398 mojo::View
* view
= frame_to_view_
[info
.frame
].view
;
399 mojo::URLRequestPtr url_request
= mojo::URLRequest::From(info
.urlRequest
);
400 view
->EmbedAllowingReembed(url_request
.Pass());
401 // TODO(sky): I tried swapping the frame types here, but that resulted in
402 // the view never getting sized. Figure out why.
403 // TODO(sky): there are timing conditions here, and we should only do this
405 base::MessageLoop::current()->PostTask(
406 FROM_HERE
, base::Bind(&HTMLDocument::ConvertLocalFrameToRemoteFrame
,
407 base::Unretained(this), info
.frame
));
408 return blink::WebNavigationPolicyIgnore
;
411 if (CanNavigateLocally(info
.frame
, info
.urlRequest
))
412 return info
.defaultPolicy
;
414 if (navigator_host_
.get()) {
415 mojo::URLRequestPtr url_request
= mojo::URLRequest::From(info
.urlRequest
);
416 navigator_host_
->RequestNavigate(
417 WebNavigationPolicyToNavigationTarget(info
.defaultPolicy
),
421 return blink::WebNavigationPolicyIgnore
;
424 void HTMLDocument::didAddMessageToConsole(
425 const blink::WebConsoleMessage
& message
,
426 const blink::WebString
& source_name
,
427 unsigned source_line
,
428 const blink::WebString
& stack_trace
) {
429 VLOG(1) << "[" << source_name
.utf8() << "(" << source_line
<< ")] "
430 << message
.text
.utf8();
433 void HTMLDocument::didFinishLoad(blink::WebLocalFrame
* frame
) {
434 // TODO(msw): Notify AxProvider clients of updates on child frame loads.
435 did_finish_load_
= true;
436 // Bind any pending AxProviderImpl interface requests.
437 for (auto it
: ax_provider_requests_
)
438 ax_providers_
.insert(new AxProviderImpl(web_view_
, it
->Pass()));
439 STLDeleteElements(&ax_provider_requests_
);
442 void HTMLDocument::didNavigateWithinPage(
443 blink::WebLocalFrame
* frame
,
444 const blink::WebHistoryItem
& history_item
,
445 blink::WebHistoryCommitType commit_type
) {
446 if (navigator_host_
.get())
447 navigator_host_
->DidNavigateLocally(history_item
.urlString().utf8());
450 blink::WebEncryptedMediaClient
* HTMLDocument::encryptedMediaClient() {
451 return setup_
->media_factory()->GetEncryptedMediaClient();
454 void HTMLDocument::OnViewBoundsChanged(View
* view
,
455 const Rect
& old_bounds
,
456 const Rect
& new_bounds
) {
457 DCHECK_EQ(view
, root_
);
458 UpdateWebviewSizeFromViewSize();
461 void HTMLDocument::OnViewViewportMetricsChanged(
463 const mojo::ViewportMetrics
& old_metrics
,
464 const mojo::ViewportMetrics
& new_metrics
) {
465 InitSetupAndLoadIfNecessary();
468 void HTMLDocument::OnViewDestroyed(View
* view
) {
469 DCHECK_EQ(view
, root_
);
473 void HTMLDocument::OnViewInputEvent(View
* view
, const mojo::EventPtr
& event
) {
474 if (event
->pointer_data
) {
475 // Blink expects coordintes to be in DIPs.
476 event
->pointer_data
->x
/= setup_
->device_pixel_ratio();
477 event
->pointer_data
->y
/= setup_
->device_pixel_ratio();
478 event
->pointer_data
->screen_x
/= setup_
->device_pixel_ratio();
479 event
->pointer_data
->screen_y
/= setup_
->device_pixel_ratio();
482 if ((event
->action
== mojo::EVENT_TYPE_POINTER_DOWN
||
483 event
->action
== mojo::EVENT_TYPE_POINTER_UP
||
484 event
->action
== mojo::EVENT_TYPE_POINTER_CANCEL
||
485 event
->action
== mojo::EVENT_TYPE_POINTER_MOVE
) &&
486 event
->pointer_data
->kind
== mojo::POINTER_KIND_TOUCH
) {
487 touch_handler_
->OnTouchEvent(*event
);
490 scoped_ptr
<blink::WebInputEvent
> web_event
=
491 event
.To
<scoped_ptr
<blink::WebInputEvent
>>();
493 web_view_
->handleInputEvent(*web_event
);
496 void HTMLDocument::OnViewFocusChanged(mojo::View
* gained_focus
,
497 mojo::View
* lost_focus
) {
501 void HTMLDocument::UpdateFocus() {
504 bool is_focused
= root_
&& root_
->HasFocus();
505 web_view_
->setFocus(is_focused
);
506 web_view_
->setIsActive(is_focused
);
509 } // namespace html_viewer