Commit d5925980 authored by Sylvain Defresne's avatar Sylvain Defresne Committed by Commit Bot

Refactor BrowsingDataRemovalController

Move all the code related to clearing browsing data to
IOSChromeBrowsingDataRemover after remarking that method
-removeIOSSpecificIncognitoBrowsingDataFromBrowserState:..
is unnecessary as -removeBrowsingDataFromBrowserState:...
clear exactly the same data is BrowserState is incognito.

Refactor some method clearing browsing data to accept a
closure (those that are asynchronous).

Bug: none
Change-Id: I7dfbb7040b87343fdd3df3b473a7d4c2046d4c46
Cq-Include-Trybots: master.tryserver.chromium.mac:ios-simulator-cronet;master.tryserver.chromium.mac:ios-simulator-full-configs
Reviewed-on: https://chromium-review.googlesource.com/919264
Commit-Queue: Sylvain Defresne <sdefresne@chromium.org>
Reviewed-by: default avatarOlivier Robin <olivierrobin@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537809}
parent 9550a3b8
...@@ -833,9 +833,10 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData( ...@@ -833,9 +833,10 @@ void MainControllerAuthenticationServiceDelegate::ClearBrowsingData(
}; };
const BrowsingDataRemoveMask mask = BrowsingDataRemoveMask::REMOVE_ALL; const BrowsingDataRemoveMask mask = BrowsingDataRemoveMask::REMOVE_ALL;
[self.browsingDataRemovalController [self.browsingDataRemovalController
removeIOSSpecificIncognitoBrowsingDataFromBrowserState:otrBrowserState removeBrowsingDataFromBrowserState:otrBrowserState
mask:mask mask:mask
completionHandler:completion]; timePeriod:browsing_data::TimePeriod::ALL_TIME
completionHandler:completion];
} }
- (void)deleteIncognitoBrowserState { - (void)deleteIncognitoBrowserState {
......
...@@ -23,6 +23,7 @@ source_set("browsing_data") { ...@@ -23,6 +23,7 @@ source_set("browsing_data") {
"//components/keyed_service/core", "//components/keyed_service/core",
"//components/language/core/browser", "//components/language/core/browser",
"//components/omnibox/browser", "//components/omnibox/browser",
"//components/open_from_clipboard",
"//components/password_manager/core/browser", "//components/password_manager/core/browser",
"//components/prefs", "//components/prefs",
"//components/sessions", "//components/sessions",
...@@ -37,8 +38,10 @@ source_set("browsing_data") { ...@@ -37,8 +38,10 @@ source_set("browsing_data") {
"//ios/chrome/browser/sessions", "//ios/chrome/browser/sessions",
"//ios/chrome/browser/sessions:serialisation", "//ios/chrome/browser/sessions:serialisation",
"//ios/chrome/browser/signin", "//ios/chrome/browser/signin",
"//ios/chrome/browser/snapshots",
"//ios/chrome/browser/sync", "//ios/chrome/browser/sync",
"//ios/chrome/browser/translate:translate", "//ios/chrome/browser/translate:translate",
"//ios/chrome/browser/ui:external_files",
"//ios/net", "//ios/net",
"//ios/public/provider/chrome/browser", "//ios/public/provider/chrome/browser",
"//ios/web", "//ios/web",
...@@ -65,6 +68,8 @@ source_set("unit_tests") { ...@@ -65,6 +68,8 @@ source_set("unit_tests") {
"//base", "//base",
"//base/test:test_support", "//base/test:test_support",
"//components/browsing_data/core", "//components/browsing_data/core",
"//components/open_from_clipboard",
"//components/open_from_clipboard:test_support",
"//components/pref_registry", "//components/pref_registry",
"//components/prefs", "//components/prefs",
"//components/prefs:test_support", "//components/prefs:test_support",
...@@ -76,7 +81,6 @@ source_set("unit_tests") { ...@@ -76,7 +81,6 @@ source_set("unit_tests") {
"//ios/web/public/test/fakes", "//ios/web/public/test/fakes",
"//net", "//net",
"//testing/gtest", "//testing/gtest",
"//third_party/ocmock",
] ]
} }
......
specific_include_rules = { specific_include_rules = {
# TODO(crbug.com/619783): Remove this exception. # TODO(crbug.com/619783): Remove this exception.
"^browsing_data_removal_controller\.mm$": [ "^ios_chrome_browsing_data_remover\.mm$": [
"+ios/web/web_state/ui/wk_web_view_configuration_provider.h", "+ios/web/web_state/ui/wk_web_view_configuration_provider.h",
], ],
} }
...@@ -11,8 +11,6 @@ ...@@ -11,8 +11,6 @@
#include "components/browsing_data/core/browsing_data_utils.h" #include "components/browsing_data/core/browsing_data_utils.h"
#include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h" #include "ios/chrome/browser/browsing_data/browsing_data_remove_mask.h"
@class BrowserViewController;
namespace ios { namespace ios {
class ChromeBrowserState; class ChromeBrowserState;
} // namespace ios } // namespace ios
...@@ -30,19 +28,6 @@ class ChromeBrowserState; ...@@ -30,19 +28,6 @@ class ChromeBrowserState;
timePeriod:(browsing_data::TimePeriod)timePeriod timePeriod:(browsing_data::TimePeriod)timePeriod
completionHandler:(ProceduralBlock)completionHandler; completionHandler:(ProceduralBlock)completionHandler;
// Removes browsing data that iOS has associated with |browserState| and which
// is not removed when the |browserState| is destroyed. |mask| is obtained from
// BrowsingDataRemoveMask. |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)
removeIOSSpecificIncognitoBrowsingDataFromBrowserState:
(ios::ChromeBrowserState*)browserState
mask:(BrowsingDataRemoveMask)
mask
completionHandler:
(ProceduralBlock)completionHandler;
// Called when |browserState| is destroyed. // Called when |browserState| is destroyed.
- (void)browserStateDestroyed:(ios::ChromeBrowserState*)browserState; - (void)browserStateDestroyed:(ios::ChromeBrowserState*)browserState;
......
...@@ -6,41 +6,63 @@ ...@@ -6,41 +6,63 @@
#include <memory> #include <memory>
#import "base/test/ios/wait_util.h" #include "base/logging.h"
#include "base/run_loop.h"
#include "components/open_from_clipboard/clipboard_recent_content.h"
#include "components/open_from_clipboard/fake_clipboard_recent_content.h"
#include "ios/chrome/browser/browser_state/test_chrome_browser_state.h" #include "ios/chrome/browser/browser_state/test_chrome_browser_state.h"
#include "ios/web/public/test/web_test.h" #include "ios/web/public/test/test_web_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#import "third_party/ocmock/OCMock/OCMock.h" #include "testing/platform_test.h"
#import "third_party/ocmock/gtest_support.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
#endif #endif
typedef web::WebTest BrowsingDataRemovalControllerTest; class BrowsingDataRemovalControllerTest : public PlatformTest {
public:
BrowsingDataRemovalControllerTest()
: browser_state_(TestChromeBrowserState::Builder().Build()) {
DCHECK_EQ(ClipboardRecentContent::GetInstance(), nullptr);
ClipboardRecentContent::SetInstance(
std::make_unique<FakeClipboardRecentContent>());
}
// Tests that |removeIOSSpecificIncognitoBrowsingDataFromBrowserState:| can ~BrowsingDataRemovalControllerTest() override {
DCHECK_NE(ClipboardRecentContent::GetInstance(), nullptr);
ClipboardRecentContent::SetInstance(nullptr);
}
protected:
web::TestWebThreadBundle thread_bundle_;
std::unique_ptr<ios::ChromeBrowserState> browser_state_;
};
// Tests that
// -removeBrowsingDataFromBrowserState:mask:timePeriod:completionHandler: can
// finish performing its operation even when a BrowserState is destroyed. // finish performing its operation even when a BrowserState is destroyed.
TEST_F(BrowsingDataRemovalControllerTest, PerformAfterBrowserStateDestruction) { TEST_F(BrowsingDataRemovalControllerTest, PerformAfterBrowserStateDestruction) {
__block BOOL block_was_called = NO; base::RunLoop run_loop;
base::RepeatingClosure quit_run_loop = run_loop.QuitClosure();
BrowsingDataRemovalController* removal_controller = BrowsingDataRemovalController* removal_controller =
[[BrowsingDataRemovalController alloc] init]; [[BrowsingDataRemovalController alloc] init];
TestChromeBrowserState::Builder builder; __block BOOL block_was_called = NO;
std::unique_ptr<TestChromeBrowserState> browser_state = builder.Build();
ios::ChromeBrowserState* otr_browser_state =
browser_state->GetOffTheRecordChromeBrowserState();
const BrowsingDataRemoveMask mask = BrowsingDataRemoveMask::REMOVE_ALL; const BrowsingDataRemoveMask mask = BrowsingDataRemoveMask::REMOVE_ALL;
[removal_controller [removal_controller
removeIOSSpecificIncognitoBrowsingDataFromBrowserState:otr_browser_state removeBrowsingDataFromBrowserState:browser_state_.get()
mask:mask mask:mask
completionHandler:^{ timePeriod:browsing_data::TimePeriod::ALL_TIME
block_was_called = YES; completionHandler:^{
}]; block_was_called = YES;
quit_run_loop.Run();
}];
// Destroy the BrowserState immediately. // Destroy the BrowserState immediately.
browser_state->DestroyOffTheRecordChromeBrowserState(); [removal_controller browserStateDestroyed:browser_state_.get()];
browser_state_.reset();
base::test::ios::WaitUntilCondition(^bool() { run_loop.RunUntilIdle();
return block_was_called; EXPECT_TRUE(block_was_called);
});
} }
...@@ -81,6 +81,11 @@ class IOSChromeBrowsingDataRemover { ...@@ -81,6 +81,11 @@ class IOSChromeBrowsingDataRemover {
base::Time delete_end, base::Time delete_end,
BrowsingDataRemoveMask mask); BrowsingDataRemoveMask mask);
// Removes the browsing data stored in WKWebsiteDataStore if needed.
void RemoveDataFromWKWebsiteDataStore(base::Time delete_begin,
base::Time delete_end,
BrowsingDataRemoveMask mask);
// Invokes the current task callback that the removal has completed. // Invokes the current task callback that the removal has completed.
void NotifyRemovalComplete(); void NotifyRemovalComplete();
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#include "base/callback.h"
#include "base/sequenced_task_runner.h" #include "base/sequenced_task_runner.h"
@class SessionIOS; @class SessionIOS;
...@@ -44,7 +45,8 @@ using SessionIOSFactory = SessionIOS* (^)(); ...@@ -44,7 +45,8 @@ using SessionIOSFactory = SessionIOS* (^)();
- (SessionIOS*)loadSessionFromPath:(NSString*)sessionPath; - (SessionIOS*)loadSessionFromPath:(NSString*)sessionPath;
// Schedules deletion of the file containing the last session in |directory|. // Schedules deletion of the file containing the last session in |directory|.
- (void)deleteLastSessionFileInDirectory:(NSString*)directory; - (void)deleteLastSessionFileInDirectory:(NSString*)directory
completion:(base::OnceClosure)callback;
// Returns the path of the session file for |directory|. // Returns the path of the session file for |directory|.
+ (NSString*)sessionPathForDirectory:(NSString*)directory; + (NSString*)sessionPathForDirectory:(NSString*)directory;
......
...@@ -164,9 +164,10 @@ NSString* const kRootObjectKey = @"root"; // Key for the root object. ...@@ -164,9 +164,10 @@ NSString* const kRootObjectKey = @"root"; // Key for the root object.
return base::mac::ObjCCastStrict<SessionIOS>(rootObject); return base::mac::ObjCCastStrict<SessionIOS>(rootObject);
} }
- (void)deleteLastSessionFileInDirectory:(NSString*)directory { - (void)deleteLastSessionFileInDirectory:(NSString*)directory
completion:(base::OnceClosure)callback {
NSString* sessionPath = [[self class] sessionPathForDirectory:directory]; NSString* sessionPath = [[self class] sessionPathForDirectory:directory];
_taskRunner->PostTask( _taskRunner->PostTaskAndReply(
FROM_HERE, base::BindBlockArc(^{ FROM_HERE, base::BindBlockArc(^{
base::AssertBlockingAllowed(); base::AssertBlockingAllowed();
NSFileManager* fileManager = [NSFileManager defaultManager]; NSFileManager* fileManager = [NSFileManager defaultManager];
...@@ -178,7 +179,8 @@ NSString* const kRootObjectKey = @"root"; // Key for the root object. ...@@ -178,7 +179,8 @@ NSString* const kRootObjectKey = @"root"; // Key for the root object.
CHECK(false) << "Unable to delete session file: " CHECK(false) << "Unable to delete session file: "
<< base::SysNSStringToUTF8(sessionPath) << ": " << base::SysNSStringToUTF8(sessionPath) << ": "
<< base::SysNSStringToUTF8([error description]); << base::SysNSStringToUTF8([error description]);
})); }),
std::move(callback));
} }
+ (NSString*)sessionPathForDirectory:(NSString*)directory { + (NSString*)sessionPathForDirectory:(NSString*)directory {
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#include <memory> #include <memory>
#include <vector> #include <vector>
#include "base/callback.h"
namespace ios { namespace ios {
class ChromeBrowserState; class ChromeBrowserState;
} }
...@@ -24,8 +26,10 @@ class WebState; ...@@ -24,8 +26,10 @@ class WebState;
namespace session_util { namespace session_util {
// Deletes the file containing the commands for the last session. Finishes the // Deletes the file containing the commands for the last session. Finishes the
// deletion even if |browser_state| is destroyed after this call. // deletion even if |browser_state| is destroyed after this call. |callback| is
void DeleteLastSession(ios::ChromeBrowserState* browser_state); // invoked once the deletion completes.
void DeleteLastSession(ios::ChromeBrowserState* browser_state,
base::OnceClosure callback);
// Create a WebState initialized with |browser_state| and serialized navigation. // Create a WebState initialized with |browser_state| and serialized navigation.
// The returned WebState has web usage enabled. // The returned WebState has web usage enabled.
......
...@@ -22,11 +22,13 @@ ...@@ -22,11 +22,13 @@
namespace session_util { namespace session_util {
// Deletes the file containing the commands for the last session. // Deletes the file containing the commands for the last session.
void DeleteLastSession(ios::ChromeBrowserState* browser_state) { void DeleteLastSession(ios::ChromeBrowserState* browser_state,
base::OnceClosure callback) {
NSString* state_path = NSString* state_path =
base::SysUTF8ToNSString(browser_state->GetStatePath().AsUTF8Unsafe()); base::SysUTF8ToNSString(browser_state->GetStatePath().AsUTF8Unsafe());
[[SessionServiceIOS sharedService] [[SessionServiceIOS sharedService]
deleteLastSessionFileInDirectory:state_path]; deleteLastSessionFileInDirectory:state_path
completion:std::move(callback)];
} }
std::unique_ptr<web::WebState> CreateWebStateWithNavigationEntries( std::unique_ptr<web::WebState> CreateWebStateWithNavigationEntries(
......
...@@ -7,10 +7,12 @@ ...@@ -7,10 +7,12 @@
#include <vector> #include <vector>
#include "base/callback.h"
#include "base/files/file_path.h" #include "base/files/file_path.h"
// Clears the application snapshots taken by iOS. // Clears the application snapshots taken by iOS and invoke |callback| when
void ClearIOSSnapshots(); // the deletion has completed (asynchronously).
void ClearIOSSnapshots(base::OnceClosure callback);
// Adds to |snapshotsPaths| all the possible paths to the application's // Adds to |snapshotsPaths| all the possible paths to the application's
// snapshots taken by iOS. // snapshots taken by iOS.
......
...@@ -14,6 +14,7 @@ ...@@ -14,6 +14,7 @@
#include "base/path_service.h" #include "base/path_service.h"
#include "base/strings/stringprintf.h" #include "base/strings/stringprintf.h"
#include "base/task_scheduler/post_task.h" #include "base/task_scheduler/post_task.h"
#include "base/threading/thread_restrictions.h"
#if !defined(__has_feature) || !__has_feature(objc_arc) #if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support." #error "This file requires ARC support."
...@@ -26,41 +27,47 @@ const char* kOrientationDescriptions[] = { ...@@ -26,41 +27,47 @@ const char* kOrientationDescriptions[] = {
"Portrait", "Portrait",
"PortraitUpsideDown", "PortraitUpsideDown",
}; };
// Delete all files in |paths|.
void DeleteAllFiles(std::vector<base::FilePath> paths) {
base::AssertBlockingAllowed();
for (const auto& path : paths) {
ignore_result(base::DeleteFile(path, false));
}
}
} // namespace } // namespace
void ClearIOSSnapshots() { void ClearIOSSnapshots(base::OnceClosure callback) {
// Generates a list containing all the possible snapshot paths because the // Generates a list containing all the possible snapshot paths because the
// list of snapshots stored on the device can't be obtained programmatically. // list of snapshots stored on the device can't be obtained programmatically.
std::vector<base::FilePath> snapshotsPaths; std::vector<base::FilePath> snapshots_paths;
GetSnapshotsPaths(&snapshotsPaths); GetSnapshotsPaths(&snapshots_paths);
for (base::FilePath snapshotPath : snapshotsPaths) { base::PostTaskWithTraitsAndReply(
base::PostTaskWithTraits( FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND}, base::BindOnce(&DeleteAllFiles, std::move(snapshots_paths)),
base::BindOnce(base::IgnoreResult(&base::DeleteFile), snapshotPath, std::move(callback));
false));
}
} }
void GetSnapshotsPaths(std::vector<base::FilePath>* snapshotsPaths) { void GetSnapshotsPaths(std::vector<base::FilePath>* snapshots_paths) {
DCHECK(snapshotsPaths); DCHECK(snapshots_paths);
base::FilePath snapshotsDir; base::FilePath snapshots_dir;
PathService::Get(base::DIR_CACHE, &snapshotsDir); PathService::Get(base::DIR_CACHE, &snapshots_dir);
// Snapshots are located in a path with the bundle ID used twice. // Snapshots are located in a path with the bundle ID used twice.
snapshotsDir = snapshotsDir.Append("Snapshots") snapshots_dir = snapshots_dir.Append("Snapshots")
.Append(base::mac::BaseBundleID()) .Append(base::mac::BaseBundleID())
.Append(base::mac::BaseBundleID()); .Append(base::mac::BaseBundleID());
const char* retinaSuffix = ""; const char* retina_suffix = "";
CGFloat scale = [UIScreen mainScreen].scale; CGFloat scale = [UIScreen mainScreen].scale;
if (scale == 2) { if (scale == 2) {
retinaSuffix = "@2x"; retina_suffix = "@2x";
} else if (scale == 3) { } else if (scale == 3) {
retinaSuffix = "@3x"; retina_suffix = "@3x";
} }
for (unsigned int i = 0; i < arraysize(kOrientationDescriptions); i++) { for (unsigned int i = 0; i < arraysize(kOrientationDescriptions); i++) {
std::string snapshotFilename = std::string snapshot_filename =
base::StringPrintf("UIApplicationAutomaticSnapshotDefault-%s%s.png", base::StringPrintf("UIApplicationAutomaticSnapshotDefault-%s%s.png",
kOrientationDescriptions[i], retinaSuffix); kOrientationDescriptions[i], retina_suffix);
base::FilePath snapshotPath = snapshotsDir.Append(snapshotFilename); base::FilePath snapshot_path = snapshots_dir.Append(snapshot_filename);
snapshotsPaths->push_back(snapshotPath); snapshots_paths->push_back(snapshot_path);
} }
} }
...@@ -232,6 +232,32 @@ bundle_data("resources") { ...@@ -232,6 +232,32 @@ bundle_data("resources") {
] ]
} }
source_set("external_files") {
configs += [ "//build/config/compiler:enable_arc" ]
sources = [
"external_file_controller.h",
"external_file_controller.mm",
"external_file_remover.h",
"external_file_remover_factory.h",
"external_file_remover_factory.mm",
"external_file_remover_impl.h",
"external_file_remover_impl.mm",
]
deps = [
":ui",
"//base",
"//components/bookmarks/browser",
"//components/keyed_service/core",
"//components/keyed_service/ios",
"//components/sessions",
"//ios/chrome/browser",
"//ios/chrome/browser/bookmarks",
"//ios/chrome/browser/browser_state",
"//ios/chrome/browser/sessions",
"//ios/chrome/browser/tabs",
]
}
source_set("ui_internal") { source_set("ui_internal") {
configs += [ "//build/config/compiler:enable_arc" ] configs += [ "//build/config/compiler:enable_arc" ]
sources = [ sources = [
...@@ -245,13 +271,6 @@ source_set("ui_internal") { ...@@ -245,13 +271,6 @@ source_set("ui_internal") {
"browser_view_controller_helper.mm", "browser_view_controller_helper.mm",
"chrome_web_view_factory.h", "chrome_web_view_factory.h",
"chrome_web_view_factory.mm", "chrome_web_view_factory.mm",
"external_file_controller.h",
"external_file_controller.mm",
"external_file_remover.h",
"external_file_remover_factory.h",
"external_file_remover_factory.mm",
"external_file_remover_impl.h",
"external_file_remover_impl.mm",
"fade_truncated_label.h", "fade_truncated_label.h",
"fade_truncated_label.mm", "fade_truncated_label.mm",
"key_commands_provider.h", "key_commands_provider.h",
...@@ -281,7 +300,6 @@ source_set("ui_internal") { ...@@ -281,7 +300,6 @@ source_set("ui_internal") {
"//components/feature_engagement", "//components/feature_engagement",
"//components/image_fetcher/ios", "//components/image_fetcher/ios",
"//components/infobars/core", "//components/infobars/core",
"//components/keyed_service/ios",
"//components/language/ios/browser", "//components/language/ios/browser",
"//components/payments/core", "//components/payments/core",
"//components/prefs", "//components/prefs",
...@@ -414,6 +432,7 @@ source_set("ui_internal") { ...@@ -414,6 +432,7 @@ source_set("ui_internal") {
"//ios/chrome/browser/ui/settings", "//ios/chrome/browser/ui/settings",
] ]
public_deps = [ public_deps = [
":external_files",
"//ios/chrome/browser/ui/side_swipe", "//ios/chrome/browser/ui/side_swipe",
"//ios/chrome/browser/ui/toolbar", "//ios/chrome/browser/ui/toolbar",
] ]
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/bind.h" #include "base/bind.h"
#include "base/callback.h" #include "base/callback.h"
#include "base/logging.h" #include "base/logging.h"
#import "base/mac/bind_objc_block.h"
#include "base/scoped_observer.h" #include "base/scoped_observer.h"
#include "ios/chrome/browser/chrome_url_constants.h" #include "ios/chrome/browser/chrome_url_constants.h"
#import "ios/chrome/browser/sessions/session_ios.h" #import "ios/chrome/browser/sessions/session_ios.h"
...@@ -246,7 +247,9 @@ bool BrowserListSessionServiceImpl::RestoreSession() { ...@@ -246,7 +247,9 @@ bool BrowserListSessionServiceImpl::RestoreSession() {
void BrowserListSessionServiceImpl::ScheduleLastSessionDeletion() { void BrowserListSessionServiceImpl::ScheduleLastSessionDeletion() {
DCHECK(browser_list_) << "ScheduleLastSessionDeletion called after Shutdown."; DCHECK(browser_list_) << "ScheduleLastSessionDeletion called after Shutdown.";
[session_service_ deleteLastSessionFileInDirectory:session_directory_]; [session_service_ deleteLastSessionFileInDirectory:session_directory_
completion:base::BindBlockArc(^{
})];
} }
void BrowserListSessionServiceImpl::ScheduleSaveSession(bool immediately) { void BrowserListSessionServiceImpl::ScheduleSaveSession(bool immediately) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment