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 @@ ...@@ -22,8 +22,17 @@
// Tells the receiver to close the item with identifier |itemID|. If there is // Tells the receiver to close the item with identifier |itemID|. If there is
// no item with that identifier, no item is closed. // no item with that identifier, no item is closed.
- (void)closeItemWithID:(NSString*)itemID; - (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; - (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 @end
#endif // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_GRID_COMMANDS_H_ #endif // IOS_CHROME_BROWSER_UI_TAB_GRID_GRID_GRID_COMMANDS_H_
...@@ -176,24 +176,8 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -176,24 +176,8 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
cell.accessibilityIdentifier = cell.accessibilityIdentifier =
[NSString stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix, [NSString stringWithFormat:@"%@%ld", kGridCellIdentifierPrefix,
base::checked_cast<long>(indexPath.item)]; base::checked_cast<long>(indexPath.item)];
cell.delegate = self;
cell.theme = self.theme;
GridItem* item = self.items[indexPath.item]; GridItem* item = self.items[indexPath.item];
cell.itemIdentifier = item.identifier; [self configureCell:cell withItem:item];
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;
}];
return cell; return cell;
} }
...@@ -346,7 +330,9 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -346,7 +330,9 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
self.items[index] = item; self.items[index] = item;
if (![self isViewVisible]) if (![self isViewVisible])
return; 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 { - (void)moveItemWithID:(NSString*)itemID toIndex:(NSUInteger)toIndex {
...@@ -403,6 +389,34 @@ NSIndexPath* CreateIndexPath(NSInteger index) { ...@@ -403,6 +389,34 @@ NSIndexPath* CreateIndexPath(NSInteger index) {
return self.viewIfLoaded.window != nil; 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. // Animates the empty state into view.
- (void)animateEmptyStateIntoView { - (void)animateEmptyStateIntoView {
// TODO(crbug.com/820410) : Polish the animation, and put constants where they // TODO(crbug.com/820410) : Polish the animation, and put constants where they
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
#import "ios/chrome/browser/web/tab_id_tab_helper.h" #import "ios/chrome/browser/web/tab_id_tab_helper.h"
#include "ios/chrome/browser/web_state_list/web_state_list.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_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/chrome/browser/web_state_list/web_state_opener.h"
#include "ios/web/public/web_state/web_state.h" #include "ios/web/public/web_state/web_state.h"
#import "ios/web/public/web_state/web_state_observer_bridge.h" #import "ios/web/public/web_state/web_state_observer_bridge.h"
...@@ -71,6 +72,8 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) { ...@@ -71,6 +72,8 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
@property(nonatomic, assign) WebStateList* webStateList; @property(nonatomic, assign) WebStateList* webStateList;
// The UI consumer to which updates are made. // The UI consumer to which updates are made.
@property(nonatomic, weak) id<GridConsumer> consumer; @property(nonatomic, weak) id<GridConsumer> consumer;
// The saved session window just before close all tabs is called.
@property(nonatomic, strong) SessionWindowIOS* closedSessionWindow;
@end @end
@implementation TabGridMediator { @implementation TabGridMediator {
...@@ -89,6 +92,7 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) { ...@@ -89,6 +92,7 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
// Private properties. // Private properties.
@synthesize webStateList = _webStateList; @synthesize webStateList = _webStateList;
@synthesize consumer = _consumer; @synthesize consumer = _consumer;
@synthesize closedSessionWindow = _closedSessionWindow;
- (instancetype)initWithConsumer:(id<GridConsumer>)consumer { - (instancetype)initWithConsumer:(id<GridConsumer>)consumer {
if (self = [super init]) { if (self = [super init]) {
...@@ -228,9 +232,32 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) { ...@@ -228,9 +232,32 @@ int GetIndexOfTabWithId(WebStateList* webStateList, NSString* identifier) {
} }
- (void)closeAllItems { - (void)closeAllItems {
// This is a no-op if |webStateList| is already empty.
self.webStateList->CloseAllWebStates(WebStateList::CLOSE_USER_ACTION); 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 #pragma mark - GridImageDataSource
- (void)snapshotForIdentifier:(NSString*)identifier - (void)snapshotForIdentifier:(NSString*)identifier
......
...@@ -243,13 +243,49 @@ TEST_F(TabGridMediatorTest, CloseItemCommand) { ...@@ -243,13 +243,49 @@ TEST_F(TabGridMediatorTest, CloseItemCommand) {
EXPECT_EQ(2UL, consumer_.items.count); EXPECT_EQ(2UL, consumer_.items.count);
} }
// Tests that the |web_state_list_| is empty when |-closeAllItems| is called. // Tests that the |web_state_list_| and consumer's list are empty when
// Tests that the consumer's item list is also empty. // |-closeAllItems| is called. Tests that |-undoCloseAllItems| does not restore
// the |web_state_list_|.
TEST_F(TabGridMediatorTest, CloseAllItemsCommand) { TEST_F(TabGridMediatorTest, CloseAllItemsCommand) {
// Previously there were 3 items. // Previously there were 3 items.
[mediator_ closeAllItems]; [mediator_ closeAllItems];
EXPECT_EQ(0, web_state_list_->count()); EXPECT_EQ(0, web_state_list_->count());
EXPECT_EQ(0UL, consumer_.items.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 // Tests that when |-addNewItem| is called, the |web_state_list_| count is
......
...@@ -51,18 +51,6 @@ NSUInteger GetPageIndexFromPage(TabGridPage page) { ...@@ -51,18 +51,6 @@ NSUInteger GetPageIndexFromPage(TabGridPage page) {
} }
return static_cast<NSUInteger>(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 } // namespace
@interface TabGridViewController ()<GridViewControllerDelegate, @interface TabGridViewController ()<GridViewControllerDelegate,
...@@ -153,6 +141,7 @@ UIAlertController* NotImplementedAlert() { ...@@ -153,6 +141,7 @@ UIAlertController* NotImplementedAlert() {
- (void)viewWillDisappear:(BOOL)animated { - (void)viewWillDisappear:(BOOL)animated {
self.undoCloseAllAvailable = NO; self.undoCloseAllAvailable = NO;
[self.regularTabsDelegate discardSavedClosedItems];
if (animated && self.transitionCoordinator) { if (animated && self.transitionCoordinator) {
[self animateToolbarsForDisappearance]; [self animateToolbarsForDisappearance];
} }
...@@ -783,13 +772,10 @@ UIAlertController* NotImplementedAlert() { ...@@ -783,13 +772,10 @@ UIAlertController* NotImplementedAlert() {
DCHECK_EQ(self.undoCloseAllAvailable, DCHECK_EQ(self.undoCloseAllAvailable,
self.regularTabsViewController.gridEmpty); self.regularTabsViewController.gridEmpty);
if (self.undoCloseAllAvailable) { if (self.undoCloseAllAvailable) {
// TODO(crbug.com/804567) : Implement Undo Close All. [self.regularTabsDelegate undoCloseAllItems];
[self presentViewController:NotImplementedAlert()
animated:YES
completion:nil];
} else { } else {
[self.incognitoTabsDelegate closeAllItems]; [self.incognitoTabsDelegate closeAllItems];
[self.regularTabsDelegate closeAllItems]; [self.regularTabsDelegate saveAndCloseAllItems];
} }
self.undoCloseAllAvailable = !self.undoCloseAllAvailable; self.undoCloseAllAvailable = !self.undoCloseAllAvailable;
[self configureCloseAllButtonForCurrentPageAndUndoAvailability]; [self configureCloseAllButtonForCurrentPageAndUndoAvailability];
......
...@@ -23,33 +23,17 @@ ...@@ -23,33 +23,17 @@
namespace { namespace {
// Serializable sub-class of TestWebState. std::unique_ptr<web::WebState> CreateWebState() {
class SerializableTestWebState : public web::TestWebState { return std::make_unique<web::TestWebState>();
public: }
static std::unique_ptr<web::WebState> Create() {
return std::make_unique<SerializableTestWebState>();
}
static std::unique_ptr<web::WebState> CreateWithSessionStorage(
CRWSessionStorage* session_storage) {
std::unique_ptr<web::WebState> web_state = Create();
web::SerializableUserDataManager::FromWebState(web_state.get())
->AddSerializableUserData(session_storage.userData);
return web_state;
}
private: std::unique_ptr<web::WebState> CreateWebStateWithSessionStorage(
// web::TestWebState implementation. CRWSessionStorage* session_storage) {
CRWSessionStorage* BuildSessionStorage() override { std::unique_ptr<web::WebState> web_state = CreateWebState();
std::unique_ptr<web::SerializableUserData> serializable_user_data = web::SerializableUserDataManager::FromWebState(web_state.get())
web::SerializableUserDataManager::FromWebState(this) ->AddSerializableUserData(session_storage.userData);
->CreateSerializableUserData(); return web_state;
}
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 // Compares whether both WebStateList |original| and |restored| have the same
// opener-opened relationship. The |restored| WebStateList may have additional // opener-opened relationship. The |restored| WebStateList may have additional
...@@ -105,18 +89,17 @@ TEST_F(WebStateListSerializationTest, SerializationEmpty) { ...@@ -105,18 +89,17 @@ TEST_F(WebStateListSerializationTest, SerializationEmpty) {
TEST_F(WebStateListSerializationTest, SerializationRoundTrip) { TEST_F(WebStateListSerializationTest, SerializationRoundTrip) {
WebStateList original_web_state_list(web_state_list_delegate()); 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( 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, WebStateList::INSERT_FORCE_INDEX | WebStateList::INSERT_ACTIVATE,
WebStateOpener(original_web_state_list.GetWebStateAt(0), 3)); WebStateOpener(original_web_state_list.GetWebStateAt(0), 3));
original_web_state_list.InsertWebState( 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)); WebStateOpener(original_web_state_list.GetWebStateAt(0), 2));
original_web_state_list.InsertWebState( 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)); WebStateOpener(original_web_state_list.GetWebStateAt(1), 1));
SessionWindowIOS* session_window = SessionWindowIOS* session_window =
...@@ -127,14 +110,13 @@ TEST_F(WebStateListSerializationTest, SerializationRoundTrip) { ...@@ -127,14 +110,13 @@ TEST_F(WebStateListSerializationTest, SerializationRoundTrip) {
// Create a deserialized WebStateList and verify its contents. // Create a deserialized WebStateList and verify its contents.
WebStateList restored_web_state_list(web_state_list_delegate()); WebStateList restored_web_state_list(web_state_list_delegate());
restored_web_state_list.InsertWebState(0, SerializableTestWebState::Create(), restored_web_state_list.InsertWebState(
WebStateList::INSERT_FORCE_INDEX, 0, CreateWebState(), WebStateList::INSERT_FORCE_INDEX, WebStateOpener());
WebStateOpener());
ASSERT_EQ(1, restored_web_state_list.count()); ASSERT_EQ(1, restored_web_state_list.count());
DeserializeWebStateList( DeserializeWebStateList(
&restored_web_state_list, session_window, &restored_web_state_list, session_window,
base::BindRepeating(&SerializableTestWebState::CreateWithSessionStorage)); base::BindRepeating(&CreateWebStateWithSessionStorage));
EXPECT_EQ(5, restored_web_state_list.count()); EXPECT_EQ(5, restored_web_state_list.count());
EXPECT_EQ(2, restored_web_state_list.active_index()); EXPECT_EQ(2, restored_web_state_list.active_index());
......
...@@ -10,6 +10,9 @@ ...@@ -10,6 +10,9 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/threading/sequenced_task_runner_handle.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/ui/crw_content_view.h"
#import "ios/web/public/web_state/web_state_policy_decider.h" #import "ios/web/public/web_state/web_state_policy_decider.h"
#include "ui/gfx/image/image.h" #include "ui/gfx/image/image.h"
...@@ -110,7 +113,13 @@ TestWebState::GetSessionCertificatePolicyCache() { ...@@ -110,7 +113,13 @@ TestWebState::GetSessionCertificatePolicyCache() {
} }
CRWSessionStorage* TestWebState::BuildSessionStorage() { 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( 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