Include all dupe types (event when value is zero) in scan stats.
[chromium-blink-merge.git] / ios / web / crw_browsing_data_store.mm
blob714277762d22e48704413c345e2e5922a46cb4f5
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 (UIWebViews and
27   // WKWebViews) can read from them.
28   RESTORE,
29   // Remove operation involves removing the data that web views create.
30   REMOVE,
33 // The name of the NSOperation that performs a restore operation.
34 NSString* const kRestoreOperationName = @"CRWBrowsingDataStore.RESTORE";
35 // The name of the NSOperation that performs a stash operation.
36 NSString* const kStashOperationName = @"CRWBrowsingDataStore.STASH";
37 }  // namespace
39 @interface CRWBrowsingDataStore ()
40 // Returns a serial queue on which stash and restore operations can be scheduled
41 // to be run. All stash/restore operations need to be run on the same queue
42 // hence it is shared with all CRWBrowsingDataStores.
43 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations;
44 // Returns a concurrent queue on which remove operations can be scheduled to be
45 // run. All remove operations need to be run on the same queue hence it is
46 // shared with all CRWBrowsingDataStores.
47 + (NSOperationQueue*)operationQueueForRemoveOperations;
49 // The array of all browsing data managers. Must be accessed from the main
50 // thread.
51 @property(nonatomic, readonly) NSArray* allBrowsingDataManagers;
52 // Returns an array of browsing data managers for the given |browsingDataTypes|.
53 - (NSArray*)browsingDataManagersForBrowsingDataTypes:
54         (web::BrowsingDataTypes)browsingDataTypes;
55 // Returns the selector that needs to be performed on the
56 // CRWBrowsingDataManagers for the |operationType|. |operationType| cannot be
57 // |NONE|.
58 - (SEL)browsingDataManagerSelectorForOperationType:(OperationType)operationType;
59 // Returns the selector that needs to be performed on the
60 // CRWBrowsingDataManagers for a REMOVE operation.
61 - (SEL)browsingDataManagerSelectorForRemoveOperationType;
63 // Redefined to be read-write. Must be called from the main thread.
64 @property(nonatomic, assign) CRWBrowsingDataStoreMode mode;
65 // Sets the mode iff there are no more stash or restore operations that are
66 // still pending. |mode| can only be either |ACTIVE| or |INACTIVE|.
67 // |handler| is called immediately (in the same runloop) with a BOOL indicating
68 // whether the mode change was successful or not. |handler| can be nil.
69 - (void)finalizeChangeToMode:(CRWBrowsingDataStoreMode)mode
70     andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler;
72 // Changes the mode of the CRWBrowsingDataStore to |mode|. This is an
73 // asynchronous operation and the mode is not changed immediately.
74 // |completionHandler| can be nil.
75 // |completionHandler| is called on the main thread. This block has no return
76 // value and takes a single BOOL argument that indicates whether or not the
77 // mode change was successfully changed to |mode|.
78 - (void)changeMode:(CRWBrowsingDataStoreMode)mode
79     completionHandler:(void (^)(BOOL modeChangeWasSuccessful))completionHandler;
81 // The number of stash or restore operations that are still pending.
82 @property(nonatomic, assign) NSUInteger numberOfPendingStashOrRestoreOperations;
84 // Performs operations of type |operationType| on each of the
85 // |browsingDataManagers|. |operationType| cannot be |NONE|.
86 // Precondition: There must be no web views associated with the BrowserState.
87 // |completionHandler| is called on the main thread and cannot be nil.
88 - (void)performOperationWithType:(OperationType)operationType
89             browsingDataManagers:(NSArray*)browsingDataManagers
90                completionHandler:(ProceduralBlock)completionHandler;
92 // Creates an NSOperation that calls |selector| on all the
93 // |browsingDataManagers|. |selector| needs to be one of the methods in
94 // CRWBrowsingDataManager. The created NSOperation will have a completionBlock
95 // of |completionHandler|.
96 - (NSOperation*)
97     newOperationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
98                                 selector:(SEL)selector
99                        completionHandler:(ProceduralBlock)completionHandler;
100 // Enqueues |operation| to be run on |queue|. All operations are serialized to
101 // be run one after another.
102 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue;
103 @end
105 @implementation CRWBrowsingDataStore {
106   web::BrowserState* _browserState;  // Weak, owns this object.
107   // The delegate.
108   base::WeakNSProtocol<id<CRWBrowsingDataStoreDelegate>> _delegate;
109   // The mode of the CRWBrowsingDataStore.
110   CRWBrowsingDataStoreMode _mode;
111   // The dictionary that maps a browsing data type to its
112   // CRWBrowsingDataManager.
113   base::scoped_nsobject<NSDictionary> _browsingDataTypeMap;
114   // The last operation that was enqueued to be run. Can be stash, restore or a
115   // delete operation.
116   base::scoped_nsobject<NSOperation> _lastDispatchedOperation;
117   // The last dispatched stash or restore operation that was enqueued to be run.
118   base::scoped_nsobject<NSOperation> _lastDispatchedStashOrRestoreOperation;
119   // The number of stash or restore operations that are still pending. If this
120   // value > 0 the mode of the CRWBrowsingDataStore is SYNCHRONIZING. The mode
121   // can be made ACTIVE or INACTIVE only be set when this value is 0.
122   NSUInteger _numberOfPendingStashOrRestoreOperations;
125 + (NSOperationQueue*)operationQueueForStashAndRestoreOperations {
126   static dispatch_once_t onceToken = 0;
127   static NSOperationQueue* operationQueueForStashAndRestoreOperations = nil;
128   dispatch_once(&onceToken, ^{
129     operationQueueForStashAndRestoreOperations =
130         [[NSOperationQueue alloc] init];
131     [operationQueueForStashAndRestoreOperations
132         setMaxConcurrentOperationCount:1U];
133     if (base::ios::IsRunningOnIOS8OrLater()) {
134       [operationQueueForStashAndRestoreOperations
135           setQualityOfService:NSQualityOfServiceUserInteractive];
136     }
137   });
138   return operationQueueForStashAndRestoreOperations;
141 + (NSOperationQueue*)operationQueueForRemoveOperations {
142   static dispatch_once_t onceToken = 0;
143   static NSOperationQueue* operationQueueForRemoveOperations = nil;
144   dispatch_once(&onceToken, ^{
145     operationQueueForRemoveOperations = [[NSOperationQueue alloc] init];
146     [operationQueueForRemoveOperations
147         setMaxConcurrentOperationCount:NSUIntegerMax];
148     if (base::ios::IsRunningOnIOS8OrLater()) {
149       [operationQueueForRemoveOperations
150           setQualityOfService:NSQualityOfServiceUserInitiated];
151     }
152   });
153   return operationQueueForRemoveOperations;
156 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState {
157   self = [super init];
158   if (self) {
159     DCHECK([NSThread isMainThread]);
160     // TODO(shreyasv): Instantiate the necessary CRWBrowsingDataManagers that
161     // are encapsulated within this class. crbug.com/480654.
162     _browserState = browserState;
163     web::ActiveStateManager* activeStateManager =
164         web::BrowserState::GetActiveStateManager(browserState);
165     DCHECK(activeStateManager);
166     _mode = activeStateManager->IsActive() ? ACTIVE : INACTIVE;
167     // TODO(shreyasv): If the creation of CRWBrowsingDataManagers turns out to
168     // be an expensive operations re-visit this with a lazy-evaluation approach.
169     base::scoped_nsobject<CRWCookieBrowsingDataManager>
170         cookieBrowsingDataManager([[CRWCookieBrowsingDataManager alloc]
171             initWithBrowserState:browserState]);
172     _browsingDataTypeMap.reset([@{
173       @(web::BROWSING_DATA_TYPE_COOKIES) : cookieBrowsingDataManager,
174     } retain]);
175   }
176   return self;
179 - (NSString*)description {
180   NSString* format = @"<%@: %p; hasPendingOperations = { %@ }>";
181   NSString* hasPendingOperationsString =
182       [self hasPendingOperations] ? @"YES" : @"NO";
183   NSString* result =
184       [NSString stringWithFormat:format, NSStringFromClass(self.class), self,
185                                  hasPendingOperationsString];
186   return result;
190 - (NSArray*)allBrowsingDataManagers {
191   DCHECK([NSThread isMainThread]);
192   return [_browsingDataTypeMap allValues];
195 - (NSArray*)browsingDataManagersForBrowsingDataTypes:
196         (web::BrowsingDataTypes)browsingDataTypes {
197   __block NSMutableArray* result = [NSMutableArray array];
198   [_browsingDataTypeMap
199       enumerateKeysAndObjectsUsingBlock:^(NSNumber* dataType,
200                                           id<CRWBrowsingDataManager> manager,
201                                           BOOL*) {
202         if ([dataType unsignedIntegerValue] & browsingDataTypes) {
203           [result addObject:manager];
204         }
205       }];
206   return result;
209 - (id<CRWBrowsingDataStoreDelegate>)delegate {
210   return _delegate;
213 - (void)setDelegate:(id<CRWBrowsingDataStoreDelegate>)delegate {
214   _delegate.reset(delegate);
217 - (SEL)browsingDataManagerSelectorForOperationType:
218         (OperationType)operationType {
219   switch (operationType) {
220     case NONE:
221       NOTREACHED();
222       return nullptr;
223     case STASH:
224       return @selector(stashData);
225     case RESTORE:
226       return @selector(restoreData);
227     case REMOVE:
228       return [self browsingDataManagerSelectorForRemoveOperationType];
229   };
230   NOTREACHED();
231   return nullptr;
234 - (SEL)browsingDataManagerSelectorForRemoveOperationType {
235   if (self.mode == ACTIVE) {
236     return @selector(removeDataAtCanonicalPath);
237   }
238   if (self.mode == INACTIVE) {
239     return @selector(removeDataAtStashPath);
240   }
241   DCHECK(_lastDispatchedStashOrRestoreOperation);
242   NSString* lastDispatchedStashOrRestoreOperationName =
243       [_lastDispatchedStashOrRestoreOperation name];
244   if ([lastDispatchedStashOrRestoreOperationName
245           isEqual:kRestoreOperationName]) {
246     return @selector(removeDataAtCanonicalPath);
247   }
248   if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) {
249     return @selector(removeDataAtStashPath);
250   }
251   NOTREACHED();
252   return nullptr;
255 + (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
256   // It is necessary to override this for |mode| because the default KVO
257   // behavior in NSObject is to fire a notification irrespective of if an actual
258   // change was made to the ivar or not. The |mode| property needs fine grained
259   // control over the actual notifications being fired since observers need to
260   // be notified iff the |mode| actually changed.
261   if ([key isEqual:@"mode"]) {
262     return NO;
263   }
264   return [super automaticallyNotifiesObserversForKey:(NSString*)key];
267 - (CRWBrowsingDataStoreMode)mode {
268   DCHECK([NSThread isMainThread]);
269   return _mode;
272 - (void)setMode:(CRWBrowsingDataStoreMode)mode {
273   DCHECK([NSThread isMainThread]);
274   if (_mode == mode) {
275     return;
276   }
277   if (mode == ACTIVE || mode == INACTIVE) {
278     DCHECK(!self.numberOfPendingStashOrRestoreOperations);
279   }
280   [self willChangeValueForKey:@"mode"];
281   _mode = mode;
282   [self didChangeValueForKey:@"mode"];
285 - (void)finalizeChangeToMode:(CRWBrowsingDataStoreMode)mode
286     andCallCompletionHandler:(void (^)(BOOL modeChangeWasSuccessful))handler {
287   DCHECK([NSThread isMainThread]);
288   DCHECK_NE(SYNCHRONIZING, mode);
290   BOOL modeChangeWasSuccessful = NO;
291   if (!self.numberOfPendingStashOrRestoreOperations) {
292     [self setMode:mode];
293     modeChangeWasSuccessful = YES;
294   }
295   if (handler) {
296     handler(modeChangeWasSuccessful);
297   }
300 - (NSUInteger)numberOfPendingStashOrRestoreOperations {
301   DCHECK([NSThread isMainThread]);
302   return _numberOfPendingStashOrRestoreOperations;
305 - (void)setNumberOfPendingStashOrRestoreOperations:
306         (NSUInteger)numberOfPendingStashOrRestoreOperations {
307   DCHECK([NSThread isMainThread]);
308   _numberOfPendingStashOrRestoreOperations =
309       numberOfPendingStashOrRestoreOperations;
312 - (void)makeActiveWithCompletionHandler:
313         (void (^)(BOOL success))completionHandler {
314   DCHECK([NSThread isMainThread]);
316   [self changeMode:ACTIVE completionHandler:completionHandler];
319 - (void)makeInactiveWithCompletionHandler:
320         (void (^)(BOOL success))completionHandler {
321   DCHECK([NSThread isMainThread]);
323   [self changeMode:INACTIVE completionHandler:completionHandler];
326 - (void)changeMode:(CRWBrowsingDataStoreMode)mode
327     completionHandler:
328         (void (^)(BOOL modeChangeWasSuccessful))completionHandler {
329   DCHECK([NSThread isMainThread]);
331   ProceduralBlock completionHandlerAfterPerformingOperation = ^{
332     [self finalizeChangeToMode:mode andCallCompletionHandler:completionHandler];
333   };
335   // Already in the desired mode.
336   if (self.mode == mode) {
337     // As a caller of this API, it is awkward to get the callback before the
338     // method call has completed, hence defer it.
339     dispatch_async(dispatch_get_main_queue(), ^{
340       completionHandlerAfterPerformingOperation();
341     });
342     return;
343   }
345   OperationType operationType = NONE;
346   if (mode == ACTIVE) {
347     // By default a |RESTORE| operation is performed when the mode is changed
348     // to |ACTIVE|.
349     operationType = RESTORE;
350     web::BrowsingDataStoreMakeActivePolicy makeActivePolicy =
351         [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self];
352     operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE;
353   } else {
354     // By default a |STASH| operation is performed when the mode is changed to
355     // |INACTIVE|.
356     operationType = STASH;
357     web::BrowsingDataStoreMakeInactivePolicy makeInactivePolicy =
358         [_delegate decideMakeInactiveOperationPolicyForBrowsingDataStore:self];
359     operationType = (makeInactivePolicy == web::DELETE) ? REMOVE : STASH;
360   }
361   DCHECK_NE(NONE, operationType);
362   [self performOperationWithType:operationType
363             browsingDataManagers:[self allBrowsingDataManagers]
364                completionHandler:completionHandlerAfterPerformingOperation];
367 - (void)removeDataOfTypes:(web::BrowsingDataTypes)browsingDataTypes
368         completionHandler:(ProceduralBlock)completionHandler {
369   DCHECK([NSThread isMainThread]);
371   NSArray* browsingDataManagers =
372       [self browsingDataManagersForBrowsingDataTypes:browsingDataTypes];
373   [self performOperationWithType:REMOVE
374             browsingDataManagers:browsingDataManagers
375                completionHandler:^{
376                  // Since this may be called on a background thread, bounce to
377                  // the main thread.
378                  dispatch_async(dispatch_get_main_queue(), completionHandler);
379                }];
382 - (BOOL)hasPendingOperations {
383   if (!_lastDispatchedOperation) {
384     return NO;
385   }
386   return ![_lastDispatchedOperation isFinished];
389 - (void)performOperationWithType:(OperationType)operationType
390             browsingDataManagers:(NSArray*)browsingDataManagers
391                completionHandler:(ProceduralBlock)completionHandler {
392   DCHECK([NSThread isMainThread]);
393   DCHECK(completionHandler);
394   DCHECK_NE(NONE, operationType);
396   SEL selector =
397       [self browsingDataManagerSelectorForOperationType:operationType];
398   DCHECK(selector);
400   if (operationType == RESTORE || operationType == STASH) {
401     [self setMode:SYNCHRONIZING];
402     ++self.numberOfPendingStashOrRestoreOperations;
403     completionHandler = ^{
404       --self.numberOfPendingStashOrRestoreOperations;
405       // It is safe to this and does not lead to the block (|completionHandler|)
406       // retaining itself.
407       completionHandler();
408     };
409   }
411   id callCompletionHandlerOnMainThread = ^{
412     // This is called on a background thread, hence the need to bounce to the
413     // main thread.
414     dispatch_async(dispatch_get_main_queue(), ^{
415       completionHandler();
416     });
417   };
418   base::scoped_nsobject<NSOperation> operation([self
419       newOperationWithBrowsingDataManagers:browsingDataManagers
420                                   selector:selector
421                          completionHandler:callCompletionHandlerOnMainThread]);
423   if (operationType == RESTORE || operationType == STASH) {
424     [operation setName:(RESTORE ? kRestoreOperationName : kStashOperationName)];
425     _lastDispatchedStashOrRestoreOperation.reset([operation retain]);
426   }
428   NSOperationQueue* queue = nil;
429   switch (operationType) {
430     case STASH:
431     case RESTORE:
432       queue = [CRWBrowsingDataStore operationQueueForStashAndRestoreOperations];
433       break;
434     case REMOVE:
435       queue = [CRWBrowsingDataStore operationQueueForRemoveOperations];
436       break;
437     default:
438       NOTREACHED();
439       break;
440   }
442   [self addOperation:operation toQueue:queue];
445 - (NSOperation*)
446     newOperationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
447                                 selector:(SEL)selector
448                        completionHandler:(ProceduralBlock)completionHandler {
449   NSBlockOperation* operation = [[NSBlockOperation alloc] init];
450   for (id<CRWBrowsingDataManager> manager : browsingDataManagers) {
451     // |addExecutionBlock| farms out the different blocks added to it. hence the
452     // operations are implicitly parallelized.
453     [operation addExecutionBlock:^{
454       [manager performSelector:selector];
455     }];
456   }
457   [operation setCompletionBlock:completionHandler];
458   return operation;
461 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue {
462   DCHECK([NSThread isMainThread]);
463   DCHECK(operation);
464   DCHECK(queue);
466   if (_lastDispatchedOperation) {
467     [operation addDependency:_lastDispatchedOperation];
468   }
469   _lastDispatchedOperation.reset([operation retain]);
470   [queue addOperation:operation];
473 @end