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_dialogs/javascript_dialog_manager.h"
20 #include "components/favicon_base/select_favicon_frames.h"
21 #include "content/public/browser/native_web_keyboard_event.h"
22 #include "content/public/browser/navigation_controller.h"
23 #include "content/public/browser/web_contents.h"
24 #include "content/public/browser/web_contents_delegate.h"
25 #include "content/public/common/content_switches.h"
26 #include "content/public/common/favicon_url.h"
27 #include "ui/aura/window.h"
28 #include "ui/base/l10n/l10n_util.h"
29 #include "ui/compositor/closure_animation_observer.h"
30 #include "ui/compositor/scoped_layer_animation_settings.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 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
),
89 content::GetModifiersFromNativeWebKeyboardEvent(event
));
90 if (event
.type
== blink::WebInputEvent::KeyUp
)
91 accelerator
.set_type(ui::ET_KEY_RELEASED
);
93 if (reserved_accelerator_enabled_
&&
94 accelerator_manager_
->IsRegistered(accelerator
, AF_RESERVED
)) {
95 return web_view_
->GetFocusManager()->ProcessAccelerator(accelerator
);
97 *is_keyboard_shortcut
=
98 accelerator_manager_
->IsRegistered(accelerator
, AF_NONE
);
102 void HandleKeyboardEvent(content::WebContents
* source
,
103 const content::NativeWebKeyboardEvent
& event
) {
104 unhandled_keyboard_event_handler_
.HandleKeyboardEvent(
105 event
, web_view_
->GetFocusManager());
109 // AcceleratorHandler:
110 bool IsCommandEnabled(int command_id
) const override
{
111 switch (command_id
) {
113 case CMD_RELOAD_IGNORE_CACHE
:
116 return web_view_
->GetWebContents()->GetController().CanGoBack();
118 return web_view_
->GetWebContents()->GetController().CanGoForward();
120 // TODO(oshima): check onbeforeunload handler.
123 return web_view_
->GetWebContents()->IsLoading();
128 bool OnAcceleratorFired(int command_id
,
129 const ui::Accelerator
& accelerator
) override
{
130 switch (command_id
) {
132 web_view_
->GetWebContents()->GetController().Reload(false);
134 case CMD_RELOAD_IGNORE_CACHE
:
135 web_view_
->GetWebContents()->GetController().ReloadIgnoringCache(false);
138 web_view_
->GetWebContents()->GetController().GoBack();
141 web_view_
->GetWebContents()->GetController().GoForward();
144 Activity::Delete(owner_activity_
);
147 web_view_
->GetWebContents()->Stop();
153 Activity
* const owner_activity_
;
154 views::WebView
* web_view_
;
155 bool reserved_accelerator_enabled_
;
156 scoped_ptr
<AcceleratorManager
> accelerator_manager_
;
157 views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_
;
159 DISALLOW_COPY_AND_ASSIGN(WebActivityController
);
162 const SkColor kDefaultTitleColor
= SkColorSetRGB(0xf2, 0xf2, 0xf2);
163 const int kIconSize
= 32;
164 const float kDistanceShowReloadMessage
= 100;
165 const float kDistanceReload
= 150;
169 // A web view for athena's web activity. Note that AthenaWebView will create its
170 // own content so that it can eject and reload it.
171 class AthenaWebView
: public views::WebView
{
173 explicit AthenaWebView(content::BrowserContext
* context
,
174 WebActivity
* owner_activity
)
175 : views::WebView(context
),
176 controller_(new WebActivityController(owner_activity
, this)),
177 owner_activity_(owner_activity
),
180 SetEmbedFullscreenWidgetMode(true);
181 // TODO(skuhne): Add content observer to detect renderer crash and set
182 // content status to unloaded if that happens.
185 AthenaWebView(content::WebContents
* web_contents
,
186 WebActivity
* owner_activity
)
187 : views::WebView(web_contents
->GetBrowserContext()),
188 controller_(new WebActivityController(owner_activity
, this)),
189 owner_activity_(owner_activity
) {
190 scoped_ptr
<content::WebContents
> old_contents(
191 SwapWebContents(scoped_ptr
<content::WebContents
>(web_contents
)));
194 ~AthenaWebView() override
{}
196 void InstallAccelerators() { controller_
->InstallAccelerators(); }
198 void EvictContent() {
199 scoped_ptr
<content::WebContents
> old_contents(SwapWebContents(
200 scoped_ptr
<content::WebContents
>(content::WebContents::Create(
201 content::WebContents::CreateParams(browser_context())))));
202 // If there is a progress bar, we need to get rid of it now since its
203 // associated content, parent window and layers will disappear with evicting
205 progress_bar_
.reset();
206 evicted_web_contents_
.reset(
207 content::WebContents::Create(content::WebContents::CreateParams(
208 old_contents
->GetBrowserContext())));
209 evicted_web_contents_
->GetController().CopyStateFrom(
210 old_contents
->GetController());
211 // As soon as the new contents becomes visible, it should reload.
212 // TODO(skuhne): This breaks script connections with other activities.
213 // Even though this is the same technique as used by the TabStripModel,
214 // we might want to address this cleaner since we are more likely to
215 // run into this state. by unloading.
218 void AttachHelpers() {
219 if (!IsContentEvicted())
220 AttachWebActivityHelpers(GetWebContents());
221 // Else: The helpers will be attached when the evicted content is reloaded.
224 void ReloadEvictedContent() {
225 CHECK(evicted_web_contents_
.get());
227 // Order is important. The helpers must be attached prior to the RenderView
229 AttachWebActivityHelpers(evicted_web_contents_
.get());
231 SwapWebContents(evicted_web_contents_
.Pass());
234 // Check if the content got evicted.
235 const bool IsContentEvicted() { return !!evicted_web_contents_
.get(); }
237 // content::WebContentsDelegate:
238 content::WebContents
* OpenURLFromTab(
239 content::WebContents
* source
,
240 const content::OpenURLParams
& params
) override
{
241 switch(params
.disposition
) {
243 DCHECK(source
== web_contents());
244 content::NavigationController::LoadURLParams
load_url_params(
246 load_url_params
.referrer
= params
.referrer
;
247 load_url_params
.frame_tree_node_id
= params
.frame_tree_node_id
;
248 load_url_params
.transition_type
= params
.transition
;
249 load_url_params
.extra_headers
= params
.extra_headers
;
250 load_url_params
.should_replace_current_entry
=
251 params
.should_replace_current_entry
;
252 load_url_params
.is_renderer_initiated
= params
.is_renderer_initiated
;
253 load_url_params
.transferred_global_request_id
=
254 params
.transferred_global_request_id
;
255 web_contents()->GetController().LoadURLWithParams(load_url_params
);
256 return web_contents();
258 case NEW_FOREGROUND_TAB
:
259 case NEW_BACKGROUND_TAB
:
262 Activity
* activity
= ActivityFactory::Get()->CreateWebActivity(
263 browser_context(), base::string16(), params
.url
);
264 Activity::Show(activity
);
270 // nullptr is returned if the URL wasn't opened immediately.
274 bool CanOverscrollContent() const override
{
275 const std::string value
= CommandLine::ForCurrentProcess()->
276 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation
);
280 void OverscrollUpdate(float delta_y
) override
{
281 overscroll_y_
= delta_y
;
282 if (overscroll_y_
> kDistanceShowReloadMessage
) {
283 if (!reload_message_
)
284 CreateReloadMessage();
285 reload_message_
->Show();
286 float opacity
= 1.0f
;
287 if (overscroll_y_
< kDistanceReload
) {
288 opacity
= (overscroll_y_
- kDistanceShowReloadMessage
) /
289 (kDistanceReload
- kDistanceShowReloadMessage
);
291 reload_message_
->GetLayer()->SetOpacity(opacity
);
292 } else if (reload_message_
) {
293 reload_message_
->Hide();
297 void OverscrollComplete() override
{
298 if (overscroll_y_
>= kDistanceReload
)
299 GetWebContents()->GetController().Reload(false);
301 reload_message_
->Hide();
305 void AddNewContents(content::WebContents
* source
,
306 content::WebContents
* new_contents
,
307 WindowOpenDisposition disposition
,
308 const gfx::Rect
& initial_pos
,
310 bool* was_blocked
) override
{
312 ActivityFactory::Get()->CreateWebActivity(new_contents
);
313 Activity::Show(activity
);
316 bool PreHandleKeyboardEvent(content::WebContents
* source
,
317 const content::NativeWebKeyboardEvent
& event
,
318 bool* is_keyboard_shortcut
) override
{
319 return controller_
->PreHandleKeyboardEvent(
320 source
, event
, is_keyboard_shortcut
);
323 void HandleKeyboardEvent(
324 content::WebContents
* source
,
325 const content::NativeWebKeyboardEvent
& event
) override
{
326 controller_
->HandleKeyboardEvent(source
, event
);
329 void ToggleFullscreenModeForTab(content::WebContents
* web_contents
,
330 bool enter_fullscreen
) override
{
331 fullscreen_
= enter_fullscreen
;
332 GetWidget()->SetFullscreen(fullscreen_
);
335 bool IsFullscreenForTabOrPending(
336 const content::WebContents
* web_contents
) const override
{
340 void LoadingStateChanged(content::WebContents
* source
,
341 bool to_different_document
) override
{
342 bool has_stopped
= source
== nullptr || !source
->IsLoading();
343 LoadProgressChanged(source
, has_stopped
? 1 : 0);
346 void LoadProgressChanged(content::WebContents
* source
,
347 double progress
) override
{
351 if (!progress_bar_
) {
353 source
->GetNativeView()->layer()->Add(progress_bar_
.get());
355 progress_bar_
->SetBounds(gfx::Rect(
356 0, 0, progress
* progress_bar_
->parent()->bounds().width(), 3));
360 ui::ScopedLayerAnimationSettings
settings(progress_bar_
->GetAnimator());
361 settings
.SetTweenType(gfx::Tween::EASE_IN
);
362 ui::Layer
* layer
= progress_bar_
.get();
363 settings
.AddObserver(new ui::ClosureAnimationObserver(
364 base::Bind(&base::DeletePointer
<ui::Layer
>, progress_bar_
.release())));
365 layer
->SetOpacity(0.f
);
368 content::JavaScriptDialogManager
* GetJavaScriptDialogManager() override
{
369 return GetJavaScriptDialogManagerInstance();
372 content::ColorChooser
* OpenColorChooser(
373 content::WebContents
* web_contents
,
375 const std::vector
<content::ColorSuggestion
>& suggestions
) override
{
376 return athena::OpenColorChooser(web_contents
, color
, suggestions
);
379 // Called when a file selection is to be done.
380 void RunFileChooser(content::WebContents
* web_contents
,
381 const content::FileChooserParams
& params
) override
{
382 return athena::OpenFileChooser(web_contents
, params
);
385 void CloseContents(content::WebContents
* contents
) override
{
386 Activity::Delete(owner_activity_
);
390 void CreateProgressBar() {
391 CHECK(!progress_bar_
);
392 progress_bar_
.reset(new ui::Layer(ui::LAYER_SOLID_COLOR
));
393 progress_bar_
->SetColor(SkColorSetRGB(0x17, 0x59, 0xcd));
396 void CreateReloadMessage() {
397 CHECK(!reload_message_
);
398 reload_message_
.reset(new views::Widget
);
399 views::Widget::InitParams
params(views::Widget::InitParams::TYPE_CONTROL
);
400 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
401 params
.parent
= GetWidget()->GetNativeView();
402 reload_message_
->Init(params
);
404 views::Label
* label
= new views::Label(
405 l10n_util::GetStringUTF16(IDS_ATHENA_PULL_TO_RELOAD_MESSAGE
));
406 label
->SetBackgroundColor(SK_ColorGRAY
);
407 label
->set_background(
408 views::Background::CreateSolidBackground(SK_ColorGRAY
));
410 reload_message_
->SetContentsView(label
);
411 reload_message_
->SetBounds(ConvertRectToWidget(
412 gfx::Rect(0, 0, width(), label
->GetPreferredSize().height())));
415 scoped_ptr
<WebActivityController
> controller_
;
417 Activity
* const owner_activity_
;
419 // If the activity got evicted, this is the web content which holds the known
420 // state of the content before eviction.
421 scoped_ptr
<content::WebContents
> evicted_web_contents_
;
423 scoped_ptr
<ui::Layer
> progress_bar_
;
425 scoped_ptr
<views::Widget
> reload_message_
;
427 // TODO(oshima): Find out if we should support window fullscreen.
428 // It may still useful when a user is in split mode.
431 // The distance that the user has overscrolled vertically.
434 DISALLOW_COPY_AND_ASSIGN(AthenaWebView
);
437 WebActivity::WebActivity(content::BrowserContext
* browser_context
,
438 const base::string16
& title
,
440 : browser_context_(browser_context
),
441 web_view_(new AthenaWebView(browser_context
, this)),
443 title_color_(kDefaultTitleColor
),
444 current_state_(ACTIVITY_UNLOADED
),
445 activity_view_(nullptr),
446 weak_ptr_factory_(this) {
447 // Order is important. The web activity helpers must be attached prior to the
448 // RenderView being created.
449 SetCurrentState(ACTIVITY_INVISIBLE
);
450 web_view_
->LoadInitialURL(url
);
453 WebActivity::WebActivity(content::WebContents
* contents
)
454 : browser_context_(contents
->GetBrowserContext()),
455 web_view_(new AthenaWebView(contents
, this)),
456 title_color_(kDefaultTitleColor
),
457 current_state_(ACTIVITY_UNLOADED
),
458 activity_view_(nullptr),
459 weak_ptr_factory_(this) {
460 // If the activity was created as a result of
461 // WebContentsDelegate::AddNewContents(), web activity helpers may not be
462 // created prior to the RenderView being created. Desktop Chrome has a
464 SetCurrentState(ACTIVITY_INVISIBLE
);
467 WebActivity::~WebActivity() {
468 // It is not required to change the activity state to UNLOADED - unless we
469 // would add state observers.
472 ActivityViewModel
* WebActivity::GetActivityViewModel() {
476 void WebActivity::SetCurrentState(Activity::ActivityState state
) {
477 DCHECK_NE(state
, current_state_
);
478 if (current_state_
== ACTIVITY_UNLOADED
) {
479 web_view_
->AttachHelpers();
480 if (web_view_
->IsContentEvicted())
481 web_view_
->ReloadEvictedContent();
482 Observe(web_view_
->GetWebContents());
486 case ACTIVITY_VISIBLE
:
489 case ACTIVITY_INVISIBLE
:
490 if (current_state_
== ACTIVITY_VISIBLE
)
494 case ACTIVITY_BACKGROUND_LOW_PRIORITY
:
495 DCHECK(ACTIVITY_VISIBLE
== current_state_
||
496 ACTIVITY_INVISIBLE
== current_state_
);
497 // TODO(skuhne): Do this.
499 case ACTIVITY_PERSISTENT
:
500 DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY
, current_state_
);
501 // TODO(skuhne): Do this. As soon as the new resource management is
502 // agreed upon - or remove otherwise.
504 case ACTIVITY_UNLOADED
:
505 DCHECK_NE(ACTIVITY_UNLOADED
, current_state_
);
507 content_proxy_
->ContentWillUnload();
509 web_view_
->EvictContent();
512 // Remember the last requested state.
513 current_state_
= state
;
516 Activity::ActivityState
WebActivity::GetCurrentState() {
517 // If the content is evicted, the state has to be UNLOADED.
518 DCHECK(!web_view_
->IsContentEvicted() ||
519 current_state_
== ACTIVITY_UNLOADED
);
520 return current_state_
;
523 bool WebActivity::IsVisible() {
524 return web_view_
->visible() && current_state_
!= ACTIVITY_UNLOADED
;
527 Activity::ActivityMediaState
WebActivity::GetMediaState() {
528 return current_state_
== ACTIVITY_UNLOADED
?
529 Activity::ACTIVITY_MEDIA_STATE_NONE
:
530 GetActivityMediaState(GetWebContents());
533 aura::Window
* WebActivity::GetWindow() {
534 return web_view_
->GetWidget() ? web_view_
->GetWidget()->GetNativeWindow()
538 content::WebContents
* WebActivity::GetWebContents() {
539 return web_view_
->GetWebContents();
542 void WebActivity::Init() {
543 web_view_
->InstallAccelerators();
546 SkColor
WebActivity::GetRepresentativeColor() const {
550 base::string16
WebActivity::GetTitle() const {
553 const base::string16
& title
= web_view_
->GetWebContents()->GetTitle();
556 return base::UTF8ToUTF16(web_view_
->GetWebContents()->GetVisibleURL().host());
559 gfx::ImageSkia
WebActivity::GetIcon() const {
563 void WebActivity::SetActivityView(ActivityView
* view
) {
564 DCHECK(!activity_view_
);
565 activity_view_
= view
;
568 bool WebActivity::UsesFrame() const {
572 views::View
* WebActivity::GetContentsView() {
576 gfx::ImageSkia
WebActivity::GetOverviewModeImage() {
577 if (content_proxy_
.get())
578 return content_proxy_
->GetContentImage();
579 return gfx::ImageSkia();
582 void WebActivity::PrepareContentsForOverview() {
583 // Turn on fast resizing to avoid re-laying out the web contents when
584 // entering / exiting overview mode and the content is visible.
585 if (!content_proxy_
.get())
586 web_view_
->SetFastResize(true);
589 void WebActivity::ResetContentsView() {
590 // Turn on fast resizing to avoid re-laying out the web contents when
591 // entering / exiting overview mode and the content is visible.
592 if (!content_proxy_
.get()) {
593 web_view_
->SetFastResize(false);
598 void WebActivity::TitleWasSet(content::NavigationEntry
* entry
,
601 activity_view_
->UpdateTitle();
604 void WebActivity::DidNavigateMainFrame(
605 const content::LoadCommittedDetails
& details
,
606 const content::FrameNavigateParams
& params
) {
607 // Prevent old image requests from calling back to OnDidDownloadFavicon().
608 weak_ptr_factory_
.InvalidateWeakPtrs();
610 icon_
= gfx::ImageSkia();
612 activity_view_
->UpdateIcon();
615 void WebActivity::DidUpdateFaviconURL(
616 const std::vector
<content::FaviconURL
>& candidates
) {
617 // Pick an arbitrary favicon of type FAVICON to use.
618 // TODO(pkotwicz): Do something better once the favicon code is componentized.
619 // (crbug.com/401997)
620 weak_ptr_factory_
.InvalidateWeakPtrs();
621 for (size_t i
= 0; i
< candidates
.size(); ++i
) {
622 if (candidates
[i
].icon_type
== content::FaviconURL::FAVICON
) {
623 web_view_
->GetWebContents()->DownloadImage(
624 candidates
[i
].icon_url
,
627 base::Bind(&WebActivity::OnDidDownloadFavicon
,
628 weak_ptr_factory_
.GetWeakPtr()));
634 void WebActivity::OnDidDownloadFavicon(
636 int http_status_code
,
638 const std::vector
<SkBitmap
>& bitmaps
,
639 const std::vector
<gfx::Size
>& original_bitmap_sizes
) {
640 icon_
= CreateFaviconImageSkia(
641 bitmaps
, original_bitmap_sizes
, kIconSize
, nullptr);
643 activity_view_
->UpdateIcon();
646 void WebActivity::DidChangeThemeColor(SkColor theme_color
) {
647 title_color_
= theme_color
;
649 activity_view_
->UpdateRepresentativeColor();
652 void WebActivity::HideContentProxy() {
653 if (content_proxy_
.get())
654 content_proxy_
.reset(nullptr);
657 void WebActivity::ShowContentProxy() {
658 if (!content_proxy_
.get())
659 content_proxy_
.reset(new ContentProxy(web_view_
));
662 } // namespace athena