Enable Enhanced Bookmark on Android Tablet
[chromium-blink-merge.git] / athena / content / web_activity.cc
blobd5f1c296dcb9971ace05f4f99af91e2fcc336560
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"
38 namespace athena {
39 namespace {
41 class WebActivityController : public AcceleratorHandler {
42 public:
43 enum Command {
44 CMD_BACK,
45 CMD_FORWARD,
46 CMD_RELOAD,
47 CMD_RELOAD_IGNORE_CACHE,
48 CMD_CLOSE,
49 CMD_STOP,
52 explicit WebActivityController(WebActivity* owner_activity,
53 views::WebView* web_view)
54 : owner_activity_(owner_activity),
55 web_view_(web_view),
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,
65 AF_NONE},
66 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_REFRESH, ui::EF_NONE, CMD_RELOAD,
67 AF_NONE},
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,
71 AF_NONE},
72 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_BACK, ui::EF_NONE, CMD_BACK,
73 AF_NONE},
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
82 // contents.
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);
96 return false;
99 void HandleKeyboardEvent(content::WebContents* source,
100 const content::NativeWebKeyboardEvent& event) {
101 unhandled_keyboard_event_handler_.HandleKeyboardEvent(
102 event, web_view_->GetFocusManager());
105 private:
106 // AcceleratorHandler:
107 bool IsCommandEnabled(int command_id) const override {
108 switch (command_id) {
109 case CMD_RELOAD:
110 case CMD_RELOAD_IGNORE_CACHE:
111 return true;
112 case CMD_BACK:
113 return web_view_->GetWebContents()->GetController().CanGoBack();
114 case CMD_FORWARD:
115 return web_view_->GetWebContents()->GetController().CanGoForward();
116 case CMD_CLOSE:
117 // TODO(oshima): check onbeforeunload handler.
118 return true;
119 case CMD_STOP:
120 return web_view_->GetWebContents()->IsLoading();
122 return false;
125 bool OnAcceleratorFired(int command_id,
126 const ui::Accelerator& accelerator) override {
127 switch (command_id) {
128 case CMD_RELOAD:
129 web_view_->GetWebContents()->GetController().Reload(false);
130 return true;
131 case CMD_RELOAD_IGNORE_CACHE:
132 web_view_->GetWebContents()->GetController().ReloadIgnoringCache(false);
133 return true;
134 case CMD_BACK:
135 web_view_->GetWebContents()->GetController().GoBack();
136 return true;
137 case CMD_FORWARD:
138 web_view_->GetWebContents()->GetController().GoForward();
139 return true;
140 case CMD_CLOSE:
141 Activity::Delete(owner_activity_);
142 return true;
143 case CMD_STOP:
144 web_view_->GetWebContents()->Stop();
145 return true;
147 return false;
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;
164 } // namespace
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 {
169 public:
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),
175 fullscreen_(false),
176 overscroll_y_(0) {
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
201 // the content.
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
225 // being created.
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) {
239 case CURRENT_TAB: {
240 DCHECK(source == web_contents());
241 content::NavigationController::LoadURLParams load_url_params(
242 params.url);
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:
257 case NEW_POPUP:
258 case NEW_WINDOW: {
259 Activity* activity = ActivityFactory::Get()->CreateWebActivity(
260 browser_context(), base::string16(), params.url);
261 Activity::Show(activity);
262 break;
264 default:
265 break;
267 // nullptr is returned if the URL wasn't opened immediately.
268 return nullptr;
271 bool CanOverscrollContent() const override {
272 const std::string value = CommandLine::ForCurrentProcess()->
273 GetSwitchValueASCII(switches::kOverscrollHistoryNavigation);
274 return value != "0";
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);
297 if (reload_message_)
298 reload_message_->Hide();
299 overscroll_y_ = 0;
302 void AddNewContents(content::WebContents* source,
303 content::WebContents* new_contents,
304 WindowOpenDisposition disposition,
305 const gfx::Rect& initial_pos,
306 bool user_gesture,
307 bool* was_blocked) override {
308 Activity* activity =
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 {
334 return fullscreen_;
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 {
345 if (!progress)
346 return;
348 if (!progress_bar_) {
349 CreateProgressBar();
350 source->GetNativeView()->layer()->Add(progress_bar_.get());
352 progress_bar_->SetBounds(gfx::Rect(
353 0, 0, progress * progress_bar_->parent()->bounds().width(), 3));
354 if (progress < 1)
355 return;
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,
372 SkColor color,
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_);
387 private:
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.
427 bool fullscreen_;
429 // The distance that the user has overscrolled vertically.
430 float overscroll_y_;
432 DISALLOW_COPY_AND_ASSIGN(AthenaWebView);
435 WebActivity::WebActivity(content::BrowserContext* browser_context,
436 const base::string16& title,
437 const GURL& url)
438 : browser_context_(browser_context),
439 web_view_(new AthenaWebView(browser_context, this)),
440 title_(title),
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
461 // similar problem.
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() {
471 return this;
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());
483 switch (state) {
484 case ACTIVITY_VISIBLE:
485 HideContentProxy();
486 break;
487 case ACTIVITY_INVISIBLE:
488 if (current_state_ == ACTIVITY_VISIBLE)
489 ShowContentProxy();
491 break;
492 case ACTIVITY_BACKGROUND_LOW_PRIORITY:
493 DCHECK(ACTIVITY_VISIBLE == current_state_ ||
494 ACTIVITY_INVISIBLE == current_state_);
495 // TODO(skuhne): Do this.
496 break;
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.
501 break;
502 case ACTIVITY_UNLOADED:
503 DCHECK_NE(ACTIVITY_UNLOADED, current_state_);
504 if (content_proxy_)
505 content_proxy_->ContentWillUnload();
506 Observe(nullptr);
507 web_view_->EvictContent();
508 break;
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()
533 : nullptr;
536 content::WebContents* WebActivity::GetWebContents() {
537 return web_view_->GetWebContents();
540 void WebActivity::Init() {
541 web_view_->InstallAccelerators();
544 SkColor WebActivity::GetRepresentativeColor() const {
545 return title_color_;
548 base::string16 WebActivity::GetTitle() const {
549 if (!title_.empty())
550 return title_;
551 const base::string16& title = web_view_->GetWebContents()->GetTitle();
552 if (!title.empty())
553 return title;
554 return base::UTF8ToUTF16(web_view_->GetWebContents()->GetVisibleURL().host());
557 gfx::ImageSkia WebActivity::GetIcon() const {
558 return icon_;
561 void WebActivity::SetActivityView(ActivityView* view) {
562 DCHECK(!activity_view_);
563 activity_view_ = view;
566 bool WebActivity::UsesFrame() const {
567 return true;
570 views::View* WebActivity::GetContentsView() {
571 return web_view_;
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);
592 web_view_->Layout();
596 void WebActivity::TitleWasSet(content::NavigationEntry* entry,
597 bool explicit_set) {
598 if (activity_view_)
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();
609 if (activity_view_)
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,
623 true,
625 base::Bind(&WebActivity::OnDidDownloadFavicon,
626 weak_ptr_factory_.GetWeakPtr()));
627 break;
632 void WebActivity::OnDidDownloadFavicon(
633 int id,
634 int http_status_code,
635 const GURL& url,
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);
640 if (activity_view_)
641 activity_view_->UpdateIcon();
644 void WebActivity::DidChangeThemeColor(SkColor theme_color) {
645 title_color_ = theme_color;
646 if (activity_view_)
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