summaryrefslogtreecommitdiffstats
path: root/ios/web/crw_browsing_data_store.mm
blob: fe93382762318d3c8dab52f459d24553d7a34e1d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#import "ios/web/public/crw_browsing_data_store.h"

#import <Foundation/Foundation.h>

#include "base/ios/ios_util.h"
#import "base/ios/weak_nsobject.h"
#include "base/logging.h"
#import "base/mac/scoped_nsobject.h"
#import "ios/web/browsing_data_managers/crw_cookie_browsing_data_manager.h"
#include "ios/web/public/active_state_manager.h"
#include "ios/web/public/browser_state.h"

namespace {
// Represents the type of operations that a CRWBrowsingDataStore can perform.
enum OperationType {
  // Represents NOP.
  NONE = 1,
  // Stash operation involves stashing browsing data that web views create to
  // the associated BrowserState's state path.
  STASH,
  // Restore operation involves restoring browsing data from the
  // associated BrowserState's state path so that web views can read from them.
  RESTORE,
  // Remove operation involves removing the data that web views create.
  REMOVE,
};

// The name of the NSOperation that performs a |RESTORE| operation.
NSString* const kRestoreOperationName = @"CRWBrowsingDataStore.RESTORE";
// The name of the NSOperation that performs a |STASH| operation.
NSString* const kStashOperationName = @"CRWBrowsingDataStore.STASH";
}  // namespace

// CRWBrowsingDataStore is implemented using 2 queues.
// 1) operationQueueForStashAndRestoreOperations:
// - This queue is used to perform |STASH| and |RESTORE| operations.
// - |STASH|, |RESTORE| operations are operations that have been explicitly
//   requested by the user. And the performance of these operations block
//   further user interaction. Hence this queue has a QoS of
//   NSQualityOfServiceUserInitiated.
// - |STASH|, |RESTORE| operations from 2 different CRWBrowsingDataStores are
//    not re-entrant. Hence this is a serial queue.
// 2) operationQueueForRemoveOperations:
// - This queue is used to perform |REMOVE| operations.
// - |REMOVE| operations is an operation that has been explicitly requested by
//   the user. And the performance of this operation blocks further user
//   interaction. Hence this queue has a QoS of NSQualityOfServiceUserInitiated.
// - |REMOVE| operations from 2 different CRWBrowingDataStores can be run in
//   parallel. Hence this is a concurrent queue.
//
// |STASH|, |RESTORE|, |REMOVE| operations of a particular CRWBrowsingDataStore
// are not re-entrant. Hence these operations are serialized.

@interface CRWBrowsingDataStore ()
// Redefined to be read-write. Must be called from the main thread.
@property(nonatomic, assign) web::BrowsingDataStoreMode mode;
// The array of all browsing data managers. Must be called from the main
// thread.
@property(nonatomic, readonly) NSArray* allBrowsingDataManagers;
// The number of |STASH| or |RESTORE| operations that are still pending. Must be
// called from the main thread.
@property(nonatomic, assign) NSUInteger numberOfPendingStashOrRestoreOperations;

// Returns a serial queue on which |STASH| and |RESTORE| operations can be
// scheduled to be run. All |STASH|/|RESTORE| operations need to be run on the
// same queue hence it is shared with all CRWBrowsingDataStores.
+ (NSOperationQueue*)operationQueueForStashAndRestoreOperations;
// Returns a concurrent queue on which remove operations can be scheduled to be
// run. All |REMOVE| operations need to be run on the same queue hence it is
// shared with all CRWBrowsingDataStores.
+ (NSOperationQueue*)operationQueueForRemoveOperations;

// Returns an array of CRWBrowsingDataManagers for the given
// |browsingDataTypes|.
- (NSArray*)browsingDataManagersForBrowsingDataTypes:
    (web::BrowsingDataTypes)browsingDataTypes;
// Returns the selector that needs to be performed on the
// CRWBrowsingDataManagers for the |operationType|. |operationType| cannot be
// |NONE|.
- (SEL)browsingDataManagerSelectorForOperationType:(OperationType)operationType;
// Returns the selector that needs to be performed on the
// CRWBrowsingDataManagers for a |REMOVE| operation.
- (SEL)browsingDataManagerSelectorForRemoveOperationType;

