Update To 11.40.268.0
[platform/framework/web/crosswalk.git] / src / chrome / browser / chromeos / file_system_provider / provided_file_system_unittest.cc
index 77ed95b..905458d 100644 (file)
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-// TODO(mtomasz): Move these test cases to operations/unmount_unittest.cc.
-
 #include "chrome/browser/chromeos/file_system_provider/provided_file_system.h"
 
 #include <string>
 #include <vector>
 
 #include "base/files/file.h"
+#include "base/memory/ref_counted.h"
 #include "base/memory/scoped_ptr.h"
+#include "base/memory/scoped_vector.h"
 #include "base/run_loop.h"
+#include "base/thread_task_runner_handle.h"
 #include "base/values.h"
 #include "chrome/browser/chromeos/file_system_provider/mount_path_util.h"
+#include "chrome/browser/chromeos/file_system_provider/notification_manager.h"
 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_info.h"
 #include "chrome/browser/chromeos/file_system_provider/provided_file_system_interface.h"
+#include "chrome/browser/chromeos/file_system_provider/provided_file_system_observer.h"
 #include "chrome/browser/chromeos/file_system_provider/request_manager.h"
+#include "chrome/browser/chromeos/file_system_provider/watcher.h"
 #include "chrome/common/extensions/api/file_system_provider.h"
+#include "chrome/common/extensions/api/file_system_provider_internal.h"
 #include "chrome/test/base/testing_profile.h"
 #include "content/public/test/test_browser_thread_bundle.h"
 #include "extensions/browser/event_router.h"
