generated_credit_card_bubble_controller.cc revision cedac228d2dd51db4b79ea1e72c7f249408ee061
1// Copyright 2013 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/autofill/generated_credit_card_bubble_controller.h"
6
7#include <climits>
8
9#include "base/logging.h"
10#include "base/prefs/pref_service.h"
11#include "base/strings/string_split.h"
12#include "base/strings/utf_string_conversions.h"
13#include "chrome/browser/profiles/profile.h"
14#include "chrome/browser/ui/autofill/generated_credit_card_bubble_view.h"
15#include "chrome/browser/ui/autofill/tab_autofill_manager_delegate.h"
16#include "chrome/browser/ui/browser_finder.h"
17#include "chrome/browser/ui/browser_navigator.h"
18#include "chrome/browser/ui/browser_window.h"
19#include "chrome/browser/ui/omnibox/location_bar.h"
20#include "chrome/browser/ui/tabs/tab_strip_model.h"
21#include "chrome/common/pref_names.h"
22#include "components/pref_registry/pref_registry_syncable.h"
23#include "content/public/browser/navigation_details.h"
24#include "content/public/browser/navigation_entry.h"
25#include "content/public/browser/web_contents.h"
26#include "grit/generated_resources.h"
27#include "grit/theme_resources.h"
28#include "ui/base/l10n/l10n_util.h"
29#include "ui/base/resource/resource_bundle.h"
30
31DEFINE_WEB_CONTENTS_USER_DATA_KEY(
32    autofill::GeneratedCreditCardBubbleController);
33
34namespace autofill {
35
36namespace {
37
38static const int kMaxGeneratedCardTimesToShow = INT_MAX;
39static const base::char16 kRangeSeparator = '|';
40static const char kWalletGeneratedCardLearnMoreLink[] =
41    "http://support.google.com/wallet/bin/answer.py?hl=en&answer=2740044";
42
43GeneratedCreditCardBubbleController* GetOrCreate(content::WebContents* wc) {
44  GeneratedCreditCardBubbleController::CreateForWebContents(wc);
45  return GeneratedCreditCardBubbleController::FromWebContents(wc);
46}
47
48}  // namespace
49
50bool TextRange::operator==(const TextRange& other) const {
51  return other.range == range && other.is_link == is_link;
52}
53
54GeneratedCreditCardBubbleController::GeneratedCreditCardBubbleController(
55    content::WebContents* web_contents)
56    : WebContentsObserver(web_contents),
57      web_contents_(web_contents),
58      title_text_(l10n_util::GetStringUTF16(
59          IDS_AUTOFILL_GENERATED_CREDIT_CARD_BUBBLE_TITLE)),
60      should_show_anchor_(true),
61      weak_ptr_factory_(this) {}
62
63GeneratedCreditCardBubbleController::~GeneratedCreditCardBubbleController() {
64  // In the case that the tab is closed, the controller can be deleted while
65  // bubble is showing. Always calling |Hide()| ensures that the bubble closes.
66  Hide();
67}
68
69// static
70void GeneratedCreditCardBubbleController::RegisterUserPrefs(
71    user_prefs::PrefRegistrySyncable* registry) {
72  registry->RegisterIntegerPref(
73      ::prefs::kAutofillGeneratedCardBubbleTimesShown,
74      0,
75      user_prefs::PrefRegistrySyncable::SYNCABLE_PREF);
76}
77
78// static
79void GeneratedCreditCardBubbleController::Show(
80    content::WebContents* contents,
81    const base::string16& fronting_card_name,
82    const base::string16& backing_card_name) {
83  GetOrCreate(contents)->SetupAndShow(fronting_card_name, backing_card_name);
84}
85
86void GeneratedCreditCardBubbleController::DidNavigateMainFrame(
87    const content::LoadCommittedDetails& details,
88    const content::FrameNavigateParams& params) {
89  if (!details.entry)
90    return;
91
92  // Don't destory the bubble due to reloads, form submits, or redirects right
93  // after the dialog succeeds. Merchants often navigate to a confirmation page.
94  content::PageTransition transition = details.entry->GetTransitionType();
95  if (transition == content::PAGE_TRANSITION_FORM_SUBMIT ||
96      transition == content::PAGE_TRANSITION_RELOAD ||
97      content::PageTransitionIsRedirect(transition)) {
98    return;
99  }
100
101  should_show_anchor_ = false;
102  UpdateAnchor();
103  web_contents()->RemoveUserData(UserDataKey());
104  // |this| is now deleted.
105}
106
107bool GeneratedCreditCardBubbleController::IsHiding() const {
108  return bubble_ && bubble_->IsHiding();
109}
110
111gfx::Image GeneratedCreditCardBubbleController::AnchorIcon() const {
112  if (!should_show_anchor_)
113    return gfx::Image();
114  return ui::ResourceBundle::GetSharedInstance().GetImageNamed(IDR_WALLET_ICON);
115}
116
117const base::string16& GeneratedCreditCardBubbleController::TitleText() const {
118  return title_text_;
119}
120
121const base::string16& GeneratedCreditCardBubbleController::ContentsText()
122    const {
123  return contents_text_;
124}
125
126const std::vector<TextRange>& GeneratedCreditCardBubbleController::
127    ContentsTextRanges() const {
128  return contents_text_ranges_;
129}
130
131void GeneratedCreditCardBubbleController::OnAnchorClicked() {
132  Show(true);
133}
134
135void GeneratedCreditCardBubbleController::OnLinkClicked() {
136  // Open a new tab to the Online Wallet help link.
137  chrome::NavigateParams params(
138      chrome::FindBrowserWithWebContents(web_contents()),
139      GURL(kWalletGeneratedCardLearnMoreLink),
140      content::PAGE_TRANSITION_AUTO_BOOKMARK);
141  params.disposition = NEW_FOREGROUND_TAB;
142  chrome::Navigate(&params);
143
144  Hide();
145}
146
147base::WeakPtr<GeneratedCreditCardBubbleController>
148    GeneratedCreditCardBubbleController::GetWeakPtr() {
149  return weak_ptr_factory_.GetWeakPtr();
150}
151
152base::WeakPtr<GeneratedCreditCardBubbleView>
153    GeneratedCreditCardBubbleController::CreateBubble() {
154  return GeneratedCreditCardBubbleView::Create(GetWeakPtr());
155}
156
157base::WeakPtr<GeneratedCreditCardBubbleView>
158    GeneratedCreditCardBubbleController::bubble() {
159  return bubble_;
160}
161
162bool GeneratedCreditCardBubbleController::CanShow() const {
163  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
164  return web_contents() == browser->tab_strip_model()->GetActiveWebContents();
165}
166
167bool GeneratedCreditCardBubbleController::ShouldDisplayBubbleInitially() const {
168  Profile* profile = Profile::FromBrowserContext(
169      web_contents_->GetBrowserContext());
170  int times_shown = profile->GetPrefs()->GetInteger(
171      ::prefs::kAutofillGeneratedCardBubbleTimesShown);
172  return times_shown < kMaxGeneratedCardTimesToShow;
173}
174
175void GeneratedCreditCardBubbleController::SetupAndShow(
176    const base::string16& fronting_card_name,
177    const base::string16& backing_card_name) {
178  DCHECK(!fronting_card_name.empty());
179  DCHECK(!backing_card_name.empty());
180
181  fronting_card_name_ = fronting_card_name;
182  backing_card_name_ = backing_card_name;
183
184  // Clear any generated state or from the last |SetupAndShow()| call.
185  contents_text_.clear();
186  contents_text_ranges_.clear();
187
188  base::string16 to_split = l10n_util::GetStringFUTF16(
189      IDS_AUTOFILL_GENERATED_CREDIT_CARD_BUBBLE_CONTENTS,
190      fronting_card_name_,
191      backing_card_name_);
192
193  // Split the full text on '|' to highlight certain parts. For example, "sly"
194  // and "jumped" would be bolded in "The |sly| fox |jumped| over the lazy dog".
195  std::vector<base::string16> pieces;
196  base::SplitStringDontTrim(to_split, kRangeSeparator, &pieces);
197
198  while (!pieces.empty()) {
199    base::string16 piece = pieces.front();
200
201    // Every second piece should be bolded. Because |base::SplitString*()|
202    // leaves an empty "" even if '|' is the first character, this is guaranteed
203    // to work for "|highlighting| starts here". Ignore empty pieces because
204    // there's nothing to highlight.
205    if (!piece.empty() && pieces.size() % 2 == 0) {
206      const size_t start = contents_text_.size();
207      TextRange bold_text;
208      bold_text.range = gfx::Range(start, start + piece.size());
209      bold_text.is_link = false;
210      contents_text_ranges_.push_back(bold_text);
211    }
212
213    // Append the piece whether it's bolded or not and move on to the next one.
214    contents_text_.append(piece);
215    pieces.erase(pieces.begin(), pieces.begin() + 1);
216  }
217
218  // Add a "Learn more" link at the end of the header text if it's a generated
219  // card bubble.
220  base::string16 learn_more = l10n_util::GetStringUTF16(IDS_LEARN_MORE);
221  contents_text_.append(base::ASCIIToUTF16(" ") + learn_more);
222  const size_t header_size = contents_text_.size();
223  TextRange end_link;
224  end_link.range = gfx::Range(header_size - learn_more.size(), header_size);
225  end_link.is_link = true;
226  contents_text_ranges_.push_back(end_link);
227
228  UpdateAnchor();
229
230  if (ShouldDisplayBubbleInitially())
231    Show(false);
232}
233
234void GeneratedCreditCardBubbleController::Show(bool was_anchor_click) {
235  Hide();
236
237  if (!CanShow())
238    return;
239
240  bubble_ = CreateBubble();
241  if (!bubble_) {
242    // TODO(dbeam): Make a bubble on all applicable platforms.
243    return;
244  }
245
246  bubble_->Show();
247
248  if (!was_anchor_click) {
249    // If the bubble was an automatically created "you generated a card" bubble,
250    // count it as a show. If the user clicked the omnibox icon, don't count it.
251    PrefService* prefs = Profile::FromBrowserContext(
252        web_contents()->GetBrowserContext())->GetPrefs();
253    prefs->SetInteger(::prefs::kAutofillGeneratedCardBubbleTimesShown,
254        prefs->GetInteger(::prefs::kAutofillGeneratedCardBubbleTimesShown) + 1);
255  }
256}
257
258void GeneratedCreditCardBubbleController::UpdateAnchor() {
259  Browser* browser = chrome::FindBrowserWithWebContents(web_contents());
260  if (browser && browser->window() && browser->window()->GetLocationBar())
261    browser->window()->GetLocationBar()->UpdateGeneratedCreditCardView();
262}
263
264void GeneratedCreditCardBubbleController::Hide() {
265  if (bubble_ && !bubble_->IsHiding())
266    bubble_->Hide();
267}
268
269}  // namespace autofill
270