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/search/instant_page.h"
6
7#include "apps/app_launcher.h"
8#include "base/strings/utf_string_conversions.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/history/most_visited_tiles_experiment.h"
11#include "chrome/browser/history/top_sites.h"
12#include "chrome/browser/profiles/profile.h"
13#include "chrome/browser/search/instant_service.h"
14#include "chrome/browser/search/instant_service_factory.h"
15#include "chrome/browser/search/search.h"
16#include "chrome/browser/ui/browser_finder.h"
17#include "chrome/browser/ui/search/instant_tab.h"
18#include "chrome/browser/ui/search/search_model.h"
19#include "chrome/browser/ui/search/search_tab_helper.h"
20#include "chrome/browser/ui/tabs/tab_strip_model.h"
21#include "chrome/browser/ui/tabs/tab_strip_model_utils.h"
22#include "chrome/common/render_messages.h"
23#include "chrome/common/url_constants.h"
24#include "content/public/browser/navigation_controller.h"
25#include "content/public/browser/navigation_details.h"
26#include "content/public/browser/navigation_entry.h"
27#include "content/public/browser/notification_service.h"
28#include "content/public/browser/notification_source.h"
29#include "content/public/browser/web_contents.h"
30#include "content/public/common/frame_navigate_params.h"
31#include "ui/base/resource/resource_bundle.h"
32#include "ui/gfx/font.h"
33
34InstantPage::Delegate::~Delegate() {
35}
36
37InstantPage::~InstantPage() {
38  if (contents())
39    SearchTabHelper::FromWebContents(contents())->model()->RemoveObserver(this);
40
41  // |profile_| may be NULL during unit tests.
42  if (profile_) {
43    InstantService* instant_service =
44        InstantServiceFactory::GetForProfile(profile_);
45    instant_service->RemoveObserver(this);
46  }
47}
48
49bool InstantPage::supports_instant() const {
50  return contents() ?
51      SearchTabHelper::FromWebContents(contents())->SupportsInstant() : false;
52}
53
54const std::string& InstantPage::instant_url() const {
55  return instant_url_;
56}
57
58bool InstantPage::IsLocal() const {
59  return contents() &&
60      contents()->GetURL() == GURL(chrome::kChromeSearchLocalNtpUrl);
61}
62
63void InstantPage::InitializeFonts() {
64#if defined(OS_MACOSX)
65  // This value should be kept in sync with OmniboxViewMac::GetFieldFont.
66  const gfx::Font& omnibox_font =
67      ui::ResourceBundle::GetSharedInstance().GetFont(
68          ui::ResourceBundle::MediumFont).DeriveFont(1);
69#else
70  const gfx::Font& omnibox_font =
71      ui::ResourceBundle::GetSharedInstance().GetFont(
72          ui::ResourceBundle::MediumFont);
73#endif
74  sender()->SetFontInformation(UTF8ToUTF16(omnibox_font.GetFontName()),
75                               omnibox_font.GetFontSize());
76}
77
78void InstantPage::InitializePromos() {
79  sender()->SetPromoInformation(apps::IsAppLauncherEnabled());
80}
81
82InstantPage::InstantPage(Delegate* delegate, const std::string& instant_url,
83                         Profile* profile, bool is_incognito)
84    : profile_(profile),
85      delegate_(delegate),
86      ipc_sender_(InstantIPCSender::Create(is_incognito)),
87      instant_url_(instant_url),
88      is_incognito_(is_incognito) {
89  // |profile_| may be NULL during unit tests.
90  if (profile_) {
91    InstantService* instant_service =
92        InstantServiceFactory::GetForProfile(profile_);
93    instant_service->AddObserver(this);
94  }
95}
96
97void InstantPage::SetContents(content::WebContents* web_contents) {
98  ClearContents();
99
100  if (!web_contents)
101    return;
102
103  sender()->SetContents(web_contents);
104  Observe(web_contents);
105  SearchModel* model = SearchTabHelper::FromWebContents(contents())->model();
106  model->AddObserver(this);
107
108  // Already know whether the page supports instant.
109  if (model->instant_support() != INSTANT_SUPPORT_UNKNOWN)
110    InstantSupportDetermined(model->instant_support() == INSTANT_SUPPORT_YES);
111}
112
113bool InstantPage::ShouldProcessAboutToNavigateMainFrame() {
114  return false;
115}
116
117bool InstantPage::ShouldProcessFocusOmnibox() {
118  return false;
119}
120
121bool InstantPage::ShouldProcessNavigateToURL() {
122  return false;
123}
124
125bool InstantPage::ShouldProcessPasteIntoOmnibox() {
126  return false;
127}
128
129bool InstantPage::ShouldProcessDeleteMostVisitedItem() {
130  return false;
131}
132
133bool InstantPage::ShouldProcessUndoMostVisitedDeletion() {
134  return false;
135}
136
137bool InstantPage::ShouldProcessUndoAllMostVisitedDeletions() {
138  return false;
139}
140
141bool InstantPage::OnMessageReceived(const IPC::Message& message) {
142  if (is_incognito_)
143    return false;
144
145  bool handled = true;
146  IPC_BEGIN_MESSAGE_MAP(InstantPage, message)
147    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_FocusOmnibox, OnFocusOmnibox)
148    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SearchBoxNavigate,
149                        OnSearchBoxNavigate);
150    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_PasteAndOpenDropdown,
151                        OnSearchBoxPaste);
152    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_CountMouseover, OnCountMouseover);
153    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SearchBoxDeleteMostVisitedItem,
154                        OnDeleteMostVisitedItem);
155    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SearchBoxUndoMostVisitedDeletion,
156                        OnUndoMostVisitedDeletion);
157    IPC_MESSAGE_HANDLER(ChromeViewHostMsg_SearchBoxUndoAllMostVisitedDeletions,
158                        OnUndoAllMostVisitedDeletions);
159    IPC_MESSAGE_UNHANDLED(handled = false)
160  IPC_END_MESSAGE_MAP()
161  return handled;
162}
163
164void InstantPage::DidCommitProvisionalLoadForFrame(
165    int64 /* frame_id */,
166    bool is_main_frame,
167    const GURL& url,
168    content::PageTransition /* transition_type */,
169    content::RenderViewHost* /* render_view_host */) {
170  if (is_main_frame && ShouldProcessAboutToNavigateMainFrame())
171    delegate_->InstantPageAboutToNavigateMainFrame(contents(), url);
172}
173
174void InstantPage::DidNavigateMainFrame(
175    const content::LoadCommittedDetails& details,
176    const content::FrameNavigateParams& /* params */) {
177  // A 204 can be sent by the search provider as a lightweight signal
178  // to fall back to the local page, and we obviously want to fall back
179  // if we get any response code that indicates an error.
180  if (details.http_status_code == 204 || details.http_status_code >= 400)
181    delegate_->InstantPageLoadFailed(contents());
182}
183
184void InstantPage::DidFailProvisionalLoad(
185    int64 /* frame_id */,
186    bool is_main_frame,
187    const GURL& /* validated_url */,
188    int /* error_code */,
189    const string16& /* error_description */,
190    content::RenderViewHost* /* render_view_host */) {
191  if (is_main_frame)
192    delegate_->InstantPageLoadFailed(contents());
193}
194
195void InstantPage::ThemeInfoChanged(const ThemeBackgroundInfo& theme_info) {
196  sender()->SendThemeBackgroundInfo(theme_info);
197}
198
199void InstantPage::MostVisitedItemsChanged(
200    const std::vector<InstantMostVisitedItem>& items) {
201  std::vector<InstantMostVisitedItem> items_copy(items);
202  MaybeRemoveMostVisitedItems(&items_copy);
203
204  sender()->SendMostVisitedItems(items_copy);
205
206  content::NotificationService::current()->Notify(
207      chrome::NOTIFICATION_INSTANT_SENT_MOST_VISITED_ITEMS,
208      content::Source<InstantPage>(this),
209      content::NotificationService::NoDetails());
210}
211
212void InstantPage::ModelChanged(const SearchModel::State& old_state,
213                               const SearchModel::State& new_state) {
214  if (old_state.instant_support != new_state.instant_support)
215    InstantSupportDetermined(new_state.instant_support == INSTANT_SUPPORT_YES);
216}
217
218void InstantPage::InstantSupportDetermined(bool supports_instant) {
219  delegate_->InstantSupportDetermined(contents(), supports_instant);
220
221  // If the page doesn't support Instant, stop listening to it.
222  if (!supports_instant)
223    ClearContents();
224}
225
226void InstantPage::OnFocusOmnibox(int page_id, OmniboxFocusState state) {
227  if (!contents()->IsActiveEntry(page_id))
228    return;
229
230  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
231  if (!ShouldProcessFocusOmnibox())
232    return;
233
234  delegate_->FocusOmnibox(contents(), state);
235}
236
237void InstantPage::OnSearchBoxNavigate(int page_id,
238                                      const GURL& url,
239                                      content::PageTransition transition,
240                                      WindowOpenDisposition disposition,
241                                      bool is_search_type) {
242  if (!contents()->IsActiveEntry(page_id))
243    return;
244
245  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
246  if (!ShouldProcessNavigateToURL())
247    return;
248
249  delegate_->NavigateToURL(
250      contents(), url, transition, disposition, is_search_type);
251}
252
253void InstantPage::OnSearchBoxPaste(int page_id, const string16& text) {
254  if (!contents()->IsActiveEntry(page_id))
255    return;
256
257  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
258  if (!ShouldProcessPasteIntoOmnibox())
259    return;
260
261  delegate_->PasteIntoOmnibox(contents(), text);
262}
263
264void InstantPage::OnCountMouseover(int page_id) {
265  if (!contents()->IsActiveEntry(page_id))
266    return;
267
268  InstantTab::CountMouseover(contents());
269}
270
271void InstantPage::OnDeleteMostVisitedItem(int page_id, const GURL& url) {
272  if (!contents()->IsActiveEntry(page_id))
273    return;
274
275  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
276  if (!ShouldProcessDeleteMostVisitedItem())
277    return;
278
279  delegate_->DeleteMostVisitedItem(url);
280}
281
282void InstantPage::OnUndoMostVisitedDeletion(int page_id, const GURL& url) {
283  if (!contents()->IsActiveEntry(page_id))
284    return;
285
286  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
287  if (!ShouldProcessUndoMostVisitedDeletion())
288    return;
289
290  delegate_->UndoMostVisitedDeletion(url);
291}
292
293void InstantPage::OnUndoAllMostVisitedDeletions(int page_id) {
294  if (!contents()->IsActiveEntry(page_id))
295    return;
296
297  SearchTabHelper::FromWebContents(contents())->InstantSupportChanged(true);
298  if (!ShouldProcessUndoAllMostVisitedDeletions())
299    return;
300
301  delegate_->UndoAllMostVisitedDeletions();
302}
303
304void InstantPage::ClearContents() {
305  if (contents())
306    SearchTabHelper::FromWebContents(contents())->model()->RemoveObserver(this);
307
308  sender()->SetContents(NULL);
309  Observe(NULL);
310}
311
312void InstantPage::MaybeRemoveMostVisitedItems(
313    std::vector<InstantMostVisitedItem>* items) {
314// The code below uses APIs not available on Android and the experiment should
315// not run there.
316#if !defined(OS_ANDROID)
317  if (!history::MostVisitedTilesExperiment::IsDontShowOpenURLsEnabled())
318    return;
319
320  Browser* browser = chrome::FindBrowserWithProfile(profile_,
321                                                    chrome::GetActiveDesktop());
322  if (!browser)
323    return;
324
325  TabStripModel* tab_strip_model = browser->tab_strip_model();
326  history::TopSites* top_sites = profile_->GetTopSites();
327  if (!tab_strip_model || !top_sites) {
328    NOTREACHED();
329    return;
330  }
331
332  std::set<std::string> open_urls;
333  chrome::GetOpenUrls(*tab_strip_model, *top_sites, &open_urls);
334  history::MostVisitedTilesExperiment::RemoveItemsMatchingOpenTabs(
335      open_urls, items);
336
337#endif
338}
339