910e62b5创建于 1月15日历史提交
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/devtools/devtools_file_watcher.h"

#include <algorithm>
#include <map>
#include <memory>
#include <set>
#include <unordered_map>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_path_watcher.h"
#include "base/functional/bind.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/stringprintf.h"
#include "base/task/lazy_thread_pool_task_runner.h"
#include "base/task/sequenced_task_runner.h"
#include "base/time/time.h"
#include "base/trace_event/memory_dump_manager.h"
#include "base/trace_event/memory_dump_provider.h"
#include "content/public/browser/browser_thread.h"

using content::BrowserThread;

static constexpr int kFirstThrottleTimeout = 10;
static constexpr int kDefaultThrottleTimeout = 200;

// DevToolsFileWatcher::SharedFileWatcher --------------------------------------

class DevToolsFileWatcher::SharedFileWatcher
    : public base::RefCounted<SharedFileWatcher>,
      public base::trace_event::MemoryDumpProvider {
 public:
  SharedFileWatcher();

  void AddListener(DevToolsFileWatcher* watcher);
  void RemoveListener(DevToolsFileWatcher* watcher);
  void AddWatch(const base::FilePath& path);
  void RemoveWatch(const base::FilePath& path);

  // base::trace_event::MemoryDumpProvider implementation:
  bool OnMemoryDump(
      const base::trace_event::MemoryDumpArgs& args,
      base::trace_event::ProcessMemoryDump* process_memory_dump) override;

 private:
  friend class base::RefCounted<SharedFileWatcher>;
  ~SharedFileWatcher() override;

  using FilePathTimesMap = std::unordered_map<base::FilePath, base::Time>;
  FilePathTimesMap GetModificationTimes(const base::FilePath& path);
  void DirectoryChanged(const base::FilePath& path, bool error);
  void DispatchNotifications();

  std::vector<raw_ptr<DevToolsFileWatcher, VectorExperimental>> listeners_;
  std::map<base::FilePath, std::unique_ptr<base::FilePathWatcher>> watchers_;
  std::map<base::FilePath, FilePathTimesMap> file_path_times_;
  std::set<base::FilePath> pending_paths_;
  base::Time last_event_time_;
  base::TimeDelta last_dispatch_cost_;
  SEQUENCE_CHECKER(sequence_checker_);
  base::WeakPtrFactory<SharedFileWatcher> weak_factory_{this};
};

DevToolsFileWatcher::SharedFileWatcher::SharedFileWatcher()
    : last_dispatch_cost_(base::Milliseconds(kDefaultThrottleTimeout)) {
  DevToolsFileWatcher::s_shared_watcher_ = this;
  base::trace_event::MemoryDumpManager::GetInstance()
      ->RegisterDumpProviderWithSequencedTaskRunner(
          this, "DevTools", base::SequencedTaskRunner::GetCurrentDefault(),
          base::trace_event::MemoryDumpProvider::Options());
}

DevToolsFileWatcher::SharedFileWatcher::~SharedFileWatcher() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  base::trace_event::MemoryDumpManager::GetInstance()->UnregisterDumpProvider(
      this);
  DevToolsFileWatcher::s_shared_watcher_ = nullptr;
}

bool DevToolsFileWatcher::SharedFileWatcher::OnMemoryDump(
    const base::trace_event::MemoryDumpArgs& args,
    base::trace_event::ProcessMemoryDump* process_memory_dump) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  int index = 0;
  for (auto& file_path : file_path_times_) {
    size_t file_paths_size = 0;
    for (auto& path_and_time : file_path.second) {
      file_paths_size += path_and_time.first.value().length() *
                         sizeof(base::FilePath::StringType::value_type);
    }
    auto* dump = process_memory_dump->CreateAllocatorDump(
        base::StringPrintf("devtools/file_watcher_0x%x", index++));
    dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount,
                    base::trace_event::MemoryAllocatorDump::kUnitsObjects,
                    file_path.second.size());
    dump->AddScalar(base::trace_event::MemoryAllocatorDump::kNameSize,
                    base::trace_event::MemoryAllocatorDump::kUnitsBytes,
                    file_paths_size);
  }
  return true;
}

void DevToolsFileWatcher::SharedFileWatcher::AddListener(
    DevToolsFileWatcher* watcher) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  listeners_.push_back(watcher);
}

void DevToolsFileWatcher::SharedFileWatcher::RemoveListener(
    DevToolsFileWatcher* watcher) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  auto it = std::ranges::find(listeners_, watcher);
  listeners_.erase(it);
  if (listeners_.empty()) {
    file_path_times_.clear();
    pending_paths_.clear();
  }
}

void DevToolsFileWatcher::SharedFileWatcher::AddWatch(
    const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (watchers_.find(path) != watchers_.end())
    return;
  if (!base::FilePathWatcher::RecursiveWatchAvailable())
    return;
  watchers_[path] = std::make_unique<base::FilePathWatcher>();
  bool success = watchers_[path]->Watch(
      path, base::FilePathWatcher::Type::kRecursive,
      base::BindRepeating(&SharedFileWatcher::DirectoryChanged,
                          weak_factory_.GetWeakPtr()));
  if (!success)
    return;

  file_path_times_[path] = GetModificationTimes(path);
}