// Sets the mode iff there are no more |STASH| or |RESTORE| operations that are
// still pending. |mode| can only be either |ACTIVE| or |INACTIVE|.
// Returns YES if the mode was successfully changed to |mode|.
- (BOOL)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode;
// Changes the mode of the CRWBrowsingDataStore to |mode|. This is an
// asynchronous operation and the mode is not changed immediately.
// |completionHandler| can be nil.
// |completionHandler| is called on the main thread. This block has no return
// value and takes a single BOOL argument that indicates whether or not the
// mode change was successfully changed to |mode|.
- (void)changeMode:(web::BrowsingDataStoreMode)mode
    completionHandler:(void (^)(BOOL modeChangeWasSuccessful))completionHandler;

// Returns the OperationType (that needs to be performed) in order to change the
// mode to |mode|. Consults the delegate if one is present. |mode| cannot be
// |CHANGING|.
- (OperationType)operationTypeToChangeMode:(web::BrowsingDataStoreMode)mode;
// Performs an operation of type |operationType| on each of the
// |browsingDataManagers|. |operationType| cannot be |NONE|.
// Precondition: There must be no web views associated with the BrowserState.
// |completionHandler| is called on the main thread and cannot be nil.
- (void)performOperationWithType:(OperationType)operationType
            browsingDataManagers:(NSArray*)browsingDataManagers
               completionHandler:(ProceduralBlock)completionHandler;
// Returns an NSOperation that calls |selector| on all the
// |browsingDataManagers|. |selector| needs to be one of the methods in
// CRWBrowsingDataManager.
- (NSOperation*)operationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
                                         selector:(SEL)selector;
