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/strings/utf_string_conversions.h"
11 #include "content/public/browser/native_web_keyboard_event.h"
12 #include "content/public/browser/navigation_controller.h"
13 #include "content/public/browser/web_contents.h"
14 #include "content/public/browser/web_contents_delegate.h"
15 #include "ui/aura/window.h"
16 #include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
17 #include "ui/views/controls/webview/webview.h"
18 #include "ui/views/focus/focus_manager.h"
19 #include "ui/views/widget/widget.h"
24 class WebActivityController
: public AcceleratorHandler
{
30 CMD_RELOAD_IGNORE_CACHE
,
34 explicit WebActivityController(views::WebView
* web_view
)
35 : web_view_(web_view
), reserved_accelerator_enabled_(true) {}
36 virtual ~WebActivityController() {}
38 // Installs accelerators for web activity.
39 void InstallAccelerators() {
40 accelerator_manager_
= AcceleratorManager::CreateForFocusManager(
41 web_view_
->GetFocusManager()).Pass();
42 const AcceleratorData accelerator_data
[] = {
43 {TRIGGER_ON_PRESS
, ui::VKEY_R
, ui::EF_CONTROL_DOWN
, CMD_RELOAD
,
45 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_REFRESH
, ui::EF_NONE
, CMD_RELOAD
,
47 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_REFRESH
, ui::EF_CONTROL_DOWN
,
48 CMD_RELOAD_IGNORE_CACHE
, AF_NONE
},
49 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_FORWARD
, ui::EF_NONE
, CMD_FORWARD
,
51 {TRIGGER_ON_PRESS
, ui::VKEY_BROWSER_BACK
, ui::EF_NONE
, CMD_BACK
,
53 {TRIGGER_ON_PRESS
, ui::VKEY_W
, ui::EF_CONTROL_DOWN
, CMD_CLOSE
, AF_NONE
},
55 accelerator_manager_
->RegisterAccelerators(
56 accelerator_data
, arraysize(accelerator_data
), this);
59 // Methods that are called before and after key events are consumed by the web
61 // See the documentation in WebContentsDelegate: for more details.
62 bool PreHandleKeyboardEvent(content::WebContents
* source
,
63 const content::NativeWebKeyboardEvent
& event
,
64 bool* is_keyboard_shortcut
) {
65 ui::Accelerator
accelerator(
66 static_cast<ui::KeyboardCode
>(event
.windowsKeyCode
),
67 content::GetModifiersFromNativeWebKeyboardEvent(event
));
68 if (event
.type
== blink::WebInputEvent::KeyUp
)
69 accelerator
.set_type(ui::ET_KEY_RELEASED
);
71 if (reserved_accelerator_enabled_
&&
72 accelerator_manager_
->IsRegistered(accelerator
, AF_RESERVED
)) {
73 return web_view_
->GetFocusManager()->ProcessAccelerator(accelerator
);
75 *is_keyboard_shortcut
=
76 accelerator_manager_
->IsRegistered(accelerator
, AF_NONE
);
80 void HandleKeyboardEvent(content::WebContents
* source
,
81 const content::NativeWebKeyboardEvent
& event
) {
82 unhandled_keyboard_event_handler_
.HandleKeyboardEvent(
83 event
, web_view_
->GetFocusManager());
87 // AcceleratorHandler:
88 virtual bool IsCommandEnabled(int command_id
) const OVERRIDE
{
91 case CMD_RELOAD_IGNORE_CACHE
:
94 return web_view_
->GetWebContents()->GetController().CanGoBack();
96 return web_view_
->GetWebContents()->GetController().CanGoForward();
98 // TODO(oshima): check onbeforeunload handler.
104 virtual bool OnAcceleratorFired(int command_id
,
105 const ui::Accelerator
& accelerator
) OVERRIDE
{
106 switch (command_id
) {
108 web_view_
->GetWebContents()->GetController().Reload(false);
110 case CMD_RELOAD_IGNORE_CACHE
:
111 web_view_
->GetWebContents()->GetController().ReloadIgnoringCache(false);
114 web_view_
->GetWebContents()->GetController().GoBack();
117 web_view_
->GetWebContents()->GetController().GoForward();
120 web_view_
->GetWidget()->Close();
126 views::WebView
* web_view_
;
127 bool reserved_accelerator_enabled_
;
128 scoped_ptr
<AcceleratorManager
> accelerator_manager_
;
129 views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_
;
131 DISALLOW_COPY_AND_ASSIGN(WebActivityController
);
134 const SkColor kDefaultTitleColor
= SkColorSetRGB(0xf2, 0xf2, 0xf2);
135 const SkColor kDefaultUnavailableColor
= SkColorSetRGB(0xbb, 0x77, 0x77);
139 // A web view for athena's web activity. Note that AthenaWebView will create its
140 // own content so that it can eject and reload it.
141 class AthenaWebView
: public views::WebView
{
143 AthenaWebView(content::BrowserContext
* context
)
144 : views::WebView(context
), controller_(new WebActivityController(this)),
146 SetEmbedFullscreenWidgetMode(true);
147 // TODO(skuhne): Add content observer to detect renderer crash and set
148 // content status to unloaded if that happens.
151 AthenaWebView(content::WebContents
* web_contents
)
152 : views::WebView(web_contents
->GetBrowserContext()),
153 controller_(new WebActivityController(this)) {
154 scoped_ptr
<content::WebContents
> old_contents(
155 SwapWebContents(scoped_ptr
<content::WebContents
>(web_contents
)));
158 virtual ~AthenaWebView() {}
160 void InstallAccelerators() { controller_
->InstallAccelerators(); }
162 void EvictContent() {
163 scoped_ptr
<content::WebContents
> old_contents(SwapWebContents(
164 scoped_ptr
<content::WebContents
>(content::WebContents::Create(
165 content::WebContents::CreateParams(browser_context())))));
166 evicted_web_contents_
.reset(
167 content::WebContents::Create(content::WebContents::CreateParams(
168 old_contents
->GetBrowserContext())));
169 evicted_web_contents_
->GetController().CopyStateFrom(
170 old_contents
->GetController());
171 // As soon as the new contents becomes visible, it should reload.
172 // TODO(skuhne): This breaks script connections with other activities.
173 // Even though this is the same technique as used by the TabStripModel,
174 // we might want to address this cleaner since we are more likely to
175 // run into this state. by unloading.
178 void ReloadContent() {
179 CHECK(evicted_web_contents_
.get());
180 scoped_ptr
<content::WebContents
> replaced_contents(SwapWebContents(
181 evicted_web_contents_
.Pass()));
184 // Check if the content got evicted.
185 const bool IsContentEvicted() { return !!evicted_web_contents_
.get(); }
187 // content::WebContentsDelegate:
188 virtual content::WebContents
* OpenURLFromTab(
189 content::WebContents
* source
,
190 const content::OpenURLParams
& params
) OVERRIDE
{
191 switch(params
.disposition
) {
193 DCHECK(source
== web_contents());
194 content::NavigationController::LoadURLParams
load_url_params(
196 load_url_params
.referrer
= params
.referrer
;
197 load_url_params
.frame_tree_node_id
= params
.frame_tree_node_id
;
198 load_url_params
.transition_type
= params
.transition
;
199 load_url_params
.extra_headers
= params
.extra_headers
;
200 load_url_params
.should_replace_current_entry
=
201 params
.should_replace_current_entry
;
202 load_url_params
.is_renderer_initiated
= params
.is_renderer_initiated
;
203 load_url_params
.transferred_global_request_id
=
204 params
.transferred_global_request_id
;
205 web_contents()->GetController().LoadURLWithParams(load_url_params
);
206 return web_contents();
208 case NEW_FOREGROUND_TAB
:
209 case NEW_BACKGROUND_TAB
:
212 ActivityManager::Get()->AddActivity(
213 ActivityFactory::Get()->CreateWebActivity(browser_context(),
220 // NULL is returned if the URL wasn't opened immediately.
224 virtual void AddNewContents(content::WebContents
* source
,
225 content::WebContents
* new_contents
,
226 WindowOpenDisposition disposition
,
227 const gfx::Rect
& initial_pos
,
229 bool* was_blocked
) OVERRIDE
{
230 ActivityManager::Get()->AddActivity(
231 new WebActivity(new AthenaWebView(new_contents
)));
234 virtual bool PreHandleKeyboardEvent(
235 content::WebContents
* source
,
236 const content::NativeWebKeyboardEvent
& event
,
237 bool* is_keyboard_shortcut
) OVERRIDE
{
238 return controller_
->PreHandleKeyboardEvent(
239 source
, event
, is_keyboard_shortcut
);
242 virtual void HandleKeyboardEvent(
243 content::WebContents
* source
,
244 const content::NativeWebKeyboardEvent
& event
) OVERRIDE
{
245 controller_
->HandleKeyboardEvent(source
, event
);
248 virtual void ToggleFullscreenModeForTab(content::WebContents
* web_contents
,
249 bool enter_fullscreen
) OVERRIDE
{
250 fullscreen_
= enter_fullscreen
;
251 GetWidget()->SetFullscreen(fullscreen_
);
254 virtual bool IsFullscreenForTabOrPending(
255 const content::WebContents
* web_contents
) const OVERRIDE
{
260 scoped_ptr
<WebActivityController
> controller_
;
262 // If the activity got evicted, this is the web content which holds the known
263 // state of the content before eviction.
264 scoped_ptr
<content::WebContents
> evicted_web_contents_
;
266 // TODO(oshima): Find out if we should support window fullscreen.
267 // It may still useful when a user is in split mode.
270 DISALLOW_COPY_AND_ASSIGN(AthenaWebView
);
273 WebActivity::WebActivity(content::BrowserContext
* browser_context
,
275 : browser_context_(browser_context
),
278 title_color_(kDefaultTitleColor
),
279 current_state_(ACTIVITY_UNLOADED
) {
282 WebActivity::WebActivity(AthenaWebView
* web_view
)
283 : browser_context_(web_view
->browser_context()),
284 url_(web_view
->GetWebContents()->GetURL()),
286 current_state_(ACTIVITY_UNLOADED
) {
287 // Transition to state ACTIVITY_INVISIBLE to perform the same setup steps
288 // as on new activities (namely adding a WebContentsObserver).
289 SetCurrentState(ACTIVITY_INVISIBLE
);
292 WebActivity::~WebActivity() {
293 // It is not required to change the activity state to UNLOADED - unless we
294 // would add state observers.
297 ActivityViewModel
* WebActivity::GetActivityViewModel() {
301 void WebActivity::SetCurrentState(Activity::ActivityState state
) {
303 case ACTIVITY_VISIBLE
:
304 // Fall through (for the moment).
305 case ACTIVITY_INVISIBLE
:
306 // By clearing the overview mode image we allow the content to be shown.
307 overview_mode_image_
= gfx::ImageSkia();
308 if (web_view_
->IsContentEvicted()) {
309 DCHECK_EQ(ACTIVITY_UNLOADED
, current_state_
);
310 web_view_
->ReloadContent();
312 Observe(web_view_
->GetWebContents());
314 case ACTIVITY_BACKGROUND_LOW_PRIORITY
:
315 DCHECK(ACTIVITY_VISIBLE
== current_state_
||
316 ACTIVITY_INVISIBLE
== current_state_
);
317 // TODO(skuhne): Do this.
319 case ACTIVITY_PERSISTENT
:
320 DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY
, current_state_
);
321 // TODO(skuhne): Do this. As soon as the new resource management is
322 // agreed upon - or remove otherwise.
324 case ACTIVITY_UNLOADED
:
325 DCHECK_NE(ACTIVITY_UNLOADED
, current_state_
);
327 web_view_
->EvictContent();
330 // Remember the last requested state.
331 current_state_
= state
;
334 Activity::ActivityState
WebActivity::GetCurrentState() {
335 if (!web_view_
|| web_view_
->IsContentEvicted()) {
336 DCHECK_EQ(ACTIVITY_UNLOADED
, current_state_
);
337 return ACTIVITY_UNLOADED
;
339 // TODO(skuhne): This should be controlled by an observer and should not
341 if (IsVisible() && current_state_
!= ACTIVITY_VISIBLE
)
342 SetCurrentState(ACTIVITY_VISIBLE
);
343 // Note: If the activity is not visible it does not necessarily mean that it
344 // does not have GPU compositor resources (yet).
346 return current_state_
;
349 bool WebActivity::IsVisible() {
351 web_view_
->IsDrawn() &&
352 current_state_
!= ACTIVITY_UNLOADED
&&
354 GetWindow()->IsVisible();
357 Activity::ActivityMediaState
WebActivity::GetMediaState() {
358 // TODO(skuhne): The function GetTabMediaStateForContents(WebContents),
359 // and the AudioStreamMonitor needs to be moved from Chrome into contents to
360 // make it more modular and so that we can use it from here.
361 return Activity::ACTIVITY_MEDIA_STATE_NONE
;
364 aura::Window
* WebActivity::GetWindow() {
365 return !web_view_
? NULL
: web_view_
->GetWidget()->GetNativeWindow();
368 void WebActivity::Init() {
370 web_view_
->InstallAccelerators();
373 SkColor
WebActivity::GetRepresentativeColor() const {
374 // TODO(sad): Compute the color from the favicon.
375 return web_view_
? title_color_
: kDefaultUnavailableColor
;
378 base::string16
WebActivity::GetTitle() const {
379 return web_view_
? base::UTF8ToUTF16(
380 web_view_
->GetWebContents()->GetVisibleURL().host())
384 bool WebActivity::UsesFrame() const {
388 views::View
* WebActivity::GetContentsView() {
390 web_view_
= new AthenaWebView(browser_context_
);
391 web_view_
->LoadInitialURL(url_
);
392 SetCurrentState(ACTIVITY_INVISIBLE
);
393 // Reset the overview mode image.
394 overview_mode_image_
= gfx::ImageSkia();
399 void WebActivity::CreateOverviewModeImage() {
400 // TODO(skuhne): Create an overview.
403 gfx::ImageSkia
WebActivity::GetOverviewModeImage() {
404 return overview_mode_image_
;
407 void WebActivity::TitleWasSet(content::NavigationEntry
* entry
,
409 ActivityManager::Get()->UpdateActivity(this);
412 void WebActivity::DidUpdateFaviconURL(
413 const std::vector
<content::FaviconURL
>& candidates
) {
414 ActivityManager::Get()->UpdateActivity(this);
417 void WebActivity::DidChangeThemeColor(SkColor theme_color
) {
418 title_color_
= theme_color
;
421 } // namespace athena