supervised_user_interstitial.cc revision 1320f92c476a1ad9d19dba2a48c72b75566198e9
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 "chrome/browser/supervised_user/supervised_user_interstitial.h"
6
7#include "base/memory/weak_ptr.h"
8#include "base/metrics/histogram.h"
9#include "base/prefs/pref_service.h"
10#include "base/strings/string_number_conversions.h"
11#include "base/strings/utf_string_conversions.h"
12#include "base/values.h"
13#include "chrome/browser/infobars/infobar_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/supervised_user/supervised_user_service.h"
16#include "chrome/browser/supervised_user/supervised_user_service_factory.h"
17#include "chrome/common/pref_names.h"
18#include "chrome/grit/generated_resources.h"
19#include "components/infobars/core/infobar.h"
20#include "components/infobars/core/infobar_delegate.h"
21#include "content/public/browser/browser_thread.h"
22#include "content/public/browser/interstitial_page.h"
23#include "content/public/browser/navigation_controller.h"
24#include "content/public/browser/navigation_details.h"
25#include "content/public/browser/navigation_entry.h"
26#include "content/public/browser/web_contents.h"
27#include "content/public/browser/web_contents_user_data.h"
28#include "content/public/browser/web_ui.h"
29#include "grit/browser_resources.h"
30#include "ui/base/l10n/l10n_util.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/base/webui/jstemplate_builder.h"
33#include "ui/base/webui/web_ui_util.h"
34
35#if !defined(OS_ANDROID)
36#include "chrome/browser/ui/browser_finder.h"
37#include "chrome/browser/ui/tabs/tab_strip_model.h"
38#endif
39
40using content::BrowserThread;
41using content::WebContents;
42
43namespace {
44
45static const int kAvatarSize1x = 45;
46static const int kAvatarSize2x = 90;
47
48std::string BuildAvatarImageUrl(const std::string& url, int size) {
49  std::string result = url;
50  size_t slash = result.rfind('/');
51  if (slash != std::string::npos)
52    result.insert(slash, "/s" + base::IntToString(size));
53  return result;
54}
55
56class TabCloser : public content::WebContentsUserData<TabCloser> {
57  // To use, call TabCloser::CreateForWebContents.
58 private:
59  friend class content::WebContentsUserData<TabCloser>;
60
61  explicit TabCloser(WebContents* web_contents)
62      : web_contents_(web_contents), weak_ptr_factory_(this) {
63    BrowserThread::PostTask(
64        BrowserThread::UI,
65        FROM_HERE,
66        base::Bind(&TabCloser::CloseTabImpl, weak_ptr_factory_.GetWeakPtr()));
67  }
68  virtual ~TabCloser() {}
69
70  void CloseTabImpl() {
71    // On Android, FindBrowserWithWebContents and TabStripModel don't exist.
72#if !defined(OS_ANDROID)
73    Browser* browser = chrome::FindBrowserWithWebContents(web_contents_);
74    DCHECK(browser);
75    TabStripModel* tab_strip = browser->tab_strip_model();
76    DCHECK_NE(TabStripModel::kNoTab,
77              tab_strip->GetIndexOfWebContents(web_contents_));
78    if (tab_strip->count() <= 1) {
79      // Don't close the last tab in the window.
80      web_contents_->RemoveUserData(UserDataKey());
81      return;
82    }
83#endif
84    web_contents_->Close();
85  }
86
87  WebContents* web_contents_;
88  base::WeakPtrFactory<TabCloser> weak_ptr_factory_;
89};
90
91}  // namespace
92
93DEFINE_WEB_CONTENTS_USER_DATA_KEY(TabCloser);
94
95// static
96void SupervisedUserInterstitial::Show(
97    WebContents* web_contents,
98    const GURL& url,
99    const base::Callback<void(bool)>& callback) {
100  SupervisedUserInterstitial* interstitial =
101      new SupervisedUserInterstitial(web_contents, url, callback);
102
103  // If Init() does not complete fully, immediately delete the interstitial.
104  if (!interstitial->Init())
105    delete interstitial;
106  // Otherwise |interstitial_page_| is responsible for deleting it.
107}
108
109SupervisedUserInterstitial::SupervisedUserInterstitial(
110    WebContents* web_contents,
111    const GURL& url,
112    const base::Callback<void(bool)>& callback)
113    : web_contents_(web_contents),
114      profile_(Profile::FromBrowserContext(web_contents->GetBrowserContext())),
115      interstitial_page_(NULL),
116      url_(url),
117      callback_(callback) {}
118
119SupervisedUserInterstitial::~SupervisedUserInterstitial() {
120  DCHECK(!web_contents_);
121}
122
123bool SupervisedUserInterstitial::Init() {
124  if (ShouldProceed()) {
125    // It can happen that the site was only allowed very recently and the URL
126    // filter on the IO thread had not been updated yet. Proceed with the
127    // request without showing the interstitial.
128    DispatchContinueRequest(true);
129    return false;
130  }
131
132  InfoBarService* service = InfoBarService::FromWebContents(web_contents_);
133  if (service) {
134    // Remove all the infobars which are attached to |web_contents_| and for
135    // which ShouldExpire() returns true.
136    content::LoadCommittedDetails details;
137    // |details.is_in_page| is default false, and |details.is_main_frame| is
138    // default true. This results in is_navigation_to_different_page() returning
139    // true.
140    DCHECK(details.is_navigation_to_different_page());
141    const content::NavigationController& controller =
142        web_contents_->GetController();
143    details.entry = controller.GetActiveEntry();
144    if (controller.GetLastCommittedEntry()) {
145      details.previous_entry_index = controller.GetLastCommittedEntryIndex();
146      details.previous_url = controller.GetLastCommittedEntry()->GetURL();
147    }
148    details.type = content::NAVIGATION_TYPE_NEW_PAGE;
149    for (int i = service->infobar_count() - 1; i >= 0; --i) {
150      infobars::InfoBar* infobar = service->infobar_at(i);
151      if (infobar->delegate()->ShouldExpire(
152              InfoBarService::NavigationDetailsFromLoadCommittedDetails(
153                  details)))
154        service->RemoveInfoBar(infobar);
155    }
156  }
157
158  SupervisedUserService* supervised_user_service =
159      SupervisedUserServiceFactory::GetForProfile(profile_);
160  supervised_user_service->AddObserver(this);
161
162  interstitial_page_ =
163      content::InterstitialPage::Create(web_contents_, true, url_, this);
164  interstitial_page_->Show();
165
166  return true;
167}
168
169std::string SupervisedUserInterstitial::GetHTMLContents() {
170  base::DictionaryValue strings;
171  strings.SetString("blockPageTitle",
172                    l10n_util::GetStringUTF16(IDS_BLOCK_INTERSTITIAL_TITLE));
173
174  SupervisedUserService* supervised_user_service =
175      SupervisedUserServiceFactory::GetForProfile(profile_);
176
177  bool allow_access_requests = supervised_user_service->AccessRequestsEnabled();
178  strings.SetBoolean("allowAccessRequests", allow_access_requests);
179
180  std::string profile_image_url = profile_->GetPrefs()->GetString(
181      prefs::kSupervisedUserCustodianProfileImageURL);
182  strings.SetString("avatarURL1x", BuildAvatarImageUrl(profile_image_url,
183                                                       kAvatarSize1x));
184  strings.SetString("avatarURL2x", BuildAvatarImageUrl(profile_image_url,
185                                                       kAvatarSize2x));
186
187  std::string profile_image_url2 = profile_->GetPrefs()->GetString(
188      prefs::kSupervisedUserSecondCustodianProfileImageURL);
189  strings.SetString("secondAvatarURL1x", BuildAvatarImageUrl(profile_image_url2,
190                                                             kAvatarSize1x));
191  strings.SetString("secondAvatarURL2x", BuildAvatarImageUrl(profile_image_url2,
192                                                             kAvatarSize2x));
193
194  base::string16 custodian =
195      base::UTF8ToUTF16(supervised_user_service->GetCustodianName());
196  strings.SetString(
197      "blockPageMessage",
198      allow_access_requests
199          ? l10n_util::GetStringFUTF16(IDS_BLOCK_INTERSTITIAL_MESSAGE,
200                                       custodian)
201          : l10n_util::GetStringUTF16(
202                IDS_BLOCK_INTERSTITIAL_MESSAGE_ACCESS_REQUESTS_DISABLED));
203
204  strings.SetString("backButton", l10n_util::GetStringUTF16(IDS_BACK_BUTTON));
205  strings.SetString(
206      "requestAccessButton",
207      l10n_util::GetStringUTF16(IDS_BLOCK_INTERSTITIAL_REQUEST_ACCESS_BUTTON));
208
209  strings.SetString(
210      "requestSentMessage",
211      l10n_util::GetStringFUTF16(IDS_BLOCK_INTERSTITIAL_REQUEST_SENT_MESSAGE,
212                                 custodian));
213
214  webui::SetFontAndTextDirection(&strings);
215
216  base::StringPiece html(ResourceBundle::GetSharedInstance().GetRawDataResource(
217      IDR_SUPERVISED_USER_BLOCK_INTERSTITIAL_HTML));
218
219  webui::UseVersion2 version;
220  return webui::GetI18nTemplateHtml(html, &strings);
221}
222
223void SupervisedUserInterstitial::CommandReceived(const std::string& command) {
224  // For use in histograms.
225  enum Commands {
226    PREVIEW,
227    BACK,
228    NTP,
229    ACCESS_REQUEST,
230    HISTOGRAM_BOUNDING_VALUE
231  };
232
233  if (command == "\"back\"") {
234    UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand",
235                              BACK,
236                              HISTOGRAM_BOUNDING_VALUE);
237
238    // Close the tab if there is no history entry to go back to.
239    DCHECK(web_contents_->GetController().GetTransientEntry());
240    if (web_contents_->GetController().GetEntryCount() == 1)
241      TabCloser::CreateForWebContents(web_contents_);
242
243    interstitial_page_->DontProceed();
244    return;
245  }
246
247  if (command == "\"request\"") {
248    UMA_HISTOGRAM_ENUMERATION("ManagedMode.BlockingInterstitialCommand",
249                              ACCESS_REQUEST,
250                              HISTOGRAM_BOUNDING_VALUE);
251
252    SupervisedUserService* supervised_user_service =
253        SupervisedUserServiceFactory::GetForProfile(profile_);
254    supervised_user_service->AddAccessRequest(url_);
255    DVLOG(1) << "Sent access request for " << url_.spec();
256
257    return;
258  }
259
260  NOTREACHED();
261}
262
263void SupervisedUserInterstitial::OnProceed() {
264  // CHECK instead of DCHECK as defense in depth in case we'd accidentally
265  // proceed on a blocked page.
266  CHECK(ShouldProceed());
267  DispatchContinueRequest(true);
268}
269
270void SupervisedUserInterstitial::OnDontProceed() {
271  DispatchContinueRequest(false);
272}
273
274void SupervisedUserInterstitial::OnURLFilterChanged() {
275  if (ShouldProceed())
276    interstitial_page_->Proceed();
277}
278
279bool SupervisedUserInterstitial::ShouldProceed() {
280  SupervisedUserService* supervised_user_service =
281      SupervisedUserServiceFactory::GetForProfile(profile_);
282  SupervisedUserURLFilter* url_filter =
283      supervised_user_service->GetURLFilterForUIThread();
284  return url_filter->GetFilteringBehaviorForURL(url_) !=
285         SupervisedUserURLFilter::BLOCK;
286}
287
288void SupervisedUserInterstitial::DispatchContinueRequest(
289    bool continue_request) {
290  SupervisedUserService* supervised_user_service =
291      SupervisedUserServiceFactory::GetForProfile(profile_);
292  supervised_user_service->RemoveObserver(this);
293
294  BrowserThread::PostTask(
295      BrowserThread::IO, FROM_HERE, base::Bind(callback_, continue_request));
296
297  // After this, the WebContents may be destroyed. Make sure we don't try to use
298  // it again.
299  web_contents_ = NULL;
300}
301