// Enqueues |operation| to be run on |queue|. All operations are serialized to
// be run one after another.
- (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue;
@end

@implementation CRWBrowsingDataStore {
  web::BrowserState* _browserState;  // Weak, owns this object.
  // The delegate.
  base::WeakNSProtocol<id<CRWBrowsingDataStoreDelegate>> _delegate;
  // The mode of the CRWBrowsingDataStore.
  web::BrowsingDataStoreMode _mode;
  // The dictionary that maps a browsing data type to its
  // CRWBrowsingDataManager.
  base::scoped_nsobject<NSDictionary> _browsingDataTypeMap;
  // The last operation that was enqueued to be run. Can be a |STASH|, |RESTORE|
  // or a |REMOVE| operation.
  base::scoped_nsobject<NSOperation> _lastDispatchedOperation;
  // The last dispatched |STASH| or |RESTORE| operation that was enqueued to be
  // run.
  base::scoped_nsobject<NSOperation> _lastDispatchedStashOrRestoreOperation;
  // The number of |STASH| or |RESTORE| operations that are still pending. If
  // the number of |STASH| or |RESTORE| operations is greater than 0U, the mode
  // of the CRWBrowsingDataStore is |CHANGING|. The mode can be made ACTIVE or
  // INACTIVE only be set when this value is 0U.
  NSUInteger _numberOfPendingStashOrRestoreOperations;
}

#pragma mark -
#pragma mark NSObject Methods

- (instancetype)initWithBrowserState:(web::BrowserState*)browserState {
  self = [super init];
  if (self) {
    DCHECK([NSThread isMainThread]);
    DCHECK(browserState);
    _browserState = browserState;
    web::ActiveStateManager* activeStateManager =
        web::BrowserState::GetActiveStateManager(browserState);
    DCHECK(activeStateManager);
    _mode = activeStateManager->IsActive() ? web::ACTIVE : web::INACTIVE;
    base::scoped_nsobject<CRWCookieBrowsingDataManager>
        cookieBrowsingDataManager([[CRWCookieBrowsingDataManager alloc]
            initWithBrowserState:browserState]);
    _browsingDataTypeMap.reset([@{
      @(web::BROWSING_DATA_TYPE_COOKIES) : cookieBrowsingDataManager,
    } retain]);
  }
  return self;
}

- (instancetype)init {
  NOTREACHED();
  return nil;
}

- (NSString*)description {
  NSString* format = @"<%@: %p; hasPendingOperations = { %@ }; mode = { %@ }>";
  NSString* hasPendingOperationsString =
      [self hasPendingOperations] ? @"YES" : @"NO";
  NSString* modeString = nil;
  switch (self.mode) {
    case web::ACTIVE:
      modeString = @"ACTIVE";
      break;
    case web::CHANGING:
      modeString = @"CHANGING";
      break;
    case web::INACTIVE:
      modeString = @"INACTIVE";
      break;
  }
  NSString* result = [NSString stringWithFormat:format,
      NSStringFromClass(self.class), self, hasPendingOperationsString,
      modeString];
  return result;
}

+ (BOOL)automaticallyNotifiesObserversForKey:(NSString*)key {
  // It is necessary to override this for |mode| because the default KVO
  // behavior in NSObject is to fire a notification irrespective of if an actual
  // change was made to the ivar or not. The |mode| property needs fine grained
  // control over the actual notifications being fired since observers need to
  // be notified iff the |mode| actually changed.
  if ([key isEqual:@"mode"]) {
    return NO;
  }
  return [super automaticallyNotifiesObserversForKey:key];
}

#pragma mark -
#pragma mark Public Properties

- (id<CRWBrowsingDataStoreDelegate>)delegate {
  return _delegate;
}

- (void)setDelegate:(id<CRWBrowsingDataStoreDelegate>)delegate {
  _delegate.reset(delegate);
}

- (web::BrowsingDataStoreMode)mode {
  DCHECK([NSThread isMainThread]);
  return _mode;
}

- (BOOL)hasPendingOperations {
  if (!_lastDispatchedOperation) {
    return NO;
  }
  return ![_lastDispatchedOperation isFinished];
}

#pragma mark -
#pragma mark Public Methods

- (void)makeActiveWithCompletionHandler:
    (void (^)(BOOL success))completionHandler {
  DCHECK([NSThread isMainThread]);

  [self changeMode:web::ACTIVE completionHandler:completionHandler];
}

- (void)makeInactiveWithCompletionHandler:
    (void (^)(BOOL success))completionHandler {
  DCHECK([NSThread isMainThread]);

  [self changeMode:web::INACTIVE completionHandler:completionHandler];
}

- (void)removeDataOfTypes:(web::BrowsingDataTypes)browsingDataTypes
        completionHandler:(ProceduralBlock)completionHandler {
  DCHECK([NSThread isMainThread]);

  NSArray* browsingDataManagers =
      [self browsingDataManagersForBrowsingDataTypes:browsingDataTypes];
  [self performOperationWithType:REMOVE
            browsingDataManagers:browsingDataManagers
               completionHandler:completionHandler];
}

#pragma mark -
#pragma mark Private Properties

- (void)setMode:(web::BrowsingDataStoreMode)mode {
  DCHECK([NSThread isMainThread]);

  if (_mode == mode) {
    return;
  }
  if (mode == web::ACTIVE || mode == web::INACTIVE) {
    DCHECK(!self.numberOfPendingStashOrRestoreOperations);
  }
  [self willChangeValueForKey:@"mode"];
  _mode = mode;
  [self didChangeValueForKey:@"mode"];
}

- (NSArray*)allBrowsingDataManagers {
  DCHECK([NSThread isMainThread]);
  return [_browsingDataTypeMap allValues];
}

- (NSUInteger)numberOfPendingStashOrRestoreOperations {
  DCHECK([NSThread isMainThread]);
  return _numberOfPendingStashOrRestoreOperations;
}

- (void)setNumberOfPendingStashOrRestoreOperations:
    (NSUInteger)numberOfPendingStashOrRestoreOperations {
  DCHECK([NSThread isMainThread]);
  _numberOfPendingStashOrRestoreOperations =
      numberOfPendingStashOrRestoreOperations;
}

#pragma mark -
#pragma mark Private Class Methods

+ (NSOperationQueue*)operationQueueForStashAndRestoreOperations {
  static dispatch_once_t onceToken = 0;
  static NSOperationQueue* operationQueueForStashAndRestoreOperations = nil;
  dispatch_once(&onceToken, ^{
    operationQueueForStashAndRestoreOperations =
        [[NSOperationQueue alloc] init];
    [operationQueueForStashAndRestoreOperations
        setMaxConcurrentOperationCount:1U];
    if (base::ios::IsRunningOnIOS8OrLater()) {
      [operationQueueForStashAndRestoreOperations
          setQualityOfService:NSQualityOfServiceUserInitiated];
    }
  });
  return operationQueueForStashAndRestoreOperations;
}

+ (NSOperationQueue*)operationQueueForRemoveOperations {
  static dispatch_once_t onceToken = 0;
  static NSOperationQueue* operationQueueForRemoveOperations = nil;
  dispatch_once(&onceToken, ^{
    operationQueueForRemoveOperations = [[NSOperationQueue alloc] init];
    [operationQueueForRemoveOperations
        setMaxConcurrentOperationCount:NSUIntegerMax];
    if (base::ios::IsRunningOnIOS8OrLater()) {
      [operationQueueForRemoveOperations
          setQualityOfService:NSQualityOfServiceUserInitiated];
    }
  });
  return operationQueueForRemoveOperations;
}

#pragma mark -
#pragma mark Private Methods

- (NSArray*)browsingDataManagersForBrowsingDataTypes:
    (web::BrowsingDataTypes)browsingDataTypes {
  __block NSMutableArray* result = [NSMutableArray array];
  [_browsingDataTypeMap
      enumerateKeysAndObjectsUsingBlock:^(NSNumber* dataType,
                                          id<CRWBrowsingDataManager> manager,
                                          BOOL*) {
        if ([dataType unsignedIntegerValue] & browsingDataTypes) {
          [result addObject:manager];
        }
      }];
  return result;
}

- (SEL)browsingDataManagerSelectorForOperationType:
    (OperationType)operationType {
  switch (operationType) {
    case NONE:
      NOTREACHED();
      return nullptr;
    case STASH:
      return @selector(stashData);
    case RESTORE:
      return @selector(restoreData);
    case REMOVE:
      return [self browsingDataManagerSelectorForRemoveOperationType];
  };
}

- (SEL)browsingDataManagerSelectorForRemoveOperationType {
  if (self.mode == web::ACTIVE) {
    return @selector(removeDataAtCanonicalPath);
  }
  if (self.mode == web::INACTIVE) {
    return @selector(removeDataAtStashPath);
  }
  // Since the mode is |CHANGING|, find the last |STASH| or |RESTORE| operation
  // that was enqueued in order to find out the eventual mode that the
  // CRWBrowsingDataStore will be in when this |REMOVE| operation is run.
  DCHECK(_lastDispatchedStashOrRestoreOperation);
  NSString* lastDispatchedStashOrRestoreOperationName =
      [_lastDispatchedStashOrRestoreOperation name];
  if ([lastDispatchedStashOrRestoreOperationName
          isEqual:kRestoreOperationName]) {
    return @selector(removeDataAtCanonicalPath);
  }
  if ([lastDispatchedStashOrRestoreOperationName isEqual:kStashOperationName]) {
    return @selector(removeDataAtStashPath);
  }
  NOTREACHED();
  return nullptr;
}

- (BOOL)finalizeChangeToMode:(web::BrowsingDataStoreMode)mode {
  DCHECK([NSThread isMainThread]);
  DCHECK_NE(web::CHANGING, mode);

  BOOL modeChangeWasSuccessful = NO;
  if (!self.numberOfPendingStashOrRestoreOperations) {
    [self setMode:mode];
    modeChangeWasSuccessful = YES;
  }
  return modeChangeWasSuccessful;
}

- (void)changeMode:(web::BrowsingDataStoreMode)mode
    completionHandler:
        (void (^)(BOOL modeChangeWasSuccessful))completionHandler {
  DCHECK([NSThread isMainThread]);

  ProceduralBlock completionHandlerAfterPerformingOperation = ^{
    BOOL modeChangeWasSuccessful = [self finalizeChangeToMode:mode];
    if (completionHandler) {
      completionHandler(modeChangeWasSuccessful);
    }
  };

  OperationType operationType = [self operationTypeToChangeMode:mode];
  if (operationType == NONE) {
    // As a caller of this API, it is awkward to get the callback before the
    // method call has completed, hence defer it.
    dispatch_async(dispatch_get_main_queue(),
                   completionHandlerAfterPerformingOperation);
  } else {
    [self performOperationWithType:operationType
              browsingDataManagers:[self allBrowsingDataManagers]
                 completionHandler:completionHandlerAfterPerformingOperation];
  }
}

- (OperationType)operationTypeToChangeMode:(web::BrowsingDataStoreMode)mode {
  DCHECK_NE(web::CHANGING, mode);

  OperationType operationType = NONE;
  if (mode == self.mode) {
    // Already in the desired mode.
    operationType = NONE;
  } else if (mode == web::ACTIVE) {
    // By default a |RESTORE| operation is performed when the mode is changed
    // to |ACTIVE|.
    operationType = RESTORE;
    web::BrowsingDataStoreMakeActivePolicy makeActivePolicy =
        [_delegate decideMakeActiveOperationPolicyForBrowsingDataStore:self];
    operationType = (makeActivePolicy == web::ADOPT) ? REMOVE : RESTORE;
  } else {
    // By default a |STASH| operation is performed when the mode is changed to
    // |INACTIVE|.
    operationType = STASH;
    web::BrowsingDataStoreMakeInactivePolicy makeInactivePolicy =
        [_delegate decideMakeInactiveOperationPolicyForBrowsingDataStore:self];
    operationType = (makeInactivePolicy == web::DELETE) ? REMOVE : STASH;
  }
  return operationType;
}

- (void)performOperationWithType:(OperationType)operationType
            browsingDataManagers:(NSArray*)browsingDataManagers
               completionHandler:(ProceduralBlock)completionHandler {
  DCHECK([NSThread isMainThread]);
  DCHECK(completionHandler);
  DCHECK_NE(NONE, operationType);

  SEL selector =
      [self browsingDataManagerSelectorForOperationType:operationType];
  DCHECK(selector);

  if (operationType == RESTORE || operationType == STASH) {
    [self setMode:web::CHANGING];
    ++self.numberOfPendingStashOrRestoreOperations;
    completionHandler = ^{
      --self.numberOfPendingStashOrRestoreOperations;
      // It is safe to this and does not lead to the block (|completionHandler|)
      // retaining itself.
      completionHandler();
    };
  }

  ProceduralBlock callCompletionHandlerOnMainThread = ^{
    // This is called on a background thread, hence the need to bounce to the
    // main thread.
    dispatch_async(dispatch_get_main_queue(), completionHandler);
  };
  NSOperation* operation =
      [self operationWithBrowsingDataManagers:browsingDataManagers
                                     selector:selector];

  if (operationType == RESTORE || operationType == STASH) {
    [operation setName:(RESTORE ? kRestoreOperationName : kStashOperationName)];
    _lastDispatchedStashOrRestoreOperation.reset([operation retain]);
  }

  NSOperationQueue* queue = nil;
  switch (operationType) {
    case NONE:
      NOTREACHED();
      break;
    case STASH:
    case RESTORE:
      queue = [CRWBrowsingDataStore operationQueueForStashAndRestoreOperations];
      break;
    case REMOVE:
      queue = [CRWBrowsingDataStore operationQueueForRemoveOperations];
      break;
  }

  NSOperation* completionHandlerOperation = [NSBlockOperation
      blockOperationWithBlock:callCompletionHandlerOnMainThread];

  [self addOperation:operation toQueue:queue];
  [self addOperation:completionHandlerOperation toQueue:queue];
}

- (NSOperation*)operationWithBrowsingDataManagers:(NSArray*)browsingDataManagers
                                         selector:(SEL)selector {
  NSBlockOperation* operation = [[[NSBlockOperation alloc] init] autorelease];
  for (id<CRWBrowsingDataManager> manager : browsingDataManagers) {
    // |addExecutionBlock| farms out the different blocks added to it. hence the
    // operations are implicitly parallelized.
    [operation addExecutionBlock:^{
      [manager performSelector:selector];
    }];
  }
  return operation;
}

- (void)addOperation:(NSOperation*)operation toQueue:(NSOperationQueue*)queue {
  DCHECK([NSThread isMainThread]);
  DCHECK(operation);
  DCHECK(queue);

  if (_lastDispatchedOperation) {
    [operation addDependency:_lastDispatchedOperation];
  }
  _lastDispatchedOperation.reset([operation retain]);
  [queue addOperation:operation];
}

@end