OLD | NEW |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/memory/tab_manager.h" | 5 #include "chrome/browser/memory/tab_manager.h" |
6 | 6 |
7 #include <algorithm> | 7 #include <algorithm> |
8 #include <set> | 8 #include <set> |
9 #include <vector> | 9 #include <vector> |
10 | 10 |
(...skipping 162 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
173 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), false, | 173 AddTabStats(BrowserList::GetInstance(chrome::HOST_DESKTOP_TYPE_ASH), false, |
174 &stats_list); | 174 &stats_list); |
175 } | 175 } |
176 | 176 |
177 // Sort the collected data so that least desirable to be killed is first, most | 177 // Sort the collected data so that least desirable to be killed is first, most |
178 // desirable is last. | 178 // desirable is last. |
179 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); | 179 std::sort(stats_list.begin(), stats_list.end(), CompareTabStats); |
180 return stats_list; | 180 return stats_list; |
181 } | 181 } |
182 | 182 |
| 183 bool TabManager::IsTabDiscarded(content::WebContents* contents) const { |
| 184 return GetWebContentsData(contents)->IsDiscarded(); |
| 185 } |
| 186 |
183 // TODO(jamescook): This should consider tabs with references to other tabs, | 187 // TODO(jamescook): This should consider tabs with references to other tabs, |
184 // such as tabs created with JavaScript window.open(). Potentially consider | 188 // such as tabs created with JavaScript window.open(). Potentially consider |
185 // discarding the entire set together, or use that in the priority computation. | 189 // discarding the entire set together, or use that in the priority computation. |
186 bool TabManager::DiscardTab() { | 190 bool TabManager::DiscardTab() { |
187 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 191 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
188 TabStatsList stats = GetTabStats(); | 192 TabStatsList stats = GetTabStats(); |
189 if (stats.empty()) | 193 if (stats.empty()) |
190 return false; | 194 return false; |
191 // Loop until a non-discarded tab to kill is found. | 195 // Loop until a non-discarded tab to kill is found. |
192 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin(); | 196 for (TabStatsList::const_reverse_iterator stats_rit = stats.rbegin(); |
(...skipping 27 matching lines...) Expand all Loading... |
220 const base::Closure& callback) { | 224 const base::Closure& callback) { |
221 DCHECK_CURRENTLY_ON(BrowserThread::UI); | 225 DCHECK_CURRENTLY_ON(BrowserThread::UI); |
222 OomMemoryDetails::Log(title, callback); | 226 OomMemoryDetails::Log(title, callback); |
223 } | 227 } |
224 | 228 |
225 void TabManager::TabChangedAt(content::WebContents* contents, | 229 void TabManager::TabChangedAt(content::WebContents* contents, |
226 int index, | 230 int index, |
227 TabChangeType change_type) { | 231 TabChangeType change_type) { |
228 if (change_type != TabChangeType::ALL) | 232 if (change_type != TabChangeType::ALL) |
229 return; | 233 return; |
230 | 234 auto data = GetWebContentsData(contents); |
231 bool old_state = WebContentsData::IsRecentlyAudible(contents); | 235 bool old_state = data->IsRecentlyAudible(); |
232 bool current_state = contents->WasRecentlyAudible(); | 236 bool current_state = contents->WasRecentlyAudible(); |
233 if (old_state != current_state) { | 237 if (old_state != current_state) { |
234 WebContentsData::SetRecentlyAudible(contents, current_state); | 238 data->SetRecentlyAudible(current_state); |
235 WebContentsData::SetLastAudioChangeTime(contents, TimeTicks::Now()); | 239 data->SetLastAudioChangeTime(TimeTicks::Now()); |
236 } | 240 } |
237 } | 241 } |
238 | 242 |
| 243 void TabManager::ActiveTabChanged(content::WebContents* old_contents, |
| 244 content::WebContents* new_contents, |
| 245 int index, |
| 246 int reason) { |
| 247 GetWebContentsData(new_contents)->SetDiscardState(false); |
| 248 } |
| 249 |
239 /////////////////////////////////////////////////////////////////////////////// | 250 /////////////////////////////////////////////////////////////////////////////// |
240 // TabManager, private: | 251 // TabManager, private: |
241 | 252 |
242 // static | 253 // static |
243 void TabManager::PurgeMemoryAndDiscardTab() { | 254 void TabManager::PurgeMemoryAndDiscardTab() { |
244 if (g_browser_process && g_browser_process->GetTabManager()) { | 255 if (g_browser_process && g_browser_process->GetTabManager()) { |
245 TabManager* manager = g_browser_process->GetTabManager(); | 256 TabManager* manager = g_browser_process->GetTabManager(); |
246 manager->PurgeBrowserMemory(); | 257 manager->PurgeBrowserMemory(); |
247 manager->DiscardTab(); | 258 manager->DiscardTab(); |
248 } | 259 } |
(...skipping 112 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
361 for (int i = 0; i < model->count(); i++) { | 372 for (int i = 0; i < model->count(); i++) { |
362 WebContents* contents = model->GetWebContentsAt(i); | 373 WebContents* contents = model->GetWebContentsAt(i); |
363 if (!contents->IsCrashed()) { | 374 if (!contents->IsCrashed()) { |
364 TabStats stats; | 375 TabStats stats; |
365 stats.is_app = is_browser_for_app; | 376 stats.is_app = is_browser_for_app; |
366 stats.is_internal_page = | 377 stats.is_internal_page = |
367 IsInternalPage(contents->GetLastCommittedURL()); | 378 IsInternalPage(contents->GetLastCommittedURL()); |
368 stats.is_playing_audio = IsAudioTab(contents); | 379 stats.is_playing_audio = IsAudioTab(contents); |
369 stats.is_pinned = model->IsTabPinned(i); | 380 stats.is_pinned = model->IsTabPinned(i); |
370 stats.is_selected = browser_active && model->IsTabSelected(i); | 381 stats.is_selected = browser_active && model->IsTabSelected(i); |
371 stats.is_discarded = WebContentsData::IsDiscarded(contents); | 382 stats.is_discarded = GetWebContentsData(contents)->IsDiscarded(); |
372 stats.has_form_entry = | 383 stats.has_form_entry = |
373 contents->GetPageImportanceSignals().had_form_interaction; | 384 contents->GetPageImportanceSignals().had_form_interaction; |
374 stats.discard_count = WebContentsData::DiscardCount(contents); | 385 stats.discard_count = GetWebContentsData(contents)->DiscardCount(); |
375 stats.last_active = contents->GetLastActiveTime(); | 386 stats.last_active = contents->GetLastActiveTime(); |
376 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); | 387 stats.renderer_handle = contents->GetRenderProcessHost()->GetHandle(); |
377 stats.child_process_host_id = contents->GetRenderProcessHost()->GetID(); | 388 stats.child_process_host_id = contents->GetRenderProcessHost()->GetID(); |
378 #if defined(OS_CHROMEOS) | 389 #if defined(OS_CHROMEOS) |
379 stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id); | 390 stats.oom_score = delegate_->GetOomScore(stats.child_process_host_id); |
380 #endif | 391 #endif |
381 stats.title = contents->GetTitle(); | 392 stats.title = contents->GetTitle(); |
382 stats.tab_contents_id = IdFromWebContents(contents); | 393 stats.tab_contents_id = IdFromWebContents(contents); |
383 stats_list->push_back(stats); | 394 stats_list->push_back(stats); |
384 } | 395 } |
(...skipping 49 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
434 if (web_contents->GetPageImportanceSignals().had_form_interaction) | 445 if (web_contents->GetPageImportanceSignals().had_form_interaction) |
435 return false; | 446 return false; |
436 | 447 |
437 // Do not discard tabs that are playing audio as it's too distruptive to the | 448 // Do not discard tabs that are playing audio as it's too distruptive to the |
438 // user experience. Note that tabs that have recently stopped playing audio by | 449 // user experience. Note that tabs that have recently stopped playing audio by |
439 // at least |kAudioProtectionTimeSeconds| seconds are protected as well. | 450 // at least |kAudioProtectionTimeSeconds| seconds are protected as well. |
440 if (IsAudioTab(web_contents)) | 451 if (IsAudioTab(web_contents)) |
441 return false; | 452 return false; |
442 | 453 |
443 // Do not discard a previously discarded tab if that's the desired behavior. | 454 // Do not discard a previously discarded tab if that's the desired behavior. |
444 if (discard_once_ && WebContentsData::DiscardCount(web_contents) > 0) | 455 if (discard_once_ && GetWebContentsData(web_contents)->DiscardCount() > 0) |
445 return false; | 456 return false; |
446 | 457 |
447 return true; | 458 return true; |
448 } | 459 } |
449 | 460 |
450 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) { | 461 WebContents* TabManager::DiscardWebContentsAt(int index, TabStripModel* model) { |
451 // Can't discard active index. | 462 // Can't discard active index. |
452 if (model->active_index() == index) | 463 if (model->active_index() == index) |
453 return nullptr; | 464 return nullptr; |
454 | 465 |
455 WebContents* old_contents = model->GetWebContentsAt(index); | 466 WebContents* old_contents = model->GetWebContentsAt(index); |
456 | 467 |
457 // Can't discard tabs that are already discarded. | 468 // Can't discard tabs that are already discarded. |
458 if (WebContentsData::IsDiscarded(old_contents)) | 469 if (GetWebContentsData(old_contents)->IsDiscarded()) |
459 return nullptr; | 470 return nullptr; |
460 | 471 |
461 // Record statistics before discarding to capture the memory state that leads | 472 // Record statistics before discarding to capture the memory state that leads |
462 // to the discard. | 473 // to the discard. |
463 RecordDiscardStatistics(); | 474 RecordDiscardStatistics(); |
464 | 475 |
465 WebContents* null_contents = | 476 WebContents* null_contents = |
466 WebContents::Create(WebContents::CreateParams(model->profile())); | 477 WebContents::Create(WebContents::CreateParams(model->profile())); |
467 // Copy over the state from the navigation controller to preserve the | 478 // Copy over the state from the navigation controller to preserve the |
468 // back/forward history and to continue to display the correct title/favicon. | 479 // back/forward history and to continue to display the correct title/favicon. |
469 null_contents->GetController().CopyStateFrom(old_contents->GetController()); | 480 null_contents->GetController().CopyStateFrom(old_contents->GetController()); |
470 | 481 |
471 // Make sure to persist the last active time property. | 482 // Make sure to persist the last active time property. |
472 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime()); | 483 null_contents->SetLastActiveTime(old_contents->GetLastActiveTime()); |
473 // Copy over the discard count. | 484 // Copy over the discard count. |
474 WebContentsData::CopyState(old_contents, null_contents); | 485 WebContentsData::CopyState(old_contents, null_contents); |
475 | 486 |
476 // Replace the discarded tab with the null version. | 487 // Replace the discarded tab with the null version. |
477 model->ReplaceWebContentsAt(index, null_contents); | 488 model->ReplaceWebContentsAt(index, null_contents); |
478 // Mark the tab so it will reload when clicked on. | 489 // Mark the tab so it will reload when clicked on. |
479 WebContentsData::SetDiscardState(null_contents, true); | 490 GetWebContentsData(null_contents)->SetDiscardState(true); |
480 WebContentsData::IncrementDiscardCount(null_contents); | 491 GetWebContentsData(null_contents)->IncrementDiscardCount(); |
481 | 492 |
482 // Discard the old tab's renderer. | 493 // Discard the old tab's renderer. |
483 // TODO(jamescook): This breaks script connections with other tabs. | 494 // TODO(jamescook): This breaks script connections with other tabs. |
484 // Find a different approach that doesn't do that, perhaps based on navigation | 495 // Find a different approach that doesn't do that, perhaps based on navigation |
485 // to swappedout://. | 496 // to swappedout://. |
486 delete old_contents; | 497 delete old_contents; |
487 recent_tab_discard_ = true; | 498 recent_tab_discard_ = true; |
488 | 499 |
489 return null_contents; | 500 return null_contents; |
490 } | 501 } |
(...skipping 10 matching lines...) Expand all Loading... |
501 LogMemoryAndDiscardTab(); | 512 LogMemoryAndDiscardTab(); |
502 } | 513 } |
503 // TODO(skuhne): If more memory pressure levels are introduced, consider | 514 // TODO(skuhne): If more memory pressure levels are introduced, consider |
504 // calling PurgeBrowserMemory() before CRITICAL is reached. | 515 // calling PurgeBrowserMemory() before CRITICAL is reached. |
505 } | 516 } |
506 | 517 |
507 bool TabManager::IsAudioTab(WebContents* contents) const { | 518 bool TabManager::IsAudioTab(WebContents* contents) const { |
508 if (contents->WasRecentlyAudible()) | 519 if (contents->WasRecentlyAudible()) |
509 return true; | 520 return true; |
510 auto delta = | 521 auto delta = |
511 TimeTicks::Now() - WebContentsData::LastAudioChangeTime(contents); | 522 TimeTicks::Now() - GetWebContentsData(contents)->LastAudioChangeTime(); |
512 return delta < TimeDelta::FromSeconds(kAudioProtectionTimeSeconds); | 523 return delta < TimeDelta::FromSeconds(kAudioProtectionTimeSeconds); |
513 } | 524 } |
514 | 525 |
| 526 TabManager::WebContentsData* TabManager::GetWebContentsData( |
| 527 content::WebContents* contents) const { |
| 528 WebContentsData::CreateForWebContents(contents); |
| 529 return WebContentsData::FromWebContents(contents); |
| 530 } |
| 531 |
515 // static | 532 // static |
516 bool TabManager::CompareTabStats(TabStats first, TabStats second) { | 533 bool TabManager::CompareTabStats(TabStats first, TabStats second) { |
517 // Being currently selected is most important to protect. | 534 // Being currently selected is most important to protect. |
518 if (first.is_selected != second.is_selected) | 535 if (first.is_selected != second.is_selected) |
519 return first.is_selected; | 536 return first.is_selected; |
520 | 537 |
521 // Protect tabs with pending form entries. | 538 // Protect tabs with pending form entries. |
522 if (first.has_form_entry != second.has_form_entry) | 539 if (first.has_form_entry != second.has_form_entry) |
523 return first.has_form_entry; | 540 return first.has_form_entry; |
524 | 541 |
(...skipping 21 matching lines...) Expand all Loading... |
546 // sudden_termination_allowed false, and that covers too many common pages | 563 // sudden_termination_allowed false, and that covers too many common pages |
547 // with ad networks and statistics scripts. Ideally check for beforeUnload | 564 // with ad networks and statistics scripts. Ideally check for beforeUnload |
548 // handlers, which are likely to present a dialog asking if the user wants to | 565 // handlers, which are likely to present a dialog asking if the user wants to |
549 // discard state. crbug.com/123049. | 566 // discard state. crbug.com/123049. |
550 | 567 |
551 // Being more recently active is more important. | 568 // Being more recently active is more important. |
552 return first.last_active > second.last_active; | 569 return first.last_active > second.last_active; |
553 } | 570 } |
554 | 571 |
555 } // namespace memory | 572 } // namespace memory |
OLD | NEW |