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

#include "components/bookmarks/managed/managed_bookmarks_tracker.h"

#include <memory>
#include <string>

#include "base/check.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/notreached.h"
#include "base/strings/utf_string_conversions.h"
#include "base/uuid.h"
#include "base/values.h"
#include "components/bookmarks/browser/bookmark_model.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/bookmarks/common/bookmark_metrics.h"
#include "components/bookmarks/common/bookmark_pref_names.h"
#include "components/prefs/pref_service.h"
#include "components/strings/grit/components_strings.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"

namespace bookmarks {

const char ManagedBookmarksTracker::kName[] = "name";
const char ManagedBookmarksTracker::kUrl[] = "url";
const char ManagedBookmarksTracker::kChildren[] = "children";
const char ManagedBookmarksTracker::kFolderName[] = "toplevel_name";

ManagedBookmarksTracker::ManagedBookmarksTracker(
    BookmarkModel* model,
    PrefService* prefs,
    GetManagementDomainCallback callback)
    : model_(model),
      managed_node_(nullptr),
      prefs_(prefs),
      get_management_domain_callback_(std::move(callback)) {}

ManagedBookmarksTracker::~ManagedBookmarksTracker() = default;

base::Value::List ManagedBookmarksTracker::GetInitialManagedBookmarks() {
  const base::Value::List& list = prefs_->GetList(prefs::kManagedBookmarks);
  return list.Clone();
}

// static
int64_t ManagedBookmarksTracker::LoadInitial(BookmarkNode* folder,
                                             const base::Value::List& list,
                                             int64_t next_node_id) {
  for (size_t i = 0; i < list.size(); ++i) {
    // Extract the data for the next bookmark from the `list`.
    std::u16string title;
    GURL url;
    const base::Value::List* children = nullptr;
    if (!LoadBookmark(list, i, &title, &url, &children))
      continue;

    BookmarkNode* child = folder->Add(std::make_unique<BookmarkNode>(
        next_node_id++, base::Uuid::GenerateRandomV4(), url));
    child->SetTitle(title);
    if (children) {
      child->set_date_folder_modified(base::Time::Now());
      next_node_id = LoadInitial(child, *children, next_node_id);
    } else {
      child->set_date_added(base::Time::Now());
    }
  }

  return next_node_id;
}

void ManagedBookmarksTracker::Init(BookmarkPermanentNode* managed_node) {
  managed_node_ = managed_node;
  registrar_.Init(prefs_);
  registrar_.Add(
      prefs::kManagedBookmarks,
      base::BindRepeating(&ManagedBookmarksTracker::ReloadManagedBookmarks,
                          base::Unretained(this)));
  registrar_.Add(
      prefs::kManagedBookmarksFolderName,
      base::BindRepeating(
          &ManagedBookmarksTracker::ReloadManagedBookmarksFolderTitle,
          base::Unretained(this)));
  // Reload now just in case something changed since the initial load started.
  // Note that  we must not load managed bookmarks until the cloud policy system
  // has been fully initialized (which will make our preference a managed
  // preference).
  if (prefs_->IsManagedPreference(prefs::kManagedBookmarks))
    ReloadManagedBookmarks();
}

std::u16string ManagedBookmarksTracker::GetBookmarksFolderTitle() const {
  std::string name = prefs_->GetString(prefs::kManagedBookmarksFolderName);
  if (!name.empty())
    return base::UTF8ToUTF16(name);

  const std::string domain = get_management_domain_callback_.Run();
  if (domain.empty()) {
    return l10n_util::GetStringUTF16(
        IDS_BOOKMARK_BAR_MANAGED_FOLDER_DEFAULT_NAME);
  }
  return l10n_util::GetStringFUTF16(IDS_BOOKMARK_BAR_MANAGED_FOLDER_DOMAIN_NAME,
                                    base::UTF8ToUTF16(domain));
}

void ManagedBookmarksTracker::ReloadManagedBookmarksFolderTitle() {
  model_->SetTitle(managed_node_, GetBookmarksFolderTitle(),
                   bookmarks::metrics::BookmarkEditSource::kOther);
}

void ManagedBookmarksTracker::ReloadManagedBookmarks() {
  // In case the user just signed into or out of the account.
  ReloadManagedBookmarksFolderTitle();
  // Recursively update all the managed bookmarks and folders.
  const base::Value::List& list = prefs_->GetList(prefs::kManagedBookmarks);
  UpdateBookmarks(managed_node_, list);
}

void ManagedBookmarksTracker::UpdateBookmarks(const BookmarkNode* folder,
                                              const base::Value::List& list) {
  size_t folder_index = 0;
  for (size_t i = 0; i < list.size(); ++i) {
    // Extract the data for the next bookmark from the `list`.
    std::u16string title;
    GURL url;
    const base::Value::List* children = nullptr;
    if (!LoadBookmark(list, i, &title, &url, &children)) {
      // Skip this bookmark from `list` but don't advance `folder_index`.
      continue;
    }

    // Look for a bookmark at `folder_index` or ahead that matches the current
    // bookmark from the pref.
    const auto matches_current = [&title, &url, children](const auto& node) {
      return node->GetTitle() == title &&
             (children ? node->is_folder() : (node->url() == url));
    };
    const auto j = std::find_if(folder->children().cbegin() + folder_index,
                                folder->children().cend(), matches_current);
    if (j != folder->children().cend()) {
      // Reuse the existing node. The Move() is a nop if `existing` is already
      // at `folder_index`.
      const BookmarkNode* existing = j->get();
      model_->Move(existing, folder, folder_index);
      if (children)
        UpdateBookmarks(existing, *children);
    } else if (children) {
      UpdateBookmarks(model_->AddFolder(folder, folder_index, title),
                      *children);
    } else {
      model_->AddURL(folder, folder_index, title, url);
    }

    // The `folder_index` index of `folder` has been updated, so advance it.
    ++folder_index;
  }

  // Remove any extra children of `folder` that haven't been reused.
  while (folder->children().size() != folder_index)
    model_->Remove(folder->children()[folder_index].get(),
                   bookmarks::metrics::BookmarkEditSource::kOther, FROM_HERE);
}

// static
bool ManagedBookmarksTracker::LoadBookmark(const base::Value::List& list,
                                           size_t index,
                                           std::u16string* title,
                                           GURL* url,
                                           const base::Value::List** children) {
  *url = GURL();
  *children = nullptr;
  const base::Value::Dict* dict = list[index].GetIfDict();
  if (!dict) {
    // Should never happen after policy validation.
    NOTREACHED();
  }
  const std::string* name = dict->FindString(kName);
  const std::string* spec = dict->FindString(kUrl);
  const base::Value::List* children_list = dict->FindList(kChildren);
  if (!name || (!spec && !children_list)) {
    // Should never happen after policy validation.
    NOTREACHED();
  }

  *title = base::UTF8ToUTF16(*name);
  *children = children_list;
  if (!*children) {
    *url = GURL(*spec);
    DCHECK(url->is_valid());
  }
  return true;
}

}  // namespace bookmarks