Commit 98820ac7 authored by khmel@chromium.org's avatar khmel@chromium.org Committed by Commit Bot

Unify extension and web default apps filtering.

This unifies using user type filtering for extension based and web
default apps via json file declaration.

DD: https://goto.google.com/web_app_default

webapp support was added here: crrev.com/c/1401508

crrev.com/i/784837 - update default app list for all boards. These
are only apps that has child_users in external_extensions.json
and no managed_users are used currently in external_extensions.json

crrev.com/c/1407458 - update script that generates default extensions.

TEST=Locally, set of apps as expected on nocturne for different user
types. Unit test added
BUG=921121

Change-Id: I655c1e3bf1e9974aed52e0b74d544a7d41450214
Reviewed-on: https://chromium-review.googlesource.com/c/1407498Reviewed-by: default avatarXiyuan Xia <xiyuan@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Commit-Queue: Yury Khmel <khmel@chromium.org>
Cr-Commit-Position: refs/heads/master@{#625784}
parent 2d9a8a0a
...@@ -100,6 +100,8 @@ jumbo_split_static_library("browser") { ...@@ -100,6 +100,8 @@ jumbo_split_static_library("browser") {
"app_controller_mac.mm", "app_controller_mac.mm",
"app_mode/app_mode_utils.cc", "app_mode/app_mode_utils.cc",
"app_mode/app_mode_utils.h", "app_mode/app_mode_utils.h",
"apps/user_type_filter.cc",
"apps/user_type_filter.h",
"assist_ranker/assist_ranker_service_factory.cc", "assist_ranker/assist_ranker_service_factory.cc",
"assist_ranker/assist_ranker_service_factory.h", "assist_ranker/assist_ranker_service_factory.h",
"autocomplete/autocomplete_classifier_factory.cc", "autocomplete/autocomplete_classifier_factory.cc",
......
// 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/apps/user_type_filter.h"
#include "base/values.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/profiles/profile.h"
namespace apps {
namespace {
std::string DetermineUserType(Profile* profile) {
DCHECK(!profile->IsOffTheRecord());
if (profile->IsGuestSession())
return kUserTypeGuest;
if (profile->IsChild())
return kUserTypeChild;
if (profile->IsLegacySupervised())
return kUserTypeSupervised;
if (policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile)
->IsManaged()) {
return kUserTypeManaged;
}
return kUserTypeUnmanaged;
}
} // namespace
// kUserType is required key that specifies enumeration of user types for which
// web app is visible. See kUserType* constants
const char kKeyUserType[] = "user_type";
const char kUserTypeChild[] = "child";
const char kUserTypeGuest[] = "guest";
const char kUserTypeManaged[] = "managed";
const char kUserTypeSupervised[] = "supervised";
const char kUserTypeUnmanaged[] = "unmanaged";
bool ProfileMatchJsonUserType(Profile* profile,
const std::string& app_id,
const base::Value* json_root,
const base::ListValue* default_user_types) {
DCHECK(profile);
DCHECK(json_root);
if (!json_root->is_dict()) {
LOG(ERROR) << "Non-dictionary Json is passed to user type filter for "
<< app_id << ".";
return false;
}
const base::Value* value =
json_root->FindKeyOfType(kKeyUserType, base::Value::Type::LIST);
if (!value) {
if (!default_user_types) {
LOG(ERROR) << "Json has no user type filter for " << app_id << ".";
return false;
}
value = default_user_types;
LOG(WARNING) << "No user type filter specified for " << app_id
<< ". Using default user type filter, please update the app.";
}
bool user_type_match = false;
const std::string user_type = DetermineUserType(profile);
for (const auto& it : value->GetList()) {
if (!it.is_string()) {
LOG(ERROR) << "Invalid user type value for " << app_id << ".";
return false;
}
if (it.GetString() != user_type)
continue;
user_type_match = true;
break;
}
if (!user_type_match) {
VLOG(1) << "Skip " << app_id << ". It does not match user type "
<< user_type << ".";
return false;
}
return true;
}
} // namespace apps
// 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.
#ifndef CHROME_BROWSER_APPS_USER_TYPE_FILTER_H_
#define CHROME_BROWSER_APPS_USER_TYPE_FILTER_H_
#include <string>
class Profile;
namespace base {
class Value;
class ListValue;
} // namespace base
namespace apps {
extern const char kKeyUserType[];
extern const char kUserTypeChild[];
extern const char kUserTypeGuest[];
extern const char kUserTypeManaged[];
extern const char kUserTypeSupervised[];
extern const char kUserTypeUnmanaged[];
// This filter is used to verify that testing profile |profile| matches user
// type filter defined in root Json element |json_root|. |json_root| should have
// the list of acceptable user types. Following values are valid: child, guest,
// managed, supervised, unmanaged. Primary use of this filter is to determine if
// particular default webapp or extension has to be installed for current user.
// Returns true if profile is accepted for this filter. |default_user_types| is
// optional and used to support transition for the extension based default apps.
// |app_id| is used for error logging purpose.
bool ProfileMatchJsonUserType(Profile* profile,
const std::string& app_id,
const base::Value* json_root,
const base::ListValue* default_user_types);
} // namespace apps
#endif // CHROME_BROWSER_APPS_USER_TYPE_FILTER_H_
// 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/apps/user_type_filter.h"
#include <memory>
#include <string>
#include <vector>
#include "base/macros.h"
#include "base/values.h"
#include "chrome/browser/policy/profile_policy_connector.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/supervised_user/supervised_user_constants.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace apps {
namespace {
// Helper that simulates Json file with embedded user type filter.
std::unique_ptr<base::DictionaryValue> CreateJsonWithFilter(
const std::vector<std::string>& user_types) {
auto root = std::make_unique<base::DictionaryValue>();
base::ListValue filter;
for (const auto& user_type : user_types)
filter.GetList().push_back(base::Value(user_type));
root->SetKey(kKeyUserType, std::move(filter));
return root;
}
} // namespace
class UserTypeFilterTest : public testing::Test {
public:
UserTypeFilterTest() = default;
~UserTypeFilterTest() override = default;
protected:
// Helper that creates simple test profile.
std::unique_ptr<TestingProfile> CreateProfile() {
TestingProfile::Builder profile_builder;
return profile_builder.Build();
}
// Helper that creates simple test guest profile.
std::unique_ptr<TestingProfile> CreateGuestProfile() {
TestingProfile::Builder profile_builder;
profile_builder.SetGuestSession();
return profile_builder.Build();
}
bool Match(const std::unique_ptr<TestingProfile>& profile,
const std::unique_ptr<base::Value>& json_root) {
return ProfileMatchJsonUserType(profile.get(), std::string() /* app_id */,
json_root.get(),
nullptr /* default_user_types */);
}
bool MatchDefault(const std::unique_ptr<TestingProfile>& profile,
const base::ListValue& default_user_types) {
base::DictionaryValue json_root;
return ProfileMatchJsonUserType(profile.get(), std::string() /* app_id */,
&json_root, &default_user_types);
}
private:
// To support context of browser threads.
content::TestBrowserThreadBundle thread_bundle_;
DISALLOW_COPY_AND_ASSIGN(UserTypeFilterTest);
};
TEST_F(UserTypeFilterTest, ChildUser) {
const auto profile = CreateProfile();
profile->SetSupervisedUserId(supervised_users::kChildAccountSUID);
EXPECT_FALSE(Match(profile, CreateJsonWithFilter({kUserTypeUnmanaged})));
EXPECT_TRUE(Match(profile, CreateJsonWithFilter({kUserTypeChild})));
EXPECT_TRUE(Match(
profile, CreateJsonWithFilter({kUserTypeUnmanaged, kUserTypeChild})));
}
TEST_F(UserTypeFilterTest, GuestUser) {
auto profile = CreateGuestProfile();
EXPECT_FALSE(Match(profile, CreateJsonWithFilter({kUserTypeUnmanaged})));
EXPECT_TRUE(Match(profile, CreateJsonWithFilter({kUserTypeGuest})));
EXPECT_TRUE(Match(
profile, CreateJsonWithFilter({kUserTypeUnmanaged, kUserTypeGuest})));
}
TEST_F(UserTypeFilterTest, ManagedUser) {
const auto profile = CreateProfile();
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile.get())
->OverrideIsManagedForTesting(true);
EXPECT_FALSE(Match(profile, CreateJsonWithFilter({kUserTypeUnmanaged})));
EXPECT_TRUE(Match(profile, CreateJsonWithFilter({kUserTypeManaged})));
EXPECT_TRUE(Match(
profile, CreateJsonWithFilter({kUserTypeUnmanaged, kUserTypeManaged})));
}
TEST_F(UserTypeFilterTest, SupervisedUser) {
const auto profile = CreateProfile();
profile->SetSupervisedUserId("asdf");
EXPECT_FALSE(Match(profile, CreateJsonWithFilter({kUserTypeUnmanaged})));
EXPECT_TRUE(Match(profile, CreateJsonWithFilter({kUserTypeSupervised})));
EXPECT_TRUE(Match(profile, CreateJsonWithFilter(
{kUserTypeUnmanaged, kUserTypeSupervised})));
}
TEST_F(UserTypeFilterTest, UnmanagedUser) {
EXPECT_TRUE(
Match(CreateProfile(), CreateJsonWithFilter({kUserTypeUnmanaged})));
}
TEST_F(UserTypeFilterTest, EmptyFilter) {
EXPECT_FALSE(Match(CreateProfile(), CreateJsonWithFilter({})));
}
TEST_F(UserTypeFilterTest, DefaultFilter) {
auto profile = CreateProfile();
base::ListValue default_filter;
default_filter.GetList().push_back(base::Value(kUserTypeUnmanaged));
default_filter.GetList().push_back(base::Value(kUserTypeGuest));
// Unmanaged user.
EXPECT_TRUE(MatchDefault(profile, default_filter));
// Guest user.
EXPECT_TRUE(MatchDefault(CreateGuestProfile(), default_filter));
// Child user.
profile->SetSupervisedUserId(supervised_users::kChildAccountSUID);
EXPECT_FALSE(MatchDefault(profile, default_filter));
// Supervised user.
profile->SetSupervisedUserId("asdf");
EXPECT_FALSE(MatchDefault(profile, default_filter));
// Managed user.
profile = CreateProfile();
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile.get())
->OverrideIsManagedForTesting(true);
EXPECT_FALSE(MatchDefault(profile, default_filter));
}
} // namespace apps
...@@ -21,6 +21,7 @@ ...@@ -21,6 +21,7 @@
#include "base/task/lazy_task_runner.h" #include "base/task/lazy_task_runner.h"
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/apps/user_type_filter.h"
#include "chrome/browser/defaults.h" #include "chrome/browser/defaults.h"
#include "chrome/browser/prefs/pref_service_syncable_util.h" #include "chrome/browser/prefs/pref_service_syncable_util.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
...@@ -167,7 +168,7 @@ class ExternalPrefLoader::PrioritySyncReadyWaiter ...@@ -167,7 +168,7 @@ class ExternalPrefLoader::PrioritySyncReadyWaiter
}; };
ExternalPrefLoader::ExternalPrefLoader(int base_path_id, ExternalPrefLoader::ExternalPrefLoader(int base_path_id,
Options options, int options,
Profile* profile) Profile* profile)
: base_path_id_(base_path_id), options_(options), profile_(profile) { : base_path_id_(base_path_id), options_(options), profile_(profile) {
DCHECK_CURRENTLY_ON(BrowserThread::UI); DCHECK_CURRENTLY_ON(BrowserThread::UI);
...@@ -317,12 +318,20 @@ void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles( ...@@ -317,12 +318,20 @@ void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
return; return;
} }
// TODO(crbug.com/1407498): Remove this once migration is completed.
std::unique_ptr<base::ListValue> default_user_types;
if (options_ & USE_USER_TYPE_PROFILE_FILTER) {
default_user_types = std::make_unique<base::ListValue>();
default_user_types->GetList().push_back(
base::Value(apps::kUserTypeUnmanaged));
}
// For each file read the json description & build the proper // For each file read the json description & build the proper
// associated prefs. // associated prefs.
for (auto it = candidates.begin(); it != candidates.end(); ++it) { for (auto it = candidates.begin(); it != candidates.end(); ++it) {
base::FilePath extension_candidate_path = base_path_.Append(*it); base::FilePath extension_candidate_path = base_path_.Append(*it);
std::string id = const std::string id =
#if defined(OS_WIN) #if defined(OS_WIN)
base::UTF16ToASCII( base::UTF16ToASCII(
extension_candidate_path.RemoveExtension().BaseName().value()); extension_candidate_path.RemoveExtension().BaseName().value());
...@@ -336,10 +345,19 @@ void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles( ...@@ -336,10 +345,19 @@ void ExternalPrefLoader::ReadStandaloneExtensionPrefFiles(
JSONFileValueDeserializer deserializer(extension_candidate_path); JSONFileValueDeserializer deserializer(extension_candidate_path);
std::unique_ptr<base::DictionaryValue> ext_prefs = std::unique_ptr<base::DictionaryValue> ext_prefs =
ExtractExtensionPrefs(&deserializer, extension_candidate_path); ExtractExtensionPrefs(&deserializer, extension_candidate_path);
if (ext_prefs) { if (!ext_prefs)
DVLOG(1) << "Adding extension with id: " << id; continue;
prefs->Set(id, std::move(ext_prefs));
if (options_ & USE_USER_TYPE_PROFILE_FILTER &&
!apps::ProfileMatchJsonUserType(profile_, id /* app_id */,
ext_prefs.get(),
default_user_types.get())) {
// Already logged.
continue;
} }
DVLOG(1) << "Adding extension with id: " << id;
prefs->Set(id, std::move(ext_prefs));
} }
} }
......
...@@ -34,13 +34,17 @@ class ExternalPrefLoader : public ExternalLoader { ...@@ -34,13 +34,17 @@ class ExternalPrefLoader : public ExternalLoader {
// Delay external preference load. It delays default apps installation // Delay external preference load. It delays default apps installation
// to not overload the system on first time user login. // to not overload the system on first time user login.
DELAY_LOAD_UNTIL_PRIORITY_SYNC = 1 << 1, DELAY_LOAD_UNTIL_PRIORITY_SYNC = 1 << 1,
// Use profile user type filter to load extensions.
USE_USER_TYPE_PROFILE_FILTER = 1 << 2,
}; };
// |base_path_id| is the directory containing the external_extensions.json // |base_path_id| is the directory containing the external_extensions.json
// file or the standalone extension manifest files. Relative file paths to // file or the standalone extension manifest files. Relative file paths to
// extension files are resolved relative to this path. |profile| is used to // extension files are resolved relative to this path. |profile| is used to
// wait priority sync if DELAY_LOAD_UNTIL_PRIORITY_SYNC set. // wait priority sync if DELAY_LOAD_UNTIL_PRIORITY_SYNC set.
ExternalPrefLoader(int base_path_id, Options options, Profile* profile); // |options| is combination of |Options|.
ExternalPrefLoader(int base_path_id, int options, Profile* profile);
const base::FilePath GetBaseCrxFilePath() override; const base::FilePath GetBaseCrxFilePath() override;
...@@ -56,7 +60,7 @@ class ExternalPrefLoader : public ExternalLoader { ...@@ -56,7 +60,7 @@ class ExternalPrefLoader : public ExternalLoader {
// file containing which extensions to load. // file containing which extensions to load.
const int base_path_id_; const int base_path_id_;
const Options options_; const int options_;
private: private:
friend class base::RefCountedThreadSafe<ExternalLoader>; friend class base::RefCountedThreadSafe<ExternalLoader>;
......
...@@ -686,8 +686,9 @@ void ExternalProviderImpl::CreateExternalProviders( ...@@ -686,8 +686,9 @@ void ExternalProviderImpl::CreateExternalProviders(
// In tests don't install extensions from default external sources. // In tests don't install extensions from default external sources.
// It would only slowdown tests and make them flaky. // It would only slowdown tests and make them flaky.
if (base::CommandLine::ForCurrentProcess()->HasSwitch( if (base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kDisableDefaultApps)) ::switches::kDisableDefaultApps)) {
return; return;
}
// On Mac OS, items in /Library/... should be written by the superuser. // On Mac OS, items in /Library/... should be written by the superuser.
// Check that all components of the path are writable by root only. // Check that all components of the path are writable by root only.
...@@ -707,29 +708,17 @@ void ExternalProviderImpl::CreateExternalProviders( ...@@ -707,29 +708,17 @@ void ExternalProviderImpl::CreateExternalProviders(
Extension::WAS_INSTALLED_BY_DEFAULT; Extension::WAS_INSTALLED_BY_DEFAULT;
if (!is_chrome_os_public_session) { if (!is_chrome_os_public_session) {
std::vector<int> external_apps_path_ids; int pref_load_flags =
if (profile->IsChild()) {
external_apps_path_ids.push_back(chrome::DIR_CHILD_USERS_DEFAULT_APPS);
} else if (profile->IsSupervised()) {
external_apps_path_ids.push_back(
chrome::DIR_SUPERVISED_USERS_DEFAULT_APPS);
} else {
external_apps_path_ids.push_back(
chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS);
external_apps_path_ids.push_back(chrome::DIR_CHILD_USERS_DEFAULT_APPS);
}
const ExternalPrefLoader::Options pref_load_flags =
profile->IsNewProfile() profile->IsNewProfile()
? ExternalPrefLoader::DELAY_LOAD_UNTIL_PRIORITY_SYNC ? ExternalPrefLoader::DELAY_LOAD_UNTIL_PRIORITY_SYNC
: ExternalPrefLoader::NONE; : ExternalPrefLoader::NONE;
for (const auto external_apps_path_id : external_apps_path_ids) { pref_load_flags |= ExternalPrefLoader::USE_USER_TYPE_PROFILE_FILTER;
provider_list->push_back(std::make_unique<ExternalProviderImpl>( provider_list->push_back(std::make_unique<ExternalProviderImpl>(
service, service,
new ExternalPrefLoader(external_apps_path_id, pref_load_flags, new ExternalPrefLoader(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
profile), pref_load_flags, profile),
profile, Manifest::EXTERNAL_PREF, Manifest::EXTERNAL_PREF_DOWNLOAD, profile, Manifest::EXTERNAL_PREF, Manifest::EXTERNAL_PREF_DOWNLOAD,
bundled_extension_creation_flags)); bundled_extension_creation_flags));
}
// OEM default apps. // OEM default apps.
int oem_extension_creation_flags = int oem_extension_creation_flags =
...@@ -758,16 +747,14 @@ void ExternalProviderImpl::CreateExternalProviders( ...@@ -758,16 +747,14 @@ void ExternalProviderImpl::CreateExternalProviders(
chromeos::DemoSession::Get()->SetExtensionsExternalLoader(loader); chromeos::DemoSession::Get()->SetExtensionsExternalLoader(loader);
provider_list->push_back(std::move(demo_apps_provider)); provider_list->push_back(std::move(demo_apps_provider));
} }
#elif defined(OS_LINUX) #elif defined(OS_LINUX)
if (!profile->IsLegacySupervised()) { provider_list->push_back(std::make_unique<ExternalProviderImpl>(
provider_list->push_back(std::make_unique<ExternalProviderImpl>( service,
service, new ExternalPrefLoader(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
new ExternalPrefLoader(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS, ExternalPrefLoader::USE_USER_TYPE_PROFILE_FILTER,
ExternalPrefLoader::NONE, nullptr), profile),
profile, Manifest::EXTERNAL_PREF, Manifest::EXTERNAL_PREF_DOWNLOAD, profile, Manifest::EXTERNAL_PREF, Manifest::EXTERNAL_PREF_DOWNLOAD,
bundled_extension_creation_flags)); bundled_extension_creation_flags));
}
#endif #endif
if (!profile->IsLegacySupervised()) { if (!profile->IsLegacySupervised()) {
......
...@@ -51,7 +51,7 @@ constexpr char kAppUnmanagedUrl[] = "https://www.google.com/unmanaged"; ...@@ -51,7 +51,7 @@ constexpr char kAppUnmanagedUrl[] = "https://www.google.com/unmanaged";
// Returns the chrome/test/data/web_app_default_apps/sub_dir directory that // Returns the chrome/test/data/web_app_default_apps/sub_dir directory that
// holds the *.json data files from which ScanDirForExternalWebAppsForTesting // holds the *.json data files from which ScanDirForExternalWebAppsForTesting
// should extract URLs from. // should extract URLs from.
static base::FilePath test_dir(const char* sub_dir) { static base::FilePath test_dir(const std::string& sub_dir) {
base::FilePath dir; base::FilePath dir;
if (!base::PathService::Get(chrome::DIR_TEST_DATA, &dir)) { if (!base::PathService::Get(chrome::DIR_TEST_DATA, &dir)) {
ADD_FAILURE() ADD_FAILURE()
...@@ -64,15 +64,12 @@ using AppInfos = std::vector<web_app::PendingAppManager::AppInfo>; ...@@ -64,15 +64,12 @@ using AppInfos = std::vector<web_app::PendingAppManager::AppInfo>;
} // namespace } // namespace
class ScanDirForExternalWebAppsTest : public testing::Test {}; class ScanDirForExternalWebAppsTest : public testing::Test {
class ScanDirForExternalWebAppsWithProfileTest
: public ScanDirForExternalWebAppsTest {
public: public:
ScanDirForExternalWebAppsWithProfileTest() = default; ScanDirForExternalWebAppsTest() = default;
~ScanDirForExternalWebAppsWithProfileTest() override = default; ~ScanDirForExternalWebAppsTest() override = default;
// ScanDirForExternalWebAppsTest: // testing::Test:
void SetUp() override { void SetUp() override {
testing::Test::SetUp(); testing::Test::SetUp();
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
...@@ -110,6 +107,12 @@ class ScanDirForExternalWebAppsWithProfileTest ...@@ -110,6 +107,12 @@ class ScanDirForExternalWebAppsWithProfileTest
return result; return result;
} }
std::vector<web_app::PendingAppManager::AppInfo>
ScanTestDirForExternalWebApps(const std::string& dir) {
return web_app::ScanDirForExternalWebAppsForTesting(test_dir(dir),
CreateProfile().get());
}
// Helper that creates simple test profile. // Helper that creates simple test profile.
std::unique_ptr<TestingProfile> CreateProfile() { std::unique_ptr<TestingProfile> CreateProfile() {
TestingProfile::Builder profile_builder; TestingProfile::Builder profile_builder;
...@@ -166,21 +169,11 @@ class ScanDirForExternalWebAppsWithProfileTest ...@@ -166,21 +169,11 @@ class ScanDirForExternalWebAppsWithProfileTest
// To support context of browser threads. // To support context of browser threads.
content::TestBrowserThreadBundle thread_bundle_; content::TestBrowserThreadBundle thread_bundle_;
DISALLOW_COPY_AND_ASSIGN(ScanDirForExternalWebAppsWithProfileTest); DISALLOW_COPY_AND_ASSIGN(ScanDirForExternalWebAppsTest);
};
class ScanDirForExternalWebAppsNonPrimaryProfileTest
: public ScanDirForExternalWebAppsTest {
ScanDirForExternalWebAppsNonPrimaryProfileTest() = default;
~ScanDirForExternalWebAppsNonPrimaryProfileTest() override = default;
private:
DISALLOW_COPY_AND_ASSIGN(ScanDirForExternalWebAppsNonPrimaryProfileTest);
}; };
TEST_F(ScanDirForExternalWebAppsTest, GoodJson) { TEST_F(ScanDirForExternalWebAppsTest, GoodJson) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps(kGoodJsonTestDir);
web_app::ScanDirForExternalWebAppsForTesting(test_dir(kGoodJsonTestDir));
// The good_json directory contains two good JSON files: // The good_json directory contains two good JSON files:
// chrome_platform_status.json and google_io_2016.json. // chrome_platform_status.json and google_io_2016.json.
...@@ -213,16 +206,14 @@ TEST_F(ScanDirForExternalWebAppsTest, GoodJson) { ...@@ -213,16 +206,14 @@ TEST_F(ScanDirForExternalWebAppsTest, GoodJson) {
} }
TEST_F(ScanDirForExternalWebAppsTest, BadJson) { TEST_F(ScanDirForExternalWebAppsTest, BadJson) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("bad_json");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("bad_json"));
// The bad_json directory contains one (malformed) JSON file. // The bad_json directory contains one (malformed) JSON file.
EXPECT_EQ(0u, app_infos.size()); EXPECT_EQ(0u, app_infos.size());
} }
TEST_F(ScanDirForExternalWebAppsTest, TxtButNoJson) { TEST_F(ScanDirForExternalWebAppsTest, TxtButNoJson) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("txt_but_no_json");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("txt_but_no_json"));
// The txt_but_no_json directory contains one file, and the contents of that // The txt_but_no_json directory contains one file, and the contents of that
// file is valid JSON, but that file's name does not end with ".json". // file is valid JSON, but that file's name does not end with ".json".
...@@ -230,8 +221,7 @@ TEST_F(ScanDirForExternalWebAppsTest, TxtButNoJson) { ...@@ -230,8 +221,7 @@ TEST_F(ScanDirForExternalWebAppsTest, TxtButNoJson) {
} }
TEST_F(ScanDirForExternalWebAppsTest, MixedJson) { TEST_F(ScanDirForExternalWebAppsTest, MixedJson) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("mixed_json");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("mixed_json"));
// The mixed_json directory contains one empty JSON file, one malformed JSON // The mixed_json directory contains one empty JSON file, one malformed JSON
// file and one good JSON file. ScanDirForExternalWebAppsForTesting should // file and one good JSON file. ScanDirForExternalWebAppsForTesting should
...@@ -244,8 +234,7 @@ TEST_F(ScanDirForExternalWebAppsTest, MixedJson) { ...@@ -244,8 +234,7 @@ TEST_F(ScanDirForExternalWebAppsTest, MixedJson) {
} }
TEST_F(ScanDirForExternalWebAppsTest, MissingAppUrl) { TEST_F(ScanDirForExternalWebAppsTest, MissingAppUrl) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("missing_app_url");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("missing_app_url"));
// The missing_app_url directory contains one JSON file which is correct // The missing_app_url directory contains one JSON file which is correct
// except for a missing "app_url" field. // except for a missing "app_url" field.
...@@ -253,8 +242,7 @@ TEST_F(ScanDirForExternalWebAppsTest, MissingAppUrl) { ...@@ -253,8 +242,7 @@ TEST_F(ScanDirForExternalWebAppsTest, MissingAppUrl) {
} }
TEST_F(ScanDirForExternalWebAppsTest, EmptyAppUrl) { TEST_F(ScanDirForExternalWebAppsTest, EmptyAppUrl) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("empty_app_url");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("empty_app_url"));
// The empty_app_url directory contains one JSON file which is correct // The empty_app_url directory contains one JSON file which is correct
// except for an empty "app_url" field. // except for an empty "app_url" field.
...@@ -262,8 +250,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EmptyAppUrl) { ...@@ -262,8 +250,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EmptyAppUrl) {
} }
TEST_F(ScanDirForExternalWebAppsTest, InvalidAppUrl) { TEST_F(ScanDirForExternalWebAppsTest, InvalidAppUrl) {
const auto app_infos = const auto app_infos = ScanTestDirForExternalWebApps("invalid_app_url");
web_app::ScanDirForExternalWebAppsForTesting(test_dir("invalid_app_url"));
// The invalid_app_url directory contains one JSON file which is correct // The invalid_app_url directory contains one JSON file which is correct
// except for an invalid "app_url" field. // except for an invalid "app_url" field.
...@@ -271,8 +258,8 @@ TEST_F(ScanDirForExternalWebAppsTest, InvalidAppUrl) { ...@@ -271,8 +258,8 @@ TEST_F(ScanDirForExternalWebAppsTest, InvalidAppUrl) {
} }
TEST_F(ScanDirForExternalWebAppsTest, InvalidCreateShortcuts) { TEST_F(ScanDirForExternalWebAppsTest, InvalidCreateShortcuts) {
const auto app_infos = web_app::ScanDirForExternalWebAppsForTesting( const auto app_infos =
test_dir("invalid_create_shortcuts")); ScanTestDirForExternalWebApps("invalid_create_shortcuts");
// The invalid_create_shortcuts directory contains one JSON file which is // The invalid_create_shortcuts directory contains one JSON file which is
// correct except for an invalid "create_shortctus" field. // correct except for an invalid "create_shortctus" field.
...@@ -280,8 +267,8 @@ TEST_F(ScanDirForExternalWebAppsTest, InvalidCreateShortcuts) { ...@@ -280,8 +267,8 @@ TEST_F(ScanDirForExternalWebAppsTest, InvalidCreateShortcuts) {
} }
TEST_F(ScanDirForExternalWebAppsTest, MissingLaunchContainer) { TEST_F(ScanDirForExternalWebAppsTest, MissingLaunchContainer) {
const auto app_infos = web_app::ScanDirForExternalWebAppsForTesting( const auto app_infos =
test_dir("missing_launch_container")); ScanTestDirForExternalWebApps("missing_launch_container");
// The missing_launch_container directory contains one JSON file which is // The missing_launch_container directory contains one JSON file which is
// correct except for a missing "launch_container" field. // correct except for a missing "launch_container" field.
...@@ -289,8 +276,8 @@ TEST_F(ScanDirForExternalWebAppsTest, MissingLaunchContainer) { ...@@ -289,8 +276,8 @@ TEST_F(ScanDirForExternalWebAppsTest, MissingLaunchContainer) {
} }
TEST_F(ScanDirForExternalWebAppsTest, InvalidLaunchContainer) { TEST_F(ScanDirForExternalWebAppsTest, InvalidLaunchContainer) {
const auto app_infos = web_app::ScanDirForExternalWebAppsForTesting( const auto app_infos =
test_dir("invalid_launch_container")); ScanTestDirForExternalWebApps("invalid_launch_container");
// The invalidg_launch_container directory contains one JSON file which is // The invalidg_launch_container directory contains one JSON file which is
// correct except for an invalid "launch_container" field. // correct except for an invalid "launch_container" field.
...@@ -301,8 +288,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EnabledByFinch) { ...@@ -301,8 +288,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EnabledByFinch) {
base::test::ScopedFeatureList scoped_feature_list; base::test::ScopedFeatureList scoped_feature_list;
scoped_feature_list.InitAndEnableFeature( scoped_feature_list.InitAndEnableFeature(
base::Feature{"test_feature_name", base::FEATURE_DISABLED_BY_DEFAULT}); base::Feature{"test_feature_name", base::FEATURE_DISABLED_BY_DEFAULT});
const auto app_infos = web_app::ScanDirForExternalWebAppsForTesting( const auto app_infos = ScanTestDirForExternalWebApps("enabled_by_finch");
test_dir("enabled_by_finch"));
// The enabled_by_finch directory contains two JSON file containing apps // The enabled_by_finch directory contains two JSON file containing apps
// that have field trials. As the matching featureis enabled, they should be // that have field trials. As the matching featureis enabled, they should be
...@@ -311,8 +297,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EnabledByFinch) { ...@@ -311,8 +297,7 @@ TEST_F(ScanDirForExternalWebAppsTest, EnabledByFinch) {
} }
TEST_F(ScanDirForExternalWebAppsTest, NotEnabledByFinch) { TEST_F(ScanDirForExternalWebAppsTest, NotEnabledByFinch) {
const auto app_infos = web_app::ScanDirForExternalWebAppsForTesting( const auto app_infos = ScanTestDirForExternalWebApps("enabled_by_finch");
test_dir("enabled_by_finch"));
// The enabled_by_finch directory contains two JSON file containing apps // The enabled_by_finch directory contains two JSON file containing apps
// that have field trials. As the matching featureis enabled, they should not // that have field trials. As the matching featureis enabled, they should not
...@@ -321,42 +306,42 @@ TEST_F(ScanDirForExternalWebAppsTest, NotEnabledByFinch) { ...@@ -321,42 +306,42 @@ TEST_F(ScanDirForExternalWebAppsTest, NotEnabledByFinch) {
} }
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
TEST_F(ScanDirForExternalWebAppsWithProfileTest, ChildUser) { TEST_F(ScanDirForExternalWebAppsTest, ChildUser) {
const auto profile = CreateProfileAndLogin(); const auto profile = CreateProfileAndLogin();
profile->SetSupervisedUserId(supervised_users::kChildAccountSUID); profile->SetSupervisedUserId(supervised_users::kChildAccountSUID);
VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppChildUrl)}); VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppChildUrl)});
} }
TEST_F(ScanDirForExternalWebAppsWithProfileTest, GuestUser) { TEST_F(ScanDirForExternalWebAppsTest, GuestUser) {
VerifySetOfApps(CreateGuestProfileAndLogin().get(), VerifySetOfApps(CreateGuestProfileAndLogin().get(),
{GURL(kAppAllUrl), GURL(kAppGuestUrl)}); {GURL(kAppAllUrl), GURL(kAppGuestUrl)});
} }
TEST_F(ScanDirForExternalWebAppsWithProfileTest, ManagedUser) { TEST_F(ScanDirForExternalWebAppsTest, ManagedUser) {
const auto profile = CreateProfileAndLogin(); const auto profile = CreateProfileAndLogin();
policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile.get()) policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile.get())
->OverrideIsManagedForTesting(true); ->OverrideIsManagedForTesting(true);
VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppManagedUrl)}); VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppManagedUrl)});
} }
TEST_F(ScanDirForExternalWebAppsWithProfileTest, SupervisedUser) { TEST_F(ScanDirForExternalWebAppsTest, SupervisedUser) {
const auto profile = CreateProfileAndLogin(); const auto profile = CreateProfileAndLogin();
profile->SetSupervisedUserId("asdf"); profile->SetSupervisedUserId("asdf");
VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppSupervisedUrl)}); VerifySetOfApps(profile.get(), {GURL(kAppAllUrl), GURL(kAppSupervisedUrl)});
} }
TEST_F(ScanDirForExternalWebAppsWithProfileTest, UnmanagedUser) { TEST_F(ScanDirForExternalWebAppsTest, UnmanagedUser) {
VerifySetOfApps(CreateProfileAndLogin().get(), VerifySetOfApps(CreateProfileAndLogin().get(),
{GURL(kAppAllUrl), GURL(kAppUnmanagedUrl)}); {GURL(kAppAllUrl), GURL(kAppUnmanagedUrl)});
} }
TEST_F(ScanDirForExternalWebAppsWithProfileTest, NonPrimaryProfile) { TEST_F(ScanDirForExternalWebAppsTest, NonPrimaryProfile) {
EXPECT_TRUE( EXPECT_TRUE(
ScanApps(CreateProfile().get(), test_dir(kUserTypesTestDir)).empty()); ScanApps(CreateProfile().get(), test_dir(kUserTypesTestDir)).empty());
} }
#else #else
// No app is expected for non-ChromeOS builds. // No app is expected for non-ChromeOS builds.
TEST_F(ScanDirForExternalWebAppsWithProfileTest, NoApp) { TEST_F(ScanDirForExternalWebAppsTest, NoApp) {
EXPECT_TRUE( EXPECT_TRUE(
ScanApps(CreateProfile().get(), test_dir(kUserTypesTestDir)).empty()); ScanApps(CreateProfile().get(), test_dir(kUserTypesTestDir)).empty());
} }
......
...@@ -19,8 +19,7 @@ ...@@ -19,8 +19,7 @@
#include "base/task/post_task.h" #include "base/task/post_task.h"
#include "base/threading/scoped_blocking_call.h" #include "base/threading/scoped_blocking_call.h"
#include "build/build_config.h" #include "build/build_config.h"
#include "chrome/browser/policy/profile_policy_connector.h" #include "chrome/browser/apps/user_type_filter.h"
#include "chrome/browser/policy/profile_policy_connector_factory.h"
#include "chrome/browser/profiles/profile.h" #include "chrome/browser/profiles/profile.h"
#include "chrome/browser/web_applications/components/web_app_constants.h" #include "chrome/browser/web_applications/components/web_app_constants.h"
#include "chrome/common/chrome_paths.h" #include "chrome/common/chrome_paths.h"
...@@ -55,16 +54,6 @@ constexpr char kLaunchContainer[] = "launch_container"; ...@@ -55,16 +54,6 @@ constexpr char kLaunchContainer[] = "launch_container";
constexpr char kLaunchContainerTab[] = "tab"; constexpr char kLaunchContainerTab[] = "tab";
constexpr char kLaunchContainerWindow[] = "window"; constexpr char kLaunchContainerWindow[] = "window";
// kUserType is required key that specifies enumeration of user types for which
// web app is visible. See kUserType* constants
constexpr char kUserType[] = "user_type";
constexpr char kUserTypeChild[] = "child";
constexpr char kUserTypeGuest[] = "guest";
constexpr char kUserTypeManaged[] = "managed";
constexpr char kUserTypeSupervised[] = "supervised";
constexpr char kUserTypeUnmanaged[] = "unmanaged";
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
// The sub-directory of the extensions directory in which to scan for external // The sub-directory of the extensions directory in which to scan for external
// web apps (as opposed to external extensions or external ARC apps). // web apps (as opposed to external extensions or external ARC apps).
...@@ -98,7 +87,7 @@ bool IsFeatureEnabled(const std::string& feature_name) { ...@@ -98,7 +87,7 @@ bool IsFeatureEnabled(const std::string& feature_name) {
std::vector<web_app::PendingAppManager::AppInfo> ScanDir( std::vector<web_app::PendingAppManager::AppInfo> ScanDir(
const base::FilePath& dir, const base::FilePath& dir,
const std::string& user_type) { Profile* profile) {
base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK); base::ScopedBlockingCall scoped_blocking_call(base::BlockingType::MAY_BLOCK);
base::FilePath::StringType extension(FILE_PATH_LITERAL(".json")); base::FilePath::StringType extension(FILE_PATH_LITERAL(".json"));
base::FileEnumerator json_files(dir, base::FileEnumerator json_files(dir,
...@@ -126,28 +115,15 @@ std::vector<web_app::PendingAppManager::AppInfo> ScanDir( ...@@ -126,28 +115,15 @@ std::vector<web_app::PendingAppManager::AppInfo> ScanDir(
continue; continue;
} }
const base::Value* value = if (!apps::ProfileMatchJsonUserType(
dict->FindKeyOfType(kUserType, base::Value::Type::LIST); profile, file.MaybeAsASCII() /* app_id */, dict.get(),
if (!value) { nullptr /* default_user_types */)) {
LOG(ERROR) << file.value() << " has no user type filter"; // Already logged.
continue;
}
bool user_type_match = false;
for (const auto& it : value->GetList()) {
if (!it.is_string()) {
LOG(ERROR) << file.value() << " has invalid user type value";
user_type_match = false;
break;
}
user_type_match |= (it.GetString() == user_type);
}
if (!user_type_match) {
VLOG(1) << file.value() << " skip, does not match user type "
<< user_type;
continue; continue;
} }
value = dict->FindKeyOfType(kFeatureName, base::Value::Type::STRING); const base::Value* value =
dict->FindKeyOfType(kFeatureName, base::Value::Type::STRING);
if (value) { if (value) {
std::string feature_name = value->GetString(); std::string feature_name = value->GetString();
VLOG(1) << file.value() << " checking feature " << feature_name; VLOG(1) << file.value() << " checking feature " << feature_name;
...@@ -230,28 +206,14 @@ base::FilePath DetermineScanDir(const Profile* profile) { ...@@ -230,28 +206,14 @@ base::FilePath DetermineScanDir(const Profile* profile) {
return dir; return dir;
} }
std::string DetermineUserType(Profile* profile) {
DCHECK(!profile->IsOffTheRecord());
if (profile->IsGuestSession())
return kUserTypeGuest;
if (profile->IsChild())
return kUserTypeChild;
if (profile->IsLegacySupervised())
return kUserTypeSupervised;
if (policy::ProfilePolicyConnectorFactory::GetForBrowserContext(profile)
->IsManaged()) {
return kUserTypeManaged;
}
return kUserTypeUnmanaged;
}
} // namespace } // namespace
namespace web_app { namespace web_app {
std::vector<web_app::PendingAppManager::AppInfo> std::vector<web_app::PendingAppManager::AppInfo>
ScanDirForExternalWebAppsForTesting(const base::FilePath& dir) { ScanDirForExternalWebAppsForTesting(const base::FilePath& dir,
return ScanDir(dir, kUserTypeUnmanaged); Profile* profile) {
return ScanDir(dir, profile);
} }
void ScanForExternalWebApps(Profile* profile, void ScanForExternalWebApps(Profile* profile,
...@@ -275,8 +237,7 @@ void ScanForExternalWebApps(Profile* profile, ...@@ -275,8 +237,7 @@ void ScanForExternalWebApps(Profile* profile,
FROM_HERE, FROM_HERE,
{base::MayBlock(), base::TaskPriority::BEST_EFFORT, {base::MayBlock(), base::TaskPriority::BEST_EFFORT,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}, base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&ScanDir, dir, DetermineUserType(profile)), base::BindOnce(&ScanDir, dir, profile), std::move(callback));
std::move(callback));
} }
} // namespace web_app } // namespace web_app
...@@ -30,7 +30,8 @@ void ScanForExternalWebApps(Profile* profile, ...@@ -30,7 +30,8 @@ void ScanForExternalWebApps(Profile* profile,
// //
// This function performs file I/O, and must not be scheduled on UI threads. // This function performs file I/O, and must not be scheduled on UI threads.
std::vector<web_app::PendingAppManager::AppInfo> std::vector<web_app::PendingAppManager::AppInfo>
ScanDirForExternalWebAppsForTesting(const base::FilePath& dir); ScanDirForExternalWebAppsForTesting(const base::FilePath& dir,
Profile* profile);
} // namespace web_app } // namespace web_app
......
...@@ -418,15 +418,6 @@ bool PathProvider(int key, base::FilePath* result) { ...@@ -418,15 +418,6 @@ bool PathProvider(int key, base::FilePath* result) {
break; break;
#endif #endif
#if BUILDFLAG(ENABLE_SUPERVISED_USERS) #if BUILDFLAG(ENABLE_SUPERVISED_USERS)
#if defined(OS_LINUX)
case chrome::DIR_SUPERVISED_USERS_DEFAULT_APPS:
if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&cur)) {
return false;
}
cur = cur.Append(FILE_PATH_LITERAL("managed_users"));
break;
#endif
case chrome::DIR_SUPERVISED_USER_INSTALLED_WHITELISTS: case chrome::DIR_SUPERVISED_USER_INSTALLED_WHITELISTS:
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur)) if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
return false; return false;
...@@ -570,13 +561,6 @@ bool PathProvider(int key, base::FilePath* result) { ...@@ -570,13 +561,6 @@ bool PathProvider(int key, base::FilePath* result) {
cur = base::FilePath(kChromeOSComponentFlash); cur = base::FilePath(kChromeOSComponentFlash);
create_dir = false; create_dir = false;
break; break;
case chrome::DIR_CHILD_USERS_DEFAULT_APPS:
if (!base::PathService::Get(chrome::DIR_STANDALONE_EXTERNAL_EXTENSIONS,
&cur)) {
return false;
}
cur = cur.Append(FILE_PATH_LITERAL("child_users"));
break;
case chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_LOCATION: case chrome::FILE_CHROME_OS_TPM_FIRMWARE_UPDATE_LOCATION:
cur = base::FilePath(kChromeOSTPMFirmwareUpdateLocation); cur = base::FilePath(kChromeOSTPMFirmwareUpdateLocation);
create_dir = false; create_dir = false;
......
...@@ -104,9 +104,6 @@ enum { ...@@ -104,9 +104,6 @@ enum {
DIR_CHROMEOS_CUSTOM_WALLPAPERS, // Directory where custom wallpapers DIR_CHROMEOS_CUSTOM_WALLPAPERS, // Directory where custom wallpapers
// reside. // reside.
#endif #endif
DIR_SUPERVISED_USERS_DEFAULT_APPS, // Directory where installer places .crx
// files to be installed when managed user
// session starts.
DIR_SUPERVISED_USER_INSTALLED_WHITELISTS, // Directory where sanitized DIR_SUPERVISED_USER_INSTALLED_WHITELISTS, // Directory where sanitized
// supervised user whitelists are // supervised user whitelists are
// installed. // installed.
...@@ -132,9 +129,6 @@ enum { ...@@ -132,9 +129,6 @@ enum {
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
FILE_CHROME_OS_COMPONENT_FLASH, // The location of component updated Flash on FILE_CHROME_OS_COMPONENT_FLASH, // The location of component updated Flash on
// Chrome OS. // Chrome OS.
DIR_CHILD_USERS_DEFAULT_APPS, // Directory where installer places .crx
// files to be installed when child user
// session starts.
// File containing the location of the updated TPM firmware binary in the file // File containing the location of the updated TPM firmware binary in the file
// system. // system.
......
...@@ -2412,6 +2412,7 @@ test("unit_tests") { ...@@ -2412,6 +2412,7 @@ test("unit_tests") {
"../browser/android/webapk/webapk_web_manifest_checker_unittest.cc", "../browser/android/webapk/webapk_web_manifest_checker_unittest.cc",
"../browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc", "../browser/android/webapps/add_to_homescreen_data_fetcher_unittest.cc",
"../browser/app_controller_mac_unittest.mm", "../browser/app_controller_mac_unittest.mm",
"../browser/apps/user_type_filter_unittest.cc",
"../browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc", "../browser/autocomplete/chrome_autocomplete_provider_client_unittest.cc",
"../browser/autocomplete/chrome_autocomplete_scheme_classifier_unittest.cc", "../browser/autocomplete/chrome_autocomplete_scheme_classifier_unittest.cc",
"../browser/autocomplete/search_provider_unittest.cc", "../browser/autocomplete/search_provider_unittest.cc",
......
{ {
"external_crx": "hcglmfcclpfgljeaiahehebeoaiicbko.crx", "external_crx": "hcglmfcclpfgljeaiahehebeoaiicbko.crx",
"external_version": "1.0" "external_version": "1.0",
"user_type": ["unmanaged", "child"]
} }
{ {
"external_crx": "ldnnhddmnhbkjipkidpdiheffobcpfmf.crx", "external_crx": "ldnnhddmnhbkjipkidpdiheffobcpfmf.crx",
"external_version": "1" "external_version": "1",
"user_type": ["unmanaged"]
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment