blob: 77f94b9f38fbfc84a7290ae8958e6f97d4d43a8c [file] [log] [blame]
[email protected]e41982a72012-11-20 07:16:511// Copyright 2012 The Chromium Authors. All rights reserved.
[email protected]fd2b9ce2010-08-11 04:03:572// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
[email protected]03bb7102013-03-17 22:44:475#include "chrome/browser/ui/search/instant_controller.h"
[email protected]fd2b9ce2010-08-11 04:03:576
[email protected]7e03e812010-11-15 23:01:017#include "base/metrics/histogram.h"
[email protected]2b3ded92013-06-19 05:48:438#include "base/prefs/pref_service.h"
[email protected]f92d65882013-06-10 22:02:369#include "base/strings/stringprintf.h"
[email protected]1a7ff742013-07-12 00:26:2310#include "chrome/browser/chrome_notification_types.h"
[email protected]2b3ded92013-06-19 05:48:4311#include "chrome/browser/content_settings/content_settings_provider.h"
12#include "chrome/browser/content_settings/host_content_settings_map.h"
[email protected]ba6680f2010-11-01 20:35:0813#include "chrome/browser/platform_util.h"
[email protected]d58688c62013-07-03 23:09:1214#include "chrome/browser/profiles/profile.h"
[email protected]a7b8e43d2013-03-18 18:52:4315#include "chrome/browser/search/instant_service.h"
16#include "chrome/browser/search/instant_service_factory.h"
17#include "chrome/browser/search/search.h"
[email protected]c55364c2013-03-06 00:24:4318#include "chrome/browser/search_engines/search_terms_data.h"
[email protected]8e5c89a2011-06-07 18:13:3319#include "chrome/browser/search_engines/template_url_service.h"
20#include "chrome/browser/search_engines/template_url_service_factory.h"
[email protected]4418dbb2012-10-25 03:21:5421#include "chrome/browser/ui/browser_instant_controller.h"
[email protected]03bb7102013-03-17 22:44:4722#include "chrome/browser/ui/search/instant_ntp.h"
[email protected]03bb7102013-03-17 22:44:4723#include "chrome/browser/ui/search/instant_tab.h"
[email protected]23f02b22012-10-26 06:08:2724#include "chrome/browser/ui/search/search_tab_helper.h"
[email protected]fd2b9ce2010-08-11 04:03:5725#include "chrome/common/chrome_switches.h"
[email protected]2b3ded92013-06-19 05:48:4326#include "chrome/common/content_settings_types.h"
27#include "chrome/common/pref_names.h"
[email protected]0c9406632013-02-08 01:13:3328#include "chrome/common/url_constants.h"
[email protected]40a7e412013-04-29 18:13:0129#include "components/sessions/serialized_navigation_entry.h"
[email protected]c55e3b82012-08-09 15:27:0530#include "content/public/browser/navigation_entry.h"
[email protected]ad50def52011-10-19 23:17:0731#include "content/public/browser/notification_service.h"
[email protected]d928589a2013-03-14 13:12:3932#include "content/public/browser/render_process_host.h"
[email protected]5626b0892012-02-20 14:46:5833#include "content/public/browser/render_widget_host_view.h"
[email protected]0987a412013-03-15 12:21:1434#include "content/public/browser/user_metrics.h"
[email protected]3d6a8952012-12-14 03:18:0735#include "content/public/browser/web_contents.h"
[email protected]f3615f02013-02-26 06:09:0636#include "content/public/browser/web_contents_view.h"
[email protected]a7c22e52012-10-12 19:19:0537#include "net/base/escape.h"
[email protected]5aaa17d2013-05-06 22:44:3438#include "net/base/network_change_notifier.h"
[email protected]fd2b9ce2010-08-11 04:03:5739
[email protected]6eb8ea92011-08-22 21:01:4140#if defined(TOOLKIT_VIEWS)
[email protected]c13be0d2011-11-22 02:09:5841#include "ui/views/widget/widget.h"
[email protected]6eb8ea92011-08-22 21:01:4142#endif
43
[email protected]c55e3b82012-08-09 15:27:0544namespace {
45
[email protected]e6af47b2013-05-06 06:26:4646// For reporting Instant navigations.
[email protected]18956a52013-04-26 21:37:0547enum InstantNavigation {
48 INSTANT_NAVIGATION_LOCAL_CLICK = 0,
49 INSTANT_NAVIGATION_LOCAL_SUBMIT = 1,
50 INSTANT_NAVIGATION_ONLINE_CLICK = 2,
51 INSTANT_NAVIGATION_ONLINE_SUBMIT = 3,
[email protected]e6af47b2013-05-06 06:26:4652 INSTANT_NAVIGATION_NONEXTENDED = 4,
53 INSTANT_NAVIGATION_MAX = 5
[email protected]18956a52013-04-26 21:37:0554};
55
[email protected]e6af47b2013-05-06 06:26:4656void RecordNavigationHistogram(bool is_local, bool is_click, bool is_extended) {
[email protected]18956a52013-04-26 21:37:0557 InstantNavigation navigation;
[email protected]e6af47b2013-05-06 06:26:4658 if (!is_extended) {
59 navigation = INSTANT_NAVIGATION_NONEXTENDED;
60 } else if (is_local) {
61 navigation = is_click ? INSTANT_NAVIGATION_LOCAL_CLICK :
62 INSTANT_NAVIGATION_LOCAL_SUBMIT;
[email protected]18956a52013-04-26 21:37:0563 } else {
[email protected]e6af47b2013-05-06 06:26:4664 navigation = is_click ? INSTANT_NAVIGATION_ONLINE_CLICK :
65 INSTANT_NAVIGATION_ONLINE_SUBMIT;
[email protected]18956a52013-04-26 21:37:0566 }
67 UMA_HISTOGRAM_ENUMERATION("InstantExtended.InstantNavigation",
68 navigation,
69 INSTANT_NAVIGATION_MAX);
70}
71
[email protected]0c9406632013-02-08 01:13:3372bool IsContentsFrom(const InstantPage* page,
73 const content::WebContents* contents) {
74 return page && (page->contents() == contents);
75}
76
[email protected]0b684262013-02-20 02:18:2177// Adds a transient NavigationEntry to the supplied |contents|'s
78// NavigationController if the page's URL has not already been updated with the
79// supplied |search_terms|. Sets the |search_terms| on the transient entry for
80// search terms extraction to work correctly.
81void EnsureSearchTermsAreSet(content::WebContents* contents,
82 const string16& search_terms) {
[email protected]988cf722013-03-08 00:42:0083 content::NavigationController* controller = &contents->GetController();
[email protected]0b684262013-02-20 02:18:2184
85 // If search terms are already correct or there is already a transient entry
86 // (there shouldn't be), bail out early.
[email protected]165fe422013-03-27 06:34:0387 if (chrome::GetSearchTerms(contents) == search_terms ||
[email protected]988cf722013-03-08 00:42:0088 controller->GetTransientEntry())
[email protected]0b684262013-02-20 02:18:2189 return;
90
[email protected]988cf722013-03-08 00:42:0091 const content::NavigationEntry* active_entry = controller->GetActiveEntry();
92 content::NavigationEntry* transient = controller->CreateNavigationEntry(
93 active_entry->GetURL(),
94 active_entry->GetReferrer(),
95 active_entry->GetTransitionType(),
96 false,
97 std::string(),
98 contents->GetBrowserContext());
[email protected]40a7e412013-04-29 18:13:0199 transient->SetExtraData(sessions::kSearchTermsKey, search_terms);
[email protected]988cf722013-03-08 00:42:00100 controller->SetTransientEntry(transient);
[email protected]0b684262013-02-20 02:18:21101
[email protected]165fe422013-03-27 06:34:03102 SearchTabHelper::FromWebContents(contents)->NavigationEntryUpdated();
[email protected]0b684262013-02-20 02:18:21103}
104
[email protected]cc459062013-05-02 08:05:25105template <typename T>
106void DeletePageSoon(scoped_ptr<T> page) {
[email protected]30d75a02013-05-24 23:05:14107 if (page->contents()) {
108 base::MessageLoop::current()->DeleteSoon(
109 FROM_HERE, page->ReleaseContents().release());
110 }
[email protected]cc459062013-05-02 08:05:25111
[email protected]3264d162013-05-08 22:02:11112 base::MessageLoop::current()->DeleteSoon(FROM_HERE, page.release());
[email protected]cc459062013-05-02 08:05:25113}
114
[email protected]b67d0a42012-09-04 20:57:35115} // namespace
116
[email protected]165fe422013-03-27 06:34:03117InstantController::InstantController(BrowserInstantController* browser,
[email protected]411c9c02013-02-07 04:55:59118 bool extended_enabled)
[email protected]e41982a72012-11-20 07:16:51119 : browser_(browser),
120 extended_enabled_(extended_enabled),
[email protected]6a6811a2012-12-12 04:18:16121 omnibox_focus_state_(OMNIBOX_FOCUS_NONE),
[email protected]8d4c276d2013-05-29 19:39:52122 omnibox_focus_change_reason_(OMNIBOX_FOCUS_CHANGE_EXPLICIT),
[email protected]d58688c62013-07-03 23:09:12123 omnibox_bounds_(-1, -1, 0, 0) {
[email protected]9c349e82013-03-12 00:52:53124
125 // When the InstantController lives, the InstantService should live.
126 // InstantService sets up profile-level facilities such as the ThemeSource for
127 // the NTP.
[email protected]4facb8b2013-05-23 21:01:45128 // However, in some tests, browser_ may be null.
[email protected]6af41782013-06-22 13:49:11129 if (browser_) {
130 InstantService* instant_service = GetInstantService();
131 instant_service->AddObserver(this);
132 }
[email protected]e41982a72012-11-20 07:16:51133}
134
[email protected]6b723f82010-10-05 20:14:27135InstantController::~InstantController() {
[email protected]6af41782013-06-22 13:49:11136 if (browser_) {
137 InstantService* instant_service = GetInstantService();
138 instant_service->RemoveObserver(this);
139 }
[email protected]03bb953d2010-09-14 21:38:30140}
141
[email protected]0c9406632013-02-08 01:13:33142scoped_ptr<content::WebContents> InstantController::ReleaseNTPContents() {
[email protected]4facb8b2013-05-23 21:01:45143 if (!extended_enabled() || !browser_->profile() ||
[email protected]51742d92013-06-21 03:58:14144 browser_->profile()->IsOffTheRecord() ||
145 !chrome::ShouldShowInstantNTP())
[email protected]4b4c0132013-06-12 17:58:55146 return scoped_ptr<content::WebContents>();
[email protected]0c9406632013-02-08 01:13:33147
148 LOG_INSTANT_DEBUG_EVENT(this, "ReleaseNTPContents");
149
[email protected]01a07a1d2013-04-19 23:02:14150 if (ShouldSwitchToLocalNTP())
[email protected]cc459062013-05-02 08:05:25151 ResetNTP(GetLocalInstantURL());
[email protected]453b6eb72013-03-24 03:13:37152
[email protected]b82d2652013-03-29 19:44:44153 scoped_ptr<content::WebContents> ntp_contents = ntp_->ReleaseContents();
[email protected]453b6eb72013-03-24 03:13:37154
[email protected]eab7c1bc2013-05-07 03:05:28155 // Preload a new Instant NTP.
[email protected]d58688c62013-07-03 23:09:12156 ResetNTP(GetInstantURL());
[email protected]eab7c1bc2013-05-07 03:05:28157
[email protected]0c9406632013-02-08 01:13:33158 return ntp_contents.Pass();
159}
160
[email protected]fcde79a2013-02-28 02:25:09161void InstantController::SetOmniboxBounds(const gfx::Rect& bounds) {
[email protected]4facb8b2013-05-23 21:01:45162 if (!extended_enabled() || omnibox_bounds_ == bounds)
[email protected]ec4aad542012-12-14 01:11:04163 return;
164
[email protected]fcde79a2013-02-28 02:25:09165 omnibox_bounds_ = bounds;
[email protected]0c9406632013-02-08 01:13:33166 if (ntp_)
[email protected]215ebc42013-06-22 00:24:50167 ntp_->sender()->SetOmniboxBounds(omnibox_bounds_);
[email protected]ec4aad542012-12-14 01:11:04168 if (instant_tab_)
[email protected]215ebc42013-06-22 00:24:50169 instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
[email protected]ec4aad542012-12-14 01:11:04170}
171
[email protected]0a46856e2013-04-24 00:33:02172void InstantController::OnDefaultSearchProviderChanged() {
[email protected]4facb8b2013-05-23 21:01:45173 if (ntp_ && extended_enabled()) {
[email protected]0a46856e2013-04-24 00:33:02174 ntp_.reset();
[email protected]d58688c62013-07-03 23:09:12175 ResetNTP(GetInstantURL());
[email protected]0a46856e2013-04-24 00:33:02176 }
[email protected]bdb30382013-04-12 00:39:34177}
178
[email protected]3473ae02013-06-07 00:28:08179void InstantController::ToggleVoiceSearch() {
180 if (instant_tab_)
[email protected]215ebc42013-06-22 00:24:50181 instant_tab_->sender()->ToggleVoiceSearch();
[email protected]3473ae02013-06-07 00:28:08182}
183
[email protected]8cc9e532013-05-06 21:01:47184void InstantController::InstantPageLoadFailed(content::WebContents* contents) {
[email protected]4facb8b2013-05-23 21:01:45185 if (!chrome::ShouldPreferRemoteNTPOnStartup() || !extended_enabled()) {
[email protected]8cc9e532013-05-06 21:01:47186 // We only need to fall back on errors if we're showing the online page
187 // at startup, as otherwise we fall back correctly when trying to show
188 // a page that hasn't yet indicated that it supports the InstantExtended
189 // API.
190 return;
191 }
192
193 if (IsContentsFrom(instant_tab(), contents)) {
194 // Verify we're not already on a local page and that the URL precisely
195 // equals the instant_url (minus the query params, as those will be filled
196 // in by template values). This check is necessary to make sure we don't
197 // inadvertently redirect to the local NTP if someone, say, reloads a SRP
198 // while offline, as a committed results page still counts as an instant
199 // url. We also check to make sure there's no forward history, as if
200 // someone hits the back button a lot when offline and returns to a NTP
201 // we don't want to redirect and nuke their forward history stack.
202 const GURL& current_url = contents->GetURL();
203 if (instant_tab_->IsLocal() ||
204 !chrome::MatchesOriginAndPath(GURL(GetInstantURL()), current_url) ||
205 !current_url.ref().empty() ||
206 contents->GetController().CanGoForward())
207 return;
208 LOG_INSTANT_DEBUG_EVENT(this, "InstantPageLoadFailed: instant_tab");
209 RedirectToLocalNTP(contents);
210 } else if (IsContentsFrom(ntp(), contents)) {
211 LOG_INSTANT_DEBUG_EVENT(this, "InstantPageLoadFailed: ntp");
212 bool is_local = ntp_->IsLocal();
213 DeletePageSoon(ntp_.Pass());
214 if (!is_local)
215 ResetNTP(GetLocalInstantURL());
[email protected]d58688c62013-07-03 23:09:12216 } else {
217 NOTREACHED();
[email protected]8cc9e532013-05-06 21:01:47218 }
219}
220
[email protected]364c8502013-06-06 05:28:16221content::WebContents* InstantController::GetNTPContents() const {
222 return ntp_ ? ntp_->contents() : NULL;
223}
224
[email protected]413558cb2013-06-10 16:44:45225bool InstantController::SubmitQuery(const string16& search_terms) {
226 if (extended_enabled() && instant_tab_ && instant_tab_->supports_instant() &&
227 search_mode_.is_origin_search()) {
228 // Use |instant_tab_| to run the query if we're already on a search results
229 // page. (NOTE: in particular, we do not send the query to NTPs.)
[email protected]215ebc42013-06-22 00:24:50230 instant_tab_->sender()->Submit(search_terms);
[email protected]413558cb2013-06-10 16:44:45231 instant_tab_->contents()->GetView()->Focus();
232 EnsureSearchTermsAreSet(instant_tab_->contents(), search_terms);
233 return true;
234 }
235 return false;
236}
237
[email protected]6a6811a2012-12-12 04:18:16238void InstantController::OmniboxFocusChanged(
239 OmniboxFocusState state,
240 OmniboxFocusChangeReason reason,
241 gfx::NativeView view_gaining_focus) {
[email protected]b97059d2013-01-26 00:06:40242 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
243 "OmniboxFocusChanged: %d to %d for reason %d", omnibox_focus_state_,
244 state, reason));
[email protected]484ae5912010-09-29 19:16:14245
[email protected]6a6811a2012-12-12 04:18:16246 omnibox_focus_state_ = state;
[email protected]d58688c62013-07-03 23:09:12247 if (!extended_enabled() || !instant_tab_)
[email protected]c55e3b82012-08-09 15:27:05248 return;
[email protected]484ae5912010-09-29 19:16:14249
[email protected]7d4c1daa2013-07-03 16:16:19250 content::NotificationService::current()->Notify(
251 chrome::NOTIFICATION_OMNIBOX_FOCUS_CHANGED,
252 content::Source<InstantController>(this),
253 content::NotificationService::NoDetails());
254
[email protected]d58688c62013-07-03 23:09:12255 instant_tab_->sender()->FocusChanged(omnibox_focus_state_, reason);
256 // Don't send oninputstart/oninputend updates in response to focus changes
257 // if there's a navigation in progress. This prevents Chrome from sending
258 // a spurious oninputend when the user accepts a match in the omnibox.
259 if (instant_tab_->contents()->GetController().GetPendingEntry() == NULL)
260 instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
[email protected]f2557bd2011-06-01 02:33:07261}
262
[email protected]165fe422013-03-27 06:34:03263void InstantController::SearchModeChanged(const SearchMode& old_mode,
264 const SearchMode& new_mode) {
[email protected]4facb8b2013-05-23 21:01:45265 if (!extended_enabled())
[email protected]249a0352012-11-26 21:06:19266 return;
267
[email protected]b97059d2013-01-26 00:06:40268 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
269 "SearchModeChanged: [origin:mode] %d:%d to %d:%d", old_mode.origin,
270 old_mode.mode, new_mode.origin, new_mode.mode));
[email protected]cd533bf2012-12-04 19:14:59271
[email protected]e41982a72012-11-20 07:16:51272 search_mode_ = new_mode;
[email protected]cd533bf2012-12-04 19:14:59273 ResetInstantTab();
[email protected]0d0b4a42013-06-14 00:46:26274
[email protected]274b42c2013-06-18 11:54:52275 if (instant_tab_ && old_mode.is_ntp() != new_mode.is_ntp())
[email protected]215ebc42013-06-22 00:24:50276 instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
[email protected]0b10c9ff2012-10-09 17:31:55277}
278
[email protected]e41982a72012-11-20 07:16:51279void InstantController::ActiveTabChanged() {
[email protected]d58688c62013-07-03 23:09:12280 if (!extended_enabled())
[email protected]249a0352012-11-26 21:06:19281 return;
282
[email protected]b97059d2013-01-26 00:06:40283 LOG_INSTANT_DEBUG_EVENT(this, "ActiveTabChanged");
[email protected]d58688c62013-07-03 23:09:12284 ResetInstantTab();
[email protected]e41982a72012-11-20 07:16:51285}
286
[email protected]3d6a8952012-12-14 03:18:07287void InstantController::TabDeactivated(content::WebContents* contents) {
[email protected]ebc80462013-07-08 18:47:14288 // If user is deactivating an NTP tab, log the number of mouseovers for this
289 // NTP session.
290 if (chrome::IsInstantNTP(contents))
291 InstantNTP::EmitMouseoverCount(contents);
[email protected]a0b84662010-10-04 23:22:04292}
293
[email protected]6af41782013-06-22 13:49:11294void InstantController::ThemeInfoChanged(
295 const ThemeBackgroundInfo& theme_info) {
[email protected]4facb8b2013-05-23 21:01:45296 if (!extended_enabled())
[email protected]249a0352012-11-26 21:06:19297 return;
298
[email protected]0c9406632013-02-08 01:13:33299 if (ntp_)
[email protected]215ebc42013-06-22 00:24:50300 ntp_->sender()->SendThemeBackgroundInfo(theme_info);
[email protected]0c9406632013-02-08 01:13:33301 if (instant_tab_)
[email protected]215ebc42013-06-22 00:24:50302 instant_tab_->sender()->SendThemeBackgroundInfo(theme_info);
[email protected]a6827652012-11-20 23:41:08303}
304
[email protected]0c9406632013-02-08 01:13:33305void InstantController::LogDebugEvent(const std::string& info) const {
306 DVLOG(1) << info;
307
308 debug_events_.push_front(std::make_pair(
309 base::Time::Now().ToInternalValue(), info));
310 static const size_t kMaxDebugEventSize = 2000;
311 if (debug_events_.size() > kMaxDebugEventSize)
312 debug_events_.pop_back();
313}
314
[email protected]66be51812013-03-05 22:28:09315void InstantController::ClearDebugEvents() {
316 debug_events_.clear();
317}
318
[email protected]ed68ae32013-06-29 20:46:48319void InstantController::MostVisitedItemsChanged(
320 const std::vector<InstantMostVisitedItem>& items) {
[email protected]723eaf482013-06-22 20:28:11321 if (ntp_)
[email protected]215ebc42013-06-22 00:24:50322 ntp_->sender()->SendMostVisitedItems(items);
[email protected]723eaf482013-06-22 20:28:11323 if (instant_tab_)
[email protected]215ebc42013-06-22 00:24:50324 instant_tab_->sender()->SendMostVisitedItems(items);
[email protected]280be992013-06-04 03:19:51325
326 content::NotificationService::current()->Notify(
327 chrome::NOTIFICATION_INSTANT_SENT_MOST_VISITED_ITEMS,
328 content::Source<InstantController>(this),
329 content::NotificationService::NoDetails());
[email protected]dfee2f62013-02-22 06:46:06330}
331
[email protected]280be992013-06-04 03:19:51332void InstantController::DeleteMostVisitedItem(const GURL& url) {
333 DCHECK(!url.is_empty());
[email protected]6af41782013-06-22 13:49:11334 InstantService* instant_service = GetInstantService();
[email protected]280be992013-06-04 03:19:51335 if (!instant_service)
[email protected]dfee2f62013-02-22 06:46:06336 return;
337
[email protected]280be992013-06-04 03:19:51338 instant_service->DeleteMostVisitedItem(url);
339}
340
341void InstantController::UndoMostVisitedDeletion(const GURL& url) {
342 DCHECK(!url.is_empty());
[email protected]6af41782013-06-22 13:49:11343 InstantService* instant_service = GetInstantService();
[email protected]280be992013-06-04 03:19:51344 if (!instant_service)
345 return;
346
347 instant_service->UndoMostVisitedDeletion(url);
[email protected]dfee2f62013-02-22 06:46:06348}
349
350void InstantController::UndoAllMostVisitedDeletions() {
[email protected]6af41782013-06-22 13:49:11351 InstantService* instant_service = GetInstantService();
[email protected]280be992013-06-04 03:19:51352 if (!instant_service)
[email protected]dfee2f62013-02-22 06:46:06353 return;
354
[email protected]280be992013-06-04 03:19:51355 instant_service->UndoAllMostVisitedDeletions();
[email protected]dfee2f62013-02-22 06:46:06356}
357
[email protected]cc459062013-05-02 08:05:25358Profile* InstantController::profile() const {
359 return browser_->profile();
360}
361
[email protected]cc459062013-05-02 08:05:25362InstantTab* InstantController::instant_tab() const {
363 return instant_tab_.get();
364}
365
366InstantNTP* InstantController::ntp() const {
367 return ntp_.get();
368}
369
[email protected]f2bb4ef2013-07-01 19:54:13370void InstantController::OnNetworkChanged(
371 net::NetworkChangeNotifier::ConnectionType type) {
372 // Not interested in events conveying change to offline
373 if (type == net::NetworkChangeNotifier::CONNECTION_NONE)
374 return;
[email protected]d58688c62013-07-03 23:09:12375 if (!extended_enabled_)
[email protected]f2bb4ef2013-07-01 19:54:13376 return;
377 if (!ntp_ || ntp_->IsLocal())
378 ResetNTP(GetInstantURL());
379}
380
[email protected]0c9406632013-02-08 01:13:33381// TODO(shishir): We assume that the WebContent's current RenderViewHost is the
382// RenderViewHost being created which is not always true. Fix this.
383void InstantController::InstantPageRenderViewCreated(
384 const content::WebContents* contents) {
[email protected]4facb8b2013-05-23 21:01:45385 if (!extended_enabled())
[email protected]0c9406632013-02-08 01:13:33386 return;
387
388 // Update theme info so that the page picks it up.
[email protected]6af41782013-06-22 13:49:11389 InstantService* instant_service = GetInstantService();
[email protected]ed68ae32013-06-29 20:46:48390 if (instant_service) {
[email protected]6af41782013-06-22 13:49:11391 instant_service->UpdateThemeInfo();
[email protected]ed68ae32013-06-29 20:46:48392 instant_service->UpdateMostVisitedItemsInfo();
393 }
[email protected]0c9406632013-02-08 01:13:33394
395 // Ensure the searchbox API has the correct initial state.
[email protected]d58688c62013-07-03 23:09:12396 if (IsContentsFrom(ntp(), contents)) {
[email protected]215ebc42013-06-22 00:24:50397 ntp_->sender()->SetOmniboxBounds(omnibox_bounds_);
[email protected]0c9406632013-02-08 01:13:33398 ntp_->InitializeFonts();
[email protected]43132fbd2013-06-26 02:18:03399 ntp_->InitializePromos();
[email protected]0c9406632013-02-08 01:13:33400 } else {
401 NOTREACHED();
402 }
[email protected]0c9406632013-02-08 01:13:33403}
404
[email protected]4066a695d2013-06-20 14:08:54405void InstantController::InstantSupportChanged(
406 InstantSupportState instant_support) {
[email protected]4066a695d2013-06-20 14:08:54407 // Handle INSTANT_SUPPORT_YES here because InstantPage is not hooked up to the
408 // active tab. Search model changed listener in InstantPage will handle other
409 // cases.
410 if (instant_support != INSTANT_SUPPORT_YES)
411 return;
412
413 ResetInstantTab();
414}
415
[email protected]0c9406632013-02-08 01:13:33416void InstantController::InstantSupportDetermined(
417 const content::WebContents* contents,
418 bool supports_instant) {
419 if (IsContentsFrom(instant_tab(), contents)) {
420 if (!supports_instant)
[email protected]3264d162013-05-08 22:02:11421 base::MessageLoop::current()->DeleteSoon(FROM_HERE,
422 instant_tab_.release());
[email protected]ab3e66672013-03-20 23:32:16423
424 content::NotificationService::current()->Notify(
425 chrome::NOTIFICATION_INSTANT_TAB_SUPPORT_DETERMINED,
426 content::Source<InstantController>(this),
427 content::NotificationService::NoDetails());
[email protected]0c9406632013-02-08 01:13:33428 } else if (IsContentsFrom(ntp(), contents)) {
[email protected]8cc9e532013-05-06 21:01:47429 if (!supports_instant) {
430 bool is_local = ntp_->IsLocal();
[email protected]cc459062013-05-02 08:05:25431 DeletePageSoon(ntp_.Pass());
[email protected]8cc9e532013-05-06 21:01:47432 // Preload a local NTP in place of the broken online one.
433 if (!is_local)
434 ResetNTP(GetLocalInstantURL());
435 }
[email protected]0c9406632013-02-08 01:13:33436
437 content::NotificationService::current()->Notify(
438 chrome::NOTIFICATION_INSTANT_NTP_SUPPORT_DETERMINED,
439 content::Source<InstantController>(this),
440 content::NotificationService::NoDetails());
441
[email protected]d58688c62013-07-03 23:09:12442 } else {
443 NOTREACHED();
[email protected]0c9406632013-02-08 01:13:33444 }
445}
446
[email protected]58d5cfe2013-07-10 02:40:52447void InstantController::InstantPageRenderProcessGone(
[email protected]0c9406632013-02-08 01:13:33448 const content::WebContents* contents) {
[email protected]d58688c62013-07-03 23:09:12449 if (IsContentsFrom(ntp(), contents)) {
[email protected]cc459062013-05-02 08:05:25450 DeletePageSoon(ntp_.Pass());
451 } else {
[email protected]0c9406632013-02-08 01:13:33452 NOTREACHED();
[email protected]cc459062013-05-02 08:05:25453 }
[email protected]0c9406632013-02-08 01:13:33454}
455
456void InstantController::InstantPageAboutToNavigateMainFrame(
457 const content::WebContents* contents,
458 const GURL& url) {
[email protected]d58688c62013-07-03 23:09:12459 if (IsContentsFrom(instant_tab(), contents)) {
[email protected]8cc9e532013-05-06 21:01:47460 // The Instant tab navigated. Send it the data it needs to display
461 // properly.
462 UpdateInfoForInstantTab();
463 } else {
464 NOTREACHED();
[email protected]0c9406632013-02-08 01:13:33465 }
[email protected]a6827652012-11-20 23:41:08466}
467
[email protected]ca98bd32013-04-09 05:16:05468void InstantController::FocusOmnibox(const content::WebContents* contents,
469 OmniboxFocusState state) {
[email protected]4facb8b2013-05-23 21:01:45470 if (!extended_enabled())
[email protected]10aec592013-03-06 20:19:00471 return;
472
473 DCHECK(IsContentsFrom(instant_tab(), contents));
[email protected]10aec592013-03-06 20:19:00474
[email protected]ca98bd32013-04-09 05:16:05475 // Do not add a default case in the switch block for the following reasons:
476 // (1) Explicitly handle the new states. If new states are added in the
477 // OmniboxFocusState, the compiler will warn the developer to handle the new
478 // states.
479 // (2) An attacker may control the renderer and sends the browser process a
480 // malformed IPC. This function responds to the invalid |state| values by
481 // doing nothing instead of crashing the browser process (intentional no-op).
482 switch (state) {
483 case OMNIBOX_FOCUS_VISIBLE:
[email protected]2ff3ed42013-05-30 03:23:41484 // TODO(samarth): re-enable this once setValue() correctly handles
485 // URL-shaped queries.
486 // browser_->FocusOmnibox(true);
[email protected]ca98bd32013-04-09 05:16:05487 break;
488 case OMNIBOX_FOCUS_INVISIBLE:
489 browser_->FocusOmnibox(false);
490 break;
491 case OMNIBOX_FOCUS_NONE:
492 if (omnibox_focus_state_ != OMNIBOX_FOCUS_INVISIBLE)
493 contents->GetView()->Focus();
494 break;
495 }
[email protected]0c9406632013-02-08 01:13:33496}
497
498void InstantController::NavigateToURL(const content::WebContents* contents,
499 const GURL& url,
[email protected]3c3acca2013-02-26 03:07:07500 content::PageTransition transition,
[email protected]63bd68642013-05-13 23:02:55501 WindowOpenDisposition disposition,
502 bool is_search_type) {
[email protected]3c3acca2013-02-26 03:07:07503 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
504 "NavigateToURL: url='%s'", url.spec().c_str()));
505
[email protected]0c9406632013-02-08 01:13:33506 // TODO(samarth): handle case where contents are no longer "active" (e.g. user
507 // has switched tabs).
[email protected]4facb8b2013-05-23 21:01:45508 if (!extended_enabled())
[email protected]0c9406632013-02-08 01:13:33509 return;
[email protected]0987a412013-03-15 12:21:14510
511 if (transition == content::PAGE_TRANSITION_AUTO_BOOKMARK) {
512 content::RecordAction(
513 content::UserMetricsAction("InstantExtended.MostVisitedClicked"));
[email protected]e6af47b2013-05-06 06:26:46514 } else {
[email protected]63bd68642013-05-13 23:02:55515 // Exclude navigation by Most Visited click and searches.
516 if (!is_search_type)
517 RecordNavigationHistogram(UsingLocalPage(), true, true);
[email protected]0987a412013-03-15 12:21:14518 }
[email protected]3c3acca2013-02-26 03:07:07519 browser_->OpenURL(url, transition, disposition);
[email protected]30889c22013-02-04 21:27:56520}
521
[email protected]cc459062013-05-02 08:05:25522std::string InstantController::GetLocalInstantURL() const {
523 return chrome::GetLocalInstantURL(profile()).spec();
[email protected]e3033eb2012-12-13 23:46:08524}
525
[email protected]cc459062013-05-02 08:05:25526std::string InstantController::GetInstantURL() const {
[email protected]d58688c62013-07-03 23:09:12527 if (extended_enabled() && net::NetworkChangeNotifier::IsOffline())
[email protected]cc459062013-05-02 08:05:25528 return GetLocalInstantURL();
529
530 const GURL instant_url = chrome::GetInstantURL(profile(),
531 omnibox_bounds_.x());
532 if (instant_url.is_valid())
533 return instant_url.spec();
534
535 // Only extended mode has a local fallback.
[email protected]4facb8b2013-05-23 21:01:45536 return extended_enabled() ? GetLocalInstantURL() : std::string();
537}
538
539bool InstantController::extended_enabled() const {
540 return extended_enabled_;
[email protected]cc459062013-05-02 08:05:25541}
542
543bool InstantController::PageIsCurrent(const InstantPage* page) const {
544
545 const std::string& instant_url = GetInstantURL();
546 if (instant_url.empty() ||
547 !chrome::MatchesOriginAndPath(GURL(page->instant_url()),
548 GURL(instant_url)))
[email protected]e7a80bd2013-01-25 06:53:41549 return false;
[email protected]ed6e37b2012-12-16 06:36:51550
[email protected]cc459062013-05-02 08:05:25551 return page->supports_instant();
[email protected]e7a80bd2013-01-25 06:53:41552}
553
[email protected]cc459062013-05-02 08:05:25554void InstantController::ResetNTP(const std::string& instant_url) {
[email protected]51742d92013-06-21 03:58:14555 // Never load the Instant NTP if it is disabled.
556 if (!chrome::ShouldShowInstantNTP())
557 return;
558
[email protected]cc459062013-05-02 08:05:25559 // Instant NTP is only used in extended mode so we should always have a
560 // non-empty URL to use.
561 DCHECK(!instant_url.empty());
[email protected]215ebc42013-06-22 00:24:50562 ntp_.reset(new InstantNTP(this, instant_url,
563 browser_->profile()->IsOffTheRecord()));
[email protected]cc459062013-05-02 08:05:25564 ntp_->InitContents(profile(), browser_->GetActiveWebContents(),
565 base::Bind(&InstantController::ReloadStaleNTP,
566 base::Unretained(this)));
[email protected]b97059d2013-01-26 00:06:40567 LOG_INSTANT_DEBUG_EVENT(this, base::StringPrintf(
[email protected]cc459062013-05-02 08:05:25568 "ResetNTP: instant_url='%s'", instant_url.c_str()));
[email protected]08a9f972012-08-27 23:23:15569}
570
[email protected]cc459062013-05-02 08:05:25571void InstantController::ReloadStaleNTP() {
[email protected]d58688c62013-07-03 23:09:12572 if (extended_enabled())
573 ResetNTP(GetInstantURL());
[email protected]cc459062013-05-02 08:05:25574}
575
576bool InstantController::ShouldSwitchToLocalNTP() const {
[email protected]2b3ded92013-06-19 05:48:43577 if (!ntp())
578 return true;
579
580 // Assume users with Javascript disabled do not want the online experience.
581 if (!IsJavascriptEnabled())
[email protected]cc459062013-05-02 08:05:25582 return true;
583
584 // Already a local page. Not calling IsLocal() because we want to distinguish
585 // between the Google-specific and generic local NTP.
[email protected]2b3ded92013-06-19 05:48:43586 if (extended_enabled() && ntp()->instant_url() == GetLocalInstantURL())
[email protected]cc459062013-05-02 08:05:25587 return false;
588
589 if (PageIsCurrent(ntp()))
590 return false;
591
[email protected]cc459062013-05-02 08:05:25592 // The preloaded NTP does not support instant yet. If we're not in startup,
593 // always fall back to the local NTP. If we are in startup, use the local NTP
594 // (unless the finch flag to use the remote NTP is set).
[email protected]2b3ded92013-06-19 05:48:43595 return !(InStartup() && chrome::ShouldPreferRemoteNTPOnStartup());
[email protected]cc459062013-05-02 08:05:25596}
597
[email protected]cd533bf2012-12-04 19:14:59598void InstantController::ResetInstantTab() {
[email protected]215ebc42013-06-22 00:24:50599 if (!search_mode_.is_origin_default()) {
[email protected]cd533bf2012-12-04 19:14:59600 content::WebContents* active_tab = browser_->GetActiveWebContents();
601 if (!instant_tab_ || active_tab != instant_tab_->contents()) {
[email protected]215ebc42013-06-22 00:24:50602 instant_tab_.reset(
603 new InstantTab(this, browser_->profile()->IsOffTheRecord()));
[email protected]0c9406632013-02-08 01:13:33604 instant_tab_->Init(active_tab);
[email protected]8cc9e532013-05-06 21:01:47605 UpdateInfoForInstantTab();
[email protected]cd533bf2012-12-04 19:14:59606 }
[email protected]cd533bf2012-12-04 19:14:59607 } else {
608 instant_tab_.reset();
609 }
[email protected]0a387472010-10-07 00:18:20610}
611
[email protected]8cc9e532013-05-06 21:01:47612void InstantController::UpdateInfoForInstantTab() {
613 if (instant_tab_) {
[email protected]215ebc42013-06-22 00:24:50614 instant_tab_->sender()->SetOmniboxBounds(omnibox_bounds_);
[email protected]6af41782013-06-22 13:49:11615
616 // Update theme details.
617 InstantService* instant_service = GetInstantService();
[email protected]ed68ae32013-06-29 20:46:48618 if (instant_service) {
[email protected]6af41782013-06-22 13:49:11619 instant_service->UpdateThemeInfo();
[email protected]ed68ae32013-06-29 20:46:48620 instant_service->UpdateMostVisitedItemsInfo();
621 }
[email protected]6af41782013-06-22 13:49:11622
[email protected]8cc9e532013-05-06 21:01:47623 instant_tab_->InitializeFonts();
[email protected]43132fbd2013-06-26 02:18:03624 instant_tab_->InitializePromos();
[email protected]215ebc42013-06-22 00:24:50625 instant_tab_->sender()->FocusChanged(omnibox_focus_state_,
626 omnibox_focus_change_reason_);
627 instant_tab_->sender()->SetInputInProgress(IsInputInProgress());
[email protected]8cc9e532013-05-06 21:01:47628 }
629}
630
[email protected]0d0b4a42013-06-14 00:46:26631bool InstantController::IsInputInProgress() const {
[email protected]274b42c2013-06-18 11:54:52632 return !search_mode_.is_ntp() &&
[email protected]0d0b4a42013-06-14 00:46:26633 omnibox_focus_state_ == OMNIBOX_FOCUS_VISIBLE;
634}
635
[email protected]18956a52013-04-26 21:37:05636bool InstantController::UsingLocalPage() const {
[email protected]d58688c62013-07-03 23:09:12637 return instant_tab_ && instant_tab_->IsLocal();
[email protected]18956a52013-04-26 21:37:05638}
[email protected]8cc9e532013-05-06 21:01:47639
640void InstantController::RedirectToLocalNTP(content::WebContents* contents) {
641 contents->GetController().LoadURL(
642 chrome::GetLocalInstantURL(browser_->profile()),
643 content::Referrer(),
644 content::PAGE_TRANSITION_SERVER_REDIRECT,
645 std::string()); // No extra headers.
646 // TODO(dcblack): Remove extraneous history entry caused by 404s.
647 // Note that the base case of a 204 being returned doesn't push a history
648 // entry.
649}
[email protected]2485c8122013-05-07 03:17:52650
[email protected]2b3ded92013-06-19 05:48:43651bool InstantController::IsJavascriptEnabled() const {
652 GURL instant_url(GetInstantURL());
653 GURL origin(instant_url.GetOrigin());
654 ContentSetting js_setting = profile()->GetHostContentSettingsMap()->
655 GetContentSetting(origin, origin, CONTENT_SETTINGS_TYPE_JAVASCRIPT,
656 NO_RESOURCE_IDENTIFIER);
657 // Javascript can be disabled either in content settings or via a WebKit
658 // preference, so check both. Disabling it through the Settings page affects
659 // content settings. I'm not sure how to disable the WebKit preference, but
660 // it's theoretically possible some users have it off.
661 bool js_content_enabled =
662 js_setting == CONTENT_SETTING_DEFAULT ||
663 js_setting == CONTENT_SETTING_ALLOW;
664 bool js_webkit_enabled = profile()->GetPrefs()->GetBoolean(
665 prefs::kWebKitJavascriptEnabled);
666 return js_content_enabled && js_webkit_enabled;
667}
668
669bool InstantController::InStartup() const {
670 // TODO(shishir): This is not completely reliable. Find a better way to detect
671 // startup time.
672 return !browser_->GetActiveWebContents();
673}
[email protected]6af41782013-06-22 13:49:11674
675InstantService* InstantController::GetInstantService() const {
676 return InstantServiceFactory::GetForProfile(profile());
677}