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.
4
5#include "athena/content/web_activity.h"
6
7#include "athena/activity/public/activity_factory.h"
8#include "athena/activity/public/activity_manager.h"
9#include "athena/content/content_proxy.h"
10#include "athena/content/public/dialogs.h"
11#include "athena/input/public/accelerator_manager.h"
12#include "athena/strings/grit/athena_strings.h"
13#include "base/bind.h"
14#include "base/command_line.h"
15#include "base/strings/utf_string_conversions.h"
16#include "components/favicon_base/select_favicon_frames.h"
17#include "content/public/browser/native_web_keyboard_event.h"
18#include "content/public/browser/navigation_controller.h"
19#include "content/public/browser/web_contents.h"
20#include "content/public/browser/web_contents_delegate.h"
21#include "content/public/common/content_switches.h"
22#include "content/public/common/favicon_url.h"
23#include "ui/aura/window.h"
24#include "ui/base/l10n/l10n_util.h"
25#include "ui/compositor/closure_animation_observer.h"
26#include "ui/compositor/scoped_layer_animation_settings.h"
27#include "ui/views/background.h"
28#include "ui/views/controls/label.h"
29#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
30#include "ui/views/controls/webview/webview.h"
31#include "ui/views/focus/focus_manager.h"
32#include "ui/views/widget/widget.h"
33
34namespace athena {
35namespace {
36
37class WebActivityController : public AcceleratorHandler {
38 public:
39  enum Command {
40    CMD_BACK,
41    CMD_FORWARD,
42    CMD_RELOAD,
43    CMD_RELOAD_IGNORE_CACHE,
44    CMD_CLOSE,
45    CMD_STOP,
46  };
47
48  explicit WebActivityController(views::WebView* web_view)
49      : web_view_(web_view), reserved_accelerator_enabled_(true) {}
50  virtual ~WebActivityController() {}
51
52  // Installs accelerators for web activity.
53  void InstallAccelerators() {
54    accelerator_manager_ = AcceleratorManager::CreateForFocusManager(
55                               web_view_->GetFocusManager()).Pass();
56    const AcceleratorData accelerator_data[] = {
57        {TRIGGER_ON_PRESS, ui::VKEY_R, ui::EF_CONTROL_DOWN, CMD_RELOAD,
58         AF_NONE},
59        {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_REFRESH, ui::EF_NONE, CMD_RELOAD,
60         AF_NONE},
61        {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_REFRESH, ui::EF_CONTROL_DOWN,
62         CMD_RELOAD_IGNORE_CACHE, AF_NONE},
63        {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_FORWARD, ui::EF_NONE, CMD_FORWARD,
64         AF_NONE},
65        {TRIGGER_ON_PRESS, ui::VKEY_BROWSER_BACK, ui::EF_NONE, CMD_BACK,
66         AF_NONE},
67        {TRIGGER_ON_PRESS, ui::VKEY_W, ui::EF_CONTROL_DOWN, CMD_CLOSE, AF_NONE},
68        {TRIGGER_ON_PRESS, ui::VKEY_ESCAPE, ui::EF_NONE, CMD_STOP, AF_NONE},
69    };
70    accelerator_manager_->RegisterAccelerators(
71        accelerator_data, arraysize(accelerator_data), this);
72  }
73
74  // Methods that are called before and after key events are consumed by the web
75  // contents.
76  // See the documentation in WebContentsDelegate: for more details.
77  bool PreHandleKeyboardEvent(content::WebContents* source,
78                              const content::NativeWebKeyboardEvent& event,
79                              bool* is_keyboard_shortcut) {
80    ui::Accelerator accelerator(
81        static_cast<ui::KeyboardCode>(event.windowsKeyCode),
82        content::GetModifiersFromNativeWebKeyboardEvent(event));
83    if (event.type == blink::WebInputEvent::KeyUp)
84      accelerator.set_type(ui::ET_KEY_RELEASED);
85
86    if (reserved_accelerator_enabled_ &&
87        accelerator_manager_->IsRegistered(accelerator, AF_RESERVED)) {
88      return web_view_->GetFocusManager()->ProcessAccelerator(accelerator);
89    }
90    *is_keyboard_shortcut =
91        accelerator_manager_->IsRegistered(accelerator, AF_NONE);
92    return false;
93  }
94
95  void HandleKeyboardEvent(content::WebContents* source,
96                           const content::NativeWebKeyboardEvent& event) {
97    unhandled_keyboard_event_handler_.HandleKeyboardEvent(
98        event, web_view_->GetFocusManager());
99  }
100
101 private:
102  // AcceleratorHandler:
103  virtual bool IsCommandEnabled(int command_id) const OVERRIDE {
104    switch (command_id) {
105      case CMD_RELOAD:
106      case CMD_RELOAD_IGNORE_CACHE:
107        return true;
108      case CMD_BACK:
109        return web_view_->GetWebContents()->GetController().CanGoBack();
110      case CMD_FORWARD:
111        return web_view_->GetWebContents()->GetController().CanGoForward();
112      case CMD_CLOSE:
113        // TODO(oshima): check onbeforeunload handler.
114        return true;
115      case CMD_STOP:
116        return web_view_->GetWebContents()->IsLoading();
117    }
118    return false;
119  }
120
121  virtual bool OnAcceleratorFired(int command_id,
122                                  const ui::Accelerator& accelerator) OVERRIDE {
123    switch (command_id) {
124      case CMD_RELOAD:
125        web_view_->GetWebContents()->GetController().Reload(false);
126        return true;
127      case CMD_RELOAD_IGNORE_CACHE:
128        web_view_->GetWebContents()->GetController().ReloadIgnoringCache(false);
129        return true;
130      case CMD_BACK:
131        web_view_->GetWebContents()->GetController().GoBack();
132        return true;
133      case CMD_FORWARD:
134        web_view_->GetWebContents()->GetController().GoForward();
135        return true;
136      case CMD_CLOSE:
137        web_view_->GetWidget()->Close();
138        return true;
139      case CMD_STOP:
140        web_view_->GetWebContents()->Stop();
141        return true;
142    }
143    return false;
144  }
145
146  views::WebView* web_view_;
147  bool reserved_accelerator_enabled_;
148  scoped_ptr<AcceleratorManager> accelerator_manager_;
149  views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
150
151  DISALLOW_COPY_AND_ASSIGN(WebActivityController);
152};
153
154const SkColor kDefaultTitleColor = SkColorSetRGB(0xf2, 0xf2, 0xf2);
155const SkColor kDefaultUnavailableColor = SkColorSetRGB(0xbb, 0x77, 0x77);
156const int kIconSize = 32;
157const int kDistanceShowReloadMessage = 100;
158const int kDistanceReload = 150;
159
160}  // namespace
161
162// A web view for athena's web activity. Note that AthenaWebView will create its
163// own content so that it can eject and reload it.
164class AthenaWebView : public views::WebView {
165 public:
166  AthenaWebView(content::BrowserContext* context)
167      : views::WebView(context), controller_(new WebActivityController(this)),
168        fullscreen_(false),
169        overscroll_y_(0) {
170    SetEmbedFullscreenWidgetMode(true);
171    // TODO(skuhne): Add content observer to detect renderer crash and set
172    // content status to unloaded if that happens.
173  }
174
175  AthenaWebView(content::WebContents* web_contents)
176      : views::WebView(web_contents->GetBrowserContext()),
177        controller_(new WebActivityController(this)) {
178    scoped_ptr<content::WebContents> old_contents(
179        SwapWebContents(scoped_ptr<content::WebContents>(web_contents)));
180  }
181
182  virtual ~AthenaWebView() {}
183
184  void InstallAccelerators() { controller_->InstallAccelerators(); }
185
186  void EvictContent() {
187    scoped_ptr<content::WebContents> old_contents(SwapWebContents(
188        scoped_ptr<content::WebContents>(content::WebContents::Create(
189            content::WebContents::CreateParams(browser_context())))));
190    // If there is a progress bar, we need to get rid of it now since its
191    // associated content, parent window and layers will disappear with evicting
192    // the content.
193    progress_bar_.reset();
194    evicted_web_contents_.reset(
195        content::WebContents::Create(content::WebContents::CreateParams(
196            old_contents->GetBrowserContext())));
197    evicted_web_contents_->GetController().CopyStateFrom(
198        old_contents->GetController());
199    // As soon as the new contents becomes visible, it should reload.
200    // TODO(skuhne): This breaks script connections with other activities.
201    // Even though this is the same technique as used by the TabStripModel,
202    // we might want to address this cleaner since we are more likely to
203    // run into this state. by unloading.
204  }
205
206  void ReloadContent() {
207    CHECK(evicted_web_contents_.get());
208    scoped_ptr<content::WebContents> replaced_contents(SwapWebContents(
209        evicted_web_contents_.Pass()));
210  }
211
212  // Check if the content got evicted.
213  const bool IsContentEvicted() { return !!evicted_web_contents_.get(); }
214
215  // content::WebContentsDelegate:
216  virtual content::WebContents* OpenURLFromTab(
217      content::WebContents* source,
218      const content::OpenURLParams& params) OVERRIDE {
219    switch(params.disposition) {
220      case CURRENT_TAB: {
221        DCHECK(source == web_contents());
222        content::NavigationController::LoadURLParams load_url_params(
223            params.url);
224        load_url_params.referrer = params.referrer;
225        load_url_params.frame_tree_node_id = params.frame_tree_node_id;
226        load_url_params.transition_type = params.transition;
227        load_url_params.extra_headers = params.extra_headers;
228        load_url_params.should_replace_current_entry =
229            params.should_replace_current_entry;
230        load_url_params.is_renderer_initiated = params.is_renderer_initiated;
231        load_url_params.transferred_global_request_id =
232            params.transferred_global_request_id;
233        web_contents()->GetController().LoadURLWithParams(load_url_params);
234        return web_contents();
235      }
236      case NEW_FOREGROUND_TAB:
237      case NEW_BACKGROUND_TAB:
238      case NEW_POPUP:
239      case NEW_WINDOW: {
240        Activity* activity = ActivityFactory::Get()->CreateWebActivity(
241            browser_context(), base::string16(), params.url);
242        Activity::Show(activity);
243        break;
244      }
245      default:
246        break;
247    }
248    // NULL is returned if the URL wasn't opened immediately.
249    return NULL;
250  }
251
252  virtual bool CanOverscrollContent() const OVERRIDE {
253    const std::string value = CommandLine::ForCurrentProcess()->
254        GetSwitchValueASCII(switches::kOverscrollHistoryNavigation);
255    return value != "0";
256  }
257
258  virtual void OverscrollUpdate(int delta_y) OVERRIDE {
259    overscroll_y_ = delta_y;
260    if (overscroll_y_ > kDistanceShowReloadMessage) {
261      if (!reload_message_)
262        CreateReloadMessage();
263      reload_message_->Show();
264      float opacity = 1.0f;
265      if (overscroll_y_ < kDistanceReload) {
266        opacity =
267            (overscroll_y_ - kDistanceShowReloadMessage) /
268            static_cast<float>(kDistanceReload - kDistanceShowReloadMessage);
269      }
270      reload_message_->GetLayer()->SetOpacity(opacity);
271    } else if (reload_message_) {
272      reload_message_->Hide();
273    }
274  }
275
276  virtual void OverscrollComplete() OVERRIDE {
277    if (overscroll_y_ >= kDistanceReload)
278      GetWebContents()->GetController().Reload(false);
279    if (reload_message_)
280      reload_message_->Hide();
281    overscroll_y_ = 0;
282  }
283
284  virtual void AddNewContents(content::WebContents* source,
285                              content::WebContents* new_contents,
286                              WindowOpenDisposition disposition,
287                              const gfx::Rect& initial_pos,
288                              bool user_gesture,
289                              bool* was_blocked) OVERRIDE {
290    // TODO(oshima): Use factory.
291    ActivityManager::Get()->AddActivity(
292        new WebActivity(new AthenaWebView(new_contents)));
293  }
294
295  virtual bool PreHandleKeyboardEvent(
296      content::WebContents* source,
297      const content::NativeWebKeyboardEvent& event,
298      bool* is_keyboard_shortcut) OVERRIDE {
299    return controller_->PreHandleKeyboardEvent(
300        source, event, is_keyboard_shortcut);
301  }
302
303  virtual void HandleKeyboardEvent(
304      content::WebContents* source,
305      const content::NativeWebKeyboardEvent& event) OVERRIDE {
306    controller_->HandleKeyboardEvent(source, event);
307  }
308
309  virtual void ToggleFullscreenModeForTab(content::WebContents* web_contents,
310                                          bool enter_fullscreen) OVERRIDE {
311    fullscreen_ = enter_fullscreen;
312    GetWidget()->SetFullscreen(fullscreen_);
313  }
314
315  virtual bool IsFullscreenForTabOrPending(
316      const content::WebContents* web_contents) const OVERRIDE {
317    return fullscreen_;
318  }
319
320  virtual void LoadingStateChanged(content::WebContents* source,
321                                   bool to_different_document) OVERRIDE {
322    bool has_stopped = source == NULL || !source->IsLoading();
323    LoadProgressChanged(source, has_stopped ? 1 : 0);
324  }
325
326  virtual void LoadProgressChanged(content::WebContents* source,
327                                   double progress) OVERRIDE {
328    if (!progress)
329      return;
330
331    if (!progress_bar_) {
332      CreateProgressBar();
333      source->GetNativeView()->layer()->Add(progress_bar_.get());
334    }
335    progress_bar_->SetBounds(gfx::Rect(
336        0, 0, progress * progress_bar_->parent()->bounds().width(), 3));
337    if (progress < 1)
338      return;
339
340    ui::ScopedLayerAnimationSettings settings(progress_bar_->GetAnimator());
341    settings.SetTweenType(gfx::Tween::EASE_IN);
342    ui::Layer* layer = progress_bar_.get();
343    settings.AddObserver(new ui::ClosureAnimationObserver(
344        base::Bind(&base::DeletePointer<ui::Layer>, progress_bar_.release())));
345    layer->SetOpacity(0.f);
346  }
347
348  virtual content::ColorChooser* OpenColorChooser(
349      content::WebContents* web_contents,
350      SkColor color,
351      const std::vector<content::ColorSuggestion>& suggestions) OVERRIDE {
352    return athena::OpenColorChooser(web_contents, color, suggestions);
353  }
354
355  // Called when a file selection is to be done.
356  virtual void RunFileChooser(
357      content::WebContents* web_contents,
358      const content::FileChooserParams& params) OVERRIDE {
359    return athena::OpenFileChooser(web_contents, params);
360  }
361
362 private:
363  void CreateProgressBar() {
364    CHECK(!progress_bar_);
365    progress_bar_.reset(new ui::Layer(ui::LAYER_SOLID_COLOR));
366    progress_bar_->SetColor(SkColorSetRGB(0x17, 0x59, 0xcd));
367  }
368
369  void CreateReloadMessage() {
370    CHECK(!reload_message_);
371    reload_message_.reset(new views::Widget);
372    views::Widget::InitParams params(views::Widget::InitParams::TYPE_CONTROL);
373    params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
374    params.parent = GetWidget()->GetNativeView();
375    reload_message_->Init(params);
376
377    views::Label* label = new views::Label(
378        l10n_util::GetStringUTF16(IDS_ATHENA_PULL_TO_RELOAD_MESSAGE));
379    label->SetBackgroundColor(SK_ColorGRAY);
380    label->set_background(
381        views::Background::CreateSolidBackground(SK_ColorGRAY));
382
383    reload_message_->SetContentsView(label);
384    reload_message_->SetBounds(ConvertRectToWidget(
385        gfx::Rect(0, 0, width(), label->GetPreferredSize().height())));
386  }
387
388  scoped_ptr<WebActivityController> controller_;
389
390  // If the activity got evicted, this is the web content which holds the known
391  // state of the content before eviction.
392  scoped_ptr<content::WebContents> evicted_web_contents_;
393
394  scoped_ptr<ui::Layer> progress_bar_;
395
396  scoped_ptr<views::Widget> reload_message_;
397
398  // TODO(oshima): Find out if we should support window fullscreen.
399  // It may still useful when a user is in split mode.
400  bool fullscreen_;
401
402  // The distance that the user has overscrolled vertically.
403  int overscroll_y_;
404
405  DISALLOW_COPY_AND_ASSIGN(AthenaWebView);
406};
407
408WebActivity::WebActivity(content::BrowserContext* browser_context,
409                         const base::string16& title,
410                         const GURL& url)
411    : browser_context_(browser_context),
412      title_(title),
413      url_(url),
414      web_view_(NULL),
415      title_color_(kDefaultTitleColor),
416      current_state_(ACTIVITY_UNLOADED),
417      weak_ptr_factory_(this) {
418}
419
420WebActivity::WebActivity(AthenaWebView* web_view)
421    : browser_context_(web_view->browser_context()),
422      url_(web_view->GetWebContents()->GetURL()),
423      web_view_(web_view),
424      current_state_(ACTIVITY_UNLOADED),
425      weak_ptr_factory_(this) {
426  // Transition to state ACTIVITY_INVISIBLE to perform the same setup steps
427  // as on new activities (namely adding a WebContentsObserver).
428  SetCurrentState(ACTIVITY_INVISIBLE);
429}
430
431WebActivity::~WebActivity() {
432  // It is not required to change the activity state to UNLOADED - unless we
433  // would add state observers.
434}
435
436ActivityViewModel* WebActivity::GetActivityViewModel() {
437  return this;
438}
439
440void WebActivity::SetCurrentState(Activity::ActivityState state) {
441  DCHECK_NE(state, current_state_);
442  switch (state) {
443    case ACTIVITY_VISIBLE:
444      if (!web_view_)
445        break;
446      HideContentProxy();
447      ReloadAndObserve();
448      break;
449    case ACTIVITY_INVISIBLE:
450      if (!web_view_)
451        break;
452
453      if (current_state_ == ACTIVITY_VISIBLE)
454        ShowContentProxy();
455      else
456        ReloadAndObserve();
457
458      break;
459    case ACTIVITY_BACKGROUND_LOW_PRIORITY:
460      DCHECK(ACTIVITY_VISIBLE == current_state_ ||
461             ACTIVITY_INVISIBLE == current_state_);
462      // TODO(skuhne): Do this.
463      break;
464    case ACTIVITY_PERSISTENT:
465      DCHECK_EQ(ACTIVITY_BACKGROUND_LOW_PRIORITY, current_state_);
466      // TODO(skuhne): Do this. As soon as the new resource management is
467      // agreed upon - or remove otherwise.
468      break;
469    case ACTIVITY_UNLOADED:
470      DCHECK_NE(ACTIVITY_UNLOADED, current_state_);
471      if (content_proxy_)
472        content_proxy_->ContentWillUnload();
473      Observe(NULL);
474      web_view_->EvictContent();
475      break;
476  }
477  // Remember the last requested state.
478  current_state_ = state;
479}
480
481Activity::ActivityState WebActivity::GetCurrentState() {
482  // If the content is evicted, the state has to be UNLOADED.
483  DCHECK(!web_view_ ||
484         !web_view_->IsContentEvicted() ||
485         current_state_ == ACTIVITY_UNLOADED);
486  return current_state_;
487}
488
489bool WebActivity::IsVisible() {
490  return web_view_ &&
491         web_view_->visible() &&
492         current_state_ != ACTIVITY_UNLOADED;
493}
494
495Activity::ActivityMediaState WebActivity::GetMediaState() {
496  // TODO(skuhne): The function GetTabMediaStateForContents(WebContents),
497  // and the AudioStreamMonitor needs to be moved from Chrome into contents to
498  // make it more modular and so that we can use it from here.
499  return Activity::ACTIVITY_MEDIA_STATE_NONE;
500}
501
502aura::Window* WebActivity::GetWindow() {
503  return !web_view_ ? NULL : web_view_->GetWidget()->GetNativeWindow();
504}
505
506content::WebContents* WebActivity::GetWebContents() {
507  return !web_view_ ? NULL : web_view_->GetWebContents();
508}
509
510void WebActivity::Init() {
511  DCHECK(web_view_);
512  web_view_->InstallAccelerators();
513}
514
515SkColor WebActivity::GetRepresentativeColor() const {
516  return web_view_ ? title_color_ : kDefaultUnavailableColor;
517}
518
519base::string16 WebActivity::GetTitle() const {
520  if (!title_.empty())
521    return title_;
522  // TODO(oshima): Use title set by the web contents.
523  return web_view_ ? base::UTF8ToUTF16(
524                         web_view_->GetWebContents()->GetVisibleURL().host())
525                   : base::string16();
526}
527
528gfx::ImageSkia WebActivity::GetIcon() const {
529  return icon_;
530}
531
532bool WebActivity::UsesFrame() const {
533  return true;
534}
535
536views::View* WebActivity::GetContentsView() {
537  if (!web_view_) {
538    web_view_ = new AthenaWebView(browser_context_);
539    web_view_->LoadInitialURL(url_);
540    // Make sure the content gets properly shown.
541    if (current_state_ == ACTIVITY_VISIBLE) {
542      HideContentProxy();
543      ReloadAndObserve();
544    } else if (current_state_ == ACTIVITY_INVISIBLE) {
545      ShowContentProxy();
546      ReloadAndObserve();
547    } else {
548      // If not previously specified, we change the state now to invisible..
549      SetCurrentState(ACTIVITY_INVISIBLE);
550    }
551  }
552  return web_view_;
553}
554
555views::Widget* WebActivity::CreateWidget() {
556  return NULL;  // Use default widget.
557}
558
559gfx::ImageSkia WebActivity::GetOverviewModeImage() {
560  if (content_proxy_.get())
561    content_proxy_->GetContentImage();
562  return gfx::ImageSkia();
563}
564
565void WebActivity::PrepareContentsForOverview() {
566  // Turn on fast resizing to avoid re-laying out the web contents when
567  // entering / exiting overview mode and the content is visible.
568  if (!content_proxy_.get())
569    web_view_->SetFastResize(true);
570}
571
572void WebActivity::ResetContentsView() {
573  // Turn on fast resizing to avoid re-laying out the web contents when
574  // entering / exiting overview mode and the content is visible.
575  if (!content_proxy_.get()) {
576    web_view_->SetFastResize(false);
577    web_view_->Layout();
578  }
579}
580
581void WebActivity::TitleWasSet(content::NavigationEntry* entry,
582                              bool explicit_set) {
583  ActivityManager::Get()->UpdateActivity(this);
584}
585
586void WebActivity::DidNavigateMainFrame(
587    const content::LoadCommittedDetails& details,
588    const content::FrameNavigateParams& params) {
589  // Prevent old image requests from calling back to OnDidDownloadFavicon().
590  weak_ptr_factory_.InvalidateWeakPtrs();
591
592  icon_ = gfx::ImageSkia();
593  ActivityManager::Get()->UpdateActivity(this);
594}
595
596void WebActivity::DidUpdateFaviconURL(
597    const std::vector<content::FaviconURL>& candidates) {
598  // Pick an arbitrary favicon of type FAVICON to use.
599  // TODO(pkotwicz): Do something better once the favicon code is componentized.
600  // (crbug.com/401997)
601  weak_ptr_factory_.InvalidateWeakPtrs();
602  for (size_t i = 0; i < candidates.size(); ++i) {
603    if (candidates[i].icon_type == content::FaviconURL::FAVICON) {
604      web_view_->GetWebContents()->DownloadImage(
605          candidates[i].icon_url,
606          true,
607          0,
608          base::Bind(&WebActivity::OnDidDownloadFavicon,
609                     weak_ptr_factory_.GetWeakPtr()));
610      break;
611    }
612  }
613}
614
615void WebActivity::OnDidDownloadFavicon(
616    int id,
617    int http_status_code,
618    const GURL& url,
619    const std::vector<SkBitmap>& bitmaps,
620    const std::vector<gfx::Size>& original_bitmap_sizes) {
621  icon_ = CreateFaviconImageSkia(
622      bitmaps, original_bitmap_sizes, kIconSize, NULL);
623  ActivityManager::Get()->UpdateActivity(this);
624}
625
626void WebActivity::DidChangeThemeColor(SkColor theme_color) {
627  title_color_ = theme_color;
628  ActivityManager::Get()->UpdateActivity(this);
629}
630
631void WebActivity::HideContentProxy() {
632  if (content_proxy_.get())
633    content_proxy_.reset(NULL);
634}
635
636void WebActivity::ShowContentProxy() {
637  if (!content_proxy_.get() && web_view_)
638    content_proxy_.reset(new ContentProxy(web_view_, this));
639}
640
641void WebActivity::ReloadAndObserve() {
642  if (web_view_->IsContentEvicted()) {
643    DCHECK_EQ(ACTIVITY_UNLOADED, current_state_);
644    web_view_->ReloadContent();
645  }
646  Observe(web_view_->GetWebContents());
647}
648
649}  // namespace athena
650