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 (UIWebViews and
27 // WKWebViews) can read from them.
29 // Remove operation involves removing the data that web views create.
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";
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
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
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|.
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;
105 @implementation CRWBrowsingDataStore {
106 web::BrowserState* _browserState; // Weak, owns this object.
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
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];
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];
153 return operationQueueForRemoveOperations;
156 - (instancetype)initWithBrowserState:(web::BrowserState*)browserState {
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,
179 - (NSString*)description {
180 NSString* format = @"<%@: %p; hasPendingOperations = { %@ }>";
181 NSString* hasPendingOperationsString =
182 [self hasPendingOperations] ? @"YES" : @"NO";
184 [NSString stringWithFormat:format, NSStringFromClass(self.class), self,
185 hasPendingOperationsString];
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,
202 if ([dataType unsignedIntegerValue] & browsingDataTypes) {
203 [result addObject:manager];
209 - (id<CRWBrowsingDataStoreDelegate>)delegate {
213 - (void)setDelegate:(id<CRWBrowsingDataStoreDelegate>)delegate {
214 _delegate.reset(delegate);
217 - (SEL)browsingDataManagerSelectorForOperationType:
218 (OperationType)operationType {
219 switch (operationType) {
224 return @selector(stashData);
226 return @selector(restoreData);
228 return [self browsingDataManagerSelectorForRemoveOperationType];
234 - (SEL)browsingDataManagerSelectorForRemoveOperationType {
235 if (self.mode == ACTIVE) {
236 return @selector(removeDataAtCanonicalPath);
238 if (self.mode == INACTIVE) {
239 return @selector(removeDataAtStashPath);
241 DCHECK(_lastDispatchedStashOrRestoreOperation);
242 NSString* lastDispatchedStashOrRestoreOperationName =
243 [_lastDispatchedStashOrRestoreOperation name];
244 if ([lastDispatchedStashOrRestoreOperationName
245 isEqual:kRestoreOperationName]) {
246 return @selector(removeDataAtCanonicalPath);
248 if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) {
249 return @selector(removeDataAtStashPath);
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"]) {
264 return [super automaticallyNotifiesObserversForKey:(NSString*)key];
267 - (CRWBrowsingDataStoreMode)mode {
268 DCHECK([NSThread isMainThread]);
272 - (void)setMode:(CRWBrowsingDataStoreMode)mode {
273 DCHECK([NSThread isMainThread]);
277 if (mode == ACTIVE || mode == INACTIVE) {
278 DCHECK(!self.numberOfPendingStashOrRestoreOperations);
280 [self willChangeValueForKey:@"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) {
293 modeChangeWasSuccessful = YES;
296 handler(modeChangeWasSuccessful);
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
328 (void (^)(BOOL modeChangeWasSuccessful))completionHandler {
329 DCHECK([NSThread isMainThread]);
331 ProceduralBlock completionHandlerAfterPerformingOperation = ^{
332 [self finalizeChangeToMode:mode andCallCompletionHandler:completionHandler];
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();
345 OperationType operationType = NONE;
346 if (mode == ACTIVE) {
347 // By default a |RESTORE| operation is performed when the mode is changed
349 operationType = RESTORE;
350 web::BrowsingDataStoreMakeActivePolicy makeActivePolicy =
351 [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self];
352 operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE;
354 // By default a |STASH| operation is performed when the mode is changed to
356 operationType = STASH;
357 web::BrowsingDataStoreMakeInactivePolicy makeInactivePolicy =
358 [_delegate decideMakeInactiveOperationPolicyForBrowsingDataStore:self];
359 operationType = (makeInactivePolicy == web::DELETE) ? REMOVE : STASH;
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
376 // Since this may be called on a background thread, bounce to
378 dispatch_async(dispatch_get_main_queue(), completionHandler);
382 - (BOOL)hasPendingOperations {
383 if (!_lastDispatchedOperation) {
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);
397 [self browsingDataManagerSelectorForOperationType:operationType];
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|)
411 id callCompletionHandlerOnMainThread = ^{
412 // This is called on a background thread, hence the need to bounce to the
414 dispatch_async(dispatch_get_main_queue(), ^{
418 base::scoped_nsobject<NSOperation> operation([self
419 newOperationWithBrowsingDataManagers:browsingDataManagers
421 completionHandler:callCompletionHandlerOnMainThread]);
423 if (operationType == RESTORE || operationType == STASH) {
424 [operation setName:(RESTORE ? kRestoreOperationName : kStashOperationName)];
425 _lastDispatchedStashOrRestoreOperation.reset([operation retain]);
428 NSOperationQueue* queue = nil;
429 switch (operationType) {
432 queue = [CRWBrowsingDataStore operationQueueForStashAndRestoreOperations];
435 queue = [CRWBrowsingDataStore operationQueueForRemoveOperations];
442 [self addOperation:operation toQueue:queue];
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];
457 [operation setCompletionBlock:completionHandler];
461 - (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue {
462 DCHECK([NSThread isMainThread]);
466 if (_lastDispatchedOperation) {
467 [operation addDependency:_lastDispatchedOperation];
469 _lastDispatchedOperation.reset([operation retain]);
470 [queue addOperation:operation];