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