Add ICU message format support
[chromium-blink-merge.git] / ios / web / crw_browsing_data_store.mm
blobc0826ce47674b62fcdbc2b2497b990949a729fcb
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"
17 namespace {
18 // Represents the type of operations that a CRWBrowsingDataStore can perform.
19 enum OperationType {
20   // Represents NOP.
21   NONE = 1,
22   // Stash operation involves stashing browsing data that web views create to
23   // the associated BrowserState's state path.
24   STASH,
25   // Restore operation involves restoring browsing data from the
26   // associated BrowserState's state path so that web views can read from them.
27   RESTORE,
28   // Remove operation involves removing the data that web views create.
29   REMOVE,
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";
36 }  // namespace
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
62 // thread.
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
83 // |NONE|.
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
104 // |CHANGING|.
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;
121 @end
123 @implementation CRWBrowsingDataStore {
124   web::BrowserState* _browserState;  // Weak, owns this object.
125   // The delegate.
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
136   // run.
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;
145 #pragma mark -
146 #pragma mark NSObject Methods
148 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState {
149   self = [super init];
150   if (self) {
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,
167     } retain]);
168   }
169   return self;
172 - (instancetype)init {
173   NOTREACHED();
174   return nil;
177 - (NSString*)description {
178   NSString* format = @"<%@: %p; hasPendingOperations = { %@ }; mode = { %@ }>";
179   NSString* hasPendingOperationsString =
180       [self hasPendingOperations] ? @"YES" : @"NO";
181   NSString* modeString = nil;
182   switch (self.mode) {
183     case web::ACTIVE:
184       modeString = @"ACTIVE";
185       break;
186     case web::CHANGING:
187       modeString = @"CHANGING";
188       break;
189     case web::INACTIVE:
190       modeString = @"INACTIVE";
191       break;
192   }
193   NSString* result = [NSString stringWithFormat:format,
194       NSStringFromClass(self.class), self, hasPendingOperationsString,
195       modeString];
196   return result;
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"]) {
206     return NO;
207   }
208   return [super automaticallyNotifiesObserversForKey:key];
211 #pragma mark -
212 #pragma mark Public Properties
214 - (id<CRWBrowsingDataStoreDelegate>)delegate {
215   return _delegate;
218 - (void)setDelegate:(id<CRWBrowsingDataStoreDelegate>)delegate {
219   _delegate.reset(delegate);
222 - (web::BrowsingDataStoreMode)mode {
223   DCHECK([NSThread isMainThread]);
224   return _mode;
227 - (BOOL)hasPendingOperations {
228   if (!_lastDispatchedOperation) {
229     return NO;
230   }
231   return ![_lastDispatchedOperation isFinished];
234 #pragma mark -
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];
268 #pragma mark -
269 #pragma mark Private Properties
271 - (void)setMode:(web::BrowsingDataStoreMode)mode {
272   DCHECK([NSThread isMainThread]);
274   if (_mode == mode) {
275     return;
276   }
277   if (mode == web::ACTIVE || mode == web::INACTIVE) {
278     DCHECK(!self.numberOfPendingStashOrRestoreOperations);
279   }
280   [self willChangeValueForKey:@"mode"];
281   _mode = 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;
302 #pragma mark -
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];
316     }
317   });
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];
331     }
332   });
333   return operationQueueForRemoveOperations;
336 #pragma mark -
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,
345                                           BOOL*) {
346         if ([dataType unsignedIntegerValue] & browsingDataTypes) {
347           [result addObject:manager];
348         }
349       }];
350   return result;
353 - (SEL)browsingDataManagerSelectorForOperationType:
354     (OperationType)operationType {
355   switch (operationType) {
356     case NONE:
357       NOTREACHED();
358       return nullptr;
359     case STASH:
360       return @selector(stashData);
361     case RESTORE:
362       return @selector(restoreData);
363     case REMOVE:
364       return [self browsingDataManagerSelectorForRemoveOperationType];
365   };
368 - (SEL)browsingDataManagerSelectorForRemoveOperationType {
369   if (self.mode == web::ACTIVE) {
370     return @selector(removeDataAtCanonicalPath);
371   }
372   if (self.mode == web::INACTIVE) {
373     return @selector(removeDataAtStashPath);
374   }
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);
384   }
385   if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) {
386     return @selector(removeDataAtStashPath);
387   }
388   NOTREACHED();
389   return nullptr;
392 - (BOOL)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode {
393   DCHECK([NSThread isMainThread]);
394   DCHECK_NE(web::CHANGING, mode);
396   BOOL modeChangeWasSuccessful = NO;
397   if (!self.numberOfPendingStashOrRestoreOperations) {
398     [self setMode:mode];
399     modeChangeWasSuccessful = YES;
400   }
401   return modeChangeWasSuccessful;
404 - (void)changeMode:(web::BrowsingDataStoreMode)mode
405     completionHandler:
406         (void (^)(BOOL modeChangeWasSuccessful))completionHandler {
407   DCHECK([NSThread isMainThread]);
409   ProceduralBlock completionHandlerAfterPerformingOperation = ^{
410     BOOL modeChangeWasSuccessful = [self finalizeChangeToMode:mode];
411     if (completionHandler) {
412       completionHandler(modeChangeWasSuccessful);
413     }
414   };
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);
422   } else {
423     [self performOperationWithType:operationType
424               browsingDataManagers:[self allBrowsingDataManagers]
425                  completionHandler:completionHandlerAfterPerformingOperation];
426   }
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
438     // to |ACTIVE|.
439     operationType = RESTORE;
440     web::BrowsingDataStoreMakeActivePolicy makeActivePolicy =
441         [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self];
442     operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE;
443   } else {
444     // By default a |STASH| operation is performed when the mode is changed to
445     // |INACTIVE|.
446     operationType = STASH;
447     web::BrowsingDataStoreMakeInactivePolicy makeInactivePolicy =
448         [_delegate decideMakeInactiveOperationPolicyForBrowsingDataStore:self];
449     operationType = (makeInactivePolicy == web::DELETE) ? REMOVE : STASH;
450   }
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);
461   SEL selector =
462       [self browsingDataManagerSelectorForOperationType:operationType];
463   DCHECK(selector);
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|)
471       // retaining itself.
472       completionHandler();
473     };
474   }
476   ProceduralBlock callCompletionHandlerOnMainThread = ^{
477     // This is called on a background thread, hence the need to bounce to the
478     // main thread.
479     dispatch_async(dispatch_get_main_queue(), completionHandler);
480   };
481   NSOperation* operation =
482       [self operationWithBrowsingDataManagers:browsingDataManagers
483                                      selector:selector];
485   if (operationType == RESTORE || operationType == STASH) {
486     [operation setName:(RESTORE ? kRestoreOperationName : kStashOperationName)];
487     _lastDispatchedStashOrRestoreOperation.reset([operation retain]);
488   }
490   NSOperationQueue* queue = nil;
491   switch (operationType) {
492     case NONE:
493       NOTREACHED();
494       break;
495     case STASH:
496     case RESTORE:
497       queue = [CRWBrowsingDataStore operationQueueForStashAndRestoreOperations];
498       break;
499     case REMOVE:
500       queue = [CRWBrowsingDataStore operationQueueForRemoveOperations];
501       break;
502   }
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];
519     }];
520   }
521   return operation;
524 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue {
525   DCHECK([NSThread isMainThread]);
526   DCHECK(operation);
527   DCHECK(queue);
529   if (_lastDispatchedOperation) {
530     [operation addDependency:_lastDispatchedOperation];
531   }
532   _lastDispatchedOperation.reset([operation retain]);
533   [queue addOperation:operation];
536 @end