Commit f0a943fa authored by edchin's avatar edchin Committed by Commit Bot

[ios] Enable undo close all tabs

This CL enables undo close all tabs in the tab grid by serializing
the current window. The undo operation deserializes the saved closed
window.

Current known issue is that snapshots are erased when WebStates are
closed. Another CL will deal with keeping snapshots around long enough
for undo operations.

Bug: 804567
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Change-Id: I12cf8c11d1e067a7de1b9a6ce44d207810619eb8
Reviewed-on: https://chromium-review.googlesource.com/1036257Reviewed-by: default avatarEugene But <eugenebut@chromium.org>
Reviewed-by: default avataredchin <edchin@chromium.org>
Commit-Queue: edchin <edchin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#555520}
parent 97e89f68
......@@ -22,8 +22,17 @@
// Tells the receiver to close the item with identifier |itemID|. If there is
// no item with that identifier, no item is closed.
- (void)closeItemWithID:(NSString*)itemID;
// Tells the receiver to close all items.
// Tells the receiver to close all items. This operation does not save items.
- (void)closeAllItems;
// Tells the receiver to save all items for an undo operation, then close all
// items.
- (void)saveAndCloseAllItems;
// Tells the receiver to restore saved closed items, and then discard the saved
// items. If there are no saved closed items, this is a no-op.
- (void)undoCloseAllItems;
// Tells the receiver to discard saved closed items. If the consumer has saved
// closed items, it will discard them. Otherwise, this is a no-op.
- (void)discardSavedClosedItems;
@end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_GRID_COMMANDS_H_
......@@ -176,24 +176,8 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
cell.accessibilityIdentifier =
[NSString stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix,
base::checked_cast<long>(indexPath.item)];
cell.delegate = self;
cell.theme = self.theme;
GridItem* item = self.items[indexPath.item];
cell.itemIdentifier = item.identifier;
cell.title = item.title;
NSString* itemIdentifier = item.identifier;
[self.imageDataSource
faviconForIdentifier:itemIdentifier
completion:^(UIImage* icon) {
if (icon && cell.itemIdentifier == itemIdentifier)
cell.icon = icon;
}];
[self.imageDataSource
snapshotForIdentifier:itemIdentifier
completion:^(UIImage* snapshot) {
if (snapshot && cell.itemIdentifier == itemIdentifier)
cell.snapshot = snapshot;
}];
[self configureCell:cell withItem:item];
return cell;
}
......@@ -346,7 +330,9 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
self.items[index] = item;
if (![self isViewVisible])
return;
[self.collectionView reloadItemsAtIndexPaths:@[ CreateIndexPath(index) ]];
GridCell* cell = base::mac::ObjCCastStrict<GridCell>(
[self.collectionView cellForItemAtIndexPath:CreateIndexPath(index)]);
[self configureCell:cell withItem:item];
}
- (void)moveItemWithID:(NSString*)itemID toIndex:(NSUInteger)toIndex {
......@@ -403,6 +389,34 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
return self.viewIfLoaded.window != nil;
}
// Configures |cell|'s title synchronously, and favicon and snapshot
// asynchronously with information from |item|. Updates the |cell|'s theme to
// this view controller's theme. This view controller becomes the delegate for
// the cell.
- (void)configureCell:(GridCell*)cell withItem:(GridItem*)item {
DCHECK(cell);
DCHECK(item);
cell.delegate = self;
cell.theme = self.theme;
cell.itemIdentifier = item.identifier;
cell.title = item.title;
NSString* itemIdentifier = item.identifier;
[self.imageDataSource faviconForIdentifier:itemIdentifier
completion:^(UIImage* icon) {
// Only update the icon if the cell is not
// already reused for another item.
if (cell.itemIdentifier == itemIdentifier)
cell.icon = icon;
}];
[self.imageDataSource snapshotForIdentifier:itemIdentifier
completion:^(UIImage* snapshot) {
// Only update the icon if the cell is not
// already reused for another item.
if (cell.itemIdentifier == itemIdentifier)
cell.snapshot = snapshot;
}];
}
// Animates the empty state into view.
- (void)animateEmptyStateIntoView {
// TODO(crbug.com/820410) : Polish the animation, and put constants where they
......
......@@ -18,6 +18,7 @@
#import "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/browser/web_state_list/web_state_list.h"
#import "ios/chrome/browser/web_state_list/web_state_list_observer_bridge.h"
#import "ios/chrome/browser/web_state_list/web_state_list_serialization.h"
#include "ios/chrome/browser/web_state_list/web_state_opener.h"
#include "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h"
......@@ -71,6 +72,8 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
@property(nonatomic, assign) WebStateList* webStateList;
// The UI consumer to which updates are made.
@property(nonatomic, weak) id<GridConsumer> consumer;
// The saved session window just before close all tabs is called.
@property(nonatomic, strong) SessionWindowIOS* closedSessionWindow;
@end
@implementation TabGridMediator {
......@@ -89,6 +92,7 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
// Private properties.
@synthesize webStateList = _webStateList;
@synthesize consumer = _consumer;
@synthesize closedSessionWindow = _closedSessionWindow;
- (instancetype)initWithConsumer:(id<GridConsumer>)consumer {
if (self = [super init]) {
......@@ -228,9 +232,32 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
}
- (void)closeAllItems {
// This is a no-op if |webStateList| is already empty.
self.webStateList->CloseAllWebStates(WebStateList::CLOSE_USER_ACTION);
}
- (void)saveAndCloseAllItems {
if (self.webStateList->empty())
return;
self.closedSessionWindow = SerializeWebStateList(self.webStateList);
self.webStateList->CloseAllWebStates(WebStateList::CLOSE_USER_ACTION);
}
- (void)undoCloseAllItems {
if (!self.closedSessionWindow)
return;
web::WebState::CreateParams createParams(self.tabModel.browserState);
DeserializeWebStateList(
self.webStateList, self.closedSessionWindow,
base::BindRepeating(&web::WebState::CreateWithStorageSession,
createParams));
self.closedSessionWindow = nil;
}
- (void)discardSavedClosedItems {
self.closedSessionWindow = nil;
}
#pragma mark - GridImageDataSource
- (void)snapshotForIdentifier:(NSString*)identifier
......
......@@ -243,13 +243,49 @@ TEST_F(TabGridMediatorTest, CloseItemCommand) {
EXPECT_EQ(2UL, consumer_.items.count);
}
// Tests that the |web_state_list_| is empty when |-closeAllItems| is called.
// Tests that the consumer's item list is also empty.
// Tests that the |web_state_list_| and consumer's list are empty when
// |-closeAllItems| is called. Tests that |-undoCloseAllItems| does not restore
// the |web_state_list_|.
TEST_F(TabGridMediatorTest, CloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ closeAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
[mediator_ undoCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
}
// Tests that the |web_state_list_| and consumer's list are empty when
// |-saveAndCloseAllItems| is called.
TEST_F(TabGridMediatorTest, SaveAndCloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
}
// Tests that the |web_state_list_| is not restored to 3 items when
// |-undoCloseAllItems| is called after |-discardSavedClosedItems| is called.
TEST_F(TabGridMediatorTest, DiscardSavedClosedItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
[mediator_ discardSavedClosedItems];
[mediator_ undoCloseAllItems];
EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.count);
}
// Tests that the |web_state_list_| is restored to 3 items when
// |-undoCloseAllItems| is called.
TEST_F(TabGridMediatorTest, UndoCloseAllItemsCommand) {
// Previously there were 3 items.
[mediator_ saveAndCloseAllItems];
[mediator_ undoCloseAllItems];
EXPECT_EQ(3, web_state_list_->count());
EXPECT_EQ(3UL, consumer_.items.count);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[0]]);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[1]]);
EXPECT_TRUE([original_identifiers_ containsObject:consumer_.items[2]]);
}
// Tests that when |-addNewItem| is called, the |web_state_list_| count is
......
......@@ -51,18 +51,6 @@ NSUInteger GetPageIndexFromPage(TabGridPage page) {
}
return static_cast<NSUInteger>(page);
}
// Temporary alert used while building this feature.
UIAlertController* NotImplementedAlert() {
UIAlertController* alert =
[UIAlertController alertControllerWithTitle:@"Not implemented"
message:nil
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"OK"
style:UIAlertActionStyleCancel
handler:nil]];
return alert;
}
} // namespace
@interface TabGridViewController ()<GridViewControllerDelegate,
......@@ -153,6 +141,7 @@ UIAlertController* NotImplementedAlert() {
- (void)viewWillDisappear:(BOOL)animated {
self.undoCloseAllAvailable = NO;
[self.regularTabsDelegate discardSavedClosedItems];
if (animated && self.transitionCoordinator) {
[self animateToolbarsForDisappearance];
}
......@@ -783,13 +772,10 @@ UIAlertController* NotImplementedAlert() {
DCHECK_EQ(self.undoCloseAllAvailable,
self.regularTabsViewController.gridEmpty);
if (self.undoCloseAllAvailable) {
// TODO(crbug.com/804567) : Implement Undo Close All.
[self presentViewController:NotImplementedAlert()
animated:YES
completion:nil];
[self.regularTabsDelegate undoCloseAllItems];
} else {
[self.incognitoTabsDelegate closeAllItems];
[self.regularTabsDelegate closeAllItems];
[self.regularTabsDelegate saveAndCloseAllItems];
}
self.undoCloseAllAvailable = !self.undoCloseAllAvailable;
[self configureCloseAllButtonForCurrentPageAndUndoAvailability];
......
......@@ -23,33 +23,17 @@
namespace {
// Serializable sub-class of TestWebState.
class SerializableTestWebState : public web::TestWebState {
public:
static std::unique_ptr<web::WebState> Create() {
return std::make_unique<SerializableTestWebState>();
}
std::unique_ptr<web::WebState> CreateWebState() {
return std::make_unique<web::TestWebState>();
}
static std::unique_ptr<web::WebState> CreateWithSessionStorage(
std::unique_ptr<web::WebState> CreateWebStateWithSessionStorage(
CRWSessionStorage* session_storage) {
std::unique_ptr<web::WebState> web_state = Create();
std::unique_ptr<web::WebState> web_state = CreateWebState();
web::SerializableUserDataManager::FromWebState(web_state.get())
->AddSerializableUserData(session_storage.userData);
return web_state;
}
private:
// web::TestWebState implementation.
CRWSessionStorage* BuildSessionStorage() override {
std::unique_ptr<web::SerializableUserData> serializable_user_data =
web::SerializableUserDataManager::FromWebState(this)
->CreateSerializableUserData();
CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
[session_storage setSerializableUserData:std::move(serializable_user_data)];
return session_storage;
}
};
}
// Compares whether both WebStateList |original| and |restored| have the same
// opener-opened relationship. The |restored| WebStateList may have additional
......@@ -105,18 +89,17 @@ TEST_F(WebStateListSerializationTest, SerializationEmpty) {
TEST_F(WebStateListSerializationTest, SerializationRoundTrip) {
WebStateList original_web_state_list(web_state_list_delegate());
original_web_state_list.InsertWebState(0, SerializableTestWebState::Create(),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
original_web_state_list.InsertWebState(
1, SerializableTestWebState::Create(),
0, CreateWebState(), WebStateList::INSERT_FORCE_INDEX, WebStateOpener());
original_web_state_list.InsertWebState(
1, CreateWebState(),
WebStateList::INSERT_FORCE_INDEX | WebStateList::INSERT_ACTIVATE,
WebStateOpener(original_web_state_list.GetWebStateAt(0), 3));
original_web_state_list.InsertWebState(
2, SerializableTestWebState::Create(), WebStateList::INSERT_FORCE_INDEX,
2, CreateWebState(), WebStateList::INSERT_FORCE_INDEX,
WebStateOpener(original_web_state_list.GetWebStateAt(0), 2));
original_web_state_list.InsertWebState(
3, SerializableTestWebState::Create(), WebStateList::INSERT_FORCE_INDEX,
3, CreateWebState(), WebStateList::INSERT_FORCE_INDEX,
WebStateOpener(original_web_state_list.GetWebStateAt(1), 1));
SessionWindowIOS* session_window =
......@@ -127,14 +110,13 @@ TEST_F(WebStateListSerializationTest, SerializationRoundTrip) {
// Create a deserialized WebStateList and verify its contents.
WebStateList restored_web_state_list(web_state_list_delegate());
restored_web_state_list.InsertWebState(0, SerializableTestWebState::Create(),
WebStateList::INSERT_FORCE_INDEX,
WebStateOpener());
restored_web_state_list.InsertWebState(
0, CreateWebState(), WebStateList::INSERT_FORCE_INDEX, WebStateOpener());
ASSERT_EQ(1, restored_web_state_list.count());
DeserializeWebStateList(
&restored_web_state_list, session_window,
base::BindRepeating(&SerializableTestWebState::CreateWithSessionStorage));
base::BindRepeating(&CreateWebStateWithSessionStorage));
EXPECT_EQ(5, restored_web_state_list.count());
EXPECT_EQ(2, restored_web_state_list.active_index());
......
......@@ -10,6 +10,9 @@
#include "base/bind.h"
#include "base/callback.h"
#include "base/threading/sequenced_task_runner_handle.h"
#import "ios/web/public/crw_navigation_item_storage.h"
#import "ios/web/public/crw_session_storage.h"
#import "ios/web/public/serializable_user_data_manager.h"
#import "ios/web/public/web_state/ui/crw_content_view.h"
#import "ios/web/public/web_state/web_state_policy_decider.h"
#include "ui/gfx/image/image.h"
......@@ -110,7 +113,13 @@ TestWebState::GetSessionCertificatePolicyCache() {
}
CRWSessionStorage* TestWebState::BuildSessionStorage() {
return nil;
std::unique_ptr<web::SerializableUserData> serializable_user_data =
web::SerializableUserDataManager::FromWebState(this)
->CreateSerializableUserData();
CRWSessionStorage* session_storage = [[CRWSessionStorage alloc] init];
[session_storage setSerializableUserData:std::move(serializable_user_data)];
session_storage.itemStorages = @[ [[CRWNavigationItemStorage alloc] init] ];
return session_storage;
}
void TestWebState::SetNavigationManager(
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment