1 // Copyright 2015 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.
5 #import "ios/web/public/crw_browsing_data_store.h"
7 #import <Foundation/Foundation.h>
9 #include "base/ios/ios_util.h"
10 #import "base/ios/weak_nsobject.h"
11 #include "base/logging.h"
12 #import "base/mac/scoped_nsobject.h"
13 #import "ios/web/browsing_data_managers/crw_cookie_browsing_data_manager.h"
14 #include "ios/web/public/active_state_manager.h"
15 #include "ios/web/public/browser_state.h"
18 // Represents the type of operations that a CRWBrowsingDataStore can perform.
22 // Stash operation involves stashing browsing data that web views create to
23 // the associated BrowserState's state path.
25 // Restore operation involves restoring browsing data from the
26 // associated BrowserState's state path so that web views can read from them.
28 // Remove operation involves removing the data that web views create.
32 // The name of the NSOperation that performs a |RESTORE| operation.
33 NSString* const kRestoreOperationName = @"CRWBrowsingDataStore.RESTORE";
34 // The name of the NSOperation that performs a |STASH| operation.
35 NSString* const kStashOperationName = @"CRWBrowsingDataStore.STASH";
38 // CRWBrowsingDataStore is implemented using 2 queues.
39 // 1) operationQueueForStashAndRestoreOperations:
40 // - This queue is used to perform |STASH| and |RESTORE| operations.
41 // - |STASH|, |RESTORE| operations are operations that have been explicitly
42 // requested by the user. And the performance of these operations block
43 // further user interaction. Hence this queue has a QoS of
44 // NSQualityOfServiceUserInitiated.
45 // - |STASH|, |RESTORE| operations from 2 different CRWBrowsingDataStores are
46 // not re-entrant. Hence this is a serial queue.
47 // 2) operationQueueForRemoveOperations:
48 // - This queue is used to perform |REMOVE| operations.
49 // - |REMOVE| operations is an operation that has been explicitly requested by
50 // the user. And the performance of this operation blocks further user
51 // interaction. Hence this queue has a QoS of NSQualityOfServiceUserInitiated.
52 // - |REMOVE| operations from 2 different CRWBrowingDataStores can be run in
53 // parallel. Hence this is a concurrent queue.
55 // |STASH|, |RESTORE|, |REMOVE| operations of a particular CRWBrowsingDataStore
56 // are not re-entrant. Hence these operations are serialized.
58 @interface CRWBrowsingDataStore ()
59 // Redefined to be read-write. Must be called from the main thread.
60 @property(nonatomic, assign) web::BrowsingDataStoreMode mode;
61 // The array of all browsing data managers. Must be called from the main
63 @property(nonatomic, readonly) NSArray* allBrowsingDataManagers;
64 // The number of |STASH| or |RESTORE| operations that are still pending. Must be
65 // called from the main thread.
66 @property(nonatomic, assign) NSUInteger numberOfPendingStashOrRestoreOperations;
68 // Returns a serial queue on which |STASH| and |RESTORE| operations can be
69 // scheduled to be run. All |STASH|/|RESTORE| operations need to be run on the
70 // same queue hence it is shared with all CRWBrowsingDataStores.
71 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations;
72 // Returns a concurrent queue on which remove operations can be scheduled to be
73 // run. All |REMOVE| operations need to be run on the same queue hence it is
74 // shared with all CRWBrowsingDataStores.
75 + (NSOperationQueue*)operationQueueForRemoveOperations;
77 // Returns an array of CRWBrowsingDataManagers for the given
78 // |browsingDataTypes|.
79 - (NSArray*)browsingDataManagersForBrowsingDataTypes:
80 (web::BrowsingDataTypes)browsingDataTypes;
81 // Returns the selector that needs to be performed on the
82 // CRWBrowsingDataManagers for the |operationType|. |operationType| cannot be
84 - (SEL)browsingDataManagerSelectorForOperationType:(OperationType)operationType;
85 // Returns the selector that needs to be performed on the
86 // CRWBrowsingDataManagers for a |REMOVE| operation.
87 - (SEL)browsingDataManagerSelectorForRemoveOperationType;
89 // Sets the mode iff there are no more |STASH| or |RESTORE| operations that are
90 // still pending. |mode| can only be either |ACTIVE| or |INACTIVE|.
91 // Returns YES if the mode was successfully changed to |mode|.
92 - (BOOL)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode;
93 // Changes the mode of the CRWBrowsingDataStore to |mode|. This is an
94 // asynchronous operation and the mode is not changed immediately.
95 // |completionHandler| can be nil.
96 // |completionHandler| is called on the main thread. This block has no return
97 // value and takes a single BOOL argument that indicates whether or not the
98 // mode change was successfully changed to |mode|.
99 - (void)changeMode:(web::BrowsingDataStoreMode)mode
100 completionHandler:(void (^)(BOOL modeChangeWasSuccessful))completionHandler;
102 // Returns the OperationType (that needs to be performed) in order to change the
103 // mode to |mode|. Consults the delegate if one is present. |mode| cannot be
105 - (OperationType)operationTypeToChangeMode:(web::BrowsingDataStoreMode)mode;
106 // Performs an operation of type |operationType| on each of the
107 // |browsingDataManagers|. |operationType| cannot be |NONE|.
108 // Precondition: There must be no web views associated with the BrowserState.
109 // |completionHandler| is called on the main thread and cannot be nil.
110 - (void)performOperationWithType:(OperationType)operationType
111 browsingDataManagers:(NSArray*)browsingDataManagers
112 completionHandler:(ProceduralBlock)completionHandler;
113 // Returns an NSOperation that calls |selector| on all the
114 // |browsingDataManagers|. |selector| needs to be one of the methods in
115 // CRWBrowsingDataManager.
116 - (NSOperation*)operationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
117 selector:(SEL)selector;
118 // Enqueues |operation| to be run on |queue|. All operations are serialized to
119 // be run one after another.
120 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue;
123 @implementation CRWBrowsingDataStore {
124 web::BrowserState* _browserState; // Weak, owns this object.
126 base::WeakNSProtocol<id<CRWBrowsingDataStoreDelegate>> _delegate;
127 // The mode of the CRWBrowsingDataStore.
128 web::BrowsingDataStoreMode _mode;
129 // The dictionary that maps a browsing data type to its
130 // CRWBrowsingDataManager.
131 base::scoped_nsobject<NSDictionary> _browsingDataTypeMap;
132 // The last operation that was enqueued to be run. Can be a |STASH|, |RESTORE|
133 // or a |REMOVE| operation.
134 base::scoped_nsobject<NSOperation> _lastDispatchedOperation;
135 // The last dispatched |STASH| or |RESTORE| operation that was enqueued to be
137 base::scoped_nsobject<NSOperation> _lastDispatchedStashOrRestoreOperation;
138 // The number of |STASH| or |RESTORE| operations that are still pending. If
139 // the number of |STASH| or |RESTORE| operations is greater than 0U, the mode
140 // of the CRWBrowsingDataStore is |CHANGING|. The mode can be made ACTIVE or
141 // INACTIVE only be set when this value is 0U.
142 NSUInteger _numberOfPendingStashOrRestoreOperations;
146 #pragma mark NSObject Methods
148 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState {
151 DCHECK([NSThread isMainThread]);
152 DCHECK(browserState);
153 // TODO(shreyasv): Instantiate the necessary CRWBrowsingDataManagers that
154 // are encapsulated within this class. crbug.com/480654.
155 _browserState = browserState;
156 web::ActiveStateManager* activeStateManager =
157 web::BrowserState::GetActiveStateManager(browserState);
158 DCHECK(activeStateManager);
159 _mode = activeStateManager->IsActive() ? web::ACTIVE : web::INACTIVE;
160 // TODO(shreyasv): If the creation of CRWBrowsingDataManagers turns out to
161 // be an expensive operations re-visit this with a lazy-evaluation approach.
162 base::scoped_nsobject<CRWCookieBrowsingDataManager>
163 cookieBrowsingDataManager([[CRWCookieBrowsingDataManager alloc]
164 initWithBrowserState:browserState]);
165 _browsingDataTypeMap.reset([@{
166 @(web::BROWSING_DATA_TYPE_COOKIES) : cookieBrowsingDataManager,
172 - (instancetype)init {
177 - (NSString*)description {
178 NSString* format = @"<%@: %p; hasPendingOperations = { %@ }; mode = { %@ }>";
179 NSString* hasPendingOperationsString =
180 [self hasPendingOperations] ? @"YES" : @"NO";
181 NSString* modeString = nil;
184 modeString = @"ACTIVE";
187 modeString = @"CHANGING";
190 modeString = @"INACTIVE";
193 NSString* result = [NSString stringWithFormat:format,
194 NSStringFromClass(self.class), self, hasPendingOperationsString,
199 + (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
200 // It is necessary to override this for |mode| because the default KVO
201 // behavior in NSObject is to fire a notification irrespective of if an actual
202 // change was made to the ivar or not. The |mode| property needs fine grained
203 // control over the actual notifications being fired since observers need to
204 // be notified iff the |mode| actually changed.
205 if ([key isEqual:@"mode"]) {
208 return [super automaticallyNotifiesObserversForKey:key];
212 #pragma mark Public Properties
214 - (id<CRWBrowsingDataStoreDelegate>)delegate {
218 - (void)setDelegate:(id<CRWBrowsingDataStoreDelegate>)delegate {
219 _delegate.reset(delegate);
222 - (web::BrowsingDataStoreMode)mode {
223 DCHECK([NSThread isMainThread]);
227 - (BOOL)hasPendingOperations {
228 if (!_lastDispatchedOperation) {
231 return ![_lastDispatchedOperation isFinished];
235 #pragma mark Public Methods
237 - (void)makeActiveWithCompletionHandler:
238 (void (^)(BOOL success))completionHandler {
239 DCHECK([NSThread isMainThread]);
240 // TODO(shreyasv): Verify the preconditions for this method when
241 // web::WebViewCounter class is implemented. crbug.com/480507.
243 [self changeMode:web::ACTIVE completionHandler:completionHandler];
246 - (void)makeInactiveWithCompletionHandler:
247 (void (^)(BOOL success))completionHandler {
248 DCHECK([NSThread isMainThread]);
249 // TODO(shreyasv): Verify the preconditions for this method when
250 // web::WebViewCounter class is implemented. crbug.com/480507.
252 [self changeMode:web::INACTIVE completionHandler:completionHandler];
255 - (void)removeDataOfTypes:(web::BrowsingDataTypes)browsingDataTypes
256 completionHandler:(ProceduralBlock)completionHandler {
257 DCHECK([NSThread isMainThread]);
258 // TODO(shreyasv): Verify the preconditions for this method when
259 // web::WebViewCounter class is implemented. crbug.com/480507.
261 NSArray* browsingDataManagers =
262 [self browsingDataManagersForBrowsingDataTypes:browsingDataTypes];
263 [self performOperationWithType:REMOVE
264 browsingDataManagers:browsingDataManagers
265 completionHandler:completionHandler];
269 #pragma mark Private Properties
271 - (void)setMode:(web::BrowsingDataStoreMode)mode {
272 DCHECK([NSThread isMainThread]);
277 if (mode == web::ACTIVE || mode == web::INACTIVE) {
278 DCHECK(!self.numberOfPendingStashOrRestoreOperations);
280 [self willChangeValueForKey:@"mode"];
282 [self didChangeValueForKey:@"mode"];
285 - (NSArray*)allBrowsingDataManagers {
286 DCHECK([NSThread isMainThread]);
287 return [_browsingDataTypeMap allValues];
290 - (NSUInteger)numberOfPendingStashOrRestoreOperations {
291 DCHECK([NSThread isMainThread]);
292 return _numberOfPendingStashOrRestoreOperations;
295 - (void)setNumberOfPendingStashOrRestoreOperations:
296 (NSUInteger)numberOfPendingStashOrRestoreOperations {
297 DCHECK([NSThread isMainThread]);
298 _numberOfPendingStashOrRestoreOperations =
299 numberOfPendingStashOrRestoreOperations;
303 #pragma mark Private Class Methods
305 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations {
306 static dispatch_once_t onceToken = 0;
307 static NSOperationQueue* operationQueueForStashAndRestoreOperations = nil;
308 dispatch_once(&onceToken, ^{
309 operationQueueForStashAndRestoreOperations =
310 [[NSOperationQueue alloc] init];
311 [operationQueueForStashAndRestoreOperations
312 setMaxConcurrentOperationCount:1U];
313 if (base::ios::IsRunningOnIOS8OrLater()) {
314 [operationQueueForStashAndRestoreOperations
315 setQualityOfService:NSQualityOfServiceUserInitiated];
318 return operationQueueForStashAndRestoreOperations;
321 + (NSOperationQueue*)operationQueueForRemoveOperations {
322 static dispatch_once_t onceToken = 0;
323 static NSOperationQueue* operationQueueForRemoveOperations = nil;
324 dispatch_once(&onceToken, ^{
325 operationQueueForRemoveOperations = [[NSOperationQueue alloc] init];
326 [operationQueueForRemoveOperations
327 setMaxConcurrentOperationCount:NSUIntegerMax];
328 if (base::ios::IsRunningOnIOS8OrLater()) {
329 [operationQueueForRemoveOperations
330 setQualityOfService:NSQualityOfServiceUserInitiated];
333 return operationQueueForRemoveOperations;
337 #pragma mark Private Methods
339 - (NSArray*)browsingDataManagersForBrowsingDataTypes:
340 (web::BrowsingDataTypes)browsingDataTypes {
341 __block NSMutableArray* result = [NSMutableArray array];
342 [_browsingDataTypeMap
343 enumerateKeysAndObjectsUsingBlock:^(NSNumber* dataType,
344 id<CRWBrowsingDataManager> manager,
346 if ([dataType unsignedIntegerValue] & browsingDataTypes) {
347 [result addObject:manager];
353 - (SEL)browsingDataManagerSelectorForOperationType:
354 (OperationType)operationType {
355 switch (operationType) {
360 return @selector(stashData);
362 return @selector(restoreData);
364 return [self browsingDataManagerSelectorForRemoveOperationType];
368 - (SEL)browsingDataManagerSelectorForRemoveOperationType {
369 if (self.mode == web::ACTIVE) {
370 return @selector(removeDataAtCanonicalPath);
372 if (self.mode == web::INACTIVE) {
373 return @selector(removeDataAtStashPath);
375 // Since the mode is |CHANGING|, find the last |STASH| or |RESTORE| operation
376 // that was enqueued in order to find out the eventual mode that the
377 // CRWBrowsingDataStore will be in when this |REMOVE| operation is run.
378 DCHECK(_lastDispatchedStashOrRestoreOperation);
379 NSString* lastDispatchedStashOrRestoreOperationName =
380 [_lastDispatchedStashOrRestoreOperation name];
381 if ([lastDispatchedStashOrRestoreOperationName
382 isEqual:kRestoreOperationName]) {
383 return @selector(removeDataAtCanonicalPath);
385 if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) {
386 return @selector(removeDataAtStashPath);
392 - (BOOL)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode {
393 DCHECK([NSThread isMainThread]);
394 DCHECK_NE(web::CHANGING, mode);
396 BOOL modeChangeWasSuccessful = NO;
397 if (!self.numberOfPendingStashOrRestoreOperations) {
399 modeChangeWasSuccessful = YES;
401 return modeChangeWasSuccessful;
404 - (void)changeMode:(web::BrowsingDataStoreMode)mode
406 (void (^)(BOOL modeChangeWasSuccessful))completionHandler {
407 DCHECK([NSThread isMainThread]);
409 ProceduralBlock completionHandlerAfterPerformingOperation = ^{
410 BOOL modeChangeWasSuccessful = [self finalizeChangeToMode:mode];
411 if (completionHandler) {
412 completionHandler(modeChangeWasSuccessful);
416 OperationType operationType = [self operationTypeToChangeMode:mode];
417 if (operationType == NONE) {
418 // As a caller of this API, it is awkward to get the callback before the
419 // method call has completed, hence defer it.
420 dispatch_async(dispatch_get_main_queue(),
421 completionHandlerAfterPerformingOperation);
423 [self performOperationWithType:operationType
424 browsingDataManagers:[self allBrowsingDataManagers]
425 completionHandler:completionHandlerAfterPerformingOperation];
429 - (OperationType)operationTypeToChangeMode:(web::BrowsingDataStoreMode)mode {
430 DCHECK_NE(web::CHANGING, mode);
432 OperationType operationType = NONE;
433 if (mode == self.mode) {
434 // Already in the desired mode.
435 operationType = NONE;
436 } else if (mode == web::ACTIVE) {
437 // By default a |RESTORE| operation is performed when the mode is changed
439 operationType = RESTORE;
440 web::BrowsingDataStoreMakeActivePolicy makeActivePolicy =
441 [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self];
442 operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE;
444 // By default a |STASH| operation is performed when the mode is changed to
446 operationType = STASH;
447 web::BrowsingDataStoreMakeInactivePolicy makeInactivePolicy =
448 [_delegate decideMakeInactiveOperationPolicyForBrowsingDataStore:self];
449 operationType = (makeInactivePolicy == web::DELETE) ? REMOVE : STASH;
451 return operationType;
454 - (void)performOperationWithType:(OperationType)operationType
455 browsingDataManagers:(NSArray*)browsingDataManagers
456 completionHandler:(ProceduralBlock)completionHandler {
457 DCHECK([NSThread isMainThread]);
458 DCHECK(completionHandler);
459 DCHECK_NE(NONE, operationType);
462 [self browsingDataManagerSelectorForOperationType:operationType];
465 if (operationType == RESTORE || operationType == STASH) {
466 [self setMode:web::CHANGING];
467 ++self.numberOfPendingStashOrRestoreOperations;
468 completionHandler = ^{
469 --self.numberOfPendingStashOrRestoreOperations;
470 // It is safe to this and does not lead to the block (|completionHandler|)
476 ProceduralBlock callCompletionHandlerOnMainThread = ^{
477 // This is called on a background thread, hence the need to bounce to the
479 dispatch_async(dispatch_get_main_queue(), completionHandler);
481 NSOperation* operation =
482 [self operationWithBrowsingDataManagers:browsingDataManagers
485 if (operationType == RESTORE || operationType == STASH) {
486 [operation setName:(RESTORE ? kRestoreOperationName : kStashOperationName)];
487 _lastDispatchedStashOrRestoreOperation.reset([operation retain]);
490 NSOperationQueue* queue = nil;
491 switch (operationType) {
497 queue = [CRWBrowsingDataStore operationQueueForStashAndRestoreOperations];
500 queue = [CRWBrowsingDataStore operationQueueForRemoveOperations];
504 NSOperation* completionHandlerOperation = [NSBlockOperation
505 blockOperationWithBlock:callCompletionHandlerOnMainThread];
507 [self addOperation:operation toQueue:queue];
508 [self addOperation:completionHandlerOperation toQueue:queue];
511 - (NSOperation*)operationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
512 selector:(SEL)selector {
513 NSBlockOperation* operation = [[[NSBlockOperation alloc] init] autorelease];
514 for (id<CRWBrowsingDataManager> manager : browsingDataManagers) {
515 // |addExecutionBlock| farms out the different blocks added to it. hence the
516 // operations are implicitly parallelized.
517 [operation addExecutionBlock:^{
518 [manager performSelector:selector];
524 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue {
525 DCHECK([NSThread isMainThread]);
529 if (_lastDispatchedOperation) {
530 [operation addDependency:_lastDispatchedOperation];
532 _lastDispatchedOperation.reset([operation retain]);
533 [queue addOperation:operation];