OLD | NEW |
(Empty) | |
| 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. |
| 4 |
| 5 #import "ios/chrome/browser/browsing_data/browsing_data_removal_controller.h" |
| 6 |
| 7 #import <WebKit/WebKit.h> |
| 8 |
| 9 #include <memory> |
| 10 |
| 11 #include "base/bind.h" |
| 12 #include "base/bind_helpers.h" |
| 13 #include "base/containers/hash_tables.h" |
| 14 #import "base/ios/weak_nsobject.h" |
| 15 #include "base/logging.h" |
| 16 #import "base/mac/bind_objc_block.h" |
| 17 #include "base/memory/ref_counted.h" |
| 18 #include "components/open_from_clipboard/clipboard_recent_content.h" |
| 19 #include "components/signin/ios/browser/account_consistency_service.h" |
| 20 #include "ios/chrome/browser/browser_state/chrome_browser_state.h" |
| 21 #include "ios/chrome/browser/browsing_data/browsing_data_remover_helper.h" |
| 22 #include "ios/chrome/browser/browsing_data/ios_chrome_browsing_data_remover.h" |
| 23 #include "ios/chrome/browser/callback_counter.h" |
| 24 #include "ios/chrome/browser/sessions/session_util.h" |
| 25 #include "ios/chrome/browser/signin/account_consistency_service_factory.h" |
| 26 #import "ios/chrome/browser/snapshots/snapshots_util.h" |
| 27 #import "ios/chrome/browser/ui/browser_view_controller.h" |
| 28 #include "ios/public/provider/chrome/browser/chrome_browser_provider.h" |
| 29 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_metad
ata.h" |
| 30 #import "ios/public/provider/chrome/browser/native_app_launcher/native_app_white
list_manager.h" |
| 31 #include "ios/web/public/web_thread.h" |
| 32 #import "ios/web/public/web_view_creation_util.h" |
| 33 #import "ios/web/web_state/ui/wk_web_view_configuration_provider.h" |
| 34 #include "net/cookies/cookie_store.h" |
| 35 #include "net/ssl/channel_id_service.h" |
| 36 #include "net/ssl/channel_id_store.h" |
| 37 #include "net/url_request/url_request_context.h" |
| 38 #include "net/url_request/url_request_context_getter.h" |
| 39 |
| 40 namespace { |
| 41 // Empty callback used by DeleteAllCreatedBetweenAsync below. |
| 42 void DoNothing(int n) {} |
| 43 } |
| 44 |
| 45 @interface BrowsingDataRemovalController () |
| 46 // Removes data used by the Google App Launcher. |
| 47 - (void)removeGALData; |
| 48 // Removes browsing data that is created by web views associated with |
| 49 // |browserState|. |mask| is obtained from |
| 50 // IOSChromeBrowsingDataRemover::RemoveDataMask. |deleteBegin| defines the begin |
| 51 // time from which the data has to be removed, up to the present time. |
| 52 // |completionHandler| is called when this operation finishes. This method |
| 53 // finishes removal of the browsing data even if |browserState| is destroyed |
| 54 // after this method call. |
| 55 - (void)removeWebViewCreatedBrowsingDataFromBrowserState: |
| 56 (ios::ChromeBrowserState*)browserState |
| 57 mask:(int)mask |
| 58 deleteBegin:(base::Time)deleteBegin |
| 59 completionHandler: |
| 60 (ProceduralBlock)completionHandler; |
| 61 // Removes browsing data that is created by WKWebViews associated with |
| 62 // |browserState|. |browserState| must not be off the record. |mask| is obtained |
| 63 // from IOSChromeBrowsingDataRemover::RemoveDataMask. |deleteBegin| defines the |
| 64 // begin time from which the data has to be removed, up to the present time. |
| 65 // |completionHandler| is called when this operation finishes. This method |
| 66 // finishes removal of the browsing data even if |browserState| is destroyed |
| 67 // after this method call. |
| 68 // Note: This method works only on iOS9+. |
| 69 - (void)removeWKWebViewCreatedBrowsingDataFromBrowserState: |
| 70 (ios::ChromeBrowserState*)browserState |
| 71 mask:(int)mask |
| 72 deleteBegin: |
| 73 (base::Time)deleteBegin |
| 74 completionHandler: |
| 75 (ProceduralBlock)completionHandler; |
| 76 |
| 77 // Removes browsing data associated with |browserState| that is specific to iOS |
| 78 // and not removed when the |browserState| is destroyed. |
| 79 // |mask| is obtained from IOSChromeBrowsingDataRemover::RemoveDataMask |
| 80 // |deleteBegin| defines the begin time from which the data has to be removed. |
| 81 // |browserState| cannot be null. |completionHandler| is called when |
| 82 // this operation finishes. This method finishes removal of the browsing data |
| 83 // even if |browserState| is destroyed after this method call. |
| 84 - (void)removeIOSSpecificBrowsingDataFromBrowserState: |
| 85 (ios::ChromeBrowserState*)browserState |
| 86 mask:(int)mask |
| 87 deleteBegin:(base::Time)deleteBegin |
| 88 completionHandler: |
| 89 (ProceduralBlock)completionHandler; |
| 90 // Removes browsing data from |browserState| that is persisted on disk. |
| 91 // |mask| is obtained from IOSChromeBrowsingDataRemover::RemoveDataMask. |
| 92 // |browserState| cannot be null and must be off the record. |
| 93 // This method finishes removal of the browsing data even if |browserState| is |
| 94 // destroyed after this method call. |
| 95 - (void)removeIOSSpecificPersistentIncognitoDataFromBrowserState: |
| 96 (ios::ChromeBrowserState*)browserState |
| 97 mask:(int)mask; |
| 98 |
| 99 // Increments the count of pending removal operations for |browserState|. |
| 100 // Called when any removal operation for |browserState| starts. |
| 101 - (void)incrementPendingRemovalCountForBrowserState: |
| 102 (ios::ChromeBrowserState*)browserState; |
| 103 // Decrements the count of pending removal operations for |browserState|. |
| 104 // Called when a removal operation for |browserState| finishes. |
| 105 - (void)decrementPendingRemovalCountForBrowserState: |
| 106 (ios::ChromeBrowserState*)browserState; |
| 107 @end |
| 108 |
| 109 @implementation BrowsingDataRemovalController { |
| 110 // Wrapper around IOSChromeBrowsingDataRemover that serializes removal |
| 111 // operations. |
| 112 std::unique_ptr<BrowsingDataRemoverHelper> _browsingDataRemoverHelper; |
| 113 // The delegate. |
| 114 base::WeakNSProtocol<id<BrowsingDataRemovalControllerDelegate>> _delegate; |
| 115 // A map that tracks the number of pending removals for a given |
| 116 // ChromeBrowserState. |
| 117 base::hash_map<ios::ChromeBrowserState*, int> _pendingRemovalCount; |
| 118 } |
| 119 |
| 120 - (instancetype)initWithDelegate: |
| 121 (id<BrowsingDataRemovalControllerDelegate>)delegate { |
| 122 if ((self = [super init])) { |
| 123 DCHECK(delegate); |
| 124 _browsingDataRemoverHelper.reset(new BrowsingDataRemoverHelper()); |
| 125 _delegate.reset(delegate); |
| 126 } |
| 127 return self; |
| 128 } |
| 129 |
| 130 - (instancetype)init { |
| 131 NOTREACHED(); |
| 132 return nil; |
| 133 } |
| 134 |
| 135 - (void)removeBrowsingDataFromBrowserState: |
| 136 (ios::ChromeBrowserState*)browserState |
| 137 mask:(int)mask |
| 138 timePeriod:(browsing_data::TimePeriod)timePeriod |
| 139 completionHandler:(ProceduralBlock)completionHandler { |
| 140 DCHECK(browserState); |
| 141 DLOG_IF(WARNING, !mask) << "Nothing to remove!"; |
| 142 // Cookies and server bound certificates should have the same lifetime. |
| 143 DCHECK_EQ((mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) != 0, |
| 144 (mask & IOSChromeBrowsingDataRemover::REMOVE_CHANNEL_IDS) != 0); |
| 145 |
| 146 [self incrementPendingRemovalCountForBrowserState:browserState]; |
| 147 |
| 148 ProceduralBlock browsingDataCleared = ^{ |
| 149 [self decrementPendingRemovalCountForBrowserState:browserState]; |
| 150 if (AccountConsistencyService* accountConsistencyService = |
| 151 ios::AccountConsistencyServiceFactory::GetForBrowserState( |
| 152 browserState)) { |
| 153 accountConsistencyService->OnBrowsingDataRemoved(); |
| 154 } |
| 155 if (completionHandler) { |
| 156 completionHandler(); |
| 157 } |
| 158 }; |
| 159 |
| 160 scoped_refptr<CallbackCounter> callbackCounter = |
| 161 new CallbackCounter(base::BindBlock(browsingDataCleared)); |
| 162 ProceduralBlock decrementCallbackCounterCount = ^{ |
| 163 callbackCounter->DecrementCount(); |
| 164 }; |
| 165 |
| 166 callbackCounter->IncrementCount(); |
| 167 base::Time beginDeleteTime = |
| 168 browsing_data::CalculateBeginDeleteTime(timePeriod); |
| 169 [self removeIOSSpecificBrowsingDataFromBrowserState:browserState |
| 170 mask:mask |
| 171 deleteBegin:beginDeleteTime |
| 172 completionHandler: |
| 173 decrementCallbackCounterCount]; |
| 174 |
| 175 if (mask & IOSChromeBrowsingDataRemover::REMOVE_DOWNLOADS) { |
| 176 DCHECK_EQ(browsing_data::ALL_TIME, timePeriod) |
| 177 << "Partial clearing not supported"; |
| 178 callbackCounter->IncrementCount(); |
| 179 [_delegate |
| 180 removeExternalFilesForBrowserState:browserState |
| 181 completionHandler:decrementCallbackCounterCount]; |
| 182 } |
| 183 |
| 184 if (!browserState->IsOffTheRecord()) { |
| 185 callbackCounter->IncrementCount(); |
| 186 _browsingDataRemoverHelper->Remove(browserState, mask, timePeriod, |
| 187 base::BindBlock(^{ |
| 188 callbackCounter->DecrementCount(); |
| 189 })); |
| 190 } |
| 191 } |
| 192 |
| 193 - (void)removeIOSSpecificIncognitoBrowsingDataFromBrowserState: |
| 194 (ios::ChromeBrowserState*)browserState |
| 195 mask:(int)mask |
| 196 completionHandler: |
| 197 (ProceduralBlock) |
| 198 completionHandler { |
| 199 DCHECK(browserState && browserState->IsOffTheRecord()); |
| 200 [self removeIOSSpecificBrowsingDataFromBrowserState:browserState |
| 201 mask:mask |
| 202 deleteBegin:base::Time() |
| 203 completionHandler:completionHandler]; |
| 204 } |
| 205 |
| 206 - (void)removeIOSSpecificBrowsingDataFromBrowserState: |
| 207 (ios::ChromeBrowserState*)browserState |
| 208 mask:(int)mask |
| 209 deleteBegin:(base::Time)deleteBegin |
| 210 completionHandler: |
| 211 (ProceduralBlock)completionHandler { |
| 212 DCHECK(browserState); |
| 213 [self incrementPendingRemovalCountForBrowserState:browserState]; |
| 214 |
| 215 ProceduralBlock browsingDataCleared = ^{ |
| 216 [self decrementPendingRemovalCountForBrowserState:browserState]; |
| 217 if (completionHandler) { |
| 218 completionHandler(); |
| 219 } |
| 220 }; |
| 221 |
| 222 // Note: Before adding any method below, make sure that it can finish clearing |
| 223 // browsing data even when |browserState| is destroyed after this method call. |
| 224 |
| 225 // If deleting history, clear visited links. |
| 226 if (mask & IOSChromeBrowsingDataRemover::REMOVE_HISTORY) { |
| 227 if (!browserState->IsOffTheRecord()) { |
| 228 ClipboardRecentContent::GetInstance()->SuppressClipboardContent(); |
| 229 session_util::DeleteLastSession(browserState); |
| 230 } |
| 231 // Remove the screenshots taken by the system when backgrounding the |
| 232 // application. Partial removal based on timePeriod is not required. |
| 233 ClearIOSSnapshots(); |
| 234 } |
| 235 |
| 236 // TODO(crbug.com/227636): Support multiple profile. |
| 237 // Google App Launcher data is tied to the normal profile. |
| 238 if (mask & IOSChromeBrowsingDataRemover::REMOVE_GOOGLE_APP_LAUNCHER_DATA && |
| 239 !browserState->IsOffTheRecord()) { |
| 240 [self removeGALData]; |
| 241 } |
| 242 |
| 243 if (browserState->IsOffTheRecord()) { |
| 244 // In incognito, only data removal for all time is currently supported. |
| 245 DCHECK_EQ(base::Time(), deleteBegin); |
| 246 [self removeIOSSpecificPersistentIncognitoDataFromBrowserState:browserState |
| 247 mask:mask]; |
| 248 } |
| 249 |
| 250 [self removeWebViewCreatedBrowsingDataFromBrowserState:browserState |
| 251 mask:mask |
| 252 deleteBegin:deleteBegin |
| 253 completionHandler:browsingDataCleared]; |
| 254 } |
| 255 |
| 256 - (void)removeGALData { |
| 257 [ios::GetChromeBrowserProvider()->GetNativeAppWhitelistManager() |
| 258 filteredAppsUsingBlock:^BOOL(const id<NativeAppMetadata> app, |
| 259 BOOL* stop) { |
| 260 [app resetInfobarHistory]; |
| 261 return NO; |
| 262 }]; |
| 263 } |
| 264 |
| 265 - (void)removeWebViewCreatedBrowsingDataFromBrowserState: |
| 266 (ios::ChromeBrowserState*)browserState |
| 267 mask:(int)mask |
| 268 deleteBegin:(base::Time)deleteBegin |
| 269 completionHandler: |
| 270 (ProceduralBlock)completionHandler { |
| 271 // TODO(crbug.com/480654): Remove this check once browsing data partitioning |
| 272 // between BrowserStates is achieved. |
| 273 if (browserState->IsOffTheRecord()) { |
| 274 if (completionHandler) { |
| 275 dispatch_async(dispatch_get_main_queue(), completionHandler); |
| 276 } |
| 277 return; |
| 278 } |
| 279 scoped_refptr<CallbackCounter> callbackCounter = |
| 280 new CallbackCounter(base::BindBlock(completionHandler ? completionHandler |
| 281 : ^{ |
| 282 })); |
| 283 |
| 284 // Note: Before adding any method below, make sure that it can finish clearing |
| 285 // browsing data even when |browserState| is destroyed after this method call. |
| 286 |
| 287 callbackCounter->IncrementCount(); |
| 288 [self removeWKWebViewCreatedBrowsingDataFromBrowserState:browserState |
| 289 mask:mask |
| 290 deleteBegin:deleteBegin |
| 291 completionHandler:^{ |
| 292 callbackCounter->DecrementCount(); |
| 293 }]; |
| 294 } |
| 295 |
| 296 - (void) |
| 297 removeWKWebViewCreatedBrowsingDataFromBrowserState: |
| 298 (ios::ChromeBrowserState*)browserState |
| 299 mask:(int)mask |
| 300 deleteBegin:(base::Time)deleteBegin |
| 301 completionHandler: |
| 302 (ProceduralBlock)completionHandler { |
| 303 scoped_refptr<CallbackCounter> callbackCounter = |
| 304 new CallbackCounter(base::BindBlock(completionHandler ? completionHandler |
| 305 : ^{ |
| 306 })); |
| 307 ProceduralBlock decrementCallbackCounterCount = ^{ |
| 308 callbackCounter->DecrementCount(); |
| 309 }; |
| 310 |
| 311 // Converts browsing data types from |
| 312 // IOSChromeBrowsingDataRemover::RemoveDataMask to |
| 313 // WKWebsiteDataStore strings. |
| 314 base::scoped_nsobject<NSMutableSet> dataTypesToRemove( |
| 315 [[NSMutableSet alloc] init]); |
| 316 if (mask & IOSChromeBrowsingDataRemover::REMOVE_CACHE_STORAGE) { |
| 317 [dataTypesToRemove addObject:WKWebsiteDataTypeDiskCache]; |
| 318 [dataTypesToRemove addObject:WKWebsiteDataTypeMemoryCache]; |
| 319 } |
| 320 if (mask & IOSChromeBrowsingDataRemover::REMOVE_APPCACHE) { |
| 321 [dataTypesToRemove addObject:WKWebsiteDataTypeOfflineWebApplicationCache]; |
| 322 } |
| 323 WKWebView* markerWKWebView = nil; |
| 324 if (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) { |
| 325 // TODO(crbug.com/661630): This approach of creating a WKWebView to clear |
| 326 // cookies is a workaround for |
| 327 // https://bugs.webkit.org/show_bug.cgi?id=149078. Remove this, when that |
| 328 // bug is fixed. Note: This WKWebView will be released when cookies have |
| 329 // been cleared. |
| 330 markerWKWebView = web::BuildWKWebView(CGRectZero, browserState); |
| 331 [dataTypesToRemove addObject:WKWebsiteDataTypeCookies]; |
| 332 } |
| 333 if (mask & IOSChromeBrowsingDataRemover::REMOVE_LOCAL_STORAGE) { |
| 334 [dataTypesToRemove addObject:WKWebsiteDataTypeSessionStorage]; |
| 335 [dataTypesToRemove addObject:WKWebsiteDataTypeLocalStorage]; |
| 336 } |
| 337 if (mask & IOSChromeBrowsingDataRemover::REMOVE_WEBSQL) { |
| 338 [dataTypesToRemove addObject:WKWebsiteDataTypeWebSQLDatabases]; |
| 339 } |
| 340 if (mask & IOSChromeBrowsingDataRemover::REMOVE_INDEXEDDB) { |
| 341 [dataTypesToRemove addObject:WKWebsiteDataTypeIndexedDBDatabases]; |
| 342 } |
| 343 |
| 344 ProceduralBlock afterRemovalFromWKWebsiteDataStore = ^{ |
| 345 if (mask & IOSChromeBrowsingDataRemover::REMOVE_VISITED_LINKS) { |
| 346 // TODO(crbug.com/557963): Purging the WKProcessPool is a workaround for |
| 347 // the fact that there is no public API to clear visited links in |
| 348 // WKWebView. Remove this workaround if/when that API is made public. |
| 349 // Note: Purging the WKProcessPool for clearing visisted links does have |
| 350 // the side-effect of also losing the in-memory cookies of WKWebView but |
| 351 // it is not a problem in practice since there is no UI to only have |
| 352 // visited links be removed but not cookies. |
| 353 DCHECK(mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES); |
| 354 web::WKWebViewConfigurationProvider::FromBrowserState(browserState) |
| 355 .Purge(); |
| 356 } |
| 357 |
| 358 decrementCallbackCounterCount(); |
| 359 }; |
| 360 |
| 361 if ([dataTypesToRemove count]) { |
| 362 callbackCounter->IncrementCount(); |
| 363 ProceduralBlock removeFromWKWebsiteDataStore = ^{ |
| 364 NSDate* beginDeleteDate = |
| 365 [NSDate dateWithTimeIntervalSince1970:deleteBegin.ToDoubleT()]; |
| 366 [[WKWebsiteDataStore defaultDataStore] |
| 367 removeDataOfTypes:dataTypesToRemove |
| 368 modifiedSince:beginDeleteDate |
| 369 completionHandler:afterRemovalFromWKWebsiteDataStore]; |
| 370 }; |
| 371 |
| 372 if (markerWKWebView) { |
| 373 // TODO(crbug.com/661630): Executing JS enables the markerWKWebView to |
| 374 // connect to the Networking process. This is so that the |
| 375 // -[WKWebsiteDataStore removeDataOfTypes:] API is able to send an IPC |
| 376 // message to the Networking process to clear cookies. This has been |
| 377 // reverse-engineered by code inspection on the WebKit2 source code and is |
| 378 // an undocumented workaround for |
| 379 // https://bugs.webkit.org/show_bug.cgi?id=149078. Remove it, when that |
| 380 // bug is fixed. |
| 381 [markerWKWebView evaluateJavaScript:@"" |
| 382 completionHandler:^(id, NSError*) { |
| 383 removeFromWKWebsiteDataStore(); |
| 384 }]; |
| 385 } else { |
| 386 removeFromWKWebsiteDataStore(); |
| 387 } |
| 388 } |
| 389 |
| 390 // This is to ensure that the caller of this API still gets a callback even |
| 391 // when none of the masks matched. |
| 392 callbackCounter->IncrementCount(); |
| 393 dispatch_async(dispatch_get_main_queue(), decrementCallbackCounterCount); |
| 394 } |
| 395 |
| 396 - (void)removeIOSSpecificPersistentIncognitoDataFromBrowserState: |
| 397 (ios::ChromeBrowserState*)browserState |
| 398 mask:(int)mask { |
| 399 DCHECK(browserState && browserState->IsOffTheRecord()); |
| 400 // Note: Before adding any method below, make sure that it can finish clearing |
| 401 // browsing data even when |browserState| is destroyed after this method call. |
| 402 if (mask & IOSChromeBrowsingDataRemover::REMOVE_HISTORY) { |
| 403 session_util::DeleteLastSession(browserState); |
| 404 } |
| 405 |
| 406 // Cookies and server bound certificates should have the same lifetime. |
| 407 DCHECK_EQ((mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) != 0, |
| 408 (mask & IOSChromeBrowsingDataRemover::REMOVE_CHANNEL_IDS) != 0); |
| 409 if (mask & IOSChromeBrowsingDataRemover::REMOVE_COOKIES) { |
| 410 scoped_refptr<net::URLRequestContextGetter> contextGetter = |
| 411 browserState->GetRequestContext(); |
| 412 base::Closure callback = base::BindBlock(^{ |
| 413 }); |
| 414 web::WebThread::PostTask( |
| 415 web::WebThread::IO, FROM_HERE, base::BindBlock(^{ |
| 416 net::URLRequestContext* requestContext = |
| 417 contextGetter->GetURLRequestContext(); |
| 418 net::ChannelIDService* channelIdService = |
| 419 requestContext->channel_id_service(); |
| 420 DCHECK(channelIdService); |
| 421 DCHECK(channelIdService->GetChannelIDStore()); |
| 422 channelIdService->GetChannelIDStore()->DeleteAll(callback); |
| 423 DCHECK(requestContext->cookie_store()); |
| 424 requestContext->cookie_store()->DeleteAllCreatedBetweenAsync( |
| 425 base::Time(), base::Time(), base::Bind(&DoNothing)); |
| 426 })); |
| 427 } |
| 428 } |
| 429 |
| 430 - (void)incrementPendingRemovalCountForBrowserState: |
| 431 (ios::ChromeBrowserState*)browserState { |
| 432 ++_pendingRemovalCount[browserState]; |
| 433 } |
| 434 |
| 435 - (void)decrementPendingRemovalCountForBrowserState: |
| 436 (ios::ChromeBrowserState*)browserState { |
| 437 if (_pendingRemovalCount.find(browserState) != _pendingRemovalCount.end()) { |
| 438 --_pendingRemovalCount[browserState]; |
| 439 if (!_pendingRemovalCount[browserState]) { |
| 440 _pendingRemovalCount.erase(browserState); |
| 441 } |
| 442 } |
| 443 } |
| 444 |
| 445 - (BOOL)hasPendingRemovalOperations:(ios::ChromeBrowserState*)browserState { |
| 446 return _pendingRemovalCount[browserState] != 0; |
| 447 } |
| 448 |
| 449 - (void)browserStateDestroyed:(ios::ChromeBrowserState*)browserState { |
| 450 _pendingRemovalCount.erase(browserState); |
| 451 } |
| 452 |
| 453 @end |
OLD | NEW |