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_ntp_prerenderer.h"
6
7#include "base/basictypes.h"
8#include "base/bind.h"
9#include "base/message_loop/message_loop.h"
10#include "base/prefs/pref_service.h"
11#include "build/build_config.h"
12#include "chrome/browser/chrome_notification_types.h"
13#include "chrome/browser/content_settings/content_settings_provider.h"
14#include "chrome/browser/content_settings/host_content_settings_map.h"
15#include "chrome/browser/profiles/profile.h"
16#include "chrome/browser/search/instant_service.h"
17#include "chrome/browser/search/instant_service_factory.h"
18#include "chrome/browser/search/search.h"
19#include "chrome/browser/ui/browser.h"
20#include "chrome/browser/ui/browser_finder.h"
21#include "chrome/browser/ui/host_desktop.h"
22#include "chrome/browser/ui/search/instant_ntp.h"
23#include "chrome/browser/ui/tabs/tab_strip_model.h"
24#include "chrome/common/content_settings.h"
25#include "chrome/common/pref_names.h"
26#include "content/public/browser/notification_service.h"
27#include "content/public/browser/render_process_host.h"
28#include "content/public/browser/web_contents.h"
29#include "net/base/network_change_notifier.h"
30
31namespace {
32
33void DeleteNTPSoon(scoped_ptr<InstantNTP> ntp) {
34  if (!ntp)
35    return;
36
37  if (ntp->contents()) {
38    base::MessageLoop::current()->DeleteSoon(
39        FROM_HERE, ntp->ReleaseContents().release());
40  }
41  base::MessageLoop::current()->DeleteSoon(FROM_HERE, ntp.release());
42}
43
44}  // namespace
45
46
47InstantNTPPrerenderer::InstantNTPPrerenderer(Profile* profile,
48                                             PrefService* prefs)
49    : profile_(profile) {
50  DCHECK(profile);
51
52  // In unit tests, prefs may be NULL.
53  if (prefs) {
54    profile_pref_registrar_.Init(prefs);
55    profile_pref_registrar_.Add(
56        prefs::kSearchSuggestEnabled,
57        base::Bind(&InstantNTPPrerenderer::ReloadStaleNTP,
58                   base::Unretained(this)));
59    profile_pref_registrar_.Add(
60        prefs::kDefaultSearchProviderID,
61        base::Bind(&InstantNTPPrerenderer::OnDefaultSearchProviderChanged,
62                   base::Unretained(this)));
63  }
64  net::NetworkChangeNotifier::AddNetworkChangeObserver(this);
65}
66
67InstantNTPPrerenderer::~InstantNTPPrerenderer() {
68  net::NetworkChangeNotifier::RemoveNetworkChangeObserver(this);
69}
70
71void InstantNTPPrerenderer::PreloadInstantNTP() {
72  DCHECK(!ntp());
73  ReloadStaleNTP();
74}
75
76scoped_ptr<content::WebContents> InstantNTPPrerenderer::ReleaseNTPContents() {
77  if (!profile_ || profile_->IsOffTheRecord() ||
78      !chrome::ShouldShowInstantNTP())
79    return scoped_ptr<content::WebContents>();
80
81  if (ShouldSwitchToLocalNTP())
82    ResetNTP(GetLocalInstantURL());
83
84  scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents();
85
86  // Preload a new InstantNTP.
87  ResetNTP(GetInstantURL());
88  return ntp_contents.Pass();
89}
90
91content::WebContents* InstantNTPPrerenderer::GetNTPContents() const {
92  return ntp() ? ntp()->contents() : NULL;
93}
94
95void InstantNTPPrerenderer::DeleteNTPContents() {
96  if (ntp_)
97    ntp_.reset();
98}
99
100void InstantNTPPrerenderer::RenderProcessGone() {
101  DeleteNTPSoon(ntp_.Pass());
102}
103
104void InstantNTPPrerenderer::LoadCompletedMainFrame() {
105  if (!ntp_ || ntp_->supports_instant())
106    return;
107
108  content::WebContents* ntp_contents = ntp_->contents();
109  DCHECK(ntp_contents);
110
111  InstantService* instant_service =
112      InstantServiceFactory::GetForProfile(profile());
113  if (instant_service &&
114      instant_service->IsInstantProcess(
115          ntp_contents->GetRenderProcessHost()->GetID())) {
116    return;
117  }
118  InstantSupportDetermined(ntp_contents, false);
119}
120
121std::string InstantNTPPrerenderer::GetLocalInstantURL() const {
122  return chrome::GetLocalInstantURL(profile_).spec();
123}
124
125std::string InstantNTPPrerenderer::GetInstantURL() const {
126  if (net::NetworkChangeNotifier::IsOffline())
127    return GetLocalInstantURL();
128
129  // TODO(kmadhusu): Remove start margin param from chrome::GetInstantURL().
130  const GURL instant_url = chrome::GetInstantURL(profile_,
131                                                 chrome::kDisableStartMargin);
132  if (!instant_url.is_valid())
133    return GetLocalInstantURL();
134
135  return instant_url.spec();
136}
137
138bool InstantNTPPrerenderer::IsJavascriptEnabled() const {
139  GURL instant_url(GetInstantURL());
140  GURL origin(instant_url.GetOrigin());
141  ContentSetting js_setting = profile_->GetHostContentSettingsMap()->
142      GetContentSetting(origin, origin, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
143                        NO_RESOURCE_IDENTIFIER);
144  // Javascript can be disabled either in content settings or via a WebKit
145  // preference, so check both. Disabling it through the Settings page affects
146  // content settings. I'm not sure how to disable the WebKit preference, but
147  // it's theoretically possible some users have it off.
148  bool js_content_enabled =
149      js_setting == CONTENT_SETTING_DEFAULT ||
150      js_setting == CONTENT_SETTING_ALLOW;
151  bool js_webkit_enabled = profile_->GetPrefs()->GetBoolean(
152      prefs::kWebKitJavascriptEnabled);
153  return js_content_enabled && js_webkit_enabled;
154}
155
156bool InstantNTPPrerenderer::InStartup() const {
157#if !defined(OS_ANDROID)
158  // TODO(kmadhusu): This is not completely reliable. Find a better way to
159  // detect startup time.
160  Browser* browser = chrome::FindBrowserWithProfile(profile_,
161                                                    chrome::GetActiveDesktop());
162  return !browser || !browser->tab_strip_model()->GetActiveWebContents();
163#endif
164  return false;
165}
166
167InstantNTP* InstantNTPPrerenderer::ntp() const {
168  return ntp_.get();
169}
170
171void InstantNTPPrerenderer::OnNetworkChanged(
172    net::NetworkChangeNotifier::ConnectionType type) {
173  // Not interested in events conveying change to offline.
174  if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
175    return;
176
177  if (!ntp() || ntp()->IsLocal())
178    ResetNTP(GetInstantURL());
179}
180
181void InstantNTPPrerenderer::InstantSupportDetermined(
182    const content::WebContents* contents,
183    bool supports_instant) {
184  DCHECK(ntp() && ntp()->contents() == contents);
185
186  if (!supports_instant) {
187    bool is_local = ntp()->IsLocal();
188    DeleteNTPSoon(ntp_.Pass());
189    if (!is_local)
190      ResetNTP(GetLocalInstantURL());
191  }
192
193  content::NotificationService::current()->Notify(
194      chrome::NOTIFICATION_INSTANT_NTP_SUPPORT_DETERMINED,
195      content::Source<InstantNTPPrerenderer>(this),
196      content::NotificationService::NoDetails());
197}
198
199void InstantNTPPrerenderer::InstantPageAboutToNavigateMainFrame(
200    const content::WebContents* /* contents */,
201    const GURL& /* url */) {
202  NOTREACHED();
203}
204
205void InstantNTPPrerenderer::FocusOmnibox(
206    const content::WebContents* /* contents */,
207    OmniboxFocusState /* state */) {
208  NOTREACHED();
209}
210
211void InstantNTPPrerenderer::NavigateToURL(
212    const content::WebContents* /* contents */,
213    const GURL& /* url */,
214    content::PageTransition /* transition */,
215    WindowOpenDisposition /* disposition */,
216    bool /* is_search_type */) {
217  NOTREACHED();
218}
219
220void InstantNTPPrerenderer::PasteIntoOmnibox(
221    const content::WebContents* /* contents */,
222    const string16& /* text */) {
223  NOTREACHED();
224}
225
226void InstantNTPPrerenderer::DeleteMostVisitedItem(const GURL& /* url */) {
227  NOTREACHED();
228}
229
230void InstantNTPPrerenderer::UndoMostVisitedDeletion(const GURL& /* url */) {
231  NOTREACHED();
232}
233
234void InstantNTPPrerenderer::UndoAllMostVisitedDeletions() {
235  NOTREACHED();
236}
237
238void InstantNTPPrerenderer::InstantPageLoadFailed(
239    content::WebContents* contents) {
240  DCHECK(ntp() && ntp()->contents() == contents);
241
242  bool is_local = ntp()->IsLocal();
243  DeleteNTPSoon(ntp_.Pass());
244  if (!is_local)
245    ResetNTP(GetLocalInstantURL());
246}
247
248void InstantNTPPrerenderer::OnDefaultSearchProviderChanged(
249    const std::string& pref_name) {
250  DCHECK_EQ(pref_name, std::string(prefs::kDefaultSearchProviderID));
251  if (!ntp())
252    return;
253
254  ResetNTP(GetInstantURL());
255}
256
257void InstantNTPPrerenderer::ResetNTP(const std::string& instant_url) {
258  // Instant NTP is only used in extended mode so we should always have a
259  // non-empty URL to use.
260  DCHECK(!instant_url.empty());
261  ntp_.reset(new InstantNTP(this, instant_url, profile_));
262  ntp_->InitContents(base::Bind(&InstantNTPPrerenderer::ReloadStaleNTP,
263                                base::Unretained(this)));
264}
265
266void InstantNTPPrerenderer::ReloadStaleNTP() {
267  ResetNTP(GetInstantURL());
268}
269
270bool InstantNTPPrerenderer::PageIsCurrent() const {
271  const std::string& instant_url = GetInstantURL();
272  if (instant_url.empty() ||
273      !chrome::MatchesOriginAndPath(GURL(ntp()->instant_url()),
274                                    GURL(instant_url)))
275    return false;
276
277  return ntp()->supports_instant();
278}
279
280bool InstantNTPPrerenderer::ShouldSwitchToLocalNTP() const {
281  if (!ntp())
282    return true;
283
284  // Assume users with Javascript disabled do not want the online experience.
285  if (!IsJavascriptEnabled())
286    return true;
287
288  // Already a local page. Not calling IsLocal() because we want to distinguish
289  // between the Google-specific and generic local NTP.
290  if (ntp()->instant_url() == GetLocalInstantURL())
291    return false;
292
293  if (PageIsCurrent())
294    return false;
295
296  // The preloaded NTP does not support instant yet. If we're not in startup,
297  // always fall back to the local NTP. If we are in startup, use the local NTP
298  // (unless the finch flag to use the remote NTP is set).
299  return !(InStartup() && chrome::ShouldPreferRemoteNTPOnStartup());
300}
301