+#include "storage/browser/fileapi/watcher_manager.h"
 #include "testing/gtest/include/gtest/gtest.h"
 
 namespace chromeos {
 namespace file_system_provider {
-
 namespace {
 
+const char kOrigin[] =
+    "chrome-extension://abcabcabcabcabcabcabcabcabcabcabcabca/";
+const char kAnotherOrigin[] =
+    "chrome-extension://efgefgefgefgefgefgefgefgefgefgefgefge/";
 const char kExtensionId[] = "mbflcebpggnecokmikipoihdbecnjfoj";
-const int kExpectedRequestId = 1;
 const char kFileSystemId[] = "camera-pictures";
 const char kDisplayName[] = "Camera Pictures";
+const base::FilePath::CharType kDirectoryPath[] = "/hello/world";
 
+// Fake implementation of the event router, mocking out a real extension.
+// Handles requests and replies with fake answers back to the file system via
+// the request manager.
 class FakeEventRouter : public extensions::EventRouter {
  public:
-  explicit FakeEventRouter(Profile* profile) : EventRouter(profile, NULL) {}
+  FakeEventRouter(Profile* profile, ProvidedFileSystemInterface* file_system)
+      : EventRouter(profile, NULL),
+        file_system_(file_system),
+        reply_result_(base::File::FILE_OK) {}
   virtual ~FakeEventRouter() {}
 
-  virtual void DispatchEventToExtension(const std::string& extension_id,
-                                        scoped_ptr<extensions::Event> event)
-      OVERRIDE {
-    extension_id_ = extension_id;
-    event_ = event.Pass();
+  // Handles an event which would normally be routed to an extension. Instead
+  // replies with a hard coded response.
+  virtual void DispatchEventToExtension(
+      const std::string& extension_id,
+      scoped_ptr<extensions::Event> event) override {
+    ASSERT_TRUE(file_system_);
+    std::string file_system_id;
+    const base::DictionaryValue* dictionary_value = NULL;
+    ASSERT_TRUE(event->event_args->GetDictionary(0, &dictionary_value));
+    EXPECT_TRUE(dictionary_value->GetString("fileSystemId", &file_system_id));
+    EXPECT_EQ(kFileSystemId, file_system_id);
+    int request_id = -1;
+    EXPECT_TRUE(dictionary_value->GetInteger("requestId", &request_id));
+    EXPECT_TRUE(event->event_name == extensions::api::file_system_provider::
+                                         OnAddWatcherRequested::kEventName ||
+                event->event_name == extensions::api::file_system_provider::
+                                         OnRemoveWatcherRequested::kEventName);
+
+    if (reply_result_ == base::File::FILE_OK) {
+      base::ListValue value_as_list;
+      value_as_list.Set(0, new base::StringValue(kFileSystemId));
+      value_as_list.Set(1, new base::FundamentalValue(request_id));
+      value_as_list.Set(2, new base::FundamentalValue(0) /* execution_time */);
+
+      using extensions::api::file_system_provider_internal::
+          OperationRequestedSuccess::Params;
+      scoped_ptr<Params> params(Params::Create(value_as_list));
+      ASSERT_TRUE(params.get());
+      file_system_->GetRequestManager()->FulfillRequest(
+          request_id,
+          RequestValue::CreateForOperationSuccess(params.Pass()),
+          false /* has_more */);
+    } else {
+      file_system_->GetRequestManager()->RejectRequest(
+          request_id, make_scoped_ptr(new RequestValue()), reply_result_);
+    }
   }
 
-  const std::string& extension_id() const { return extension_id_; }
-
-  const extensions::Event* event() const { return event_.get(); }
+  void set_reply_result(base::File::Error result) { reply_result_ = result; }
 
  private:
-  std::string extension_id_;
-  scoped_ptr<extensions::Event> event_;
-
+  ProvidedFileSystemInterface* const file_system_;  // Not owned.
+  base::File::Error reply_result_;
   DISALLOW_COPY_AND_ASSIGN(FakeEventRouter);
 };
 
-class EventLogger {
+// Observes the tested file system.
+class Observer : public ProvidedFileSystemObserver {
  public:
-  EventLogger() {}
-  virtual ~EventLogger() {}
+  class ChangeEvent {
+   public:
+    ChangeEvent(storage::WatcherManager::ChangeType change_type,
+                const ProvidedFileSystemObserver::Changes& changes)
+        : change_type_(change_type), changes_(changes) {}
+    virtual ~ChangeEvent() {}
+
+    storage::WatcherManager::ChangeType change_type() const {
+      return change_type_;
+    }
+    const ProvidedFileSystemObserver::Changes& changes() const {
+      return changes_;
+    }
+
+   private:
+    const storage::WatcherManager::ChangeType change_type_;
+    const ProvidedFileSystemObserver::Changes changes_;
+
+    DISALLOW_COPY_AND_ASSIGN(ChangeEvent);
+  };
+
+  Observer() : list_changed_counter_(0), tag_updated_counter_(0) {}
+
+  // ProvidedFileSystemInterfaceObserver overrides.
+  virtual void OnWatcherChanged(
+      const ProvidedFileSystemInfo& file_system_info,
+      const Watcher& watcher,
+      storage::WatcherManager::ChangeType change_type,
+      const ProvidedFileSystemObserver::Changes& changes,
+      const base::Closure& callback) override {
+    EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+    change_events_.push_back(new ChangeEvent(change_type, changes));
+    base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, callback);
+  }
+
+  virtual void OnWatcherTagUpdated(
+      const ProvidedFileSystemInfo& file_system_info,
+      const Watcher& watcher) override {
+    EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+    ++tag_updated_counter_;
+  }
 
-  void OnStatusCallback(base::File::Error error) {
-    error_.reset(new base::File::Error(error));
+  virtual void OnWatcherListChanged(
+      const ProvidedFileSystemInfo& file_system_info,
+      const Watchers& watchers) override {
+    EXPECT_EQ(kFileSystemId, file_system_info.file_system_id());
+    ++list_changed_counter_;
   }
 
-  base::File::Error* error() { return error_.get(); }
+  int list_changed_counter() const { return list_changed_counter_; }
+  const ScopedVector<ChangeEvent>& change_events() const {
+    return change_events_;
+  }
+  int tag_updated_counter() const { return tag_updated_counter_; }
 
  private:
-  scoped_ptr<base::File::Error> error_;
-  DISALLOW_COPY_AND_ASSIGN(EventLogger);
+  ScopedVector<ChangeEvent> change_events_;
+  int list_changed_counter_;
+  int tag_updated_counter_;
+
+  DISALLOW_COPY_AND_ASSIGN(Observer);
 };
 
+// Stub notification manager, which works in unit tests.
+class StubNotificationManager : public NotificationManagerInterface {
+ public:
+  StubNotificationManager() {}
+  virtual ~StubNotificationManager() {}
+
+  // NotificationManagerInterface overrides.
+  virtual void ShowUnresponsiveNotification(
+      int id,
+      const NotificationCallback& callback) override {}
+  virtual void HideUnresponsiveNotification(int id) override {}
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(StubNotificationManager);
+};
+
+typedef std::vector<base::File::Error> Log;
+typedef std::vector<storage::WatcherManager::ChangeType> NotificationLog;
+
+// Writes a |result| to the |log| vector.
+void LogStatus(Log* log, base::File::Error result) {
+  log->push_back(result);
+}
+
+// Writes an |change_type| to the |notification_log| vector.
+void LogNotification(NotificationLog* notification_log,
+                     storage::WatcherManager::ChangeType change_type) {
+  notification_log->push_back(change_type);
+}
+
 }  // namespace
 
 class FileSystemProviderProvidedFileSystemTest : public testing::Test {
@@ -79,101 +202,631 @@ class FileSystemProviderProvidedFileSystemTest : public testing::Test {
   FileSystemProviderProvidedFileSystemTest() {}
   virtual ~FileSystemProviderProvidedFileSystemTest() {}
 
-  virtual void SetUp() OVERRIDE {
+  virtual void SetUp() override {
     profile_.reset(new TestingProfile);
-    event_router_.reset(new FakeEventRouter(profile_.get()));
-    event_router_->AddEventListener(
-        extensions::api::file_system_provider::OnUnmountRequested::kEventName,
-        NULL,
-        kExtensionId);
-
     const base::FilePath mount_path =
         util::GetMountPath(profile_.get(), kExtensionId, kFileSystemId);
-    file_system_info_.reset(new ProvidedFileSystemInfo(
-        kExtensionId, kFileSystemId, kDisplayName, mount_path));
+    MountOptions mount_options;
+    mount_options.file_system_id = kFileSystemId;
+    mount_options.display_name = kDisplayName;
+    mount_options.supports_notify_tag = true;
+    file_system_info_.reset(
+        new ProvidedFileSystemInfo(kExtensionId, mount_options, mount_path));
     provided_file_system_.reset(
-        new ProvidedFileSystem(event_router_.get(), *file_system_info_.get()));
+        new ProvidedFileSystem(profile_.get(), *file_system_info_.get()));
+    event_router_.reset(
+        new FakeEventRouter(profile_.get(), provided_file_system_.get()));
+    event_router_->AddEventListener(extensions::api::file_system_provider::
+                                        OnAddWatcherRequested::kEventName,
+                                    NULL,
+                                    kExtensionId);
+    event_router_->AddEventListener(extensions::api::file_system_provider::
+                                        OnRemoveWatcherRequested::kEventName,
+                                    NULL,
+                                    kExtensionId);
+    provided_file_system_->SetEventRouterForTesting(event_router_.get());
+    provided_file_system_->SetNotificationManagerForTesting(
+        make_scoped_ptr(new StubNotificationManager));
   }
 
   content::TestBrowserThreadBundle thread_bundle_;
   scoped_ptr<TestingProfile> profile_;
   scoped_ptr<FakeEventRouter> event_router_;
   scoped_ptr<ProvidedFileSystemInfo> file_system_info_;
-  scoped_ptr<ProvidedFileSystemInterface> provided_file_system_;
+  scoped_ptr<ProvidedFileSystem> provided_file_system_;
 };
 
-TEST_F(FileSystemProviderProvidedFileSystemTest, RequestUnmount_Success) {
-  EventLogger logger;
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater) {
+  Log log;
+  base::Closure firstCallback;
+  base::Closure secondCallback;
+
+  {
+    // Auto updater is ref counted, and bound to all callbacks.
+    scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+        base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+
+    firstCallback = auto_updater->CreateCallback();
+    secondCallback = auto_updater->CreateCallback();
+  }
+
+  // Getting out of scope, should not invoke updating if there are pending
+  // callbacks.
+  EXPECT_EQ(0u, log.size());
+
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, firstCallback);
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, log.size());
 
-  provided_file_system_->RequestUnmount(
-      base::Bind(&EventLogger::OnStatusCallback, base::Unretained(logger)));
+  base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, secondCallback);
   base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(1u, log.size());
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater_NoCallbacks) {
+  Log log;
+  {
+    scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+        base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+  }
+  EXPECT_EQ(1u, log.size());
+}
 
-  // Verify that the event has been sent to the providing extension.
-  EXPECT_EQ(kExtensionId, event_router_->extension_id());
-  const extensions::Event* event = event_router_->event();
-  ASSERT_TRUE(event);
-  ASSERT_TRUE(event->event_args);
-  base::ListValue* event_args = event->event_args.get();
-  EXPECT_EQ(2u, event_args->GetSize());
-  std::string file_system_id;
-  EXPECT_TRUE(event_args->GetString(0, &file_system_id));
-  EXPECT_EQ(kFileSystemId, file_system_id);
-
-  // Remember the request id, and verify it is valid.
-  int request_id = 0;
-  EXPECT_TRUE(event_args->GetInteger(1, &request_id));
-  EXPECT_EQ(kExpectedRequestId, request_id);
-
-  // Callback should not be called, yet.
-  EXPECT_FALSE(logger.error());
-
-  // Simulate sending a success response from the providing extension.
-  RequestManager* request_manager = provided_file_system_->GetRequestManager();
-  ASSERT_TRUE(request_manager);
-  scoped_ptr<RequestValue> response;
-  bool reply_result = request_manager->FulfillRequest(
-      request_id, response.Pass(), false /* has_more */);
-  EXPECT_TRUE(reply_result);
-
-  // Callback should be called. Verify the error code.
-  ASSERT_TRUE(logger.error());
-  EXPECT_EQ(base::File::FILE_OK, *logger.error());
+TEST_F(FileSystemProviderProvidedFileSystemTest, AutoUpdater_CallbackIgnored) {
+  Log log;
+  {
+    scoped_refptr<AutoUpdater> auto_updater(new AutoUpdater(
+        base::Bind(&LogStatus, base::Unretained(&log), base::File::FILE_OK)));
+    base::Closure callback = auto_updater->CreateCallback();
+    // The callback gets out of scope, so the ref counted auto updater instance
+    // gets deleted. Still, updating shouldn't be invoked, since the callback
+    // wasn't executed.
+  }
+  base::RunLoop().RunUntilIdle();
+  EXPECT_EQ(0u, log.size());
 }
 
-TEST_F(FileSystemProviderProvidedFileSystemTest, RequestUnmount_Error) {
-  EventLogger logger;
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_NotFound) {
+  Log log;
+  NotificationLog notification_log;
+  Observer observer;
+
+  provided_file_system_->AddObserver(&observer);
+
+  // First, set the extension response to an error.
+  event_router_->set_reply_result(base::File::FILE_ERROR_NOT_FOUND);
 
-  provided_file_system_->RequestUnmount(
-      base::Bind(&EventLogger::OnStatusCallback, base::Unretained(logger)));
+  provided_file_system_->AddWatcher(
+      GURL(kOrigin),
+      base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+      false /* recursive */,
+      false /* persistent */,
+      base::Bind(&LogStatus, base::Unretained(&log)),
+      base::Bind(&LogNotification, base::Unretained(&notification_log)));
   base::RunLoop().RunUntilIdle();
 
-  // Verify that the event has been sent to the providing extension.
-  EXPECT_EQ(kExtensionId, event_router_->extension_id());
-  const extensions::Event* event = event_router_->event();
-  ASSERT_TRUE(event);
-  ASSERT_TRUE(event->event_args);
-  base::ListValue* event_args = event->event_args.get();
-  EXPECT_EQ(2u, event_args->GetSize());
-  std::string file_system_id;
-  EXPECT_TRUE(event_args->GetString(0, &file_system_id));
-  EXPECT_EQ(kFileSystemId, file_system_id);
-
-  // Remember the request id, and verify it is valid.
-  int request_id = 0;
-  EXPECT_TRUE(event_args->GetInteger(1, &request_id));
-  EXPECT_EQ(kExpectedRequestId, request_id);
-
-  // Simulate sending an error response from the providing extension.
-  RequestManager* request_manager = provided_file_system_->GetRequestManager();
-  ASSERT_TRUE(request_manager);
-  bool reply_result = request_manager->RejectRequest(
-      request_id, base::File::FILE_ERROR_NOT_FOUND);
-  EXPECT_TRUE(reply_result);
-
-  // Callback should be called. Verify the error code.
-  ASSERT_TRUE(logger.error());
-  EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, *logger.error());
+  // The directory should not become watched because of an error.
+  ASSERT_EQ(1u, log.size());
+  EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, log[0]);
+  EXPECT_EQ(0u, notification_log.size());
+
+  Watchers* const watchers = provided_file_system_->GetWatchers();
+  EXPECT_EQ(0u, watchers->size());
+
+  // The observer should not be called.
+  EXPECT_EQ(0, observer.list_changed_counter());
+  EXPECT_EQ(0, observer.tag_updated_counter());
+
+  provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher) {
+  Log log;
+  Observer observer;
+
+  provided_file_system_->AddObserver(&observer);
+
+  provided_file_system_->AddWatcher(
+      GURL(kOrigin),
+      base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+      false /* recursive */,
+      true /* persistent */,
+      base::Bind(&LogStatus, base::Unretained(&log)),
+      storage::WatcherManager::NotificationCallback());
+  base::RunLoop().RunUntilIdle();
+
+  ASSERT_EQ(1u, log.size());
+  EXPECT_EQ(base::File::FILE_OK, log[0]);
+  EXPECT_EQ(1, observer.list_changed_counter());
+  EXPECT_EQ(0, observer.tag_updated_counter());
+
+  Watchers* const watchers = provided_file_system_->GetWatchers();
+  ASSERT_EQ(1u, watchers->size());
+  const Watcher& watcher = watchers->begin()->second;
+  EXPECT_EQ(FILE_PATH_LITERAL(kDirectoryPath), watcher.entry_path.value());
+  EXPECT_FALSE(watcher.recursive);
+  EXPECT_EQ("", watcher.last_tag);
+
+  provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_PersistentIllegal) {
+  {
+    // Adding a persistent watcher with a notification callback is not allowed,
+    // as it's basically impossible to restore the callback after a shutdown.
+    Log log;
+    NotificationLog notification_log;
+
+    Observer observer;
+    provided_file_system_->AddObserver(&observer);
+
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        true /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, log[0]);
+    EXPECT_EQ(0, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    provided_file_system_->RemoveObserver(&observer);
+  }
+
+  {
+    // Adding a persistent watcher is not allowed if the file system doesn't
+    // support the notify tag. It's because the notify tag is essential to be
+    // able to recreate notification during shutdown.
+    Log log;
+    Observer observer;
+
+    // Create a provided file system interface, which does not support a notify
+    // tag, though.
+    const base::FilePath mount_path =
+        util::GetMountPath(profile_.get(), kExtensionId, kFileSystemId);
+    MountOptions mount_options;
+    mount_options.file_system_id = kFileSystemId;
+    mount_options.display_name = kDisplayName;
+    mount_options.supports_notify_tag = false;
+    ProvidedFileSystemInfo file_system_info(
+        kExtensionId, mount_options, mount_path);
+    ProvidedFileSystem simple_provided_file_system(profile_.get(),
+                                                   file_system_info);
+    simple_provided_file_system.SetEventRouterForTesting(event_router_.get());
+    simple_provided_file_system.SetNotificationManagerForTesting(
+        make_scoped_ptr(new StubNotificationManager));
+
+    simple_provided_file_system.AddObserver(&observer);
+
+    simple_provided_file_system.AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        true /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        storage::WatcherManager::NotificationCallback());
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_ERROR_INVALID_OPERATION, log[0]);
+    EXPECT_EQ(0, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    simple_provided_file_system.RemoveObserver(&observer);
+  }
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_Exists) {
+  Observer observer;
+  provided_file_system_->AddObserver(&observer);
+
+  {
+    // First watch a directory not recursively.
+    Log log;
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        true /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        storage::WatcherManager::NotificationCallback());
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(1, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    ASSERT_TRUE(watchers);
+    ASSERT_EQ(1u, watchers->size());
+    const auto& watcher_it = watchers->find(
+        WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+                   false /* recursive */));
+    ASSERT_NE(watchers->end(), watcher_it);
+
+    EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+    const auto& subscriber_it =
+        watcher_it->second.subscribers.find(GURL(kOrigin));
+    ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+    EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+    EXPECT_TRUE(subscriber_it->second.persistent);
+  }
+
+  {
+    // Create another non-recursive observer. That should fail.
+    Log log;
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        true /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        storage::WatcherManager::NotificationCallback());
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_ERROR_EXISTS, log[0]);
+    EXPECT_EQ(1, observer.list_changed_counter());  // No changes on the list.
+    EXPECT_EQ(0, observer.tag_updated_counter());
+  }
+
+  {
+    // Lastly, create another recursive observer. That should succeed.
+    Log log;
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        true /* recursive */,
+        true /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        storage::WatcherManager::NotificationCallback());
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(2, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+  }
+
+  provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, AddWatcher_MultipleOrigins) {
+  Observer observer;
+  provided_file_system_->AddObserver(&observer);
+
+  {
+    // First watch a directory not recursively.
+    Log log;
+    NotificationLog notification_log;
+
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        false /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(1, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+    EXPECT_EQ(0u, notification_log.size());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    ASSERT_TRUE(watchers);
+    ASSERT_EQ(1u, watchers->size());
+    const auto& watcher_it = watchers->find(
+        WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+                   false /* recursive */));
+    ASSERT_NE(watchers->end(), watcher_it);
+
+    EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+    const auto& subscriber_it =
+        watcher_it->second.subscribers.find(GURL(kOrigin));
+    ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+    EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+    EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+    EXPECT_FALSE(subscriber_it->second.persistent);
+  }
+
+  {
+    // Create another watcher, but recursive and with a different origin.
+    Log log;
+    NotificationLog notification_log;
+
+    provided_file_system_->AddWatcher(
+        GURL(kAnotherOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        true /* recursive */,
+        false /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(2, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+    EXPECT_EQ(0u, notification_log.size());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    ASSERT_TRUE(watchers);
+    ASSERT_EQ(2u, watchers->size());
+    const auto& watcher_it = watchers->find(
+        WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+                   false /* recursive */));
+    ASSERT_NE(watchers->end(), watcher_it);
+
+    EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+    const auto& subscriber_it =
+        watcher_it->second.subscribers.find(GURL(kOrigin));
+    ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+    EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+    EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+    EXPECT_FALSE(subscriber_it->second.persistent);
+  }
+
+  {
+    // Remove the second watcher gracefully.
+    Log log;
+    provided_file_system_->RemoveWatcher(
+        GURL(kAnotherOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        true /* recursive */,
+        base::Bind(&LogStatus, base::Unretained(&log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(3, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    ASSERT_TRUE(watchers);
+    EXPECT_EQ(1u, watchers->size());
+    const auto& watcher_it = watchers->find(
+        WatcherKey(base::FilePath(FILE_PATH_LITERAL(kDirectoryPath)),
+                   false /* recursive */));
+    ASSERT_NE(watchers->end(), watcher_it);
+
+    EXPECT_EQ(1u, watcher_it->second.subscribers.size());
+    const auto& subscriber_it =
+        watcher_it->second.subscribers.find(GURL(kOrigin));
+    ASSERT_NE(watcher_it->second.subscribers.end(), subscriber_it);
+    EXPECT_EQ(kOrigin, subscriber_it->first.spec());
+    EXPECT_EQ(kOrigin, subscriber_it->second.origin.spec());
+    EXPECT_FALSE(subscriber_it->second.persistent);
+  }
+
+  provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, RemoveWatcher) {
+  Observer observer;
+  provided_file_system_->AddObserver(&observer);
+
+  {
+    // First, confirm that removing a watcher which does not exist results in an
+    // error.
+    Log log;
+    provided_file_system_->RemoveWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        base::Bind(&LogStatus, base::Unretained(&log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_ERROR_NOT_FOUND, log[0]);
+    EXPECT_EQ(0, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+  }
+
+  {
+    // Watch a directory not recursively.
+    Log log;
+    NotificationLog notification_log;
+
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        false /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(1, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+    EXPECT_EQ(0u, notification_log.size());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(1u, watchers->size());
+  }
+
+  {
+    // Remove a watcher gracefully.
+    Log log;
+    provided_file_system_->RemoveWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        base::Bind(&LogStatus, base::Unretained(&log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(2, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(0u, watchers->size());
+  }
+
+  {
+    // Confirm that it's possible to watch it again.
+    Log log;
+    NotificationLog notification_log;
+
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        false /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(3, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+    EXPECT_EQ(0u, notification_log.size());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(1u, watchers->size());
+  }
+
+  {
+    // Finally, remove it, but with an error from extension. That should result
+    // in a removed watcher, anyway. The error code should not be passed.
+    event_router_->set_reply_result(base::File::FILE_ERROR_FAILED);
+
+    Log log;
+    provided_file_system_->RemoveWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        base::Bind(&LogStatus, base::Unretained(&log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(4, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(0u, watchers->size());
+  }
+
+  provided_file_system_->RemoveObserver(&observer);
+}
+
+TEST_F(FileSystemProviderProvidedFileSystemTest, Notify) {
+  Observer observer;
+  provided_file_system_->AddObserver(&observer);
+  NotificationLog notification_log;
+
+  {
+    // Watch a directory.
+    Log log;
+
+    provided_file_system_->AddWatcher(
+        GURL(kOrigin),
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        false /* persistent */,
+        base::Bind(&LogStatus, base::Unretained(&log)),
+        base::Bind(&LogNotification, base::Unretained(&notification_log)));
+    base::RunLoop().RunUntilIdle();
+
+    ASSERT_EQ(1u, log.size());
+    EXPECT_EQ(base::File::FILE_OK, log[0]);
+    EXPECT_EQ(1, observer.list_changed_counter());
+    EXPECT_EQ(0, observer.tag_updated_counter());
+    EXPECT_EQ(0u, notification_log.size());
+
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(1u, watchers->size());
+    provided_file_system_->GetWatchers();
+    EXPECT_EQ("", watchers->begin()->second.last_tag);
+  }
+
+  {
+    // Notify about a change.
+    const storage::WatcherManager::ChangeType change_type =
+        storage::WatcherManager::CHANGED;
+    const std::string tag = "hello-world";
+    EXPECT_TRUE(provided_file_system_->Notify(
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        change_type,
+        make_scoped_ptr(new ProvidedFileSystemObserver::Changes),
+        tag));
+
+    // Confirm that the notification callback was called.
+    ASSERT_EQ(1u, notification_log.size());
+    EXPECT_EQ(change_type, notification_log[0]);
+
+    // Verify the observer event.
+    ASSERT_EQ(1u, observer.change_events().size());
+    const Observer::ChangeEvent* const change_event =
+        observer.change_events()[0];
+    EXPECT_EQ(change_type, change_event->change_type());
+    EXPECT_EQ(0u, change_event->changes().size());
+
+    // The tag should not be updated in advance, before all observers handle
+    // the notification.
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(1u, watchers->size());
+    provided_file_system_->GetWatchers();
+    EXPECT_EQ("", watchers->begin()->second.last_tag);
+
+    // Wait until all observers finish handling the notification.
+    base::RunLoop().RunUntilIdle();
+
+    // Confirm, that the watcher still exists, and that the tag is updated.
+    ASSERT_EQ(1u, watchers->size());
+    EXPECT_EQ(tag, watchers->begin()->second.last_tag);
+    EXPECT_EQ(1, observer.list_changed_counter());
+    EXPECT_EQ(1, observer.tag_updated_counter());
+  }
+
+  {
+    // Notify about deleting of the watched entry.
+    const storage::WatcherManager::ChangeType change_type =
+        storage::WatcherManager::DELETED;
+    const ProvidedFileSystemObserver::Changes changes;
+    const std::string tag = "chocolate-disco";
+    EXPECT_TRUE(provided_file_system_->Notify(
+        base::FilePath::FromUTF8Unsafe(kDirectoryPath),
+        false /* recursive */,
+        change_type,
+        make_scoped_ptr(new ProvidedFileSystemObserver::Changes),
+        tag));
+    base::RunLoop().RunUntilIdle();
+
+    // Confirm that the notification callback was called.
+    ASSERT_EQ(2u, notification_log.size());
+    EXPECT_EQ(change_type, notification_log[1]);
+
+    // Verify the observer event.
+    ASSERT_EQ(2u, observer.change_events().size());
+    const Observer::ChangeEvent* const change_event =
+        observer.change_events()[1];
+    EXPECT_EQ(change_type, change_event->change_type());
+    EXPECT_EQ(0u, change_event->changes().size());
+  }
+
+  // Confirm, that the watcher is removed.
+  {
+    Watchers* const watchers = provided_file_system_->GetWatchers();
+    EXPECT_EQ(0u, watchers->size());
+    EXPECT_EQ(2, observer.list_changed_counter());
+    EXPECT_EQ(2, observer.tag_updated_counter());
+  }
+
+  provided_file_system_->RemoveObserver(&observer);
 }
 
 }  // namespace file_system_provider