tab_strip_model.cc revision d0247b1b59f9c528cb6df88b4f2b9afaf80d181e
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/ui/tabs/tab_strip_model.h"
6
7#include <algorithm>
8#include <map>
9#include <string>
10
11#include "base/metrics/histogram.h"
12#include "base/stl_util.h"
13#include "chrome/app/chrome_command_ids.h"
14#include "chrome/browser/browser_shutdown.h"
15#include "chrome/browser/defaults.h"
16#include "chrome/browser/extensions/tab_helper.h"
17#include "chrome/browser/profiles/profile.h"
18#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
19#include "chrome/browser/ui/tab_contents/core_tab_helper_delegate.h"
20#include "chrome/browser/ui/tabs/tab_strip_model_delegate.h"
21#include "chrome/browser/ui/tabs/tab_strip_model_order_controller.h"
22#include "chrome/common/url_constants.h"
23#include "components/web_modal/web_contents_modal_dialog_manager.h"
24#include "content/public/browser/notification_service.h"
25#include "content/public/browser/notification_types.h"
26#include "content/public/browser/render_process_host.h"
27#include "content/public/browser/user_metrics.h"
28#include "content/public/browser/web_contents.h"
29#include "content/public/browser/web_contents_view.h"
30
31using content::UserMetricsAction;
32using content::WebContents;
33
34namespace {
35
36// Returns true if the specified transition is one of the types that cause the
37// opener relationships for the tab in which the transition occurred to be
38// forgotten. This is generally any navigation that isn't a link click (i.e.
39// any navigation that can be considered to be the start of a new task distinct
40// from what had previously occurred in that tab).
41bool ShouldForgetOpenersForTransition(content::PageTransition transition) {
42  return transition == content::PAGE_TRANSITION_TYPED ||
43      transition == content::PAGE_TRANSITION_AUTO_BOOKMARK ||
44      transition == content::PAGE_TRANSITION_GENERATED ||
45      transition == content::PAGE_TRANSITION_KEYWORD ||
46      transition == content::PAGE_TRANSITION_AUTO_TOPLEVEL;
47}
48
49// CloseTracker is used when closing a set of WebContents. It listens for
50// deletions of the WebContents and removes from the internal set any time one
51// is deleted.
52class CloseTracker : public content::NotificationObserver {
53 public:
54  typedef std::vector<WebContents*> Contents;
55
56  explicit CloseTracker(const Contents& contents);
57  virtual ~CloseTracker();
58
59  // Returns true if there is another WebContents in the Tracker.
60  bool HasNext() const;
61
62  // Returns the next WebContents, or NULL if there are no more.
63  WebContents* Next();
64
65 private:
66  // NotificationObserver:
67  virtual void Observe(int type,
68                       const content::NotificationSource& source,
69                       const content::NotificationDetails& details) OVERRIDE;
70
71  Contents contents_;
72
73  content::NotificationRegistrar registrar_;
74
75  DISALLOW_COPY_AND_ASSIGN(CloseTracker);
76};
77
78CloseTracker::CloseTracker(const Contents& contents)
79    : contents_(contents) {
80  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
81                 content::NotificationService::AllBrowserContextsAndSources());
82}
83
84CloseTracker::~CloseTracker() {
85}
86
87bool CloseTracker::HasNext() const {
88  return !contents_.empty();
89}
90
91WebContents* CloseTracker::Next() {
92  if (contents_.empty())
93    return NULL;
94
95  WebContents* web_contents = contents_[0];
96  contents_.erase(contents_.begin());
97  return web_contents;
98}
99
100void CloseTracker::Observe(int type,
101                           const content::NotificationSource& source,
102                           const content::NotificationDetails& details) {
103  WebContents* web_contents = content::Source<WebContents>(source).ptr();
104  Contents::iterator i =
105      std::find(contents_.begin(), contents_.end(), web_contents);
106  if (i != contents_.end())
107    contents_.erase(i);
108}
109
110}  // namespace
111
112///////////////////////////////////////////////////////////////////////////////
113// TabStripModel, public:
114
115TabStripModel::TabStripModel(TabStripModelDelegate* delegate, Profile* profile)
116    : delegate_(delegate),
117      profile_(profile),
118      closing_all_(false),
119      in_notify_(false) {
120  DCHECK(delegate_);
121  registrar_.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
122                 content::NotificationService::AllBrowserContextsAndSources());
123  order_controller_.reset(new TabStripModelOrderController(this));
124}
125
126TabStripModel::~TabStripModel() {
127  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
128                    TabStripModelDeleted());
129  STLDeleteElements(&contents_data_);
130  order_controller_.reset();
131}
132
133void TabStripModel::AddObserver(TabStripModelObserver* observer) {
134  observers_.AddObserver(observer);
135}
136
137void TabStripModel::RemoveObserver(TabStripModelObserver* observer) {
138  observers_.RemoveObserver(observer);
139}
140
141bool TabStripModel::ContainsIndex(int index) const {
142  return index >= 0 && index < count();
143}
144
145void TabStripModel::AppendWebContents(WebContents* contents,
146                                      bool foreground) {
147  InsertWebContentsAt(count(), contents,
148                      foreground ? (ADD_INHERIT_GROUP | ADD_ACTIVE) :
149                                   ADD_NONE);
150}
151
152void TabStripModel::InsertWebContentsAt(int index,
153                                        WebContents* contents,
154                                        int add_types) {
155  delegate_->WillAddWebContents(contents);
156
157  bool active = add_types & ADD_ACTIVE;
158  // Force app tabs to be pinned.
159  extensions::TabHelper* extensions_tab_helper =
160      extensions::TabHelper::FromWebContents(contents);
161  bool pin = extensions_tab_helper->is_app() || add_types & ADD_PINNED;
162  index = ConstrainInsertionIndex(index, pin);
163
164  // In tab dragging situations, if the last tab in the window was detached
165  // then the user aborted the drag, we will have the |closing_all_| member
166  // set (see DetachWebContentsAt) which will mess with our mojo here. We need
167  // to clear this bit.
168  closing_all_ = false;
169
170  // Have to get the active contents before we monkey with |contents_|
171  // otherwise we run into problems when we try to change the active contents
172  // since the old contents and the new contents will be the same...
173  WebContents* active_contents = GetActiveWebContents();
174  WebContentsData* data = new WebContentsData(contents);
175  data->pinned = pin;
176  if ((add_types & ADD_INHERIT_GROUP) && active_contents) {
177    if (active) {
178      // Forget any existing relationships, we don't want to make things too
179      // confusing by having multiple groups active at the same time.
180      ForgetAllOpeners();
181    }
182    // Anything opened by a link we deem to have an opener.
183    data->SetGroup(active_contents);
184  } else if ((add_types & ADD_INHERIT_OPENER) && active_contents) {
185    if (active) {
186      // Forget any existing relationships, we don't want to make things too
187      // confusing by having multiple groups active at the same time.
188      ForgetAllOpeners();
189    }
190    data->opener = active_contents;
191  }
192
193  web_modal::WebContentsModalDialogManager* modal_dialog_manager =
194      web_modal::WebContentsModalDialogManager::FromWebContents(contents);
195  if (modal_dialog_manager)
196    data->blocked = modal_dialog_manager->IsDialogActive();
197
198  contents_data_.insert(contents_data_.begin() + index, data);
199
200  selection_model_.IncrementFrom(index);
201
202  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
203                    TabInsertedAt(contents, index, active));
204  if (active) {
205    ui::ListSelectionModel new_model;
206    new_model.Copy(selection_model_);
207    new_model.SetSelectedIndex(index);
208    SetSelection(new_model, NOTIFY_DEFAULT);
209  }
210}
211
212WebContents* TabStripModel::ReplaceWebContentsAt(int index,
213                                                 WebContents* new_contents) {
214  delegate_->WillAddWebContents(new_contents);
215
216  DCHECK(ContainsIndex(index));
217  WebContents* old_contents = GetWebContentsAtImpl(index);
218
219  ForgetOpenersAndGroupsReferencing(old_contents);
220
221  contents_data_[index]->contents = new_contents;
222
223  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
224                    TabReplacedAt(this, old_contents, new_contents, index));
225
226  // When the active WebContents is replaced send out a selection notification
227  // too. We do this as nearly all observers need to treat a replacement of the
228  // selected contents as the selection changing.
229  if (active_index() == index) {
230    FOR_EACH_OBSERVER(
231        TabStripModelObserver,
232        observers_,
233        ActiveTabChanged(old_contents,
234                         new_contents,
235                         active_index(),
236                         TabStripModelObserver::CHANGE_REASON_REPLACED));
237  }
238  return old_contents;
239}
240
241WebContents* TabStripModel::DiscardWebContentsAt(int index) {
242  DCHECK(ContainsIndex(index));
243  // Do not discard active tab.
244  if (active_index() == index)
245    return NULL;
246
247  WebContents* null_contents =
248      WebContents::Create(WebContents::CreateParams(profile()));
249  WebContents* old_contents = GetWebContentsAtImpl(index);
250  // Copy over the state from the navigation controller so we preserve the
251  // back/forward history and continue to display the correct title/favicon.
252  null_contents->GetController().CopyStateFrom(old_contents->GetController());
253  // Replace the tab we're discarding with the null version.
254  ReplaceWebContentsAt(index, null_contents);
255  // Mark the tab so it will reload when we click.
256  contents_data_[index]->discarded = true;
257  // Discard the old tab's renderer.
258  // TODO(jamescook): This breaks script connections with other tabs.
259  // We need to find a different approach that doesn't do that, perhaps based
260  // on navigation to swappedout://.
261  delete old_contents;
262  return null_contents;
263}
264
265WebContents* TabStripModel::DetachWebContentsAt(int index) {
266  CHECK(!in_notify_);
267  if (contents_data_.empty())
268    return NULL;
269
270  DCHECK(ContainsIndex(index));
271
272  WebContents* removed_contents = GetWebContentsAtImpl(index);
273  bool was_selected = IsTabSelected(index);
274  int next_selected_index = order_controller_->DetermineNewSelectedIndex(index);
275  delete contents_data_[index];
276  contents_data_.erase(contents_data_.begin() + index);
277  ForgetOpenersAndGroupsReferencing(removed_contents);
278  if (empty())
279    closing_all_ = true;
280  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
281                    TabDetachedAt(removed_contents, index));
282  if (empty()) {
283    selection_model_.Clear();
284    // TabDetachedAt() might unregister observers, so send |TabStripEmpty()| in
285    // a second pass.
286    FOR_EACH_OBSERVER(TabStripModelObserver, observers_, TabStripEmpty());
287  } else {
288    int old_active = active_index();
289    selection_model_.DecrementFrom(index);
290    ui::ListSelectionModel old_model;
291    old_model.Copy(selection_model_);
292    if (index == old_active) {
293      NotifyIfTabDeactivated(removed_contents);
294      if (!selection_model_.empty()) {
295        // The active tab was removed, but there is still something selected.
296        // Move the active and anchor to the first selected index.
297        selection_model_.set_active(selection_model_.selected_indices()[0]);
298        selection_model_.set_anchor(selection_model_.active());
299      } else {
300        // The active tab was removed and nothing is selected. Reset the
301        // selection and send out notification.
302        selection_model_.SetSelectedIndex(next_selected_index);
303      }
304      NotifyIfActiveTabChanged(removed_contents, NOTIFY_DEFAULT);
305    }
306
307    // Sending notification in case the detached tab was selected. Using
308    // NotifyIfActiveOrSelectionChanged() here would not guarantee that a
309    // notification is sent even though the tab selection has changed because
310    // |old_model| is stored after calling DecrementFrom().
311    if (was_selected) {
312      FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
313                        TabSelectionChanged(this, old_model));
314    }
315  }
316  return removed_contents;
317}
318
319void TabStripModel::ActivateTabAt(int index, bool user_gesture) {
320  DCHECK(ContainsIndex(index));
321  ui::ListSelectionModel new_model;
322  new_model.Copy(selection_model_);
323  new_model.SetSelectedIndex(index);
324  SetSelection(new_model, user_gesture ? NOTIFY_USER_GESTURE : NOTIFY_DEFAULT);
325}
326
327void TabStripModel::AddTabAtToSelection(int index) {
328  DCHECK(ContainsIndex(index));
329  ui::ListSelectionModel new_model;
330  new_model.Copy(selection_model_);
331  new_model.AddIndexToSelection(index);
332  SetSelection(new_model, NOTIFY_DEFAULT);
333}
334
335void TabStripModel::MoveWebContentsAt(int index,
336                                      int to_position,
337                                      bool select_after_move) {
338  DCHECK(ContainsIndex(index));
339  if (index == to_position)
340    return;
341
342  int first_non_mini_tab = IndexOfFirstNonMiniTab();
343  if ((index < first_non_mini_tab && to_position >= first_non_mini_tab) ||
344      (to_position < first_non_mini_tab && index >= first_non_mini_tab)) {
345    // This would result in mini tabs mixed with non-mini tabs. We don't allow
346    // that.
347    return;
348  }
349
350  MoveWebContentsAtImpl(index, to_position, select_after_move);
351}
352
353void TabStripModel::MoveSelectedTabsTo(int index) {
354  int total_mini_count = IndexOfFirstNonMiniTab();
355  int selected_mini_count = 0;
356  int selected_count =
357      static_cast<int>(selection_model_.selected_indices().size());
358  for (int i = 0; i < selected_count &&
359           IsMiniTab(selection_model_.selected_indices()[i]); ++i) {
360    selected_mini_count++;
361  }
362
363  // To maintain that all mini-tabs occur before non-mini-tabs we move them
364  // first.
365  if (selected_mini_count > 0) {
366    MoveSelectedTabsToImpl(
367        std::min(total_mini_count - selected_mini_count, index), 0u,
368        selected_mini_count);
369    if (index > total_mini_count - selected_mini_count) {
370      // We're being told to drag mini-tabs to an invalid location. Adjust the
371      // index such that non-mini-tabs end up at a location as though we could
372      // move the mini-tabs to index. See description in header for more
373      // details.
374      index += selected_mini_count;
375    }
376  }
377  if (selected_mini_count == selected_count)
378    return;
379
380  // Then move the non-pinned tabs.
381  MoveSelectedTabsToImpl(std::max(index, total_mini_count),
382                         selected_mini_count,
383                         selected_count - selected_mini_count);
384}
385
386WebContents* TabStripModel::GetActiveWebContents() const {
387  return GetWebContentsAt(active_index());
388}
389
390WebContents* TabStripModel::GetWebContentsAt(int index) const {
391  if (ContainsIndex(index))
392    return GetWebContentsAtImpl(index);
393  return NULL;
394}
395
396int TabStripModel::GetIndexOfWebContents(const WebContents* contents) const {
397  for (size_t i = 0; i < contents_data_.size(); ++i) {
398    if (contents_data_[i]->contents == contents)
399      return i;
400  }
401  return kNoTab;
402}
403
404void TabStripModel::UpdateWebContentsStateAt(int index,
405    TabStripModelObserver::TabChangeType change_type) {
406  DCHECK(ContainsIndex(index));
407
408  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
409      TabChangedAt(GetWebContentsAtImpl(index), index, change_type));
410}
411
412void TabStripModel::CloseAllTabs() {
413  // Set state so that observers can adjust their behavior to suit this
414  // specific condition when CloseWebContentsAt causes a flurry of
415  // Close/Detach/Select notifications to be sent.
416  closing_all_ = true;
417  std::vector<int> closing_tabs;
418  for (int i = count() - 1; i >= 0; --i)
419    closing_tabs.push_back(i);
420  InternalCloseTabs(closing_tabs, CLOSE_CREATE_HISTORICAL_TAB);
421}
422
423bool TabStripModel::CloseWebContentsAt(int index, uint32 close_types) {
424  DCHECK(ContainsIndex(index));
425  std::vector<int> closing_tabs;
426  closing_tabs.push_back(index);
427  return InternalCloseTabs(closing_tabs, close_types);
428}
429
430bool TabStripModel::TabsAreLoading() const {
431  for (WebContentsDataVector::const_iterator iter = contents_data_.begin();
432       iter != contents_data_.end(); ++iter) {
433    if ((*iter)->contents->IsLoading())
434      return true;
435  }
436  return false;
437}
438
439WebContents* TabStripModel::GetOpenerOfWebContentsAt(int index) {
440  DCHECK(ContainsIndex(index));
441  return contents_data_[index]->opener;
442}
443
444void TabStripModel::SetOpenerOfWebContentsAt(int index,
445                                             WebContents* opener) {
446  DCHECK(ContainsIndex(index));
447  DCHECK(opener);
448  contents_data_[index]->opener = opener;
449}
450
451int TabStripModel::GetIndexOfNextWebContentsOpenedBy(const WebContents* opener,
452                                                     int start_index,
453                                                     bool use_group) const {
454  DCHECK(opener);
455  DCHECK(ContainsIndex(start_index));
456
457  // Check tabs after start_index first.
458  for (int i = start_index + 1; i < count(); ++i) {
459    if (OpenerMatches(contents_data_[i], opener, use_group))
460      return i;
461  }
462  // Then check tabs before start_index, iterating backwards.
463  for (int i = start_index - 1; i >= 0; --i) {
464    if (OpenerMatches(contents_data_[i], opener, use_group))
465      return i;
466  }
467  return kNoTab;
468}
469
470int TabStripModel::GetIndexOfLastWebContentsOpenedBy(const WebContents* opener,
471                                                     int start_index) const {
472  DCHECK(opener);
473  DCHECK(ContainsIndex(start_index));
474
475  for (int i = contents_data_.size() - 1; i > start_index; --i) {
476    if (contents_data_[i]->opener == opener)
477      return i;
478  }
479  return kNoTab;
480}
481
482void TabStripModel::TabNavigating(WebContents* contents,
483                                  content::PageTransition transition) {
484  if (ShouldForgetOpenersForTransition(transition)) {
485    // Don't forget the openers if this tab is a New Tab page opened at the
486    // end of the TabStrip (e.g. by pressing Ctrl+T). Give the user one
487    // navigation of one of these transition types before resetting the
488    // opener relationships (this allows for the use case of opening a new
489    // tab to do a quick look-up of something while viewing a tab earlier in
490    // the strip). We can make this heuristic more permissive if need be.
491    if (!IsNewTabAtEndOfTabStrip(contents)) {
492      // If the user navigates the current tab to another page in any way
493      // other than by clicking a link, we want to pro-actively forget all
494      // TabStrip opener relationships since we assume they're beginning a
495      // different task by reusing the current tab.
496      ForgetAllOpeners();
497      // In this specific case we also want to reset the group relationship,
498      // since it is now technically invalid.
499      ForgetGroup(contents);
500    }
501  }
502}
503
504void TabStripModel::ForgetAllOpeners() {
505  // Forget all opener memories so we don't do anything weird with tab
506  // re-selection ordering.
507  for (WebContentsDataVector::const_iterator iter = contents_data_.begin();
508       iter != contents_data_.end(); ++iter)
509    (*iter)->ForgetOpener();
510}
511
512void TabStripModel::ForgetGroup(WebContents* contents) {
513  int index = GetIndexOfWebContents(contents);
514  DCHECK(ContainsIndex(index));
515  contents_data_[index]->SetGroup(NULL);
516  contents_data_[index]->ForgetOpener();
517}
518
519bool TabStripModel::ShouldResetGroupOnSelect(WebContents* contents) const {
520  int index = GetIndexOfWebContents(contents);
521  DCHECK(ContainsIndex(index));
522  return contents_data_[index]->reset_group_on_select;
523}
524
525void TabStripModel::SetTabBlocked(int index, bool blocked) {
526  DCHECK(ContainsIndex(index));
527  if (contents_data_[index]->blocked == blocked)
528    return;
529  contents_data_[index]->blocked = blocked;
530  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
531                    TabBlockedStateChanged(contents_data_[index]->contents,
532                                           index));
533}
534
535void TabStripModel::SetTabPinned(int index, bool pinned) {
536  DCHECK(ContainsIndex(index));
537  if (contents_data_[index]->pinned == pinned)
538    return;
539
540  if (IsAppTab(index)) {
541    if (!pinned) {
542      // App tabs should always be pinned.
543      NOTREACHED();
544      return;
545    }
546    // Changing the pinned state of an app tab doesn't affect its mini-tab
547    // status.
548    contents_data_[index]->pinned = pinned;
549  } else {
550    // The tab is not an app tab, its position may have to change as the
551    // mini-tab state is changing.
552    int non_mini_tab_index = IndexOfFirstNonMiniTab();
553    contents_data_[index]->pinned = pinned;
554    if (pinned && index != non_mini_tab_index) {
555      MoveWebContentsAtImpl(index, non_mini_tab_index, false);
556      index = non_mini_tab_index;
557    } else if (!pinned && index + 1 != non_mini_tab_index) {
558      MoveWebContentsAtImpl(index, non_mini_tab_index - 1, false);
559      index = non_mini_tab_index - 1;
560    }
561
562    FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
563                      TabMiniStateChanged(contents_data_[index]->contents,
564                                          index));
565  }
566
567  // else: the tab was at the boundary and its position doesn't need to change.
568  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
569                    TabPinnedStateChanged(contents_data_[index]->contents,
570                                          index));
571}
572
573bool TabStripModel::IsTabPinned(int index) const {
574  DCHECK(ContainsIndex(index));
575  return contents_data_[index]->pinned;
576}
577
578bool TabStripModel::IsMiniTab(int index) const {
579  return IsTabPinned(index) || IsAppTab(index);
580}
581
582bool TabStripModel::IsAppTab(int index) const {
583  WebContents* contents = GetWebContentsAt(index);
584  return contents && extensions::TabHelper::FromWebContents(contents)->is_app();
585}
586
587bool TabStripModel::IsTabBlocked(int index) const {
588  return contents_data_[index]->blocked;
589}
590
591bool TabStripModel::IsTabDiscarded(int index) const {
592  return contents_data_[index]->discarded;
593}
594
595int TabStripModel::IndexOfFirstNonMiniTab() const {
596  for (size_t i = 0; i < contents_data_.size(); ++i) {
597    if (!IsMiniTab(static_cast<int>(i)))
598      return static_cast<int>(i);
599  }
600  // No mini-tabs.
601  return count();
602}
603
604int TabStripModel::ConstrainInsertionIndex(int index, bool mini_tab) {
605  return mini_tab ? std::min(std::max(0, index), IndexOfFirstNonMiniTab()) :
606      std::min(count(), std::max(index, IndexOfFirstNonMiniTab()));
607}
608
609void TabStripModel::ExtendSelectionTo(int index) {
610  DCHECK(ContainsIndex(index));
611  ui::ListSelectionModel new_model;
612  new_model.Copy(selection_model_);
613  new_model.SetSelectionFromAnchorTo(index);
614  SetSelection(new_model, NOTIFY_DEFAULT);
615}
616
617void TabStripModel::ToggleSelectionAt(int index) {
618  DCHECK(ContainsIndex(index));
619  ui::ListSelectionModel new_model;
620  new_model.Copy(selection_model());
621  if (selection_model_.IsSelected(index)) {
622    if (selection_model_.size() == 1) {
623      // One tab must be selected and this tab is currently selected so we can't
624      // unselect it.
625      return;
626    }
627    new_model.RemoveIndexFromSelection(index);
628    new_model.set_anchor(index);
629    if (new_model.active() == index ||
630        new_model.active() == ui::ListSelectionModel::kUnselectedIndex)
631      new_model.set_active(new_model.selected_indices()[0]);
632  } else {
633    new_model.AddIndexToSelection(index);
634    new_model.set_anchor(index);
635    new_model.set_active(index);
636  }
637  SetSelection(new_model, NOTIFY_DEFAULT);
638}
639
640void TabStripModel::AddSelectionFromAnchorTo(int index) {
641  ui::ListSelectionModel new_model;
642  new_model.Copy(selection_model_);
643  new_model.AddSelectionFromAnchorTo(index);
644  SetSelection(new_model, NOTIFY_DEFAULT);
645}
646
647bool TabStripModel::IsTabSelected(int index) const {
648  DCHECK(ContainsIndex(index));
649  return selection_model_.IsSelected(index);
650}
651
652void TabStripModel::SetSelectionFromModel(
653    const ui::ListSelectionModel& source) {
654  DCHECK_NE(ui::ListSelectionModel::kUnselectedIndex, source.active());
655  SetSelection(source, NOTIFY_DEFAULT);
656}
657
658void TabStripModel::AddWebContents(WebContents* contents,
659                                   int index,
660                                   content::PageTransition transition,
661                                   int add_types) {
662  // If the newly-opened tab is part of the same task as the parent tab, we want
663  // to inherit the parent's "group" attribute, so that if this tab is then
664  // closed we'll jump back to the parent tab.
665  bool inherit_group = (add_types & ADD_INHERIT_GROUP) == ADD_INHERIT_GROUP;
666
667  if (transition == content::PAGE_TRANSITION_LINK &&
668      (add_types & ADD_FORCE_INDEX) == 0) {
669    // We assume tabs opened via link clicks are part of the same task as their
670    // parent.  Note that when |force_index| is true (e.g. when the user
671    // drag-and-drops a link to the tab strip), callers aren't really handling
672    // link clicks, they just want to score the navigation like a link click in
673    // the history backend, so we don't inherit the group in this case.
674    index = order_controller_->DetermineInsertionIndex(transition,
675                                                       add_types & ADD_ACTIVE);
676    inherit_group = true;
677  } else {
678    // For all other types, respect what was passed to us, normalizing -1s and
679    // values that are too large.
680    if (index < 0 || index > count())
681      index = count();
682  }
683
684  if (transition == content::PAGE_TRANSITION_TYPED && index == count()) {
685    // Also, any tab opened at the end of the TabStrip with a "TYPED"
686    // transition inherit group as well. This covers the cases where the user
687    // creates a New Tab (e.g. Ctrl+T, or clicks the New Tab button), or types
688    // in the address bar and presses Alt+Enter. This allows for opening a new
689    // Tab to quickly look up something. When this Tab is closed, the old one
690    // is re-selected, not the next-adjacent.
691    inherit_group = true;
692  }
693  InsertWebContentsAt(index, contents,
694                      add_types | (inherit_group ? ADD_INHERIT_GROUP : 0));
695  // Reset the index, just in case insert ended up moving it on us.
696  index = GetIndexOfWebContents(contents);
697
698  if (inherit_group && transition == content::PAGE_TRANSITION_TYPED)
699    contents_data_[index]->reset_group_on_select = true;
700
701  // TODO(sky): figure out why this is here and not in InsertWebContentsAt. When
702  // here we seem to get failures in startup perf tests.
703  // Ensure that the new WebContentsView begins at the same size as the
704  // previous WebContentsView if it existed.  Otherwise, the initial WebKit
705  // layout will be performed based on a width of 0 pixels, causing a
706  // very long, narrow, inaccurate layout.  Because some scripts on pages (as
707  // well as WebKit's anchor link location calculation) are run on the
708  // initial layout and not recalculated later, we need to ensure the first
709  // layout is performed with sane view dimensions even when we're opening a
710  // new background tab.
711  if (WebContents* old_contents = GetActiveWebContents()) {
712    if ((add_types & ADD_ACTIVE) == 0) {
713      contents->GetView()->SizeContents(
714          old_contents->GetView()->GetContainerSize());
715    }
716  }
717}
718
719void TabStripModel::CloseSelectedTabs() {
720  InternalCloseTabs(selection_model_.selected_indices(),
721                    CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
722}
723
724void TabStripModel::SelectNextTab() {
725  SelectRelativeTab(true);
726}
727
728void TabStripModel::SelectPreviousTab() {
729  SelectRelativeTab(false);
730}
731
732void TabStripModel::SelectLastTab() {
733  ActivateTabAt(count() - 1, true);
734}
735
736void TabStripModel::MoveTabNext() {
737  // TODO: this likely needs to be updated for multi-selection.
738  int new_index = std::min(active_index() + 1, count() - 1);
739  MoveWebContentsAt(active_index(), new_index, true);
740}
741
742void TabStripModel::MoveTabPrevious() {
743  // TODO: this likely needs to be updated for multi-selection.
744  int new_index = std::max(active_index() - 1, 0);
745  MoveWebContentsAt(active_index(), new_index, true);
746}
747
748// Context menu functions.
749bool TabStripModel::IsContextMenuCommandEnabled(
750    int context_index, ContextMenuCommand command_id) const {
751  DCHECK(command_id > CommandFirst && command_id < CommandLast);
752  switch (command_id) {
753    case CommandNewTab:
754    case CommandCloseTab:
755      return true;
756
757    case CommandReload: {
758      std::vector<int> indices = GetIndicesForCommand(context_index);
759      for (size_t i = 0; i < indices.size(); ++i) {
760        WebContents* tab = GetWebContentsAt(indices[i]);
761        if (tab) {
762          CoreTabHelperDelegate* core_delegate =
763              CoreTabHelper::FromWebContents(tab)->delegate();
764          if (!core_delegate || core_delegate->CanReloadContents(tab))
765            return true;
766        }
767      }
768      return false;
769    }
770
771    case CommandCloseOtherTabs:
772    case CommandCloseTabsToRight:
773      return !GetIndicesClosedByCommand(context_index, command_id).empty();
774
775    case CommandDuplicate: {
776      std::vector<int> indices = GetIndicesForCommand(context_index);
777      for (size_t i = 0; i < indices.size(); ++i) {
778        if (delegate_->CanDuplicateContentsAt(indices[i]))
779          return true;
780      }
781      return false;
782    }
783
784    case CommandRestoreTab:
785      return delegate_->GetRestoreTabType() !=
786          TabStripModelDelegate::RESTORE_NONE;
787
788    case CommandTogglePinned: {
789      std::vector<int> indices = GetIndicesForCommand(context_index);
790      for (size_t i = 0; i < indices.size(); ++i) {
791        if (!IsAppTab(indices[i]))
792          return true;
793      }
794      return false;
795    }
796
797    case CommandBookmarkAllTabs:
798      return browser_defaults::bookmarks_enabled &&
799          delegate_->CanBookmarkAllTabs();
800
801    case CommandSelectByDomain:
802    case CommandSelectByOpener:
803      return true;
804
805    default:
806      NOTREACHED();
807  }
808  return false;
809}
810
811void TabStripModel::ExecuteContextMenuCommand(
812    int context_index, ContextMenuCommand command_id) {
813  DCHECK(command_id > CommandFirst && command_id < CommandLast);
814  switch (command_id) {
815    case CommandNewTab:
816      content::RecordAction(UserMetricsAction("TabContextMenu_NewTab"));
817      UMA_HISTOGRAM_ENUMERATION("Tab.NewTab",
818                                TabStripModel::NEW_TAB_CONTEXT_MENU,
819                                TabStripModel::NEW_TAB_ENUM_COUNT);
820      delegate()->AddBlankTabAt(context_index + 1, true);
821      break;
822
823    case CommandReload: {
824      content::RecordAction(UserMetricsAction("TabContextMenu_Reload"));
825      std::vector<int> indices = GetIndicesForCommand(context_index);
826      for (size_t i = 0; i < indices.size(); ++i) {
827        WebContents* tab = GetWebContentsAt(indices[i]);
828        if (tab) {
829          CoreTabHelperDelegate* core_delegate =
830              CoreTabHelper::FromWebContents(tab)->delegate();
831          if (!core_delegate || core_delegate->CanReloadContents(tab))
832            tab->GetController().Reload(true);
833        }
834      }
835      break;
836    }
837
838    case CommandDuplicate: {
839      content::RecordAction(UserMetricsAction("TabContextMenu_Duplicate"));
840      std::vector<int> indices = GetIndicesForCommand(context_index);
841      // Copy the WebContents off as the indices will change as tabs are
842      // duplicated.
843      std::vector<WebContents*> tabs;
844      for (size_t i = 0; i < indices.size(); ++i)
845        tabs.push_back(GetWebContentsAt(indices[i]));
846      for (size_t i = 0; i < tabs.size(); ++i) {
847        int index = GetIndexOfWebContents(tabs[i]);
848        if (index != -1 && delegate_->CanDuplicateContentsAt(index))
849          delegate_->DuplicateContentsAt(index);
850      }
851      break;
852    }
853
854    case CommandCloseTab: {
855      content::RecordAction(UserMetricsAction("TabContextMenu_CloseTab"));
856      InternalCloseTabs(GetIndicesForCommand(context_index),
857                        CLOSE_CREATE_HISTORICAL_TAB | CLOSE_USER_GESTURE);
858      break;
859    }
860
861    case CommandCloseOtherTabs: {
862      content::RecordAction(
863          UserMetricsAction("TabContextMenu_CloseOtherTabs"));
864      InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id),
865                        CLOSE_CREATE_HISTORICAL_TAB);
866      break;
867    }
868
869    case CommandCloseTabsToRight: {
870      content::RecordAction(
871          UserMetricsAction("TabContextMenu_CloseTabsToRight"));
872      InternalCloseTabs(GetIndicesClosedByCommand(context_index, command_id),
873                        CLOSE_CREATE_HISTORICAL_TAB);
874      break;
875    }
876
877    case CommandRestoreTab: {
878      content::RecordAction(UserMetricsAction("TabContextMenu_RestoreTab"));
879      delegate_->RestoreTab();
880      break;
881    }
882
883    case CommandTogglePinned: {
884      content::RecordAction(
885          UserMetricsAction("TabContextMenu_TogglePinned"));
886      std::vector<int> indices = GetIndicesForCommand(context_index);
887      bool pin = WillContextMenuPin(context_index);
888      if (pin) {
889        for (size_t i = 0; i < indices.size(); ++i) {
890          if (!IsAppTab(indices[i]))
891            SetTabPinned(indices[i], true);
892        }
893      } else {
894        // Unpin from the back so that the order is maintained (unpinning can
895        // trigger moving a tab).
896        for (size_t i = indices.size(); i > 0; --i) {
897          if (!IsAppTab(indices[i - 1]))
898            SetTabPinned(indices[i - 1], false);
899        }
900      }
901      break;
902    }
903
904    case CommandBookmarkAllTabs: {
905      content::RecordAction(
906          UserMetricsAction("TabContextMenu_BookmarkAllTabs"));
907
908      delegate_->BookmarkAllTabs();
909      break;
910    }
911
912    case CommandSelectByDomain:
913    case CommandSelectByOpener: {
914      std::vector<int> indices;
915      if (command_id == CommandSelectByDomain)
916        GetIndicesWithSameDomain(context_index, &indices);
917      else
918        GetIndicesWithSameOpener(context_index, &indices);
919      ui::ListSelectionModel selection_model;
920      selection_model.SetSelectedIndex(context_index);
921      for (size_t i = 0; i < indices.size(); ++i)
922        selection_model.AddIndexToSelection(indices[i]);
923      SetSelectionFromModel(selection_model);
924      break;
925    }
926
927    default:
928      NOTREACHED();
929  }
930}
931
932std::vector<int> TabStripModel::GetIndicesClosedByCommand(
933    int index,
934    ContextMenuCommand id) const {
935  DCHECK(ContainsIndex(index));
936  DCHECK(id == CommandCloseTabsToRight || id == CommandCloseOtherTabs);
937  bool is_selected = IsTabSelected(index);
938  int start;
939  if (id == CommandCloseTabsToRight) {
940    if (is_selected) {
941      start = selection_model_.selected_indices()[
942          selection_model_.selected_indices().size() - 1] + 1;
943    } else {
944      start = index + 1;
945    }
946  } else {
947    start = 0;
948  }
949  // NOTE: callers expect the vector to be sorted in descending order.
950  std::vector<int> indices;
951  for (int i = count() - 1; i >= start; --i) {
952    if (i != index && !IsMiniTab(i) && (!is_selected || !IsTabSelected(i)))
953      indices.push_back(i);
954  }
955  return indices;
956}
957
958bool TabStripModel::WillContextMenuPin(int index) {
959  std::vector<int> indices = GetIndicesForCommand(index);
960  // If all tabs are pinned, then we unpin, otherwise we pin.
961  bool all_pinned = true;
962  for (size_t i = 0; i < indices.size() && all_pinned; ++i) {
963    if (!IsAppTab(index))  // We never change app tabs.
964      all_pinned = IsTabPinned(indices[i]);
965  }
966  return !all_pinned;
967}
968
969///////////////////////////////////////////////////////////////////////////////
970// TabStripModel, content::NotificationObserver implementation:
971
972void TabStripModel::Observe(int type,
973                            const content::NotificationSource& source,
974                            const content::NotificationDetails& details) {
975  switch (type) {
976    case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
977      // Sometimes, on qemu, it seems like a WebContents object can be destroyed
978      // while we still have a reference to it. We need to break this reference
979      // here so we don't crash later.
980      int index = GetIndexOfWebContents(
981          content::Source<WebContents>(source).ptr());
982      if (index != TabStripModel::kNoTab) {
983        // Note that we only detach the contents here, not close it - it's
984        // already been closed. We just want to undo our bookkeeping.
985        DetachWebContentsAt(index);
986      }
987      break;
988    }
989
990    default:
991      NOTREACHED();
992  }
993}
994
995// static
996bool TabStripModel::ContextMenuCommandToBrowserCommand(int cmd_id,
997                                                       int* browser_cmd) {
998  switch (cmd_id) {
999    case CommandNewTab:
1000      *browser_cmd = IDC_NEW_TAB;
1001      break;
1002    case CommandReload:
1003      *browser_cmd = IDC_RELOAD;
1004      break;
1005    case CommandDuplicate:
1006      *browser_cmd = IDC_DUPLICATE_TAB;
1007      break;
1008    case CommandCloseTab:
1009      *browser_cmd = IDC_CLOSE_TAB;
1010      break;
1011    case CommandRestoreTab:
1012      *browser_cmd = IDC_RESTORE_TAB;
1013      break;
1014    case CommandBookmarkAllTabs:
1015      *browser_cmd = IDC_BOOKMARK_ALL_TABS;
1016      break;
1017    default:
1018      *browser_cmd = 0;
1019      return false;
1020  }
1021
1022  return true;
1023}
1024
1025///////////////////////////////////////////////////////////////////////////////
1026// TabStripModel, private:
1027
1028std::vector<WebContents*> TabStripModel::GetWebContentsFromIndices(
1029    const std::vector<int>& indices) const {
1030  std::vector<WebContents*> contents;
1031  for (size_t i = 0; i < indices.size(); ++i)
1032    contents.push_back(GetWebContentsAtImpl(indices[i]));
1033  return contents;
1034}
1035
1036void TabStripModel::GetIndicesWithSameDomain(int index,
1037                                             std::vector<int>* indices) {
1038  std::string domain = GetWebContentsAt(index)->GetURL().host();
1039  if (domain.empty())
1040    return;
1041  for (int i = 0; i < count(); ++i) {
1042    if (i == index)
1043      continue;
1044    if (GetWebContentsAt(i)->GetURL().host() == domain)
1045      indices->push_back(i);
1046  }
1047}
1048
1049void TabStripModel::GetIndicesWithSameOpener(int index,
1050                                             std::vector<int>* indices) {
1051  WebContents* opener = contents_data_[index]->group;
1052  if (!opener) {
1053    // If there is no group, find all tabs with the selected tab as the opener.
1054    opener = GetWebContentsAt(index);
1055    if (!opener)
1056      return;
1057  }
1058  for (int i = 0; i < count(); ++i) {
1059    if (i == index)
1060      continue;
1061    if (contents_data_[i]->group == opener ||
1062        GetWebContentsAtImpl(i) == opener) {
1063      indices->push_back(i);
1064    }
1065  }
1066}
1067
1068std::vector<int> TabStripModel::GetIndicesForCommand(int index) const {
1069  if (!IsTabSelected(index)) {
1070    std::vector<int> indices;
1071    indices.push_back(index);
1072    return indices;
1073  }
1074  return selection_model_.selected_indices();
1075}
1076
1077bool TabStripModel::IsNewTabAtEndOfTabStrip(WebContents* contents) const {
1078  const GURL& url = contents->GetURL();
1079  return url.SchemeIs(chrome::kChromeUIScheme) &&
1080         url.host() == chrome::kChromeUINewTabHost &&
1081         contents == GetWebContentsAtImpl(count() - 1) &&
1082         contents->GetController().GetEntryCount() == 1;
1083}
1084
1085bool TabStripModel::InternalCloseTabs(const std::vector<int>& indices,
1086                                      uint32 close_types) {
1087  if (indices.empty())
1088    return true;
1089
1090  CloseTracker close_tracker(GetWebContentsFromIndices(indices));
1091
1092  // We only try the fast shutdown path if the whole browser process is *not*
1093  // shutting down. Fast shutdown during browser termination is handled in
1094  // BrowserShutdown.
1095  if (browser_shutdown::GetShutdownType() == browser_shutdown::NOT_VALID) {
1096    // Construct a map of processes to the number of associated tabs that are
1097    // closing.
1098    std::map<content::RenderProcessHost*, size_t> processes;
1099    for (size_t i = 0; i < indices.size(); ++i) {
1100      WebContents* closing_contents = GetWebContentsAtImpl(indices[i]);
1101      content::RenderProcessHost* process =
1102          closing_contents->GetRenderProcessHost();
1103      ++processes[process];
1104    }
1105
1106    // Try to fast shutdown the tabs that can close.
1107    for (std::map<content::RenderProcessHost*, size_t>::iterator iter =
1108         processes.begin(); iter != processes.end(); ++iter) {
1109      iter->first->FastShutdownForPageCount(iter->second);
1110    }
1111  }
1112
1113  // We now return to our regularly scheduled shutdown procedure.
1114  bool retval = true;
1115  while (close_tracker.HasNext()) {
1116    WebContents* closing_contents = close_tracker.Next();
1117    int index = GetIndexOfWebContents(closing_contents);
1118    // Make sure we still contain the tab.
1119    if (index == kNoTab)
1120      continue;
1121
1122    CoreTabHelper* core_tab_helper =
1123        CoreTabHelper::FromWebContents(closing_contents);
1124    core_tab_helper->OnCloseStarted();
1125
1126    // Update the explicitly closed state. If the unload handlers cancel the
1127    // close the state is reset in Browser. We don't update the explicitly
1128    // closed state if already marked as explicitly closed as unload handlers
1129    // call back to this if the close is allowed.
1130    if (!closing_contents->GetClosedByUserGesture()) {
1131      closing_contents->SetClosedByUserGesture(
1132          close_types & CLOSE_USER_GESTURE);
1133    }
1134
1135    if (delegate_->RunUnloadListenerBeforeClosing(closing_contents)) {
1136      retval = false;
1137      continue;
1138    }
1139
1140    InternalCloseTab(closing_contents, index,
1141                     (close_types & CLOSE_CREATE_HISTORICAL_TAB) != 0);
1142  }
1143
1144  return retval;
1145}
1146
1147void TabStripModel::InternalCloseTab(WebContents* contents,
1148                                     int index,
1149                                     bool create_historical_tabs) {
1150  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1151                    TabClosingAt(this, contents, index));
1152
1153  // Ask the delegate to save an entry for this tab in the historical tab
1154  // database if applicable.
1155  if (create_historical_tabs)
1156    delegate_->CreateHistoricalTab(contents);
1157
1158  // Deleting the WebContents will call back to us via
1159  // NotificationObserver and detach it.
1160  delete contents;
1161}
1162
1163WebContents* TabStripModel::GetWebContentsAtImpl(int index) const {
1164  CHECK(ContainsIndex(index)) <<
1165      "Failed to find: " << index << " in: " << count() << " entries.";
1166  return contents_data_[index]->contents;
1167}
1168
1169void TabStripModel::NotifyIfTabDeactivated(WebContents* contents) {
1170  if (contents) {
1171    FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1172                      TabDeactivated(contents));
1173  }
1174}
1175
1176void TabStripModel::NotifyIfActiveTabChanged(WebContents* old_contents,
1177                                             NotifyTypes notify_types) {
1178  WebContents* new_contents = GetWebContentsAtImpl(active_index());
1179  if (old_contents != new_contents) {
1180    int reason = notify_types == NOTIFY_USER_GESTURE
1181                 ? TabStripModelObserver::CHANGE_REASON_USER_GESTURE
1182                 : TabStripModelObserver::CHANGE_REASON_NONE;
1183    CHECK(!in_notify_);
1184    in_notify_ = true;
1185    FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1186        ActiveTabChanged(old_contents,
1187                         new_contents,
1188                         active_index(),
1189                         reason));
1190    in_notify_ = false;
1191    // Activating a discarded tab reloads it, so it is no longer discarded.
1192    contents_data_[active_index()]->discarded = false;
1193  }
1194}
1195
1196void TabStripModel::NotifyIfActiveOrSelectionChanged(
1197    WebContents* old_contents,
1198    NotifyTypes notify_types,
1199    const ui::ListSelectionModel& old_model) {
1200  NotifyIfActiveTabChanged(old_contents, notify_types);
1201
1202  if (!selection_model().Equals(old_model)) {
1203    FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1204                      TabSelectionChanged(this, old_model));
1205  }
1206}
1207
1208void TabStripModel::SetSelection(
1209    const ui::ListSelectionModel& new_model,
1210    NotifyTypes notify_types) {
1211  WebContents* old_contents = GetActiveWebContents();
1212  ui::ListSelectionModel old_model;
1213  old_model.Copy(selection_model_);
1214  if (new_model.active() != selection_model_.active())
1215    NotifyIfTabDeactivated(old_contents);
1216  selection_model_.Copy(new_model);
1217  NotifyIfActiveOrSelectionChanged(old_contents, notify_types, old_model);
1218}
1219
1220void TabStripModel::SelectRelativeTab(bool next) {
1221  // This may happen during automated testing or if a user somehow buffers
1222  // many key accelerators.
1223  if (contents_data_.empty())
1224    return;
1225
1226  int index = active_index();
1227  int delta = next ? 1 : -1;
1228  index = (index + count() + delta) % count();
1229  ActivateTabAt(index, true);
1230}
1231
1232void TabStripModel::MoveWebContentsAtImpl(int index,
1233                                          int to_position,
1234                                          bool select_after_move) {
1235  WebContentsData* moved_data = contents_data_[index];
1236  contents_data_.erase(contents_data_.begin() + index);
1237  contents_data_.insert(contents_data_.begin() + to_position, moved_data);
1238
1239  selection_model_.Move(index, to_position);
1240  if (!selection_model_.IsSelected(select_after_move) && select_after_move) {
1241    // TODO(sky): why doesn't this code notify observers?
1242    selection_model_.SetSelectedIndex(to_position);
1243  }
1244
1245  FOR_EACH_OBSERVER(TabStripModelObserver, observers_,
1246                    TabMoved(moved_data->contents, index, to_position));
1247}
1248
1249void TabStripModel::MoveSelectedTabsToImpl(int index,
1250                                           size_t start,
1251                                           size_t length) {
1252  DCHECK(start < selection_model_.selected_indices().size() &&
1253         start + length <= selection_model_.selected_indices().size());
1254  size_t end = start + length;
1255  int count_before_index = 0;
1256  for (size_t i = start; i < end &&
1257       selection_model_.selected_indices()[i] < index + count_before_index;
1258       ++i) {
1259    count_before_index++;
1260  }
1261
1262  // First move those before index. Any tabs before index end up moving in the
1263  // selection model so we use start each time through.
1264  int target_index = index + count_before_index;
1265  size_t tab_index = start;
1266  while (tab_index < end &&
1267         selection_model_.selected_indices()[start] < index) {
1268    MoveWebContentsAt(selection_model_.selected_indices()[start],
1269                      target_index - 1, false);
1270    tab_index++;
1271  }
1272
1273  // Then move those after the index. These don't result in reordering the
1274  // selection.
1275  while (tab_index < end) {
1276    if (selection_model_.selected_indices()[tab_index] != target_index) {
1277      MoveWebContentsAt(selection_model_.selected_indices()[tab_index],
1278                        target_index, false);
1279    }
1280    tab_index++;
1281    target_index++;
1282  }
1283}
1284
1285// static
1286bool TabStripModel::OpenerMatches(const WebContentsData* data,
1287                                  const WebContents* opener,
1288                                  bool use_group) {
1289  return data->opener == opener || (use_group && data->group == opener);
1290}
1291
1292void TabStripModel::ForgetOpenersAndGroupsReferencing(
1293    const WebContents* tab) {
1294  for (WebContentsDataVector::const_iterator i = contents_data_.begin();
1295       i != contents_data_.end(); ++i) {
1296    if ((*i)->group == tab)
1297      (*i)->group = NULL;
1298    if ((*i)->opener == tab)
1299      (*i)->opener = NULL;
1300  }
1301}
1302