blob: 41080349204b9fbf8eff02c17177151803bc4746 [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/web_applications/externally_installed_web_app_prefs.h"
#include <string>
#include <utility>
#include <vector>
#include "base/values.h"
#include "chrome/browser/web_applications/web_app_registrar.h"
#include "chrome/common/pref_names.h"
#include "components/pref_registry/pref_registry_syncable.h"
#include "components/prefs/pref_service.h"
#include "components/prefs/scoped_user_pref_update.h"
#include "content/public/browser/browser_thread.h"
#include "url/gurl.h"
namespace web_app {
namespace {
// The stored preferences look like:
//
// "web_apps": {
// "extension_ids": {
// "https://events.google.com/io2016/?utm_source=web_app_manifest": {
// "extension_id": "mjgafbdfajpigcjmkgmeokfbodbcfijl",
// "install_source": 1,
// "is_placeholder": true,
// },
// "https://www.chromestatus.com/features": {
// "extension_id": "fedbieoalmbobgfjapopkghdmhgncnaa",
// "install_source": 1
// }
// }
// }
//
// From the top, prefs::kWebAppsExtensionIDs is "web_apps.extension_ids".
//
// Two levels in is a dictionary (key/value pairs) whose keys are URLs and
// values are leaf dictionaries. Those leaf dictionaries have keys such as
// kExtensionId and kInstallSource.
// The name "extension_id" comes from when PWAs were only backed by the
// Extension system rather than their own. It cannot be changed now that it
// lives persistently in users' profiles.
constexpr char kExtensionId[] = "extension_id";
constexpr char kInstallSource[] = "install_source";
constexpr char kIsPlaceholder[] = "is_placeholder";
// Returns the base::Value in |pref_service| corresponding to our stored dict
// for |app_id|, or nullptr if it doesn't exist.
const base::Value* GetPreferenceValue(const PrefService* pref_service,
const AppId& app_id) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* urls_to_dicts =
pref_service->GetDictionary(prefs::kWebAppsExtensionIDs);
if (!urls_to_dicts) {
return nullptr;
}
// Do a simple O(N) scan for app_id being a value in each dictionary's
// key/value pairs. We expect both N and the number of times
// GetPreferenceValue is called to be relatively small in practice. If they
// turn out to be large, we can write a more sophisticated implementation.
for (auto it : urls_to_dicts->DictItems()) {
const base::Value* root = &it.second;
const base::Value* v = root;
if (v->is_dict()) {
v = v->FindKey(kExtensionId);
if (v && v->is_string() && (v->GetString() == app_id)) {
return root;
}
}
}
return nullptr;
}
} // namespace
// static
void ExternallyInstalledWebAppPrefs::RegisterProfilePrefs(
user_prefs::PrefRegistrySyncable* registry) {
registry->RegisterDictionaryPref(prefs::kWebAppsExtensionIDs);
}
// static
bool ExternallyInstalledWebAppPrefs::HasAppId(const PrefService* pref_service,
const AppId& app_id) {
return GetPreferenceValue(pref_service, app_id) != nullptr;
}
// static
// TODO(crbug.com/1236159): Can be removed after M99.
void ExternallyInstalledWebAppPrefs::RemoveTerminalPWA(
PrefService* pref_service) {
DictionaryPrefUpdate update(pref_service, prefs::kWebAppsExtensionIDs);
update->RemoveKey("chrome-untrusted://terminal/html/pwa.html");
}
// static
bool ExternallyInstalledWebAppPrefs::HasAppIdWithInstallSource(
const PrefService* pref_service,
const AppId& app_id,
ExternalInstallSource install_source) {
const base::Value* v = GetPreferenceValue(pref_service, app_id);
if (v == nullptr || !v->is_dict())
return false;
v = v->FindKeyOfType(kInstallSource, base::Value::Type::INTEGER);
return (v && v->GetInt() == static_cast<int>(install_source));
}
// static
std::map<AppId, GURL> ExternallyInstalledWebAppPrefs::BuildAppIdsMap(
const PrefService* pref_service,
ExternalInstallSource install_source) {
const base::Value* urls_to_dicts =
pref_service->GetDictionary(prefs::kWebAppsExtensionIDs);
std::map<AppId, GURL> ids_to_urls;
if (!urls_to_dicts) {
return ids_to_urls;
}
for (auto it : urls_to_dicts->DictItems()) {
const base::Value* v = &it.second;
if (!v->is_dict()) {
continue;
}
const base::Value* install_source_value =
v->FindKeyOfType(kInstallSource, base::Value::Type::INTEGER);
if (!install_source_value ||
(install_source_value->GetInt() != static_cast<int>(install_source))) {
continue;
}
v = v->FindKey(kExtensionId);
if (!v || !v->is_string()) {
continue;
}
GURL url(it.first);
DCHECK(url.is_valid() && !url.is_empty());
ids_to_urls[v->GetString()] = url;
}
return ids_to_urls;
}
ExternallyInstalledWebAppPrefs::ExternallyInstalledWebAppPrefs(
PrefService* pref_service)
: pref_service_(pref_service) {}
void ExternallyInstalledWebAppPrefs::Insert(
const GURL& url,
const AppId& app_id,
ExternalInstallSource install_source) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
base::Value dict(base::Value::Type::DICTIONARY);
dict.SetKey(kExtensionId, base::Value(app_id));
dict.SetKey(kInstallSource, base::Value(static_cast<int>(install_source)));
DictionaryPrefUpdate update(pref_service_, prefs::kWebAppsExtensionIDs);
update->SetKey(url.spec(), std::move(dict));
}
bool ExternallyInstalledWebAppPrefs::Remove(const GURL& url) {
DictionaryPrefUpdate update(pref_service_, prefs::kWebAppsExtensionIDs);
return update->RemoveKey(url.spec());
}
absl::optional<AppId> ExternallyInstalledWebAppPrefs::LookupAppId(
const GURL& url) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* v =
pref_service_->GetDictionary(prefs::kWebAppsExtensionIDs)
->FindKey(url.spec());
if (v && v->is_dict()) {
v = v->FindKey(kExtensionId);
if (v && v->is_string()) {
return absl::make_optional(v->GetString());
}
}
return absl::nullopt;
}
bool ExternallyInstalledWebAppPrefs::HasNoApps() const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* dict =
pref_service_->GetDictionary(prefs::kWebAppsExtensionIDs);
return dict->DictEmpty();
}
absl::optional<AppId> ExternallyInstalledWebAppPrefs::LookupPlaceholderAppId(
const GURL& url) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* entry =
pref_service_->GetDictionary(prefs::kWebAppsExtensionIDs)
->FindKey(url.spec());
if (!entry)
return absl::nullopt;
absl::optional<bool> is_placeholder = entry->FindBoolKey(kIsPlaceholder);
if (!is_placeholder.has_value() || !is_placeholder.value())
return absl::nullopt;
return *entry->FindStringKey(kExtensionId);
}
void ExternallyInstalledWebAppPrefs::SetIsPlaceholder(const GURL& url,
bool is_placeholder) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(pref_service_->GetDictionary(prefs::kWebAppsExtensionIDs)
->FindKey(url.spec()));
DictionaryPrefUpdate update(pref_service_, prefs::kWebAppsExtensionIDs);
base::Value* map = update.Get();
auto* app_entry = map->FindKey(url.spec());
DCHECK(app_entry);
app_entry->SetBoolKey(kIsPlaceholder, is_placeholder);
}
bool ExternallyInstalledWebAppPrefs::IsPlaceholderApp(
const AppId& app_id) const {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
const base::Value* app_prefs = GetPreferenceValue(pref_service_, app_id);
if (!app_prefs || !app_prefs->is_dict())
return false;
return app_prefs->FindBoolKey(kIsPlaceholder).value_or(false);
}
} // namespace web_app