DevToolsFileWatcher::SharedFileWatcher::FilePathTimesMap
DevToolsFileWatcher::SharedFileWatcher::GetModificationTimes(
    const base::FilePath& path) {
  FilePathTimesMap times_map;
  base::FileEnumerator enumerator(path, true, base::FileEnumerator::FILES);
  base::FilePath file_path = enumerator.Next();
  while (!file_path.empty()) {
    base::FileEnumerator::FileInfo file_info = enumerator.GetInfo();
    times_map[std::move(file_path)] = file_info.GetLastModifiedTime();
    file_path = enumerator.Next();
  }
  return times_map;
}

void DevToolsFileWatcher::SharedFileWatcher::RemoveWatch(
    const base::FilePath& path) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  watchers_.erase(path);
  file_path_times_.erase(path);
}

void DevToolsFileWatcher::SharedFileWatcher::DirectoryChanged(
    const base::FilePath& path,
    bool error) {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  pending_paths_.insert(path);
  if (pending_paths_.size() > 1)
    return;  // PostDelayedTask is already pending.

  base::Time now = base::Time::Now();
  // Quickly dispatch first chunk.
  base::TimeDelta shedule_for = now - last_event_time_ > last_dispatch_cost_
                                    ? base::Milliseconds(kFirstThrottleTimeout)
                                    : last_dispatch_cost_ * 2;

  base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
      FROM_HERE,
      base::BindOnce(
          &DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications,
          weak_factory_.GetWeakPtr()),
      shedule_for);
  last_event_time_ = now;
}

void DevToolsFileWatcher::SharedFileWatcher::DispatchNotifications() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  if (pending_paths_.empty())
    return;
  base::Time start = base::Time::Now();
  std::vector<std::string> added_paths;
  std::vector<std::string> removed_paths;
  std::vector<std::string> changed_paths;

  for (const auto& pending_path : pending_paths_) {
    FilePathTimesMap& old_times = file_path_times_[pending_path];
    FilePathTimesMap current_times = GetModificationTimes(pending_path);
    for (const auto& path_time : current_times) {
      const base::FilePath& path = path_time.first;
      auto old_timestamp = old_times.find(path);
      if (old_timestamp == old_times.end())
        added_paths.push_back(path.AsUTF8Unsafe());
      else if (old_timestamp->second != path_time.second)
        changed_paths.push_back(path.AsUTF8Unsafe());
    }
    for (const auto& path_time : old_times) {
      const base::FilePath& path = path_time.first;
      if (current_times.find(path) == current_times.end())
        removed_paths.push_back(path.AsUTF8Unsafe());
    }
    old_times.swap(current_times);
  }
  pending_paths_.clear();

  for (DevToolsFileWatcher* watcher : listeners_) {
    watcher->client_task_runner_->PostTask(
        FROM_HERE, base::BindOnce(watcher->callback_, changed_paths,
                                  added_paths, removed_paths));
  }
  last_dispatch_cost_ = base::Time::Now() - start;
}

// DevToolsFileWatcher ---------------------------------------------------------

namespace {
base::SequencedTaskRunner* impl_task_runner() {
  constexpr base::TaskTraits kImplTaskTraits = {
      base::MayBlock(), base::TaskPriority::BEST_EFFORT};
  static base::LazyThreadPoolSequencedTaskRunner s_file_task_runner =
      LAZY_THREAD_POOL_SEQUENCED_TASK_RUNNER_INITIALIZER(kImplTaskTraits);
  return s_file_task_runner.Get().get();
}
}  // namespace

// static
DevToolsFileWatcher::SharedFileWatcher* DevToolsFileWatcher::s_shared_watcher_;

// static
void DevToolsFileWatcher::Deleter::operator()(const DevToolsFileWatcher* ptr) {
  impl_task_runner()->PostTask(
      FROM_HERE,
      base::BindOnce(&DevToolsFileWatcher::Destroy, base::Unretained(ptr)));
}

DevToolsFileWatcher::DevToolsFileWatcher(
    WatchCallback callback,
    scoped_refptr<base::SequencedTaskRunner> callback_task_runner)
    : callback_(std::move(callback)),
      client_task_runner_(std::move(callback_task_runner)) {
  impl_task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DevToolsFileWatcher::InitSharedWatcher,
                                base::Unretained(this)));
}

DevToolsFileWatcher::~DevToolsFileWatcher() {
  DCHECK(impl_task_runner()->RunsTasksInCurrentSequence());
  shared_watcher_->RemoveListener(this);
}

void DevToolsFileWatcher::InitSharedWatcher() {
  if (!DevToolsFileWatcher::s_shared_watcher_)
    new SharedFileWatcher();
  shared_watcher_ = DevToolsFileWatcher::s_shared_watcher_;
  shared_watcher_->AddListener(this);
}

void DevToolsFileWatcher::AddWatch(base::FilePath path) {
  impl_task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DevToolsFileWatcher::AddWatchOnImpl,
                                base::Unretained(this), std::move(path)));
}

void DevToolsFileWatcher::RemoveWatch(base::FilePath path) {
  impl_task_runner()->PostTask(
      FROM_HERE, base::BindOnce(&DevToolsFileWatcher::RemoveWatchOnImpl,
                                base::Unretained(this), std::move(path)));
}

void DevToolsFileWatcher::AddWatchOnImpl(base::FilePath path) {
  shared_watcher_->AddWatch(std::move(path));
}

void DevToolsFileWatcher::RemoveWatchOnImpl(base::FilePath path) {
  shared_watcher_->RemoveWatch(std::move(path));
}