| OLD | NEW |
| (Empty) |
| 1 // Copyright (c) 2012 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/prerender/prerender_local_predictor.h" | |
| 6 | |
| 7 #include <ctype.h> | |
| 8 | |
| 9 #include <algorithm> | |
| 10 #include <map> | |
| 11 #include <set> | |
| 12 #include <string> | |
| 13 #include <utility> | |
| 14 | |
| 15 #include "base/json/json_reader.h" | |
| 16 #include "base/json/json_writer.h" | |
| 17 #include "base/metrics/field_trial.h" | |
| 18 #include "base/metrics/histogram.h" | |
| 19 #include "base/stl_util.h" | |
| 20 #include "base/timer/timer.h" | |
| 21 #include "chrome/browser/browser_process.h" | |
| 22 #include "chrome/browser/history/history_service_factory.h" | |
| 23 #include "chrome/browser/prerender/prerender_field_trial.h" | |
| 24 #include "chrome/browser/prerender/prerender_handle.h" | |
| 25 #include "chrome/browser/prerender/prerender_histograms.h" | |
| 26 #include "chrome/browser/prerender/prerender_manager.h" | |
| 27 #include "chrome/browser/prerender/prerender_util.h" | |
| 28 #include "chrome/browser/profiles/profile.h" | |
| 29 #include "chrome/browser/safe_browsing/database_manager.h" | |
| 30 #include "chrome/browser/safe_browsing/safe_browsing_service.h" | |
| 31 #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" | |
| 32 #include "chrome/common/prefetch_messages.h" | |
| 33 #include "components/history/core/browser/history_database.h" | |
| 34 #include "components/history/core/browser/history_db_task.h" | |
| 35 #include "components/history/core/browser/history_service.h" | |
| 36 #include "content/public/browser/browser_thread.h" | |
| 37 #include "content/public/browser/navigation_controller.h" | |
| 38 #include "content/public/browser/navigation_entry.h" | |
| 39 #include "content/public/browser/render_frame_host.h" | |
| 40 #include "content/public/browser/render_process_host.h" | |
| 41 #include "content/public/browser/web_contents.h" | |
| 42 #include "crypto/secure_hash.h" | |
| 43 #include "grit/browser_resources.h" | |
| 44 #include "net/base/escape.h" | |
| 45 #include "net/base/load_flags.h" | |
| 46 #include "net/url_request/url_fetcher.h" | |
| 47 #include "ui/base/page_transition_types.h" | |
| 48 #include "ui/base/resource/resource_bundle.h" | |
| 49 #include "url/url_canon.h" | |
| 50 | |
| 51 using base::DictionaryValue; | |
| 52 using base::ListValue; | |
| 53 using base::Value; | |
| 54 using content::BrowserThread; | |
| 55 using ui::PageTransition; | |
| 56 using content::RenderFrameHost; | |
| 57 using content::SessionStorageNamespace; | |
| 58 using content::WebContents; | |
| 59 using history::URLID; | |
| 60 using net::URLFetcher; | |
| 61 using predictors::LoggedInPredictorTable; | |
| 62 using std::string; | |
| 63 using std::vector; | |
| 64 | |
| 65 namespace prerender { | |
| 66 | |
| 67 namespace { | |
| 68 | |
| 69 static const size_t kURLHashSize = 5; | |
| 70 static const int kNumPrerenderCandidates = 5; | |
| 71 static const int kInvalidProcessId = -1; | |
| 72 static const int kInvalidFrameId = -1; | |
| 73 static const int kMaxPrefetchItems = 100; | |
| 74 | |
| 75 } // namespace | |
| 76 | |
| 77 // When considering a candidate URL to be prerendered, we need to collect the | |
| 78 // data in this struct to make the determination whether we should issue the | |
| 79 // prerender or not. | |
| 80 struct PrerenderLocalPredictor::LocalPredictorURLInfo { | |
| 81 URLID id; | |
| 82 GURL url; | |
| 83 bool url_lookup_success; | |
| 84 bool logged_in; | |
| 85 bool logged_in_lookup_ok; | |
| 86 bool local_history_based; | |
| 87 bool service_whitelist; | |
| 88 bool service_whitelist_lookup_ok; | |
| 89 bool service_whitelist_reported; | |
| 90 double priority; | |
| 91 }; | |
| 92 | |
| 93 // A struct consisting of everything needed for launching a potential prerender | |
| 94 // on a navigation: The navigation URL (source) triggering potential prerenders, | |
| 95 // and a set of candidate URLs. | |
| 96 struct PrerenderLocalPredictor::CandidatePrerenderInfo { | |
| 97 LocalPredictorURLInfo source_url_; | |
| 98 vector<LocalPredictorURLInfo> candidate_urls_; | |
| 99 scoped_refptr<SessionStorageNamespace> session_storage_namespace_; | |
| 100 // Render Process ID and Route ID of the page causing the prerender to be | |
| 101 // issued. Needed so that we can cause its renderer to issue prefetches within | |
| 102 // its context. | |
| 103 int render_process_id_; | |
| 104 int render_frame_id_; | |
| 105 scoped_ptr<gfx::Size> size_; | |
| 106 base::Time start_time_; // used for various time measurements | |
| 107 explicit CandidatePrerenderInfo(URLID source_id) | |
| 108 : render_process_id_(kInvalidProcessId), | |
| 109 render_frame_id_(kInvalidFrameId) { | |
| 110 source_url_.id = source_id; | |
| 111 } | |
| 112 void MaybeAddCandidateURLFromLocalData(URLID id, double priority) { | |
| 113 LocalPredictorURLInfo info; | |
| 114 info.id = id; | |
| 115 info.local_history_based = true; | |
| 116 info.service_whitelist = false; | |
| 117 info.service_whitelist_lookup_ok = false; | |
| 118 info.service_whitelist_reported = false; | |
| 119 info.priority = priority; | |
| 120 MaybeAddCandidateURLInternal(info); | |
| 121 } | |
| 122 void MaybeAddCandidateURLFromService(GURL url, double priority, | |
| 123 bool whitelist, | |
| 124 bool whitelist_lookup_ok) { | |
| 125 LocalPredictorURLInfo info; | |
| 126 info.id = kint64max; | |
| 127 info.url = url; | |
| 128 info.url_lookup_success = true; | |
| 129 info.local_history_based = false; | |
| 130 info.service_whitelist = whitelist; | |
| 131 info.service_whitelist_lookup_ok = whitelist_lookup_ok; | |
| 132 info.service_whitelist_reported = true; | |
| 133 info.priority = priority; | |
| 134 MaybeAddCandidateURLInternal(info); | |
| 135 } | |
| 136 void MaybeAddCandidateURLInternal(const LocalPredictorURLInfo& info) { | |
| 137 // TODO(tburkard): clean up this code, potentially using a list or a heap | |
| 138 int max_candidates = kNumPrerenderCandidates; | |
| 139 // We first insert local candidates, then service candidates. | |
| 140 // Since we want to keep kNumPrerenderCandidates for both local & service | |
| 141 // candidates, we need to double the maximum number of candidates once | |
| 142 // we start seeing service candidates. | |
| 143 if (!info.local_history_based) | |
| 144 max_candidates *= 2; | |
| 145 int insert_pos = candidate_urls_.size(); | |
| 146 if (insert_pos < max_candidates) | |
| 147 candidate_urls_.push_back(info); | |
| 148 while (insert_pos > 0 && | |
| 149 candidate_urls_[insert_pos - 1].priority < info.priority) { | |
| 150 if (insert_pos < max_candidates) | |
| 151 candidate_urls_[insert_pos] = candidate_urls_[insert_pos - 1]; | |
| 152 insert_pos--; | |
| 153 } | |
| 154 if (insert_pos < max_candidates) | |
| 155 candidate_urls_[insert_pos] = info; | |
| 156 } | |
| 157 }; | |
| 158 | |
| 159 namespace { | |
| 160 | |
| 161 #define TIMING_HISTOGRAM(name, value) \ | |
| 162 UMA_HISTOGRAM_CUSTOM_TIMES(name, value, \ | |
| 163 base::TimeDelta::FromMilliseconds(10), \ | |
| 164 base::TimeDelta::FromSeconds(10), \ | |
| 165 50); | |
| 166 | |
| 167 // Task to lookup the URL for a given URLID. | |
| 168 class GetURLForURLIDTask : public history::HistoryDBTask { | |
| 169 public: | |
| 170 GetURLForURLIDTask( | |
| 171 PrerenderLocalPredictor::CandidatePrerenderInfo* request, | |
| 172 const base::Closure& callback) | |
| 173 : request_(request), | |
| 174 callback_(callback), | |
| 175 start_time_(base::Time::Now()) { | |
| 176 } | |
| 177 | |
| 178 bool RunOnDBThread(history::HistoryBackend* backend, | |
| 179 history::HistoryDatabase* db) override { | |
| 180 DoURLLookup(db, &request_->source_url_); | |
| 181 for (int i = 0; i < static_cast<int>(request_->candidate_urls_.size()); i++) | |
| 182 DoURLLookup(db, &request_->candidate_urls_[i]); | |
| 183 return true; | |
| 184 } | |
| 185 | |
| 186 void DoneRunOnMainThread() override { | |
| 187 callback_.Run(); | |
| 188 TIMING_HISTOGRAM("Prerender.LocalPredictorURLLookupTime", | |
| 189 base::Time::Now() - start_time_); | |
| 190 } | |
| 191 | |
| 192 private: | |
| 193 ~GetURLForURLIDTask() override {} | |
| 194 | |
| 195 void DoURLLookup(history::HistoryDatabase* db, | |
| 196 PrerenderLocalPredictor::LocalPredictorURLInfo* request) { | |
| 197 history::URLRow url_row; | |
| 198 request->url_lookup_success = db->GetURLRow(request->id, &url_row); | |
| 199 if (request->url_lookup_success) | |
| 200 request->url = url_row.url(); | |
| 201 } | |
| 202 | |
| 203 PrerenderLocalPredictor::CandidatePrerenderInfo* request_; | |
| 204 base::Closure callback_; | |
| 205 base::Time start_time_; | |
| 206 DISALLOW_COPY_AND_ASSIGN(GetURLForURLIDTask); | |
| 207 }; | |
| 208 | |
| 209 // Task to load history from the visit database on startup. | |
| 210 class GetVisitHistoryTask : public history::HistoryDBTask { | |
| 211 public: | |
| 212 GetVisitHistoryTask(PrerenderLocalPredictor* local_predictor, | |
| 213 int max_visits) | |
| 214 : local_predictor_(local_predictor), | |
| 215 max_visits_(max_visits), | |
| 216 visit_history_(new vector<history::BriefVisitInfo>) { | |
| 217 } | |
| 218 | |
| 219 bool RunOnDBThread(history::HistoryBackend* backend, | |
| 220 history::HistoryDatabase* db) override { | |
| 221 db->GetBriefVisitInfoOfMostRecentVisits(max_visits_, visit_history_.get()); | |
| 222 return true; | |
| 223 } | |
| 224 | |
| 225 void DoneRunOnMainThread() override { | |
| 226 local_predictor_->OnGetInitialVisitHistory(visit_history_.Pass()); | |
| 227 } | |
| 228 | |
| 229 private: | |
| 230 ~GetVisitHistoryTask() override {} | |
| 231 | |
| 232 PrerenderLocalPredictor* local_predictor_; | |
| 233 int max_visits_; | |
| 234 scoped_ptr<vector<history::BriefVisitInfo> > visit_history_; | |
| 235 DISALLOW_COPY_AND_ASSIGN(GetVisitHistoryTask); | |
| 236 }; | |
| 237 | |
| 238 // Maximum visit history to retrieve from the visit database. | |
| 239 const int kMaxVisitHistory = 100 * 1000; | |
| 240 | |
| 241 // Visit history size at which to trigger pruning, and number of items to prune. | |
| 242 const int kVisitHistoryPruneThreshold = 120 * 1000; | |
| 243 const int kVisitHistoryPruneAmount = 20 * 1000; | |
| 244 | |
| 245 const int kMinLocalPredictionTimeMs = 500; | |
| 246 | |
| 247 int GetMaxLocalPredictionTimeMs() { | |
| 248 return GetLocalPredictorTTLSeconds() * 1000; | |
| 249 } | |
| 250 | |
| 251 bool IsBackForward(PageTransition transition) { | |
| 252 return (transition & ui::PAGE_TRANSITION_FORWARD_BACK) != 0; | |
| 253 } | |
| 254 | |
| 255 bool IsHomePage(PageTransition transition) { | |
| 256 return (transition & ui::PAGE_TRANSITION_HOME_PAGE) != 0; | |
| 257 } | |
| 258 | |
| 259 bool IsIntermediateRedirect(PageTransition transition) { | |
| 260 return (transition & ui::PAGE_TRANSITION_CHAIN_END) == 0; | |
| 261 } | |
| 262 | |
| 263 bool IsFormSubmit(PageTransition transition) { | |
| 264 return ui::PageTransitionCoreTypeIs(transition, | |
| 265 ui::PAGE_TRANSITION_FORM_SUBMIT); | |
| 266 } | |
| 267 | |
| 268 bool ShouldExcludeTransitionForPrediction(PageTransition transition) { | |
| 269 return IsBackForward(transition) || IsHomePage(transition) || | |
| 270 IsIntermediateRedirect(transition); | |
| 271 } | |
| 272 | |
| 273 base::Time GetCurrentTime() { | |
| 274 return base::Time::Now(); | |
| 275 } | |
| 276 | |
| 277 bool StringContainsIgnoringCase(string haystack, string needle) { | |
| 278 std::transform(haystack.begin(), haystack.end(), haystack.begin(), ::tolower); | |
| 279 std::transform(needle.begin(), needle.end(), needle.begin(), ::tolower); | |
| 280 return haystack.find(needle) != string::npos; | |
| 281 } | |
| 282 | |
| 283 bool IsExtendedRootURL(const GURL& url) { | |
| 284 const string& path = url.path(); | |
| 285 return path == "/index.html" || path == "/home.html" || | |
| 286 path == "/main.html" || | |
| 287 path == "/index.htm" || path == "/home.htm" || path == "/main.htm" || | |
| 288 path == "/index.php" || path == "/home.php" || path == "/main.php" || | |
| 289 path == "/index.asp" || path == "/home.asp" || path == "/main.asp" || | |
| 290 path == "/index.py" || path == "/home.py" || path == "/main.py" || | |
| 291 path == "/index.pl" || path == "/home.pl" || path == "/main.pl"; | |
| 292 } | |
| 293 | |
| 294 bool IsRootPageURL(const GURL& url) { | |
| 295 return (url.path() == "/" || url.path().empty() || IsExtendedRootURL(url)) && | |
| 296 (!url.has_query()) && (!url.has_ref()); | |
| 297 } | |
| 298 | |
| 299 bool IsLogInURL(const GURL& url) { | |
| 300 return StringContainsIgnoringCase(url.spec().c_str(), "login") || | |
| 301 StringContainsIgnoringCase(url.spec().c_str(), "signin"); | |
| 302 } | |
| 303 | |
| 304 bool IsLogOutURL(const GURL& url) { | |
| 305 return StringContainsIgnoringCase(url.spec().c_str(), "logout") || | |
| 306 StringContainsIgnoringCase(url.spec().c_str(), "signout"); | |
| 307 } | |
| 308 | |
| 309 int64 URLHashToInt64(const unsigned char* data) { | |
| 310 static_assert(kURLHashSize < sizeof(int64), "url hash must fit in int64"); | |
| 311 int64 value = 0; | |
| 312 memcpy(&value, data, kURLHashSize); | |
| 313 return value; | |
| 314 } | |
| 315 | |
| 316 int64 GetInt64URLHashForURL(const GURL& url) { | |
| 317 static_assert(kURLHashSize < sizeof(int64), "url hash must fit in int64"); | |
| 318 scoped_ptr<crypto::SecureHash> hash( | |
| 319 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); | |
| 320 int64 hash_value = 0; | |
| 321 const char* url_string = url.spec().c_str(); | |
| 322 hash->Update(url_string, strlen(url_string)); | |
| 323 hash->Finish(&hash_value, kURLHashSize); | |
| 324 return hash_value; | |
| 325 } | |
| 326 | |
| 327 bool URLsIdenticalIgnoringFragments(const GURL& url1, const GURL& url2) { | |
| 328 url::Replacements<char> replacement; | |
| 329 replacement.ClearRef(); | |
| 330 GURL u1 = url1.ReplaceComponents(replacement); | |
| 331 GURL u2 = url2.ReplaceComponents(replacement); | |
| 332 return (u1 == u2); | |
| 333 } | |
| 334 | |
| 335 void LookupLoggedInStatesOnDBThread( | |
| 336 scoped_refptr<LoggedInPredictorTable> logged_in_predictor_table, | |
| 337 PrerenderLocalPredictor::CandidatePrerenderInfo* request) { | |
| 338 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); | |
| 339 for (int i = 0; i < static_cast<int>(request->candidate_urls_.size()); i++) { | |
| 340 PrerenderLocalPredictor::LocalPredictorURLInfo* info = | |
| 341 &request->candidate_urls_[i]; | |
| 342 if (info->url_lookup_success) { | |
| 343 logged_in_predictor_table->HasUserLoggedIn( | |
| 344 info->url, &info->logged_in, &info->logged_in_lookup_ok); | |
| 345 } else { | |
| 346 info->logged_in_lookup_ok = false; | |
| 347 } | |
| 348 } | |
| 349 } | |
| 350 | |
| 351 } // namespace | |
| 352 | |
| 353 struct PrerenderLocalPredictor::PrerenderProperties { | |
| 354 PrerenderProperties(URLID url_id, const GURL& url, double priority, | |
| 355 base::Time start_time) | |
| 356 : url_id(url_id), | |
| 357 url(url), | |
| 358 priority(priority), | |
| 359 start_time(start_time), | |
| 360 would_have_matched(false) { | |
| 361 } | |
| 362 | |
| 363 // Default constructor for dummy element | |
| 364 PrerenderProperties() | |
| 365 : priority(0.0), would_have_matched(false) { | |
| 366 } | |
| 367 | |
| 368 double GetCurrentDecayedPriority() { | |
| 369 // If we are no longer prerendering, the priority is 0. | |
| 370 if (!prerender_handle || !prerender_handle->IsPrerendering()) | |
| 371 return 0.0; | |
| 372 int half_life_time_seconds = | |
| 373 GetLocalPredictorPrerenderPriorityHalfLifeTimeSeconds(); | |
| 374 if (half_life_time_seconds < 1) | |
| 375 return priority; | |
| 376 double multiple_elapsed = | |
| 377 (GetCurrentTime() - actual_start_time).InMillisecondsF() / | |
| 378 base::TimeDelta::FromSeconds(half_life_time_seconds).InMillisecondsF(); | |
| 379 // Decay factor: 2 ^ (-multiple_elapsed) | |
| 380 double decay_factor = exp(- multiple_elapsed * log(2.0)); | |
| 381 return priority * decay_factor; | |
| 382 } | |
| 383 | |
| 384 URLID url_id; | |
| 385 GURL url; | |
| 386 double priority; | |
| 387 // For expiration purposes, this is a synthetic start time consisting either | |
| 388 // of the actual start time, or of the last time the page was re-requested | |
| 389 // for prerendering - 10 seconds (unless the original request came after | |
| 390 // that). This is to emulate the effect of re-prerendering a page that is | |
| 391 // about to expire, because it was re-requested for prerendering a second | |
| 392 // time after the actual prerender being kept around. | |
| 393 base::Time start_time; | |
| 394 // The actual time this page was last requested for prerendering. | |
| 395 base::Time actual_start_time; | |
| 396 scoped_ptr<PrerenderHandle> prerender_handle; | |
| 397 // Indicates whether this prerender would have matched a URL navigated to, | |
| 398 // but was not swapped in for some reason. | |
| 399 bool would_have_matched; | |
| 400 }; | |
| 401 | |
| 402 // A class simulating a set of URLs prefetched, for statistical purposes. | |
| 403 class PrerenderLocalPredictor::PrefetchList { | |
| 404 public: | |
| 405 enum SeenType { | |
| 406 SEEN_TABCONTENTS_OBSERVER, | |
| 407 SEEN_HISTORY, | |
| 408 SEEN_MAX_VALUE | |
| 409 }; | |
| 410 | |
| 411 PrefetchList() {} | |
| 412 ~PrefetchList() { | |
| 413 STLDeleteValues(&entries_); | |
| 414 } | |
| 415 | |
| 416 // Adds a new URL being prefetched. If the URL is already in the list, | |
| 417 // nothing will happen. Returns whether a new prefetch was added. | |
| 418 bool AddURL(const GURL& url) { | |
| 419 ExpireOldItems(); | |
| 420 string url_string = url.spec().c_str(); | |
| 421 base::hash_map<string, ListEntry*>::iterator it = entries_.find(url_string); | |
| 422 if (it != entries_.end()) { | |
| 423 // If a prefetch previously existed, and has not been seen yet in either | |
| 424 // a tab contents or a history, we will not re-issue it. Otherwise, if it | |
| 425 // may have been consumed by either tab contents or history, we will | |
| 426 // permit re-issuing another one. | |
| 427 if (!it->second->seen_history_ && | |
| 428 !it->second->seen_tabcontents_) { | |
| 429 return false; | |
| 430 } | |
| 431 } | |
| 432 ListEntry* entry = new ListEntry(url_string); | |
| 433 entries_[entry->url_] = entry; | |
| 434 entry_list_.push_back(entry); | |
| 435 ExpireOldItems(); | |
| 436 return true; | |
| 437 } | |
| 438 | |
| 439 // Marks the URL provided as seen in the context specified. Returns true | |
| 440 // iff the item is currently in the list and had not been seen before in | |
| 441 // the context specified, i.e. the marking was successful. | |
| 442 bool MarkURLSeen(const GURL& url, SeenType type) { | |
| 443 ExpireOldItems(); | |
| 444 bool return_value = false; | |
| 445 base::hash_map<string, ListEntry*>::iterator it = | |
| 446 entries_.find(url.spec().c_str()); | |
| 447 if (it == entries_.end()) | |
| 448 return return_value; | |
| 449 if (type == SEEN_TABCONTENTS_OBSERVER && !it->second->seen_tabcontents_) { | |
| 450 it->second->seen_tabcontents_ = true; | |
| 451 return_value = true; | |
| 452 } | |
| 453 if (type == SEEN_HISTORY && !it->second->seen_history_) { | |
| 454 it->second->seen_history_ = true; | |
| 455 return_value = true; | |
| 456 } | |
| 457 // If the item has been seen in both the history and in tab contents, | |
| 458 // and the page load time has been recorded, erase it from the map to | |
| 459 // make room for new prefetches. | |
| 460 if (it->second->seen_tabcontents_ && it->second->seen_history_ && | |
| 461 it->second->seen_plt_) { | |
| 462 entries_.erase(url.spec().c_str()); | |
| 463 } | |
| 464 return return_value; | |
| 465 } | |
| 466 | |
| 467 // Marks the PLT for the provided UR as seen. Returns true | |
| 468 // iff the item is currently in the list and the PLT had not been seen | |
| 469 // before, i.e. the sighting was successful. | |
| 470 bool MarkPLTSeen(const GURL& url, base::TimeDelta plt) { | |
| 471 ExpireOldItems(); | |
| 472 base::hash_map<string, ListEntry*>::iterator it = | |
| 473 entries_.find(url.spec().c_str()); | |
| 474 if (it == entries_.end() || it->second->seen_plt_ || | |
| 475 it->second->add_time_ > GetCurrentTime() - plt) { | |
| 476 return false; | |
| 477 } | |
| 478 it->second->seen_plt_ = true; | |
| 479 // If the item has been seen in both the history and in tab contents, | |
| 480 // and the page load time has been recorded, erase it from the map to | |
| 481 // make room for new prefetches. | |
| 482 if (it->second->seen_tabcontents_ && it->second->seen_history_ && | |
| 483 it->second->seen_plt_) { | |
| 484 entries_.erase(url.spec().c_str()); | |
| 485 } | |
| 486 return true; | |
| 487 } | |
| 488 | |
| 489 private: | |
| 490 struct ListEntry { | |
| 491 explicit ListEntry(const string& url) | |
| 492 : url_(url), | |
| 493 add_time_(GetCurrentTime()), | |
| 494 seen_tabcontents_(false), | |
| 495 seen_history_(false), | |
| 496 seen_plt_(false) { | |
| 497 } | |
| 498 std::string url_; | |
| 499 base::Time add_time_; | |
| 500 bool seen_tabcontents_; | |
| 501 bool seen_history_; | |
| 502 bool seen_plt_; | |
| 503 }; | |
| 504 | |
| 505 void ExpireOldItems() { | |
| 506 base::Time expiry_cutoff = GetCurrentTime() - | |
| 507 base::TimeDelta::FromSeconds(GetPrerenderPrefetchListTimeoutSeconds()); | |
| 508 while (!entry_list_.empty() && | |
| 509 (entry_list_.front()->add_time_ < expiry_cutoff || | |
| 510 entries_.size() > kMaxPrefetchItems)) { | |
| 511 ListEntry* entry = entry_list_.front(); | |
| 512 entry_list_.pop_front(); | |
| 513 // If the entry to be deleted is still the one active in entries_, | |
| 514 // we must erase it from entries_. | |
| 515 base::hash_map<string, ListEntry*>::iterator it = | |
| 516 entries_.find(entry->url_); | |
| 517 if (it != entries_.end() && it->second == entry) | |
| 518 entries_.erase(entry->url_); | |
| 519 delete entry; | |
| 520 } | |
| 521 } | |
| 522 | |
| 523 base::hash_map<string, ListEntry*> entries_; | |
| 524 std::list<ListEntry*> entry_list_; | |
| 525 DISALLOW_COPY_AND_ASSIGN(PrefetchList); | |
| 526 }; | |
| 527 | |
| 528 PrerenderLocalPredictor::PrerenderLocalPredictor( | |
| 529 PrerenderManager* prerender_manager) | |
| 530 : prerender_manager_(prerender_manager), | |
| 531 prefetch_list_(new PrefetchList()), | |
| 532 history_service_observer_(this), | |
| 533 weak_factory_(this) { | |
| 534 RecordEvent(EVENT_CONSTRUCTED); | |
| 535 if (base::MessageLoop::current()) { | |
| 536 timer_.Start(FROM_HERE, | |
| 537 base::TimeDelta::FromMilliseconds(kInitDelayMs), | |
| 538 this, | |
| 539 &PrerenderLocalPredictor::Init); | |
| 540 RecordEvent(EVENT_INIT_SCHEDULED); | |
| 541 } | |
| 542 | |
| 543 static const size_t kChecksumHashSize = 32; | |
| 544 base::RefCountedStaticMemory* url_whitelist_data = | |
| 545 ResourceBundle::GetSharedInstance().LoadDataResourceBytes( | |
| 546 IDR_PRERENDER_URL_WHITELIST); | |
| 547 size_t size = url_whitelist_data->size(); | |
| 548 const unsigned char* front = url_whitelist_data->front(); | |
| 549 if (size < kChecksumHashSize || | |
| 550 (size - kChecksumHashSize) % kURLHashSize != 0) { | |
| 551 RecordEvent(EVENT_URL_WHITELIST_ERROR); | |
| 552 return; | |
| 553 } | |
| 554 scoped_ptr<crypto::SecureHash> hash( | |
| 555 crypto::SecureHash::Create(crypto::SecureHash::SHA256)); | |
| 556 hash->Update(front + kChecksumHashSize, size - kChecksumHashSize); | |
| 557 char hash_value[kChecksumHashSize]; | |
| 558 hash->Finish(hash_value, kChecksumHashSize); | |
| 559 if (memcmp(hash_value, front, kChecksumHashSize)) { | |
| 560 RecordEvent(EVENT_URL_WHITELIST_ERROR); | |
| 561 return; | |
| 562 } | |
| 563 for (const unsigned char* p = front + kChecksumHashSize; | |
| 564 p < front + size; | |
| 565 p += kURLHashSize) { | |
| 566 url_whitelist_.insert(URLHashToInt64(p)); | |
| 567 } | |
| 568 RecordEvent(EVENT_URL_WHITELIST_OK); | |
| 569 } | |
| 570 | |
| 571 PrerenderLocalPredictor::~PrerenderLocalPredictor() { | |
| 572 Shutdown(); | |
| 573 for (PrerenderProperties* p : issued_prerenders_) { | |
| 574 DCHECK(p != NULL); | |
| 575 if (p->prerender_handle) | |
| 576 p->prerender_handle->OnCancel(); | |
| 577 } | |
| 578 STLDeleteContainerPairPointers( | |
| 579 outstanding_prerender_service_requests_.begin(), | |
| 580 outstanding_prerender_service_requests_.end()); | |
| 581 } | |
| 582 | |
| 583 void PrerenderLocalPredictor::Shutdown() { | |
| 584 timer_.Stop(); | |
| 585 history_service_observer_.RemoveAll(); | |
| 586 } | |
| 587 | |
| 588 void PrerenderLocalPredictor::OnAddVisit( | |
| 589 history::HistoryService* history_service, | |
| 590 const history::BriefVisitInfo& info) { | |
| 591 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 592 RecordEvent(EVENT_ADD_VISIT); | |
| 593 if (!visit_history_.get()) | |
| 594 return; | |
| 595 visit_history_->push_back(info); | |
| 596 if (static_cast<int>(visit_history_->size()) > kVisitHistoryPruneThreshold) { | |
| 597 visit_history_->erase(visit_history_->begin(), | |
| 598 visit_history_->begin() + kVisitHistoryPruneAmount); | |
| 599 } | |
| 600 RecordEvent(EVENT_ADD_VISIT_INITIALIZED); | |
| 601 if (current_prerender_.get() && | |
| 602 current_prerender_->url_id == info.url_id && | |
| 603 IsPrerenderStillValid(current_prerender_.get())) { | |
| 604 UMA_HISTOGRAM_CUSTOM_TIMES( | |
| 605 "Prerender.LocalPredictorTimeUntilUsed", | |
| 606 GetCurrentTime() - current_prerender_->actual_start_time, | |
| 607 base::TimeDelta::FromMilliseconds(10), | |
| 608 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()), | |
| 609 50); | |
| 610 last_swapped_in_prerender_.reset(current_prerender_.release()); | |
| 611 RecordEvent(EVENT_ADD_VISIT_PRERENDER_IDENTIFIED); | |
| 612 } | |
| 613 if (ShouldExcludeTransitionForPrediction(info.transition)) | |
| 614 return; | |
| 615 Profile* profile = prerender_manager_->profile(); | |
| 616 if (!profile || | |
| 617 ShouldDisableLocalPredictorDueToPreferencesAndNetwork(profile)) { | |
| 618 return; | |
| 619 } | |
| 620 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION); | |
| 621 base::TimeDelta max_age = | |
| 622 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs()); | |
| 623 base::TimeDelta min_age = | |
| 624 base::TimeDelta::FromMilliseconds(kMinLocalPredictionTimeMs); | |
| 625 std::set<URLID> next_urls_currently_found; | |
| 626 std::map<URLID, int> next_urls_num_found; | |
| 627 int num_occurrences_of_current_visit = 0; | |
| 628 base::Time last_visited; | |
| 629 scoped_ptr<CandidatePrerenderInfo> lookup_info( | |
| 630 new CandidatePrerenderInfo(info.url_id)); | |
| 631 const vector<history::BriefVisitInfo>& visits = *(visit_history_.get()); | |
| 632 for (int i = 0; i < static_cast<int>(visits.size()); i++) { | |
| 633 if (!ShouldExcludeTransitionForPrediction(visits[i].transition)) { | |
| 634 if (visits[i].url_id == info.url_id) { | |
| 635 last_visited = visits[i].time; | |
| 636 num_occurrences_of_current_visit++; | |
| 637 next_urls_currently_found.clear(); | |
| 638 continue; | |
| 639 } | |
| 640 if (!last_visited.is_null() && | |
| 641 last_visited > visits[i].time - max_age && | |
| 642 last_visited < visits[i].time - min_age) { | |
| 643 if (!IsFormSubmit(visits[i].transition)) | |
| 644 next_urls_currently_found.insert(visits[i].url_id); | |
| 645 } | |
| 646 } | |
| 647 if (i == static_cast<int>(visits.size()) - 1 || | |
| 648 visits[i+1].url_id == info.url_id) { | |
| 649 for (std::set<URLID>::iterator it = next_urls_currently_found.begin(); | |
| 650 it != next_urls_currently_found.end(); | |
| 651 ++it) { | |
| 652 std::pair<std::map<URLID, int>::iterator, bool> insert_ret = | |
| 653 next_urls_num_found.insert(std::pair<URLID, int>(*it, 0)); | |
| 654 std::map<URLID, int>::iterator num_found_it = insert_ret.first; | |
| 655 num_found_it->second++; | |
| 656 } | |
| 657 } | |
| 658 } | |
| 659 | |
| 660 if (num_occurrences_of_current_visit > 1) { | |
| 661 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_REPEAT_URL); | |
| 662 } else { | |
| 663 RecordEvent(EVENT_ADD_VISIT_RELEVANT_TRANSITION_NEW_URL); | |
| 664 } | |
| 665 | |
| 666 for (std::map<URLID, int>::const_iterator it = next_urls_num_found.begin(); | |
| 667 it != next_urls_num_found.end(); | |
| 668 ++it) { | |
| 669 // Only consider a candidate next page for prerendering if it was viewed | |
| 670 // at least twice, and at least 10% of the time. | |
| 671 if (num_occurrences_of_current_visit > 0 && | |
| 672 it->second > 1 && | |
| 673 it->second * 10 >= num_occurrences_of_current_visit) { | |
| 674 RecordEvent(EVENT_ADD_VISIT_IDENTIFIED_PRERENDER_CANDIDATE); | |
| 675 double priority = static_cast<double>(it->second) / | |
| 676 static_cast<double>(num_occurrences_of_current_visit); | |
| 677 lookup_info->MaybeAddCandidateURLFromLocalData(it->first, priority); | |
| 678 } | |
| 679 } | |
| 680 | |
| 681 RecordEvent(EVENT_START_URL_LOOKUP); | |
| 682 history::HistoryService* history = GetHistoryIfExists(); | |
| 683 if (history) { | |
| 684 RecordEvent(EVENT_GOT_HISTORY_ISSUING_LOOKUP); | |
| 685 CandidatePrerenderInfo* lookup_info_ptr = lookup_info.get(); | |
| 686 history->ScheduleDBTask( | |
| 687 scoped_ptr<history::HistoryDBTask>( | |
| 688 new GetURLForURLIDTask( | |
| 689 lookup_info_ptr, | |
| 690 base::Bind(&PrerenderLocalPredictor::OnLookupURL, | |
| 691 base::Unretained(this), | |
| 692 base::Passed(&lookup_info)))), | |
| 693 &history_db_tracker_); | |
| 694 } | |
| 695 } | |
| 696 | |
| 697 void PrerenderLocalPredictor::OnLookupURL( | |
| 698 scoped_ptr<CandidatePrerenderInfo> info) { | |
| 699 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 700 | |
| 701 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT); | |
| 702 | |
| 703 if (!info->source_url_.url_lookup_success) { | |
| 704 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_FAILED); | |
| 705 return; | |
| 706 } | |
| 707 | |
| 708 if (prefetch_list_->MarkURLSeen(info->source_url_.url, | |
| 709 PrefetchList::SEEN_HISTORY)) { | |
| 710 RecordEvent(EVENT_PREFETCH_LIST_SEEN_HISTORY); | |
| 711 } | |
| 712 | |
| 713 if (info->candidate_urls_.size() > 0 && | |
| 714 info->candidate_urls_[0].url_lookup_success) { | |
| 715 LogCandidateURLStats(info->candidate_urls_[0].url); | |
| 716 } | |
| 717 | |
| 718 WebContents* source_web_contents = NULL; | |
| 719 bool multiple_source_web_contents_candidates = false; | |
| 720 | |
| 721 #if !defined(OS_ANDROID) | |
| 722 // We need to figure out what tab launched the prerender. We do this by | |
| 723 // comparing URLs. This may not always work: the URL may occur in two | |
| 724 // tabs, and we pick the wrong one, or the tab we should have picked | |
| 725 // may have navigated elsewhere. Hopefully, this doesn't happen too often, | |
| 726 // so we ignore these cases for now. | |
| 727 // TODO(tburkard): Reconsider this, potentially measure it, and fix this | |
| 728 // in the future. | |
| 729 for (TabContentsIterator it; !it.done(); it.Next()) { | |
| 730 if (it->GetURL() == info->source_url_.url) { | |
| 731 if (!source_web_contents) | |
| 732 source_web_contents = *it; | |
| 733 else | |
| 734 multiple_source_web_contents_candidates = true; | |
| 735 } | |
| 736 } | |
| 737 #endif | |
| 738 | |
| 739 if (!source_web_contents) { | |
| 740 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_SOURCE_WEBCONTENTS_FOUND); | |
| 741 return; | |
| 742 } | |
| 743 | |
| 744 if (multiple_source_web_contents_candidates) | |
| 745 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_MULTIPLE_SOURCE_WEBCONTENTS_FOUND); | |
| 746 | |
| 747 info->session_storage_namespace_ = | |
| 748 source_web_contents->GetController().GetDefaultSessionStorageNamespace(); | |
| 749 RenderFrameHost* rfh = source_web_contents->GetMainFrame(); | |
| 750 info->render_process_id_ = rfh->GetProcess()->GetID(); | |
| 751 info->render_frame_id_ = rfh->GetRoutingID(); | |
| 752 | |
| 753 gfx::Rect container_bounds = source_web_contents->GetContainerBounds(); | |
| 754 info->size_.reset(new gfx::Size(container_bounds.size())); | |
| 755 | |
| 756 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_SUCCESS); | |
| 757 | |
| 758 DoPrerenderServiceCheck(info.Pass()); | |
| 759 } | |
| 760 | |
| 761 void PrerenderLocalPredictor::DoPrerenderServiceCheck( | |
| 762 scoped_ptr<CandidatePrerenderInfo> info) { | |
| 763 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 764 if (!ShouldQueryPrerenderService(prerender_manager_->profile())) { | |
| 765 RecordEvent(EVENT_PRERENDER_SERVICE_DISABLED); | |
| 766 DoLoggedInLookup(info.Pass()); | |
| 767 return; | |
| 768 } | |
| 769 /* | |
| 770 Create a JSON request. | |
| 771 Here is a sample request: | |
| 772 { "prerender_request": { | |
| 773 "version": 1, | |
| 774 "behavior_id": 6, | |
| 775 "hint_request": { | |
| 776 "browse_history": [ | |
| 777 { "url": "http://www.cnn.com/" | |
| 778 } | |
| 779 ] | |
| 780 }, | |
| 781 "candidate_check_request": { | |
| 782 "candidates": [ | |
| 783 { "url": "http://www.cnn.com/sports/" | |
| 784 }, | |
| 785 { "url": "http://www.cnn.com/politics/" | |
| 786 } | |
| 787 ] | |
| 788 } | |
| 789 } | |
| 790 } | |
| 791 */ | |
| 792 base::DictionaryValue json_data; | |
| 793 base::DictionaryValue* req = new base::DictionaryValue(); | |
| 794 req->SetInteger("version", 1); | |
| 795 req->SetInteger("behavior_id", GetPrerenderServiceBehaviorID()); | |
| 796 if (ShouldQueryPrerenderServiceForCurrentURL() && | |
| 797 info->source_url_.url_lookup_success) { | |
| 798 base::ListValue* browse_history = new base::ListValue(); | |
| 799 base::DictionaryValue* browse_item = new base::DictionaryValue(); | |
| 800 browse_item->SetString("url", info->source_url_.url.spec()); | |
| 801 browse_history->Append(browse_item); | |
| 802 base::DictionaryValue* hint_request = new base::DictionaryValue(); | |
| 803 hint_request->Set("browse_history", browse_history); | |
| 804 req->Set("hint_request", hint_request); | |
| 805 } | |
| 806 int num_candidate_urls = 0; | |
| 807 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { | |
| 808 if (info->candidate_urls_[i].url_lookup_success) | |
| 809 num_candidate_urls++; | |
| 810 } | |
| 811 if (ShouldQueryPrerenderServiceForCandidateURLs() && | |
| 812 num_candidate_urls > 0) { | |
| 813 base::ListValue* candidates = new base::ListValue(); | |
| 814 base::DictionaryValue* candidate; | |
| 815 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { | |
| 816 if (info->candidate_urls_[i].url_lookup_success) { | |
| 817 candidate = new base::DictionaryValue(); | |
| 818 candidate->SetString("url", info->candidate_urls_[i].url.spec()); | |
| 819 candidates->Append(candidate); | |
| 820 } | |
| 821 } | |
| 822 base::DictionaryValue* candidate_check_request = | |
| 823 new base::DictionaryValue(); | |
| 824 candidate_check_request->Set("candidates", candidates); | |
| 825 req->Set("candidate_check_request", candidate_check_request); | |
| 826 } | |
| 827 json_data.Set("prerender_request", req); | |
| 828 string request_string; | |
| 829 base::JSONWriter::Write(&json_data, &request_string); | |
| 830 GURL fetch_url(GetPrerenderServiceURLPrefix() + | |
| 831 net::EscapeQueryParamValue(request_string, false)); | |
| 832 net::URLFetcher* fetcher = net::URLFetcher::Create( | |
| 833 0, | |
| 834 fetch_url, | |
| 835 URLFetcher::GET, this); | |
| 836 fetcher->SetRequestContext( | |
| 837 prerender_manager_->profile()->GetRequestContext()); | |
| 838 fetcher->SetLoadFlags(net::LOAD_DISABLE_CACHE | | |
| 839 net::LOAD_DO_NOT_SAVE_COOKIES | | |
| 840 net::LOAD_DO_NOT_SEND_COOKIES); | |
| 841 fetcher->AddExtraRequestHeader("Pragma: no-cache"); | |
| 842 info->start_time_ = base::Time::Now(); | |
| 843 outstanding_prerender_service_requests_.insert( | |
| 844 std::make_pair(fetcher, info.release())); | |
| 845 base::MessageLoop::current()->PostDelayedTask( | |
| 846 FROM_HERE, | |
| 847 base::Bind(&PrerenderLocalPredictor::MaybeCancelURLFetcher, | |
| 848 weak_factory_.GetWeakPtr(), fetcher), | |
| 849 base::TimeDelta::FromMilliseconds(GetPrerenderServiceFetchTimeoutMs())); | |
| 850 RecordEvent(EVENT_PRERENDER_SERVICE_ISSUED_LOOKUP); | |
| 851 fetcher->Start(); | |
| 852 } | |
| 853 | |
| 854 void PrerenderLocalPredictor::MaybeCancelURLFetcher(net::URLFetcher* fetcher) { | |
| 855 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 856 OutstandingFetchers::iterator it = | |
| 857 outstanding_prerender_service_requests_.find(fetcher); | |
| 858 if (it == outstanding_prerender_service_requests_.end()) | |
| 859 return; | |
| 860 delete it->first; | |
| 861 scoped_ptr<CandidatePrerenderInfo> info(it->second); | |
| 862 outstanding_prerender_service_requests_.erase(it); | |
| 863 RecordEvent(EVENT_PRERENDER_SERVICE_LOOKUP_TIMED_OUT); | |
| 864 DoLoggedInLookup(info.Pass()); | |
| 865 } | |
| 866 | |
| 867 bool PrerenderLocalPredictor::ApplyParsedPrerenderServiceResponse( | |
| 868 base::DictionaryValue* dict, | |
| 869 CandidatePrerenderInfo* info, | |
| 870 bool* hinting_timed_out, | |
| 871 bool* hinting_url_lookup_timed_out, | |
| 872 bool* candidate_url_lookup_timed_out) { | |
| 873 /* | |
| 874 Process the response to the request. | |
| 875 Here is a sample response to illustrate the format. | |
| 876 { | |
| 877 "prerender_response": { | |
| 878 "behavior_id": 6, | |
| 879 "hint_response": { | |
| 880 "hinting_timed_out": 0, | |
| 881 "candidates": [ | |
| 882 { "url": "http://www.cnn.com/story-1", | |
| 883 "in_index": 1, | |
| 884 "likelihood": 0.60, | |
| 885 "in_index_timed_out": 0 | |
| 886 }, | |
| 887 { "url": "http://www.cnn.com/story-2", | |
| 888 "in_index": 1, | |
| 889 "likelihood": 0.30, | |
| 890 "in_index_timed_out": 0 | |
| 891 } | |
| 892 ] | |
| 893 }, | |
| 894 "candidate_check_response": { | |
| 895 "candidates": [ | |
| 896 { "url": "http://www.cnn.com/sports/", | |
| 897 "in_index": 1, | |
| 898 "in_index_timed_out": 0 | |
| 899 }, | |
| 900 { "url": "http://www.cnn.com/politics/", | |
| 901 "in_index": 0, | |
| 902 "in_index_timed_out": "1" | |
| 903 } | |
| 904 ] | |
| 905 } | |
| 906 } | |
| 907 } | |
| 908 */ | |
| 909 base::ListValue* list = NULL; | |
| 910 int int_value; | |
| 911 if (!dict->GetInteger("prerender_response.behavior_id", &int_value) || | |
| 912 int_value != GetPrerenderServiceBehaviorID()) { | |
| 913 return false; | |
| 914 } | |
| 915 if (!dict->GetList("prerender_response.candidate_check_response.candidates", | |
| 916 &list)) { | |
| 917 if (ShouldQueryPrerenderServiceForCandidateURLs()) { | |
| 918 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { | |
| 919 if (info->candidate_urls_[i].url_lookup_success) | |
| 920 return false; | |
| 921 } | |
| 922 } | |
| 923 } else { | |
| 924 for (size_t i = 0; i < list->GetSize(); i++) { | |
| 925 base::DictionaryValue* d; | |
| 926 if (!list->GetDictionary(i, &d)) | |
| 927 return false; | |
| 928 string url_string; | |
| 929 if (!d->GetString("url", &url_string) || !GURL(url_string).is_valid()) | |
| 930 return false; | |
| 931 GURL url(url_string); | |
| 932 int in_index_timed_out = 0; | |
| 933 int in_index = 0; | |
| 934 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) || | |
| 935 in_index_timed_out != 1) && | |
| 936 !d->GetInteger("in_index", &in_index)) { | |
| 937 return false; | |
| 938 } | |
| 939 if (in_index < 0 || in_index > 1 || | |
| 940 in_index_timed_out < 0 || in_index_timed_out > 1) { | |
| 941 return false; | |
| 942 } | |
| 943 if (in_index_timed_out == 1) | |
| 944 *candidate_url_lookup_timed_out = true; | |
| 945 for (size_t j = 0; j < info->candidate_urls_.size(); j++) { | |
| 946 if (info->candidate_urls_[j].url == url) { | |
| 947 info->candidate_urls_[j].service_whitelist_reported = true; | |
| 948 info->candidate_urls_[j].service_whitelist = (in_index == 1); | |
| 949 info->candidate_urls_[j].service_whitelist_lookup_ok = | |
| 950 ((1 - in_index_timed_out) == 1); | |
| 951 } | |
| 952 } | |
| 953 } | |
| 954 for (size_t i = 0; i < info->candidate_urls_.size(); i++) { | |
| 955 if (info->candidate_urls_[i].url_lookup_success && | |
| 956 !info->candidate_urls_[i].service_whitelist_reported) { | |
| 957 return false; | |
| 958 } | |
| 959 } | |
| 960 } | |
| 961 | |
| 962 if (ShouldQueryPrerenderServiceForCurrentURL() && | |
| 963 info->source_url_.url_lookup_success) { | |
| 964 list = NULL; | |
| 965 if (dict->GetInteger("prerender_response.hint_response.hinting_timed_out", | |
| 966 &int_value) && | |
| 967 int_value == 1) { | |
| 968 *hinting_timed_out = true; | |
| 969 } else if (!dict->GetList("prerender_response.hint_response.candidates", | |
| 970 &list)) { | |
| 971 return false; | |
| 972 } else { | |
| 973 for (int i = 0; i < static_cast<int>(list->GetSize()); i++) { | |
| 974 base::DictionaryValue* d; | |
| 975 if (!list->GetDictionary(i, &d)) | |
| 976 return false; | |
| 977 string url; | |
| 978 if (!d->GetString("url", &url) || !GURL(url).is_valid()) | |
| 979 return false; | |
| 980 double priority; | |
| 981 if (!d->GetDouble("likelihood", &priority) || priority < 0.0 || | |
| 982 priority > 1.0) { | |
| 983 return false; | |
| 984 } | |
| 985 int in_index_timed_out = 0; | |
| 986 int in_index = 0; | |
| 987 if ((!d->GetInteger("in_index_timed_out", &in_index_timed_out) || | |
| 988 in_index_timed_out != 1) && | |
| 989 !d->GetInteger("in_index", &in_index)) { | |
| 990 return false; | |
| 991 } | |
| 992 if (in_index < 0 || in_index > 1 || in_index_timed_out < 0 || | |
| 993 in_index_timed_out > 1) { | |
| 994 return false; | |
| 995 } | |
| 996 if (in_index_timed_out == 1) | |
| 997 *hinting_url_lookup_timed_out = true; | |
| 998 info->MaybeAddCandidateURLFromService(GURL(url), | |
| 999 priority, | |
| 1000 in_index == 1, | |
| 1001 !in_index_timed_out); | |
| 1002 } | |
| 1003 if (list->GetSize() > 0) | |
| 1004 RecordEvent(EVENT_PRERENDER_SERVICE_RETURNED_HINTING_CANDIDATES); | |
| 1005 } | |
| 1006 } | |
| 1007 | |
| 1008 return true; | |
| 1009 } | |
| 1010 | |
| 1011 void PrerenderLocalPredictor::OnURLFetchComplete( | |
| 1012 const net::URLFetcher* source) { | |
| 1013 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1014 RecordEvent(EVENT_PRERENDER_SERVICE_RECEIVED_RESULT); | |
| 1015 net::URLFetcher* fetcher = const_cast<net::URLFetcher*>(source); | |
| 1016 OutstandingFetchers::iterator it = | |
| 1017 outstanding_prerender_service_requests_.find(fetcher); | |
| 1018 if (it == outstanding_prerender_service_requests_.end()) { | |
| 1019 RecordEvent(EVENT_PRERENDER_SERVICE_NO_RECORD_FOR_RESULT); | |
| 1020 return; | |
| 1021 } | |
| 1022 scoped_ptr<CandidatePrerenderInfo> info(it->second); | |
| 1023 outstanding_prerender_service_requests_.erase(it); | |
| 1024 TIMING_HISTOGRAM("Prerender.LocalPredictorServiceLookupTime", | |
| 1025 base::Time::Now() - info->start_time_); | |
| 1026 string result; | |
| 1027 fetcher->GetResponseAsString(&result); | |
| 1028 scoped_ptr<base::Value> root; | |
| 1029 root.reset(base::JSONReader::Read(result)); | |
| 1030 bool hinting_timed_out = false; | |
| 1031 bool hinting_url_lookup_timed_out = false; | |
| 1032 bool candidate_url_lookup_timed_out = false; | |
| 1033 if (!root.get() || !root->IsType(base::Value::TYPE_DICTIONARY)) { | |
| 1034 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR_INCORRECT_JSON); | |
| 1035 } else { | |
| 1036 if (ApplyParsedPrerenderServiceResponse( | |
| 1037 static_cast<base::DictionaryValue*>(root.get()), | |
| 1038 info.get(), | |
| 1039 &hinting_timed_out, | |
| 1040 &hinting_url_lookup_timed_out, | |
| 1041 &candidate_url_lookup_timed_out)) { | |
| 1042 // We finished parsing the result, and found no errors. | |
| 1043 RecordEvent(EVENT_PRERENDER_SERVICE_PARSED_CORRECTLY); | |
| 1044 if (hinting_timed_out) | |
| 1045 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_TIMED_OUT); | |
| 1046 if (hinting_url_lookup_timed_out) | |
| 1047 RecordEvent(EVENT_PRERENDER_SERVICE_HINTING_URL_LOOKUP_TIMED_OUT); | |
| 1048 if (candidate_url_lookup_timed_out) | |
| 1049 RecordEvent(EVENT_PRERENDER_SERVICE_CANDIDATE_URL_LOOKUP_TIMED_OUT); | |
| 1050 DoLoggedInLookup(info.Pass()); | |
| 1051 return; | |
| 1052 } | |
| 1053 } | |
| 1054 | |
| 1055 // If we did not return earlier, an error happened during parsing. | |
| 1056 // Record this, and proceed. | |
| 1057 RecordEvent(EVENT_PRERENDER_SERVICE_PARSE_ERROR); | |
| 1058 DoLoggedInLookup(info.Pass()); | |
| 1059 } | |
| 1060 | |
| 1061 void PrerenderLocalPredictor:: DoLoggedInLookup( | |
| 1062 scoped_ptr<CandidatePrerenderInfo> info) { | |
| 1063 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1064 scoped_refptr<LoggedInPredictorTable> logged_in_table = | |
| 1065 prerender_manager_->logged_in_predictor_table(); | |
| 1066 | |
| 1067 if (!logged_in_table.get()) { | |
| 1068 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_NO_LOGGED_IN_TABLE_FOUND); | |
| 1069 return; | |
| 1070 } | |
| 1071 | |
| 1072 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_ISSUING_LOGGED_IN_LOOKUP); | |
| 1073 | |
| 1074 info->start_time_ = base::Time::Now(); | |
| 1075 | |
| 1076 CandidatePrerenderInfo* info_ptr = info.get(); | |
| 1077 BrowserThread::PostTaskAndReply( | |
| 1078 BrowserThread::DB, FROM_HERE, | |
| 1079 base::Bind(&LookupLoggedInStatesOnDBThread, | |
| 1080 logged_in_table, | |
| 1081 info_ptr), | |
| 1082 base::Bind(&PrerenderLocalPredictor::ContinuePrerenderCheck, | |
| 1083 weak_factory_.GetWeakPtr(), | |
| 1084 base::Passed(&info))); | |
| 1085 } | |
| 1086 | |
| 1087 void PrerenderLocalPredictor::LogCandidateURLStats(const GURL& url) const { | |
| 1088 if (url_whitelist_.count(GetInt64URLHashForURL(url)) > 0) { | |
| 1089 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST); | |
| 1090 if (IsRootPageURL(url)) | |
| 1091 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ON_WHITELIST_ROOT_PAGE); | |
| 1092 } | |
| 1093 if (IsRootPageURL(url)) | |
| 1094 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE); | |
| 1095 if (IsExtendedRootURL(url)) | |
| 1096 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_EXTENDED_ROOT_PAGE); | |
| 1097 if (IsRootPageURL(url) && url.SchemeIs("http")) | |
| 1098 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_ROOT_PAGE_HTTP); | |
| 1099 if (url.SchemeIs("http")) | |
| 1100 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_IS_HTTP); | |
| 1101 if (url.has_query()) | |
| 1102 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_HAS_QUERY_STRING); | |
| 1103 if (IsLogOutURL(url)) | |
| 1104 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGOUT); | |
| 1105 if (IsLogInURL(url)) | |
| 1106 RecordEvent(EVENT_PRERENDER_URL_LOOKUP_RESULT_CONTAINS_LOGIN); | |
| 1107 } | |
| 1108 | |
| 1109 void PrerenderLocalPredictor::OnGetInitialVisitHistory( | |
| 1110 scoped_ptr<vector<history::BriefVisitInfo> > visit_history) { | |
| 1111 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1112 DCHECK(!visit_history_.get()); | |
| 1113 RecordEvent(EVENT_INIT_SUCCEEDED); | |
| 1114 // Since the visit history has descending timestamps, we must reverse it. | |
| 1115 visit_history_.reset(new vector<history::BriefVisitInfo>( | |
| 1116 visit_history->rbegin(), visit_history->rend())); | |
| 1117 } | |
| 1118 | |
| 1119 history::HistoryService* PrerenderLocalPredictor::GetHistoryIfExists() const { | |
| 1120 Profile* profile = prerender_manager_->profile(); | |
| 1121 if (!profile) | |
| 1122 return NULL; | |
| 1123 return HistoryServiceFactory::GetForProfileWithoutCreating(profile); | |
| 1124 } | |
| 1125 | |
| 1126 void PrerenderLocalPredictor::Init() { | |
| 1127 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1128 RecordEvent(EVENT_INIT_STARTED); | |
| 1129 Profile* profile = prerender_manager_->profile(); | |
| 1130 if (!profile || | |
| 1131 ShouldDisableLocalPredictorBasedOnSyncAndConfiguration(profile)) { | |
| 1132 RecordEvent(EVENT_INIT_FAILED_UNENCRYPTED_SYNC_NOT_ENABLED); | |
| 1133 return; | |
| 1134 } | |
| 1135 history::HistoryService* history = GetHistoryIfExists(); | |
| 1136 if (history) { | |
| 1137 CHECK(!history_service_observer_.IsObserving(history)); | |
| 1138 history->ScheduleDBTask( | |
| 1139 scoped_ptr<history::HistoryDBTask>( | |
| 1140 new GetVisitHistoryTask(this, kMaxVisitHistory)), | |
| 1141 &history_db_tracker_); | |
| 1142 history_service_observer_.Add(history); | |
| 1143 } else { | |
| 1144 RecordEvent(EVENT_INIT_FAILED_NO_HISTORY); | |
| 1145 } | |
| 1146 } | |
| 1147 | |
| 1148 void PrerenderLocalPredictor::OnPLTEventForURL(const GURL& url, | |
| 1149 base::TimeDelta page_load_time) { | |
| 1150 if (prefetch_list_->MarkPLTSeen(url, page_load_time)) { | |
| 1151 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.LocalPredictorPrefetchMatchPLT", | |
| 1152 page_load_time, | |
| 1153 base::TimeDelta::FromMilliseconds(10), | |
| 1154 base::TimeDelta::FromSeconds(60), | |
| 1155 100); | |
| 1156 } | |
| 1157 | |
| 1158 scoped_ptr<PrerenderProperties> prerender; | |
| 1159 if (DoesPrerenderMatchPLTRecord(last_swapped_in_prerender_.get(), | |
| 1160 url, page_load_time)) { | |
| 1161 prerender.reset(last_swapped_in_prerender_.release()); | |
| 1162 } | |
| 1163 if (DoesPrerenderMatchPLTRecord(current_prerender_.get(), | |
| 1164 url, page_load_time)) { | |
| 1165 prerender.reset(current_prerender_.release()); | |
| 1166 } | |
| 1167 if (!prerender.get()) | |
| 1168 return; | |
| 1169 if (IsPrerenderStillValid(prerender.get())) { | |
| 1170 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingBaselinePLT", | |
| 1171 page_load_time, | |
| 1172 base::TimeDelta::FromMilliseconds(10), | |
| 1173 base::TimeDelta::FromSeconds(60), | |
| 1174 100); | |
| 1175 | |
| 1176 base::TimeDelta prerender_age = GetCurrentTime() - prerender->start_time; | |
| 1177 if (prerender_age > page_load_time) { | |
| 1178 base::TimeDelta new_plt; | |
| 1179 if (prerender_age < 2 * page_load_time) | |
| 1180 new_plt = 2 * page_load_time - prerender_age; | |
| 1181 UMA_HISTOGRAM_CUSTOM_TIMES("Prerender.SimulatedLocalBrowsingPLT", | |
| 1182 new_plt, | |
| 1183 base::TimeDelta::FromMilliseconds(10), | |
| 1184 base::TimeDelta::FromSeconds(60), | |
| 1185 100); | |
| 1186 } | |
| 1187 } | |
| 1188 } | |
| 1189 | |
| 1190 bool PrerenderLocalPredictor::IsPrerenderStillValid( | |
| 1191 PrerenderLocalPredictor::PrerenderProperties* prerender) const { | |
| 1192 return (prerender && | |
| 1193 (prerender->start_time + | |
| 1194 base::TimeDelta::FromMilliseconds(GetMaxLocalPredictionTimeMs())) | |
| 1195 > GetCurrentTime()); | |
| 1196 } | |
| 1197 | |
| 1198 void PrerenderLocalPredictor::RecordEvent( | |
| 1199 PrerenderLocalPredictor::Event event) const { | |
| 1200 UMA_HISTOGRAM_ENUMERATION("Prerender.LocalPredictorEvent", | |
| 1201 event, PrerenderLocalPredictor::EVENT_MAX_VALUE); | |
| 1202 } | |
| 1203 | |
| 1204 bool PrerenderLocalPredictor::DoesPrerenderMatchPLTRecord( | |
| 1205 PrerenderProperties* prerender, | |
| 1206 const GURL& url, | |
| 1207 base::TimeDelta plt) const { | |
| 1208 if (prerender && prerender->start_time < GetCurrentTime() - plt) { | |
| 1209 if (prerender->url.is_empty()) | |
| 1210 RecordEvent(EVENT_ERROR_NO_PRERENDER_URL_FOR_PLT); | |
| 1211 return (prerender->url == url); | |
| 1212 } else { | |
| 1213 return false; | |
| 1214 } | |
| 1215 } | |
| 1216 | |
| 1217 PrerenderLocalPredictor::PrerenderProperties* | |
| 1218 PrerenderLocalPredictor::GetIssuedPrerenderSlotForPriority(const GURL& url, | |
| 1219 double priority) { | |
| 1220 int num_prerenders = GetLocalPredictorMaxConcurrentPrerenders(); | |
| 1221 while (static_cast<int>(issued_prerenders_.size()) < num_prerenders) | |
| 1222 issued_prerenders_.push_back(new PrerenderProperties()); | |
| 1223 // First, check if we already have a prerender for the same URL issued. | |
| 1224 // If yes, we don't want to prerender this URL again, so we return NULL | |
| 1225 // (on matching slot found). | |
| 1226 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { | |
| 1227 PrerenderProperties* p = issued_prerenders_[i]; | |
| 1228 DCHECK(p != NULL); | |
| 1229 if (p->prerender_handle && p->prerender_handle->IsPrerendering() && | |
| 1230 p->prerender_handle->Matches(url, NULL)) { | |
| 1231 return NULL; | |
| 1232 } | |
| 1233 } | |
| 1234 // Otherwise, let's see if there are any empty slots. If yes, return the first | |
| 1235 // one we find. Otherwise, if the lowest priority prerender has a lower | |
| 1236 // priority than the page we want to prerender, use its slot. | |
| 1237 PrerenderProperties* lowest_priority_prerender = NULL; | |
| 1238 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { | |
| 1239 PrerenderProperties* p = issued_prerenders_[i]; | |
| 1240 DCHECK(p != NULL); | |
| 1241 if (!p->prerender_handle || !p->prerender_handle->IsPrerendering()) | |
| 1242 return p; | |
| 1243 double decayed_priority = p->GetCurrentDecayedPriority(); | |
| 1244 if (decayed_priority > priority) | |
| 1245 continue; | |
| 1246 if (lowest_priority_prerender == NULL || | |
| 1247 lowest_priority_prerender->GetCurrentDecayedPriority() > | |
| 1248 decayed_priority) { | |
| 1249 lowest_priority_prerender = p; | |
| 1250 } | |
| 1251 } | |
| 1252 return lowest_priority_prerender; | |
| 1253 } | |
| 1254 | |
| 1255 void PrerenderLocalPredictor::ContinuePrerenderCheck( | |
| 1256 scoped_ptr<CandidatePrerenderInfo> info) { | |
| 1257 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1258 TIMING_HISTOGRAM("Prerender.LocalPredictorLoggedInLookupTime", | |
| 1259 base::Time::Now() - info->start_time_); | |
| 1260 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_STARTED); | |
| 1261 if (info->candidate_urls_.size() == 0) { | |
| 1262 RecordEvent(EVENT_NO_PRERENDER_CANDIDATES); | |
| 1263 return; | |
| 1264 } | |
| 1265 scoped_ptr<LocalPredictorURLInfo> url_info; | |
| 1266 #if defined(FULL_SAFE_BROWSING) | |
| 1267 scoped_refptr<SafeBrowsingDatabaseManager> sb_db_manager = | |
| 1268 g_browser_process->safe_browsing_service()->database_manager(); | |
| 1269 #endif | |
| 1270 int num_issued = 0; | |
| 1271 for (int i = 0; i < static_cast<int>(info->candidate_urls_.size()); i++) { | |
| 1272 if (num_issued >= GetLocalPredictorMaxLaunchPrerenders()) | |
| 1273 return; | |
| 1274 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL); | |
| 1275 url_info.reset(new LocalPredictorURLInfo(info->candidate_urls_[i])); | |
| 1276 if (url_info->local_history_based) { | |
| 1277 if (SkipLocalPredictorLocalCandidates()) { | |
| 1278 url_info.reset(NULL); | |
| 1279 continue; | |
| 1280 } | |
| 1281 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_LOCAL); | |
| 1282 } | |
| 1283 if (!url_info->local_history_based) { | |
| 1284 if (SkipLocalPredictorServiceCandidates()) { | |
| 1285 url_info.reset(NULL); | |
| 1286 continue; | |
| 1287 } | |
| 1288 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_SERVICE); | |
| 1289 } | |
| 1290 | |
| 1291 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_EXAMINE_NEXT_URL_NOT_SKIPPED); | |
| 1292 | |
| 1293 // We need to check whether we can issue a prerender for this URL. | |
| 1294 // We test a set of conditions. Each condition can either rule out | |
| 1295 // a prerender (in which case we reset url_info, so that it will not | |
| 1296 // be prerendered, and we continue, which means try the next candidate | |
| 1297 // URL), or it can be sufficient to issue the prerender without any | |
| 1298 // further checks (in which case we just break). | |
| 1299 // The order of the checks is critical, because it prescribes the logic | |
| 1300 // we use here to decide what to prerender. | |
| 1301 if (!url_info->url_lookup_success) { | |
| 1302 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NO_URL); | |
| 1303 url_info.reset(NULL); | |
| 1304 continue; | |
| 1305 } | |
| 1306 if (!SkipLocalPredictorFragment() && | |
| 1307 URLsIdenticalIgnoringFragments(info->source_url_.url, | |
| 1308 url_info->url)) { | |
| 1309 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_URLS_IDENTICAL_BUT_FRAGMENT); | |
| 1310 url_info.reset(NULL); | |
| 1311 continue; | |
| 1312 } | |
| 1313 if (!SkipLocalPredictorHTTPS() && url_info->url.SchemeIs("https")) { | |
| 1314 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_HTTPS); | |
| 1315 url_info.reset(NULL); | |
| 1316 continue; | |
| 1317 } | |
| 1318 if (IsRootPageURL(url_info->url)) { | |
| 1319 // For root pages, we assume that they are reasonably safe, and we | |
| 1320 // will just prerender them without any additional checks. | |
| 1321 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ROOT_PAGE); | |
| 1322 IssuePrerender(info.get(), url_info.get()); | |
| 1323 num_issued++; | |
| 1324 continue; | |
| 1325 } | |
| 1326 if (IsLogOutURL(url_info->url)) { | |
| 1327 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGOUT_URL); | |
| 1328 url_info.reset(NULL); | |
| 1329 continue; | |
| 1330 } | |
| 1331 if (IsLogInURL(url_info->url)) { | |
| 1332 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_LOGIN_URL); | |
| 1333 url_info.reset(NULL); | |
| 1334 continue; | |
| 1335 } | |
| 1336 #if defined(FULL_SAFE_BROWSING) | |
| 1337 if (!SkipLocalPredictorWhitelist() && sb_db_manager.get() && | |
| 1338 sb_db_manager->CheckSideEffectFreeWhitelistUrl(url_info->url)) { | |
| 1339 // If a page is on the side-effect free whitelist, we will just prerender | |
| 1340 // it without any additional checks. | |
| 1341 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SIDE_EFFECT_FREE_WHITELIST); | |
| 1342 IssuePrerender(info.get(), url_info.get()); | |
| 1343 num_issued++; | |
| 1344 continue; | |
| 1345 } | |
| 1346 #endif | |
| 1347 if (!SkipLocalPredictorServiceWhitelist() && | |
| 1348 url_info->service_whitelist && url_info->service_whitelist_lookup_ok) { | |
| 1349 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ON_SERVICE_WHITELIST); | |
| 1350 IssuePrerender(info.get(), url_info.get()); | |
| 1351 num_issued++; | |
| 1352 continue; | |
| 1353 } | |
| 1354 if (!SkipLocalPredictorLoggedIn() && | |
| 1355 !url_info->logged_in && url_info->logged_in_lookup_ok) { | |
| 1356 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_NOT_LOGGED_IN); | |
| 1357 IssuePrerender(info.get(), url_info.get()); | |
| 1358 num_issued++; | |
| 1359 continue; | |
| 1360 } | |
| 1361 if (!SkipLocalPredictorDefaultNoPrerender()) { | |
| 1362 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_NOT_PRERENDERING); | |
| 1363 url_info.reset(NULL); | |
| 1364 } else { | |
| 1365 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_FALLTHROUGH_PRERENDERING); | |
| 1366 IssuePrerender(info.get(), url_info.get()); | |
| 1367 num_issued++; | |
| 1368 continue; | |
| 1369 } | |
| 1370 } | |
| 1371 } | |
| 1372 | |
| 1373 void PrerenderLocalPredictor::IssuePrerender( | |
| 1374 CandidatePrerenderInfo* info, | |
| 1375 LocalPredictorURLInfo* url_info) { | |
| 1376 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); | |
| 1377 RecordEvent(EVENT_ISSUE_PRERENDER_CALLED); | |
| 1378 if (prefetch_list_->AddURL(url_info->url)) { | |
| 1379 RecordEvent(EVENT_PREFETCH_LIST_ADDED); | |
| 1380 // If we are prefetching rather than prerendering, now is the time to launch | |
| 1381 // the prefetch. | |
| 1382 if (IsLocalPredictorPrerenderPrefetchEnabled()) { | |
| 1383 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ENABLED); | |
| 1384 // Obtain the render frame host that caused this prefetch. | |
| 1385 RenderFrameHost* rfh = RenderFrameHost::FromID(info->render_process_id_, | |
| 1386 info->render_frame_id_); | |
| 1387 // If it is still alive, launch the prefresh. | |
| 1388 if (rfh) { | |
| 1389 rfh->Send(new PrefetchMsg_Prefetch(rfh->GetRoutingID(), url_info->url)); | |
| 1390 RecordEvent(EVENT_ISSUE_PRERENDER_PREFETCH_ISSUED); | |
| 1391 } | |
| 1392 } | |
| 1393 } | |
| 1394 PrerenderProperties* prerender_properties = | |
| 1395 GetIssuedPrerenderSlotForPriority(url_info->url, url_info->priority); | |
| 1396 if (!prerender_properties) { | |
| 1397 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_PRIORITY_TOO_LOW); | |
| 1398 return; | |
| 1399 } | |
| 1400 RecordEvent(EVENT_CONTINUE_PRERENDER_CHECK_ISSUING_PRERENDER); | |
| 1401 DCHECK(prerender_properties != NULL); | |
| 1402 DCHECK(info != NULL); | |
| 1403 DCHECK(url_info != NULL); | |
| 1404 if (!IsLocalPredictorPrerenderLaunchEnabled()) | |
| 1405 return; | |
| 1406 URLID url_id = url_info->id; | |
| 1407 const GURL& url = url_info->url; | |
| 1408 double priority = url_info->priority; | |
| 1409 base::Time current_time = GetCurrentTime(); | |
| 1410 RecordEvent(EVENT_ISSUING_PRERENDER); | |
| 1411 | |
| 1412 // Issue the prerender and obtain a new handle. | |
| 1413 scoped_ptr<prerender::PrerenderHandle> new_prerender_handle( | |
| 1414 prerender_manager_->AddPrerenderFromLocalPredictor( | |
| 1415 url, info->session_storage_namespace_.get(), *(info->size_))); | |
| 1416 | |
| 1417 // Check if this is a duplicate of an existing prerender. If yes, clean up | |
| 1418 // the new handle. | |
| 1419 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { | |
| 1420 PrerenderProperties* p = issued_prerenders_[i]; | |
| 1421 DCHECK(p != NULL); | |
| 1422 if (new_prerender_handle && | |
| 1423 new_prerender_handle->RepresentingSamePrerenderAs( | |
| 1424 p->prerender_handle.get())) { | |
| 1425 new_prerender_handle->OnCancel(); | |
| 1426 new_prerender_handle.reset(NULL); | |
| 1427 RecordEvent(EVENT_ISSUE_PRERENDER_ALREADY_PRERENDERING); | |
| 1428 break; | |
| 1429 } | |
| 1430 } | |
| 1431 | |
| 1432 if (new_prerender_handle.get()) { | |
| 1433 RecordEvent(EVENT_ISSUE_PRERENDER_NEW_PRERENDER); | |
| 1434 // The new prerender does not match any existing prerenders. Update | |
| 1435 // prerender_properties so that it reflects the new entry. | |
| 1436 prerender_properties->url_id = url_id; | |
| 1437 prerender_properties->url = url; | |
| 1438 prerender_properties->priority = priority; | |
| 1439 prerender_properties->start_time = current_time; | |
| 1440 prerender_properties->actual_start_time = current_time; | |
| 1441 prerender_properties->would_have_matched = false; | |
| 1442 prerender_properties->prerender_handle.swap(new_prerender_handle); | |
| 1443 // new_prerender_handle now represents the old previou prerender that we | |
| 1444 // are replacing. So we need to cancel it. | |
| 1445 if (new_prerender_handle) { | |
| 1446 new_prerender_handle->OnCancel(); | |
| 1447 RecordEvent(EVENT_ISSUE_PRERENDER_CANCELLED_OLD_PRERENDER); | |
| 1448 } | |
| 1449 } | |
| 1450 | |
| 1451 RecordEvent(EVENT_ADD_VISIT_PRERENDERING); | |
| 1452 if (current_prerender_.get() && current_prerender_->url_id == url_id) { | |
| 1453 RecordEvent(EVENT_ADD_VISIT_PRERENDERING_EXTENDED); | |
| 1454 if (priority > current_prerender_->priority) | |
| 1455 current_prerender_->priority = priority; | |
| 1456 // If the prerender already existed, we want to extend it. However, | |
| 1457 // we do not want to set its start_time to the current time to | |
| 1458 // disadvantage PLT computations when the prerender is swapped in. | |
| 1459 // So we set the new start time to current_time - 10s (since the vast | |
| 1460 // majority of PLTs are < 10s), provided that is not before the actual | |
| 1461 // time the prerender was started (so as to not artificially advantage | |
| 1462 // the PLT computation). | |
| 1463 base::Time simulated_new_start_time = | |
| 1464 current_time - base::TimeDelta::FromSeconds(10); | |
| 1465 if (simulated_new_start_time > current_prerender_->start_time) | |
| 1466 current_prerender_->start_time = simulated_new_start_time; | |
| 1467 } else { | |
| 1468 current_prerender_.reset( | |
| 1469 new PrerenderProperties(url_id, url, priority, current_time)); | |
| 1470 } | |
| 1471 current_prerender_->actual_start_time = current_time; | |
| 1472 } | |
| 1473 | |
| 1474 void PrerenderLocalPredictor::OnTabHelperURLSeen( | |
| 1475 const GURL& url, WebContents* web_contents) { | |
| 1476 RecordEvent(EVENT_TAB_HELPER_URL_SEEN); | |
| 1477 | |
| 1478 if (prefetch_list_->MarkURLSeen(url, PrefetchList::SEEN_TABCONTENTS_OBSERVER)) | |
| 1479 RecordEvent(EVENT_PREFETCH_LIST_SEEN_TABCONTENTS); | |
| 1480 bool browser_navigate_initiated = false; | |
| 1481 const content::NavigationEntry* entry = | |
| 1482 web_contents->GetController().GetPendingEntry(); | |
| 1483 if (entry) { | |
| 1484 base::string16 result; | |
| 1485 browser_navigate_initiated = | |
| 1486 entry->GetExtraData(kChromeNavigateExtraDataKey, &result); | |
| 1487 } | |
| 1488 | |
| 1489 // If the namespace matches and the URL matches, we might be able to swap | |
| 1490 // in. However, the actual code initating the swapin is in the renderer | |
| 1491 // and is checking for other criteria (such as POSTs). There may | |
| 1492 // also be conditions when a swapin should happen but does not. By recording | |
| 1493 // the two previous events, we can keep an eye on the magnitude of the | |
| 1494 // discrepancy. | |
| 1495 | |
| 1496 PrerenderProperties* best_matched_prerender = NULL; | |
| 1497 bool session_storage_namespace_matches = false; | |
| 1498 SessionStorageNamespace* tab_session_storage_namespace = | |
| 1499 web_contents->GetController().GetDefaultSessionStorageNamespace(); | |
| 1500 for (int i = 0; i < static_cast<int>(issued_prerenders_.size()); i++) { | |
| 1501 PrerenderProperties* p = issued_prerenders_[i]; | |
| 1502 DCHECK(p != NULL); | |
| 1503 if (!p->prerender_handle.get() || | |
| 1504 !p->prerender_handle->Matches(url, NULL) || | |
| 1505 p->would_have_matched) { | |
| 1506 continue; | |
| 1507 } | |
| 1508 if (!best_matched_prerender || !session_storage_namespace_matches) { | |
| 1509 best_matched_prerender = p; | |
| 1510 session_storage_namespace_matches = | |
| 1511 p->prerender_handle->Matches(url, tab_session_storage_namespace); | |
| 1512 } | |
| 1513 } | |
| 1514 if (best_matched_prerender) { | |
| 1515 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH); | |
| 1516 if (entry) | |
| 1517 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_ENTRY); | |
| 1518 if (browser_navigate_initiated) | |
| 1519 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_MATCH_BROWSER_NAVIGATE); | |
| 1520 best_matched_prerender->would_have_matched = true; | |
| 1521 if (session_storage_namespace_matches) { | |
| 1522 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH); | |
| 1523 if (entry) | |
| 1524 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_ENTRY); | |
| 1525 if (browser_navigate_initiated) | |
| 1526 RecordEvent(EVENT_TAB_HELPER_URL_SEEN_NAMESPACE_MATCH_BROWSER_NAVIGATE); | |
| 1527 } | |
| 1528 } | |
| 1529 } | |
| 1530 | |
| 1531 } // namespace prerender | |
| OLD | NEW |