athena: Add an accelerator to abort loading on escape.
[chromium-blink-merge.git] / athena / content / web_activity.cc
blobcadf1122daa156d3b4c354bb391453f69c558deb
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/input/public/accelerator_manager.h"
10 #include "base/bind.h"
11 #include "base/strings/utf_string_conversions.h"
12 #include "content/public/browser/native_web_keyboard_event.h"
13 #include "content/public/browser/navigation_controller.h"
14 #include "content/public/browser/web_contents.h"
15 #include "content/public/browser/web_contents_delegate.h"
16 #include "ui/aura/window.h"
17 #include "ui/compositor/closure_animation_observer.h"
18 #include "ui/compositor/scoped_layer_animation_settings.h"
19 #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
20 #include "ui/views/controls/webview/webview.h"
21 #include "ui/views/focus/focus_manager.h"
22 #include "ui/views/widget/widget.h"
24 namespace athena {
25 namespace {
27 class WebActivityController : public AcceleratorHandler {
28 public:
29 enum Command {
30 CMD_BACK,
31 CMD_FORWARD,
32 CMD_RELOAD,
33 CMD_RELOAD_IGNORE_CACHE,
34 CMD_CLOSE,
35 CMD_STOP,
38 explicit WebActivityController(views::WebView* web_view)
39 : web_view_(web_view), reserved_accelerator_enabled_(true) {}
40 virtual ~WebActivityController() {}
42 // Installs accelerators for web activity.
43 void InstallAccelerators() {
44 accelerator_manager_ = AcceleratorManager::CreateForFocusManager(
45 web_view_->GetFocusManager()).Pass();
46 const AcceleratorData accelerator_data[] = {
47 {TRIGGER_ON_PRESS, ui::VKEY_R, ui::EF_CONTROL_DOWN, CMD_RELOAD,
48 AF_NONE},
49 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_REFRESH, ui::EF_NONE, CMD_RELOAD,
50 AF_NONE},
51 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_REFRESH, ui::EF_CONTROL_DOWN,
52 CMD_RELOAD_IGNORE_CACHE, AF_NONE},
53 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_FORWARD, ui::EF_NONE, CMD_FORWARD,
54 AF_NONE},
55 {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_BACK, ui::EF_NONE, CMD_BACK,
56 AF_NONE},
57 {TRIGGER_ON_PRESS, ui::VKEY_W, ui::EF_CONTROL_DOWN, CMD_CLOSE, AF_NONE},
58 {TRIGGER_ON_PRESS, ui::VKEY_ESCAPE, ui::EF_NONE, CMD_STOP, AF_NONE},
60 accelerator_manager_->RegisterAccelerators(
61 accelerator_data, arraysize(accelerator_data), this);
64 // Methods that are called before and after key events are consumed by the web
65 // contents.
66 // See the documentation in WebContentsDelegate: for more details.
67 bool PreHandleKeyboardEvent(content::WebContents* source,
68 const content::NativeWebKeyboardEvent& event,
69 bool* is_keyboard_shortcut) {
70 ui::Accelerator accelerator(
71 static_cast<ui::KeyboardCode>(event.windowsKeyCode),
72 content::GetModifiersFromNativeWebKeyboardEvent(event));
73 if (event.type == blink::WebInputEvent::KeyUp)
74 accelerator.set_type(ui::ET_KEY_RELEASED);
76 if (reserved_accelerator_enabled_ &&
77 accelerator_manager_->IsRegistered(accelerator, AF_RESERVED)) {
78 return web_view_->GetFocusManager()->ProcessAccelerator(accelerator);
80 *is_keyboard_shortcut =
81 accelerator_manager_->IsRegistered(accelerator, AF_NONE);
82 return false;
85 void HandleKeyboardEvent(content::WebContents* source,
86 const content::NativeWebKeyboardEvent& event) {
87 unhandled_keyboard_event_handler_.HandleKeyboardEvent(
88 event, web_view_->GetFocusManager());
91 private:
92 // AcceleratorHandler:
93 virtual bool IsCommandEnabled(int command_id) const OVERRIDE {
94 switch (command_id) {
95 case CMD_RELOAD:
96 case CMD_RELOAD_IGNORE_CACHE:
97 return true;
98 case CMD_BACK:
99 return web_view_->GetWebContents()->GetController().CanGoBack();
100 case CMD_FORWARD:
101 return web_view_->GetWebContents()->GetController().CanGoForward();
102 case CMD_CLOSE:
103 // TODO(oshima): check onbeforeunload handler.
104 return true;
105 case CMD_STOP:
106 return web_view_->GetWebContents()->IsLoading();
108 return false;
111 virtual bool OnAcceleratorFired(int command_id,
112 const ui::Accelerator& accelerator) OVERRIDE {
113 switch (command_id) {
114 case CMD_RELOAD:
115 web_view_->GetWebContents()->GetController().Reload(false);
116 return true;
117 case CMD_RELOAD_IGNORE_CACHE:
118 web_view_->GetWebContents()->GetController().ReloadIgnoringCache(false);
119 return true;
120 case CMD_BACK:
121 web_view_->GetWebContents()->GetController().GoBack();
122 return true;
123 case CMD_FORWARD:
124 web_view_->GetWebContents()->GetController().GoForward();
125 return true;
126 case CMD_CLOSE:
127 web_view_->GetWidget()->Close();
128 return true;
129 case CMD_STOP:
130 web_view_->GetWebContents()->Stop();
131 return true;
133 return false;
136 views::WebView* web_view_;
137 bool reserved_accelerator_enabled_;
138 scoped_ptr<AcceleratorManager> accelerator_manager_;
139 views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
141 DISALLOW_COPY_AND_ASSIGN(WebActivityController);
144 const SkColor kDefaultTitleColor = SkColorSetRGB(0xf2, 0xf2, 0xf2);
145 const SkColor kDefaultUnavailableColor = SkColorSetRGB(0xbb, 0x77, 0x77);
147 } // namespace
149 // A web view for athena's web activity. Note that AthenaWebView will create its
150 // own content so that it can eject and reload it.
151 class AthenaWebView : public views::WebView {
152 public:
153 AthenaWebView(content::BrowserContext* context)
154 : views::WebView(context), controller_(new WebActivityController(this)),
155 fullscreen_(false) {
156 SetEmbedFullscreenWidgetMode(true);
157 // TODO(skuhne): Add content observer to detect renderer crash and set
158 // content status to unloaded if that happens.
161 AthenaWebView(content::WebContents* web_contents)
162 : views::WebView(web_contents->GetBrowserContext()),
163 controller_(new WebActivityController(this)) {
164 scoped_ptr<content::WebContents> old_contents(
165 SwapWebContents(scoped_ptr<content::WebContents>(web_contents)));
168 virtual ~AthenaWebView() {}
170 void InstallAccelerators() { controller_->InstallAccelerators(); }
172 void EvictContent() {
173 scoped_ptr<content::WebContents> old_contents(SwapWebContents(
174 scoped_ptr<content::WebContents>(content::WebContents::Create(
175 content::WebContents::CreateParams(browser_context())))));
176 evicted_web_contents_.reset(
177 content::WebContents::Create(content::WebContents::CreateParams(
178 old_contents->GetBrowserContext())));
179 evicted_web_contents_->GetController().CopyStateFrom(
180 old_contents->GetController());
181 // As soon as the new contents becomes visible, it should reload.
182 // TODO(skuhne): This breaks script connections with other activities.
183 // Even though this is the same technique as used by the TabStripModel,
184 // we might want to address this cleaner since we are more likely to
185 // run into this state. by unloading.
188 void ReloadContent() {
189 CHECK(evicted_web_contents_.get());
190 scoped_ptr<content::WebContents> replaced_contents(SwapWebContents(
191 evicted_web_contents_.Pass()));
194 // Check if the content got evicted.
195 const bool IsContentEvicted() { return !!evicted_web_contents_.get(); }
197 // content::WebContentsDelegate:
198 virtual content::WebContents* OpenURLFromTab(
199 content::WebContents* source,
200 const content::OpenURLParams& params) OVERRIDE {
201 switch(params.disposition) {
202 case CURRENT_TAB: {
203 DCHECK(source == web_contents());
204 content::NavigationController::LoadURLParams load_url_params(
205 params.url);
206 load_url_params.referrer = params.referrer;
207 load_url_params.frame_tree_node_id = params.frame_tree_node_id;
208 load_url_params.transition_type = params.transition;
209 load_url_params.extra_headers = params.extra_headers;
210 load_url_params.should_replace_current_entry =
211 params.should_replace_current_entry;
212 load_url_params.is_renderer_initiated = params.is_renderer_initiated;
213 load_url_params.transferred_global_request_id =
214 params.transferred_global_request_id;
215 web_contents()->GetController().LoadURLWithParams(load_url_params);
216 return web_contents();
218 case NEW_FOREGROUND_TAB:
219 case NEW_BACKGROUND_TAB:
220 case NEW_POPUP:
221 case NEW_WINDOW: {
222 ActivityManager::Get()->AddActivity(
223 ActivityFactory::Get()->CreateWebActivity(browser_context(),
224 params.url));
225 break;
227 default:
228 break;
230 // NULL is returned if the URL wasn't opened immediately.
231 return NULL;
234 virtual void AddNewContents(content::WebContents* source,
235 content::WebContents* new_contents,
236 WindowOpenDisposition disposition,
237 const gfx::Rect& initial_pos,
238 bool user_gesture,
239 bool* was_blocked) OVERRIDE {
240 ActivityManager::Get()->AddActivity(
241 new WebActivity(new AthenaWebView(new_contents)));
244 virtual bool PreHandleKeyboardEvent(
245 content::WebContents* source,
246 const content::NativeWebKeyboardEvent& event,
247 bool* is_keyboard_shortcut) OVERRIDE {
248 return controller_->PreHandleKeyboardEvent(
249 source, event, is_keyboard_shortcut);
252 virtual void HandleKeyboardEvent(
253 content::WebContents* source,
254 const content::NativeWebKeyboardEvent& event) OVERRIDE {
255 controller_->HandleKeyboardEvent(source, event);
258 virtual void ToggleFullscreenModeForTab(content::WebContents* web_contents,
259 bool enter_fullscreen) OVERRIDE {
260 fullscreen_ = enter_fullscreen;
261 GetWidget()->SetFullscreen(fullscreen_);
264 virtual bool IsFullscreenForTabOrPending(
265 const content::WebContents* web_contents) const OVERRIDE {
266 return fullscreen_;
269 virtual void LoadingStateChanged(content::WebContents* source,
270 bool to_different_document) OVERRIDE {
271 bool has_stopped = source == NULL || !source->IsLoading();
272 LoadProgressChanged(source, has_stopped ? 1 : 0);
275 virtual void LoadProgressChanged(content::WebContents* source,
276 double progress) OVERRIDE {
277 if (!progress)
278 return;
280 if (!progress_bar_) {
281 CreateProgressBar();
282 source->GetNativeView()->layer()->Add(progress_bar_.get());
284 progress_bar_->SetBounds(gfx::Rect(
285 0, 0, progress * progress_bar_->parent()->bounds().width(), 3));
286 if (progress < 1)
287 return;
289 ui::ScopedLayerAnimationSettings settings(progress_bar_->GetAnimator());
290 settings.SetTweenType(gfx::Tween::EASE_IN);
291 ui::Layer* layer = progress_bar_.get();
292 settings.AddObserver(new ui::ClosureAnimationObserver(
293 base::Bind(&base::DeletePointer<ui::Layer>, progress_bar_.release())));
294 layer->SetOpacity(0.f);
297 private:
298 void CreateProgressBar() {
299 CHECK(!progress_bar_);
300 progress_bar_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
301 progress_bar_->SetColor(SkColorSetRGB(0x00, 0xb0, 0xc7));
304 scoped_ptr<WebActivityController> controller_;
306 // If the activity got evicted, this is the web content which holds the known
307 // state of the content before eviction.
308 scoped_ptr<content::WebContents> evicted_web_contents_;
310 scoped_ptr<ui::Layer> progress_bar_;
312 // TODO(oshima): Find out if we should support window fullscreen.
313 // It may still useful when a user is in split mode.
314 bool fullscreen_;
316 DISALLOW_COPY_AND_ASSIGN(AthenaWebView);
319 WebActivity::WebActivity(content::BrowserContext* browser_context,
320 const GURL& url)
321 : browser_context_(browser_context),
322 url_(url),
323 web_view_(NULL),
324 title_color_(kDefaultTitleColor),
325 current_state_(ACTIVITY_UNLOADED) {
328 WebActivity::WebActivity(AthenaWebView* web_view)
329 : browser_context_(web_view->browser_context()),
330 url_(web_view->GetWebContents()->GetURL()),
331 web_view_(web_view),
332 current_state_(ACTIVITY_UNLOADED) {
333 // Transition to state ACTIVITY_INVISIBLE to perform the same setup steps
334 // as on new activities (namely adding a WebContentsObserver).
335 SetCurrentState(ACTIVITY_INVISIBLE);
338 WebActivity::~WebActivity() {
339 // It is not required to change the activity state to UNLOADED - unless we
340 // would add state observers.
343 ActivityViewModel* WebActivity::GetActivityViewModel() {
344 return this;
347 void WebActivity::SetCurrentState(Activity::ActivityState state) {
348 switch (state) {
349 case ACTIVITY_VISIBLE:
350 // Fall through (for the moment).
351 case ACTIVITY_INVISIBLE:
352 // By clearing the overview mode image we allow the content to be shown.
353 overview_mode_image_ = gfx::ImageSkia();
354 if (web_view_->IsContentEvicted()) {
355 DCHECK_EQ(ACTIVITY_UNLOADED, current_state_);
356 web_view_->ReloadContent();
358 Observe(web_view_->GetWebContents());
359 break;
360 case ACTIVITY_BACKGROUND_LOW_PRIORITY:
361 DCHECK(ACTIVITY_VISIBLE == current_state_ ||
362 ACTIVITY_INVISIBLE == current_state_);
363 // TODO(skuhne): Do this.
364 break;
365 case ACTIVITY_PERSISTENT:
366 DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY, current_state_);
367 // TODO(skuhne): Do this. As soon as the new resource management is
368 // agreed upon - or remove otherwise.
369 break;
370 case ACTIVITY_UNLOADED:
371 DCHECK_NE(ACTIVITY_UNLOADED, current_state_);
372 Observe(NULL);
373 web_view_->EvictContent();
374 break;
376 // Remember the last requested state.
377 current_state_ = state;
380 Activity::ActivityState WebActivity::GetCurrentState() {
381 if (!web_view_ || web_view_->IsContentEvicted()) {
382 DCHECK_EQ(ACTIVITY_UNLOADED, current_state_);
383 return ACTIVITY_UNLOADED;
385 // TODO(skuhne): This should be controlled by an observer and should not
386 // reside here.
387 if (IsVisible() && current_state_ != ACTIVITY_VISIBLE)
388 SetCurrentState(ACTIVITY_VISIBLE);
389 // Note: If the activity is not visible it does not necessarily mean that it
390 // does not have GPU compositor resources (yet).
392 return current_state_;
395 bool WebActivity::IsVisible() {
396 return web_view_ &&
397 web_view_->IsDrawn() &&
398 current_state_ != ACTIVITY_UNLOADED &&
399 GetWindow() &&
400 GetWindow()->IsVisible();
403 Activity::ActivityMediaState WebActivity::GetMediaState() {
404 // TODO(skuhne): The function GetTabMediaStateForContents(WebContents),
405 // and the AudioStreamMonitor needs to be moved from Chrome into contents to
406 // make it more modular and so that we can use it from here.
407 return Activity::ACTIVITY_MEDIA_STATE_NONE;
410 aura::Window* WebActivity::GetWindow() {
411 return !web_view_ ? NULL : web_view_->GetWidget()->GetNativeWindow();
414 void WebActivity::Init() {
415 DCHECK(web_view_);
416 web_view_->InstallAccelerators();
419 SkColor WebActivity::GetRepresentativeColor() const {
420 // TODO(sad): Compute the color from the favicon.
421 return web_view_ ? title_color_ : kDefaultUnavailableColor;
424 base::string16 WebActivity::GetTitle() const {
425 return web_view_ ? base::UTF8ToUTF16(
426 web_view_->GetWebContents()->GetVisibleURL().host())
427 : base::string16();
430 bool WebActivity::UsesFrame() const {
431 return true;
434 views::View* WebActivity::GetContentsView() {
435 if (!web_view_) {
436 web_view_ = new AthenaWebView(browser_context_);
437 web_view_->LoadInitialURL(url_);
438 SetCurrentState(ACTIVITY_INVISIBLE);
439 // Reset the overview mode image.
440 overview_mode_image_ = gfx::ImageSkia();
442 return web_view_;
445 void WebActivity::CreateOverviewModeImage() {
446 // TODO(skuhne): Create an overview.
449 gfx::ImageSkia WebActivity::GetOverviewModeImage() {
450 return overview_mode_image_;
453 void WebActivity::TitleWasSet(content::NavigationEntry* entry,
454 bool explicit_set) {
455 ActivityManager::Get()->UpdateActivity(this);
458 void WebActivity::DidUpdateFaviconURL(
459 const std::vector<content::FaviconURL>& candidates) {
460 ActivityManager::Get()->UpdateActivity(this);
463 void WebActivity::DidChangeThemeColor(SkColor theme_color) {
464 title_color_ = theme_color;
467 } // namespace athena