1// Copyright 2012 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 "chrome/browser/ui/search/instant_unload_handler.h"
6
7#include <algorithm>
8
9#include "base/message_loop/message_loop.h"
10#include "chrome/browser/ui/browser_navigator.h"
11#include "content/public/browser/render_view_host.h"
12#include "content/public/browser/web_contents.h"
13#include "content/public/browser/web_contents_delegate.h"
14
15class InstantUnloadHandler::WebContentsDelegateImpl
16    : public content::WebContentsDelegate {
17 public:
18  WebContentsDelegateImpl(InstantUnloadHandler* handler,
19                          scoped_ptr<content::WebContents> contents,
20                          int index)
21      : handler_(handler),
22        contents_(contents.Pass()),
23        index_(index) {
24    contents_->SetDelegate(this);
25    contents_->GetRenderViewHost()->FirePageBeforeUnload(false);
26  }
27
28  // Overridden from content::WebContentsDelegate:
29  virtual void CloseContents(content::WebContents* source) OVERRIDE {
30    DCHECK_EQ(contents_, source);
31    // Remove ourselves as the delegate, so that CloseContents() won't be
32    // called twice, leading to double deletion (http://crbug.com/155848).
33    contents_->SetDelegate(NULL);
34    handler_->Destroy(this);
35  }
36
37  virtual void WillRunBeforeUnloadConfirm() OVERRIDE {
38    contents_->SetDelegate(NULL);
39    handler_->Activate(this, contents_.Pass(), index_);
40  }
41
42  virtual bool ShouldSuppressDialogs() OVERRIDE {
43    return true;
44  }
45
46 private:
47  InstantUnloadHandler* const handler_;
48  scoped_ptr<content::WebContents> contents_;
49
50  // The tab strip index |contents_| was originally at. If we add the tab back
51  // to the tabstrip, we add it at this index.
52  const int index_;
53
54  DISALLOW_COPY_AND_ASSIGN(WebContentsDelegateImpl);
55};
56
57InstantUnloadHandler::InstantUnloadHandler(Browser* browser)
58    : browser_(browser) {
59}
60
61InstantUnloadHandler::~InstantUnloadHandler() {
62}
63
64void InstantUnloadHandler::RunUnloadListenersOrDestroy(
65    scoped_ptr<content::WebContents> contents,
66    int index) {
67  DCHECK(!contents->GetDelegate());
68
69  if (!contents->NeedToFireBeforeUnload()) {
70    // Tab doesn't have any beforeunload listeners and can be safely deleted.
71    // However, the tab object should not be deleted immediately because when we
72    // get here from BrowserInstantController::TabDeactivated, other tab
73    // observers may still expect to interact with the tab before the event has
74    // finished propagating.
75    base::MessageLoop::current()->DeleteSoon(FROM_HERE, contents.release());
76    return;
77  }
78
79  // Tab has beforeunload listeners. Install a delegate to run them.
80  delegates_.push_back(
81      new WebContentsDelegateImpl(this, contents.Pass(), index));
82}
83
84void InstantUnloadHandler::Activate(WebContentsDelegateImpl* delegate,
85                                    scoped_ptr<content::WebContents> contents,
86                                    int index) {
87  // Remove (and delete) the delegate.
88  Destroy(delegate);
89
90  // Add the tab back in.
91  chrome::NavigateParams params(browser_, contents.release());
92  params.disposition = NEW_FOREGROUND_TAB;
93  params.tabstrip_index = index;
94  chrome::Navigate(&params);
95}
96
97void InstantUnloadHandler::Destroy(WebContentsDelegateImpl* delegate) {
98  ScopedVector<WebContentsDelegateImpl>::iterator i =
99      std::find(delegates_.begin(), delegates_.end(), delegate);
100  DCHECK(i != delegates_.end());
101
102  // The delegate's method is a caller on the stack, so schedule the deletion
103  // for later.
104  delegates_.weak_erase(i);
105  base::MessageLoop::current()->DeleteSoon(FROM_HERE, delegate);
106}
107