| Index: ios/chrome/browser/browsing_data/browsing_data_removal_controller.mm
|
| diff --git a/ios/chrome/browser/browsing_data/browsing_data_removal_controller.mm b/ios/chrome/browser/browsing_data/browsing_data_removal_controller.mm
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..a04ec4d416cd2c844f42be162f75d7b378474a7b
|
| --- /dev/null
|
| +++ b/ios/chrome/browser/browsing_data/browsing_data_removal_controller.mm
|
| @@ -0,0 +1,453 @@
|
| +// 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/chrome/browser/browsing_data/browsing_data_removal_controller.h"
|
| +
|
| +#import <WebKit/WebKit.h>
|
| +
|
| +#include <memory>
|
| +
|
| +#include "base/bind.h"
|
| +#include "base/bind_helpers.h"
|
| +#include "base/containers/hash_tables.h"
|
| +#import "base/ios/weak_nsobject.h"
|
| +#include "base/logging.h"
|
| +#import "base/mac/bind_objc_block.h"
|
| +#include "base/memory/ref_counted.h"
|
| +#include "components/open_from_clipboard/clipboard_recent_content.h"
|
| +#include "components/signin/ios/browser/account_consistency_service.h"
|
| +#include "ios/chrome/browser/browser_state/chrome_browser_state.h"
|
| +#include "ios/chrome/browser/browsing_data/browsing_data_remover_helper.h"
|
| +#include "ios/chrome/browser/browsing_data/ios_chrome_browsing_data_remover.h"
|
| +#include "ios/chrome/browser/callback_counter.h"
|
| +#include "ios/chrome/browser/sessions/session_util.h"
|
| +#include "ios/chrome/browser/signin/account_consistency_service_factory.h"
|
| +#import "ios/chrome/browser/snapshots/snapshots_util.h"
|
| +#import "ios/chrome/browser/ui/browser_view_controller.h"
|
| +#include "ios/public/provider/chrome/browser/chrome_browser_provider.h"
|
| +#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metadata.h"
|
| +#import "ios/public/provider/chrome/browser/native_app_launcher/native_app_whitelist_manager.h"
|
| +#include "ios/web/public/web_thread.h"
|
| +#import "ios/web/public/web_view_creation_util.h"
|
| +#import "ios/web/web_state/ui/wk_web_view_configuration_provider.h"
|
| +#include "net/cookies/cookie_store.h"
|
| +#include "net/ssl/channel_id_service.h"
|
| +#include "net/ssl/channel_id_store.h"
|
| +#include "net/url_request/url_request_context.h"
|
| +#include "net/url_request/url_request_context_getter.h"
|
| +
|
| +namespace {
|
| +// Empty callback used by DeleteAllCreatedBetweenAsync below.
|
| +void DoNothing(int n) {}
|
| +}
|
| +
|
| +@interface BrowsingDataRemovalController ()
|
| +// Removes data used by the Google App Launcher.
|
| +- (void)removeGALData;
|
| +// Removes browsing data that is created by web views associated with
|
| +// |browserState|. |mask| is obtained from
|
| +// IOSChromeBrowsingDataRemover::RemoveDataMask. |deleteBegin| defines the begin
|
| +// time from which the data has to be removed, up to the present time.
|
| +// |completionHandler| is called when this operation finishes. This method
|
| +// finishes removal of the browsing data even if |browserState| is destroyed
|
| +// after this method call.
|
| +- (void)removeWebViewCreatedBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:(base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler;
|
| +// Removes browsing data that is created by WKWebViews associated with
|
| +// |browserState|. |browserState| must not be off the record. |mask| is obtained
|
| +// from IOSChromeBrowsingDataRemover::RemoveDataMask. |deleteBegin| defines the
|
| +// begin time from which the data has to be removed, up to the present time.
|
| +// |completionHandler| is called when this operation finishes. This method
|
| +// finishes removal of the browsing data even if |browserState| is destroyed
|
| +// after this method call.
|
| +// Note: This method works only on iOS9+.
|
| +- (void)removeWKWebViewCreatedBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:
|
| + (base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler;
|
| +
|
| +// Removes browsing data associated with |browserState| that is specific to iOS
|
| +// and not removed when the |browserState| is destroyed.
|
| +// |mask| is obtained from IOSChromeBrowsingDataRemover::RemoveDataMask
|
| +// |deleteBegin| defines the begin time from which the data has to be removed.
|
| +// |browserState| cannot be null. |completionHandler| is called when
|
| +// this operation finishes. This method finishes removal of the browsing data
|
| +// even if |browserState| is destroyed after this method call.
|
| +- (void)removeIOSSpecificBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:(base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler;
|
| +// Removes browsing data from |browserState| that is persisted on disk.
|
| +// |mask| is obtained from IOSChromeBrowsingDataRemover::RemoveDataMask.
|
| +// |browserState| cannot be null and must be off the record.
|
| +// This method finishes removal of the browsing data even if |browserState| is
|
| +// destroyed after this method call.
|
| +- (void)removeIOSSpecificPersistentIncognitoDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask;
|
| +
|
| +// Increments the count of pending removal operations for |browserState|.
|
| +// Called when any removal operation for |browserState| starts.
|
| +- (void)incrementPendingRemovalCountForBrowserState:
|
| + (ios::ChromeBrowserState*)browserState;
|
| +// Decrements the count of pending removal operations for |browserState|.
|
| +// Called when a removal operation for |browserState| finishes.
|
| +- (void)decrementPendingRemovalCountForBrowserState:
|
| + (ios::ChromeBrowserState*)browserState;
|
| +@end
|
| +
|
| +@implementation BrowsingDataRemovalController {
|
| + // Wrapper around IOSChromeBrowsingDataRemover that serializes removal
|
| + // operations.
|
| + std::unique_ptr<BrowsingDataRemoverHelper> _browsingDataRemoverHelper;
|
| + // The delegate.
|
| + base::WeakNSProtocol<id<BrowsingDataRemovalControllerDelegate>> _delegate;
|
| + // A map that tracks the number of pending removals for a given
|
| + // ChromeBrowserState.
|
| + base::hash_map<ios::ChromeBrowserState*, int> _pendingRemovalCount;
|
| +}
|
| +
|
| +- (instancetype)initWithDelegate:
|
| + (id<BrowsingDataRemovalControllerDelegate>)delegate {
|
| + if ((self = [super init])) {
|
| + DCHECK(delegate);
|
| + _browsingDataRemoverHelper.reset(new BrowsingDataRemoverHelper());
|
| + _delegate.reset(delegate);
|
| + }
|
| + return self;
|
| +}
|
| +
|
| +- (instancetype)init {
|
| + NOTREACHED();
|
| + return nil;
|
| +}
|
| +
|
| +- (void)removeBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + timePeriod:(browsing_data::TimePeriod)timePeriod
|
| + completionHandler:(ProceduralBlock)completionHandler {
|
| + DCHECK(browserState);
|
| + DLOG_IF(WARNING, !mask) << "Nothing to remove!";
|
| + // Cookies and server bound certificates should have the same lifetime.
|
| + DCHECK_EQ((mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) != 0,
|
| + (mask & IOSChromeBrowsingDataRemover::REMOVE_CHANNEL_IDS) != 0);
|
| +
|
| + [self incrementPendingRemovalCountForBrowserState:browserState];
|
| +
|
| + ProceduralBlock browsingDataCleared = ^{
|
| + [self decrementPendingRemovalCountForBrowserState:browserState];
|
| + if (AccountConsistencyService* accountConsistencyService =
|
| + ios::AccountConsistencyServiceFactory::GetForBrowserState(
|
| + browserState)) {
|
| + accountConsistencyService->OnBrowsingDataRemoved();
|
| + }
|
| + if (completionHandler) {
|
| + completionHandler();
|
| + }
|
| + };
|
| +
|
| + scoped_refptr<CallbackCounter> callbackCounter =
|
| + new CallbackCounter(base::BindBlock(browsingDataCleared));
|
| + ProceduralBlock decrementCallbackCounterCount = ^{
|
| + callbackCounter->DecrementCount();
|
| + };
|
| +
|
| + callbackCounter->IncrementCount();
|
| + base::Time beginDeleteTime =
|
| + browsing_data::CalculateBeginDeleteTime(timePeriod);
|
| + [self removeIOSSpecificBrowsingDataFromBrowserState:browserState
|
| + mask:mask
|
| + deleteBegin:beginDeleteTime
|
| + completionHandler:
|
| + decrementCallbackCounterCount];
|
| +
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_DOWNLOADS) {
|
| + DCHECK_EQ(browsing_data::ALL_TIME, timePeriod)
|
| + << "Partial clearing not supported";
|
| + callbackCounter->IncrementCount();
|
| + [_delegate
|
| + removeExternalFilesForBrowserState:browserState
|
| + completionHandler:decrementCallbackCounterCount];
|
| + }
|
| +
|
| + if (!browserState->IsOffTheRecord()) {
|
| + callbackCounter->IncrementCount();
|
| + _browsingDataRemoverHelper->Remove(browserState, mask, timePeriod,
|
| + base::BindBlock(^{
|
| + callbackCounter->DecrementCount();
|
| + }));
|
| + }
|
| +}
|
| +
|
| +- (void)removeIOSSpecificIncognitoBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + completionHandler:
|
| + (ProceduralBlock)
|
| + completionHandler {
|
| + DCHECK(browserState && browserState->IsOffTheRecord());
|
| + [self removeIOSSpecificBrowsingDataFromBrowserState:browserState
|
| + mask:mask
|
| + deleteBegin:base::Time()
|
| + completionHandler:completionHandler];
|
| +}
|
| +
|
| +- (void)removeIOSSpecificBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:(base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler {
|
| + DCHECK(browserState);
|
| + [self incrementPendingRemovalCountForBrowserState:browserState];
|
| +
|
| + ProceduralBlock browsingDataCleared = ^{
|
| + [self decrementPendingRemovalCountForBrowserState:browserState];
|
| + if (completionHandler) {
|
| + completionHandler();
|
| + }
|
| + };
|
| +
|
| + // Note: Before adding any method below, make sure that it can finish clearing
|
| + // browsing data even when |browserState| is destroyed after this method call.
|
| +
|
| + // If deleting history, clear visited links.
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_HISTORY) {
|
| + if (!browserState->IsOffTheRecord()) {
|
| + ClipboardRecentContent::GetInstance()->SuppressClipboardContent();
|
| + session_util::DeleteLastSession(browserState);
|
| + }
|
| + // Remove the screenshots taken by the system when backgrounding the
|
| + // application. Partial removal based on timePeriod is not required.
|
| + ClearIOSSnapshots();
|
| + }
|
| +
|
| + // TODO(crbug.com/227636): Support multiple profile.
|
| + // Google App Launcher data is tied to the normal profile.
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_GOOGLE_APP_LAUNCHER_DATA &&
|
| + !browserState->IsOffTheRecord()) {
|
| + [self removeGALData];
|
| + }
|
| +
|
| + if (browserState->IsOffTheRecord()) {
|
| + // In incognito, only data removal for all time is currently supported.
|
| + DCHECK_EQ(base::Time(), deleteBegin);
|
| + [self removeIOSSpecificPersistentIncognitoDataFromBrowserState:browserState
|
| + mask:mask];
|
| + }
|
| +
|
| + [self removeWebViewCreatedBrowsingDataFromBrowserState:browserState
|
| + mask:mask
|
| + deleteBegin:deleteBegin
|
| + completionHandler:browsingDataCleared];
|
| +}
|
| +
|
| +- (void)removeGALData {
|
| + [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager()
|
| + filteredAppsUsingBlock:^BOOL(const id<NativeAppMetadata> app,
|
| + BOOL* stop) {
|
| + [app resetInfobarHistory];
|
| + return NO;
|
| + }];
|
| +}
|
| +
|
| +- (void)removeWebViewCreatedBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:(base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler {
|
| + // TODO(crbug.com/480654): Remove this check once browsing data partitioning
|
| + // between BrowserStates is achieved.
|
| + if (browserState->IsOffTheRecord()) {
|
| + if (completionHandler) {
|
| + dispatch_async(dispatch_get_main_queue(), completionHandler);
|
| + }
|
| + return;
|
| + }
|
| + scoped_refptr<CallbackCounter> callbackCounter =
|
| + new CallbackCounter(base::BindBlock(completionHandler ? completionHandler
|
| + : ^{
|
| + }));
|
| +
|
| + // Note: Before adding any method below, make sure that it can finish clearing
|
| + // browsing data even when |browserState| is destroyed after this method call.
|
| +
|
| + callbackCounter->IncrementCount();
|
| + [self removeWKWebViewCreatedBrowsingDataFromBrowserState:browserState
|
| + mask:mask
|
| + deleteBegin:deleteBegin
|
| + completionHandler:^{
|
| + callbackCounter->DecrementCount();
|
| + }];
|
| +}
|
| +
|
| +- (void)
|
| +removeWKWebViewCreatedBrowsingDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask
|
| + deleteBegin:(base::Time)deleteBegin
|
| + completionHandler:
|
| + (ProceduralBlock)completionHandler {
|
| + scoped_refptr<CallbackCounter> callbackCounter =
|
| + new CallbackCounter(base::BindBlock(completionHandler ? completionHandler
|
| + : ^{
|
| + }));
|
| + ProceduralBlock decrementCallbackCounterCount = ^{
|
| + callbackCounter->DecrementCount();
|
| + };
|
| +
|
| + // Converts browsing data types from
|
| + // IOSChromeBrowsingDataRemover::RemoveDataMask to
|
| + // WKWebsiteDataStore strings.
|
| + base::scoped_nsobject<NSMutableSet> dataTypesToRemove(
|
| + [[NSMutableSet alloc] init]);
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_CACHE_STORAGE) {
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeDiskCache];
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeMemoryCache];
|
| + }
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_APPCACHE) {
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeOfflineWebApplicationCache];
|
| + }
|
| + WKWebView* markerWKWebView = nil;
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) {
|
| + // TODO(crbug.com/661630): This approach of creating a WKWebView to clear
|
| + // cookies is a workaround for
|
| + // https://bugs.webkit.org/show_bug.cgi?id=149078. Remove this, when that
|
| + // bug is fixed. Note: This WKWebView will be released when cookies have
|
| + // been cleared.
|
| + markerWKWebView = web::BuildWKWebView(CGRectZero, browserState);
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeCookies];
|
| + }
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_LOCAL_STORAGE) {
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeSessionStorage];
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeLocalStorage];
|
| + }
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_WEBSQL) {
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeWebSQLDatabases];
|
| + }
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_INDEXEDDB) {
|
| + [dataTypesToRemove addObject:WKWebsiteDataTypeIndexedDBDatabases];
|
| + }
|
| +
|
| + ProceduralBlock afterRemovalFromWKWebsiteDataStore = ^{
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_VISITED_LINKS) {
|
| + // TODO(crbug.com/557963): Purging the WKProcessPool is a workaround for
|
| + // the fact that there is no public API to clear visited links in
|
| + // WKWebView. Remove this workaround if/when that API is made public.
|
| + // Note: Purging the WKProcessPool for clearing visisted links does have
|
| + // the side-effect of also losing the in-memory cookies of WKWebView but
|
| + // it is not a problem in practice since there is no UI to only have
|
| + // visited links be removed but not cookies.
|
| + DCHECK(mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES);
|
| + web::WKWebViewConfigurationProvider::FromBrowserState(browserState)
|
| + .Purge();
|
| + }
|
| +
|
| + decrementCallbackCounterCount();
|
| + };
|
| +
|
| + if ([dataTypesToRemove count]) {
|
| + callbackCounter->IncrementCount();
|
| + ProceduralBlock removeFromWKWebsiteDataStore = ^{
|
| + NSDate* beginDeleteDate =
|
| + [NSDate dateWithTimeIntervalSince1970:deleteBegin.ToDoubleT()];
|
| + [[WKWebsiteDataStore defaultDataStore]
|
| + removeDataOfTypes:dataTypesToRemove
|
| + modifiedSince:beginDeleteDate
|
| + completionHandler:afterRemovalFromWKWebsiteDataStore];
|
| + };
|
| +
|
| + if (markerWKWebView) {
|
| + // TODO(crbug.com/661630): Executing JS enables the markerWKWebView to
|
| + // connect to the Networking process. This is so that the
|
| + // -[WKWebsiteDataStore removeDataOfTypes:] API is able to send an IPC
|
| + // message to the Networking process to clear cookies. This has been
|
| + // reverse-engineered by code inspection on the WebKit2 source code and is
|
| + // an undocumented workaround for
|
| + // https://bugs.webkit.org/show_bug.cgi?id=149078. Remove it, when that
|
| + // bug is fixed.
|
| + [markerWKWebView evaluateJavaScript:@""
|
| + completionHandler:^(id, NSError*) {
|
| + removeFromWKWebsiteDataStore();
|
| + }];
|
| + } else {
|
| + removeFromWKWebsiteDataStore();
|
| + }
|
| + }
|
| +
|
| + // This is to ensure that the caller of this API still gets a callback even
|
| + // when none of the masks matched.
|
| + callbackCounter->IncrementCount();
|
| + dispatch_async(dispatch_get_main_queue(), decrementCallbackCounterCount);
|
| +}
|
| +
|
| +- (void)removeIOSSpecificPersistentIncognitoDataFromBrowserState:
|
| + (ios::ChromeBrowserState*)browserState
|
| + mask:(int)mask {
|
| + DCHECK(browserState && browserState->IsOffTheRecord());
|
| + // Note: Before adding any method below, make sure that it can finish clearing
|
| + // browsing data even when |browserState| is destroyed after this method call.
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_HISTORY) {
|
| + session_util::DeleteLastSession(browserState);
|
| + }
|
| +
|
| + // Cookies and server bound certificates should have the same lifetime.
|
| + DCHECK_EQ((mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) != 0,
|
| + (mask & IOSChromeBrowsingDataRemover::REMOVE_CHANNEL_IDS) != 0);
|
| + if (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) {
|
| + scoped_refptr<net::URLRequestContextGetter> contextGetter =
|
| + browserState->GetRequestContext();
|
| + base::Closure callback = base::BindBlock(^{
|
| + });
|
| + web::WebThread::PostTask(
|
| + web::WebThread::IO, FROM_HERE, base::BindBlock(^{
|
| + net::URLRequestContext* requestContext =
|
| + contextGetter->GetURLRequestContext();
|
| + net::ChannelIDService* channelIdService =
|
| + requestContext->channel_id_service();
|
| + DCHECK(channelIdService);
|
| + DCHECK(channelIdService->GetChannelIDStore());
|
| + channelIdService->GetChannelIDStore()->DeleteAll(callback);
|
| + DCHECK(requestContext->cookie_store());
|
| + requestContext->cookie_store()->DeleteAllCreatedBetweenAsync(
|
| + base::Time(), base::Time(), base::Bind(&DoNothing));
|
| + }));
|
| + }
|
| +}
|
| +
|
| +- (void)incrementPendingRemovalCountForBrowserState:
|
| + (ios::ChromeBrowserState*)browserState {
|
| + ++_pendingRemovalCount[browserState];
|
| +}
|
| +
|
| +- (void)decrementPendingRemovalCountForBrowserState:
|
| + (ios::ChromeBrowserState*)browserState {
|
| + if (_pendingRemovalCount.find(browserState) != _pendingRemovalCount.end()) {
|
| + --_pendingRemovalCount[browserState];
|
| + if (!_pendingRemovalCount[browserState]) {
|
| + _pendingRemovalCount.erase(browserState);
|
| + }
|
| + }
|
| +}
|
| +
|
| +- (BOOL)hasPendingRemovalOperations:(ios::ChromeBrowserState*)browserState {
|
| + return _pendingRemovalCount[browserState] != 0;
|
| +}
|
| +
|
| +- (void)browserStateDestroyed:(ios::ChromeBrowserState*)browserState {
|
| + _pendingRemovalCount.erase(browserState);
|
| +}
|
| +
|
| +@end
|
|
|