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 "athena/content/web_activity.h"
7 #include "athena/activity/public/activity_factory.h"
8 #include "athena/activity/public/activity_manager.h"
9 #include "athena/activity/public/activity_view.h"
10 #include "athena/content/content_proxy.h"
11 #include "athena/content/media_utils.h"
12 #include "athena/content/public/dialogs.h"
13 #include "athena/content/web_activity_helpers.h"
14 #include "athena/input/public/accelerator_manager.h"
15 #include "athena/strings/grit/athena_strings.h"
16 #include "base/bind.h"
17 #include "base/command_line.h"
18 #include "base/strings/utf_string_conversions.h"
19 #include "components/app_modal/javascript_dialog_manager.h"
20 #include "components/favicon_base/select_favicon_frames.h"
21 #include "content/public/browser/navigation_controller.h"
22 #include "content/public/browser/web_contents.h"
23 #include "content/public/browser/web_contents_delegate.h"
24 #include "content/public/common/content_switches.h"
25 #include "content/public/common/favicon_url.h"
26 #include "ui/aura/window.h"
27 #include "ui/base/l10n/l10n_util.h"
28 #include "ui/compositor/closure_animation_observer.h"
29 #include "ui/compositor/scoped_layer_animation_settings.h"
30 #include "ui/content_accelerators/accelerator_util.h"
31 #include "ui/views/background.h"
32 #include "ui/views/controls/label.h"
33 #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
34 #include "ui/views/controls/webview/webview.h"
35 #include "ui/views/focus/focus_manager.h"
36 #include "ui/views/widget/widget.h"
41 class WebActivityController
: public AcceleratorHandler
{
47 CMD_RELOAD_IGNORE_CACHE
,
52 explicit WebActivityController(WebActivity
* owner_activity
,
53 views::WebView
* web_view
)
54 : owner_activity_(owner_activity
),
56 reserved_accelerator_enabled_(true) {}
57 ~WebActivityController() override
{}
59 // Installs accelerators for web activity.
60 void InstallAccelerators() {
61 accelerator_manager_
= AcceleratorManager::CreateForFocusManager(
62 web_view_
->GetFocusManager()).Pass();
63 const AcceleratorData accelerator_data
[] = {
64 {TRIGGER_ON_PRESS
, ui::VKEY_R
, ui::EF_CONTROL_DOWN
, CMD_RELOAD
,
66 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_REFRESH
, ui::EF_NONE
, CMD_RELOAD
,
68 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_REFRESH
, ui::EF_CONTROL_DOWN
,
69 CMD_RELOAD_IGNORE_CACHE
, AF_NONE
},
70 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_FORWARD
, ui::EF_NONE
, CMD_FORWARD
,
72 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_BACK
, ui::EF_NONE
, CMD_BACK
,
74 {TRIGGER_ON_PRESS
, ui::VKEY_W
, ui::EF_CONTROL_DOWN
, CMD_CLOSE
, AF_NONE
},
75 {TRIGGER_ON_PRESS
, ui::VKEY_ESCAPE
, ui::EF_NONE
, CMD_STOP
, AF_NONE
},
77 accelerator_manager_
->RegisterAccelerators(
78 accelerator_data
, arraysize(accelerator_data
), this);
81 // Methods that are called before and after key events are consumed by the web
83 // See the documentation in WebContentsDelegate: for more details.
84 bool PreHandleKeyboardEvent(content::WebContents
* source
,
85 const content::NativeWebKeyboardEvent
& event
,
86 bool* is_keyboard_shortcut
) {
87 ui::Accelerator accelerator
=
88 ui::GetAcceleratorFromNativeWebKeyboardEvent(event
);
90 if (reserved_accelerator_enabled_
&&
91 accelerator_manager_
->IsRegistered(accelerator
, AF_RESERVED
)) {
92 return web_view_
->GetFocusManager()->ProcessAccelerator(accelerator
);
94 *is_keyboard_shortcut
=
95 accelerator_manager_
->IsRegistered(accelerator
, AF_NONE
);
99 void HandleKeyboardEvent(content::WebContents
* source
,
100 const content::NativeWebKeyboardEvent
& event
) {
101 unhandled_keyboard_event_handler_
.HandleKeyboardEvent(
102 event
, web_view_
->GetFocusManager());
106 // AcceleratorHandler:
107 bool IsCommandEnabled(int command_id
) const override
{
108 switch (command_id
) {
110 case CMD_RELOAD_IGNORE_CACHE
:
113 return web_view_
->GetWebContents()->GetController().CanGoBack();
115 return web_view_
->GetWebContents()->GetController().CanGoForward();
117 // TODO(oshima): check onbeforeunload handler.
120 return web_view_
->GetWebContents()->IsLoading();
125 bool OnAcceleratorFired(int command_id
,
126 const ui::Accelerator
& accelerator
) override
{
127 switch (command_id
) {
129 web_view_
->GetWebContents()->GetController().Reload(false);
131 case CMD_RELOAD_IGNORE_CACHE
:
132 web_view_
->GetWebContents()->GetController().ReloadIgnoringCache(false);
135 web_view_
->GetWebContents()->GetController().GoBack();
138 web_view_
->GetWebContents()->GetController().GoForward();
141 Activity::Delete(owner_activity_
);
144 web_view_
->GetWebContents()->Stop();
150 Activity
* const owner_activity_
;
151 views::WebView
* web_view_
;
152 bool reserved_accelerator_enabled_
;
153 scoped_ptr
<AcceleratorManager
> accelerator_manager_
;
154 views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_
;
156 DISALLOW_COPY_AND_ASSIGN(WebActivityController
);
159 const SkColor kDefaultTitleColor
= SkColorSetRGB(0xf2, 0xf2, 0xf2);
160 const int kIconSize
= 32;
161 const float kDistanceShowReloadMessage
= 100;
162 const float kDistanceReload
= 150;
166 // A web view for athena's web activity. Note that AthenaWebView will create its
167 // own content so that it can eject and reload it.
168 class AthenaWebView
: public views::WebView
{
170 explicit AthenaWebView(content::BrowserContext
* context
,
171 WebActivity
* owner_activity
)
172 : views::WebView(context
),
173 controller_(new WebActivityController(owner_activity
, this)),
174 owner_activity_(owner_activity
),
177 SetEmbedFullscreenWidgetMode(true);
178 // TODO(skuhne): Add content observer to detect renderer crash and set
179 // content status to unloaded if that happens.
182 AthenaWebView(content::WebContents
* web_contents
,
183 WebActivity
* owner_activity
)
184 : views::WebView(web_contents
->GetBrowserContext()),
185 controller_(new WebActivityController(owner_activity
, this)),
186 owner_activity_(owner_activity
) {
187 scoped_ptr
<content::WebContents
> old_contents(
188 SwapWebContents(scoped_ptr
<content::WebContents
>(web_contents
)));
191 ~AthenaWebView() override
{}
193 void InstallAccelerators() { controller_
->InstallAccelerators(); }
195 void EvictContent() {
196 scoped_ptr
<content::WebContents
> old_contents(SwapWebContents(
197 scoped_ptr
<content::WebContents
>(content::WebContents::Create(
198 content::WebContents::CreateParams(browser_context())))));
199 // If there is a progress bar, we need to get rid of it now since its
200 // associated content, parent window and layers will disappear with evicting
202 progress_bar_
.reset();
203 evicted_web_contents_
.reset(
204 content::WebContents::Create(content::WebContents::CreateParams(
205 old_contents
->GetBrowserContext())));
206 evicted_web_contents_
->GetController().CopyStateFrom(
207 old_contents
->GetController());
208 // As soon as the new contents becomes visible, it should reload.
209 // TODO(skuhne): This breaks script connections with other activities.
210 // Even though this is the same technique as used by the TabStripModel,
211 // we might want to address this cleaner since we are more likely to
212 // run into this state. by unloading.
215 void AttachHelpers() {
216 if (!IsContentEvicted())
217 AttachWebActivityHelpers(GetWebContents());
218 // Else: The helpers will be attached when the evicted content is reloaded.
221 void ReloadEvictedContent() {
222 CHECK(evicted_web_contents_
.get());
224 // Order is important. The helpers must be attached prior to the RenderView
226 AttachWebActivityHelpers(evicted_web_contents_
.get());
228 SwapWebContents(evicted_web_contents_
.Pass());
231 // Check if the content got evicted.
232 const bool IsContentEvicted() { return !!evicted_web_contents_
.get(); }
234 // content::WebContentsDelegate:
235 content::WebContents
* OpenURLFromTab(
236 content::WebContents
* source
,
237 const content::OpenURLParams
& params
) override
{
238 switch(params
.disposition
) {
240 DCHECK(source
== web_contents());
241 content::NavigationController::LoadURLParams
load_url_params(
243 load_url_params
.referrer
= params
.referrer
;
244 load_url_params
.frame_tree_node_id
= params
.frame_tree_node_id
;
245 load_url_params
.transition_type
= params
.transition
;
246 load_url_params
.extra_headers
= params
.extra_headers
;
247 load_url_params
.should_replace_current_entry
=
248 params
.should_replace_current_entry
;
249 load_url_params
.is_renderer_initiated
= params
.is_renderer_initiated
;
250 load_url_params
.transferred_global_request_id
=
251 params
.transferred_global_request_id
;
252 web_contents()->GetController().LoadURLWithParams(load_url_params
);
253 return web_contents();
255 case NEW_FOREGROUND_TAB
:
256 case NEW_BACKGROUND_TAB
:
259 Activity
* activity
= ActivityFactory::Get()->CreateWebActivity(
260 browser_context(), base::string16(), params
.url
);
261 Activity::Show(activity
);
267 // nullptr is returned if the URL wasn't opened immediately.
271 bool CanOverscrollContent() const override
{
272 const std::string value
= CommandLine::ForCurrentProcess()->
273 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation
);
277 void OverscrollUpdate(float delta_y
) override
{
278 overscroll_y_
= delta_y
;
279 if (overscroll_y_
> kDistanceShowReloadMessage
) {
280 if (!reload_message_
)
281 CreateReloadMessage();
282 reload_message_
->Show();
283 float opacity
= 1.0f
;
284 if (overscroll_y_
< kDistanceReload
) {
285 opacity
= (overscroll_y_
- kDistanceShowReloadMessage
) /
286 (kDistanceReload
- kDistanceShowReloadMessage
);
288 reload_message_
->GetLayer()->SetOpacity(opacity
);
289 } else if (reload_message_
) {
290 reload_message_
->Hide();
294 void OverscrollComplete() override
{
295 if (overscroll_y_
>= kDistanceReload
)
296 GetWebContents()->GetController().Reload(false);
298 reload_message_
->Hide();
302 void AddNewContents(content::WebContents
* source
,
303 content::WebContents
* new_contents
,
304 WindowOpenDisposition disposition
,
305 const gfx::Rect
& initial_pos
,
307 bool* was_blocked
) override
{
309 ActivityFactory::Get()->CreateWebActivity(new_contents
);
310 Activity::Show(activity
);
313 bool PreHandleKeyboardEvent(content::WebContents
* source
,
314 const content::NativeWebKeyboardEvent
& event
,
315 bool* is_keyboard_shortcut
) override
{
316 return controller_
->PreHandleKeyboardEvent(
317 source
, event
, is_keyboard_shortcut
);
320 void HandleKeyboardEvent(
321 content::WebContents
* source
,
322 const content::NativeWebKeyboardEvent
& event
) override
{
323 controller_
->HandleKeyboardEvent(source
, event
);
326 void ToggleFullscreenModeForTab(content::WebContents
* web_contents
,
327 bool enter_fullscreen
) override
{
328 fullscreen_
= enter_fullscreen
;
329 GetWidget()->SetFullscreen(fullscreen_
);
332 bool IsFullscreenForTabOrPending(
333 const content::WebContents
* web_contents
) const override
{
337 void LoadingStateChanged(content::WebContents
* source
,
338 bool to_different_document
) override
{
339 bool has_stopped
= source
== nullptr || !source
->IsLoading();
340 LoadProgressChanged(source
, has_stopped
? 1 : 0);
343 void LoadProgressChanged(content::WebContents
* source
,
344 double progress
) override
{
348 if (!progress_bar_
) {
350 source
->GetNativeView()->layer()->Add(progress_bar_
.get());
352 progress_bar_
->SetBounds(gfx::Rect(
353 0, 0, progress
* progress_bar_
->parent()->bounds().width(), 3));
357 ui::ScopedLayerAnimationSettings
settings(progress_bar_
->GetAnimator());
358 settings
.SetTweenType(gfx::Tween::EASE_IN
);
359 ui::Layer
* layer
= progress_bar_
.get();
360 settings
.AddObserver(new ui::ClosureAnimationObserver(
361 base::Bind(&base::DeletePointer
<ui::Layer
>, progress_bar_
.release())));
362 layer
->SetOpacity(0.f
);
365 content::JavaScriptDialogManager
* GetJavaScriptDialogManager(
366 content::WebContents
* contents
) override
{
367 return app_modal::JavaScriptDialogManager::GetInstance();
370 content::ColorChooser
* OpenColorChooser(
371 content::WebContents
* web_contents
,
373 const std::vector
<content::ColorSuggestion
>& suggestions
) override
{
374 return athena::OpenColorChooser(web_contents
, color
, suggestions
);
377 // Called when a file selection is to be done.
378 void RunFileChooser(content::WebContents
* web_contents
,
379 const content::FileChooserParams
& params
) override
{
380 return athena::OpenFileChooser(web_contents
, params
);
383 void CloseContents(content::WebContents
* contents
) override
{
384 Activity::Delete(owner_activity_
);
388 void CreateProgressBar() {
389 CHECK(!progress_bar_
);
390 progress_bar_
.reset(new ui::Layer(ui::LAYER_SOLID_COLOR
));
391 progress_bar_
->SetColor(SkColorSetRGB(0x17, 0x59, 0xcd));
394 void CreateReloadMessage() {
395 CHECK(!reload_message_
);
396 reload_message_
.reset(new views::Widget
);
397 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_CONTROL
);
398 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
399 params
.parent
= GetWidget()->GetNativeView();
400 reload_message_
->Init(params
);
402 views::Label
* label
= new views::Label(
403 l10n_util::GetStringUTF16(IDS_ATHENA_PULL_TO_RELOAD_MESSAGE
));
404 label
->SetBackgroundColor(SK_ColorGRAY
);
405 label
->set_background(
406 views::Background::CreateSolidBackground(SK_ColorGRAY
));
408 reload_message_
->SetContentsView(label
);
409 reload_message_
->SetBounds(ConvertRectToWidget(
410 gfx::Rect(0, 0, width(), label
->GetPreferredSize().height())));
413 scoped_ptr
<WebActivityController
> controller_
;
415 Activity
* const owner_activity_
;
417 // If the activity got evicted, this is the web content which holds the known
418 // state of the content before eviction.
419 scoped_ptr
<content::WebContents
> evicted_web_contents_
;
421 scoped_ptr
<ui::Layer
> progress_bar_
;
423 scoped_ptr
<views::Widget
> reload_message_
;
425 // TODO(oshima): Find out if we should support window fullscreen.
426 // It may still useful when a user is in split mode.
429 // The distance that the user has overscrolled vertically.
432 DISALLOW_COPY_AND_ASSIGN(AthenaWebView
);
435 WebActivity::WebActivity(content::BrowserContext
* browser_context
,
436 const base::string16
& title
,
438 : browser_context_(browser_context
),
439 web_view_(new AthenaWebView(browser_context
, this)),
441 title_color_(kDefaultTitleColor
),
442 current_state_(ACTIVITY_UNLOADED
),
443 activity_view_(nullptr),
444 weak_ptr_factory_(this) {
445 // Order is important. The web activity helpers must be attached prior to the
446 // RenderView being created.
447 SetCurrentState(ACTIVITY_INVISIBLE
);
448 web_view_
->LoadInitialURL(url
);
451 WebActivity::WebActivity(content::WebContents
* contents
)
452 : browser_context_(contents
->GetBrowserContext()),
453 web_view_(new AthenaWebView(contents
, this)),
454 title_color_(kDefaultTitleColor
),
455 current_state_(ACTIVITY_UNLOADED
),
456 activity_view_(nullptr),
457 weak_ptr_factory_(this) {
458 // If the activity was created as a result of
459 // WebContentsDelegate::AddNewContents(), web activity helpers may not be
460 // created prior to the RenderView being created. Desktop Chrome has a
462 SetCurrentState(ACTIVITY_INVISIBLE
);
465 WebActivity::~WebActivity() {
466 // It is not required to change the activity state to UNLOADED - unless we
467 // would add state observers.
470 ActivityViewModel
* WebActivity::GetActivityViewModel() {
474 void WebActivity::SetCurrentState(Activity::ActivityState state
) {
475 DCHECK_NE(state
, current_state_
);
476 if (current_state_
== ACTIVITY_UNLOADED
) {
477 web_view_
->AttachHelpers();
478 if (web_view_
->IsContentEvicted())
479 web_view_
->ReloadEvictedContent();
480 Observe(web_view_
->GetWebContents());
484 case ACTIVITY_VISIBLE
:
487 case ACTIVITY_INVISIBLE
:
488 if (current_state_
== ACTIVITY_VISIBLE
)
492 case ACTIVITY_BACKGROUND_LOW_PRIORITY
:
493 DCHECK(ACTIVITY_VISIBLE
== current_state_
||
494 ACTIVITY_INVISIBLE
== current_state_
);
495 // TODO(skuhne): Do this.
497 case ACTIVITY_PERSISTENT
:
498 DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY
, current_state_
);
499 // TODO(skuhne): Do this. As soon as the new resource management is
500 // agreed upon - or remove otherwise.
502 case ACTIVITY_UNLOADED
:
503 DCHECK_NE(ACTIVITY_UNLOADED
, current_state_
);
505 content_proxy_
->ContentWillUnload();
507 web_view_
->EvictContent();
510 // Remember the last requested state.
511 current_state_
= state
;
514 Activity::ActivityState
WebActivity::GetCurrentState() {
515 // If the content is evicted, the state has to be UNLOADED.
516 DCHECK(!web_view_
->IsContentEvicted() ||
517 current_state_
== ACTIVITY_UNLOADED
);
518 return current_state_
;
521 bool WebActivity::IsVisible() {
522 return web_view_
->visible() && current_state_
!= ACTIVITY_UNLOADED
;
525 Activity::ActivityMediaState
WebActivity::GetMediaState() {
526 return current_state_
== ACTIVITY_UNLOADED
?
527 Activity::ACTIVITY_MEDIA_STATE_NONE
:
528 GetActivityMediaState(GetWebContents());
531 aura::Window
* WebActivity::GetWindow() {
532 return web_view_
->GetWidget() ? web_view_
->GetWidget()->GetNativeWindow()
536 content::WebContents
* WebActivity::GetWebContents() {
537 return web_view_
->GetWebContents();
540 void WebActivity::Init() {
541 web_view_
->InstallAccelerators();
544 SkColor
WebActivity::GetRepresentativeColor() const {
548 base::string16
WebActivity::GetTitle() const {
551 const base::string16
& title
= web_view_
->GetWebContents()->GetTitle();
554 return base::UTF8ToUTF16(web_view_
->GetWebContents()->GetVisibleURL().host());
557 gfx::ImageSkia
WebActivity::GetIcon() const {
561 void WebActivity::SetActivityView(ActivityView
* view
) {
562 DCHECK(!activity_view_
);
563 activity_view_
= view
;
566 bool WebActivity::UsesFrame() const {
570 views::View
* WebActivity::GetContentsView() {
574 gfx::ImageSkia
WebActivity::GetOverviewModeImage() {
575 if (content_proxy_
.get())
576 return content_proxy_
->GetContentImage();
577 return gfx::ImageSkia();
580 void WebActivity::PrepareContentsForOverview() {
581 // Turn on fast resizing to avoid re-laying out the web contents when
582 // entering / exiting overview mode and the content is visible.
583 if (!content_proxy_
.get())
584 web_view_
->SetFastResize(true);
587 void WebActivity::ResetContentsView() {
588 // Turn on fast resizing to avoid re-laying out the web contents when
589 // entering / exiting overview mode and the content is visible.
590 if (!content_proxy_
.get()) {
591 web_view_
->SetFastResize(false);
596 void WebActivity::TitleWasSet(content::NavigationEntry
* entry
,
599 activity_view_
->UpdateTitle();
602 void WebActivity::DidNavigateMainFrame(
603 const content::LoadCommittedDetails
& details
,
604 const content::FrameNavigateParams
& params
) {
605 // Prevent old image requests from calling back to OnDidDownloadFavicon().
606 weak_ptr_factory_
.InvalidateWeakPtrs();
608 icon_
= gfx::ImageSkia();
610 activity_view_
->UpdateIcon();
613 void WebActivity::DidUpdateFaviconURL(
614 const std::vector
<content::FaviconURL
>& candidates
) {
615 // Pick an arbitrary favicon of type FAVICON to use.
616 // TODO(pkotwicz): Do something better once the favicon code is componentized.
617 // (crbug.com/401997)
618 weak_ptr_factory_
.InvalidateWeakPtrs();
619 for (size_t i
= 0; i
< candidates
.size(); ++i
) {
620 if (candidates
[i
].icon_type
== content::FaviconURL::FAVICON
) {
621 web_view_
->GetWebContents()->DownloadImage(
622 candidates
[i
].icon_url
,
625 base::Bind(&WebActivity::OnDidDownloadFavicon
,
626 weak_ptr_factory_
.GetWeakPtr()));
632 void WebActivity::OnDidDownloadFavicon(
634 int http_status_code
,
636 const std::vector
<SkBitmap
>& bitmaps
,
637 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
638 icon_
= CreateFaviconImageSkia(
639 bitmaps
, original_bitmap_sizes
, kIconSize
, nullptr);
641 activity_view_
->UpdateIcon();
644 void WebActivity::DidChangeThemeColor(SkColor theme_color
) {
645 title_color_
= theme_color
;
647 activity_view_
->UpdateRepresentativeColor();
650 void WebActivity::HideContentProxy() {
651 if (content_proxy_
.get())
652 content_proxy_
.reset(nullptr);
655 void WebActivity::ShowContentProxy() {
656 if (!content_proxy_
.get())
657 content_proxy_
.reset(new ContentProxy(web_view_
));
660 } // namespace athena