910e62b5创建于 1月15日历史提交
// Copyright 2021 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_settings.h"

#include "base/strings/to_string.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/pref_names.h"
#include "components/prefs/scoped_user_pref_update.h"

const char DevToolsSettings::kSyncDevToolsPreferencesFrontendName[] =
    "sync-preferences";
const bool DevToolsSettings::kSyncDevToolsPreferencesDefault = false;

DevToolsSettings::DevToolsSettings(Profile* profile) : profile_(profile) {
  pref_change_registrar_.Init(profile_->GetPrefs());
  pref_change_registrar_.Add(
      prefs::kDevToolsSyncPreferences,
      base::BindRepeating(&DevToolsSettings::DevToolsSyncPreferencesChanged,
                          base::Unretained(this)));
}

DevToolsSettings::~DevToolsSettings() = default;

void DevToolsSettings::Register(const std::string& name,
                                const RegisterOptions& options) {
  // kSyncDevToolsPreferenceFrontendName is not stored in any of the relevant
  // dictionaries. Skip registration.
  if (name == kSyncDevToolsPreferencesFrontendName)
    return;

  if (options.sync_mode == RegisterOptions::SyncMode::kSync) {
    synced_setting_names_.insert(name);
  }

  // Setting might have had a different sync status in the past. Move the
  // setting to the correct dictionary.
  PrefService* prefs = profile_->GetPrefs();
  const char* dictionary_to_remove_from =
      options.sync_mode == RegisterOptions::SyncMode::kSync
          ? prefs::kDevToolsPreferences
          : GetDictionaryNameForSyncedPrefs();
  const std::string* settings_value =
      prefs->GetDict(dictionary_to_remove_from).FindString(name);
  if (!settings_value) {
    return;
  }

  const char* dictionary_to_insert_into =
      GetDictionaryNameForSettingsName(name);
  // Settings already moved to the synced dictionary on a different device have
  // precedence.
  const std::string* already_synced_value =
      prefs->GetDict(dictionary_to_insert_into).FindString(name);
  if (dictionary_to_insert_into == prefs::kDevToolsPreferences ||
      !already_synced_value) {
    ScopedDictPrefUpdate insert_update(profile_->GetPrefs(),
                                       dictionary_to_insert_into);
    insert_update->Set(name, *settings_value);
  }

  ScopedDictPrefUpdate remove_update(profile_->GetPrefs(),
                                     dictionary_to_remove_from);
  remove_update->Remove(name);
}

base::Value::Dict DevToolsSettings::Get() {
  base::Value::Dict settings;

  PrefService* prefs = profile_->GetPrefs();
  // DevTools expects any kind of preference to be a string. Parsing is
  // happening on the frontend.
  settings.Set(
      kSyncDevToolsPreferencesFrontendName,
      base::ToString(prefs->GetBoolean(prefs::kDevToolsSyncPreferences)));
  settings.Merge(prefs->GetDict(prefs::kDevToolsPreferences).Clone());
  settings.Merge(prefs->GetDict(GetDictionaryNameForSyncedPrefs()).Clone());

  return settings;
}

std::optional<base::Value> DevToolsSettings::Get(const std::string& name) {
  PrefService* prefs = profile_->GetPrefs();
  if (name == kSyncDevToolsPreferencesFrontendName) {
    // DevTools expects any kind of preference to be a string. Parsing is
    // happening on the frontend.
    bool result = prefs->GetBoolean(prefs::kDevToolsSyncPreferences);
    return base::Value(base::ToString(result));
  }
  const char* dict_name = GetDictionaryNameForSettingsName(name);
  const base::Value::Dict& dict = prefs->GetDict(dict_name);
  const base::Value* value = dict.Find(name);
  return value ? std::optional<base::Value>(value->Clone()) : std::nullopt;
}

void DevToolsSettings::Set(const std::string& name, const std::string& value) {
  if (name == kSyncDevToolsPreferencesFrontendName) {
    profile_->GetPrefs()->SetBoolean(prefs::kDevToolsSyncPreferences,
                                     value == "true");
    return;
  }

  ScopedDictPrefUpdate update(profile_->GetPrefs(),
                              GetDictionaryNameForSettingsName(name));
  update->Set(name, value);
}

void DevToolsSettings::Remove(const std::string& name) {
  if (name == kSyncDevToolsPreferencesFrontendName) {
    profile_->GetPrefs()->SetBoolean(prefs::kDevToolsSyncPreferences,
                                     kSyncDevToolsPreferencesDefault);
    return;
  }

  PrefService* prefs = profile_->GetPrefs();
  for (auto* dict_name :
       {GetDictionaryNameForSyncedPrefs(), prefs::kDevToolsPreferences}) {
    const base::Value::Dict& dict = prefs->GetDict(dict_name);
    if (dict.Find(name)) {
      ScopedDictPrefUpdate update(profile_->GetPrefs(), dict_name);
      update->Remove(name);
    }
  }
}

void DevToolsSettings::Clear() {
  profile_->GetPrefs()->SetBoolean(prefs::kDevToolsSyncPreferences,
                                   kSyncDevToolsPreferencesDefault);
  profile_->GetPrefs()->SetDict(prefs::kDevToolsPreferences,
                                base::Value::Dict());
  profile_->GetPrefs()->SetDict(prefs::kDevToolsSyncedPreferencesSyncEnabled,
                                base::Value::Dict());
  profile_->GetPrefs()->SetDict(prefs::kDevToolsSyncedPreferencesSyncDisabled,
                                base::Value::Dict());
}

const char* DevToolsSettings::GetDictionaryNameForSettingsName(
    const std::string& name) const {
  return synced_setting_names_.contains(name)
             ? GetDictionaryNameForSyncedPrefs()
             : prefs::kDevToolsPreferences;
}

const char* DevToolsSettings::GetDictionaryNameForSyncedPrefs() const {
  const bool isDevToolsSyncEnabled =
      profile_->GetPrefs()->GetBoolean(prefs::kDevToolsSyncPreferences);
  return isDevToolsSyncEnabled ? prefs::kDevToolsSyncedPreferencesSyncEnabled
                               : prefs::kDevToolsSyncedPreferencesSyncDisabled;
}

void DevToolsSettings::DevToolsSyncPreferencesChanged() {
  // There are two cases to handle:
  //
  // Sync was enabled: We assume this was triggered by the user in the local
  // DevTools session as opposed to synced from a different device. As such, the
  // local settings have precedence when merging. The unsynced dictonary is
  // cleared.
  //
  // Sync was disabled: As kDevToolsSyncPreferences is synced itself we can
  // clear the synced dictionary after copying to the unsynced one. The unsynced
  // dictionary is empty.
  //
  // Considering the points above, the implementation between both cases can be
  // shared modulo the source/target dictionary.
  const bool sync_enabled =
      profile_->GetPrefs()->GetBoolean(prefs::kDevToolsSyncPreferences);
  const char* target_dictionary =
      sync_enabled ? prefs::kDevToolsSyncedPreferencesSyncEnabled
                   : prefs::kDevToolsSyncedPreferencesSyncDisabled;
  const char* source_dictionary =
      sync_enabled ? prefs::kDevToolsSyncedPreferencesSyncDisabled
                   : prefs::kDevToolsSyncedPreferencesSyncEnabled;

  ScopedDictPrefUpdate source_update(profile_->GetPrefs(), source_dictionary);
  ScopedDictPrefUpdate target_update(profile_->GetPrefs(), target_dictionary);
  base::Value::Dict source_dict;
  std::swap(source_dict, *source_update);
  target_update->Merge(std::move(source_dict));
}