blob: 1ae68ba034f2b5addd39ed4475dc6f345c51b289 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import "ios/shared/chrome/browser/tabs/web_state_list.h"
#include <algorithm>
#include <utility>
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#import "ios/shared/chrome/browser/tabs/web_state_list_delegate.h"
#import "ios/shared/chrome/browser/tabs/web_state_list_observer.h"
#import "ios/shared/chrome/browser/tabs/web_state_list_order_controller.h"
#import "ios/shared/chrome/browser/tabs/web_state_opener.h"
#import "ios/web/public/navigation_manager.h"
#import "ios/web/public/web_state/web_state.h"
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
// Wrapper around a WebState stored in a WebStateList.
class WebStateList::WebStateWrapper {
public:
explicit WebStateWrapper(web::WebState* web_state);
~WebStateWrapper();
web::WebState* web_state() const { return web_state_; }
// Replaces the wrapped WebState (and clear associated state) and returns the
// old WebState after forfeiting ownership.
web::WebState* ReplaceWebState(web::WebState* web_state);
// Gets and sets information about this WebState opener. The navigation index
// is used to detect navigation changes during the same session.
WebStateOpener opener() const { return opener_; }
void set_opener(WebStateOpener opener) { opener_ = opener; }
// Returns whether |opener| spawned the wrapped WebState. If |use_group| is
// true, also use the opener navigation index to detect navigation changes
// during the same session.
bool WasOpenedBy(const web::WebState* opener,
int opener_navigation_index,
bool use_group) const;
private:
web::WebState* web_state_;
WebStateOpener opener_;
DISALLOW_COPY_AND_ASSIGN(WebStateWrapper);
};
WebStateList::WebStateWrapper::WebStateWrapper(web::WebState* web_state)
: web_state_(web_state), opener_(nullptr) {
DCHECK(web_state_);
}
WebStateList::WebStateWrapper::~WebStateWrapper() = default;
web::WebState* WebStateList::WebStateWrapper::ReplaceWebState(
web::WebState* web_state) {
DCHECK(web_state);
DCHECK_NE(web_state, web_state_);
std::swap(web_state, web_state_);
opener_ = WebStateOpener(nullptr);
return web_state;
}
bool WebStateList::WebStateWrapper::WasOpenedBy(const web::WebState* opener,
int opener_navigation_index,
bool use_group) const {
DCHECK(opener);
if (opener_.opener != opener)
return false;
if (!use_group)
return true;
return opener_.navigation_index == opener_navigation_index;
}
WebStateList::WebStateList(WebStateListDelegate* delegate,
WebStateOwnership ownership)
: delegate_(delegate),
web_state_ownership_(ownership),
order_controller_(base::MakeUnique<WebStateListOrderController>(this)) {
DCHECK(delegate_);
}
WebStateList::~WebStateList() {
// Once WebStateList owns the WebState and has a CloseWebStateAt() method,
// then change this to close all the WebState. See http://crbug.com/546222
// for progress.
if (web_state_ownership_ == WebStateOwned) {
for (auto& web_state_wrapper : web_state_wrappers_) {
web::WebState* web_state = web_state_wrapper->web_state();
delete web_state;
}
}
}
bool WebStateList::ContainsIndex(int index) const {
return 0 <= index && index < count();
}
web::WebState* WebStateList::GetActiveWebState() const {
if (active_index_ != kInvalidIndex)
return GetWebStateAt(active_index_);
return nullptr;
}
web::WebState* WebStateList::GetWebStateAt(int index) const {
DCHECK(ContainsIndex(index));
return web_state_wrappers_[index]->web_state();
}
int WebStateList::GetIndexOfWebState(const web::WebState* web_state) const {
for (int index = 0; index < count(); ++index) {
if (web_state_wrappers_[index]->web_state() == web_state)
return index;
}
return kInvalidIndex;
}
WebStateOpener WebStateList::GetOpenerOfWebStateAt(int index) const {
DCHECK(ContainsIndex(index));
return web_state_wrappers_[index]->opener();
}
void WebStateList::SetOpenerOfWebStateAt(int index, WebStateOpener opener) {
DCHECK(ContainsIndex(index));
DCHECK(ContainsIndex(GetIndexOfWebState(opener.opener)));
web_state_wrappers_[index]->set_opener(opener);
}
int WebStateList::GetIndexOfNextWebStateOpenedBy(const web::WebState* opener,
int start_index,
bool use_group) const {
return GetIndexOfNthWebStateOpenedBy(opener, start_index, use_group, 1);
}
int WebStateList::GetIndexOfLastWebStateOpenedBy(const web::WebState* opener,
int start_index,
bool use_group) const {
return GetIndexOfNthWebStateOpenedBy(opener, start_index, use_group, INT_MAX);
}
void WebStateList::InsertWebState(int index, web::WebState* web_state) {
DCHECK(ContainsIndex(index) || index == count());
delegate_->WillAddWebState(web_state);
web_state_wrappers_.insert(web_state_wrappers_.begin() + index,
base::MakeUnique<WebStateWrapper>(web_state));
if (active_index_ >= index)
++active_index_;
for (auto& observer : observers_)
observer.WebStateInsertedAt(this, web_state, index);
}
void WebStateList::AppendWebState(ui::PageTransition transition,
web::WebState* web_state,
WebStateOpener opener) {
int index =
order_controller_->DetermineInsertionIndex(transition, opener.opener);
if (index < 0 || count() < index)
index = count();
InsertWebState(index, web_state);
if (opener.opener)
SetOpenerOfWebStateAt(index, opener);
}
void WebStateList::MoveWebStateAt(int from_index, int to_index) {
DCHECK(ContainsIndex(from_index));
DCHECK(ContainsIndex(to_index));
if (from_index == to_index)
return;
std::unique_ptr<WebStateWrapper> web_state_wrapper =
std::move(web_state_wrappers_[from_index]);
web::WebState* web_state = web_state_wrapper->web_state();
web_state_wrappers_.erase(web_state_wrappers_.begin() + from_index);
web_state_wrappers_.insert(web_state_wrappers_.begin() + to_index,
std::move(web_state_wrapper));
if (active_index_ == from_index) {
active_index_ = to_index;
} else {
int min = std::min(from_index, to_index);
int max = std::max(from_index, to_index);
int delta = from_index < to_index ? -1 : +1;
if (min <= active_index_ && active_index_ <= max)
active_index_ += delta;
}
for (auto& observer : observers_)
observer.WebStateMoved(this, web_state, from_index, to_index);
}
web::WebState* WebStateList::ReplaceWebStateAt(int index,
web::WebState* web_state) {
DCHECK(ContainsIndex(index));
delegate_->WillAddWebState(web_state);
ClearOpenersReferencing(index);
auto& web_state_wrapper = web_state_wrappers_[index];
web::WebState* old_web_state = web_state_wrapper->ReplaceWebState(web_state);
for (auto& observer : observers_)
observer.WebStateReplacedAt(this, old_web_state, web_state, index);
delegate_->WebStateDetached(old_web_state);
return old_web_state;
}
web::WebState* WebStateList::DetachWebStateAt(int index) {
DCHECK(ContainsIndex(index));
int new_active_index = order_controller_->DetermineNewActiveIndex(index);
web::WebState* old_web_state = web_state_wrappers_[index]->web_state();
for (auto& observer : observers_)
observer.WillDetachWebStateAt(this, old_web_state, index);
ClearOpenersReferencing(index);
web_state_wrappers_.erase(web_state_wrappers_.begin() + index);
// Update the active index to prevent observer from seeing an invalid WebState
// as the active one but only send the WebStateActivatedAt notification after
// the WebStateDetachedAt one.
bool active_web_state_was_closed = (index == active_index_);
if (active_index_ > index)
--active_index_;
else if (active_index_ == index)
active_index_ = new_active_index;
for (auto& observer : observers_)
observer.WebStateDetachedAt(this, old_web_state, index);
if (active_web_state_was_closed)
NotifyIfActiveWebStateChanged(old_web_state, false);
delegate_->WebStateDetached(old_web_state);
return old_web_state;
}
void WebStateList::ActivateWebStateAt(int index) {
DCHECK(ContainsIndex(index));
web::WebState* old_web_state = GetActiveWebState();
active_index_ = index;
NotifyIfActiveWebStateChanged(old_web_state, true);
}
void WebStateList::AddObserver(WebStateListObserver* observer) {
observers_.AddObserver(observer);
}
void WebStateList::RemoveObserver(WebStateListObserver* observer) {
observers_.RemoveObserver(observer);
}
void WebStateList::ClearOpenersReferencing(int index) {
web::WebState* old_web_state = web_state_wrappers_[index]->web_state();
for (auto& web_state_wrapper : web_state_wrappers_) {
if (web_state_wrapper->opener().opener == old_web_state)
web_state_wrapper->set_opener(WebStateOpener(nullptr));
}
}
void WebStateList::NotifyIfActiveWebStateChanged(web::WebState* old_web_state,
bool user_action) {
web::WebState* new_web_state = GetActiveWebState();
if (old_web_state == new_web_state)
return;
for (auto& observer : observers_) {
observer.WebStateActivatedAt(this, old_web_state, new_web_state,
active_index_, user_action);
}
}
int WebStateList::GetIndexOfNthWebStateOpenedBy(const web::WebState* opener,
int start_index,
bool use_group,
int n) const {
DCHECK_GT(n, 0);
if (!opener || !ContainsIndex(start_index) || start_index == INT_MAX)
return kInvalidIndex;
const int opener_navigation_index =
use_group ? opener->GetNavigationManager()->GetLastCommittedItemIndex()
: -1;
int found_index = kInvalidIndex;
for (int index = start_index + 1; index < count() && n; ++index) {
if (web_state_wrappers_[index]->WasOpenedBy(opener, opener_navigation_index,
use_group)) {
found_index = index;
--n;
} else if (found_index != kInvalidIndex) {
return found_index;
}
}
return found_index;
}
// static
const int WebStateList::kInvalidIndex;