0% found this document useful (0 votes)
26 views372 pages

Message

The document outlines the implementation of a Device Management (DM) client in C++, which handles communication with a DM server for device registration, policy fetching, and validation reporting. It includes classes for configuring the DM client, sending requests, and processing responses, utilizing network fetchers and storage for device management tokens and policies. Key functionalities include building request URLs, handling HTTP responses, and managing device state based on server interactions.

Uploaded by

gnssehriban
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
26 views372 pages

Message

The document outlines the implementation of a Device Management (DM) client in C++, which handles communication with a DM server for device registration, policy fetching, and validation reporting. It includes classes for configuring the DM client, sending requests, and processing responses, utilizing network fetchers and storage for device management tokens and policies. Key functionalities include building request URLs, handling HTTP responses, and managing device state based on server interactions.

Uploaded by

gnssehriban
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as TXT, PDF, TXT or read online on Scribd

#include "chrome/updater/device_management/dm_client.

h"

#include <cstdint>
#include <memory>
#include <optional>
#include <ostream>
#include <string>
#include <utility>

#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/strings/strcat.h"
#include "base/strings/stringprintf.h"
#include "base/system/sys_info.h"
#include "base/task/sequenced_task_runner.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/device_management_storage/dm_storage.h"
#include "chrome/updater/device_management/dm_message.h"
#include "chrome/updater/device_management/dm_response_validator.h"
#include "chrome/updater/net/network.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/update_client/network.h"
#include "url/gurl.h"

namespace updater {
namespace {

// Content-type of DM requests.
constexpr char kDMContentType[] = "application/x-protobuf";

// String constants for the device and app type we report to the server.
constexpr char kParamAgent[] = "agent";
constexpr char kParamRequest[] = "request";
constexpr char kParamPlatform[] = "platform";
constexpr char kParamDeviceID[] = "deviceid";
constexpr char kParamAppType[] = "apptype";
constexpr char kValueAppType[] = "Chrome";

constexpr char kAuthorizationHeader[] = "Authorization";

// Constants for device management enrollment requests.


constexpr char kRegistrationRequestType[] = "register_policy_agent";
constexpr char kRegistrationTokenType[] = "GoogleEnrollmentToken";

// Constants for policy fetch requests.


constexpr char kPolicyFetchRequestType[] = "policy";
constexpr char kDMTokenType[] = "GoogleDMToken";
constexpr char kGoogleUpdateMachineLevelApps[] = "google/machine-level-apps";

// Constants for policy validation report requests.


constexpr char kValidationReportRequestType[] = "policy_validation_report";
constexpr int kHTTPStatusOK = 200;
constexpr int kHTTPStatusGone = 410;

class DefaultConfigurator : public DMClient::Configurator {


public:
DefaultConfigurator(const GURL& server_url,
std::optional<PolicyServiceProxyConfiguration>
policy_service_proxy_configuration)
: server_url_(server_url),
policy_service_proxy_configuration_(
std::move(policy_service_proxy_configuration)) {}

GURL GetDMServerUrl() const override { return server_url_; }

std::string GetAgentParameter() const override {


return GetUpdaterUserAgent();
}

std::string GetPlatformParameter() const override {


int32_t major = 0;
int32_t minor = 0;
int32_t bugfix = 0;
base::SysInfo::OperatingSystemVersionNumbers(&major, &minor, &bugfix);
return base::StringPrintf(
"%s|%s|%d.%d.%d", base::SysInfo::OperatingSystemName().c_str(),
base::SysInfo::OperatingSystemArchitecture().c_str(), major, minor,
bugfix);
}

std::unique_ptr<update_client::NetworkFetcher> CreateNetworkFetcher()
const override {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!network_fetcher_factory_) {
network_fetcher_factory_ = base::MakeRefCounted<NetworkFetcherFactory>(
policy_service_proxy_configuration_);
}
return network_fetcher_factory_->Create();
}

private:
SEQUENCE_CHECKER(sequence_checker_);
const GURL server_url_;
const std::optional<PolicyServiceProxyConfiguration>
policy_service_proxy_configuration_;
mutable scoped_refptr<update_client::NetworkFetcherFactory>
network_fetcher_factory_;
};

// Builds a DM request and sends it via the wrapped network fetcher. Raw fetch
// result will be translated into DM request result for external callback.
// Do not reuse this class for multiple requests, as the class maintains the
// intermediate state of the wrapped network request.
class DMFetch : public base::RefCountedThreadSafe<DMFetch> {
public:
enum class TokenType {
kEnrollmentToken,
kDMToken,
};
using Callback =
base::OnceCallback<void(DMClient::RequestResult result,
std::unique_ptr<std::string> response_body)>;

DMFetch(std::unique_ptr<DMClient::Configurator> config,
scoped_refptr<device_management_storage::DMStorage> storage);
DMFetch(const DMFetch&) = delete;
DMFetch& operator=(const DMFetch&) = delete;

const DMClient::Configurator* config() const { return config_.get(); }

// Returns the storage where this client saves the data from DM server.
scoped_refptr<device_management_storage::DMStorage> storage() const {
return storage_;
}

void PostRequest(const std::string& request_type,


TokenType token_type,
const std::string& request_data,
Callback callback);

private:
friend class base::RefCountedThreadSafe<DMFetch>;

~DMFetch();

// Get the authorization token string.


std::string BuildTokenString(TokenType type) const;

// Gets the full request URL to DM server for the given request type.
// Additional device specific values, such as device ID, platform etc. will
// be appended to the URL as query parameters.
GURL BuildURL(const std::string& request_type) const;

// Callback functions for the URLFetcher.


void OnRequestStarted(int response_code, int64_t content_length);
void OnRequestProgress(int64_t current);
void OnRequestComplete(std::unique_ptr<std::string> response_body,
int net_error,
const std::string& header_etag,
const std::string& header_x_cup_server_proof,
int64_t xheader_retry_after_sec);

std::unique_ptr<DMClient::Configurator> config_;
scoped_refptr<device_management_storage::DMStorage> storage_;

std::unique_ptr<update_client::NetworkFetcher> network_fetcher_;
int http_status_code_ = 0;

Callback callback_;
SEQUENCE_CHECKER(sequence_checker_);
};

DMFetch::DMFetch(std::unique_ptr<DMClient::Configurator> config,
scoped_refptr<device_management_storage::DMStorage> storage)
: config_(std::move(config)), storage_(std::move(storage)) {}

DMFetch::~DMFetch() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

GURL DMFetch::BuildURL(const std::string& request_type) const {


GURL url(config_->GetDMServerUrl());
url = AppendQueryParameter(url, kParamRequest, request_type);
url = AppendQueryParameter(url, kParamAppType, kValueAppType);
url = AppendQueryParameter(url, kParamAgent, config_->GetAgentParameter());
url = AppendQueryParameter(url, kParamPlatform,
config_->GetPlatformParameter());
return AppendQueryParameter(url, kParamDeviceID, storage_->GetDeviceID());
}

std::string DMFetch::BuildTokenString(TokenType type) const {


switch (type) {
case TokenType::kEnrollmentToken:
return base::StringPrintf("%s token=%s", kRegistrationTokenType,
storage_->GetEnrollmentToken().c_str());
case TokenType::kDMToken:
return base::StringPrintf("%s token=%s", kDMTokenType,
storage_->GetDmToken().c_str());
}
}

void DMFetch::PostRequest(const std::string& request_type,


TokenType token_type,
const std::string& request_data,
Callback callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

callback_ = std::move(callback);

DMClient::RequestResult result = [&] {


if (storage_->IsDeviceDeregistered()) {
return DMClient::RequestResult::kDeregistered;
}
if (storage_->GetDeviceID().empty()) {
return DMClient::RequestResult::kNoDeviceID;
}
if (request_data.empty()) {
return DMClient::RequestResult::kNoPayload;
}
if (token_type == TokenType::kEnrollmentToken) {
if (storage_->GetEnrollmentToken().empty()) {
return DMClient::RequestResult::kNotManaged;
}
if (!storage_->GetDmToken().empty()) {
return DMClient::RequestResult::kAlreadyRegistered;
}
storage_->RemoveAllPolicies();
return DMClient::RequestResult::kSuccess;
}
if (storage_->GetDmToken().empty()) {
return DMClient::RequestResult::kNoDMToken;
}
return DMClient::RequestResult::kSuccess;
}();

if (result == DMClient::RequestResult::kSuccess) {
network_fetcher_ = config_->CreateNetworkFetcher();
if (!network_fetcher_) {
result = DMClient::RequestResult::kFetcherError;
}
}

if (result != DMClient::RequestResult::kSuccess) {
VLOG(1) << "DM request not sent: " << result;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback_), result,
std::make_unique<std::string>()));
return;
}
network_fetcher_->PostRequest(
BuildURL(request_type), request_data, kDMContentType,
{{kAuthorizationHeader, BuildTokenString(token_type)}},
base::BindRepeating(&DMFetch::OnRequestStarted, base::Unretained(this)),
base::BindRepeating(&DMFetch::OnRequestProgress, base::Unretained(this)),
base::BindOnce(&DMFetch::OnRequestComplete, base::Unretained(this)));
}

void DMFetch::OnRequestStarted(int response_code, int64_t content_length) {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << "DM request is sent to server, status: " << response_code
<< ". Content length: " << content_length << ".";
http_status_code_ = response_code;
}

void DMFetch::OnRequestProgress(int64_t current) {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(3) << "DM request progress made, current bytes: " << current;
}

void DMFetch::OnRequestComplete(std::unique_ptr<std::string> response_body,


int net_error,
const std::string& header_etag,
const std::string& header_x_cup_server_proof,
int64_t xheader_retry_after_sec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(2) << __func__;

DMClient::RequestResult result = DMClient::RequestResult::kSuccess;


if (net_error != 0) {
VLOG(1) << "DM request failed due to net error: " << net_error;
result = DMClient::RequestResult::kNetworkError;
} else if (http_status_code_ == kHTTPStatusGone) {
VLOG(1) << "Got response to delete/invalidate the DM token.";
if (ShouldDeleteDmToken(*response_body)) {
storage_->DeleteDMToken();
result = DMClient::RequestResult::kNoDMToken;
VLOG(1) << "Device is now de-registered by deleting the DM token.";
} else {
storage_->InvalidateDMToken();
result = DMClient::RequestResult::kDeregistered;
VLOG(1) << "Device is now de-registered by invalidating the DM token.";
}
} else if (http_status_code_ != kHTTPStatusOK) {
VLOG(1) << "DM request failed due to HTTP error: " << http_status_code_;
result = DMClient::RequestResult::kHttpError;
}

base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback_), result, std::move(response_body)));
}

void OnDMRegisterRequestComplete(scoped_refptr<DMFetch> dm_fetch,


DMClient::RegisterCallback callback,
DMClient::RequestResult result,
std::unique_ptr<std::string> response_body) {
VLOG(2) << __func__ << ": result=" << result;
if (result == DMClient::RequestResult::kSuccess) {
const std::string dm_token =
ParseDeviceRegistrationResponse(*response_body);
if (dm_token.empty()) {
VLOG(1) << "Failed to parse DM token from registration response.";
result = DMClient::RequestResult::kUnexpectedResponse;
} else {
VLOG(1) << "Register request completed, got DM token: " << dm_token;
if (!dm_fetch->storage()->StoreDmToken(dm_token)) {
result = DMClient::RequestResult::kSerializationError;
}
}
}

base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
}

void OnDMPolicyFetchRequestComplete(
scoped_refptr<DMFetch> dm_fetch,
DMClient::PolicyFetchCallback callback,
std::unique_ptr<device_management_storage::CachedPolicyInfo> cached_info,
DMClient::RequestResult result,
std::unique_ptr<std::string> response_body) {
VLOG(2) << __func__ << ": result=" << result;
std::vector<PolicyValidationResult> validation_results;
scoped_refptr<device_management_storage::DMStorage> storage =
dm_fetch->storage();
if (result == DMClient::RequestResult::kSuccess) {
device_management_storage::DMPolicyMap policies = ParsePolicyFetchResponse(
*response_body, *cached_info, storage->GetDmToken(),
storage->GetDeviceID(), validation_results);

if ([Link]()) {
VLOG(1) << "No policy passes the validation, reset the policy cache.";
result = DMClient::RequestResult::kUnexpectedResponse;
storage->RemoveAllPolicies();
} else {
VLOG(1) << "Policy fetch request completed, got " << [Link]()
<< " new policies.";
if (!storage->PersistPolicies(policies)) {
result = DMClient::RequestResult::kSerializationError;
}
}
}

base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), result, validation_results));
}

void OnDMPolicyValidationReportRequestComplete(
scoped_refptr<DMFetch> dm_fetch,
DMClient::PolicyValidationReportCallback callback,
DMClient::RequestResult result,
std::unique_ptr<std::string> response_body) {
VLOG(2) << __func__ << ": result=" << result;
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), result));
}

} // namespace

void DMClient::RegisterDevice(
std::unique_ptr<Configurator> config,
scoped_refptr<device_management_storage::DMStorage> storage,
RegisterCallback callback) {
VLOG(2) << __func__;
auto dm_fetch = base::MakeRefCounted<DMFetch>(std::move(config), storage);
dm_fetch->PostRequest(kRegistrationRequestType,
DMFetch::TokenType::kEnrollmentToken,
GetRegisterBrowserRequestData(),
base::BindOnce(OnDMRegisterRequestComplete, dm_fetch,
std::move(callback)));
}

void DMClient::FetchPolicy(
policy::PolicyFetchReason reason,
std::unique_ptr<Configurator> config,
scoped_refptr<device_management_storage::DMStorage> storage,
PolicyFetchCallback callback) {
VLOG(2) << __func__;
if (!storage->CanPersistPolicies()) {
VLOG(2) << "Cannot persist policies.";
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
DMClient::RequestResult::kSerializationError,
std::vector<PolicyValidationResult>()));
return;
}

auto dm_fetch = base::MakeRefCounted<DMFetch>(std::move(config), storage);


std::unique_ptr<device_management_storage::CachedPolicyInfo> cached_info =
dm_fetch->storage()->GetCachedPolicyInfo();
const std::string request_data = GetPolicyFetchRequestData(
reason, kGoogleUpdateMachineLevelApps, *cached_info);
dm_fetch->PostRequest(
kPolicyFetchRequestType, DMFetch::TokenType::kDMToken, request_data,
base::BindOnce(&OnDMPolicyFetchRequestComplete, dm_fetch,
std::move(callback), std::move(cached_info)));
}

void DMClient::ReportPolicyValidationErrors(
std::unique_ptr<Configurator> config,
scoped_refptr<device_management_storage::DMStorage> storage,
const PolicyValidationResult& validation_result,
PolicyValidationReportCallback callback) {
VLOG(2) << __func__;
auto dm_fetch = base::MakeRefCounted<DMFetch>(std::move(config), storage);
dm_fetch->PostRequest(
kValidationReportRequestType, DMFetch::TokenType::kDMToken,
GetPolicyValidationReportRequestData(validation_result),
base::BindOnce(&OnDMPolicyValidationReportRequestComplete, dm_fetch,
std::move(callback)));
}

std::unique_ptr<DMClient::Configurator> DMClient::CreateDefaultConfigurator(
const GURL& server_url,
std::optional<PolicyServiceProxyConfiguration>
policy_service_proxy_configuration) {
return std::make_unique<DefaultConfigurator>(
server_url, policy_service_proxy_configuration);
}

std::ostream& operator<<(std::ostream& os,


const DMClient::RequestResult& result) {
#define SWITCH_ENTRY(p) \
case p: \
return os << #p
switch (result) {
SWITCH_ENTRY(DMClient::RequestResult::kSuccess);
SWITCH_ENTRY(DMClient::RequestResult::kNoDeviceID);
SWITCH_ENTRY(DMClient::RequestResult::kAlreadyRegistered);
SWITCH_ENTRY(DMClient::RequestResult::kNotManaged);
SWITCH_ENTRY(DMClient::RequestResult::kDeregistered);
SWITCH_ENTRY(DMClient::RequestResult::kNoDMToken);
SWITCH_ENTRY(DMClient::RequestResult::kFetcherError);
SWITCH_ENTRY(DMClient::RequestResult::kNetworkError);
SWITCH_ENTRY(DMClient::RequestResult::kHttpError);
SWITCH_ENTRY(DMClient::RequestResult::kSerializationError);
SWITCH_ENTRY(DMClient::RequestResult::kUnexpectedResponse);
SWITCH_ENTRY(DMClient::RequestResult::kNoPayload);
SWITCH_ENTRY(DMClient::RequestResult::kNoDefaultDMStorage);
}
#undef SWITCH_ENTRY
}

} // namespace updater
#include "chrome/updater/device_management/dm_client.h"

#include <cstdint>
#include <memory>
#include <optional>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/run_loop.h"
#include "base/test/gmock_callback_support.h"
#include "base/test/task_environment.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/device_management_storage/dm_storage.h"
#include "chrome/updater/device_management/dm_message.h"
#include "chrome/updater/device_management/dm_policy_builder_for_testing.h"
#include "chrome/updater/device_management/dm_response_validator.h"
#include "chrome/updater/net/network.h"
#include "chrome/updater/policy/dm_policy_manager.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/protos/omaha_settings.pb.h"
#include "chrome/updater/test/unit_test_util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/update_client/network.h"
#include "net/base/url_util.h"
#include "net/http/http_status_code.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"

using base::test::RunClosure;

namespace updater {
namespace {

class TestTokenService
: public device_management_storage::TokenServiceInterface {
public:
TestTokenService(const std::string& enrollment_token,
const std::string& dm_token)
: enrollment_token_(enrollment_token), dm_token_(dm_token) {}

// Overrides for TokenServiceInterface.


std::string GetDeviceID() const override { return "test-device-id"; }

bool IsEnrollmentMandatory() const override { return false; }

bool StoreEnrollmentToken(const std::string& enrollment_token) override {


enrollment_token_ = enrollment_token;
return true;
}

bool DeleteEnrollmentToken() override { return StoreEnrollmentToken(""); }

std::string GetEnrollmentToken() const override { return enrollment_token_; }

bool StoreDmToken(const std::string& dm_token) override {


dm_token_ = dm_token;
return true;
}

bool DeleteDmToken() override {


dm_token_.clear();
return true;
}

std::string GetDmToken() const override { return dm_token_; }


private:
std::string enrollment_token_;
std::string dm_token_;
};

class TestConfigurator : public DMClient::Configurator {


public:
explicit TestConfigurator(const GURL& url);

GURL GetDMServerUrl() const override { return server_url_; }

std::string GetAgentParameter() const override {


return "Updater-Test-Agent";
}

std::string GetPlatformParameter() const override { return "Test-Platform"; }

std::unique_ptr<update_client::NetworkFetcher> CreateNetworkFetcher()
const override {
return network_fetcher_factory_->Create();
}

private:
scoped_refptr<update_client::NetworkFetcherFactory> network_fetcher_factory_;
const GURL server_url_;
};

TestConfigurator::TestConfigurator(const GURL& url)


: network_fetcher_factory_(base::MakeRefCounted<NetworkFetcherFactory>(
PolicyServiceProxyConfiguration::Get(
test::CreateTestPolicyService()))),
server_url_(url) {}

class DMRequestCallbackHandler
: public base::RefCountedThreadSafe<DMRequestCallbackHandler> {
public:
DMRequestCallbackHandler() = default;

enum class PublicKeyType {


kNone,
kTestKey1,
kTestKey2,
};

MOCK_METHOD0(PostRequestCompleted, void(void));

void CreateStorage(bool init_dm_token, bool init_cache_info) {


ASSERT_TRUE(storage_dir_.CreateUniqueTempDir());
constexpr char kEnrollmentToken[] = "TestEnrollmentToken";
constexpr char kDmToken[] = "test-dm-token";
storage_ =
CreateDMStorage(storage_dir_.GetPath(),
std::make_unique<TestTokenService>(
kEnrollmentToken, init_dm_token ? kDmToken : ""));

if (init_cache_info) {
ASSERT_TRUE(storage_->CanPersistPolicies());
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);
std::unique_ptr<device_management_storage::CachedPolicyInfo> info =
storage_->GetCachedPolicyInfo();
std::vector<PolicyValidationResult> validation_results;
DMPolicyMap policies = ParsePolicyFetchResponse(
dm_response->SerializeAsString(), *[Link](), storage_->GetDmToken(),
storage_->GetDeviceID(), validation_results);
ASSERT_FALSE([Link]());
ASSERT_TRUE(storage_->PersistPolicies(policies));
ASSERT_TRUE(validation_results.empty());
}
}

void SetExpectedHttpStatus(net::HttpStatusCode expected_http_status) {


expected_http_status_ = expected_http_status;
}
void SetExpectedRequestResult(DMClient::RequestResult expected_result) {
expected_result_ = expected_result;
}

void SetExpectedPublicKey(PublicKeyType expect_key_type) {


expected_public_key_type_ = expect_key_type;
}

scoped_refptr<device_management_storage::DMStorage> GetStorage() {
return storage_;
}

protected:
virtual ~DMRequestCallbackHandler() = default;

base::ScopedTempDir storage_dir_;
scoped_refptr<device_management_storage::DMStorage> storage_;

net::HttpStatusCode expected_http_status_ = net::HTTP_OK;


DMClient::RequestResult expected_result_ = DMClient::RequestResult::kSuccess;
PublicKeyType expected_public_key_type_ = PublicKeyType::kNone;

private:
friend class base::RefCountedThreadSafe<DMRequestCallbackHandler>;
};

class DMRegisterRequestCallbackHandler : public DMRequestCallbackHandler {


public:
explicit DMRegisterRequestCallbackHandler(bool expect_registered)
: expect_registered_(expect_registered) {}

void OnRequestComplete(DMClient::RequestResult result) {


if (expect_registered_) {
EXPECT_EQ(result, expected_result_);
if (result == DMClient::RequestResult::kSuccess ||
result == DMClient::RequestResult::kAlreadyRegistered) {
EXPECT_EQ(storage_->GetDmToken(), "test-dm-token");
} else {
EXPECT_TRUE(storage_->GetDmToken().empty());
}
} else {
// Device should be unregistered.
EXPECT_EQ(result, DMClient::RequestResult::kDeregistered);
EXPECT_TRUE(storage_->IsDeviceDeregistered());
}
PostRequestCompleted();
}

private:
~DMRegisterRequestCallbackHandler() override = default;

const bool expect_registered_;

friend class base::RefCountedThreadSafe<DMRegisterRequestCallbackHandler>;


};

class DMPolicyFetchRequestCallbackHandler : public DMRequestCallbackHandler {


public:
void AppendExpectedValidationResult(const PolicyValidationResult& result) {
expected_validation_results_.push_back(result);
}

void OnRequestComplete(
DMClient::RequestResult result,
const std::vector<PolicyValidationResult>& validation_results) {
EXPECT_EQ(result, expected_result_);

if (expected_http_status_ != net::HTTP_OK ||
expected_result_ == DMClient::RequestResult::kNoDMToken) {
PostRequestCompleted();
return;
}

std::unique_ptr<device_management_storage::CachedPolicyInfo> info =
storage_->GetCachedPolicyInfo();
switch (expected_public_key_type_) {
case PublicKeyType::kTestKey1:
EXPECT_EQ(info->public_key(), GetTestKey1()->GetPublicKeyString());
break;
case PublicKeyType::kTestKey2:
EXPECT_EQ(info->public_key(), GetTestKey2()->GetPublicKeyString());
break;
case PublicKeyType::kNone:
default:
EXPECT_TRUE(info->public_key().empty());
break;
}

if (result == DMClient::RequestResult::kSuccess) {
std::optional<::wireless_android_enterprise_devicemanagement::
OmahaSettingsClientProto>
omaha_settings = GetOmahaPolicySettings(storage_);
EXPECT_TRUE(omaha_settings);

// Sample some of the policy values and check they are expected.
EXPECT_EQ(omaha_settings->proxy_mode(), "pac_script");
const ::wireless_android_enterprise_devicemanagement::ApplicationSettings&
chrome_settings = omaha_settings->application_settings()[0];
EXPECT_EQ(chrome_settings.app_guid(), test::kChromeAppId);
EXPECT_EQ(chrome_settings.update(),
::wireless_android_enterprise_devicemanagement::
AUTOMATIC_UPDATES_ONLY);
EXPECT_EQ(chrome_settings.target_version_prefix(), "81.");
}

EXPECT_EQ(expected_validation_results_, validation_results);
PostRequestCompleted();
}

private:
~DMPolicyFetchRequestCallbackHandler() override = default;
friend class base::RefCountedThreadSafe<DMPolicyFetchRequestCallbackHandler>;

std::vector<PolicyValidationResult> expected_validation_results_;
};

class DMValidationReportRequestCallbackHandler
: public DMRequestCallbackHandler {
public:
void OnRequestComplete(DMClient::RequestResult result) {
EXPECT_EQ(result, expected_result_);
PostRequestCompleted();
}

private:
~DMValidationReportRequestCallbackHandler() override = default;
friend class base::RefCountedThreadSafe<
DMValidationReportRequestCallbackHandler>;
};

} // namespace

class DMClientTest : public ::testing::Test {


public:
std::unique_ptr<net::test_server::HttpResponse> HandleRequest(
const net::test_server::HttpRequest& request) {
std::string app_type;
EXPECT_TRUE(
net::GetValueForKeyInQuery([Link](), "apptype", &app_type));
EXPECT_EQ(app_type, "Chrome");

std::string platform;
EXPECT_TRUE(
net::GetValueForKeyInQuery([Link](), "platform", &platform));
EXPECT_EQ(platform, "Test-Platform");

std::string device_id;
EXPECT_TRUE(
net::GetValueForKeyInQuery([Link](), "deviceid", &device_id));
EXPECT_EQ(device_id, "test-device-id");

EXPECT_EQ([Link]("Content-Type"), "application/x-protobuf");

std::string request_type;
EXPECT_TRUE(
net::GetValueForKeyInQuery([Link](), "request", &request_type));
EXPECT_EQ(request_type, GetExpectedRequestType());

EXPECT_EQ([Link]("Authorization"),
GetExpectedAuthorizationToken());
auto http_response =
std::make_unique<net::test_server::BasicHttpResponse>();
http_response->set_code(response_http_status_);
http_response->set_content_type("application/x-protobuf");
http_response->set_content(response_body_);
return http_response;
}

void StartTestServerWithResponse(net::HttpStatusCode http_status,


const std::string& body) {
response_http_status_ = http_status;
response_body_ = body;
test_server_.RegisterRequestHandler(base::BindRepeating(
&DMClientTest::HandleRequest, base::Unretained(this)));
ASSERT_TRUE(test_server_.Start());
}

virtual std::string GetExpectedRequestType() const = 0;


virtual std::string GetExpectedAuthorizationToken() const = 0;

net::EmbeddedTestServer test_server_;
net::HttpStatusCode response_http_status_ = net::HTTP_OK;
std::string response_body_;

base::test::TaskEnvironment task_environment_;
};

class DMRegisterClientTest : public DMClientTest {


public:
void PostRequest() {
DMClient::RegisterDevice(
std::make_unique<TestConfigurator>(test_server_.GetURL("/dm_api")),
callback_handler_->GetStorage(),
base::BindOnce(&DMRegisterRequestCallbackHandler::OnRequestComplete,
callback_handler_));
}

std::string GetExpectedAuthorizationToken() const override {


return "GoogleEnrollmentToken token=TestEnrollmentToken";
}

std::string GetExpectedRequestType() const override {


return "register_policy_agent";
}

std::string GetDefaultResponse() const {


auto dm_response =
std::make_unique<enterprise_management::DeviceManagementResponse>();
dm_response->mutable_register_response()->set_device_management_token(
"test-dm-token");
return dm_response->SerializeAsString();
}

scoped_refptr<DMRegisterRequestCallbackHandler> callback_handler_;
};

class DMPolicyFetchClientTest : public DMClientTest {


public:
void PostRequest() {
DMClient::FetchPolicy(
policy::PolicyFetchReason::kTest,
std::make_unique<TestConfigurator>(test_server_.GetURL("/dm_api")),
callback_handler_->GetStorage(),
base::BindOnce(&DMPolicyFetchRequestCallbackHandler::OnRequestComplete,
callback_handler_));
}

std::string GetExpectedAuthorizationToken() const override {


return "GoogleDMToken token=test-dm-token";
}

std::string GetExpectedRequestType() const override { return "policy"; }

scoped_refptr<DMPolicyFetchRequestCallbackHandler> callback_handler_;
};

class DMPolicyValidationReportClientTest : public DMClientTest {


public:
void PostRequest(const PolicyValidationResult& validation_result) {
DMClient::ReportPolicyValidationErrors(
std::make_unique<TestConfigurator>(test_server_.GetURL("/dm_api")),
callback_handler_->GetStorage(), validation_result,
base::BindOnce(
&DMValidationReportRequestCallbackHandler::OnRequestComplete,
callback_handler_));
}

std::string GetExpectedAuthorizationToken() const override {


return "GoogleDMToken token=test-dm-token";
}

std::string GetExpectedRequestType() const override {


return "policy_validation_report";
}

scoped_refptr<DMValidationReportRequestCallbackHandler> callback_handler_;
};

TEST_F(DMRegisterClientTest, Success) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(true);
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);
StartTestServerWithResponse(net::HTTP_OK, GetDefaultResponse());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));
PostRequest();
run_loop.Run();
}

TEST_F(DMRegisterClientTest, Deregister) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(false);
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);
StartTestServerWithResponse(net::HTTP_GONE, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMRegisterClientTest, DeregisterWithDeletion) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(true);
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kNoDMToken);

enterprise_management::DeviceManagementResponse response;
response.add_error_detail(
enterprise_management::CBCM_DELETION_POLICY_PREFERENCE_DELETE_TOKEN);

StartTestServerWithResponse(net::HTTP_GONE, [Link]());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMRegisterClientTest, BadRequest) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(true);
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey1);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kHttpError);
StartTestServerWithResponse(net::HTTP_BAD_REQUEST, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}
TEST_F(DMRegisterClientTest, AlreadyRegistered) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(true);
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kAlreadyRegistered);
StartTestServerWithResponse(net::HTTP_OK, GetDefaultResponse());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMRegisterClientTest, BadResponseData) {
callback_handler_ =
base::MakeRefCounted<DMRegisterRequestCallbackHandler>(true);
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kNone);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kUnexpectedResponse);
StartTestServerWithResponse(net::HTTP_OK, "BadResponseData");

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, NoDMToken) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey1);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kNoDMToken);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/true,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));
PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, FirstRequest) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/false);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey1);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, NoRotateKey) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey1);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, RotateKey) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey2);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/true,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, RejectKeyWithBadSignature) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kNone);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kUnexpectedResponse);

PolicyValidationResult expected_validation_result;
expected_validation_result.policy_type = "google/machine-level-omaha";
expected_validation_result.status =
PolicyValidationResult::Status::kValidationBadKeyVerificationSignature;
callback_handler_->AppendExpectedValidationResult(expected_validation_result);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/true,
DMPolicyBuilderForTesting::SigningOption::kTamperKeySignature);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());
base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, RejectDataWithBadSignature) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kNone);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kUnexpectedResponse);
PolicyValidationResult expected_validation_result;
expected_validation_result.policy_type = "google/machine-level-omaha";
expected_validation_result.status =
PolicyValidationResult::Status::kValidationBadSignature;
callback_handler_->AppendExpectedValidationResult(expected_validation_result);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kTamperDataSignature);
StartTestServerWithResponse(net::HTTP_OK, dm_response->SerializeAsString());

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, Deregister) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kDeregistered);
callback_handler_->SetExpectedHttpStatus(net::HTTP_GONE);

StartTestServerWithResponse(net::HTTP_GONE, "" /* response body */);


base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();

EXPECT_TRUE(callback_handler_->GetStorage()->IsDeviceDeregistered());
}

TEST_F(DMPolicyFetchClientTest, DeregisterWithDeletion) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kNoDMToken);
callback_handler_->SetExpectedHttpStatus(net::HTTP_GONE);

enterprise_management::DeviceManagementResponse response;
response.add_error_detail(
enterprise_management::CBCM_DELETION_POLICY_PREFERENCE_DELETE_TOKEN);

StartTestServerWithResponse(net::HTTP_GONE, [Link]());
base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();

EXPECT_TRUE(callback_handler_->GetStorage()->GetDmToken().empty());
}

TEST_F(DMPolicyFetchClientTest, BadResponse) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kNone);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kUnexpectedResponse);
StartTestServerWithResponse(net::HTTP_OK, "Unexpected response data");

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyFetchClientTest, BadRequest) {
callback_handler_ =
base::MakeRefCounted<DMPolicyFetchRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/true);
callback_handler_->SetExpectedPublicKey(
DMRequestCallbackHandler::PublicKeyType::kTestKey1);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kHttpError);
StartTestServerWithResponse(net::HTTP_BAD_REQUEST, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PostRequest();
run_loop.Run();
}

TEST_F(DMPolicyValidationReportClientTest, Success) {
callback_handler_ =
base::MakeRefCounted<DMValidationReportRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kSuccess);
StartTestServerWithResponse(net::HTTP_OK, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PolicyValidationResult validation_result;
validation_result.policy_type = kGoogleUpdatePolicyType;
validation_result.policy_token = "TestPolicyToken";
validation_result.issues.emplace_back(
"test_policy", PolicyValueValidationIssue::Severity::kError,
"Policy value out of range.");
PostRequest(validation_result);
run_loop.Run();
}

TEST_F(DMPolicyValidationReportClientTest, NoDMToken) {
callback_handler_ =
base::MakeRefCounted<DMValidationReportRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/false,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kNoDMToken);
StartTestServerWithResponse(net::HTTP_OK, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));

PolicyValidationResult validation_result;
validation_result.policy_type = kGoogleUpdatePolicyType;
validation_result.policy_token = "TestPolicyToken";
validation_result.issues.emplace_back(
"test_policy", PolicyValueValidationIssue::Severity::kError,
"Policy value out of range.");
PostRequest(validation_result);
run_loop.Run();
}

TEST_F(DMPolicyValidationReportClientTest, NoPayload) {
callback_handler_ =
base::MakeRefCounted<DMValidationReportRequestCallbackHandler>();
callback_handler_->CreateStorage(/*init_dm_token=*/true,
/*init_cache_info=*/false);
callback_handler_->SetExpectedRequestResult(
DMClient::RequestResult::kNoPayload);
StartTestServerWithResponse(net::HTTP_OK, "" /* response body */);

base::RunLoop run_loop;
base::RepeatingClosure quit_closure = run_loop.QuitClosure();
EXPECT_CALL(*callback_handler_, PostRequestCompleted())
.WillOnce(RunClosure(quit_closure));
PostRequest(PolicyValidationResult());
run_loop.Run();
}

TEST(DMClient, StreamRequestResultEnumValue) {
{
std::stringstream output;
output << DMClient::RequestResult::kSuccess;
EXPECT_EQ([Link](), "DMClient::RequestResult::kSuccess");
}
{
std::stringstream output;
output << DMClient::RequestResult::kNoDeviceID;
EXPECT_EQ([Link](), "DMClient::RequestResult::kNoDeviceID");
}
{
std::stringstream output;
output << DMClient::RequestResult::kAlreadyRegistered;
EXPECT_EQ([Link](), "DMClient::RequestResult::kAlreadyRegistered");
}
{
std::stringstream output;
output << DMClient::RequestResult::kNotManaged;
EXPECT_EQ([Link](), "DMClient::RequestResult::kNotManaged");
}
{
std::stringstream output;
output << DMClient::RequestResult::kDeregistered;
EXPECT_EQ([Link](), "DMClient::RequestResult::kDeregistered");
}
{
std::stringstream output;
output << DMClient::RequestResult::kNoDMToken;
EXPECT_EQ([Link](), "DMClient::RequestResult::kNoDMToken");
}
{
std::stringstream output;
output << DMClient::RequestResult::kFetcherError;
EXPECT_EQ([Link](), "DMClient::RequestResult::kFetcherError");
}
{
std::stringstream output;
output << DMClient::RequestResult::kNetworkError;
EXPECT_EQ([Link](), "DMClient::RequestResult::kNetworkError");
}
{
std::stringstream output;
output << DMClient::RequestResult::kHttpError;
EXPECT_EQ([Link](), "DMClient::RequestResult::kHttpError");
}
{
std::stringstream output;
output << DMClient::RequestResult::kSerializationError;
EXPECT_EQ([Link](), "DMClient::RequestResult::kSerializationError");
}
{
std::stringstream output;
output << DMClient::RequestResult::kUnexpectedResponse;
EXPECT_EQ([Link](), "DMClient::RequestResult::kUnexpectedResponse");
}
{
std::stringstream output;
output << DMClient::RequestResult::kNoPayload;
EXPECT_EQ([Link](), "DMClient::RequestResult::kNoPayload");
}
}

} // namespace updater
#include "chrome/updater/device_management/dm_response_validator.h"

#include <cstdint>
#include <memory>
#include <utility>

#include "chrome/enterprise_companion/device_management_storage/dm_storage.h"
#include "chrome/updater/device_management/dm_message.h"
#include "chrome/updater/device_management/dm_policy_builder_for_testing.h"
#include "chrome/updater/protos/omaha_settings.pb.h"
#include "chrome/updater/test/unit_test_util.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace updater {

namespace edm = ::wireless_android_enterprise_devicemanagement;

class DMResponseValidatorTests : public ::testing::Test {


protected:
void GetCachedInfoWithPublicKey(
device_management_storage::CachedPolicyInfo& cached_info) const;

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
GetDMResponseWithOmahaPolicy(
const edm::OmahaSettingsClientProto& omaha_settings) const;
};

void DMResponseValidatorTests::GetCachedInfoWithPublicKey(
device_management_storage::CachedPolicyInfo& cached_info) const {
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);
std::string policy_fetch_response;
EXPECT_TRUE([Link](&policy_fetch_response));
cached_info.Populate(policy_fetch_response);
EXPECT_FALSE(cached_info.public_key().empty());
}

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
DMResponseValidatorTests::GetDMResponseWithOmahaPolicy(
const edm::OmahaSettingsClientProto& omaha_settings) const {
std::unique_ptr<DMPolicyBuilderForTesting> policy_builder =
DMPolicyBuilderForTesting::CreateInstanceWithOptions(
/*first_request=*/true, /*rotate_to_new_key=*/true,
DMPolicyBuilderForTesting::SigningOption::kSignNormally,
"test-dm-token", "test-device-id");
DMPolicyMap policy_map;
policy_map.emplace(kGoogleUpdatePolicyType,
omaha_settings.SerializeAsString());
return policy_builder->BuildDMResponseForPolicies(policy_map);
}
TEST_F(DMResponseValidatorTests, ValidationOKWithoutPublicKey) {
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"test-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_TRUE([Link](response, validation_result));
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationOK);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, ValidationOKWithPublicKey) {
// Cached info should be created before parsing next policy response.
device_management_storage::CachedPolicyInfo cached_info;
GetCachedInfoWithPublicKey(cached_info);

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(cached_info, "test-dm-token", "test-device-id");


PolicyValidationResult validation_result;
EXPECT_TRUE([Link](response, validation_result));
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationOK);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, UnexpectedDMToken) {
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"wrong-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.policy_type, "google/machine-level-omaha");
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationBadDMToken);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, UnexpectedDeviceID) {
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"test-dm-token", "unexpected-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.policy_type, "google/machine-level-omaha");
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationBadDeviceID);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, NoCachedPublicKey) {
// Verify that client must have a cached public key other than the first
// request.
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/false, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"test-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.policy_type, "google/machine-level-omaha");
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationBadSignature);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, BadSignedPublicKey) {
// Cached info should be created before parsing next policy response.
device_management_storage::CachedPolicyInfo cached_info;
GetCachedInfoWithPublicKey(cached_info);

// Validation should fail if the public key is not signed properly.


std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kSignNormally);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);
DMResponseValidator validator(cached_info, "test-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.policy_type, "google/machine-level-omaha");
EXPECT_EQ(
validation_result.status,
PolicyValidationResult::Status::kValidationBadKeyVerificationSignature);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, BadSignedPolicyData) {
// Validation should fail if policy data is not signed properly.
std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDefaultTestingPolicyFetchDMResponse(
/*first_request=*/true, /*rotate_to_new_key=*/false,
DMPolicyBuilderForTesting::SigningOption::kTamperDataSignature);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"test-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationBadSignature);
EXPECT_TRUE(validation_result.[Link]());
}

TEST_F(DMResponseValidatorTests, OmahaPolicyWithBadValues) {
edm::OmahaSettingsClientProto omaha_settings;

omaha_settings.set_auto_update_check_period_minutes(43201);
omaha_settings.set_download_preference("InvalidDownloadPreference");
omaha_settings.mutable_updates_suppressed()->set_start_hour(25);
omaha_settings.mutable_updates_suppressed()->set_start_minute(-1);
omaha_settings.mutable_updates_suppressed()->set_duration_min(1000);
omaha_settings.set_proxy_mode("weird_proxy_mode");
omaha_settings.set_proxy_server("unexpected_proxy");
omaha_settings.set_proxy_pac_url("foo.c/[Link]");
omaha_settings.set_install_default(edm::INSTALL_DEFAULT_DISABLED);
omaha_settings.set_update_default(edm::MANUAL_UPDATES_ONLY);

edm::ApplicationSettings app;
app.set_app_guid(test::kChromeAppId);

app.set_install(edm::INSTALL_DISABLED);
app.set_update(edm::AUTOMATIC_UPDATES_ONLY);
app.set_target_channel("");
app.set_target_version_prefix("");
app.set_rollback_to_target_version(edm::ROLLBACK_TO_TARGET_VERSION_DISABLED);
omaha_settings.mutable_application_settings()->Add(std::move(app));

std::unique_ptr<::enterprise_management::DeviceManagementResponse>
dm_response = GetDMResponseWithOmahaPolicy(omaha_settings);

EXPECT_EQ(dm_response->policy_response().responses_size(), 1);
const ::enterprise_management::PolicyFetchResponse& response =
dm_response->policy_response().responses(0);

DMResponseValidator validator(device_management_storage::CachedPolicyInfo(),
"test-dm-token", "test-device-id");
PolicyValidationResult validation_result;
EXPECT_FALSE([Link](response, validation_result));
EXPECT_EQ(validation_result.policy_type, kGoogleUpdatePolicyType);
EXPECT_EQ(validation_result.status,
PolicyValidationResult::Status::kValidationOK);
EXPECT_EQ(validation_result.[Link](), size_t{10});
EXPECT_EQ(validation_result.issues[0].policy_name,
"auto_update_check_period_minutes");
EXPECT_EQ(validation_result.issues[0].severity,
PolicyValueValidationIssue::Severity::kError);
EXPECT_EQ(validation_result.issues[0].message,
"Value out of range (0 - 43200): 43201");
EXPECT_EQ(validation_result.issues[1].policy_name, "download_preference");
EXPECT_EQ(validation_result.issues[1].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[1].message,
"Unrecognized download preference: InvalidDownloadPreference");
EXPECT_EQ(validation_result.issues[2].policy_name,
"updates_suppressed.start_hour");
EXPECT_EQ(validation_result.issues[2].severity,
PolicyValueValidationIssue::Severity::kError);
EXPECT_EQ(validation_result.issues[2].message,
"Value out of range(0 - 23): 25");
EXPECT_EQ(validation_result.issues[3].policy_name,
"updates_suppressed.start_minute");
EXPECT_EQ(validation_result.issues[3].severity,
PolicyValueValidationIssue::Severity::kError);
EXPECT_EQ(validation_result.issues[3].message,
"Value out of range(0 - 59): -1");
EXPECT_EQ(validation_result.issues[4].policy_name,
"updates_suppressed.duration_min");
EXPECT_EQ(validation_result.issues[4].severity,
PolicyValueValidationIssue::Severity::kError);
EXPECT_EQ(validation_result.issues[4].message,
"Value out of range(0 - 960): 1000");
EXPECT_EQ(validation_result.issues[5].policy_name, "proxy_mode");
EXPECT_EQ(validation_result.issues[5].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[5].message,
"Unrecognized proxy mode: weird_proxy_mode");
EXPECT_EQ(validation_result.issues[6].policy_name, "proxy_server");
EXPECT_EQ(validation_result.issues[6].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[6].message,
"Proxy server setting [unexpected_proxy] is ignored because proxy "
"mode is not [fixed_servers]");
EXPECT_EQ(validation_result.issues[7].policy_name, "proxy_pac_url");
EXPECT_EQ(validation_result.issues[7].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[7].message,
"Proxy PAC URL setting [foo.c/[Link]] is ignored because proxy "
"mode is not [pac_script]");
EXPECT_EQ(validation_result.issues[8].policy_name, "target_channel");
EXPECT_EQ(validation_result.issues[8].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[8].message,
"{8A69D345-D564-463C-AFF1-A69D9E530F96} empty policy value");
EXPECT_EQ(validation_result.issues[9].policy_name, "target_version_prefix");
EXPECT_EQ(validation_result.issues[9].severity,
PolicyValueValidationIssue::Severity::kWarning);
EXPECT_EQ(validation_result.issues[9].message,
"{8A69D345-D564-463C-AFF1-A69D9E530F96} empty policy value");
}

} // namespace updater
#include "chrome/updater/ipc/update_service_proxy_posix.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/cancelable_callback.h"
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/types/expected.h"
#include "base/version.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/ipc/ipc_names.h"
#include "chrome/updater/ipc/update_service_dialer.h"
#include "chrome/updater/ipc/update_service_proxy.h"
#include "chrome/updater/mojom/updater_service.mojom.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/service_proxy_factory.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/posix_util.h"
#include "components/named_mojo_ipc_server/named_mojo_ipc_server_client_util.h"
#include "components/policy/core/common/policy_types.h"
#include "mojo/public/cpp/bindings/callback_helpers.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/pending_remote.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_endpoint.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "mojo/public/cpp/system/message_pipe.h"

namespace updater {
namespace {
// The maximum amount of time to poll the server's socket for a connection.
constexpr base::TimeDelta kConnectionTimeout = base::Minutes(3);

[[nodiscard]] UpdateService::UpdateState MakeUpdateState(


const mojom::UpdateStatePtr& state_mojom) {
updater::UpdateService::UpdateState state;
state.app_id = state_mojom->app_id;
[Link] =
static_cast<UpdateService::UpdateState::State>(state_mojom->state);
state.next_version = base::Version(state_mojom->next_version);
state.downloaded_bytes = state_mojom->downloaded_bytes;
state.total_bytes = state_mojom->total_bytes;
state.install_progress = state_mojom->install_progress;
state.error_category =
static_cast<UpdateService::ErrorCategory>(state_mojom->error_category);
state.error_code = state_mojom->error_code;
state.extra_code1 = state_mojom->extra_code1;
state.installer_text = state_mojom->installer_text;
state.installer_cmd_line = state_mojom->installer_cmd_line;

return state;
}

[[nodiscard]] UpdateService::AppState MakeAppState(


const mojom::AppStatePtr& app_state_mojo) {
UpdateService::AppState app_state;
app_state.app_id = app_state_mojo->app_id;
app_state.version = base::Version(app_state_mojo->version);
if (app_state_mojo->version_path) {
app_state.version_path = *app_state_mojo->version_path;
}
if (app_state_mojo->version_key) {
app_state.version_key = *app_state_mojo->version_key;
}
app_state.ap = app_state_mojo->ap;
if (app_state_mojo->ap_path) {
app_state.ap_path = *app_state_mojo->ap_path;
}
if (app_state_mojo->ap_key) {
app_state.ap_key = *app_state_mojo->ap_key;
}
app_state.brand_code = app_state_mojo->brand_code;
app_state.brand_path = app_state_mojo->brand_path;
app_state.ecp = app_state_mojo->ecp;
if (app_state_mojo->cohort) {
app_state.cohort = *app_state_mojo->cohort;
}

return app_state;
}

[[nodiscard]] mojom::RegistrationRequestPtr MakeRegistrationRequest(


const RegistrationRequest& request) {
return mojom::RegistrationRequest::New(
request.app_id, request.brand_code, request.brand_path, [Link],
[Link](), request.existence_checker_path,
request.ap_path, request.ap_key, request.version_path,
request.version_key, request.install_id);
}
class StateChangeObserverImpl : public mojom::StateChangeObserver {
public:
explicit StateChangeObserverImpl(
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_change_callback,
base::OnceCallback<void(UpdateService::Result)> complete_callback)
: state_change_callback_(std::move(state_change_callback)),
complete_callback_(std::move(complete_callback)) {}
StateChangeObserverImpl(const StateChangeObserverImpl&) = delete;
StateChangeObserverImpl& operator=(const StateChangeObserverImpl&) = delete;

// Overrides for mojom::StateChangeObserver.


void OnStateChange(mojom::UpdateStatePtr state_mojom) override {
CHECK(complete_callback_) << "OnStateChange received after OnComplete";
state_change_callback_.Run(MakeUpdateState(state_mojom));
}

void OnComplete(mojom::UpdateService::Result result) override {


CHECK(complete_callback_) << "OnComplete received without a valid "
"callback. Was OnComplete run twice?";
if (complete_callback_) {
std::move(complete_callback_)
.Run(static_cast<updater::UpdateService::Result>(result));
}
}

private:
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_change_callback_;
base::OnceCallback<void(UpdateService::Result)> complete_callback_;
};

template <typename T>


base::OnceCallback<void(T)> ToMojoCallback(
base::OnceCallback<void(base::expected<T, RpcError>)> callback) {
return base::BindOnce(
[](base::OnceCallback<void(base::expected<T, RpcError>)> callback,
T value) { std::move(callback).Run(base::ok(value)); },
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), base::unexpected(kErrorIpcDisconnect)));
}

// Binds a callback which creates a self-owned StateChangeObserverImpl to


// forward RPC callbacks to the provided native callbacks.
[[nodiscard]] base::OnceCallback<
void(mojo::PendingReceiver<mojom::StateChangeObserver>)>
MakeStateChangeObserver(
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_change_callback,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
complete_callback) {
return base::BindOnce(
[](base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_change_callback,
base::OnceCallback<void(UpdateService::Result)> complete_callback,
mojo::PendingReceiver<mojom::StateChangeObserver> receiver) {
mojo::MakeSelfOwnedReceiver(
std::make_unique<StateChangeObserverImpl>(
state_change_callback,
base::BindOnce(std::move(complete_callback))),
std::move(receiver));
},
base::BindPostTaskToCurrentDefault(state_change_callback),
base::BindPostTaskToCurrentDefault(
ToMojoCallback(std::move(complete_callback))));
}

std::optional<mojo::PlatformChannelEndpoint> ConnectMojo(UpdaterScope scope,


int tries) {
if (tries == 1 && !DialUpdateService(scope)) {
return std::nullopt;
}
return named_mojo_ipc_server::ConnectToServer(
GetUpdateServiceServerName(scope));
}

void Connect(
UpdaterScope scope,
int tries,
base::Time deadline,
base::OnceCallback<void(std::optional<mojo::PlatformChannelEndpoint>)>
connected_callback) {
if (base::Time::Now() > deadline) {
VLOG(1) << "Failed to connect to UpdateService remote. "
"Connection timed out.";
std::move(connected_callback).Run(std::nullopt);
return;
}
std::optional<mojo::PlatformChannelEndpoint> endpoint =
ConnectMojo(scope, tries);

if (!endpoint) {
VLOG(1) << "Failed to connect to UpdateService remote. "
"No updater exists.";
std::move(connected_callback).Run(std::nullopt);
return;
}

if (endpoint->is_valid()) {
std::move(connected_callback).Run(std::move(endpoint));
return;
}

base::ThreadPool::PostDelayedTask(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&Connect, scope, tries + 1, deadline,
std::move(connected_callback)),
base::Milliseconds(30 * tries));
}

} // namespace

UpdateServiceProxyImpl::UpdateServiceProxyImpl(
UpdaterScope scope,
base::TimeDelta get_version_timeout)
: scope_(scope), get_version_timeout_(get_version_timeout) {}
void UpdateServiceProxyImpl::GetVersion(
base::OnceCallback<void(base::expected<base::Version, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();

// Because GetVersion is used as a health checker, it has a special timeout


// in the event that the server receives the call and hangs. If the timeout
// elapses, this calls OnDisconnected to reset the connection and trigger the
// DefaultInvokeIfNotRun wrapper around `callback`.
auto timeout_callback =
std::make_unique<base::CancelableOnceClosure>(base::BindOnce(
&UpdateServiceProxyImpl::OnDisconnected, weak_factory_.GetWeakPtr()));

// If get_version_timeout_ elapses, call the timeout callback.


base::SequencedTaskRunner::GetCurrentDefault()->PostDelayedTask(
FROM_HERE, timeout_callback->callback(), get_version_timeout_);

remote_->GetVersion(base::BindOnce(
[](base::OnceCallback<void(base::expected<base::Version, RpcError>)>
callback,
std::unique_ptr<base::CancelableOnceClosure> timeout_callback,
const std::string& version) {
timeout_callback->Cancel();
std::move(callback).Run(base::Version(version));
},
mojo::WrapCallbackWithDefaultInvokeIfNotRun(
std::move(callback), base::unexpected(kErrorIpcDisconnect)),
std::move(timeout_callback)));
}

void UpdateServiceProxyImpl::FetchPolicies(
policy::PolicyFetchReason reason,
base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->FetchPolicies(reason, ToMojoCallback(std::move(callback)));
}

void UpdateServiceProxyImpl::RegisterApp(
const RegistrationRequest& request,
base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->RegisterApp(MakeRegistrationRequest(request),
ToMojoCallback(std::move(callback)));
}

void UpdateServiceProxyImpl::GetAppStates(
base::OnceCallback<void(base::expected<std::vector<UpdateService::AppState>,
RpcError>)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->GetAppStates(
base::BindOnce([](std::vector<mojom::AppStatePtr> app_states_mojo) {
std::vector<updater::UpdateService::AppState> app_states;
std::ranges::transform(app_states_mojo, std::back_inserter(app_states),
&MakeAppState);
return app_states;
}).Then(ToMojoCallback(std::move(callback))));
}

void UpdateServiceProxyImpl::RunPeriodicTasks(
base::OnceCallback<void(base::expected<int, RpcError>)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->RunPeriodicTasks(base::BindOnce(
[](base::OnceCallback<void(int)> callback) {
std::move(callback).Run(kErrorOk);
},
ToMojoCallback(std::move(callback))));
}

void UpdateServiceProxyImpl::CheckForUpdate(
const std::string& app_id,
UpdateService::Priority priority,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->CheckForUpdate(
app_id, static_cast<mojom::UpdateService::Priority>(priority),
static_cast<mojom::UpdateService::PolicySameVersionUpdate>(
policy_same_version_update),
language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyImpl::Update(
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->Update(app_id, install_data_index,
static_cast<mojom::UpdateService::Priority>(priority),
static_cast<mojom::UpdateService::PolicySameVersionUpdate>(
policy_same_version_update),
/*do_update_check_only=*/false, language,
MakeStateChangeObserver(state_update, std::move(callback)));
}
void UpdateServiceProxyImpl::UpdateAll(
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->UpdateAll(
MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyImpl::Install(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
UpdateService::Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->Install(
MakeRegistrationRequest(registration), client_install_data,
install_data_index, static_cast<mojom::UpdateService::Priority>(priority),
language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyImpl::CancelInstalls(const std::string& app_id) {


VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->CancelInstalls(app_id);
}

void UpdateServiceProxyImpl::RunInstaller(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
EnsureConnecting();
remote_->RunInstaller(
app_id, installer_path, install_args, install_data, install_settings,
language, MakeStateChangeObserver(state_update, std::move(callback)));
}

void UpdateServiceProxyImpl::OnConnected(
mojo::PendingReceiver<mojom::UpdateService> pending_receiver,
std::optional<mojo::PlatformChannelEndpoint> endpoint) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!endpoint) {
remote_.reset();
return;
}

auto connection = std::make_unique<mojo::IsolatedConnection>();


// Connect `remote_` to the RPC server by fusing its message pipe to the one
// created by `IsolatedConnection::Connect`.
if (!mojo::FusePipes(
std::move(pending_receiver),
mojo::PendingRemote<mojom::UpdateService>(
connection->Connect(std::move([Link]())), 0))) {
LOG(ERROR) << "Failed to fuse Mojo pipes for RPC.";
remote_.reset();
return;
}

connection_ = std::move(connection);

// A weak pointer is used here to prevent remote_ from forming a reference


// cycle with this object.
remote_.set_disconnect_handler(base::BindOnce(
&UpdateServiceProxyImpl::OnDisconnected, weak_factory_.GetWeakPtr()));
}

void UpdateServiceProxyImpl::OnDisconnected() {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
connection_.reset();
remote_.reset();
}

UpdateServiceProxyImpl::~UpdateServiceProxyImpl() {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}

void UpdateServiceProxyImpl::EnsureConnecting() {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (remote_) {
return;
}
base::ThreadPool::PostTask(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
&Connect, scope_, 0, base::Time::Now() + kConnectionTimeout,
base::BindPostTaskToCurrentDefault(base::BindOnce(
&UpdateServiceProxyImpl::OnConnected, weak_factory_.GetWeakPtr(),
remote_.BindNewPipeAndPassReceiver()))));
}

scoped_refptr<UpdateService> CreateUpdateServiceProxy(UpdaterScope scope,


base::TimeDelta timeout) {
return base::MakeRefCounted<UpdateServiceProxy>(
base::MakeRefCounted<UpdateServiceProxyImpl>(scope, timeout));
}

} // namespace updater
#ifndef CHROME_UPDATER_IPC_UPDATE_SERVICE_PROXY_WIN_H_
#define CHROME_UPDATER_IPC_UPDATE_SERVICE_PROXY_WIN_H_

#include <windows.h>

#include <string>
#include <vector>

#include "base/functional/callback_forward.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "base/types/expected.h"
#include "chrome/updater/update_service.h"

namespace base {
class Version;
}

namespace policy {
enum class PolicyFetchReason;
} // namespace policy

namespace updater {

using RpcError = HRESULT;

struct RegistrationRequest;
enum class UpdaterScope;
class UpdateServiceProxyImplImpl;

// All functions and callbacks must be called on the same sequence.


class UpdateServiceProxyImpl
: public base::RefCountedThreadSafe<UpdateServiceProxyImpl> {
public:
explicit UpdateServiceProxyImpl(UpdaterScope updater_scope);

// UpdateServiceProxyImpl will not be destroyed while these calls are


// outstanding; the caller need not retain a ref.
void GetVersion(
base::OnceCallback<void(base::expected<base::Version, RpcError>)>
callback);
void FetchPolicies(
policy::PolicyFetchReason reason,
base::OnceCallback<void(base::expected<int, RpcError>)> callback);
void RegisterApp(
const RegistrationRequest& request,
base::OnceCallback<void(base::expected<int, RpcError>)> callback);
void GetAppStates(
base::OnceCallback<void(
base::expected<std::vector<UpdateService::AppState>, RpcError>)>);
void RunPeriodicTasks(
base::OnceCallback<void(base::expected<int, RpcError>)> callback);
void CheckForUpdate(
const std::string& app_id,
UpdateService::Priority priority,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback);
void Update(
const std::string& app_id,
const std::string& install_data_index,
UpdateService::Priority priority,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback);
void UpdateAll(
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback);
void Install(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
UpdateService::Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback);
void CancelInstalls(const std::string& app_id);
void RunInstaller(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
state_update,
base::OnceCallback<void(base::expected<UpdateService::Result, RpcError>)>
callback);

private:
friend class base::RefCountedThreadSafe<UpdateServiceProxyImpl>;
~UpdateServiceProxyImpl();

SEQUENCE_CHECKER(sequence_checker_);
scoped_refptr<UpdateServiceProxyImplImpl> impl_;
};

} // namespace updater

#endif // CHROME_UPDATER_IPC_UPDATE_SERVICE_PROXY_WIN_H_
#include "chrome/updater/configurator.h"

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <vector>

#include "base/containers/flat_map.h"
#include "base/enterprise_util.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/memory/scoped_refptr.h"
#include "base/rand_util.h"
#include "base/sequence_checker.h"
#include "base/strings/to_string.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/updater/activity.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/crx_downloader_factory.h"
#include "chrome/updater/external_constants.h"
#include "chrome/updater/net/network.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "components/crash/core/common/crash_key.h"
#include "components/crx_file/crx_verifier.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/network.h"
#include "components/update_client/patch/in_process_patcher.h"
#include "components/update_client/patcher.h"
#include "components/update_client/protocol_handler.h"
#include "components/update_client/unzip/in_process_unzipper.h"
#include "components/update_client/unzipper.h"
#include "components/version_info/version_info.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_WIN)
#include "base/win/win_util.h"
#endif

namespace updater {

Configurator::Configurator(scoped_refptr<UpdaterPrefs> prefs,
scoped_refptr<ExternalConstants> external_constants,
bool is_ceca_experiment_enabled)
: prefs_(prefs),
external_constants_(external_constants),
persisted_data_(base::MakeRefCounted<PersistedData>(
GetUpdaterScope(),
prefs->GetPrefService(),
std::make_unique<ActivityDataService>(GetUpdaterScope()))),
policy_service_(
base::MakeRefCounted<PolicyService>(external_constants,
persisted_data_,
is_ceca_experiment_enabled)),
unzip_factory_(
base::MakeRefCounted<update_client::InProcessUnzipperFactory>()),
patch_factory_(
base::MakeRefCounted<update_client::InProcessPatcherFactory>()),
is_managed_device_([] {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
return base::IsManagedOrEnterpriseDevice();
#else
return std::nullopt;
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
}()) {
#if BUILDFLAG(IS_LINUX)
// On Linux creating the NetworkFetcherFactory requires performing blocking IO
// to load an external library. This should be done when the configurator is
// created.
GetNetworkFetcherFactory();
#endif
static crash_reporter::CrashKeyString<6> crash_key_managed("managed");
crash_key_managed.Set(is_managed_device_ ? base::ToString(*is_managed_device_)
: "n/a");
}
Configurator::~Configurator() = default;

base::TimeDelta Configurator::InitialDelay() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::RandDouble() * external_constants_->InitialDelay();
}

base::TimeDelta Configurator::ServerKeepAliveTime() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::clamp(external_constants_->ServerKeepAliveTime(),
base::Seconds(1), kServerKeepAliveTime);
}

base::TimeDelta Configurator::NextCheckDelay() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PolicyStatus<base::TimeDelta> delay = policy_service_->GetLastCheckPeriod();
CHECK(delay);
return [Link]();
}

base::TimeDelta Configurator::OnDemandDelay() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::Seconds(0);
}

base::TimeDelta Configurator::UpdateDelay() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::Seconds(0);
}

std::vector<GURL> Configurator::UpdateUrl() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return external_constants_->UpdateURL();
}

std::vector<GURL> Configurator::PingUrl() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return UpdateUrl();
}

GURL Configurator::CrashUploadURL() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return external_constants_->CrashUploadURL();
}

GURL Configurator::DeviceManagementURL() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return external_constants_->DeviceManagementURL();
}

std::string Configurator::GetProdId() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return "updater";
}

base::Version Configurator::GetBrowserVersion() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return version_info::GetVersion();
}

std::string Configurator::GetChannel() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return {};
}

std::string Configurator::GetLang() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return "";
}

std::string Configurator::GetOSLongName() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::string(version_info::GetOSType());
}

base::flat_map<std::string, std::string> Configurator::ExtraRequestParams()


const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return {};
}

std::string Configurator::GetDownloadPreference() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PolicyStatus<std::string> preference =
policy_service_->GetDownloadPreference();
return preference ? [Link]() : std::string();
}

scoped_refptr<update_client::NetworkFetcherFactory>
Configurator::GetNetworkFetcherFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!network_fetcher_factory_) {
network_fetcher_factory_ = base::MakeRefCounted<NetworkFetcherFactory>(
PolicyServiceProxyConfiguration::Get(policy_service_));
}
return network_fetcher_factory_;
}

scoped_refptr<update_client::CrxDownloaderFactory>
Configurator::GetCrxDownloaderFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!crx_downloader_factory_) {
crx_downloader_factory_ =
updater::MakeCrxDownloaderFactory(GetNetworkFetcherFactory());
}
return crx_downloader_factory_;
}

scoped_refptr<update_client::UnzipperFactory>
Configurator::GetUnzipperFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return unzip_factory_;
}

scoped_refptr<update_client::PatcherFactory> Configurator::GetPatcherFactory() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return patch_factory_;
}

bool Configurator::EnabledBackgroundDownloader() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return false;
}

bool Configurator::EnabledCupSigning() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return external_constants_->UseCUP();
}

PrefService* Configurator::GetPrefService() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return prefs_->GetPrefService();
}

update_client::PersistedData* Configurator::GetPersistedData() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return persisted_data_.get();
}

scoped_refptr<PersistedData> Configurator::GetUpdaterPersistedData() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return persisted_data_;
}

bool Configurator::IsPerUserInstall() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return !IsSystemInstall();
}

std::unique_ptr<update_client::ProtocolHandlerFactory>
Configurator::GetProtocolHandlerFactory() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return std::make_unique<update_client::ProtocolHandlerFactoryJSON>();
}

std::optional<bool> Configurator::IsMachineExternallyManaged() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
const std::optional<bool> is_managed_overridden =
external_constants_->IsMachineManaged();
return is_managed_overridden.has_value() ? is_managed_overridden
: is_managed_device_;
}

scoped_refptr<PolicyService> Configurator::GetPolicyService() const {


// The policy service is accessed by RPC on a different sequence and this
// function can't enforce the sequence check for now: [Link]/1517079.
return policy_service_;
}

crx_file::VerifierFormat Configurator::GetCrxVerifierFormat() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return external_constants_->CrxVerifierFormat();
}

update_client::UpdaterStateProvider Configurator::GetUpdaterStateProvider()
const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return base::BindRepeating([](bool /*is_machine*/) {
return update_client::UpdaterStateAttributes();
});
}

std::optional<base::FilePath> Configurator::GetCrxCachePath() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return updater::GetCrxCacheDirectory(GetUpdaterScope());
}

bool Configurator::IsConnectionMetered() const {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return false;
}

} // namespace updater
#include "chrome/updater/external_constants_override.h"

#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/check.h"
#include "base/containers/flat_map.h"
#include "base/files/file_path.h"
#include "base/json/json_file_value_serializer.h"
#include "base/json/json_reader.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/time/time.h"
#include "base/values.h"
#include "build/build_config.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/external_constants.h"
#include "chrome/updater/external_constants_default.h"
#include "chrome/updater/updater_branding.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/crx_file/crx_verifier.h"
#include "url/gurl.h"

#if BUILDFLAG(IS_MAC)
#include "base/apple/foundation_util.h"
#elif BUILDFLAG(IS_WIN)
#include "base/path_service.h"
#endif

namespace {

// Developer override file name, relative to app data directory.


const char kDevOverrideFileName[] = "[Link]";

std::vector<GURL> GURLVectorFromStringList(
const base::Value::List& update_url_list) {
std::vector<GURL> ret;
[Link](update_url_list.size());
for (const base::Value& url : update_url_list) {
CHECK(url.is_string()) << "Non-string Value in update URL list";
ret.push_back(GURL([Link]()));
}
return ret;
}

// The test binary only ever needs to contact localhost during integration
// tests. To reduce the program's utility as a mule, crash if there is a
// non-localhost override.
GURL CheckURL(const GURL& url) {
CHECK(url.is_empty() || [Link]() == "localhost" ||
[Link]() == "[Link]" || [Link]() == "not_exist")
<< "Illegal URL override: " << url;
return url;
}

std::vector<GURL> CheckURLs(const std::vector<GURL>& urls) {


for (const auto& url : urls) {
CheckURL(url);
}
return urls;
}

} // anonymous namespace

namespace updater {

std::optional<base::FilePath> GetOverrideFilePath(UpdaterScope scope) {


std::optional<base::FilePath> base = GetInstallDirectory(scope);
if (!base) {
return std::nullopt;
}
return base->DirName().AppendUTF8(kDevOverrideFileName);
}

ExternalConstantsOverrider::ExternalConstantsOverrider(
base::Value::Dict override_values,
scoped_refptr<ExternalConstants> next_provider)
: ExternalConstants(std::move(next_provider)),
override_values_(std::move(override_values)) {}
ExternalConstantsOverrider::~ExternalConstantsOverrider() = default;

std::vector<GURL> ExternalConstantsOverrider::UpdateURL() const {


if (!override_values_.contains(kDevOverrideKeyUrl)) {
return next_provider_->UpdateURL();
}
const base::Value* update_url_value =
override_values_.Find(kDevOverrideKeyUrl);
switch (update_url_value->type()) {
case base::Value::Type::STRING:
return CheckURLs({GURL(update_url_value->GetString())});
case base::Value::Type::LIST:
return CheckURLs(GURLVectorFromStringList(update_url_value->GetList()));
default:
LOG(FATAL) << "Unexpected type of override[" << kDevOverrideKeyUrl
<< "]: " << base::Value::GetTypeName(update_url_value->type());
}
}

GURL ExternalConstantsOverrider::CrashUploadURL() const {


if (!override_values_.contains(kDevOverrideKeyCrashUploadUrl)) {
return next_provider_->CrashUploadURL();
}
const base::Value* crash_upload_url_value =
override_values_.Find(kDevOverrideKeyCrashUploadUrl);
CHECK(crash_upload_url_value->is_string())
<< "Unexpected type of override[" << kDevOverrideKeyCrashUploadUrl
<< "]: " << base::Value::GetTypeName(crash_upload_url_value->type());
return CheckURL({GURL(crash_upload_url_value->GetString())});
}

GURL ExternalConstantsOverrider::DeviceManagementURL() const {


if (!override_values_.contains(kDevOverrideKeyDeviceManagementUrl)) {
return next_provider_->DeviceManagementURL();
}
const base::Value* device_management_url_value =
override_values_.Find(kDevOverrideKeyDeviceManagementUrl);
CHECK(device_management_url_value->is_string())
<< "Unexpected type of override[" << kDevOverrideKeyDeviceManagementUrl
<< "]: " << base::Value::GetTypeName(device_management_url_value->type());
return CheckURL({GURL(device_management_url_value->GetString())});
}

GURL ExternalConstantsOverrider::AppLogoURL() const {


if (!override_values_.contains(kDevOverrideKeyAppLogoUrl)) {
return next_provider_->AppLogoURL();
}
const base::Value* app_logo_url_value =
override_values_.Find(kDevOverrideKeyAppLogoUrl);
CHECK(app_logo_url_value->is_string())
<< "Unexpected type of override[" << kDevOverrideKeyAppLogoUrl
<< "]: " << base::Value::GetTypeName(app_logo_url_value->type());
return CheckURL({GURL(app_logo_url_value->GetString())});
}

bool ExternalConstantsOverrider::UseCUP() const {


if (!override_values_.contains(kDevOverrideKeyUseCUP)) {
return next_provider_->UseCUP();
}
const base::Value* use_cup_value =
override_values_.Find(kDevOverrideKeyUseCUP);
CHECK(use_cup_value->is_bool())
<< "Unexpected type of override[" << kDevOverrideKeyUseCUP
<< "]: " << base::Value::GetTypeName(use_cup_value->type());

return use_cup_value->GetBool();
}

base::TimeDelta ExternalConstantsOverrider::InitialDelay() const {


if (!override_values_.contains(kDevOverrideKeyInitialDelay)) {
return next_provider_->InitialDelay();
}

const base::Value* initial_delay_value =


override_values_.Find(kDevOverrideKeyInitialDelay);
CHECK(initial_delay_value->is_double())
<< "Unexpected type of override[" << kDevOverrideKeyInitialDelay
<< "]: " << base::Value::GetTypeName(initial_delay_value->type());
return base::Seconds(initial_delay_value->GetDouble());
}

base::TimeDelta ExternalConstantsOverrider::ServerKeepAliveTime() const {


if (!override_values_.contains(kDevOverrideKeyServerKeepAliveSeconds)) {
return next_provider_->ServerKeepAliveTime();
}

const base::Value* server_keep_alive_seconds_value =


override_values_.Find(kDevOverrideKeyServerKeepAliveSeconds);
CHECK(server_keep_alive_seconds_value->is_int())
<< "Unexpected type of override[" << kDevOverrideKeyServerKeepAliveSeconds
<< "]: "
<< base::Value::GetTypeName(server_keep_alive_seconds_value->type());
return base::Seconds(server_keep_alive_seconds_value->GetInt());
}

crx_file::VerifierFormat ExternalConstantsOverrider::CrxVerifierFormat() const {


if (!override_values_.contains(kDevOverrideKeyCrxVerifierFormat)) {
return next_provider_->CrxVerifierFormat();
}

const base::Value* crx_format_verifier_value =


override_values_.Find(kDevOverrideKeyCrxVerifierFormat);
CHECK(crx_format_verifier_value->is_int())
<< "Unexpected type of override[" << kDevOverrideKeyCrxVerifierFormat
<< "]: " << base::Value::GetTypeName(crx_format_verifier_value->type());
return static_cast<crx_file::VerifierFormat>(
crx_format_verifier_value->GetInt());
}

base::Value::Dict ExternalConstantsOverrider::DictPolicies() const {


if (!override_values_.contains(kDevOverrideKeyDictPolicies)) {
return next_provider_->DictPolicies();
}

const base::Value* dict_policies_value =


override_values_.Find(kDevOverrideKeyDictPolicies);
CHECK(dict_policies_value->is_dict())
<< "Unexpected type of override[" << kDevOverrideKeyDictPolicies
<< "]: " << base::Value::GetTypeName(dict_policies_value->type());
return dict_policies_value->GetDict().Clone();
}

base::TimeDelta ExternalConstantsOverrider::OverinstallTimeout() const {


if (!override_values_.contains(kDevOverrideKeyOverinstallTimeout)) {
return next_provider_->OverinstallTimeout();
}

const base::Value* value =


override_values_.Find(kDevOverrideKeyOverinstallTimeout);
CHECK(value->is_int()) << "Unexpected type of override["
<< kDevOverrideKeyOverinstallTimeout
<< "]: " << base::Value::GetTypeName(value->type());
return base::Seconds(value->GetInt());
}

base::TimeDelta ExternalConstantsOverrider::IdleCheckPeriod() const {


if (!override_values_.contains(kDevOverrideKeyIdleCheckPeriodSeconds)) {
return next_provider_->IdleCheckPeriod();
}

const base::Value* value =


override_values_.Find(kDevOverrideKeyIdleCheckPeriodSeconds);
CHECK(value->is_int()) << "Unexpected type of override["
<< kDevOverrideKeyIdleCheckPeriodSeconds
<< "]: " << base::Value::GetTypeName(value->type());
return base::Seconds(value->GetInt());
}

std::optional<bool> ExternalConstantsOverrider::IsMachineManaged() const {


if (!override_values_.contains(kDevOverrideKeyManagedDevice)) {
return next_provider_->IsMachineManaged();
}
const base::Value* is_managed =
override_values_.Find(kDevOverrideKeyManagedDevice);
CHECK(is_managed->is_bool())
<< "Unexpected type of override[" << kDevOverrideKeyManagedDevice
<< "]: " << base::Value::GetTypeName(is_managed->type());

return std::make_optional(is_managed->GetBool());
}

base::TimeDelta ExternalConstantsOverrider::CecaConnectionTimeout() const {


if (!override_values_.contains(kDevOverrideKeyCecaConnectionTimeout)) {
return next_provider_->CecaConnectionTimeout();
}

const base::Value* value =


override_values_.Find(kDevOverrideKeyCecaConnectionTimeout);
CHECK(value->is_int()) << "Unexpected type of override["
<< kDevOverrideKeyCecaConnectionTimeout
<< "]: " << base::Value::GetTypeName(value->type());
return base::Seconds(value->GetInt());
}

// static
scoped_refptr<ExternalConstantsOverrider>
ExternalConstantsOverrider::FromDefaultJSONFile(
scoped_refptr<ExternalConstants> next_provider) {
const std::optional<base::FilePath> override_file_path =
GetOverrideFilePath(GetUpdaterScope());
if (!override_file_path) {
LOG(ERROR) << "Cannot find override file path.";
return nullptr;
}

JSONFileValueDeserializer parser(*override_file_path,
base::JSON_ALLOW_TRAILING_COMMAS);
int error_code = 0;
std::string error_message;
std::unique_ptr<base::Value> parsed_value(
[Link](&error_code, &error_message));
if (error_code || !parsed_value) {
VLOG(2) << "Could not parse " << override_file_path << ": error "
<< error_code << ": " << error_message;
return nullptr;
}

if (!parsed_value->is_dict()) {
LOG(ERROR) << "Invalid data in " << override_file_path << ": not a dict";
return nullptr;
}

return base::MakeRefCounted<ExternalConstantsOverrider>(
std::move(*parsed_value).TakeDict(), next_provider);
}

// Declared in external_constants.h. This implementation of the function is


// used only if external_constants_override is linked into the binary.
scoped_refptr<ExternalConstants> CreateExternalConstants() {
scoped_refptr<ExternalConstants> overrider =
ExternalConstantsOverrider::FromDefaultJSONFile(
CreateDefaultExternalConstants());
return overrider ? overrider : CreateDefaultExternalConstants();
}

} // namespace updater#include "chrome/updater/installer.h"

#include <optional>
#include <string>
#include <utility>
#include <vector>

#include "base/files/file_enumerator.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/callback.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/task/thread_pool.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/time/time.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/updater/action_handler.h"
#include "chrome/updater/app/app_utils.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_usage_stats_task.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "components/crx_file/crx_verifier.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"

namespace updater {

namespace {

// Runs in thread pool, can block.


AppInfo MakeAppInfo(UpdaterScope scope,
const std::string& app_id,
const base::Version& pv,
const base::FilePath& pv_path,
const std::string& pv_key,
const std::string& ap,
const base::FilePath& ap_path,
const std::string& ap_key,
const std::string& lang,
const std::string& brand,
const base::FilePath& brand_path,
const std::string& brand_key,
const base::FilePath& ec_path) {
const base::Version pv_lookup =
LookupVersion(scope, app_id, pv_path, pv_key, pv);
return AppInfo(scope, app_id, LookupString(ap_path, ap_key, ap), lang,
LookupString(brand_path, brand_key, brand),
pv_lookup.IsValid() ? pv_lookup : base::Version(kNullVersion),
ec_path);
}

} // namespace

AppInfo::AppInfo(const UpdaterScope scope,


const std::string& app_id,
const std::string& ap,
const std::string& lang,
const std::string& brand,
const base::Version& app_version,
const base::FilePath& ecp)
: scope(scope),
app_id(app_id),
ap(ap),
lang(lang),
brand(brand),
version(app_version),
ecp(ecp) {}
AppInfo::AppInfo(const AppInfo&) = default;
AppInfo& AppInfo::operator=(const AppInfo&) = default;
AppInfo::~AppInfo() = default;

Installer::Installer(
const std::string& app_id,
const std::string& client_install_data,
const std::string& install_data_index,
const std::string& install_source,
const std::string& target_channel,
const std::string& target_version_prefix,
bool rollback_allowed,
bool update_disabled,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
scoped_refptr<PersistedData> persisted_data,
crx_file::VerifierFormat crx_verifier_format)
: updater_scope_(GetUpdaterScope()),
app_id_(app_id),
client_install_data_(client_install_data),
install_data_index_(install_data_index),
install_source_(install_source),
rollback_allowed_(rollback_allowed),
target_channel_(target_channel),
target_version_prefix_(target_version_prefix),
update_disabled_(update_disabled),
policy_same_version_update_(policy_same_version_update),
persisted_data_(persisted_data),
crx_verifier_format_(crx_verifier_format),
usage_stats_enabled_(IsUpdaterOrCompanionApp(app_id) &&
persisted_data->GetUsageStatsEnabled()),
app_info_(AppInfo(GetUpdaterScope(), app_id, {}, {}, {}, {}, {})) {}

Installer::~Installer() = default;

void Installer::MakeCrxComponent(
base::OnceCallback<void(update_client::CrxComponent)> callback) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(
&MakeAppInfo, updater_scope_, app_id_,
persisted_data_->GetProductVersion(app_id_),
persisted_data_->GetProductVersionPath(app_id_),
persisted_data_->GetProductVersionKey(app_id_),
persisted_data_->GetAP(app_id_), persisted_data_->GetAPPath(app_id_),
persisted_data_->GetAPKey(app_id_), persisted_data_->GetLang(app_id_),
persisted_data_->GetBrandCode(app_id_),
persisted_data_->GetBrandPath(app_id_), "KSBrandID",
persisted_data_->GetExistenceCheckerPath(app_id_)),
base::BindOnce(&Installer::MakeCrxComponentFromAppInfo, this,
std::move(callback)));
}

void Installer::MakeCrxComponentFromAppInfo(
base::OnceCallback<void(update_client::CrxComponent)> callback,
const AppInfo& app_info) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

VLOG(1) << __func__ << " for " << app_id_;

app_info_ = app_info;

update_client::CrxComponent component;
[Link] = scoped_refptr<Installer>(this);
component.action_handler = MakeActionHandler();
component.requires_network_encryption = false;
component.crx_format_requirement = crx_verifier_format_;
component.app_id = app_id_;

// Query server for install data only when the client does not specify one.
if (client_install_data_.empty()) {
component.install_data_index = install_data_index_;
}

[Link] = app_info_.ap;
[Link] = app_info_.lang;
[Link] = app_info_.brand;
[Link] = app_id_;
[Link] = app_info_.version;
[Link] = persisted_data_->GetFingerprint(app_id_);
[Link] = target_channel_;
component.rollback_allowed = rollback_allowed_;
component.same_version_update_allowed =
policy_same_version_update_ ==
UpdateService::PolicySameVersionUpdate::kAllowed;
component.target_version_prefix = target_version_prefix_;
component.updates_enabled = !update_disabled_;
component.install_source = install_source_;

std::move(callback).Run(component);
}

Installer::Result Installer::InstallHelper(
const base::FilePath& unpack_path,
std::unique_ptr<InstallParams> install_params,
ProgressCallback progress_callback) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
VLOG(1) << "Installing update for " << app_id_;

// Resolve the path to an installer file, which is included in the CRX, and
// specified by the |run| attribute in the manifest object of an update
// response.
if (!install_params || install_params->[Link]()) {
return Result(GOOPDATEINSTALL_E_FILENAME_INVALID,
kErrorMissingInstallParams);
}

// Assume the install params are ASCII for now.


// Upon success, when the control flow returns back to the |update_client|,
// the prefs are updated asynchronously with the new |pv| and |fingerprint|.
// The task sequencing guarantees that the prefs will be updated by the
// time another CrxDataCallback is invoked, which needs updated values.
return RunApplicationInstaller(
app_info_, unpack_path.AppendUTF8(install_params->run),
install_params->arguments,
WriteInstallerDataToTempFile(unpack_path,
client_install_data_.empty()
? install_params->server_install_data
: client_install_data_),
usage_stats_enabled_, kWaitForAppInstaller, std::move(progress_callback));
}

void Installer::InstallWithSyncPrimitives(
const base::FilePath& unpack_path,
std::unique_ptr<InstallParams> install_params,
ProgressCallback progress_callback,
Callback callback) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::WILL_BLOCK);
const auto result = InstallHelper(unpack_path, std::move(install_params),
std::move(progress_callback));
std::move(callback).Run(result);
}

void Installer::OnUpdateError(int error) {


LOG(ERROR) << "updater error: " << error << " for " << app_id_;
}

void Installer::Install(const base::FilePath& unpack_path,


const std::string& /*public_key*/,
std::unique_ptr<InstallParams> install_params,
ProgressCallback progress_callback,
Callback callback) {
base::ThreadPool::PostTask(
FROM_HERE,
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskPriority::USER_BLOCKING,
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN},
base::BindOnce(&Installer::InstallWithSyncPrimitives, this, unpack_path,
std::move(install_params), std::move(progress_callback),
std::move(callback)));
}

std::optional<base::FilePath> Installer::GetInstalledFile(
const std::string& file) {
return std::nullopt;
}

bool Installer::Uninstall() {
return false;
}

} // namespace updater
#include "chrome/updater/persisted_data.h"

#include <memory>
#include <string>

#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/time/time.h"
#include "base/version.h"
#include "chrome/updater/activity.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/test/test_scope.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/testing_pref_service.h"
#include "components/update_client/update_client.h"
#include "testing/gtest/include/gtest/gtest.h"

#if BUILDFLAG(IS_WIN)
#include <windows.h>

#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/win_constants.h"
#endif

namespace updater {

TEST(PersistedDataTest, Simple) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
RegisterPersistedDataPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

EXPECT_FALSE(metadata->GetProductVersion("someappid").IsValid());
EXPECT_TRUE(metadata->GetFingerprint("someappid").empty());
EXPECT_TRUE(metadata->GetAppIds().empty());

metadata->SetProductVersion("someappid", base::Version("1.0"));
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());

metadata->SetFingerprint("someappid", "fp1");
EXPECT_EQ("fp1", metadata->GetFingerprint("someappid"));

// Store some more apps in prefs, in addition to "someappid". Expect only


// the app ids for apps with valid versions to be returned.
metadata->SetProductVersion("appid1", base::Version("2.0"));
metadata->SetFingerprint("appid2-nopv", "somefp");
EXPECT_FALSE(metadata->GetProductVersion("appid2-nopv").IsValid());
const auto app_ids = metadata->GetAppIds();
EXPECT_EQ(2u, app_ids.size());
EXPECT_TRUE(base::Contains(app_ids, "someappid"));
EXPECT_TRUE(base::Contains(app_ids, "appid1"));
EXPECT_FALSE(base::Contains(app_ids, "appid2-nopv")); // No valid pv.

const base::Time time1 = base::Time::FromSecondsSinceUnixEpoch(10);


metadata->SetLastChecked(time1);
EXPECT_EQ(metadata->GetLastChecked(), time1);
const base::Time time2 = base::Time::FromSecondsSinceUnixEpoch(20);
metadata->SetLastStarted(time2);
EXPECT_EQ(metadata->GetLastStarted(), time2);
}

TEST(PersistedDataTest, MixedCase) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
RegisterPersistedDataPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

metadata->SetProductVersion("someappid", base::Version("1.0"));
metadata->SetProductVersion("SOMEAPPID2", base::Version("2.0"));
EXPECT_EQ("1.0", metadata->GetProductVersion("someAPPID").GetString());
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());
EXPECT_EQ("2.0", metadata->GetProductVersion("someAPPID2").GetString());
EXPECT_EQ("2.0", metadata->GetProductVersion("someappid2").GetString());
}

TEST(PersistedDataTest, SharedPref) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

metadata->SetProductVersion("someappid", base::Version("1.0"));
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());

// Now, create a new PersistedData reading from the same path, verify
// that it loads the value.
metadata = base::MakeRefCounted<PersistedData>(GetUpdaterScopeForTesting(),
[Link](), nullptr);
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());
}

TEST(PersistedDataTest, RemoveAppId) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

RegistrationRequest data;
data.app_id = "someappid";
[Link] = "somelang";
data.brand_code = "somebrand";
[Link] = "arandom-ap=likethis";
[Link] = base::Version("1.0");
data.existence_checker_path =
base::FilePath(FILE_PATH_LITERAL("some/file/path"));

metadata->RegisterApp(data);

data.app_id = "someappid2";
[Link] = "somelang";
data.brand_code = "somebrand";
[Link] = "arandom-ap=likethis";
[Link] = base::Version("2.0");
data.existence_checker_path =
base::FilePath(FILE_PATH_LITERAL("some/file/path"));

metadata->RegisterApp(data);
EXPECT_EQ(size_t{2}, metadata->GetAppIds().size());

metadata->RemoveApp("someAPPID");
EXPECT_EQ(size_t{1}, metadata->GetAppIds().size());

metadata->RemoveApp("someappid2");
EXPECT_TRUE(metadata->GetAppIds().empty());
}

TEST(PersistedDataTest, RegisterApp_SetFirstActive) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

RegistrationRequest data;
data.app_id = "someappid";
[Link] = "somelang";
data.brand_code = "somebrand";
[Link] = "arandom-ap=likethis";
[Link] = base::Version("1.0");
data.existence_checker_path =
base::FilePath(FILE_PATH_LITERAL("some/file/path"));
metadata->RegisterApp(data);
EXPECT_EQ(metadata->GetDateLastActive("someappid"), -1);
EXPECT_EQ(metadata->GetDateLastRollCall("someappid"), -1);

[Link] = base::Version("2.0");
[Link] = 1221;
[Link] = 1221;
metadata->RegisterApp(data);
EXPECT_EQ(metadata->GetDateLastActive("someappid"), 1221);
EXPECT_EQ(metadata->GetDateLastRollCall("someappid"), 1221);

[Link] = base::Version("3.0");
[Link] = std::nullopt;
[Link] = std::nullopt;
metadata->RegisterApp(data);
EXPECT_EQ(metadata->GetDateLastActive("someappid"), 1221);
EXPECT_EQ(metadata->GetDateLastRollCall("someappid"), 1221);
}

#if BUILDFLAG(IS_WIN)
TEST(PersistedDataTest, LastOSVersion) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
RegisterPersistedDataPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

EXPECT_EQ(metadata->GetLastOSVersion(), std::nullopt);

// This will persist the current OS version into the persisted data.
metadata->SetLastOSVersion();
EXPECT_NE(metadata->GetLastOSVersion(), std::nullopt);

// Compare the persisted data OS version to the version from `::GetVersionEx`.


const OSVERSIONINFOEX metadata_os = metadata->GetLastOSVersion().value();

OSVERSIONINFOEX os = {};
[Link] = sizeof(OSVERSIONINFOEX);

#pragma clang diagnostic push


#pragma clang diagnostic ignored "-Wdeprecated-declarations"
EXPECT_TRUE(::GetVersionEx(reinterpret_cast<OSVERSIONINFO*>(&os)));
#pragma clang diagnostic pop

EXPECT_EQ(metadata_os.dwOSVersionInfoSize, [Link]);
EXPECT_EQ(metadata_os.dwMajorVersion, [Link]);
EXPECT_EQ(metadata_os.dwMinorVersion, [Link]);
EXPECT_EQ(metadata_os.dwBuildNumber, [Link]);
EXPECT_EQ(metadata_os.dwPlatformId, [Link]);
EXPECT_STREQ(metadata_os.szCSDVersion, [Link]);
EXPECT_EQ(metadata_os.wServicePackMajor, [Link]);
EXPECT_EQ(metadata_os.wServicePackMinor, [Link]);
EXPECT_EQ(metadata_os.wSuiteMask, [Link]);
EXPECT_EQ(metadata_os.wProductType, [Link]);
}
TEST(PersistedDataTest, SetEulaRequired) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
RegisterPersistedDataPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

EXPECT_FALSE(metadata->GetEulaRequired());

// This will set "eula_required=true" in the persisted data and also persist
// `eulaaccepted=0` in the registry.
metadata->SetEulaRequired(/*eula_required=*/true);
EXPECT_TRUE(metadata->GetEulaRequired());
DWORD eula_accepted = 0;
const HKEY root = UpdaterScopeToHKeyRoot(GetUpdaterScopeForTesting());
EXPECT_EQ(base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
.ReadValueDW(L"eulaaccepted", &eula_accepted),
ERROR_SUCCESS);
EXPECT_EQ(eula_accepted, 0ul);

// This will set "eula_required=false" in the persisted data and also delete
// the `eulaaccepted` value in the registry.
metadata->SetEulaRequired(/*eula_required=*/false);
EXPECT_FALSE(metadata->GetEulaRequired());
EXPECT_FALSE(base::win::RegKey(root, UPDATER_KEY, Wow6432(KEY_READ))
.HasValue(L"eulaaccepted"));
}
#endif

class PersistedDataRegistrationRequestTest : public ::testing::Test {


#if BUILDFLAG(IS_WIN)
protected:
void SetUp() override { DeleteValuesInRegistry(); }
void TearDown() override { DeleteValuesInRegistry(); }

private:
void DeleteValuesInRegistry() {
for (const auto value : {kRegValueBrandCode, kRegValueLang}) {
base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScopeForTesting()),
GetAppClientStateKey(L"someappid").c_str(),
Wow6432(KEY_SET_VALUE))
.DeleteValue(value);
}
}
#endif
};

TEST_F(PersistedDataRegistrationRequestTest, RegistrationRequest) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

RegistrationRequest data;
data.app_id = "someappid";
[Link] = "somelang";
data.brand_code = "somebrand";
[Link] = "arandom-ap=likethis";
[Link] = base::Version("1.0");
data.existence_checker_path =
base::FilePath(FILE_PATH_LITERAL("some/file/path"));
[Link] = "testcohort";
data.cohort_name = "testcohortname";
data.cohort_hint = "testcohorthint";

metadata->RegisterApp(data);
EXPECT_TRUE(metadata->GetProductVersion("someappid").IsValid());
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());
EXPECT_EQ(FILE_PATH_LITERAL("some/file/path"),
metadata->GetExistenceCheckerPath("someappid").value());
EXPECT_EQ("arandom-ap=likethis", metadata->GetAP("someappid"));
EXPECT_EQ("somelang", metadata->GetLang("someappid"));
EXPECT_EQ("somebrand", metadata->GetBrandCode("someappid"));
#if BUILDFLAG(IS_WIN)
EXPECT_EQ(
base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScopeForTesting()),
GetAppClientStateKey(L"someappid").c_str(),
Wow6432(KEY_SET_VALUE))
.WriteValue(kRegValueBrandCode, L"nbrnd"),
ERROR_SUCCESS);
EXPECT_EQ(metadata->GetBrandCode("someappid"), "nbrnd");
#endif

EXPECT_EQ("testcohort", metadata->GetCohort("someappid"));
EXPECT_EQ("testcohortname", metadata->GetCohortName("someappid"));
EXPECT_EQ("testcohorthint", metadata->GetCohortHint("someappid"));

#if BUILDFLAG(IS_WIN)
base::win::RegKey key;
EXPECT_EQ([Link](UpdaterScopeToHKeyRoot(GetUpdaterScopeForTesting()),
GetAppClientStateKey(L"someappid").c_str(),
Wow6432(KEY_QUERY_VALUE)),
ERROR_SUCCESS);
std::wstring ap;
EXPECT_EQ([Link](L"ap", &ap), ERROR_SUCCESS);
EXPECT_EQ(ap, L"arandom-ap=likethis");
#endif
}

TEST_F(PersistedDataRegistrationRequestTest, RegistrationRequestPartial) {
auto pref = std::make_unique<TestingPrefServiceSimple>();
update_client::RegisterPrefs(pref->registry());
auto metadata = base::MakeRefCounted<PersistedData>(
GetUpdaterScopeForTesting(), [Link](), nullptr);

RegistrationRequest data;
data.app_id = "someappid";
[Link] = "somelang";
data.brand_code = "somebrand";
[Link] = "arandom-ap=likethis";
[Link] = base::Version("1.0");
data.existence_checker_path =
base::FilePath(FILE_PATH_LITERAL("some/file/path"));
metadata->RegisterApp(data);
EXPECT_TRUE(metadata->GetProductVersion("someappid").IsValid());
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid").GetString());
EXPECT_EQ(FILE_PATH_LITERAL("some/file/path"),
metadata->GetExistenceCheckerPath("someappid").value());
EXPECT_EQ("arandom-ap=likethis", metadata->GetAP("someappid"));
EXPECT_EQ("somelang", metadata->GetLang("someappid"));
EXPECT_EQ("somebrand", metadata->GetBrandCode("someappid"));

RegistrationRequest data2;
data2.app_id = data.app_id;
[Link] = "different_ap";
metadata->RegisterApp(data2);
EXPECT_EQ("1.0", metadata->GetProductVersion(data.app_id).GetString());
EXPECT_EQ(FILE_PATH_LITERAL("some/file/path"),
metadata->GetExistenceCheckerPath(data.app_id).value());
EXPECT_EQ("different_ap", metadata->GetAP(data.app_id));
EXPECT_EQ("somelang", metadata->GetLang("someappid"));
EXPECT_EQ("somebrand", metadata->GetBrandCode(data.app_id));

RegistrationRequest data3;
data3.app_id = "someappid3";
data3.brand_code = "somebrand";
[Link] = base::Version("1.0");
metadata->RegisterApp(data3);
EXPECT_TRUE(metadata->GetProductVersion("someappid3").IsValid());
EXPECT_EQ("1.0", metadata->GetProductVersion("someappid3").GetString());
EXPECT_EQ(FILE_PATH_LITERAL(""),
metadata->GetExistenceCheckerPath("someappid3").value());
EXPECT_EQ("", metadata->GetAP("someappid3"));
EXPECT_EQ("", metadata->GetLang("someappid3"));
EXPECT_EQ("somebrand", metadata->GetBrandCode("someappid3"));
}

} // namespace updater#include "chrome/updater/tag.h"

#include <algorithm>
#include <cstdint>
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/containers/contains.h"
#include "base/containers/span.h"
#include "base/files/file.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/memory_mapped_file.h"
#include "base/logging.h"
#include "base/no_destructor.h"
#include "base/strings/escape.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_split.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/types/cxx23_to_underlying.h"
#include "base/types/expected.h"
#include "base/uuid.h"
#include "build/build_config.h"
#include "chrome/updater/certificate_tag.h"

#if BUILDFLAG(IS_MAC)
#include <sys/types.h>
#include <sys/xattr.h>
#endif // BUILDFLAG(IS_MAC)

namespace updater::tagging {
namespace {

// Magic string used to identify the tag in the binary.


constexpr uint8_t kTagMagicUtf8[] = {'G', 'a', 'c', 't', '2', '.',
'0', 'O', 'm', 'a', 'h', 'a'};

// These constants are conceptually cross-platform, but only currently used


// on Mac.
#if BUILDFLAG(IS_MAC)
// Maximum length for the string representation of a tag that can be written
// into a binary. This is the amount of space that must be reserved in a binary
// for dynamic tagging, in a file format where tags can only be patched in place
// rather than inserted, immediately after the magic signature and size bytes.
// Because binary tag format includes an explicit tag size, no null terminator
// is included in this count.
constexpr size_t kMaxTagStringBytes = 8192;

// Maximum length for the binary representation of a tag, including its magic
// signature and length bytes.
constexpr size_t kMaxBinaryTagBytes =
kMaxTagStringBytes + 2 + sizeof(kTagMagicUtf8);
#endif // BUILDFLAG(IS_MAC)

// The name of the bundle being installed. If not specified, the first app's
// appname is used.
constexpr std::string_view kTagArgBundleName = "bundlename";

// The language of the product the user is installing.


constexpr std::string_view kTagArgLanguage = "lang";

// Flag denoting that the user is flighting a new test feature.


constexpr std::string_view kTagArgFlighting = "flighting";

// Flag denoting that the user has agreed to provide usage stats, crashreports
// etc.
constexpr std::string_view kTagArgUsageStats = "usagestats";

// A unique value for this installation session. It can be used to follow the
// progress from the website to installation completion.
constexpr std::string_view kTagArgInstallationId = "iid";

// The brand code used for branding. This value sets the initial brand for the
// updater and the client app. If a brand value already exists on the system,
// the new brand value is ignored.
constexpr std::string_view kTagArgBrandCode = "brand";

// The Client ID used for branding.


// If a client value already exists on the system, it should be ignored.
// This value is used to set the initial client for the updater and the client
// app.
constexpr std::string_view kTagArgClientId = "client";
// A set of experiment labels used to track installs that are included in
// experiments. Use "experiments" for per-app arguments; use "omahaexperiments"
// for updater-specific labels.
constexpr std::string_view kAppArgExperimentLabels = "experiments";
constexpr std::string_view kTagArgOmahaExperimentLabels = "omahaexperiments";

// A referral ID used for tracking referrals.


constexpr std::string_view kTagArgReferralId = "referral";

// Tells the updater what ap value to set in the registry.


constexpr std::string_view kAppArgAdditionalParameters = "ap";

// Indicates which browser to restart on successful install.


constexpr std::string_view kTagArgBrowserType = "browser";

// Runtime Mode: "runtime" argument in the tag tells the updater to install
// itself and stay on the system without any associated application for at least
// `kMaxServerStartsBeforeFirstReg` wakes. This feature is used to expose the
// COM API to a process that will install applications via that API.
// Example:
// "runtime=true&needsadmin=true"
constexpr std::string_view kTagArgRuntimeMode = "runtime";

// Enrollment token: "etoken" argument in the tag tells the per-machine updater
// to register the machine to the device management server. The value must be a
// GUID.
// Example:
// "etoken=5d086552-4514-4dfb-8a3e-337024ec35ac"
constexpr std::string_view kTagArgErollmentToken = "etoken";

// The list of arguments that are needed for a meta-installer, to


// indicate which application is being installed. These are stamped
// inside the meta-installer binary.
constexpr std::string_view kTagArgAppId = "appguid";
constexpr std::string_view kAppArgAppName = "appname";
constexpr std::string_view kTagArgNeedsAdmin = "needsadmin";
constexpr std::string_view kAppArgInstallDataIndex = "installdataindex";
constexpr std::string_view kAppArgUntrustedData = "untrusteddata";

// This switch allows extra data to be communicated to the application


// installer. The extra data needs to be URL-encoded. The data will be decoded
// and written to the file, that is then passed in the command line to the
// application installer in the form "/installerdata=[Link]". One per
// application.
constexpr std::string_view kAppArgInstallerData = "installerdata";

// Character that is disallowed from appearing in the tag.


constexpr char kDisallowedCharInTag = '/';

std::optional<NeedsAdmin> ParseNeedsAdminEnum(std::string_view str) {


if (base::EqualsCaseInsensitiveASCII("false", str)) {
return NeedsAdmin::kNo;
}

if (base::EqualsCaseInsensitiveASCII("true", str)) {
return NeedsAdmin::kYes;
}
if (base::EqualsCaseInsensitiveASCII("prefers", str)) {
return NeedsAdmin::kPrefers;
}

return std::nullopt;
}

// Returns std::nullopt if parsing failed.


std::optional<bool> ParseBool(std::string_view str) {
if (base::EqualsCaseInsensitiveASCII("false", str)) {
return false;
}

if (base::EqualsCaseInsensitiveASCII("true", str)) {
return true;
}

return std::nullopt;
}

// Functor used by associative containers of strings as a case-insensitive ASCII


// compare. `StringT` could be either UTF-8 or UTF-16.
struct CaseInsensitiveASCIICompare {
public:
template <typename StringT>
bool operator()(const StringT& x, const StringT& y) const {
return base::CompareCaseInsensitiveASCII(x, y) > 0;
}
};

namespace global_attributes {

ErrorCode ParseBundleName(std::string_view value, TagArgs& args) {


value = base::TrimWhitespaceASCII(value, base::TrimPositions::TRIM_ALL);
if ([Link]()) {
return ErrorCode::kGlobal_BundleNameCannotBeWhitespace;
}

args.bundle_name = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseInstallationId(std::string_view value, TagArgs& args) {


args.installation_id = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseBrandCode(std::string_view value, TagArgs& args) {


args.brand_code = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseClientId(std::string_view value, TagArgs& args) {


args.client_id = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseOmahaExperimentLabels(std::string_view value, TagArgs& args) {


value = base::TrimWhitespaceASCII(value, base::TrimPositions::TRIM_ALL);
if ([Link]()) {
return ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace;
}

args.experiment_labels = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseReferralId(std::string_view value, TagArgs& args) {


args.referral_id = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseBrowserType(std::string_view value, TagArgs& args) {


int browser_type = 0;
if (!base::StringToInt(value, &browser_type)) {
return ErrorCode::kGlobal_BrowserTypeIsInvalid;
}

if (browser_type < 0) {
return ErrorCode::kGlobal_BrowserTypeIsInvalid;
}

args.browser_type =
browser_type < base::to_underlying(TagArgs::BrowserType::kMax)
? TagArgs::BrowserType(browser_type)
: TagArgs::BrowserType::kUnknown;

return ErrorCode::kSuccess;
}

ErrorCode ParseLanguage(std::string_view value, TagArgs& args) {


// Even if we don't support the language, we want to pass it to the
// installer. Omaha will pick its language later. See [Link]
[Link] = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseFlighting(std::string_view value, TagArgs& args) {


const std::optional<bool> flighting = ParseBool(value);
if (!flighting.has_value()) {
return ErrorCode::kGlobal_FlightingValueIsNotABoolean;
}

[Link] = [Link]();
return ErrorCode::kSuccess;
}

ErrorCode ParseUsageStats(std::string_view value, TagArgs& args) {


int tristate = 0;
if (!base::StringToInt(value, &tristate)) {
return ErrorCode::kGlobal_UsageStatsValueIsInvalid;
}

if (tristate == 0) {
args.usage_stats_enable = false;
} else if (tristate == 1) {
args.usage_stats_enable = true;
} else if (tristate == 2) {
args.usage_stats_enable = std::nullopt;
} else {
return ErrorCode::kGlobal_UsageStatsValueIsInvalid;
}
return ErrorCode::kSuccess;
}

// Parses an app ID and adds it to the list of apps in |args|, if valid.


ErrorCode ParseAppId(std::string_view value, TagArgs& args) {
if (!base::IsStringASCII(value)) {
return ErrorCode::kApp_AppIdIsNotValid;
}

[Link].push_back(AppArgs(value));
return ErrorCode::kSuccess;
}

ErrorCode ParseRuntimeMode(std::string_view value, TagArgs& args) {


for (const std::string_view expected_value : {"true", "persist", "false"}) {
if (base::EqualsCaseInsensitiveASCII(expected_value, value)) {
args.runtime_mode = RuntimeModeArgs();
return ErrorCode::kSuccess;
}
}

return ErrorCode::kGlobal_RuntimeModeValueIsInvalid;
}

ErrorCode ParseEnrollmentToken(std::string_view value, TagArgs& args) {


if (!base::Uuid::ParseCaseInsensitive(value).is_valid()) {
return ErrorCode::kGlobal_EnrollmentTokenValueIsInvalid;
}
args.enrollment_token = value;
return ErrorCode::kSuccess;
}

// |value| must not be empty.


using ParseGlobalAttributeFunPtr = ErrorCode (*)(std::string_view value,
TagArgs& args);

using GlobalParseTable = std::map<std::string_view,


ParseGlobalAttributeFunPtr,
CaseInsensitiveASCIICompare>;

const GlobalParseTable& GetTable() {


static const base::NoDestructor<GlobalParseTable> instance{
{{kTagArgBundleName, &ParseBundleName},
{kTagArgInstallationId, &ParseInstallationId},
{kTagArgBrandCode, &ParseBrandCode},
{kTagArgClientId, &ParseClientId},
{kTagArgOmahaExperimentLabels, &ParseOmahaExperimentLabels},
{kTagArgReferralId, &ParseReferralId},
{kTagArgBrowserType, &ParseBrowserType},
{kTagArgLanguage, &ParseLanguage},
{kTagArgFlighting, &ParseFlighting},
{kTagArgUsageStats, &ParseUsageStats},
{kTagArgAppId, &ParseAppId},
{kTagArgRuntimeMode, &ParseRuntimeMode},
{kTagArgErollmentToken, &ParseEnrollmentToken}}};
return *instance;
}

} // namespace global_attributes

namespace app_attributes {

ErrorCode ParseAdditionalParameters(std::string_view value, AppArgs& args) {


[Link] = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseExperimentLabels(std::string_view value, AppArgs& args) {


value = base::TrimWhitespaceASCII(value, base::TrimPositions::TRIM_ALL);
if ([Link]()) {
return ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace;
}

args.experiment_labels = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseAppName(std::string_view value, AppArgs& args) {


value = base::TrimWhitespaceASCII(value, base::TrimPositions::TRIM_ALL);
if ([Link]()) {
return ErrorCode::kApp_AppNameCannotBeWhitespace;
}

args.app_name = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseNeedsAdmin(std::string_view value, AppArgs& args) {


const auto needs_admin = ParseNeedsAdminEnum(value);
if (!needs_admin.has_value()) {
return ErrorCode::kApp_NeedsAdminValueIsInvalid;
}

args.needs_admin = needs_admin.value();
return ErrorCode::kSuccess;
}

ErrorCode ParseInstallDataIndex(std::string_view value, AppArgs& args) {


args.install_data_index = value;
return ErrorCode::kSuccess;
}

ErrorCode ParseUntrustedData(std::string_view value, AppArgs& args) {


args.untrusted_data = value;
return ErrorCode::kSuccess;
}

// |value| must not be empty.


using ParseAppAttributeFunPtr = ErrorCode (*)(std::string_view value,
AppArgs& args);

using AppParseTable = std::


map<std::string_view, ParseAppAttributeFunPtr, CaseInsensitiveASCIICompare>;
const AppParseTable& GetTable() {
static const base::NoDestructor<AppParseTable> instance{{
{kAppArgAdditionalParameters, &ParseAdditionalParameters},
{kAppArgExperimentLabels, &ParseExperimentLabels},
{kAppArgAppName, &ParseAppName},
{kTagArgNeedsAdmin, &ParseNeedsAdmin},
{kAppArgInstallDataIndex, &ParseInstallDataIndex},
{kAppArgUntrustedData, &ParseUntrustedData},
}};
return *instance;
}

} // namespace app_attributes

namespace runtime_mode_attributes {

ErrorCode ParseNeedsAdmin(std::string_view value, RuntimeModeArgs& args) {


const auto needs_admin = ParseNeedsAdminEnum(value);
if (!needs_admin.has_value()) {
return ErrorCode::kRuntimeMode_NeedsAdminValueIsInvalid;
}

args.needs_admin = needs_admin.value();
return ErrorCode::kSuccess;
}

// |value| must not be empty.


using ParseRuntimeModeAttributeFunPtr = ErrorCode (*)(std::string_view value,
RuntimeModeArgs& args);

using RuntimeModeParseTable = std::map<std::string_view,


ParseRuntimeModeAttributeFunPtr,
CaseInsensitiveASCIICompare>;

const RuntimeModeParseTable& GetTable() {


static const base::NoDestructor<RuntimeModeParseTable> instance{{
{kTagArgNeedsAdmin, &ParseNeedsAdmin},
}};
return *instance;
}

} // namespace runtime_mode_attributes

namespace installer_data_attributes {

// Search for the given appid specified by |value| in |[Link]| and write its
// index to |current_app_index|.
ErrorCode FindAppIdInTagArgs(std::string_view value,
TagArgs& args,
std::optional<size_t>& current_app_index) {
if (!base::IsStringASCII(value)) {
return ErrorCode::kApp_AppIdIsNotValid;
}

// Find the app in the existing list.


for (size_t i = 0; i < [Link](); i++) {
if (base::EqualsCaseInsensitiveASCII([Link][i].app_id, value)) {
current_app_index = i;
}
}

if (!current_app_index.has_value()) {
return ErrorCode::kAppInstallerData_AppIdNotFound;
}

return ErrorCode::kSuccess;
}

ErrorCode ParseInstallerData(std::string_view value,


TagArgs& args,
std::optional<size_t>& current_app_index) {
if (!current_app_index.has_value()) {
return ErrorCode::
kAppInstallerData_InstallerDataCannotBeSpecifiedBeforeAppId;
}

[Link][current_app_index.value()].encoded_installer_data = value;

return ErrorCode::kSuccess;
}

// |value| must not be empty.


// |current_app_index| is an in/out parameter. It stores the index of the
// current app and nullopt if no app has been set yet. Writing to it will set
// the index for future calls to these functions.
using ParseInstallerDataAttributeFunPtr =
ErrorCode (*)(std::string_view value,
TagArgs& args,
std::optional<size_t>& current_app_index);

using InstallerDataParseTable = std::map<std::string_view,


ParseInstallerDataAttributeFunPtr,
CaseInsensitiveASCIICompare>;

const InstallerDataParseTable& GetTable() {


static const base::NoDestructor<InstallerDataParseTable> instance{{
{kTagArgAppId, &FindAppIdInTagArgs},
{kAppArgInstallerData, &ParseInstallerData},
}};
return *instance;
}

} // namespace installer_data_attributes

namespace query_string {

// An attribute in a metainstaller tag or app installer data args string.


// - The first value is the "name" of the attribute.
// - The second value is the "value" of the attribute.
using Attribute = std::pair<std::string, std::string>;

// Splits |query_string| into |Attribute|s. Attribute values will be unescaped


// if |unescape_value| is true.
//
// Ownership follows the same rules as |base::SplitStringPiece|.
std::vector<Attribute> Split(std::string_view query_string,
bool unescape_value = true) {
std::vector<Attribute> attributes;
for (const auto& attribute_string :
base::SplitStringPiece(query_string, "&", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
size_t separate_pos = attribute_string.find_first_of("=");
if (separate_pos == std::string_view::npos) {
// Add a name-only attribute.
std::string_view name = base::TrimWhitespaceASCII(
attribute_string, base::TrimPositions::TRIM_ALL);
attributes.emplace_back(std::string{name}, "");
} else {
std::string_view name =
base::TrimWhitespaceASCII(attribute_string.substr(0, separate_pos),
base::TrimPositions::TRIM_ALL);
std::string_view value =
base::TrimWhitespaceASCII(attribute_string.substr(separate_pos + 1),
base::TrimPositions::TRIM_ALL);
attributes.emplace_back(
name,
unescape_value
? base::UnescapeURLComponent(
value, base::UnescapeRule::SPACES |
base::UnescapeRule::
URL_SPECIAL_CHARS_EXCEPT_PATH_SEPARATORS |
base::UnescapeRule::PATH_SEPARATORS)
: std::string{value});
}
}
return attributes;
}

} // namespace query_string

// Parses global and app-specific attributes from |tag|.


ErrorCode ParseTag(std::string_view tag, TagArgs& args) {
const auto& global_func_lookup_table = global_attributes::GetTable();
const auto& app_func_lookup_table = app_attributes::GetTable();
const auto& runtime_mode_func_lookup_table =
runtime_mode_attributes::GetTable();

const std::vector<std::pair<std::string, std::string>> attributes =


query_string::Split(tag);
for (const auto& [name, value] : attributes) {
// Attribute names are only ASCII, so no i18n case folding needed.
if (global_func_lookup_table.contains(name)) {
if ([Link]()) {
return ErrorCode::kAttributeMustHaveValue;
}

const ErrorCode result = global_func_lookup_table.at(name)(value, args);


if (result != ErrorCode::kSuccess) {
return result;
}
} else if ((runtime_mode_func_lookup_table.contains(name)) &&
args.runtime_mode) {
if ([Link]()) {
return ErrorCode::kAttributeMustHaveValue;
}

const ErrorCode result =


runtime_mode_func_lookup_table.at(name)(value, *args.runtime_mode);
if (result != ErrorCode::kSuccess) {
return result;
}
} else if (app_func_lookup_table.contains(name)) {
if ([Link]()) {
return ErrorCode::kApp_AppIdNotSpecified;
}

if ([Link]()) {
return ErrorCode::kAttributeMustHaveValue;
}

AppArgs& current_app = [Link]();


const ErrorCode result =
app_func_lookup_table.at(name)(value, current_app);
if (result != ErrorCode::kSuccess) {
return result;
}
} else {
return ErrorCode::kUnrecognizedName;
}
}

// The bundle name inherits the first app's name, if not set.
if (args.bundle_name.empty() && ![Link]()) {
args.bundle_name = [Link][0].app_name;
}
args.tag_string = tag;
[Link] = attributes;

return ErrorCode::kSuccess;
}

// Parses app-specific installer data from |app_installer_data_args|.


ErrorCode ParseAppInstallerDataArgs(std::string_view app_installer_data_args,
TagArgs& args) {
// The currently tracked app index to apply installer data to.
std::optional<size_t> current_app_index;

// Installer data is assumed to be URL-encoded, so we don't unescape it.


bool unescape_value = false;

for (const auto& [name, value] :


query_string::Split(app_installer_data_args, unescape_value)) {
if ([Link]()) {
return ErrorCode::kAttributeMustHaveValue;
}

const auto& func_lookup_table = installer_data_attributes::GetTable();


if (!func_lookup_table.contains(name)) {
return ErrorCode::kUnrecognizedName;
}

const ErrorCode result =


func_lookup_table.at(name)(value, args, current_app_index);
if (result != ErrorCode::kSuccess) {
return result;
}
}

return ErrorCode::kSuccess;
}

// Checks that |args| does not contain |kDisallowedCharInTag|.


bool IsValidArgs(std::string_view args) {
return !base::Contains(args, kDisallowedCharInTag);
}

// Returns a `uint16_t` value as big-endian bytes.


std::array<uint8_t, 2> U16IntToBigEndian(uint16_t value) {
return {static_cast<uint8_t>((value & 0xFF00) >> 8),
static_cast<uint8_t>(value & 0x00FF)};
}

// Converts a big-endian 2-byte value to little-endian and returns it


// as a uint16_t.
uint16_t BigEndianReadU16(std::vector<uint8_t>::const_iterator it) {
static_assert(ARCH_CPU_LITTLE_ENDIAN, "Machine should be little-endian.");
return (uint16_t{*it} << 8) + (uint16_t{*(it + 1)});
}

// Loads up to the last 80K bytes from `filename`.


std::vector<uint8_t> ReadFileTail(const base::FilePath& filename) {
constexpr int64_t kMaxBytesToRead = 81920; // 80K

base::File file(filename, base::File::FLAG_OPEN | base::File::FLAG_READ);


if (![Link]()) {
return {};
}

const int64_t file_length = [Link]();


const int64_t bytes_to_read = std::min(file_length, kMaxBytesToRead);
const int64_t offset =
(file_length > bytes_to_read) ? file_length - bytes_to_read : 0;

std::vector<uint8_t> buffer(bytes_to_read);
return [Link](offset, base::span(buffer)) ? buffer
: std::vector<uint8_t>();
}

std::string ParseTagBuffer(const std::vector<uint8_t>& tag_buffer) {


if (tag_buffer.empty()) {
return {};
}

const std::string tag_string = ReadTag(tag_buffer.begin(), tag_buffer.end());


LOG_IF(ERROR, tag_string.empty()) << __func__ << ": Tag not found in file.";
return tag_string;
}

std::vector<uint8_t> ReadEntireFile(const base::FilePath& file) {


std::optional<int64_t> file_size = base::GetFileSize(file);
if (!file_size.has_value()) {
PLOG(ERROR) << __func__ << ": Could not get file size: " << file;
return {};
}
std::vector<uint8_t> contents(file_size.value());
if (base::ReadFile(file, reinterpret_cast<char*>(&[Link]()),
[Link]()) == -1) {
PLOG(ERROR) << __func__ << ": Could not read file: " << file;
return {};
}
return contents;
}

} // namespace

namespace internal {
std::vector<uint8_t>::const_iterator AdvanceIt(
std::vector<uint8_t>::const_iterator it,
size_t distance,
std::vector<uint8_t>::const_iterator end) {
if (it >= end) {
return end;
}

ptrdiff_t dist_to_end = 0;
if (!base::CheckedNumeric<ptrdiff_t>(end - it).AssignIfValid(&dist_to_end)) {
return end;
}

return it + std::min(distance, static_cast<size_t>(dist_to_end));


}

bool CheckRange(std::vector<uint8_t>::const_iterator it,


size_t size,
std::vector<uint8_t>::const_iterator end) {
if (it >= end || size == 0) {
return false;
}

ptrdiff_t dist_to_end = 0;
if (!base::CheckedNumeric<ptrdiff_t>(end - it).AssignIfValid(&dist_to_end)) {
return false;
}

return size <= static_cast<size_t>(dist_to_end);


}
} // namespace internal

AppArgs::AppArgs(std::string_view app_id) : app_id(base::ToLowerASCII(app_id)) {


CHECK(!app_id.empty());
}

AppArgs::~AppArgs() = default;
AppArgs::AppArgs(const AppArgs&) = default;
AppArgs& AppArgs::operator=(const AppArgs&) = default;
AppArgs::AppArgs(AppArgs&&) = default;
AppArgs& AppArgs::operator=(AppArgs&&) = default;

TagArgs::TagArgs() = default;
TagArgs::~TagArgs() = default;
TagArgs::TagArgs(const TagArgs&) = default;
TagArgs& TagArgs::operator=(const TagArgs&) = default;
TagArgs::TagArgs(TagArgs&&) = default;
TagArgs& TagArgs::operator=(TagArgs&&) = default;

ErrorCode Parse(std::string_view tag,


std::optional<std::string_view> app_installer_data_args,
TagArgs& args) {
if (!IsValidArgs(tag)) {
return ErrorCode::kTagIsInvalid;
}

const ErrorCode result = ParseTag(tag, args);


if (result != ErrorCode::kSuccess) {
return result;
}

if (!app_installer_data_args.has_value()) {
return ErrorCode::kSuccess;
}

if (!IsValidArgs(app_installer_data_args.value())) {
return ErrorCode::kTagIsInvalid;
}

return ParseAppInstallerDataArgs(app_installer_data_args.value(), args);


}

std::ostream& operator<<(std::ostream& os, const ErrorCode& error_code) {


switch (error_code) {
case ErrorCode::kSuccess:
return os << "ErrorCode::kSuccess";
case ErrorCode::kUnrecognizedName:
return os << "ErrorCode::kUnrecognizedName";
case ErrorCode::kTagIsInvalid:
return os << "ErrorCode::kTagIsInvalid";
case ErrorCode::kAttributeMustHaveValue:
return os << "ErrorCode::kAttributeMustHaveValue";
case ErrorCode::kApp_AppIdNotSpecified:
return os << "ErrorCode::kApp_AppIdNotSpecified";
case ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace:
return os << "ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace";
case ErrorCode::kApp_AppIdIsNotValid:
return os << "ErrorCode::kApp_AppIdIsNotValid";
case ErrorCode::kApp_AppNameCannotBeWhitespace:
return os << "ErrorCode::kApp_AppNameCannotBeWhitespace";
case ErrorCode::kApp_NeedsAdminValueIsInvalid:
return os << "ErrorCode::kApp_NeedsAdminValueIsInvalid";
case ErrorCode::kAppInstallerData_AppIdNotFound:
return os << "ErrorCode::kAppInstallerData_AppIdNotFound";
case ErrorCode::kAppInstallerData_InstallerDataCannotBeSpecifiedBeforeAppId:
return os << "ErrorCode::kAppInstallerData_"
"InstallerDataCannotBeSpecifiedBeforeAppId";
case ErrorCode::kGlobal_BundleNameCannotBeWhitespace:
return os << "ErrorCode::kGlobal_BundleNameCannotBeWhitespace";
case ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace:
return os << "ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace";
case ErrorCode::kGlobal_BrowserTypeIsInvalid:
return os << "ErrorCode::kGlobal_BrowserTypeIsInvalid";
case ErrorCode::kGlobal_FlightingValueIsNotABoolean:
return os << "ErrorCode::kGlobal_FlightingValueIsNotABoolean";
case ErrorCode::kGlobal_UsageStatsValueIsInvalid:
return os << "ErrorCode::kGlobal_UsageStatsValueIsInvalid";
case ErrorCode::kGlobal_RuntimeModeValueIsInvalid:
return os << "ErrorCode::kGlobal_RuntimeModeValueIsInvalid";
case ErrorCode::kGlobal_EnrollmentTokenValueIsInvalid:
return os << "ErrorCode::kGlobal_EnrollmentTokenValueIsInvalid";
case ErrorCode::kRuntimeMode_NeedsAdminValueIsInvalid:
return os << "ErrorCode::kRuntimeMode_NeedsAdminValueIsInvalid";
case ErrorCode::kTagNotFound:
return os << "ErrorCode::kTagNotFound";
}
}

std::ostream& operator<<(std::ostream& os, const NeedsAdmin& needs_admin) {


switch (needs_admin) {
case NeedsAdmin::kNo:
return os << "NeedsAdmin::kNo";
case NeedsAdmin::kYes:
return os << "NeedsAdmin::kYes";
case NeedsAdmin::kPrefers:
return os << "NeedsAdmin::kPrefers";
}
}

std::ostream& operator<<(std::ostream& os,


const TagArgs::BrowserType& browser_type) {
switch (browser_type) {
case TagArgs::BrowserType::kUnknown:
return os << "TagArgs::BrowserType::kUnknown";
case TagArgs::BrowserType::kDefault:
return os << "TagArgs::BrowserType::kDefault";
case TagArgs::BrowserType::kInternetExplorer:
return os << "TagArgs::BrowserType::kInternetExplorer";
case TagArgs::BrowserType::kFirefox:
return os << "TagArgs::BrowserType::kFirefox";
case TagArgs::BrowserType::kChrome:
return os << "TagArgs::BrowserType::kChrome";
default:
return os << "TagArgs::BrowserType(" << browser_type << ")";
}
}

std::vector<uint8_t> GetTagFromTagString(const std::string& tag_string) {


std::vector<uint8_t> tag(std::begin(kTagMagicUtf8), std::end(kTagMagicUtf8));
const std::array<uint8_t, 2> tag_length =
U16IntToBigEndian(tag_string.length());
[Link]([Link](), tag_length.begin(), tag_length.end());
[Link]([Link](), tag_string.begin(), tag_string.end());
return tag;
}

std::string ReadTag(std::vector<uint8_t>::const_iterator begin,


std::vector<uint8_t>::const_iterator end) {
const uint8_t* magic_begin = std::begin(kTagMagicUtf8);
const uint8_t* magic_end = std::end(kTagMagicUtf8);

std::vector<uint8_t>::const_iterator magic_str =
std::find_end(begin, end, magic_begin, magic_end);
if (magic_str == end) {
return std::string();
}

std::vector<uint8_t>::const_iterator taglen_buf =
internal::AdvanceIt(magic_str, magic_end - magic_begin, end);

// Checks that the stored tag length is found within the binary.
if (!internal::CheckRange(taglen_buf, sizeof(uint16_t), end)) {
return std::string();
}

// Tag length is stored as a big-endian uint16_t.


const uint16_t tag_len = BigEndianReadU16(taglen_buf);

std::vector<uint8_t>::const_iterator tag_buf =
internal::AdvanceIt(taglen_buf, sizeof(uint16_t), end);
if (tag_buf == end) {
return std::string();
}

// Checks that the specified tag is found within the binary.


if (!internal::CheckRange(tag_buf, tag_len, end)) {
return std::string();
}

return std::string(tag_buf, tag_buf + tag_len);


}

std::unique_ptr<tagging::BinaryInterface> CreateBinary(
const base::FilePath& file,
base::span<const uint8_t> contents) {
if ([Link](FILE_PATH_LITERAL(".exe"))) {
return CreatePEBinary(contents);
} else if ([Link](FILE_PATH_LITERAL(".msi"))) {
return CreateMSIBinary(contents);
} else {
std::unique_ptr<BinaryInterface> binary = CreatePEBinary(contents);
if (!binary) {
binary = CreateMSIBinary(contents);
}
return binary;
}
}

std::string BinaryReadTagString(const base::FilePath& file) {


// For MSI files, simply search the tail of the file for the tag.
if (![Link](FILE_PATH_LITERAL(".exe"))) {
return ParseTagBuffer(ReadFileTail(file));
}

base::MemoryMappedFile mapped_file;
if (!mapped_file.Initialize(file)) {
LOG(ERROR) << __func__ << ": Unknown or empty file: " << file;
return {};
}
std::unique_ptr<tagging::BinaryInterface> bin =
CreateBinary(file, mapped_file.bytes());
if (!bin) {
LOG(ERROR) << __func__ << ": Could not parse binary: " << file;
return {};
}

std::optional<std::vector<uint8_t>> tag = bin->tag();


if (!tag) {
LOG(ERROR) << __func__ << ": No superfluous certificate in file: " << file;
return {};
}

const std::vector<uint8_t> tag_data = {tag->begin(), tag->end()};


const std::string tag_string = ReadTag(tag_data.begin(), tag_data.end());
if (tag_string.empty()) {
LOG(ERROR) << __func__ << ": file is untagged: " << file;
}
return tag_string;
}

std::optional<tagging::TagArgs> BinaryReadTag(const base::FilePath& file) {


const std::string tag_string = BinaryReadTagString(file);
if (tag_string.empty()) {
return {};
}
tagging::TagArgs tag_args;
const tagging::ErrorCode error = tagging::Parse(tag_string, {}, tag_args);
if (error != tagging::ErrorCode::kSuccess) {
LOG(ERROR) << __func__ << ": Invalid tag string: " << tag_string << ": "
<< error;
return {};
}
return tag_args;
}

bool BinaryWriteTag(const base::FilePath& in_file,


const std::string& tag_string,
int padded_length,
base::FilePath out_file) {
const std::vector<uint8_t> contents = ReadEntireFile(in_file);
std::unique_ptr<tagging::BinaryInterface> bin =
CreateBinary(in_file, contents);
if (!bin) {
LOG(ERROR) << __func__ << ": Could not parse binary: " << in_file;
return false;
}

// Validate the tag string, if any.


if (!tag_string.empty()) {
tagging::TagArgs tag_args;
const tagging::ErrorCode error = tagging::Parse(tag_string, {}, tag_args);
if (error != tagging::ErrorCode::kSuccess) {
LOG(ERROR) << __func__ << ": Invalid tag string: " << tag_string << ": "
<< error;
return false;
}
}

std::vector<uint8_t> tag_contents = tagging::GetTagFromTagString(tag_string);

if (padded_length > 0) {
size_t new_size = 0;
if (base::CheckAdd(tag_contents.size(), padded_length)
.AssignIfValid(&new_size)) {
tag_contents.resize(new_size);
} else {
LOG(ERROR) << __func__ << "Failed to pad the tag contents.";
return false;
}
}

auto new_contents = bin->SetTag(tag_contents);


if (!new_contents) {
LOG(ERROR) << __func__
<< "Error while setting superfluous certificate tag.";
return false;
}
if (out_file.empty()) {
out_file = in_file;
}
if (!base::WriteFile(out_file, *new_contents)) {
PLOG(ERROR) << __func__ << "Error while writing updated file: " << out_file;
return false;
}
return true;
}

#if BUILDFLAG(IS_MAC)

base::expected<TagArgs, ErrorCode> ReadTagFromApplicationInstanceXattr(


const base::FilePath& path) {
if ([Link]()) {
VLOG(0) << "no path in ReadTagFromApplicationInstanceXattr";
return base::unexpected(ErrorCode::kTagNotFound);
}

std::vector<uint8_t> raw_tag(kMaxBinaryTagBytes, 0);


ssize_t got_bytes =
getxattr([Link]().c_str(), "[Link]-instance",
raw_tag.data(), kMaxBinaryTagBytes, 0, 0);
// If a C API says it wrote past the end of a buffer, believe it.
CHECK(got_bytes <= static_cast<ssize_t>(kMaxBinaryTagBytes))
<< "getxattr wrote " << got_bytes << " bytes into a "
<< kMaxBinaryTagBytes << " byte buffer!";
if (got_bytes < 0) {
VPLOG(1) << "getxattr could not read [Link]-instance on "
<< path;
return base::unexpected(ErrorCode::kTagNotFound);
}
std::vector<uint8_t>::iterator tag_data_begin = raw_tag.begin();
std::string tag_string = ReadTag(tag_data_begin, tag_data_begin + got_bytes);
if (tag_string.empty()) {
return base::unexpected(ErrorCode::kTagNotFound);
}
TagArgs value;
ErrorCode code = Parse(tag_string, {}, value);
if (code != ErrorCode::kSuccess) {
return base::unexpected(code);
}
return value;
}
bool WriteTagStringToApplicationInstanceXattr(const base::FilePath& path,
const std::string& tag_string) {
if ([Link]()) {
VLOG(0) << "no path provided when writing xattr tag";
return false;
}
if (tag_string.size() > kMaxTagStringBytes) {
VLOG(1) << "xattr tag too big, will be truncated when read";
// warning only; continue
}
if (tag_string.empty()) {
VLOG(1) << "writing empty xattr tag";
// warning only; continue
}
std::vector<uint8_t> tag_bytes = GetTagFromTagString(tag_string);
if (tag_bytes.empty()) {
VLOG(0) << "could not create xattr tag";
return false;
}
int result = setxattr([Link]().c_str(), "[Link]-instance",
tag_bytes.data(), tag_bytes.size(), 0, 0);
if (result) {
VPLOG(0) << "setxattr failed on " << path;
return false;
}
return true;
}

#endif // BUILDFLAG(IS_MAC)

} // namespace updater::tagging
#include "chrome/updater/tag.h"

#include <cstdint>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/types/cxx23_to_underlying.h"
#include "chrome/updater/test/unit_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace {

using updater::tagging::AppArgs;
using updater::tagging::ErrorCode;
using updater::tagging::NeedsAdmin;
using updater::tagging::RuntimeModeArgs;
using updater::tagging::TagArgs;

// Builder pattern helper to construct the TagArgs struct.


class TagArgsBuilder {
public:
TagArgs Build() { return std::move(inner_); }
TagArgsBuilder& WithBundleName(const std::string& bundle_name) {
this->inner_.bundle_name = bundle_name;
return *this;
}
TagArgsBuilder& WithInstallationId(const std::string& installation_id) {
this->inner_.installation_id = installation_id;
return *this;
}
TagArgsBuilder& WithBrandCode(const std::string& brand_code) {
this->inner_.brand_code = brand_code;
return *this;
}
TagArgsBuilder& WithClientId(const std::string& client_id) {
this->inner_.client_id = client_id;
return *this;
}
TagArgsBuilder& WithExperimentLabels(const std::string& experiment_labels) {
this->inner_.experiment_labels = experiment_labels;
return *this;
}
TagArgsBuilder& WithReferralId(const std::string& referral_id) {
this->inner_.referral_id = referral_id;
return *this;
}
TagArgsBuilder& WithLanguage(const std::string& language) {
this->inner_.language = language;
return *this;
}
TagArgsBuilder& WithFlighting(bool flighting) {
this->inner_.flighting = flighting;
return *this;
}
TagArgsBuilder& WithBrowserType(TagArgs::BrowserType browser_type) {
this->inner_.browser_type = browser_type;
return *this;
}
TagArgsBuilder& WithUsageStatsEnable(bool usage_stats_enable) {
this->inner_.usage_stats_enable = usage_stats_enable;
return *this;
}
TagArgsBuilder& WithApp(AppArgs app) {
this->inner_.apps.push_back(std::move(app));
return *this;
}
TagArgsBuilder& WithRuntimeMode(RuntimeModeArgs runtime_mode) {
this->inner_.runtime_mode = runtime_mode;
return *this;
}
TagArgsBuilder& WithEnrollmentToken(const std::string& enrollment_token) {
this->inner_.enrollment_token = enrollment_token;
return *this;
}

private:
TagArgs inner_;
};

// Builder pattern helper to construct the AppArgs struct.


class AppArgsBuilder {
public:
explicit AppArgsBuilder(const std::string& app_id) : inner_(app_id) {}

AppArgs Build() { return std::move(inner_); }

AppArgsBuilder& WithAppName(const std::string& app_name) {


this->inner_.app_name = app_name;
return *this;
}
AppArgsBuilder& WithNeedsAdmin(NeedsAdmin needs_admin) {
this->inner_.needs_admin = needs_admin;
return *this;
}
AppArgsBuilder& WithAp(const std::string& ap) {
this->inner_.ap = ap;
return *this;
}
AppArgsBuilder& WithEncodedInstallerData(
const std::string& encoded_installer_data) {
this->inner_.encoded_installer_data = encoded_installer_data;
return *this;
}
AppArgsBuilder& WithInstallDataIndex(const std::string& install_data_index) {
this->inner_.install_data_index = install_data_index;
return *this;
}
AppArgsBuilder& WithExperimentLabels(const std::string& experiment_labels) {
this->inner_.experiment_labels = experiment_labels;
return *this;
}
AppArgsBuilder& WithUntrustedData(const std::string& untrusted_data) {
this->inner_.untrusted_data = untrusted_data;
return *this;
}

private:
AppArgs inner_;
};

// Builder pattern helper to construct the RuntimeModeArgs struct.


class RuntimeModeArgsBuilder {
public:
RuntimeModeArgsBuilder() = default;

RuntimeModeArgs Build() { return std::move(inner_); }

RuntimeModeArgsBuilder& WithNeedsAdmin(NeedsAdmin needs_admin) {


this->inner_.needs_admin = needs_admin;
return *this;
}

private:
RuntimeModeArgs inner_;
};

void VerifyTagParseSuccess(
std::string_view tag,
std::optional<std::string_view> app_installer_data_args,
const TagArgs& expected) {
TagArgs actual;
ASSERT_EQ(ErrorCode::kSuccess, Parse(tag, app_installer_data_args, actual));

updater::test::ExpectTagArgsEqual(actual, expected);
}

void VerifyTagParseFail(std::string_view tag,


std::optional<std::string_view> app_installer_data_args,
ErrorCode expected) {
TagArgs args;
ASSERT_EQ(expected, Parse(tag, app_installer_data_args, args));
}

} // namespace

namespace updater {

using tagging::AppArgs;
using tagging::ErrorCode;
using tagging::Parse;
using tagging::TagArgs;

TEST(TagParserTest, InvalidValueNameIsSupersetOfValidName) {
VerifyTagParseFail(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname1=Hello",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, AppNameSpaceForValue) {
VerifyTagParseFail(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname= ",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, AppNameEncodedSpaceForValue) {
VerifyTagParseFail(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=%20",
std::nullopt, ErrorCode::kApp_AppNameCannotBeWhitespace);
}

TEST(TagParserTest, AppNameValid) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=Test",
std::nullopt,
TagArgsBuilder()
.WithBundleName("Test")
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName("Test")
.Build())
.Build());
}

// This must work because the enterprise MSI code assumes spaces are allowed.
TEST(TagParserTest, AppNameWithSpace) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=Test App",
std::nullopt,
TagArgsBuilder()
.WithBundleName("Test App")
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName("Test App")
.Build())
.Build());
}

TEST(TagParserTest, AppNameWithSpaceAtEnd) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname= T Ap p ",
std::nullopt,
TagArgsBuilder()
.WithBundleName("T Ap p")
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName("T Ap p")
.Build())
.Build());
}

TEST(TagParserTest, AppNameWithEncodedSpacesAtEnd) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=%20T%20Ap%20p%20",
std::nullopt,
TagArgsBuilder()
.WithBundleName("T Ap p")
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName("T Ap p")
.Build())
.Build());
}

TEST(TagParserTest, AppNameWithMultipleSpaces) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname= T Ap p",
std::nullopt,
TagArgsBuilder()
.WithBundleName("T Ap p")
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName("T Ap p")
.Build())
.Build());
}

TEST(TagParserTest, AppNameUnicode) {
std::string non_ascii_name = "रहा";
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=%E0%A4%B0%E0%A4%B9%E0%A4%BE",
std::nullopt,
TagArgsBuilder()
.WithBundleName(non_ascii_name)
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName(non_ascii_name)
.Build())
.Build());
}

TEST(TagParserTest, AppNameUnicode2) {
std::string non_ascii_name = "स्थापित कर रहा है।";
std::string escaped(
"%E0%A4%B8%E0%A5%8D%E0%A4%A5%E0%A4%BE%E0%A4%AA%E0%A4%BF"
"%E0%A4%A4%20%E0%A4%95%E0%A4%B0%20%E0%A4%B0%E0%A4%B9%E0"
"%A4%BE%20%E0%A4%B9%E0%A5%88%E0%A5%A4");

std::stringstream tag;
tag << "appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&";
tag << "appname=" << escaped;
VerifyTagParseSuccess(
[Link](), std::nullopt,
TagArgsBuilder()
.WithBundleName(non_ascii_name)
.WithApp(AppArgsBuilder("d0324988-da8a-49e5-bce5-925fcd04eab7")
.WithAppName(non_ascii_name)
.Build())
.Build());
}

TEST(TagParserTest, AppIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B", std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.Build());
}

TEST(TagParserTest, AppIdNotASCII) {
VerifyTagParseFail("appguid=रहा", std::nullopt,
ErrorCode::kApp_AppIdIsNotValid);
}

// Most tests here do not reflect this, but appids can be non-GUID ASCII
// strings.
TEST(TagParserTest, AppIdNotAGuid) {
VerifyTagParseSuccess(
"appguid=non-guid-id", std::nullopt,
TagArgsBuilder().WithApp(AppArgsBuilder("non-guid-id").Build()).Build());
}

TEST(TagParserTest, AppIdCaseInsensitive) {
VerifyTagParseSuccess(
"appguid=ShouldBeCaseInsensitive", std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("shouldbecaseinsensitive").Build())
.Build());
}

TEST(TagParserTest, NoRuntimeModeOrAppOnlyNeedsAdminValue) {
VerifyTagParseFail("needsadmin=true", std::nullopt,
ErrorCode::kApp_AppIdNotSpecified);
}

TEST(TagParserTest, NeedsAdminInvalid) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=Hello",
std::nullopt, ErrorCode::kApp_NeedsAdminValueIsInvalid);
}

TEST(TagParserTest, NeedsAdminSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin= ",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, NeedsAdminTrueUpperCaseT) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=True",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithNeedsAdmin(NeedsAdmin::kYes)
.Build())
.Build());
}

TEST(TagParserTest, NeedsAdminTrueLowerCaseT) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=true",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithNeedsAdmin(NeedsAdmin::kYes)
.Build())
.Build());
}

TEST(TagParserTest, NeedsFalseUpperCaseF) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=False",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithNeedsAdmin(NeedsAdmin::kNo)
.Build())
.Build());
}

TEST(TagParserTest, NeedsAdminFalseLowerCaseF) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=false",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithNeedsAdmin(NeedsAdmin::kNo)
.Build())
.Build());
}

//
// Test the handling of the contents of the extra arguments.
//

TEST(TagParserTest, AssignmentOnly) {
VerifyTagParseFail("=", std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, ExtraAssignment1) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1=",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, ExtraAssignment2) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"=usagestats=1",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, ExtraAssignment3) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1&=",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, ExtraAssignment4) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"=&usagestats=1",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, ValueWithoutName) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"=hello",
std::nullopt, ErrorCode::kUnrecognizedName);
}

// Also tests ending extra arguments with '='.


TEST(TagParserTest, NameWithoutValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, NameWithoutValueBeforeNextArgument) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=&client=hello",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, NameWithoutArgumentSeparatorAfterIntValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1client=hello",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, NameWithoutArgumentSeparatorAfterStringValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=yesclient=hello",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, TagHasDoubleAmpersand) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1&&client=hello",
{"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"installerdata=foobar"},
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithEncodedInstallerData("foobar")
.Build())
.WithUsageStatsEnable(true)
.WithClientId("hello")
.Build());
}

TEST(TagParserTest, TagAmpersandOnly) {
VerifyTagParseSuccess("&", std::nullopt, TagArgsBuilder().Build());
}

TEST(TagParserTest, TagBeginsInAmpersand) {
VerifyTagParseSuccess(
"&appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, TagEndsInAmpersand) {
VerifyTagParseSuccess(
"&appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1&",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, WhitespaceOnly) {
for (const auto& whitespace : {"", " ", "\t", "\r", "\n", "\r\n"}) {
VerifyTagParseSuccess(whitespace, std::nullopt, TagArgsBuilder().Build());
}
}

//
// Test the parsing of the extra command and its arguments into a string.
//

TEST(TagParserTest, OneValidAttribute) {
VerifyTagParseSuccess(
"&appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, TwoValidAttributes) {
VerifyTagParseSuccess(
"&appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1&client=hello",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(true)
.WithClientId("hello")
.Build());
}

TEST(TagParserTest, TagHasSwitchInTheMiddle) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1/other_value=9",
std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, TagHasDoubleQuoteInTheMiddle) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1\"/other_value=9",
std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, TagHasDoubleQuoteInTheMiddleAndNoForwardSlash) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1\"other_value=9",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, TagHasSpaceAndForwardSlashBeforeQuote) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1 /other_value=9",
std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, TagHasForwardSlashBeforeQuote) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1/other_value=9",
std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, AttributeSpecifiedTwice) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1\" \"client=10",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, WhiteSpaceBeforeArgs1) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
" usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, WhiteSpaceBeforeArgs2) {
VerifyTagParseSuccess(
"\tappguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, WhiteSpaceBeforeArgs3) {
VerifyTagParseSuccess(
"\rappguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, WhiteSpaceBeforeArgs4) {
VerifyTagParseSuccess(
"\nappguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B"
"&usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, WhiteSpaceBeforeArgs5) {
VerifyTagParseSuccess("\r\nusagestats=1", std::nullopt,
TagArgsBuilder().WithUsageStatsEnable(true).Build());
}

TEST(TagParserTest, ForwardSlash1) {
VerifyTagParseFail("/", std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, ForwardSlash2) {
VerifyTagParseFail("/ appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B",
std::nullopt, ErrorCode::kTagIsInvalid);
}

TEST(TagParserTest, BackwardSlash1) {
VerifyTagParseFail("\\", std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, BackwardSlash2) {
VerifyTagParseFail("\\appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, BackwardSlash3) {
VerifyTagParseFail("\\ appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, AppArgsMustHaveValue) {
for (const auto& tag :
{"appguid", "appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&ap",
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&experiments",
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&appname",
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&needsadmin",
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&installdataindex",
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&untrusteddata"}) {
VerifyTagParseFail(tag, std::nullopt, ErrorCode::kAttributeMustHaveValue);
}
}

//
// Test specific extra commands.
//

TEST(TagParserTest, UsageStatsOutsideExtraCommand) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"/usagestats",
std::nullopt, ErrorCode::kTagIsInvalid);
}
TEST(TagParserTest, UsageStatsOn) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=1",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(true)
.Build());
}

TEST(TagParserTest, UsageStatsOff) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=0",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithUsageStatsEnable(false)
.Build());
}

TEST(TagParserTest, UsageStatsNone) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=2",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.Build());
}

TEST(TagParserTest, UsageStatsInvalidPositiveValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=3",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, UsageStatsInvalidNegativeValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=-1",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, UsageStatsValueIsString) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"usagestats=true",
std::nullopt, ErrorCode::kGlobal_UsageStatsValueIsInvalid);
}

TEST(TagParserTest, BundleNameValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"bundlename=Google%20Bundle",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBundleName("Google Bundle")

.Build());
}

TEST(TagParserTest, BundleNameSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"bundlename= ",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, BundleNameEncodedSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"bundlename=%20",
std::nullopt, ErrorCode::kGlobal_BundleNameCannotBeWhitespace);
}

TEST(TagParserTest, BundleNameNotPresentButAppNameIs) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"appname=Google%20Chrome",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithAppName("Google Chrome")
.Build())
.WithBundleName("Google Chrome")
.Build());
}
TEST(TagParserTest, BundleNameNorAppNamePresent) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&", std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.Build());
}

TEST(TagParserTest, BundleNameNotPresentAndNoApp) {
VerifyTagParseSuccess(
"browser=0", std::nullopt,
TagArgsBuilder().WithBrowserType(TagArgs::BrowserType::kUnknown).Build());
}

TEST(TagParserTest, InstallationIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"iid=98CEC468-9429-4984-AEDE-4F53C6A14869",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithInstallationId("98CEC468-9429-4984-AEDE-4F53C6A14869")
.Build());
}

TEST(TagParserTest, InstallationIdContainsNonASCII) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"iid=रहा",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithInstallationId("रहा")
.Build());
}

TEST(TagParserTest, BrandCodeValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"brand=GOOG",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBrandCode("GOOG")
.Build());
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"brand=GOOGLE",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBrandCode("GOOGLE")
.Build());
}

TEST(TagParserTest, ClientIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"client=some_partner",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithClientId("some_partner")
.Build());
}

TEST(TagParserTest, UpdaterExperimentIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"omahaexperiments=experiment%3DgroupA%7Cexpir",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithExperimentLabels("experiment=groupA|expir")
.Build());
}

TEST(TagParserTest, UpdaterExperimentIdSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"omahaexperiments= ",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, UpdaterExperimentIdEncodedSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"omahaexperiments=%20",
std::nullopt, ErrorCode::kGlobal_ExperimentLabelsCannotBeWhitespace);
}

TEST(TagParserTest, AppExperimentIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"experiments=experiment%3DgroupA%7Cexpir",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithExperimentLabels("experiment=groupA|expir")
.Build())
.Build());
}

TEST(TagParserTest, AppExperimentIdSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"experiments= ",
std::nullopt, ErrorCode::kAttributeMustHaveValue);
}

TEST(TagParserTest, AppExperimentIdEncodedSpaceForValue) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"experiments=%20",
std::nullopt, ErrorCode::kApp_ExperimentLabelsCannotBeWhitespace);
}

TEST(TagParserTest, ReferralIdValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"referral=ABCD123",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithReferralId("ABCD123")
.Build());
}

TEST(TagParserTest, ApValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"ap=developer",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithAp("developer")
.Build())
.Build());
}

TEST(TagParserTest, AppInstallerDataArgsValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&",
{"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"installerdata=%E0%A4foobar"},
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithEncodedInstallerData("%E0%A4foobar")
.Build())
.Build());
}

TEST(TagParserTest, AppInstallerDataArgsInvalidAppId) {
VerifyTagParseFail("appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&",
{"appguid=E135384F-85A2-4328-B07D-2CF70313D505&"
"installerdata=%E0%A4foobar"},
ErrorCode::kAppInstallerData_AppIdNotFound);
}

TEST(TagParserTest, AppInstallerDataArgsInvalidAttribute) {
VerifyTagParseFail("appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&",
{"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"needsadmin=true&"},
ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, InstallerDataNotAllowedInTag) {
VerifyTagParseFail(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"appname=TestApp2&"
"needsadmin=true&"
"installerdata=Hello%20World",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, InstallDataIndexValid) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"installdataindex=foobar",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithInstallDataIndex("foobar")
.Build())
.Build());
}

TEST(TagParserTest, BrowserTypeValid) {
std::tuple<std::string_view, TagArgs::BrowserType>
pairs[base::to_underlying(TagArgs::BrowserType::kMax)] = {
{"0", TagArgs::BrowserType::kUnknown},
{"1", TagArgs::BrowserType::kDefault},
{"2", TagArgs::BrowserType::kInternetExplorer},
{"3", TagArgs::BrowserType::kFirefox},
{"4", TagArgs::BrowserType::kChrome},
};

for (const auto& [browser, browser_type] : pairs) {


std::stringstream tag;
tag << "appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&";
tag << "browser=" << browser;
VerifyTagParseSuccess(
[Link](), std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBrowserType(browser_type)
.Build());
}
}

TEST(TagParserTest, BrowserTypeInvalid) {
EXPECT_EQ(5, int(TagArgs::BrowserType::kMax))
<< "Browser type may have been added. Update the BrowserTypeValid test "
"and change browser values in tag strings below.";

VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"browser=5",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBrowserType(TagArgs::BrowserType::kUnknown)
.Build());

VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"browser=9",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithBrowserType(TagArgs::BrowserType::kUnknown)
.Build());
}

TEST(TagParserTest, ValidLang) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"lang=en",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithLanguage("en")
.Build());
}

// Language must be passed even if not supported. See [Link]


TEST(TagParserTest, UnsupportedLang) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"lang=foobar",
std::nullopt,
TagArgsBuilder()
.WithApp(
AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b").Build())
.WithLanguage("foobar")
.Build());
}

TEST(TagParserTest, AppNameSpecifiedTwice) {
VerifyTagParseSuccess(
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"appname=TestApp&"
"appname=TestApp2&",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B")
.WithAppName("TestApp2")
.Build())
.WithBundleName("TestApp2")
.Build());
}

TEST(TagParserTest, CaseInsensitiveAttributeNames) {
VerifyTagParseSuccess(
"APPguID=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"APPNAME=TestApp&",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("8617EE50-F91C-4DC1-B937-0969EEF59B0B")
.WithAppName("TestApp")
.Build())
.WithBundleName("TestApp")
.Build());
}

TEST(TagParserTest, BracesEncoding) {
VerifyTagParseSuccess(
"appguid=%7B8617EE50-F91C-4DC1-B937-0969EEF59B0B%7D&"
"appname=TestApp&",
std::nullopt,
TagArgsBuilder()
.WithApp(AppArgsBuilder("{8617EE50-F91C-4DC1-B937-0969EEF59B0B}")
.WithAppName("TestApp")
.Build())
.WithBundleName("TestApp")
.Build());
}

//
// Test multiple applications in the extra arguments
//
TEST(TagParserTestMultipleEntries, TestNotStartingWithAppId) {
VerifyTagParseFail(
"appname=TestApp&"
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"appname=TestApp&"
"appname=false&"
"iid=98CEC468-9429-4984-AEDE-4F53C6A14869&"
"ap=test_ap&"
"usagestats=1&"
"browser=2&",
std::nullopt, ErrorCode::kApp_AppIdNotSpecified);
}

// This also tests that the last occurrence of a global extra arg is the one
// that is saved.
TEST(TagParserTestMultipleEntries, ThreeApplications) {
VerifyTagParseSuccess(
"etoken=5d086552-4514-4dfb-8a3e-337024ec35ac&"
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"appname=TestApp&"
"needsadmin=false&"
"iid=98CEC468-9429-4984-AEDE-4F53C6A14869&"
"ap=test_ap&"
"usagestats=1&"
"browser=2&"
"brand=GOOG&"
"client=_some_client&"
"experiments=_experiment_a&"
"untrusteddata=ABCDEFG&"
"referral=A123456789&"
"appguid=5E46DE36-737D-4271-91C1-C062F9FE21D9&"
"appname=TestApp2&"
"needsadmin=true&"
"experiments=_experiment_b&"
"untrusteddata=1234567&"
"iid=98CEC468-9429-4984-AEDE-4F53C6A14869&"
"ap=test_ap2&"
"usagestats=0&"
"browser=3&"
"brand=g00g&"
"client=_different_client&"
"appguid=5F46DE36-737D-4271-91C1-C062F9FE21D9&"
"appname=TestApp3&"
"needsadmin=prefers&",
{"appguid=5F46DE36-737D-4271-91C1-C062F9FE21D9&"
"installerdata=installerdata_app3&"
"appguid=8617EE50-F91C-4DC1-B937-0969EEF59B0B&"
"installerdata=installerdata_app1"},
TagArgsBuilder()
.WithEnrollmentToken("5d086552-4514-4dfb-8a3e-337024ec35ac")
.WithBundleName("TestApp")
.WithInstallationId("98CEC468-9429-4984-AEDE-4F53C6A14869")
.WithBrandCode("g00g")
.WithClientId("_different_client")
.WithReferralId("A123456789")
.WithBrowserType(TagArgs::BrowserType::kFirefox)
.WithUsageStatsEnable(false)
.WithApp(AppArgsBuilder("8617ee50-f91c-4dc1-b937-0969eef59b0b")
.WithAppName("TestApp")
.WithNeedsAdmin(NeedsAdmin::kNo)
.WithAp("test_ap")
.WithEncodedInstallerData("installerdata_app1")
.WithExperimentLabels("_experiment_a")
.WithUntrustedData("A=3&Message=Hello,%20World!")
.Build())
.WithApp(AppArgsBuilder("5e46de36-737d-4271-91c1-c062f9fe21d9")
.WithAppName("TestApp2")
.WithNeedsAdmin(NeedsAdmin::kYes)
.WithAp("test_ap2")
.WithExperimentLabels("_experiment_b")
.WithUntrustedData("X=5")
.Build())
.WithApp(AppArgsBuilder("5f46de36-737d-4271-91c1-c062f9fe21d9")
.WithAppName("TestApp3")
.WithEncodedInstallerData("installerdata_app3")
.WithNeedsAdmin(NeedsAdmin::kPrefers)
.Build())
.Build());
}

TEST(TagParserTest, RuntimeModeBeforeApp) {
VerifyTagParseFail(
"runtime=true&appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname1=Hello",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, RuntimeModeAfterApp) {
VerifyTagParseFail(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&appname1=Hello&runtime="
"true",
std::nullopt, ErrorCode::kUnrecognizedName);
}

TEST(TagParserTest, RuntimeModeIncorrectValue) {
VerifyTagParseFail("runtime=foo", std::nullopt,
ErrorCode::kGlobal_RuntimeModeValueIsInvalid);
}

TEST(TagParserTest, RuntimeModeIncorrectNeedsAdminValue) {
VerifyTagParseFail("runtime=true&needsadmin=foo", std::nullopt,
ErrorCode::kRuntimeMode_NeedsAdminValueIsInvalid);
}

TEST(TagParserTest, RuntimeModeValid) {
VerifyTagParseSuccess("runtime=true", std::nullopt,
TagArgsBuilder()
.WithRuntimeMode(RuntimeModeArgsBuilder().Build())
.Build());
}

TEST(TagParserTest, RuntimeModeValidSystem) {
VerifyTagParseSuccess(
"runtime=true&needsadmin=true", std::nullopt,
TagArgsBuilder()
.WithRuntimeMode(
RuntimeModeArgsBuilder().WithNeedsAdmin(NeedsAdmin::kYes).Build())
.Build());
}

TEST(TagParserTest, RuntimeModeValidUser) {
VerifyTagParseSuccess(
"runtime=true&needsadmin=false", std::nullopt,
TagArgsBuilder()
.WithRuntimeMode(
RuntimeModeArgsBuilder().WithNeedsAdmin(NeedsAdmin::kNo).Build())
.Build());
}

TEST(TagParserTest, EnrollmentTokenBeforeApp) {
VerifyTagParseSuccess(
"etoken=5d086552-4514-4dfb-8a3e-337024ec35ac&"
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=Hello",
std::nullopt,
TagArgsBuilder()
.WithEnrollmentToken("5d086552-4514-4dfb-8a3e-337024ec35ac")
.WithBundleName("Hello")
.WithApp(AppArgsBuilder("D0324988-DA8A-49e5-BCE5-925FCD04EAB7")
.WithAppName("Hello")
.Build())
.Build());
}

TEST(TagParserTest, EnrollmentTokenAfterApp) {
VerifyTagParseSuccess(
"appguid=D0324988-DA8A-49e5-BCE5-925FCD04EAB7&"
"appname=Hello&"
"etoken=5d086552-4514-4dfb-8a3e-337024ec35ac",
std::nullopt,
TagArgsBuilder()
.WithEnrollmentToken("5d086552-4514-4dfb-8a3e-337024ec35ac")
.WithBundleName("Hello")
.WithApp(AppArgsBuilder("D0324988-DA8A-49e5-BCE5-925FCD04EAB7")
.WithAppName("Hello")
.Build())
.Build());
}

TEST(TagParserTest, EnrollmentTokenInvalidValue) {
VerifyTagParseFail("etoken=5d086552-4514-____-8a3e-337024ec35ac",
std::nullopt,
ErrorCode::kGlobal_EnrollmentTokenValueIsInvalid);
}

TEST(TagParserTest, EnrollmentTokenValid) {
VerifyTagParseSuccess(
"etoken=5d086552-4514-4dfb-8a3e-337024ec35ac", std::nullopt,
TagArgsBuilder()
.WithEnrollmentToken("5d086552-4514-4dfb-8a3e-337024ec35ac")
.Build());
}

TEST(TagExtractorTest, AdvanceIt) {
const std::vector<uint8_t> empty_binary;
ASSERT_TRUE(tagging::internal::AdvanceIt(empty_binary.begin(), 0,
empty_binary.end()) ==
empty_binary.end());

const std::vector<uint8_t> binary(5);


std::vector<uint8_t>::const_iterator it = [Link]();
ASSERT_TRUE(tagging::internal::AdvanceIt(it, 0, [Link]()) == it);
ASSERT_TRUE(tagging::internal::AdvanceIt(it, 4, [Link]()) == (it + 4));
ASSERT_TRUE(tagging::internal::AdvanceIt(it, 5, [Link]()) ==
[Link]());
ASSERT_TRUE(tagging::internal::AdvanceIt(it, 6, [Link]()) ==
[Link]());
}

TEST(TagExtractorTest, CheckRange) {
const std::vector<uint8_t> empty_binary;
ASSERT_FALSE(
tagging::internal::CheckRange(empty_binary.end(), 1, empty_binary.end()));

const std::vector<uint8_t> binary(5);

std::vector<uint8_t>::const_iterator it = [Link]();
ASSERT_FALSE(tagging::internal::CheckRange(it, 0, [Link]()));
ASSERT_TRUE(tagging::internal::CheckRange(it, 1, [Link]()));
ASSERT_TRUE(tagging::internal::CheckRange(it, 5, [Link]()));
ASSERT_FALSE(tagging::internal::CheckRange(it, 6, [Link]()));

it = [Link]() + 2;
ASSERT_TRUE(tagging::internal::CheckRange(it, 3, [Link]()));
ASSERT_FALSE(tagging::internal::CheckRange(it, 4, [Link]()));

it = [Link]() + 5;
ASSERT_FALSE(tagging::internal::CheckRange(it, 0, [Link]()));
ASSERT_FALSE(tagging::internal::CheckRange(it, 1, [Link]()));
}

TEST(ExeTagTest, FileNotFound) {
ASSERT_TRUE(
tagging::BinaryReadTagString(test::GetTestFilePath("[Link]"))
.empty());
}

TEST(ExeTagTest, UntaggedExe) {
ASSERT_TRUE(tagging::BinaryReadTagString(test::GetTestFilePath("[Link]"))
.empty());
}

TEST(ExeTagTest, TaggedExeEncodeUtf8) {
ASSERT_EQ(tagging::BinaryReadTagString(
test::GetTestFilePath("tagged_encode_utf8.exe")),
"TestTag123");
}

struct ExeTagTestExeWriteTagTestCase {
const std::string exe_file_name;
const std::string tag_string;
const bool expected_success;
};

class ExeTagTestExeWriteTagTest
: public ::testing::TestWithParam<ExeTagTestExeWriteTagTestCase> {};

INSTANTIATE_TEST_SUITE_P(
ExeTagTestExeWriteTagTestCases,
ExeTagTestExeWriteTagTest,
::testing::ValuesIn(std::vector<ExeTagTestExeWriteTagTestCase>{
// single tag parameter.
{"[Link]", "brand=QAQA", true},

// single tag parameter ending in an ampersand.


{"[Link]", "brand=QAQA&", true},

// multiple tag parameters.


{"[Link]",
"appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-8D3A-"
"4EFC-"
"6D61-AE23E3530EA2}&lang=en&browser=4&usagestats=0&appname=Google%"
"20Chrome&needsadmin=prefers&brand=CHMB&installdataindex="
"defaultbrowser",
true},

// already tagged.
{"tagged_encode_utf8.exe", "brand=QAQA", true},

// empty tag string.


{"[Link]", "", true},

// unknown tag argument `unknowntagarg`.


{"[Link]",
"appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-8D3A-"
"4EFC-"
"6D61-AE23E3530EA2}&unknowntagarg=foo",
false},
}));

TEST_P(ExeTagTestExeWriteTagTest, TestCases) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath out_file;
ASSERT_TRUE(CreateTemporaryFileInDir(temp_dir.GetPath(), &out_file));

ASSERT_EQ(tagging::BinaryWriteTag(
test::GetTestFilePath(GetParam().exe_file_name.c_str()),
GetParam().tag_string, 8206, out_file),
GetParam().expected_success)
<< GetParam().exe_file_name << ": " << GetParam().tag_string;
if (GetParam().expected_success) {
EXPECT_EQ(tagging::BinaryReadTagString(out_file), GetParam().tag_string);
}
}

struct MsiTagTestMsiReadTagTestCase {
const std::string msi_file_name;
const std::optional<tagging::TagArgs> expected_tag_args;
};

class MsiTagTestMsiReadTagTest
: public ::testing::TestWithParam<MsiTagTestMsiReadTagTestCase> {};

INSTANTIATE_TEST_SUITE_P(
MsiTagTestMsiReadTagTestCases,
MsiTagTestMsiReadTagTest,
::testing::ValuesIn(std::vector<MsiTagTestMsiReadTagTestCase>{
// tag:BRAND=QAQA.
{"[Link]",
[] {
tagging::TagArgs tag_args;
tag_args.brand_code = "QAQA";
return tag_args;
}()},

// tag:BRAND=QAQA&.
{"[Link]",
[] {
tagging::TagArgs tag_args;
tag_args.brand_code = "QAQA";
return tag_args;
}()},

// tag:
// appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&
// iid={2D8C18E9-8D3A-4EFC-6D61-AE23E3530EA2}&
// lang=en&browser=4&usagestats=0&appname=Google%20Chrome&
// needsadmin=prefers&brand=CHMB&
// installdataindex=defaultbrowser.
{"[Link]",
[] {
tagging::TagArgs tag_args;
tag_args.bundle_name = "Google Chrome";
tag_args.installation_id = "{2D8C18E9-8D3A-4EFC-6D61-AE23E3530EA2}";
tag_args.brand_code = "CHMB";
tag_args.language = "en";
tag_args.browser_type = tagging::TagArgs::BrowserType::kChrome;
tag_args.usage_stats_enable = false;

tagging::AppArgs app_args("{8A69D345-D564-463C-AFF1-A69D9E530F96}");
app_args.app_name = "Google Chrome";
app_args.install_data_index = "defaultbrowser";
app_args.needs_admin = tagging::NeedsAdmin::kPrefers;
tag_args.apps = {app_args};

return tag_args;
}()},

// MSI file size greater than `kMaxBufferLength` of 80KB.


{"[Link]",
[] {
tagging::TagArgs tag_args;
tag_args.bundle_name = "Google Chrome Beta";
tag_args.brand_code = "GGLL";

tagging::AppArgs app_args("{8237E44A-0054-442C-B6B6-EA0509993955}");
app_args.app_name = "Google Chrome Beta";
app_args.needs_admin = tagging::NeedsAdmin::kYes;
tag_args.apps = {app_args};

return tag_args;
}()},

// special character in the tag value.


{"[Link]",
[] {
tagging::TagArgs tag_args;
tag_args.brand_code = "QA*A";
return tag_args;
}()},

// tag: =value&BRAND=QAQA.
{"[Link]", {}},

// tag: BRAND=.
{"[Link]", {}},

// tag:(empty string).
{"[Link]", {}},

// invalid magic signature "Gact2.0Foo".


{"[Link]", {}},

// invalid characters in the tag key.


{"[Link]", {}},

// invalid tag format.


{"[Link]", {}},

// invalid tag format.


{"[Link]", {}},

// untagged.
{"[Link]", {}},
}));

TEST_P(MsiTagTestMsiReadTagTest, TestCases) {
const auto tag_args = tagging::BinaryReadTag(
test::GetTestFilePath("tagged_msi").AppendUTF8(GetParam().msi_file_name));
EXPECT_EQ(tag_args.has_value(), GetParam().expected_tag_args.has_value());
if (GetParam().expected_tag_args) {
test::ExpectTagArgsEqual(*tag_args, *GetParam().expected_tag_args);
}
}

struct MsiTagTestMsiWriteTagTestCase {
const std::string msi_file_name;
const std::string tag_string;
const bool expected_success;
};

class MsiTagTestMsiWriteTagTest
: public ::testing::TestWithParam<MsiTagTestMsiWriteTagTestCase> {};

INSTANTIATE_TEST_SUITE_P(
MsiTagTestMsiWriteTagTestCases,
MsiTagTestMsiWriteTagTest,
::testing::ValuesIn(std::vector<MsiTagTestMsiWriteTagTestCase>{
// single tag parameter.
{"[Link]", "brand=QAQA", true},

// single tag parameter ending in an ampersand.


{"[Link]", "brand=QAQA&", true},

// multiple tag parameters.


{"[Link]",
"appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-8D3A-"
"4EFC-"
"6D61-AE23E3530EA2}&lang=en&browser=4&usagestats=0&appname=Google%"
"20Chrome&needsadmin=prefers&brand=CHMB&installdataindex="
"defaultbrowser",
true},

// empty tag string.


{"[Link]", "", true},

// already tagged.
{"[Link]", "brand=QAQA", true},

// unknown tag argument `unknowntagarg`.


{"[Link]",
"appguid={8A69D345-D564-463C-AFF1-A69D9E530F96}&iid={2D8C18E9-8D3A-"
"4EFC-"
"6D61-AE23E3530EA2}&unknowntagarg=foo",
false},
}));

TEST_P(MsiTagTestMsiWriteTagTest, TestCases) {
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
base::FilePath out_file;
ASSERT_TRUE(CreateTemporaryFileInDir(temp_dir.GetPath(), &out_file));
const base::FilePath in_out_file = out_file.AddExtensionUTF8(".msi");
const base::FilePath msi_file_path =
test::GetTestFilePath("tagged_msi").AppendUTF8(GetParam().msi_file_name);
ASSERT_TRUE(base::CopyFile(msi_file_path, in_out_file));

for (const auto& [msi_file, out_msi_file] :


{std::make_pair(msi_file_path, out_file),
std::make_pair(in_out_file, base::FilePath())}) {
ASSERT_EQ(tagging::BinaryWriteTag(msi_file, GetParam().tag_string, 8206,
out_msi_file),
GetParam().expected_success);
if (GetParam().expected_success && !GetParam().tag_string.empty()) {
tagging::TagArgs tag_args;
ASSERT_EQ(tagging::Parse(GetParam().tag_string, {}, tag_args),
tagging::ErrorCode::kSuccess);
test::ExpectTagArgsEqual(
tagging::BinaryReadTag(!out_msi_file.empty() ? out_msi_file
: msi_file)
.value(),
tag_args);
}
}
}

} // namespace updater
#include "chrome/updater/update_service_impl_impl.h"

#include <algorithm>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/global_constants.h"
#include "chrome/updater/app/app_utils.h"
#include "chrome/updater/auto_run_on_os_upgrade_task.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/change_owners_task.h"
#include "chrome/updater/check_for_updates_task.h"
#include "chrome/updater/cleanup_task.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/handle_inconsistent_apps_task.h"
#include "chrome/updater/installer.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/remove_uninstalled_apps_task.h"
#include "chrome/updater/update_block_check.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_usage_stats_task.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"

#if BUILDFLAG(IS_MAC)
#include <sys/mount.h>
#endif // BUILDFLAG(IS_MAC)
#if BUILDFLAG(IS_WIN)
#include <winhttp.h>

#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/[Link]"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#endif // BUILDFLAG(IS_WIN)

namespace updater {

// The functions below are various adaptors between |update_client| and


// |UpdateService| types.

namespace internal {
UpdateService::Result ToResult(update_client::Error error) {
switch (error) {
case update_client::Error::NONE:
return UpdateService::Result::kSuccess;
case update_client::Error::UPDATE_IN_PROGRESS:
return UpdateService::Result::kUpdateInProgress;
case update_client::Error::UPDATE_CANCELED:
return UpdateService::Result::kUpdateCanceled;
case update_client::Error::RETRY_LATER:
return UpdateService::Result::kRetryLater;
case update_client::Error::SERVICE_ERROR:
return UpdateService::Result::kServiceFailed;
case update_client::Error::UPDATE_CHECK_ERROR:
return UpdateService::Result::kUpdateCheckFailed;
case update_client::Error::CRX_NOT_FOUND:
return UpdateService::Result::kAppNotFound;
case update_client::Error::INVALID_ARGUMENT:
case update_client::Error::BAD_CRX_DATA_CALLBACK:
return UpdateService::Result::kInvalidArgument;
case update_client::Error::MAX_VALUE:
NOTREACHED();
}
}

void GetComponents(
scoped_refptr<PolicyService> policy_service,
crx_file::VerifierFormat verifier_format,
scoped_refptr<PersistedData> persisted_data,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
const std::string& install_source,
UpdateService::Priority priority,
bool update_blocked,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::vector<std::string>& ids,
base::OnceCallback<
void(const std::vector<std::optional<update_client::CrxComponent>>&)>
callback) {
VLOG(1) << __func__
<< ". Same version update: " << policy_same_version_update;
const bool is_foreground = priority == UpdateService::Priority::kForeground;
auto barrier_callback =
base::BarrierCallback<std::optional<update_client::CrxComponent>>(
[Link](),
base::BindOnce(
[](const std::vector<std::string>& ids,
const std::vector<std::optional<update_client::CrxComponent>>&
unordered) {
// Re-order the vector to match the order of `ids`.
std::vector<std::optional<update_client::CrxComponent>> ordered;
for (const auto& id : ids) {
auto it = std::ranges::find_if(
unordered,
[&id](std::optional<update_client::CrxComponent> v) {
return v && v->app_id == id;
});
ordered.push_back(it != [Link]() ? *it : std::nullopt);
}
return ordered;
},
ids)
.Then(std::move(callback)));
for (const auto& id : ids) {
base::MakeRefCounted<Installer>(
id,
[&app_client_install_data, &id] {
auto it = app_client_install_data.find(id);
return it != app_client_install_data.end() ? it->second : "";
}(),
[&app_install_data_index, &id] {
auto it = app_install_data_index.find(id);
return it != app_install_data_index.end() ? it->second : "";
}(),
install_source,
policy_service->GetTargetChannel(id).policy_or(std::string()),
policy_service->GetTargetVersionPrefix(id).policy_or(std::string()),
policy_service->IsRollbackToTargetVersionAllowed(id).policy_or(false),
[&policy_service, &id, &is_foreground, update_blocked] {
if (update_blocked) {
return true;
}
PolicyStatus<int> app_updates =
policy_service->GetPolicyForAppUpdates(id);
return app_updates &&
(app_updates.policy() == kPolicyDisabled ||
(!is_foreground &&
app_updates.policy() == kPolicyManualUpdatesOnly) ||
(is_foreground &&
app_updates.policy() == kPolicyAutomaticUpdatesOnly));
}(),
policy_same_version_update, persisted_data, verifier_format)
->MakeCrxComponent(
base::BindOnce([](update_client::CrxComponent component) {
return component;
}).Then(barrier_callback));
}
}

#if BUILDFLAG(IS_WIN)
namespace {
std::wstring GetTextForUpdateClientInstallError(int error_code,
const std::wstring& language) {
#define INSTALL_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE, L#error_code, \
language)

switch (error_code) {
INSTALL_SWITCH_ENTRY(update_client::InstallError::NONE);
INSTALL_SWITCH_ENTRY(update_client::InstallError::FINGERPRINT_WRITE_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::BAD_MANIFEST);
INSTALL_SWITCH_ENTRY(update_client::InstallError::GENERIC_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MOVE_FILES_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::SET_PERMISSIONS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::INVALID_VERSION);
INSTALL_SWITCH_ENTRY(update_client::InstallError::VERSION_NOT_UPGRADED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::NO_DIR_COMPONENT_USER);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CLEAN_INSTALL_DIR_FAILED);
INSTALL_SWITCH_ENTRY(
update_client::InstallError::INSTALL_VERIFICATION_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MISSING_INSTALL_PARAMS);
INSTALL_SWITCH_ENTRY(update_client::InstallError::LAUNCH_PROCESS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CUSTOM_ERROR_BASE);
default:
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef INSTALL_SWITCH_ENTRY
}

std::wstring GetTextForDownloadError(int error, const std::wstring& language) {


#define DOWNLOAD_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE, L#error_code, \
language)

switch (error) {
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_URL);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_HASH);
DOWNLOAD_SWITCH_ENTRY(
update_client::CrxDownloaderError::BITS_TOO_MANY_JOBS);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::GENERIC_ERROR);

case static_cast<int>(update_client::CrxDownloaderError::BAD_HASH):
return GetLocalizedString(IDS_DOWNLOAD_HASH_MISMATCH_BASE);

default:
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef DOWNLOAD_SWITCH_ENTRY
}

std::wstring GetTextForUnpackError(int error, const std::wstring& language) {


#define UNPACK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE, L#error_code, \
language)
#define UNPACK_CACHING_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_UNPACK_CACHING_ERROR_BASE, L#error_code, \
language)

switch (error) {
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidParams);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidFile);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipPathError);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipFailed);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadManifest);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadExtension);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kIoError);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaVerificationFailure);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaBadCommands);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaUnsupportedCommand);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaOperationFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaPatchProcessFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaMissingExistingFile);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kPuffinMissingPreviousCrx);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kCrxCacheNotProvided);

UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToAddToCache);
UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToCreateCacheDir);

default:
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef UNPACK_SWITCH_ENTRY
#undef UNPACK_CACHING_SWITCH_ENTRY
}

std::wstring GetTextForServiceError(int error, const std::wstring& language) {


#define SERVICE_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE, L#error_code, \
language)

switch (error) {
SERVICE_SWITCH_ENTRY(update_client::ServiceError::SERVICE_WAIT_FAILED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::UPDATE_DISABLED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::CHECK_FOR_UPDATE_ONLY);

case static_cast<int>(update_client::ServiceError::CANCELLED):
return GetLocalizedString(IDS_SERVICE_ERROR_CANCELLED_BASE, language);

default:
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef SERVICE_SWITCH_ENTRY
}
std::wstring GetTextForUpdateCheckError(int error,
const std::wstring& language) {
#define UPDATE_CHECK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UPDATE_CHECK_ERROR_BASE, \
L#error_code, language)

switch (error) {
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::RESPONSE_NOT_TRUSTED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::MISSING_URLS);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::PARSE_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::UPDATE_RESPONSE_NOT_FOUND);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::URL_FETCHER_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::INVALID_APPID);

case static_cast<int>(update_client::ProtocolError::UNKNOWN_APPLICATION):
return GetLocalizedString(IDS_UNKNOWN_APPLICATION_BASE, language);

case static_cast<int>(update_client::ProtocolError::RESTRICTED_APPLICATION):
return GetLocalizedString(IDS_RESTRICTED_RESPONSE_FROM_SERVER_BASE,
language);

case static_cast<int>(update_client::ProtocolError::OS_NOT_SUPPORTED):
return GetLocalizedString(IDS_OS_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::HW_NOT_SUPPORTED):
return GetLocalizedString(IDS_HW_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::NO_HASH):
return GetLocalizedString(IDS_NO_HASH_BASE, language);

case static_cast<int>(update_client::ProtocolError::UNSUPPORTED_PROTOCOL):
return GetLocalizedString(IDS_UNSUPPORTED_PROTOCOL_BASE, language);

case static_cast<int>(update_client::ProtocolError::INTERNAL):
return GetLocalizedString(IDS_INTERNAL_BASE, language);

// Http Status Code `401` Unauthorized.


case 401:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_UNAUTHORIZED_BASE,
language);

// Http Status Code `403` Forbidden.


case 403:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_FORBIDDEN_BASE, language);

// Http Status Code `407` Proxy Authentication Required.


case 407:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_PROXY_AUTH_REQUIRED_BASE,
language);

case HRESULT_FROM_WIN32(ERROR_WINHTTP_NAME_NOT_RESOLVED):
return GetLocalizedStringF(IDS_NO_NETWORK_PRESENT_ERROR_BASE,
GetExecutableRelativePath().value(), language);
default:
return GetLocalizedStringF(
IDS_GENERIC_UPDATE_CHECK_ERROR_BASE,
error >= 400 && error < 600
? base::UTF8ToWide(base::StringPrintf("HTTP %d", error))
: GetTextForSystemError(error),
language);
}
#undef UPDATE_CHECK_SWITCH_ENTRY
}

std::wstring GetTextForInstallerError(int error_code,


const std::wstring& language) {
#define POLICY_ERROR_SWITCH_ENTRY(error_code) \
case error_code: \
return GetLocalizedStringF(IDS_APP_INSTALL_DISABLED_BY_GROUP_POLICY_BASE, \
L#error_code, language)

switch (error_code) {
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL);

case GOOPDATEINSTALL_E_FILENAME_INVALID:
return GetLocalizedString(IDS_INVALID_INSTALLER_FILENAME_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_FAILED_START:
return GetLocalizedString(IDS_INSTALLER_FAILED_TO_START_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT:
return GetLocalizedString(IDS_INSTALLER_TIMED_OUT_BASE, language);

case GOOPDATEINSTALL_E_INSTALL_ALREADY_RUNNING:
return GetLocalizedStringF(
IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(ERROR_INSTALL_ALREADY_RUNNING), language);

case ERROR_SUCCESS_REBOOT_INITIATED:
case ERROR_SUCCESS_REBOOT_REQUIRED:
case ERROR_SUCCESS_RESTART_REQUIRED:
return GetLocalizedStringF(IDS_INSTALL_REBOOT_BASE,
GetTextForSystemError(error_code), language);

default:
return GetLocalizedStringF(IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef POLICY_ERROR_SWITCH_ENTRY
}

} // namespace

std::string GetInstallerText(UpdateService::ErrorCategory error_category,


int error_code,
int extra_code,
const std::string& language) {
if (!error_code) {
return {};
}

const std::wstring language_w = base::UTF8ToWide(language);


return base::WideToUTF8(base::StrCat(
{[&] {
switch (error_category) {
case UpdateService::ErrorCategory::kInstall:
return GetTextForUpdateClientInstallError(error_code, language_w);
case UpdateService::ErrorCategory::kDownload:
return GetTextForDownloadError(error_code, language_w);
case UpdateService::ErrorCategory::kUnpack:
return GetTextForUnpackError(error_code, language_w);
case UpdateService::ErrorCategory::kService:
return GetTextForServiceError(error_code, language_w);
case UpdateService::ErrorCategory::kUpdateCheck:
return GetTextForUpdateCheckError(error_code, language_w);
case UpdateService::ErrorCategory::kInstaller:
return GetTextForInstallerError(error_code, language_w);
default:
LOG(ERROR) << "Unknown error category: " << error_category;
return std::wstring();
}
}(),
[&] {
if (!extra_code) {
return std::wstring();
}
return base::StrCat(
{L"\n", GetLocalizedStringF(IDS_EXTRA_CODE_BASE,
base::UTF8ToWide(base::StringPrintf(
"%#x", extra_code)),
language_w)});
}()}));
}
#endif // BUILDFLAG(IS_WIN)

base::Version GetRegisteredInstallerVersion(const std::string& app_id) {


#if BUILDFLAG(IS_WIN)
std::wstring pv;
return base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScope()),
GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ))
.ReadValue(kRegValuePV, &pv) == ERROR_SUCCESS
? base::Version(base::WideToUTF8(pv))
: base::Version();
#else // BUILDFLAG(IS_WIN)
return {};
#endif // BUILDFLAG(IS_WIN)
}

} // namespace internal

namespace {

constexpr base::flat_map<std::string, std::string> kEmptyFlatMap;

update_client::Callback MakeUpdateClientCallback(
base::OnceCallback<void(UpdateService::Result)> callback) {
return base::BindOnce(
[](base::OnceCallback<void(UpdateService::Result)> callback,
update_client::Error error) {
std::move(callback).Run(internal::ToResult(error));
},
std::move(callback));
}

UpdateService::UpdateState::State ToUpdateState(
update_client::ComponentState component_state) {
switch (component_state) {
case update_client::ComponentState::kNew:
return UpdateService::UpdateState::State::kNotStarted;

case update_client::ComponentState::kChecking:
return UpdateService::UpdateState::State::kCheckingForUpdates;

case update_client::ComponentState::kDownloading:
case update_client::ComponentState::kDownloadingDiff:
return UpdateService::UpdateState::State::kDownloading;

case update_client::ComponentState::kCanUpdate:
return UpdateService::UpdateState::State::kUpdateAvailable;

case update_client::ComponentState::kUpdating:
case update_client::ComponentState::kUpdatingDiff:
return UpdateService::UpdateState::State::kInstalling;

case update_client::ComponentState::kUpdated:
return UpdateService::UpdateState::State::kUpdated;

case update_client::ComponentState::kUpToDate:
return UpdateService::UpdateState::State::kNoUpdate;

case update_client::ComponentState::kUpdateError:
return UpdateService::UpdateState::State::kUpdateError;

case update_client::ComponentState::kRun:
case update_client::ComponentState::kLastStatus:
NOTREACHED();
}
}

UpdateService::ErrorCategory ToErrorCategory(
update_client::ErrorCategory error_category) {
switch (error_category) {
case update_client::ErrorCategory::kNone:
return UpdateService::ErrorCategory::kNone;
case update_client::ErrorCategory::kDownload:
return UpdateService::ErrorCategory::kDownload;
case update_client::ErrorCategory::kUnpack:
return UpdateService::ErrorCategory::kUnpack;
case update_client::ErrorCategory::kInstall:
return UpdateService::ErrorCategory::kInstall;
case update_client::ErrorCategory::kService:
return UpdateService::ErrorCategory::kService;
case update_client::ErrorCategory::kUpdateCheck:
return UpdateService::ErrorCategory::kUpdateCheck;
case update_client::ErrorCategory::kInstaller:
return UpdateService::ErrorCategory::kInstaller;
}
}

update_client::UpdateClient::CrxStateChangeCallback
MakeUpdateClientCrxStateChangeCallback(
scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data,
const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)> callback) {
return base::BindRepeating(
[](scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data, const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
callback,
const update_client::CrxUpdateItem& crx_update_item) {
UpdateService::UpdateState update_state;
update_state.app_id = crx_update_item.id;
update_state.state = ToUpdateState(crx_update_item.state);
update_state.next_version = crx_update_item.next_version;
update_state.downloaded_bytes = crx_update_item.downloaded_bytes;
update_state.total_bytes = crx_update_item.total_bytes;
update_state.install_progress = crx_update_item.install_progress;
update_state.error_category =
ToErrorCategory(crx_update_item.error_category);
update_state.error_code = crx_update_item.error_code;
update_state.extra_code1 = crx_update_item.extra_code1;
if (crx_update_item.installer_result) {
update_state.installer_cmd_line =
crx_update_item.installer_result->installer_cmd_line;
update_state.installer_text =
crx_update_item.installer_result->installer_text;
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
UpdateService::ErrorCategory::kInstaller,
update_state.error_code, update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)
}

if (update_state.state == UpdateService::UpdateState::State::kUpdated ||
update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate) {
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)

// If a new install encounters an error, the AppId registered in


// `UpdateServiceImplImpl::Install` needs to be removed here.
// Otherwise the updater may remain installed even if there are no
// other apps to manage, and try to update the app even though the app
// was not installed.
if (new_install &&
(update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate)) {
persisted_data->RemoveApp(update_state.app_id);
}

// Commit the prefs values written by |update_client| when the


// update has completed, such as `pv` and `fingerprint`.
config->GetPrefService()->CommitPendingWrite();
}

CHECK(!update_state.app_id.empty());
CHECK_NE(update_state.state,
UpdateService::UpdateState::State::kUnknown);
[Link](update_state);
},
config, persisted_data, new_install, language, callback);
}

bool IsPathOnReadOnlyMount(const base::FilePath& path) {


if ([Link]()) {
return false;
}
#if BUILDFLAG(IS_MAC)
struct statfs fsinfo = {};
if (statfs([Link]().c_str(), &fsinfo) == -1) {
LOG(ERROR) << "Failed to stat: " << path;
return false;
}
return fsinfo.f_flags & MNT_RDONLY;
#else
return false;
#endif // BUILDFLAG(IS_MAC)
}

void FetchPoliciesDone(
base::OnceCallback<void(base::OnceCallback<void(UpdateService::Result)>)>
fetch_policies_done,
base::OnceCallback<void(UpdateService::Result)> callback,
int result) {
if (result != kErrorOk) {
LOG(ERROR) << "FetchPolicies failed: " << result;
// Ignore policy fetch failures and fall through.
}

std::move(fetch_policies_done).Run(std::move(callback));
}

} // namespace

UpdateServiceImplImpl::UpdateServiceImplImpl(scoped_refptr<Configurator> config)
: config_(config),
main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
update_client_(update_client::UpdateClientFactory(config)) {}

void UpdateServiceImplImpl::GetVersion(
base::OnceCallback<void(const base::Version&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), base::Version(kUpdaterVersion)));
}

void UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA(
base::OnceClosure callback,
bool is_cloud_managed) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!is_cloud_managed) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
return;
}

VLOG(1) << "Starting an OTA installation of the enterprise companion app.";


RegistrationRequest registration;
registration.app_id = enterprise_companion::kCompanionAppId;
[Link] = base::Version("[Link]");
RegisterApp(
registration,
base::BindOnce([](int registration_result) {})
.Then(base::BindPostTask(
main_task_runner_,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, enterprise_companion::kCompanionAppId,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
kEmptyFlatMap, kEmptyFlatMap,
kInstallSourcePolicy, Priority::kForeground,
/*update_blocked=*/false,
PolicySameVersionUpdate::kNotAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{},
/*callback=*/base::DoNothing()),
MakeUpdateClientCallback(base::BindOnce([](Result result) {
VLOG(1)
<< "OTA installation of the "
"enterprise companion app "
"completed with result: "
<< result;
}).Then(std::move(callback)))))));
}

void UpdateServiceImplImpl::FetchPolicies(
policy::PolicyFetchReason reason,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (GetUpdaterScope() == UpdaterScope::kUser) {
VLOG(2) << "Policy fetch skipped for user updater.";
std::move(callback).Run(0);
} else {
if (config_->GetPolicyService()->IsCecaExperimentEnabled() &&
!config_->GetUpdaterPersistedData()
->GetProductVersion(enterprise_companion::kCompanionAppId)
.IsValid()) {
config_->GetPolicyService()->IsCloudManaged(base::BindOnce(
&UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA,
base::WrapRefCounted(this),
base::BindOnce(&PolicyService::FetchPolicies,
config_->GetPolicyService(), reason,
std::move(callback))));
} else {
config_->GetPolicyService()->FetchPolicies(reason, std::move(callback));
}
}
}

void UpdateServiceImplImpl::RegisterApp(
const RegistrationRequest& request,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsPathOnReadOnlyMount(request.existence_checker_path)) {
VLOG(1) << "Existence check path " << request.existence_checker_path
<< " is on read-only file system. Registration of "
<< request.app_id << " is skipped.";
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationError));
return;
}

if (!IsUpdaterOrCompanionApp(request.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}
bool send_event = !config_->GetUpdaterPersistedData()
->GetProductVersion(request.app_id)
.IsValid() &&
[Link]() &&
[Link] > base::Version("0") &&
!config_->GetUpdaterPersistedData()->GetEulaRequired();
config_->GetUpdaterPersistedData()->RegisterApp(request);
if (send_event) {
update_client::CrxComponent install_data;
install_data.ap = [Link];
install_data.app_id = request.app_id;
install_data.brand = request.brand_code;
install_data.lang = [Link];
install_data.requires_network_encryption = false;
install_data.version = [Link];
update_client_->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result = update_client::protocol_request::kEventResultSuccess},
base::BindOnce([](update_client::Error error) {
// Ignore event ping errors; registration has been successful.
}).Then(base::BindOnce(std::move(callback), kRegistrationSuccess)));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationSuccess));
}
void UpdateServiceImplImpl::GetAppStates(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(&UpdateServiceImplImpl::GetAppStatesImpl, this,
std::move(callback)));
}

void UpdateServiceImplImpl::GetAppStatesImpl(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

scoped_refptr<PersistedData> persisted_data =
config_->GetUpdaterPersistedData();
std::vector<std::string> app_ids = persisted_data->GetAppIds();
std::vector<AppState> apps;
for (const std::string& app_id : app_ids) {
AppState app_state;
app_state.app_id = app_id;
app_state.version = persisted_data->GetProductVersion(app_id);
app_state.version_path = persisted_data->GetProductVersionPath(app_id);
app_state.version_key = persisted_data->GetProductVersionKey(app_id);
app_state.ap = persisted_data->GetAP(app_id);
app_state.ap_path = persisted_data->GetAPPath(app_id);
app_state.ap_key = persisted_data->GetAPKey(app_id);
app_state.brand_code = persisted_data->GetBrandCode(app_id);
app_state.brand_path = persisted_data->GetBrandPath(app_id);
app_state.ecp = persisted_data->GetExistenceCheckerPath(app_id);
app_state.cohort = persisted_data->GetCohort(app_id);
apps.push_back(app_state);
}
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
}

void UpdateServiceImplImpl::RunPeriodicTasks(base::OnceClosure callback) {


VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

config_->GetUpdaterPersistedData()->SetLastStarted(
base::Time::NowFromSystemTime());
VLOG(1) << "last_started updated.";

// The installer should make an updater registration, but in case it halts


// before it does, synthesize a registration if necessary here.
const base::Version registered_updater_version =
config_->GetUpdaterPersistedData()->GetProductVersion(kUpdaterAppId);
if (!registered_updater_version.IsValid() ||
base::Version(kUpdaterVersion) > registered_updater_version) {
RegistrationRequest updater_request;
updater_request.app_id = kUpdaterAppId;
updater_request.version = base::Version(kUpdaterVersion);
RegisterApp(updater_request, base::DoNothing());
}
std::vector<base::OnceCallback<void(base::OnceClosure)>> new_tasks;
new_tasks.push_back(
base::BindOnce(&HandleInconsistentAppsTask::Run,
base::MakeRefCounted<HandleInconsistentAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(
base::BindOnce(&RemoveUninstalledAppsTask::Run,
base::MakeRefCounted<RemoveUninstalledAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(base::BindOnce(
&UpdateUsageStatsTask::Run,
base::MakeRefCounted<UpdateUsageStatsTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(MakeChangeOwnersTask(config_->GetUpdaterPersistedData(),
GetUpdaterScope()));

new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> update_service_impl,
base::OnceClosure callback) {
update_service_impl->FetchPolicies(
policy::PolicyFetchReason::kScheduled,
base::BindOnce(
[](base::OnceClosure callback, int /* ignore_result */) {
std::move(callback).Run();
},
std::move(callback)));
},
base::WrapRefCounted(this)));
new_tasks.push_back(
base::BindOnce(&CheckForUpdatesTask::Run,
base::MakeRefCounted<CheckForUpdatesTask>(
config_, GetUpdaterScope(),
/*task_name=*/"UpdateAll",
base::BindOnce(&UpdateServiceImplImpl::UpdateAll, this,
base::DoNothing()))));
new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
base::OnceClosure callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&UpdateServiceImplImpl::ForceInstall, self, base::DoNothing(),
base::BindOnce(
[](base::OnceClosure closure,
UpdateService::Result result) {
VLOG(0) << "ForceInstall task complete: " << result;
std::move(closure).Run();
},
std::move(callback))));
},
base::WrapRefCounted(this)));
new_tasks.push_back(base::BindOnce(
&AutoRunOnOsUpgradeTask::Run,
base::MakeRefCounted<AutoRunOnOsUpgradeTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(base::BindOnce(
&CleanupTask::Run, base::MakeRefCounted<CleanupTask>(GetUpdaterScope())));

const auto barrier_closure =


base::BarrierClosure(new_tasks.size(), std::move(callback));
for (auto& task : new_tasks) {
tasks_.push(base::BindOnce(std::move(task),
barrier_closure.Then(base::BindRepeating(
&UpdateServiceImplImpl::TaskDone, this))));
}

if (tasks_.size() == new_tasks.size()) {
TaskStart();
}
}

void UpdateServiceImplImpl::TaskStart() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!tasks_.empty()) {
main_task_runner_->PostDelayedTask(FROM_HERE, std::move(tasks_.front()),
config_->InitialDelay());
}
}

void UpdateServiceImplImpl::TaskDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
tasks_.pop();
TaskStart();
}

void UpdateServiceImplImpl::ForceInstall(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

PolicyStatus<std::vector<std::string>> force_install_apps_status =
config_->GetPolicyService()->GetForceInstallApps();
if (!force_install_apps_status) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}
std::vector<std::string> force_install_apps =
force_install_apps_status.policy();
CHECK(!force_install_apps.empty());

std::vector<std::string> installed_app_ids =
config_->GetUpdaterPersistedData()->GetAppIds();
std::ranges::sort(force_install_apps);
std::ranges::sort(installed_app_ids);

std::vector<std::string> app_ids_to_install;
std::ranges::set_difference(force_install_apps, installed_app_ids,
std::back_inserter(app_ids_to_install));
if (app_ids_to_install.empty()) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}

VLOG(1) << __func__ << ": app_ids_to_install: "


<< base::JoinString(app_ids_to_install, " ");
ShouldBlockUpdateForMeteredNetwork(
Priority::kBackground,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork,
this, app_ids_to_install, kEmptyFlatMap, kEmptyFlatMap,
UpdateService::PolicySameVersionUpdate::kNotAllowed, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::CheckForUpdate(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::CheckForUpdateImpl, this,
app_id, priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::CheckForUpdateImpl(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
VLOG(1) << __func__ << ": App not registered: " << app_id;
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork,
this, app_id, priority, policy_same_version_update, language,
state_update, std::move(callback)));
}

void UpdateServiceImplImpl::Update(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kScheduled,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::UpdateImpl,
this, app_id, install_data_index,
priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::UpdateImpl(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
std::vector<std::string>{app_id}, kEmptyFlatMap,
base::flat_map<std::string, std::string>(
{std::make_pair(app_id, install_data_index)}),
priority, policy_same_version_update, language, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::UpdateAll(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

auto app_ids = config_->GetUpdaterPersistedData()->GetAppIds();

CHECK(base::Contains(
app_ids, base::ToLowerASCII(kUpdaterAppId),
static_cast<std::string (*)(std::string_view)>(&base::ToLowerASCII)));

const Priority priority = Priority::kBackground;


ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
app_ids, kEmptyFlatMap, kEmptyFlatMap, priority,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/"", state_update,
base::BindOnce(
[](base::OnceCallback<void(Result)> callback,
scoped_refptr<PersistedData> persisted_data, Result result) {
if (result == Result::kSuccess) {
persisted_data->SetLastChecked(
base::Time::NowFromSystemTime());
VLOG(1) << "last_checked updated.";
}
std::move(callback).Run(result);
},
std::move(callback), config_->GetUpdaterPersistedData())));
}

void UpdateServiceImplImpl::Install(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::InstallImpl,
this, registration, client_install_data,
install_data_index, priority, language,
state_update),
std::move(callback))));
}
void UpdateServiceImplImpl::InstallImpl(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(registration.app_id, priority, true, policy)) {
HandleUpdateDisabledByPolicy(registration.app_id, policy, true, language,
state_update, std::move(callback));
return;
}
if (!IsUpdaterOrCompanionApp(registration.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const bool new_install = !config_->GetUpdaterPersistedData()


->GetProductVersion(registration.app_id)
.IsValid();
if (new_install) {
// Pre-register the app if there is no registration for it. This app
// registration is removed later if the app install encounters an error.
RegistrationRequest request = registration;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);
} else {
// Update lang/ap/iid.
RegistrationRequest request;
request.app_id = registration.app_id;
[Link] = language;
[Link] = [Link];
request.install_id = registration.install_id;
config_->GetUpdaterPersistedData()->RegisterApp(request);
}

std::multimap<std::string, base::RepeatingClosure>::iterator pos =


cancellation_callbacks_.emplace(registration.app_id, base::DoNothing());
pos->second = update_client_->Install(
registration.app_id,
base::BindOnce(
&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(), config_->GetUpdaterPersistedData(),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, client_install_data)}),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, install_data_index)}),
kInstallSourceTaggedMetainstaller, priority,
/*update_blocked=*/false, PolicySameVersionUpdate::kAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(), new_install, language,
state_update),
MakeUpdateClientCallback(std::move(callback))
.Then(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
const std::multimap<std::string,
base::RepeatingClosure>::iterator& pos) {
self->cancellation_callbacks_.erase(pos);
},
base::WrapRefCounted(this), pos)));
}

void UpdateServiceImplImpl::CancelInstalls(const std::string& app_id) {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << __func__;
auto [first, last] = cancellation_callbacks_.equal_range(app_id);
std::ranges::for_each(first, last, [](const auto& i) { [Link](); });
}

void UpdateServiceImplImpl::RunInstaller(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::RunInstallerImpl, this,
app_id, installer_path, install_args, install_data,
install_settings, language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::RunInstallerImpl(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, Priority::kForeground, true, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, true, language, state_update,
std::move(callback));
return;
}
if (!IsUpdaterOrCompanionApp(app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const base::Version pv =
config_->GetUpdaterPersistedData()->GetProductVersion(app_id);
const bool new_install = ![Link]();
AppInfo app_info(
GetUpdaterScope(), app_id,
config_->GetUpdaterPersistedData()->GetAP(app_id),
![Link]() ? language
: config_->GetUpdaterPersistedData()->GetLang(app_id),
config_->GetUpdaterPersistedData()->GetBrandCode(app_id), pv,
config_->GetUpdaterPersistedData()->GetExistenceCheckerPath(app_id));

// Pre-register the app in case there is no registration for it. This app
// registration is removed later if `new_install` is `true and if the app
// install encounters an error.
RegistrationRequest request;
request.app_id = app_id;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);

const base::Version installer_version([&install_settings]() -> std::string {


std::unique_ptr<base::Value> install_settings_deserialized =
JSONStringValueDeserializer(install_settings)
.Deserialize(
/*error_code=*/nullptr, /*error_message=*/nullptr);
if (install_settings_deserialized) {
const base::Value::Dict* install_settings_dict =
install_settings_deserialized->GetIfDict();
if (install_settings_dict) {
const std::string* installer_version_value =
install_settings_dict->FindString(kInstallerVersion);
if (installer_version_value) {
return *installer_version_value;
}
}
}

return {};
}());

// Create a task runner that:


// 1) has SequencedTaskRunner::CurrentDefaultHandle set, to run
// `state_update` callback.
// 2) may block, since `RunApplicationInstaller` blocks.
// 3) has `base::WithBaseSyncPrimitives()`, since `RunApplicationInstaller`
// waits on process.
auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](const AppInfo& app_info, const base::FilePath& installer_path,
const std::string& install_args, const std::string& install_data,
base::RepeatingCallback<void(const UpdateState&)> state_update,
bool usage_stats_enabled) {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
return InstallerResult(
{.category = update_client::ErrorCategory::kInstall,
.code = kErrorCreatingTempDir,
#if BUILDFLAG(IS_WIN)
.extra = HRESULTFromLastError()
#else
.extra = logging::GetLastSystemErrorCode()
#endif // BUILDFLAG(IS_WIN)
});
}

return RunApplicationInstaller(
app_info, installer_path, install_args,
WriteInstallerDataToTempFile(temp_dir.GetPath(), install_data),
usage_stats_enabled, kWaitForAppInstaller,
base::BindRepeating(
[](base::RepeatingCallback<void(const UpdateState&)>
state_update,
const std::string& app_id, int progress) {
VLOG(4) << "Install progress: " << progress;
UpdateState state;
state.app_id = app_id;
[Link] = UpdateState::State::kInstalling;
state.install_progress = progress;
state_update.Run(state);
},
state_update, app_info.app_id));
},
app_info, installer_path, install_args, install_data, state_update,
IsUpdaterOrCompanionApp(app_info.app_id) &&
config_->GetUpdaterPersistedData()->GetUsageStatsEnabled()),
base::BindOnce(
[](scoped_refptr<Configurator> config,
scoped_refptr<PersistedData> persisted_data,
scoped_refptr<update_client::UpdateClient> update_client,
base::Version installer_version,
base::RepeatingCallback<void(const UpdateState&)> state_update,
const std::string& app_id, const std::string& ap,
const std::string& brand, const std::string& language,
bool new_install, base::OnceCallback<void(Result)> callback,
const InstallerResult& result) {
// Final state update after installation completes.
UpdateState state;
state.app_id = app_id;
[Link] =
[Link] == update_client::ErrorCategory::kNone
? UpdateState::State::kUpdated
: UpdateState::State::kUpdateError;

const base::Version registered_version =


internal::GetRegisteredInstallerVersion(app_id);
if (registered_version.IsValid()) {
VLOG(1) << app_id << " registered_version " << registered_version
<< " overrides the original installer_version "
<< installer_version;
installer_version = registered_version;
}

if ([Link] == update_client::ErrorCategory::kNone &&


installer_version.IsValid()) {
persisted_data->SetProductVersion(app_id, installer_version);
config->GetPrefService()->CommitPendingWrite();
} else if (new_install) {
persisted_data->RemoveApp(app_id);
}

state.error_category = ToErrorCategory([Link]);
state.error_code = [Link];
state.extra_code1 = [Link];
state.installer_text = result.installer_text;
#if BUILDFLAG(IS_WIN)
if (state.installer_text.empty())
state.installer_text = internal::GetInstallerText(
state.error_category, state.error_code, state.extra_code1,
language);
#endif // BUILDFLAG(IS_WIN)
state.installer_cmd_line = result.installer_cmd_line;
state_update.Run(state);
VLOG(1) << app_id
<< " installation completed: " << state.error_code;

if (!persisted_data->GetEulaRequired()) {
// Send an install ping. In some environments the ping cannot be
// sent, so do not wait for it to be sent before calling back the
// client.
update_client::CrxComponent install_data;
install_data.ap = ap;
install_data.app_id = app_id;
install_data.lang = language;
install_data.brand = brand;
install_data.requires_network_encryption = false;
install_data.install_source = kInstallSourceOffline;
install_data.version = installer_version;
update_client->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result =
[Link] ==
update_client::ErrorCategory::kNone
? update_client::protocol_request::
kEventResultSuccess
: update_client::protocol_request::kEventResultError,
.error_category = [Link],
.error_code = [Link],
.extra_code1 = [Link]},
base::DoNothing());
}

std::move(callback).Run([Link] ==
update_client::ErrorCategory::kNone
? Result::kSuccess
: Result::kInstallFailed);
},
config_, config_->GetUpdaterPersistedData(), update_client_,
installer_version, state_update, app_info.app_id, app_info.ap,
app_info.brand, language, new_install, std::move(callback)));
}

bool UpdateServiceImplImpl::IsUpdateDisabledByPolicy(const std::string& app_id,


Priority priority,
bool is_install,
int& policy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

policy = kPolicyEnabled;

if (is_install) {
PolicyStatus<int> app_install_policy_status =
config_->GetPolicyService()->GetPolicyForAppInstalls(app_id);
if (app_install_policy_status) {
policy = app_install_policy_status.policy();
}
return app_install_policy_status &&
(policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
policy == kPolicyEnabledMachineOnly));
} else {
PolicyStatus<int> app_update_policy_status =
config_->GetPolicyService()->GetPolicyForAppUpdates(app_id);
if (app_update_policy_status) {
policy = app_update_policy_status.policy();
}
return app_update_policy_status &&
(policy == kPolicyDisabled ||
((policy == kPolicyManualUpdatesOnly) &&
(priority != Priority::kForeground)) ||
((policy == kPolicyAutomaticUpdatesOnly) &&
(priority == Priority::kForeground)));
}
}

void UpdateServiceImplImpl::HandleUpdateDisabledByPolicy(
const std::string& app_id,
int policy,
bool is_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

UpdateState update_state;
update_state.app_id = app_id;
update_state.state = UpdateService::UpdateState::State::kUpdateError;
update_state.error_category = UpdateService::ErrorCategory::kInstaller;
update_state.error_code =
is_install ? GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY
: policy != kPolicyAutomaticUpdatesOnly
? GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY
: GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL;
update_state.extra_code1 = 0;
#if BUILDFLAG(IS_WIN)
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)
base::BindPostTask(main_task_runner_, state_update).Run(update_state);
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kUpdateCheckFailed);
}

void UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::CheckForUpdate, update_client_, app_id,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(), kEmptyFlatMap,
kEmptyFlatMap,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::Update, update_client_, app_ids,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
PolicySameVersionUpdate policy_same_version_update,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

// The result from Install is only used for logging. Thus, arbitrarily pick
// the first non-success result to propagate.
auto barrier_callback = base::BarrierCallback<Result>(
app_ids.size(),
base::BindOnce([](const std::vector<Result>& results) {
auto error_it = std::ranges::find_if(
results, [](Result result) { return result != Result::kSuccess; });
return error_it == std::end(results) ? Result::kSuccess : *error_it;
}).Then(std::move(callback)));

for (const std::string& id : app_ids) {


main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, id,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
kInstallSourcePolicy, Priority::kBackground,
update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{}, state_update),
MakeUpdateClientCallback(barrier_callback)));
}
}

UpdateServiceImplImpl::~UpdateServiceImplImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
config_->GetPrefService()->SchedulePendingLossyWrites();
}

} // namespace updater
#include "chrome/updater/update_service_impl_impl.h"

#include <algorithm>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/global_constants.h"
#include "chrome/updater/app/app_utils.h"
#include "chrome/updater/auto_run_on_os_upgrade_task.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/change_owners_task.h"
#include "chrome/updater/check_for_updates_task.h"
#include "chrome/updater/cleanup_task.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/handle_inconsistent_apps_task.h"
#include "chrome/updater/installer.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/remove_uninstalled_apps_task.h"
#include "chrome/updater/update_block_check.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_usage_stats_task.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"

#if BUILDFLAG(IS_MAC)
#include <sys/mount.h>
#endif // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)
#include <winhttp.h>

#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/[Link]"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#endif // BUILDFLAG(IS_WIN)

namespace updater {

// The functions below are various adaptors between |update_client| and


// |UpdateService| types.

namespace internal {
UpdateService::Result ToResult(update_client::Error error) {
switch (error) {
case update_client::Error::NONE:
return UpdateService::Result::kSuccess;
case update_client::Error::UPDATE_IN_PROGRESS:
return UpdateService::Result::kUpdateInProgress;
case update_client::Error::UPDATE_CANCELED:
return UpdateService::Result::kUpdateCanceled;
case update_client::Error::RETRY_LATER:
return UpdateService::Result::kRetryLater;
case update_client::Error::SERVICE_ERROR:
return UpdateService::Result::kServiceFailed;
case update_client::Error::UPDATE_CHECK_ERROR:
return UpdateService::Result::kUpdateCheckFailed;
case update_client::Error::CRX_NOT_FOUND:
return UpdateService::Result::kAppNotFound;
case update_client::Error::INVALID_ARGUMENT:
case update_client::Error::BAD_CRX_DATA_CALLBACK:
return UpdateService::Result::kInvalidArgument;
case update_client::Error::MAX_VALUE:
NOTREACHED();
}
}

void GetComponents(
scoped_refptr<PolicyService> policy_service,
crx_file::VerifierFormat verifier_format,
scoped_refptr<PersistedData> persisted_data,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
const std::string& install_source,
UpdateService::Priority priority,
bool update_blocked,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::vector<std::string>& ids,
base::OnceCallback<
void(const std::vector<std::optional<update_client::CrxComponent>>&)>
callback) {
VLOG(1) << __func__
<< ". Same version update: " << policy_same_version_update;
const bool is_foreground = priority == UpdateService::Priority::kForeground;
auto barrier_callback =
base::BarrierCallback<std::optional<update_client::CrxComponent>>(
[Link](),
base::BindOnce(
[](const std::vector<std::string>& ids,
const std::vector<std::optional<update_client::CrxComponent>>&
unordered) {
// Re-order the vector to match the order of `ids`.
std::vector<std::optional<update_client::CrxComponent>> ordered;
for (const auto& id : ids) {
auto it = std::ranges::find_if(
unordered,
[&id](std::optional<update_client::CrxComponent> v) {
return v && v->app_id == id;
});
ordered.push_back(it != [Link]() ? *it : std::nullopt);
}
return ordered;
},
ids)
.Then(std::move(callback)));
for (const auto& id : ids) {
base::MakeRefCounted<Installer>(
id,
[&app_client_install_data, &id] {
auto it = app_client_install_data.find(id);
return it != app_client_install_data.end() ? it->second : "";
}(),
[&app_install_data_index, &id] {
auto it = app_install_data_index.find(id);
return it != app_install_data_index.end() ? it->second : "";
}(),
install_source,
policy_service->GetTargetChannel(id).policy_or(std::string()),
policy_service->GetTargetVersionPrefix(id).policy_or(std::string()),
policy_service->IsRollbackToTargetVersionAllowed(id).policy_or(false),
[&policy_service, &id, &is_foreground, update_blocked] {
if (update_blocked) {
return true;
}
PolicyStatus<int> app_updates =
policy_service->GetPolicyForAppUpdates(id);
return app_updates &&
(app_updates.policy() == kPolicyDisabled ||
(!is_foreground &&
app_updates.policy() == kPolicyManualUpdatesOnly) ||
(is_foreground &&
app_updates.policy() == kPolicyAutomaticUpdatesOnly));
}(),
policy_same_version_update, persisted_data, verifier_format)
->MakeCrxComponent(
base::BindOnce([](update_client::CrxComponent component) {
return component;
}).Then(barrier_callback));
}
}

#if BUILDFLAG(IS_WIN)
namespace {

std::wstring GetTextForUpdateClientInstallError(int error_code,


const std::wstring& language) {
#define INSTALL_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE, L#error_code, \
language)

switch (error_code) {
INSTALL_SWITCH_ENTRY(update_client::InstallError::NONE);
INSTALL_SWITCH_ENTRY(update_client::InstallError::FINGERPRINT_WRITE_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::BAD_MANIFEST);
INSTALL_SWITCH_ENTRY(update_client::InstallError::GENERIC_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MOVE_FILES_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::SET_PERMISSIONS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::INVALID_VERSION);
INSTALL_SWITCH_ENTRY(update_client::InstallError::VERSION_NOT_UPGRADED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::NO_DIR_COMPONENT_USER);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CLEAN_INSTALL_DIR_FAILED);
INSTALL_SWITCH_ENTRY(
update_client::InstallError::INSTALL_VERIFICATION_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MISSING_INSTALL_PARAMS);
INSTALL_SWITCH_ENTRY(update_client::InstallError::LAUNCH_PROCESS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CUSTOM_ERROR_BASE);
default:
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef INSTALL_SWITCH_ENTRY
}

std::wstring GetTextForDownloadError(int error, const std::wstring& language) {


#define DOWNLOAD_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE, L#error_code, \
language)

switch (error) {
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_URL);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_HASH);
DOWNLOAD_SWITCH_ENTRY(
update_client::CrxDownloaderError::BITS_TOO_MANY_JOBS);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::GENERIC_ERROR);

case static_cast<int>(update_client::CrxDownloaderError::BAD_HASH):
return GetLocalizedString(IDS_DOWNLOAD_HASH_MISMATCH_BASE);

default:
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef DOWNLOAD_SWITCH_ENTRY
}

std::wstring GetTextForUnpackError(int error, const std::wstring& language) {


#define UNPACK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE, L#error_code, \
language)
#define UNPACK_CACHING_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_UNPACK_CACHING_ERROR_BASE, L#error_code, \
language)

switch (error) {
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidParams);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidFile);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipPathError);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipFailed);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadManifest);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadExtension);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kIoError);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaVerificationFailure);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaBadCommands);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaUnsupportedCommand);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaOperationFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaPatchProcessFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaMissingExistingFile);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kPuffinMissingPreviousCrx);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kCrxCacheNotProvided);

UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToAddToCache);
UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToCreateCacheDir);

default:
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef UNPACK_SWITCH_ENTRY
#undef UNPACK_CACHING_SWITCH_ENTRY
}

std::wstring GetTextForServiceError(int error, const std::wstring& language) {


#define SERVICE_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE, L#error_code, \
language)

switch (error) {
SERVICE_SWITCH_ENTRY(update_client::ServiceError::SERVICE_WAIT_FAILED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::UPDATE_DISABLED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::CHECK_FOR_UPDATE_ONLY);

case static_cast<int>(update_client::ServiceError::CANCELLED):
return GetLocalizedString(IDS_SERVICE_ERROR_CANCELLED_BASE, language);

default:
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef SERVICE_SWITCH_ENTRY
}

std::wstring GetTextForUpdateCheckError(int error,


const std::wstring& language) {
#define UPDATE_CHECK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UPDATE_CHECK_ERROR_BASE, \
L#error_code, language)

switch (error) {
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::RESPONSE_NOT_TRUSTED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::MISSING_URLS);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::PARSE_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::UPDATE_RESPONSE_NOT_FOUND);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::URL_FETCHER_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::INVALID_APPID);

case static_cast<int>(update_client::ProtocolError::UNKNOWN_APPLICATION):
return GetLocalizedString(IDS_UNKNOWN_APPLICATION_BASE, language);

case static_cast<int>(update_client::ProtocolError::RESTRICTED_APPLICATION):
return GetLocalizedString(IDS_RESTRICTED_RESPONSE_FROM_SERVER_BASE,
language);

case static_cast<int>(update_client::ProtocolError::OS_NOT_SUPPORTED):
return GetLocalizedString(IDS_OS_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::HW_NOT_SUPPORTED):
return GetLocalizedString(IDS_HW_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::NO_HASH):
return GetLocalizedString(IDS_NO_HASH_BASE, language);

case static_cast<int>(update_client::ProtocolError::UNSUPPORTED_PROTOCOL):
return GetLocalizedString(IDS_UNSUPPORTED_PROTOCOL_BASE, language);

case static_cast<int>(update_client::ProtocolError::INTERNAL):
return GetLocalizedString(IDS_INTERNAL_BASE, language);

// Http Status Code `401` Unauthorized.


case 401:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_UNAUTHORIZED_BASE,
language);

// Http Status Code `403` Forbidden.


case 403:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_FORBIDDEN_BASE, language);

// Http Status Code `407` Proxy Authentication Required.


case 407:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_PROXY_AUTH_REQUIRED_BASE,
language);

case HRESULT_FROM_WIN32(ERROR_WINHTTP_NAME_NOT_RESOLVED):
return GetLocalizedStringF(IDS_NO_NETWORK_PRESENT_ERROR_BASE,
GetExecutableRelativePath().value(), language);
default:
return GetLocalizedStringF(
IDS_GENERIC_UPDATE_CHECK_ERROR_BASE,
error >= 400 && error < 600
? base::UTF8ToWide(base::StringPrintf("HTTP %d", error))
: GetTextForSystemError(error),
language);
}
#undef UPDATE_CHECK_SWITCH_ENTRY
}

std::wstring GetTextForInstallerError(int error_code,


const std::wstring& language) {
#define POLICY_ERROR_SWITCH_ENTRY(error_code) \
case error_code: \
return GetLocalizedStringF(IDS_APP_INSTALL_DISABLED_BY_GROUP_POLICY_BASE, \
L#error_code, language)

switch (error_code) {
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL);

case GOOPDATEINSTALL_E_FILENAME_INVALID:
return GetLocalizedString(IDS_INVALID_INSTALLER_FILENAME_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_FAILED_START:
return GetLocalizedString(IDS_INSTALLER_FAILED_TO_START_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT:
return GetLocalizedString(IDS_INSTALLER_TIMED_OUT_BASE, language);

case GOOPDATEINSTALL_E_INSTALL_ALREADY_RUNNING:
return GetLocalizedStringF(
IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(ERROR_INSTALL_ALREADY_RUNNING), language);

case ERROR_SUCCESS_REBOOT_INITIATED:
case ERROR_SUCCESS_REBOOT_REQUIRED:
case ERROR_SUCCESS_RESTART_REQUIRED:
return GetLocalizedStringF(IDS_INSTALL_REBOOT_BASE,
GetTextForSystemError(error_code), language);

default:
return GetLocalizedStringF(IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef POLICY_ERROR_SWITCH_ENTRY
}

} // namespace

std::string GetInstallerText(UpdateService::ErrorCategory error_category,


int error_code,
int extra_code,
const std::string& language) {
if (!error_code) {
return {};
}
const std::wstring language_w = base::UTF8ToWide(language);
return base::WideToUTF8(base::StrCat(
{[&] {
switch (error_category) {
case UpdateService::ErrorCategory::kInstall:
return GetTextForUpdateClientInstallError(error_code, language_w);
case UpdateService::ErrorCategory::kDownload:
return GetTextForDownloadError(error_code, language_w);
case UpdateService::ErrorCategory::kUnpack:
return GetTextForUnpackError(error_code, language_w);
case UpdateService::ErrorCategory::kService:
return GetTextForServiceError(error_code, language_w);
case UpdateService::ErrorCategory::kUpdateCheck:
return GetTextForUpdateCheckError(error_code, language_w);
case UpdateService::ErrorCategory::kInstaller:
return GetTextForInstallerError(error_code, language_w);
default:
LOG(ERROR) << "Unknown error category: " << error_category;
return std::wstring();
}
}(),
[&] {
if (!extra_code) {
return std::wstring();
}
return base::StrCat(
{L"\n", GetLocalizedStringF(IDS_EXTRA_CODE_BASE,
base::UTF8ToWide(base::StringPrintf(
"%#x", extra_code)),
language_w)});
}()}));
}
#endif // BUILDFLAG(IS_WIN)

base::Version GetRegisteredInstallerVersion(const std::string& app_id) {


#if BUILDFLAG(IS_WIN)
std::wstring pv;
return base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScope()),
GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ))
.ReadValue(kRegValuePV, &pv) == ERROR_SUCCESS
? base::Version(base::WideToUTF8(pv))
: base::Version();
#else // BUILDFLAG(IS_WIN)
return {};
#endif // BUILDFLAG(IS_WIN)
}

} // namespace internal

namespace {

constexpr base::flat_map<std::string, std::string> kEmptyFlatMap;

update_client::Callback MakeUpdateClientCallback(
base::OnceCallback<void(UpdateService::Result)> callback) {
return base::BindOnce(
[](base::OnceCallback<void(UpdateService::Result)> callback,
update_client::Error error) {
std::move(callback).Run(internal::ToResult(error));
},
std::move(callback));
}

UpdateService::UpdateState::State ToUpdateState(
update_client::ComponentState component_state) {
switch (component_state) {
case update_client::ComponentState::kNew:
return UpdateService::UpdateState::State::kNotStarted;

case update_client::ComponentState::kChecking:
return UpdateService::UpdateState::State::kCheckingForUpdates;

case update_client::ComponentState::kDownloading:
case update_client::ComponentState::kDownloadingDiff:
return UpdateService::UpdateState::State::kDownloading;

case update_client::ComponentState::kCanUpdate:
return UpdateService::UpdateState::State::kUpdateAvailable;

case update_client::ComponentState::kUpdating:
case update_client::ComponentState::kUpdatingDiff:
return UpdateService::UpdateState::State::kInstalling;

case update_client::ComponentState::kUpdated:
return UpdateService::UpdateState::State::kUpdated;

case update_client::ComponentState::kUpToDate:
return UpdateService::UpdateState::State::kNoUpdate;

case update_client::ComponentState::kUpdateError:
return UpdateService::UpdateState::State::kUpdateError;

case update_client::ComponentState::kRun:
case update_client::ComponentState::kLastStatus:
NOTREACHED();
}
}

UpdateService::ErrorCategory ToErrorCategory(
update_client::ErrorCategory error_category) {
switch (error_category) {
case update_client::ErrorCategory::kNone:
return UpdateService::ErrorCategory::kNone;
case update_client::ErrorCategory::kDownload:
return UpdateService::ErrorCategory::kDownload;
case update_client::ErrorCategory::kUnpack:
return UpdateService::ErrorCategory::kUnpack;
case update_client::ErrorCategory::kInstall:
return UpdateService::ErrorCategory::kInstall;
case update_client::ErrorCategory::kService:
return UpdateService::ErrorCategory::kService;
case update_client::ErrorCategory::kUpdateCheck:
return UpdateService::ErrorCategory::kUpdateCheck;
case update_client::ErrorCategory::kInstaller:
return UpdateService::ErrorCategory::kInstaller;
}
}
update_client::UpdateClient::CrxStateChangeCallback
MakeUpdateClientCrxStateChangeCallback(
scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data,
const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)> callback) {
return base::BindRepeating(
[](scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data, const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
callback,
const update_client::CrxUpdateItem& crx_update_item) {
UpdateService::UpdateState update_state;
update_state.app_id = crx_update_item.id;
update_state.state = ToUpdateState(crx_update_item.state);
update_state.next_version = crx_update_item.next_version;
update_state.downloaded_bytes = crx_update_item.downloaded_bytes;
update_state.total_bytes = crx_update_item.total_bytes;
update_state.install_progress = crx_update_item.install_progress;
update_state.error_category =
ToErrorCategory(crx_update_item.error_category);
update_state.error_code = crx_update_item.error_code;
update_state.extra_code1 = crx_update_item.extra_code1;
if (crx_update_item.installer_result) {
update_state.installer_cmd_line =
crx_update_item.installer_result->installer_cmd_line;
update_state.installer_text =
crx_update_item.installer_result->installer_text;
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
UpdateService::ErrorCategory::kInstaller,
update_state.error_code, update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)
}

if (update_state.state == UpdateService::UpdateState::State::kUpdated ||
update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate) {
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)

// If a new install encounters an error, the AppId registered in


// `UpdateServiceImplImpl::Install` needs to be removed here.
// Otherwise the updater may remain installed even if there are no
// other apps to manage, and try to update the app even though the app
// was not installed.
if (new_install &&
(update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate)) {
persisted_data->RemoveApp(update_state.app_id);
}

// Commit the prefs values written by |update_client| when the


// update has completed, such as `pv` and `fingerprint`.
config->GetPrefService()->CommitPendingWrite();
}

CHECK(!update_state.app_id.empty());
CHECK_NE(update_state.state,
UpdateService::UpdateState::State::kUnknown);
[Link](update_state);
},
config, persisted_data, new_install, language, callback);
}

bool IsPathOnReadOnlyMount(const base::FilePath& path) {


if ([Link]()) {
return false;
}
#if BUILDFLAG(IS_MAC)
struct statfs fsinfo = {};
if (statfs([Link]().c_str(), &fsinfo) == -1) {
LOG(ERROR) << "Failed to stat: " << path;
return false;
}
return fsinfo.f_flags & MNT_RDONLY;
#else
return false;
#endif // BUILDFLAG(IS_MAC)
}

void FetchPoliciesDone(
base::OnceCallback<void(base::OnceCallback<void(UpdateService::Result)>)>
fetch_policies_done,
base::OnceCallback<void(UpdateService::Result)> callback,
int result) {
if (result != kErrorOk) {
LOG(ERROR) << "FetchPolicies failed: " << result;
// Ignore policy fetch failures and fall through.
}

std::move(fetch_policies_done).Run(std::move(callback));
}

} // namespace

UpdateServiceImplImpl::UpdateServiceImplImpl(scoped_refptr<Configurator> config)
: config_(config),
main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
update_client_(update_client::UpdateClientFactory(config)) {}

void UpdateServiceImplImpl::GetVersion(
base::OnceCallback<void(const base::Version&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), base::Version(kUpdaterVersion)));
}

void UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA(
base::OnceClosure callback,
bool is_cloud_managed) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!is_cloud_managed) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
return;
}

VLOG(1) << "Starting an OTA installation of the enterprise companion app.";


RegistrationRequest registration;
registration.app_id = enterprise_companion::kCompanionAppId;
[Link] = base::Version("[Link]");
RegisterApp(
registration,
base::BindOnce([](int registration_result) {})
.Then(base::BindPostTask(
main_task_runner_,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, enterprise_companion::kCompanionAppId,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
kEmptyFlatMap, kEmptyFlatMap,
kInstallSourcePolicy, Priority::kForeground,
/*update_blocked=*/false,
PolicySameVersionUpdate::kNotAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{},
/*callback=*/base::DoNothing()),
MakeUpdateClientCallback(base::BindOnce([](Result result) {
VLOG(1)
<< "OTA installation of the "
"enterprise companion app "
"completed with result: "
<< result;
}).Then(std::move(callback)))))));
}

void UpdateServiceImplImpl::FetchPolicies(
policy::PolicyFetchReason reason,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (GetUpdaterScope() == UpdaterScope::kUser) {
VLOG(2) << "Policy fetch skipped for user updater.";
std::move(callback).Run(0);
} else {
if (config_->GetPolicyService()->IsCecaExperimentEnabled() &&
!config_->GetUpdaterPersistedData()
->GetProductVersion(enterprise_companion::kCompanionAppId)
.IsValid()) {
config_->GetPolicyService()->IsCloudManaged(base::BindOnce(
&UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA,
base::WrapRefCounted(this),
base::BindOnce(&PolicyService::FetchPolicies,
config_->GetPolicyService(), reason,
std::move(callback))));
} else {
config_->GetPolicyService()->FetchPolicies(reason, std::move(callback));
}
}
}

void UpdateServiceImplImpl::RegisterApp(
const RegistrationRequest& request,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsPathOnReadOnlyMount(request.existence_checker_path)) {
VLOG(1) << "Existence check path " << request.existence_checker_path
<< " is on read-only file system. Registration of "
<< request.app_id << " is skipped.";
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationError));
return;
}

if (!IsUpdaterOrCompanionApp(request.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}
bool send_event = !config_->GetUpdaterPersistedData()
->GetProductVersion(request.app_id)
.IsValid() &&
[Link]() &&
[Link] > base::Version("0") &&
!config_->GetUpdaterPersistedData()->GetEulaRequired();
config_->GetUpdaterPersistedData()->RegisterApp(request);
if (send_event) {
update_client::CrxComponent install_data;
install_data.ap = [Link];
install_data.app_id = request.app_id;
install_data.brand = request.brand_code;
install_data.lang = [Link];
install_data.requires_network_encryption = false;
install_data.version = [Link];
update_client_->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result = update_client::protocol_request::kEventResultSuccess},
base::BindOnce([](update_client::Error error) {
// Ignore event ping errors; registration has been successful.
}).Then(base::BindOnce(std::move(callback), kRegistrationSuccess)));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationSuccess));
}

void UpdateServiceImplImpl::GetAppStates(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(&UpdateServiceImplImpl::GetAppStatesImpl, this,
std::move(callback)));
}

void UpdateServiceImplImpl::GetAppStatesImpl(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

scoped_refptr<PersistedData> persisted_data =
config_->GetUpdaterPersistedData();
std::vector<std::string> app_ids = persisted_data->GetAppIds();
std::vector<AppState> apps;
for (const std::string& app_id : app_ids) {
AppState app_state;
app_state.app_id = app_id;
app_state.version = persisted_data->GetProductVersion(app_id);
app_state.version_path = persisted_data->GetProductVersionPath(app_id);
app_state.version_key = persisted_data->GetProductVersionKey(app_id);
app_state.ap = persisted_data->GetAP(app_id);
app_state.ap_path = persisted_data->GetAPPath(app_id);
app_state.ap_key = persisted_data->GetAPKey(app_id);
app_state.brand_code = persisted_data->GetBrandCode(app_id);
app_state.brand_path = persisted_data->GetBrandPath(app_id);
app_state.ecp = persisted_data->GetExistenceCheckerPath(app_id);
app_state.cohort = persisted_data->GetCohort(app_id);
apps.push_back(app_state);
}
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
}

void UpdateServiceImplImpl::RunPeriodicTasks(base::OnceClosure callback) {


VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

config_->GetUpdaterPersistedData()->SetLastStarted(
base::Time::NowFromSystemTime());
VLOG(1) << "last_started updated.";

// The installer should make an updater registration, but in case it halts


// before it does, synthesize a registration if necessary here.
const base::Version registered_updater_version =
config_->GetUpdaterPersistedData()->GetProductVersion(kUpdaterAppId);
if (!registered_updater_version.IsValid() ||
base::Version(kUpdaterVersion) > registered_updater_version) {
RegistrationRequest updater_request;
updater_request.app_id = kUpdaterAppId;
updater_request.version = base::Version(kUpdaterVersion);
RegisterApp(updater_request, base::DoNothing());
}
std::vector<base::OnceCallback<void(base::OnceClosure)>> new_tasks;
new_tasks.push_back(
base::BindOnce(&HandleInconsistentAppsTask::Run,
base::MakeRefCounted<HandleInconsistentAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(
base::BindOnce(&RemoveUninstalledAppsTask::Run,
base::MakeRefCounted<RemoveUninstalledAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(base::BindOnce(
&UpdateUsageStatsTask::Run,
base::MakeRefCounted<UpdateUsageStatsTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(MakeChangeOwnersTask(config_->GetUpdaterPersistedData(),
GetUpdaterScope()));

new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> update_service_impl,
base::OnceClosure callback) {
update_service_impl->FetchPolicies(
policy::PolicyFetchReason::kScheduled,
base::BindOnce(
[](base::OnceClosure callback, int /* ignore_result */) {
std::move(callback).Run();
},
std::move(callback)));
},
base::WrapRefCounted(this)));
new_tasks.push_back(
base::BindOnce(&CheckForUpdatesTask::Run,
base::MakeRefCounted<CheckForUpdatesTask>(
config_, GetUpdaterScope(),
/*task_name=*/"UpdateAll",
base::BindOnce(&UpdateServiceImplImpl::UpdateAll, this,
base::DoNothing()))));
new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
base::OnceClosure callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&UpdateServiceImplImpl::ForceInstall, self, base::DoNothing(),
base::BindOnce(
[](base::OnceClosure closure,
UpdateService::Result result) {
VLOG(0) << "ForceInstall task complete: " << result;
std::move(closure).Run();
},
std::move(callback))));
},
base::WrapRefCounted(this)));
new_tasks.push_back(base::BindOnce(
&AutoRunOnOsUpgradeTask::Run,
base::MakeRefCounted<AutoRunOnOsUpgradeTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(base::BindOnce(
&CleanupTask::Run, base::MakeRefCounted<CleanupTask>(GetUpdaterScope())));
const auto barrier_closure =
base::BarrierClosure(new_tasks.size(), std::move(callback));
for (auto& task : new_tasks) {
tasks_.push(base::BindOnce(std::move(task),
barrier_closure.Then(base::BindRepeating(
&UpdateServiceImplImpl::TaskDone, this))));
}

if (tasks_.size() == new_tasks.size()) {
TaskStart();
}
}

void UpdateServiceImplImpl::TaskStart() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!tasks_.empty()) {
main_task_runner_->PostDelayedTask(FROM_HERE, std::move(tasks_.front()),
config_->InitialDelay());
}
}

void UpdateServiceImplImpl::TaskDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
tasks_.pop();
TaskStart();
}

void UpdateServiceImplImpl::ForceInstall(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

PolicyStatus<std::vector<std::string>> force_install_apps_status =
config_->GetPolicyService()->GetForceInstallApps();
if (!force_install_apps_status) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}
std::vector<std::string> force_install_apps =
force_install_apps_status.policy();
CHECK(!force_install_apps.empty());

std::vector<std::string> installed_app_ids =
config_->GetUpdaterPersistedData()->GetAppIds();
std::ranges::sort(force_install_apps);
std::ranges::sort(installed_app_ids);

std::vector<std::string> app_ids_to_install;
std::ranges::set_difference(force_install_apps, installed_app_ids,
std::back_inserter(app_ids_to_install));
if (app_ids_to_install.empty()) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}

VLOG(1) << __func__ << ": app_ids_to_install: "


<< base::JoinString(app_ids_to_install, " ");

ShouldBlockUpdateForMeteredNetwork(
Priority::kBackground,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork,
this, app_ids_to_install, kEmptyFlatMap, kEmptyFlatMap,
UpdateService::PolicySameVersionUpdate::kNotAllowed, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::CheckForUpdate(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::CheckForUpdateImpl, this,
app_id, priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::CheckForUpdateImpl(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
VLOG(1) << __func__ << ": App not registered: " << app_id;
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork,
this, app_id, priority, policy_same_version_update, language,
state_update, std::move(callback)));
}

void UpdateServiceImplImpl::Update(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kScheduled,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::UpdateImpl,
this, app_id, install_data_index,
priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::UpdateImpl(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
std::vector<std::string>{app_id}, kEmptyFlatMap,
base::flat_map<std::string, std::string>(
{std::make_pair(app_id, install_data_index)}),
priority, policy_same_version_update, language, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::UpdateAll(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

auto app_ids = config_->GetUpdaterPersistedData()->GetAppIds();

CHECK(base::Contains(
app_ids, base::ToLowerASCII(kUpdaterAppId),
static_cast<std::string (*)(std::string_view)>(&base::ToLowerASCII)));

const Priority priority = Priority::kBackground;


ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
app_ids, kEmptyFlatMap, kEmptyFlatMap, priority,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/"", state_update,
base::BindOnce(
[](base::OnceCallback<void(Result)> callback,
scoped_refptr<PersistedData> persisted_data, Result result) {
if (result == Result::kSuccess) {
persisted_data->SetLastChecked(
base::Time::NowFromSystemTime());
VLOG(1) << "last_checked updated.";
}
std::move(callback).Run(result);
},
std::move(callback), config_->GetUpdaterPersistedData())));
}

void UpdateServiceImplImpl::Install(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::InstallImpl,
this, registration, client_install_data,
install_data_index, priority, language,
state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::InstallImpl(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(registration.app_id, priority, true, policy)) {
HandleUpdateDisabledByPolicy(registration.app_id, policy, true, language,
state_update, std::move(callback));
return;
}
if (!IsUpdaterOrCompanionApp(registration.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const bool new_install = !config_->GetUpdaterPersistedData()


->GetProductVersion(registration.app_id)
.IsValid();
if (new_install) {
// Pre-register the app if there is no registration for it. This app
// registration is removed later if the app install encounters an error.
RegistrationRequest request = registration;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);
} else {
// Update lang/ap/iid.
RegistrationRequest request;
request.app_id = registration.app_id;
[Link] = language;
[Link] = [Link];
request.install_id = registration.install_id;
config_->GetUpdaterPersistedData()->RegisterApp(request);
}

std::multimap<std::string, base::RepeatingClosure>::iterator pos =


cancellation_callbacks_.emplace(registration.app_id, base::DoNothing());
pos->second = update_client_->Install(
registration.app_id,
base::BindOnce(
&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(), config_->GetUpdaterPersistedData(),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, client_install_data)}),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, install_data_index)}),
kInstallSourceTaggedMetainstaller, priority,
/*update_blocked=*/false, PolicySameVersionUpdate::kAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(), new_install, language,
state_update),
MakeUpdateClientCallback(std::move(callback))
.Then(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
const std::multimap<std::string,
base::RepeatingClosure>::iterator& pos) {
self->cancellation_callbacks_.erase(pos);
},
base::WrapRefCounted(this), pos)));
}

void UpdateServiceImplImpl::CancelInstalls(const std::string& app_id) {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << __func__;
auto [first, last] = cancellation_callbacks_.equal_range(app_id);
std::ranges::for_each(first, last, [](const auto& i) { [Link](); });
}

void UpdateServiceImplImpl::RunInstaller(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::RunInstallerImpl, this,
app_id, installer_path, install_args, install_data,
install_settings, language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::RunInstallerImpl(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, Priority::kForeground, true, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, true, language, state_update,
std::move(callback));
return;
}

if (!IsUpdaterOrCompanionApp(app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const base::Version pv =
config_->GetUpdaterPersistedData()->GetProductVersion(app_id);
const bool new_install = ![Link]();
AppInfo app_info(
GetUpdaterScope(), app_id,
config_->GetUpdaterPersistedData()->GetAP(app_id),
![Link]() ? language
: config_->GetUpdaterPersistedData()->GetLang(app_id),
config_->GetUpdaterPersistedData()->GetBrandCode(app_id), pv,
config_->GetUpdaterPersistedData()->GetExistenceCheckerPath(app_id));

// Pre-register the app in case there is no registration for it. This app
// registration is removed later if `new_install` is `true and if the app
// install encounters an error.
RegistrationRequest request;
request.app_id = app_id;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);

const base::Version installer_version([&install_settings]() -> std::string {


std::unique_ptr<base::Value> install_settings_deserialized =
JSONStringValueDeserializer(install_settings)
.Deserialize(
/*error_code=*/nullptr, /*error_message=*/nullptr);
if (install_settings_deserialized) {
const base::Value::Dict* install_settings_dict =
install_settings_deserialized->GetIfDict();
if (install_settings_dict) {
const std::string* installer_version_value =
install_settings_dict->FindString(kInstallerVersion);
if (installer_version_value) {
return *installer_version_value;
}
}
}

return {};
}());

// Create a task runner that:


// 1) has SequencedTaskRunner::CurrentDefaultHandle set, to run
// `state_update` callback.
// 2) may block, since `RunApplicationInstaller` blocks.
// 3) has `base::WithBaseSyncPrimitives()`, since `RunApplicationInstaller`
// waits on process.
auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](const AppInfo& app_info, const base::FilePath& installer_path,
const std::string& install_args, const std::string& install_data,
base::RepeatingCallback<void(const UpdateState&)> state_update,
bool usage_stats_enabled) {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
return InstallerResult(
{.category = update_client::ErrorCategory::kInstall,
.code = kErrorCreatingTempDir,
#if BUILDFLAG(IS_WIN)
.extra = HRESULTFromLastError()
#else
.extra = logging::GetLastSystemErrorCode()
#endif // BUILDFLAG(IS_WIN)
});
}

return RunApplicationInstaller(
app_info, installer_path, install_args,
WriteInstallerDataToTempFile(temp_dir.GetPath(), install_data),
usage_stats_enabled, kWaitForAppInstaller,
base::BindRepeating(
[](base::RepeatingCallback<void(const UpdateState&)>
state_update,
const std::string& app_id, int progress) {
VLOG(4) << "Install progress: " << progress;
UpdateState state;
state.app_id = app_id;
[Link] = UpdateState::State::kInstalling;
state.install_progress = progress;
state_update.Run(state);
},
state_update, app_info.app_id));
},
app_info, installer_path, install_args, install_data, state_update,
IsUpdaterOrCompanionApp(app_info.app_id) &&
config_->GetUpdaterPersistedData()->GetUsageStatsEnabled()),
base::BindOnce(
[](scoped_refptr<Configurator> config,
scoped_refptr<PersistedData> persisted_data,
scoped_refptr<update_client::UpdateClient> update_client,
base::Version installer_version,
base::RepeatingCallback<void(const UpdateState&)> state_update,
const std::string& app_id, const std::string& ap,
const std::string& brand, const std::string& language,
bool new_install, base::OnceCallback<void(Result)> callback,
const InstallerResult& result) {
// Final state update after installation completes.
UpdateState state;
state.app_id = app_id;
[Link] =
[Link] == update_client::ErrorCategory::kNone
? UpdateState::State::kUpdated
: UpdateState::State::kUpdateError;

const base::Version registered_version =


internal::GetRegisteredInstallerVersion(app_id);
if (registered_version.IsValid()) {
VLOG(1) << app_id << " registered_version " << registered_version
<< " overrides the original installer_version "
<< installer_version;
installer_version = registered_version;
}

if ([Link] == update_client::ErrorCategory::kNone &&


installer_version.IsValid()) {
persisted_data->SetProductVersion(app_id, installer_version);
config->GetPrefService()->CommitPendingWrite();
} else if (new_install) {
persisted_data->RemoveApp(app_id);
}

state.error_category = ToErrorCategory([Link]);
state.error_code = [Link];
state.extra_code1 = [Link];
state.installer_text = result.installer_text;
#if BUILDFLAG(IS_WIN)
if (state.installer_text.empty())
state.installer_text = internal::GetInstallerText(
state.error_category, state.error_code, state.extra_code1,
language);
#endif // BUILDFLAG(IS_WIN)
state.installer_cmd_line = result.installer_cmd_line;
state_update.Run(state);
VLOG(1) << app_id
<< " installation completed: " << state.error_code;

if (!persisted_data->GetEulaRequired()) {
// Send an install ping. In some environments the ping cannot be
// sent, so do not wait for it to be sent before calling back the
// client.
update_client::CrxComponent install_data;
install_data.ap = ap;
install_data.app_id = app_id;
install_data.lang = language;
install_data.brand = brand;
install_data.requires_network_encryption = false;
install_data.install_source = kInstallSourceOffline;
install_data.version = installer_version;
update_client->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result =
[Link] ==
update_client::ErrorCategory::kNone
? update_client::protocol_request::
kEventResultSuccess
: update_client::protocol_request::kEventResultError,
.error_category = [Link],
.error_code = [Link],
.extra_code1 = [Link]},
base::DoNothing());
}

std::move(callback).Run([Link] ==
update_client::ErrorCategory::kNone
? Result::kSuccess
: Result::kInstallFailed);
},
config_, config_->GetUpdaterPersistedData(), update_client_,
installer_version, state_update, app_info.app_id, app_info.ap,
app_info.brand, language, new_install, std::move(callback)));
}

bool UpdateServiceImplImpl::IsUpdateDisabledByPolicy(const std::string& app_id,


Priority priority,
bool is_install,
int& policy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

policy = kPolicyEnabled;

if (is_install) {
PolicyStatus<int> app_install_policy_status =
config_->GetPolicyService()->GetPolicyForAppInstalls(app_id);
if (app_install_policy_status) {
policy = app_install_policy_status.policy();
}
return app_install_policy_status &&
(policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
policy == kPolicyEnabledMachineOnly));
} else {
PolicyStatus<int> app_update_policy_status =
config_->GetPolicyService()->GetPolicyForAppUpdates(app_id);
if (app_update_policy_status) {
policy = app_update_policy_status.policy();
}
return app_update_policy_status &&
(policy == kPolicyDisabled ||
((policy == kPolicyManualUpdatesOnly) &&
(priority != Priority::kForeground)) ||
((policy == kPolicyAutomaticUpdatesOnly) &&
(priority == Priority::kForeground)));
}
}

void UpdateServiceImplImpl::HandleUpdateDisabledByPolicy(
const std::string& app_id,
int policy,
bool is_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

UpdateState update_state;
update_state.app_id = app_id;
update_state.state = UpdateService::UpdateState::State::kUpdateError;
update_state.error_category = UpdateService::ErrorCategory::kInstaller;
update_state.error_code =
is_install ? GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY
: policy != kPolicyAutomaticUpdatesOnly
? GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY
: GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL;
update_state.extra_code1 = 0;
#if BUILDFLAG(IS_WIN)
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)

base::BindPostTask(main_task_runner_, state_update).Run(update_state);
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kUpdateCheckFailed);
}

void UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::CheckForUpdate, update_client_, app_id,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(), kEmptyFlatMap,
kEmptyFlatMap,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::Update, update_client_, app_ids,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
PolicySameVersionUpdate policy_same_version_update,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

// The result from Install is only used for logging. Thus, arbitrarily pick
// the first non-success result to propagate.
auto barrier_callback = base::BarrierCallback<Result>(
app_ids.size(),
base::BindOnce([](const std::vector<Result>& results) {
auto error_it = std::ranges::find_if(
results, [](Result result) { return result != Result::kSuccess; });
return error_it == std::end(results) ? Result::kSuccess : *error_it;
}).Then(std::move(callback)));

for (const std::string& id : app_ids) {


main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, id,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
kInstallSourcePolicy, Priority::kBackground,
update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{}, state_update),
MakeUpdateClientCallback(barrier_callback)));
}
}

UpdateServiceImplImpl::~UpdateServiceImplImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
config_->GetPrefService()->SchedulePendingLossyWrites();
}

} // namespace updater
#include "chrome/updater/update_service_impl_impl.h"

#include <algorithm>
#include <map>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include "base/barrier_callback.h"
#include "base/barrier_closure.h"
#include "base/check_op.h"
#include "base/containers/contains.h"
#include "base/containers/flat_map.h"
#include "base/containers/queue.h"
#include "base/files/file_path.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/json/json_string_value_serializer.h"
#include "base/logging.h"
#include "base/memory/scoped_refptr.h"
#include "base/notreached.h"
#include "base/run_loop.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/bind_post_task.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "base/time/time.h"
#include "base/version.h"
#include "build/build_config.h"
#include "chrome/enterprise_companion/global_constants.h"
#include "chrome/updater/app/app_utils.h"
#include "chrome/updater/auto_run_on_os_upgrade_task.h"
#include "chrome/updater/branded_constants.h"
#include "chrome/updater/change_owners_task.h"
#include "chrome/updater/check_for_updates_task.h"
#include "chrome/updater/cleanup_task.h"
#include "chrome/updater/configurator.h"
#include "chrome/updater/constants.h"
#include "chrome/updater/handle_inconsistent_apps_task.h"
#include "chrome/updater/installer.h"
#include "chrome/updater/persisted_data.h"
#include "chrome/updater/policy/service.h"
#include "chrome/updater/prefs.h"
#include "chrome/updater/registration_data.h"
#include "chrome/updater/remove_uninstalled_apps_task.h"
#include "chrome/updater/update_block_check.h"
#include "chrome/updater/update_service.h"
#include "chrome/updater/update_usage_stats_task.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/updater_version.h"
#include "chrome/updater/util/util.h"
#include "components/policy/core/common/policy_types.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/protocol_definition.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"

#if BUILDFLAG(IS_MAC)
#include <sys/mount.h>
#endif // BUILDFLAG(IS_MAC)

#if BUILDFLAG(IS_WIN)
#include <winhttp.h>

#include "base/win/registry.h"
#include "chrome/updater/util/win_util.h"
#include "chrome/updater/win/ui/l10n_util.h"
#include "chrome/updater/win/ui/resources/[Link]"
#include "chrome/updater/win/ui/resources/updater_installer_strings.h"
#include "chrome/updater/win/win_constants.h"
#endif // BUILDFLAG(IS_WIN)

namespace updater {

// The functions below are various adaptors between |update_client| and


// |UpdateService| types.

namespace internal {
UpdateService::Result ToResult(update_client::Error error) {
switch (error) {
case update_client::Error::NONE:
return UpdateService::Result::kSuccess;
case update_client::Error::UPDATE_IN_PROGRESS:
return UpdateService::Result::kUpdateInProgress;
case update_client::Error::UPDATE_CANCELED:
return UpdateService::Result::kUpdateCanceled;
case update_client::Error::RETRY_LATER:
return UpdateService::Result::kRetryLater;
case update_client::Error::SERVICE_ERROR:
return UpdateService::Result::kServiceFailed;
case update_client::Error::UPDATE_CHECK_ERROR:
return UpdateService::Result::kUpdateCheckFailed;
case update_client::Error::CRX_NOT_FOUND:
return UpdateService::Result::kAppNotFound;
case update_client::Error::INVALID_ARGUMENT:
case update_client::Error::BAD_CRX_DATA_CALLBACK:
return UpdateService::Result::kInvalidArgument;
case update_client::Error::MAX_VALUE:
NOTREACHED();
}
}

void GetComponents(
scoped_refptr<PolicyService> policy_service,
crx_file::VerifierFormat verifier_format,
scoped_refptr<PersistedData> persisted_data,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
const std::string& install_source,
UpdateService::Priority priority,
bool update_blocked,
UpdateService::PolicySameVersionUpdate policy_same_version_update,
const std::vector<std::string>& ids,
base::OnceCallback<
void(const std::vector<std::optional<update_client::CrxComponent>>&)>
callback) {
VLOG(1) << __func__
<< ". Same version update: " << policy_same_version_update;
const bool is_foreground = priority == UpdateService::Priority::kForeground;
auto barrier_callback =
base::BarrierCallback<std::optional<update_client::CrxComponent>>(
[Link](),
base::BindOnce(
[](const std::vector<std::string>& ids,
const std::vector<std::optional<update_client::CrxComponent>>&
unordered) {
// Re-order the vector to match the order of `ids`.
std::vector<std::optional<update_client::CrxComponent>> ordered;
for (const auto& id : ids) {
auto it = std::ranges::find_if(
unordered,
[&id](std::optional<update_client::CrxComponent> v) {
return v && v->app_id == id;
});
ordered.push_back(it != [Link]() ? *it : std::nullopt);
}
return ordered;
},
ids)
.Then(std::move(callback)));
for (const auto& id : ids) {
base::MakeRefCounted<Installer>(
id,
[&app_client_install_data, &id] {
auto it = app_client_install_data.find(id);
return it != app_client_install_data.end() ? it->second : "";
}(),
[&app_install_data_index, &id] {
auto it = app_install_data_index.find(id);
return it != app_install_data_index.end() ? it->second : "";
}(),
install_source,
policy_service->GetTargetChannel(id).policy_or(std::string()),
policy_service->GetTargetVersionPrefix(id).policy_or(std::string()),
policy_service->IsRollbackToTargetVersionAllowed(id).policy_or(false),
[&policy_service, &id, &is_foreground, update_blocked] {
if (update_blocked) {
return true;
}
PolicyStatus<int> app_updates =
policy_service->GetPolicyForAppUpdates(id);
return app_updates &&
(app_updates.policy() == kPolicyDisabled ||
(!is_foreground &&
app_updates.policy() == kPolicyManualUpdatesOnly) ||
(is_foreground &&
app_updates.policy() == kPolicyAutomaticUpdatesOnly));
}(),
policy_same_version_update, persisted_data, verifier_format)
->MakeCrxComponent(
base::BindOnce([](update_client::CrxComponent component) {
return component;
}).Then(barrier_callback));
}
}
#if BUILDFLAG(IS_WIN)
namespace {

std::wstring GetTextForUpdateClientInstallError(int error_code,


const std::wstring& language) {
#define INSTALL_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE, L#error_code, \
language)

switch (error_code) {
INSTALL_SWITCH_ENTRY(update_client::InstallError::NONE);
INSTALL_SWITCH_ENTRY(update_client::InstallError::FINGERPRINT_WRITE_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::BAD_MANIFEST);
INSTALL_SWITCH_ENTRY(update_client::InstallError::GENERIC_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MOVE_FILES_ERROR);
INSTALL_SWITCH_ENTRY(update_client::InstallError::SET_PERMISSIONS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::INVALID_VERSION);
INSTALL_SWITCH_ENTRY(update_client::InstallError::VERSION_NOT_UPGRADED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::NO_DIR_COMPONENT_USER);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CLEAN_INSTALL_DIR_FAILED);
INSTALL_SWITCH_ENTRY(
update_client::InstallError::INSTALL_VERIFICATION_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::MISSING_INSTALL_PARAMS);
INSTALL_SWITCH_ENTRY(update_client::InstallError::LAUNCH_PROCESS_FAILED);
INSTALL_SWITCH_ENTRY(update_client::InstallError::CUSTOM_ERROR_BASE);
default:
return GetLocalizedStringF(IDS_GENERIC_INSTALL_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef INSTALL_SWITCH_ENTRY
}

std::wstring GetTextForDownloadError(int error, const std::wstring& language) {


#define DOWNLOAD_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE, L#error_code, \
language)

switch (error) {
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_URL);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::NO_HASH);
DOWNLOAD_SWITCH_ENTRY(
update_client::CrxDownloaderError::BITS_TOO_MANY_JOBS);
DOWNLOAD_SWITCH_ENTRY(update_client::CrxDownloaderError::GENERIC_ERROR);

case static_cast<int>(update_client::CrxDownloaderError::BAD_HASH):
return GetLocalizedString(IDS_DOWNLOAD_HASH_MISMATCH_BASE);

default:
return GetLocalizedStringF(IDS_GENERIC_DOWNLOAD_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef DOWNLOAD_SWITCH_ENTRY
}

std::wstring GetTextForUnpackError(int error, const std::wstring& language) {


#define UNPACK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE, L#error_code, \
language)
#define UNPACK_CACHING_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_UNPACK_CACHING_ERROR_BASE, L#error_code, \
language)

switch (error) {
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidParams);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kInvalidFile);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipPathError);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kUnzipFailed);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadManifest);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kBadExtension);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kIoError);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaVerificationFailure);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaBadCommands);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaUnsupportedCommand);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kDeltaOperationFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaPatchProcessFailure);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kDeltaMissingExistingFile);
UNPACK_SWITCH_ENTRY(
update_client::UnpackerError::kPuffinMissingPreviousCrx);
UNPACK_SWITCH_ENTRY(update_client::UnpackerError::kCrxCacheNotProvided);

UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToAddToCache);
UNPACK_CACHING_SWITCH_ENTRY(
update_client::UnpackerError::kFailedToCreateCacheDir);

default:
return GetLocalizedStringF(IDS_GENERIC_UNPACK_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef UNPACK_SWITCH_ENTRY
#undef UNPACK_CACHING_SWITCH_ENTRY
}

std::wstring GetTextForServiceError(int error, const std::wstring& language) {


#define SERVICE_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE, L#error_code, \
language)

switch (error) {
SERVICE_SWITCH_ENTRY(update_client::ServiceError::SERVICE_WAIT_FAILED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::UPDATE_DISABLED);
SERVICE_SWITCH_ENTRY(update_client::ServiceError::CHECK_FOR_UPDATE_ONLY);

case static_cast<int>(update_client::ServiceError::CANCELLED):
return GetLocalizedString(IDS_SERVICE_ERROR_CANCELLED_BASE, language);

default:
return GetLocalizedStringF(IDS_GENERIC_SERVICE_ERROR_BASE,
GetTextForSystemError(error), language);
}
#undef SERVICE_SWITCH_ENTRY
}

std::wstring GetTextForUpdateCheckError(int error,


const std::wstring& language) {
#define UPDATE_CHECK_SWITCH_ENTRY(error_code) \
case static_cast<int>(error_code): \
return GetLocalizedStringF(IDS_GENERIC_UPDATE_CHECK_ERROR_BASE, \
L#error_code, language)

switch (error) {
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::RESPONSE_NOT_TRUSTED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::MISSING_URLS);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::PARSE_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(
update_client::ProtocolError::UPDATE_RESPONSE_NOT_FOUND);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::URL_FETCHER_FAILED);
UPDATE_CHECK_SWITCH_ENTRY(update_client::ProtocolError::INVALID_APPID);

case static_cast<int>(update_client::ProtocolError::UNKNOWN_APPLICATION):
return GetLocalizedString(IDS_UNKNOWN_APPLICATION_BASE, language);

case static_cast<int>(update_client::ProtocolError::RESTRICTED_APPLICATION):
return GetLocalizedString(IDS_RESTRICTED_RESPONSE_FROM_SERVER_BASE,
language);

case static_cast<int>(update_client::ProtocolError::OS_NOT_SUPPORTED):
return GetLocalizedString(IDS_OS_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::HW_NOT_SUPPORTED):
return GetLocalizedString(IDS_HW_NOT_SUPPORTED_BASE, language);

case static_cast<int>(update_client::ProtocolError::NO_HASH):
return GetLocalizedString(IDS_NO_HASH_BASE, language);

case static_cast<int>(update_client::ProtocolError::UNSUPPORTED_PROTOCOL):
return GetLocalizedString(IDS_UNSUPPORTED_PROTOCOL_BASE, language);

case static_cast<int>(update_client::ProtocolError::INTERNAL):
return GetLocalizedString(IDS_INTERNAL_BASE, language);

// Http Status Code `401` Unauthorized.


case 401:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_UNAUTHORIZED_BASE,
language);

// Http Status Code `403` Forbidden.


case 403:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_FORBIDDEN_BASE, language);

// Http Status Code `407` Proxy Authentication Required.


case 407:
return GetLocalizedString(IDS_ERROR_HTTPSTATUS_PROXY_AUTH_REQUIRED_BASE,
language);

case HRESULT_FROM_WIN32(ERROR_WINHTTP_NAME_NOT_RESOLVED):
return GetLocalizedStringF(IDS_NO_NETWORK_PRESENT_ERROR_BASE,
GetExecutableRelativePath().value(), language);
default:
return GetLocalizedStringF(
IDS_GENERIC_UPDATE_CHECK_ERROR_BASE,
error >= 400 && error < 600
? base::UTF8ToWide(base::StringPrintf("HTTP %d", error))
: GetTextForSystemError(error),
language);
}
#undef UPDATE_CHECK_SWITCH_ENTRY
}

std::wstring GetTextForInstallerError(int error_code,


const std::wstring& language) {
#define POLICY_ERROR_SWITCH_ENTRY(error_code) \
case error_code: \
return GetLocalizedStringF(IDS_APP_INSTALL_DISABLED_BY_GROUP_POLICY_BASE, \
L#error_code, language)

switch (error_code) {
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY);
POLICY_ERROR_SWITCH_ENTRY(GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL);

case GOOPDATEINSTALL_E_FILENAME_INVALID:
return GetLocalizedString(IDS_INVALID_INSTALLER_FILENAME_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_FAILED_START:
return GetLocalizedString(IDS_INSTALLER_FAILED_TO_START_BASE, language);

case GOOPDATEINSTALL_E_INSTALLER_TIMED_OUT:
return GetLocalizedString(IDS_INSTALLER_TIMED_OUT_BASE, language);

case GOOPDATEINSTALL_E_INSTALL_ALREADY_RUNNING:
return GetLocalizedStringF(
IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(ERROR_INSTALL_ALREADY_RUNNING), language);

case ERROR_SUCCESS_REBOOT_INITIATED:
case ERROR_SUCCESS_REBOOT_REQUIRED:
case ERROR_SUCCESS_RESTART_REQUIRED:
return GetLocalizedStringF(IDS_INSTALL_REBOOT_BASE,
GetTextForSystemError(error_code), language);

default:
return GetLocalizedStringF(IDS_GENERIC_INSTALLER_ERROR_BASE,
GetTextForSystemError(error_code), language);
}
#undef POLICY_ERROR_SWITCH_ENTRY
}

} // namespace

std::string GetInstallerText(UpdateService::ErrorCategory error_category,


int error_code,
int extra_code,
const std::string& language) {
if (!error_code) {
return {};
}
const std::wstring language_w = base::UTF8ToWide(language);
return base::WideToUTF8(base::StrCat(
{[&] {
switch (error_category) {
case UpdateService::ErrorCategory::kInstall:
return GetTextForUpdateClientInstallError(error_code, language_w);
case UpdateService::ErrorCategory::kDownload:
return GetTextForDownloadError(error_code, language_w);
case UpdateService::ErrorCategory::kUnpack:
return GetTextForUnpackError(error_code, language_w);
case UpdateService::ErrorCategory::kService:
return GetTextForServiceError(error_code, language_w);
case UpdateService::ErrorCategory::kUpdateCheck:
return GetTextForUpdateCheckError(error_code, language_w);
case UpdateService::ErrorCategory::kInstaller:
return GetTextForInstallerError(error_code, language_w);
default:
LOG(ERROR) << "Unknown error category: " << error_category;
return std::wstring();
}
}(),
[&] {
if (!extra_code) {
return std::wstring();
}
return base::StrCat(
{L"\n", GetLocalizedStringF(IDS_EXTRA_CODE_BASE,
base::UTF8ToWide(base::StringPrintf(
"%#x", extra_code)),
language_w)});
}()}));
}
#endif // BUILDFLAG(IS_WIN)

base::Version GetRegisteredInstallerVersion(const std::string& app_id) {


#if BUILDFLAG(IS_WIN)
std::wstring pv;
return base::win::RegKey(UpdaterScopeToHKeyRoot(GetUpdaterScope()),
GetAppClientsKey(app_id).c_str(), Wow6432(KEY_READ))
.ReadValue(kRegValuePV, &pv) == ERROR_SUCCESS
? base::Version(base::WideToUTF8(pv))
: base::Version();
#else // BUILDFLAG(IS_WIN)
return {};
#endif // BUILDFLAG(IS_WIN)
}

} // namespace internal

namespace {

constexpr base::flat_map<std::string, std::string> kEmptyFlatMap;

update_client::Callback MakeUpdateClientCallback(
base::OnceCallback<void(UpdateService::Result)> callback) {
return base::BindOnce(
[](base::OnceCallback<void(UpdateService::Result)> callback,
update_client::Error error) {
std::move(callback).Run(internal::ToResult(error));
},
std::move(callback));
}

UpdateService::UpdateState::State ToUpdateState(
update_client::ComponentState component_state) {
switch (component_state) {
case update_client::ComponentState::kNew:
return UpdateService::UpdateState::State::kNotStarted;

case update_client::ComponentState::kChecking:
return UpdateService::UpdateState::State::kCheckingForUpdates;

case update_client::ComponentState::kDownloading:
case update_client::ComponentState::kDownloadingDiff:
return UpdateService::UpdateState::State::kDownloading;

case update_client::ComponentState::kCanUpdate:
return UpdateService::UpdateState::State::kUpdateAvailable;

case update_client::ComponentState::kUpdating:
case update_client::ComponentState::kUpdatingDiff:
return UpdateService::UpdateState::State::kInstalling;

case update_client::ComponentState::kUpdated:
return UpdateService::UpdateState::State::kUpdated;

case update_client::ComponentState::kUpToDate:
return UpdateService::UpdateState::State::kNoUpdate;

case update_client::ComponentState::kUpdateError:
return UpdateService::UpdateState::State::kUpdateError;

case update_client::ComponentState::kRun:
case update_client::ComponentState::kLastStatus:
NOTREACHED();
}
}

UpdateService::ErrorCategory ToErrorCategory(
update_client::ErrorCategory error_category) {
switch (error_category) {
case update_client::ErrorCategory::kNone:
return UpdateService::ErrorCategory::kNone;
case update_client::ErrorCategory::kDownload:
return UpdateService::ErrorCategory::kDownload;
case update_client::ErrorCategory::kUnpack:
return UpdateService::ErrorCategory::kUnpack;
case update_client::ErrorCategory::kInstall:
return UpdateService::ErrorCategory::kInstall;
case update_client::ErrorCategory::kService:
return UpdateService::ErrorCategory::kService;
case update_client::ErrorCategory::kUpdateCheck:
return UpdateService::ErrorCategory::kUpdateCheck;
case update_client::ErrorCategory::kInstaller:
return UpdateService::ErrorCategory::kInstaller;
}
}
update_client::UpdateClient::CrxStateChangeCallback
MakeUpdateClientCrxStateChangeCallback(
scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data,
const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)> callback) {
return base::BindRepeating(
[](scoped_refptr<update_client::Configurator> config,
scoped_refptr<PersistedData> persisted_data, const bool new_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateService::UpdateState&)>
callback,
const update_client::CrxUpdateItem& crx_update_item) {
UpdateService::UpdateState update_state;
update_state.app_id = crx_update_item.id;
update_state.state = ToUpdateState(crx_update_item.state);
update_state.next_version = crx_update_item.next_version;
update_state.downloaded_bytes = crx_update_item.downloaded_bytes;
update_state.total_bytes = crx_update_item.total_bytes;
update_state.install_progress = crx_update_item.install_progress;
update_state.error_category =
ToErrorCategory(crx_update_item.error_category);
update_state.error_code = crx_update_item.error_code;
update_state.extra_code1 = crx_update_item.extra_code1;
if (crx_update_item.installer_result) {
update_state.installer_cmd_line =
crx_update_item.installer_result->installer_cmd_line;
update_state.installer_text =
crx_update_item.installer_result->installer_text;
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
UpdateService::ErrorCategory::kInstaller,
update_state.error_code, update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)
}

if (update_state.state == UpdateService::UpdateState::State::kUpdated ||
update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate) {
#if BUILDFLAG(IS_WIN)
if (update_state.installer_text.empty())
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)

// If a new install encounters an error, the AppId registered in


// `UpdateServiceImplImpl::Install` needs to be removed here.
// Otherwise the updater may remain installed even if there are no
// other apps to manage, and try to update the app even though the app
// was not installed.
if (new_install &&
(update_state.state ==
UpdateService::UpdateState::State::kUpdateError ||
update_state.state ==
UpdateService::UpdateState::State::kNoUpdate)) {
persisted_data->RemoveApp(update_state.app_id);
}

// Commit the prefs values written by |update_client| when the


// update has completed, such as `pv` and `fingerprint`.
config->GetPrefService()->CommitPendingWrite();
}

CHECK(!update_state.app_id.empty());
CHECK_NE(update_state.state,
UpdateService::UpdateState::State::kUnknown);
[Link](update_state);
},
config, persisted_data, new_install, language, callback);
}

bool IsPathOnReadOnlyMount(const base::FilePath& path) {


if ([Link]()) {
return false;
}
#if BUILDFLAG(IS_MAC)
struct statfs fsinfo = {};
if (statfs([Link]().c_str(), &fsinfo) == -1) {
LOG(ERROR) << "Failed to stat: " << path;
return false;
}
return fsinfo.f_flags & MNT_RDONLY;
#else
return false;
#endif // BUILDFLAG(IS_MAC)
}

void FetchPoliciesDone(
base::OnceCallback<void(base::OnceCallback<void(UpdateService::Result)>)>
fetch_policies_done,
base::OnceCallback<void(UpdateService::Result)> callback,
int result) {
if (result != kErrorOk) {
LOG(ERROR) << "FetchPolicies failed: " << result;
// Ignore policy fetch failures and fall through.
}

std::move(fetch_policies_done).Run(std::move(callback));
}

} // namespace

UpdateServiceImplImpl::UpdateServiceImplImpl(scoped_refptr<Configurator> config)
: config_(config),
main_task_runner_(base::SequencedTaskRunner::GetCurrentDefault()),
update_client_(update_client::UpdateClientFactory(config)) {}

void UpdateServiceImplImpl::GetVersion(
base::OnceCallback<void(const base::Version&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(std::move(callback), base::Version(kUpdaterVersion)));
}

void UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA(
base::OnceClosure callback,
bool is_cloud_managed) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!is_cloud_managed) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(callback));
return;
}

VLOG(1) << "Starting an OTA installation of the enterprise companion app.";


RegistrationRequest registration;
registration.app_id = enterprise_companion::kCompanionAppId;
[Link] = base::Version("[Link]");
RegisterApp(
registration,
base::BindOnce([](int registration_result) {})
.Then(base::BindPostTask(
main_task_runner_,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, enterprise_companion::kCompanionAppId,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
kEmptyFlatMap, kEmptyFlatMap,
kInstallSourcePolicy, Priority::kForeground,
/*update_blocked=*/false,
PolicySameVersionUpdate::kNotAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{},
/*callback=*/base::DoNothing()),
MakeUpdateClientCallback(base::BindOnce([](Result result) {
VLOG(1)
<< "OTA installation of the "
"enterprise companion app "
"completed with result: "
<< result;
}).Then(std::move(callback)))))));
}

void UpdateServiceImplImpl::FetchPolicies(
policy::PolicyFetchReason reason,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (GetUpdaterScope() == UpdaterScope::kUser) {
VLOG(2) << "Policy fetch skipped for user updater.";
std::move(callback).Run(0);
} else {
if (config_->GetPolicyService()->IsCecaExperimentEnabled() &&
!config_->GetUpdaterPersistedData()
->GetProductVersion(enterprise_companion::kCompanionAppId)
.IsValid()) {
config_->GetPolicyService()->IsCloudManaged(base::BindOnce(
&UpdateServiceImplImpl::MaybeInstallEnterpriseCompanionAppOTA,
base::WrapRefCounted(this),
base::BindOnce(&PolicyService::FetchPolicies,
config_->GetPolicyService(), reason,
std::move(callback))));
} else {
config_->GetPolicyService()->FetchPolicies(reason, std::move(callback));
}
}
}

void UpdateServiceImplImpl::RegisterApp(
const RegistrationRequest& request,
base::OnceCallback<void(int)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (IsPathOnReadOnlyMount(request.existence_checker_path)) {
VLOG(1) << "Existence check path " << request.existence_checker_path
<< " is on read-only file system. Registration of "
<< request.app_id << " is skipped.";
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationError));
return;
}

if (!IsUpdaterOrCompanionApp(request.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}
bool send_event = !config_->GetUpdaterPersistedData()
->GetProductVersion(request.app_id)
.IsValid() &&
[Link]() &&
[Link] > base::Version("0") &&
!config_->GetUpdaterPersistedData()->GetEulaRequired();
config_->GetUpdaterPersistedData()->RegisterApp(request);
if (send_event) {
update_client::CrxComponent install_data;
install_data.ap = [Link];
install_data.app_id = request.app_id;
install_data.brand = request.brand_code;
install_data.lang = [Link];
install_data.requires_network_encryption = false;
install_data.version = [Link];
update_client_->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result = update_client::protocol_request::kEventResultSuccess},
base::BindOnce([](update_client::Error error) {
// Ignore event ping errors; registration has been successful.
}).Then(base::BindOnce(std::move(callback), kRegistrationSuccess)));
return;
}
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), kRegistrationSuccess));
}

void UpdateServiceImplImpl::GetAppStates(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(&UpdateServiceImplImpl::GetAppStatesImpl, this,
std::move(callback)));
}

void UpdateServiceImplImpl::GetAppStatesImpl(
base::OnceCallback<void(const std::vector<AppState>&)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

scoped_refptr<PersistedData> persisted_data =
config_->GetUpdaterPersistedData();
std::vector<std::string> app_ids = persisted_data->GetAppIds();
std::vector<AppState> apps;
for (const std::string& app_id : app_ids) {
AppState app_state;
app_state.app_id = app_id;
app_state.version = persisted_data->GetProductVersion(app_id);
app_state.version_path = persisted_data->GetProductVersionPath(app_id);
app_state.version_key = persisted_data->GetProductVersionKey(app_id);
app_state.ap = persisted_data->GetAP(app_id);
app_state.ap_path = persisted_data->GetAPPath(app_id);
app_state.ap_key = persisted_data->GetAPKey(app_id);
app_state.brand_code = persisted_data->GetBrandCode(app_id);
app_state.brand_path = persisted_data->GetBrandPath(app_id);
app_state.ecp = persisted_data->GetExistenceCheckerPath(app_id);
app_state.cohort = persisted_data->GetCohort(app_id);
apps.push_back(app_state);
}
main_task_runner_->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), std::move(apps)));
}

void UpdateServiceImplImpl::RunPeriodicTasks(base::OnceClosure callback) {


VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

config_->GetUpdaterPersistedData()->SetLastStarted(
base::Time::NowFromSystemTime());
VLOG(1) << "last_started updated.";

// The installer should make an updater registration, but in case it halts


// before it does, synthesize a registration if necessary here.
const base::Version registered_updater_version =
config_->GetUpdaterPersistedData()->GetProductVersion(kUpdaterAppId);
if (!registered_updater_version.IsValid() ||
base::Version(kUpdaterVersion) > registered_updater_version) {
RegistrationRequest updater_request;
updater_request.app_id = kUpdaterAppId;
updater_request.version = base::Version(kUpdaterVersion);
RegisterApp(updater_request, base::DoNothing());
}

std::vector<base::OnceCallback<void(base::OnceClosure)>> new_tasks;
new_tasks.push_back(
base::BindOnce(&HandleInconsistentAppsTask::Run,
base::MakeRefCounted<HandleInconsistentAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(
base::BindOnce(&RemoveUninstalledAppsTask::Run,
base::MakeRefCounted<RemoveUninstalledAppsTask>(
config_, GetUpdaterScope())));
new_tasks.push_back(base::BindOnce(
&UpdateUsageStatsTask::Run,
base::MakeRefCounted<UpdateUsageStatsTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(MakeChangeOwnersTask(config_->GetUpdaterPersistedData(),
GetUpdaterScope()));

new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> update_service_impl,
base::OnceClosure callback) {
update_service_impl->FetchPolicies(
policy::PolicyFetchReason::kScheduled,
base::BindOnce(
[](base::OnceClosure callback, int /* ignore_result */) {
std::move(callback).Run();
},
std::move(callback)));
},
base::WrapRefCounted(this)));
new_tasks.push_back(
base::BindOnce(&CheckForUpdatesTask::Run,
base::MakeRefCounted<CheckForUpdatesTask>(
config_, GetUpdaterScope(),
/*task_name=*/"UpdateAll",
base::BindOnce(&UpdateServiceImplImpl::UpdateAll, this,
base::DoNothing()))));
new_tasks.push_back(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
base::OnceClosure callback) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE,
base::BindOnce(
&UpdateServiceImplImpl::ForceInstall, self, base::DoNothing(),
base::BindOnce(
[](base::OnceClosure closure,
UpdateService::Result result) {
VLOG(0) << "ForceInstall task complete: " << result;
std::move(closure).Run();
},
std::move(callback))));
},
base::WrapRefCounted(this)));
new_tasks.push_back(base::BindOnce(
&AutoRunOnOsUpgradeTask::Run,
base::MakeRefCounted<AutoRunOnOsUpgradeTask>(
GetUpdaterScope(), config_->GetUpdaterPersistedData())));
new_tasks.push_back(base::BindOnce(
&CleanupTask::Run, base::MakeRefCounted<CleanupTask>(GetUpdaterScope())));
const auto barrier_closure =
base::BarrierClosure(new_tasks.size(), std::move(callback));
for (auto& task : new_tasks) {
tasks_.push(base::BindOnce(std::move(task),
barrier_closure.Then(base::BindRepeating(
&UpdateServiceImplImpl::TaskDone, this))));
}

if (tasks_.size() == new_tasks.size()) {
TaskStart();
}
}

void UpdateServiceImplImpl::TaskStart() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!tasks_.empty()) {
main_task_runner_->PostDelayedTask(FROM_HERE, std::move(tasks_.front()),
config_->InitialDelay());
}
}

void UpdateServiceImplImpl::TaskDone() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
tasks_.pop();
TaskStart();
}

void UpdateServiceImplImpl::ForceInstall(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

PolicyStatus<std::vector<std::string>> force_install_apps_status =
config_->GetPolicyService()->GetForceInstallApps();
if (!force_install_apps_status) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}
std::vector<std::string> force_install_apps =
force_install_apps_status.policy();
CHECK(!force_install_apps.empty());

std::vector<std::string> installed_app_ids =
config_->GetUpdaterPersistedData()->GetAppIds();
std::ranges::sort(force_install_apps);
std::ranges::sort(installed_app_ids);

std::vector<std::string> app_ids_to_install;
std::ranges::set_difference(force_install_apps, installed_app_ids,
std::back_inserter(app_ids_to_install));
if (app_ids_to_install.empty()) {
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kSuccess);
return;
}
VLOG(1) << __func__ << ": app_ids_to_install: "
<< base::JoinString(app_ids_to_install, " ");

ShouldBlockUpdateForMeteredNetwork(
Priority::kBackground,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork,
this, app_ids_to_install, kEmptyFlatMap, kEmptyFlatMap,
UpdateService::PolicySameVersionUpdate::kNotAllowed, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::CheckForUpdate(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::CheckForUpdateImpl, this,
app_id, priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::CheckForUpdateImpl(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
VLOG(1) << __func__ << ": App not registered: " << app_id;
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork,
this, app_id, priority, policy_same_version_update, language,
state_update, std::move(callback)));
}

void UpdateServiceImplImpl::Update(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kScheduled,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::UpdateImpl,
this, app_id, install_data_index,
priority, policy_same_version_update,
language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::UpdateImpl(
const std::string& app_id,
const std::string& install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

if (!config_->GetUpdaterPersistedData()
->GetProductVersion(app_id)
.IsValid()) {
std::move(callback).Run(Result::kInvalidArgument);
return;
}

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, priority, false, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, false, language, state_update,
std::move(callback));
return;
}
ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
std::vector<std::string>{app_id}, kEmptyFlatMap,
base::flat_map<std::string, std::string>(
{std::make_pair(app_id, install_data_index)}),
priority, policy_same_version_update, language, state_update,
std::move(callback)));
}

void UpdateServiceImplImpl::UpdateAll(
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

auto app_ids = config_->GetUpdaterPersistedData()->GetAppIds();

CHECK(base::Contains(
app_ids, base::ToLowerASCII(kUpdaterAppId),
static_cast<std::string (*)(std::string_view)>(&base::ToLowerASCII)));

const Priority priority = Priority::kBackground;


ShouldBlockUpdateForMeteredNetwork(
priority,
base::BindOnce(
&UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork, this,
app_ids, kEmptyFlatMap, kEmptyFlatMap, priority,
UpdateService::PolicySameVersionUpdate::kNotAllowed,
/*language=*/"", state_update,
base::BindOnce(
[](base::OnceCallback<void(Result)> callback,
scoped_refptr<PersistedData> persisted_data, Result result) {
if (result == Result::kSuccess) {
persisted_data->SetLastChecked(
base::Time::NowFromSystemTime());
VLOG(1) << "last_checked updated.";
}
std::move(callback).Run(result);
},
std::move(callback), config_->GetUpdaterPersistedData())));
}

void UpdateServiceImplImpl::Install(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::InstallImpl,
this, registration, client_install_data,
install_data_index, priority, language,
state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::InstallImpl(
const RegistrationRequest& registration,
const std::string& client_install_data,
const std::string& install_data_index,
Priority priority,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(registration.app_id, priority, true, policy)) {
HandleUpdateDisabledByPolicy(registration.app_id, policy, true, language,
state_update, std::move(callback));
return;
}
if (!IsUpdaterOrCompanionApp(registration.app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const bool new_install = !config_->GetUpdaterPersistedData()


->GetProductVersion(registration.app_id)
.IsValid();
if (new_install) {
// Pre-register the app if there is no registration for it. This app
// registration is removed later if the app install encounters an error.
RegistrationRequest request = registration;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);
} else {
// Update lang/ap/iid.
RegistrationRequest request;
request.app_id = registration.app_id;
[Link] = language;
[Link] = [Link];
request.install_id = registration.install_id;
config_->GetUpdaterPersistedData()->RegisterApp(request);
}

std::multimap<std::string, base::RepeatingClosure>::iterator pos =


cancellation_callbacks_.emplace(registration.app_id, base::DoNothing());
pos->second = update_client_->Install(
registration.app_id,
base::BindOnce(
&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(), config_->GetUpdaterPersistedData(),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, client_install_data)}),
base::flat_map<std::string, std::string>(
{std::make_pair(registration.app_id, install_data_index)}),
kInstallSourceTaggedMetainstaller, priority,
/*update_blocked=*/false, PolicySameVersionUpdate::kAllowed),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(), new_install, language,
state_update),
MakeUpdateClientCallback(std::move(callback))
.Then(base::BindOnce(
[](scoped_refptr<UpdateServiceImplImpl> self,
const std::multimap<std::string,
base::RepeatingClosure>::iterator& pos) {
self->cancellation_callbacks_.erase(pos);
},
base::WrapRefCounted(this), pos)));
}

void UpdateServiceImplImpl::CancelInstalls(const std::string& app_id) {


DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
VLOG(1) << __func__;
auto [first, last] = cancellation_callbacks_.equal_range(app_id);
std::ranges::for_each(first, last, [](const auto& i) { [Link](); });
}

void UpdateServiceImplImpl::RunInstaller(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

base::MakeRefCounted<HandleInconsistentAppsTask>(config_, GetUpdaterScope())
->Run(base::BindOnce(
&UpdateServiceImplImpl::FetchPolicies, this,
policy::PolicyFetchReason::kUserRequest,
base::BindOnce(
&FetchPoliciesDone,
base::BindOnce(&UpdateServiceImplImpl::RunInstallerImpl, this,
app_id, installer_path, install_args, install_data,
install_settings, language, state_update),
std::move(callback))));
}

void UpdateServiceImplImpl::RunInstallerImpl(
const std::string& app_id,
const base::FilePath& installer_path,
const std::string& install_args,
const std::string& install_data,
const std::string& install_settings,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
VLOG(1) << __func__ << ": " << app_id << ": " << installer_path << ": "
<< install_args << ": " << install_data << ": " << install_settings;
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

int policy = kPolicyEnabled;


if (IsUpdateDisabledByPolicy(app_id, Priority::kForeground, true, policy)) {
HandleUpdateDisabledByPolicy(app_id, policy, true, language, state_update,
std::move(callback));
return;
}

if (!IsUpdaterOrCompanionApp(app_id)) {
config_->GetUpdaterPersistedData()->SetHadApps();
}

const base::Version pv =
config_->GetUpdaterPersistedData()->GetProductVersion(app_id);
const bool new_install = ![Link]();
AppInfo app_info(
GetUpdaterScope(), app_id,
config_->GetUpdaterPersistedData()->GetAP(app_id),
![Link]() ? language
: config_->GetUpdaterPersistedData()->GetLang(app_id),
config_->GetUpdaterPersistedData()->GetBrandCode(app_id), pv,
config_->GetUpdaterPersistedData()->GetExistenceCheckerPath(app_id));

// Pre-register the app in case there is no registration for it. This app
// registration is removed later if `new_install` is `true and if the app
// install encounters an error.
RegistrationRequest request;
request.app_id = app_id;
[Link] = language;
config_->GetUpdaterPersistedData()->RegisterApp(request);

const base::Version installer_version([&install_settings]() -> std::string {


std::unique_ptr<base::Value> install_settings_deserialized =
JSONStringValueDeserializer(install_settings)
.Deserialize(
/*error_code=*/nullptr, /*error_message=*/nullptr);
if (install_settings_deserialized) {
const base::Value::Dict* install_settings_dict =
install_settings_deserialized->GetIfDict();
if (install_settings_dict) {
const std::string* installer_version_value =
install_settings_dict->FindString(kInstallerVersion);
if (installer_version_value) {
return *installer_version_value;
}
}
}

return {};
}());

// Create a task runner that:


// 1) has SequencedTaskRunner::CurrentDefaultHandle set, to run
// `state_update` callback.
// 2) may block, since `RunApplicationInstaller` blocks.
// 3) has `base::WithBaseSyncPrimitives()`, since `RunApplicationInstaller`
// waits on process.
auto task_runner = base::ThreadPool::CreateSequencedTaskRunner(
{base::MayBlock(), base::WithBaseSyncPrimitives(),
base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN});
task_runner->PostTaskAndReplyWithResult(
FROM_HERE,
base::BindOnce(
[](const AppInfo& app_info, const base::FilePath& installer_path,
const std::string& install_args, const std::string& install_data,
base::RepeatingCallback<void(const UpdateState&)> state_update,
bool usage_stats_enabled) {
base::ScopedTempDir temp_dir;
if (!temp_dir.CreateUniqueTempDir()) {
return InstallerResult(
{.category = update_client::ErrorCategory::kInstall,
.code = kErrorCreatingTempDir,
#if BUILDFLAG(IS_WIN)
.extra = HRESULTFromLastError()
#else
.extra = logging::GetLastSystemErrorCode()
#endif // BUILDFLAG(IS_WIN)
});
}

return RunApplicationInstaller(
app_info, installer_path, install_args,
WriteInstallerDataToTempFile(temp_dir.GetPath(), install_data),
usage_stats_enabled, kWaitForAppInstaller,
base::BindRepeating(
[](base::RepeatingCallback<void(const UpdateState&)>
state_update,
const std::string& app_id, int progress) {
VLOG(4) << "Install progress: " << progress;
UpdateState state;
state.app_id = app_id;
[Link] = UpdateState::State::kInstalling;
state.install_progress = progress;
state_update.Run(state);
},
state_update, app_info.app_id));
},
app_info, installer_path, install_args, install_data, state_update,
IsUpdaterOrCompanionApp(app_info.app_id) &&
config_->GetUpdaterPersistedData()->GetUsageStatsEnabled()),
base::BindOnce(
[](scoped_refptr<Configurator> config,
scoped_refptr<PersistedData> persisted_data,
scoped_refptr<update_client::UpdateClient> update_client,
base::Version installer_version,
base::RepeatingCallback<void(const UpdateState&)> state_update,
const std::string& app_id, const std::string& ap,
const std::string& brand, const std::string& language,
bool new_install, base::OnceCallback<void(Result)> callback,
const InstallerResult& result) {
// Final state update after installation completes.
UpdateState state;
state.app_id = app_id;
[Link] =
[Link] == update_client::ErrorCategory::kNone
? UpdateState::State::kUpdated
: UpdateState::State::kUpdateError;

const base::Version registered_version =


internal::GetRegisteredInstallerVersion(app_id);
if (registered_version.IsValid()) {
VLOG(1) << app_id << " registered_version " << registered_version
<< " overrides the original installer_version "
<< installer_version;
installer_version = registered_version;
}

if ([Link] == update_client::ErrorCategory::kNone &&


installer_version.IsValid()) {
persisted_data->SetProductVersion(app_id, installer_version);
config->GetPrefService()->CommitPendingWrite();
} else if (new_install) {
persisted_data->RemoveApp(app_id);
}

state.error_category = ToErrorCategory([Link]);
state.error_code = [Link];
state.extra_code1 = [Link];
state.installer_text = result.installer_text;
#if BUILDFLAG(IS_WIN)
if (state.installer_text.empty())
state.installer_text = internal::GetInstallerText(
state.error_category, state.error_code, state.extra_code1,
language);
#endif // BUILDFLAG(IS_WIN)
state.installer_cmd_line = result.installer_cmd_line;
state_update.Run(state);
VLOG(1) << app_id
<< " installation completed: " << state.error_code;

if (!persisted_data->GetEulaRequired()) {
// Send an install ping. In some environments the ping cannot be
// sent, so do not wait for it to be sent before calling back the
// client.
update_client::CrxComponent install_data;
install_data.ap = ap;
install_data.app_id = app_id;
install_data.lang = language;
install_data.brand = brand;
install_data.requires_network_encryption = false;
install_data.install_source = kInstallSourceOffline;
install_data.version = installer_version;
update_client->SendPing(
install_data,
{.event_type = update_client::protocol_request::kEventInstall,
.result =
[Link] ==
update_client::ErrorCategory::kNone
? update_client::protocol_request::
kEventResultSuccess
: update_client::protocol_request::kEventResultError,
.error_category = [Link],
.error_code = [Link],
.extra_code1 = [Link]},
base::DoNothing());
}

std::move(callback).Run([Link] ==
update_client::ErrorCategory::kNone
? Result::kSuccess
: Result::kInstallFailed);
},
config_, config_->GetUpdaterPersistedData(), update_client_,
installer_version, state_update, app_info.app_id, app_info.ap,
app_info.brand, language, new_install, std::move(callback)));
}

bool UpdateServiceImplImpl::IsUpdateDisabledByPolicy(const std::string& app_id,


Priority priority,
bool is_install,
int& policy) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

policy = kPolicyEnabled;

if (is_install) {
PolicyStatus<int> app_install_policy_status =
config_->GetPolicyService()->GetPolicyForAppInstalls(app_id);
if (app_install_policy_status) {
policy = app_install_policy_status.policy();
}
return app_install_policy_status &&
(policy == kPolicyDisabled || (config_->IsPerUserInstall() &&
policy == kPolicyEnabledMachineOnly));
} else {
PolicyStatus<int> app_update_policy_status =
config_->GetPolicyService()->GetPolicyForAppUpdates(app_id);
if (app_update_policy_status) {
policy = app_update_policy_status.policy();
}
return app_update_policy_status &&
(policy == kPolicyDisabled ||
((policy == kPolicyManualUpdatesOnly) &&
(priority != Priority::kForeground)) ||
((policy == kPolicyAutomaticUpdatesOnly) &&
(priority == Priority::kForeground)));
}
}

void UpdateServiceImplImpl::HandleUpdateDisabledByPolicy(
const std::string& app_id,
int policy,
bool is_install,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

UpdateState update_state;
update_state.app_id = app_id;
update_state.state = UpdateService::UpdateState::State::kUpdateError;
update_state.error_category = UpdateService::ErrorCategory::kInstaller;
update_state.error_code =
is_install ? GOOPDATE_E_APP_INSTALL_DISABLED_BY_POLICY
: policy != kPolicyAutomaticUpdatesOnly
? GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY
: GOOPDATE_E_APP_UPDATE_DISABLED_BY_POLICY_MANUAL;
update_state.extra_code1 = 0;
#if BUILDFLAG(IS_WIN)
update_state.installer_text = internal::GetInstallerText(
update_state.error_category, update_state.error_code,
update_state.extra_code1, language);
#endif // BUILDFLAG(IS_WIN)

base::BindPostTask(main_task_runner_, state_update).Run(update_state);
base::BindPostTask(main_task_runner_, std::move(callback))
.Run(UpdateService::Result::kUpdateCheckFailed);
}

void UpdateServiceImplImpl::OnShouldBlockCheckForUpdateForMeteredNetwork(
const std::string& app_id,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::CheckForUpdate, update_client_, app_id,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(), kEmptyFlatMap,
kEmptyFlatMap,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockUpdateForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
Priority priority,
PolicySameVersionUpdate policy_same_version_update,
const std::string& language,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
&update_client::UpdateClient::Update, update_client_, app_ids,
base::BindOnce(&internal::GetComponents, config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
priority == UpdateService::Priority::kForeground
? kInstallSourceOnDemand
: "",
priority, update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false, language, state_update),
priority == Priority::kForeground,
MakeUpdateClientCallback(std::move(callback))));
}

void UpdateServiceImplImpl::OnShouldBlockForceInstallForMeteredNetwork(
const std::vector<std::string>& app_ids,
const base::flat_map<std::string, std::string>& app_client_install_data,
const base::flat_map<std::string, std::string>& app_install_data_index,
PolicySameVersionUpdate policy_same_version_update,
base::RepeatingCallback<void(const UpdateState&)> state_update,
base::OnceCallback<void(Result)> callback,
bool update_blocked) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

// The result from Install is only used for logging. Thus, arbitrarily pick
// the first non-success result to propagate.
auto barrier_callback = base::BarrierCallback<Result>(
app_ids.size(),
base::BindOnce([](const std::vector<Result>& results) {
auto error_it = std::ranges::find_if(
results, [](Result result) { return result != Result::kSuccess; });
return error_it == std::end(results) ? Result::kSuccess : *error_it;
}).Then(std::move(callback)));

for (const std::string& id : app_ids) {


main_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(
base::IgnoreResult(&update_client::UpdateClient::Install),
update_client_, id,
base::BindOnce(&internal::GetComponents,
config_->GetPolicyService(),
config_->GetCrxVerifierFormat(),
config_->GetUpdaterPersistedData(),
app_client_install_data, app_install_data_index,
kInstallSourcePolicy, Priority::kBackground,
update_blocked, policy_same_version_update),
MakeUpdateClientCrxStateChangeCallback(
config_, config_->GetUpdaterPersistedData(),
/*new_install=*/false,
/*language=*/{}, state_update),
MakeUpdateClientCallback(barrier_callback)));
}
}

UpdateServiceImplImpl::~UpdateServiceImplImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
config_->GetPrefService()->SchedulePendingLossyWrites();
}

} // namespace updater
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}
declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}

if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}

# This does not currently work. See [Link]/1311822.


# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}
# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]

public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]

ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}

if (is_linux && !is_component_build && !using_sanitizer) {


version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]
# The step's output are needed at runtime, so we also need a data_dep.
data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}
if (is_chrome_branded && is_official_build) {
deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name

info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}
if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}

extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}

# Create and bundle an [Link] for the alert helper app.


# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
# Do not --allow more libraries here without consulting with the
# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}

# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]


bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}

group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}
copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}

ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}
mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}

if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources

outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]
deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources
_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}

if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}
if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}

group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}

if (is_chrome_branded && !is_android) {


if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]

if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}
if (is_chrome_branded && !is_mac) {
public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}

# This is extracted to deserialize build dependency around


# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]

deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]

# Explicit dependency required for JNI registration to be able to


# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}

deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}

deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}

if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}

declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}

if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}

# This does not currently work. See [Link]/1311822.


# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}

# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]

public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]

ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}
if (is_linux && !is_component_build && !using_sanitizer) {
version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]

# The step's output are needed at runtime, so we also need a data_dep.


data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}

if (is_chrome_branded && is_official_build) {


deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name
info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}

# Create and bundle an [Link] for the alert helper app.


# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),

# Do not --allow more libraries here without consulting with the


# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}

# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]


bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}

group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}

copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}

ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}

mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}

if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources

outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources

_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}

if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}

if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}

group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}

if (is_chrome_branded && !is_android) {


if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]
if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}

if (is_chrome_branded && !is_mac) {


public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}

# This is extracted to deserialize build dependency around


# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]

deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]
# Explicit dependency required for JNI registration to be able to
# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}
deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}

deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}

if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}

declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}

if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}

# This does not currently work. See [Link]/1311822.


# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}

# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]
public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]

ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}

if (is_linux && !is_component_build && !using_sanitizer) {


version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]

# The step's output are needed at runtime, so we also need a data_dep.


data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}

if (is_chrome_branded && is_official_build) {


deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name

info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}

extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}

# Create and bundle an [Link] for the alert helper app.


# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),

# Do not --allow more libraries here without consulting with the


# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}
# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]
bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}

group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}

copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}

ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}

mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}

if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources

outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources

_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}

if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}

if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}
group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}

if (is_chrome_branded && !is_android) {


if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]

if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}

if (is_chrome_branded && !is_mac) {


public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}

# This is extracted to deserialize build dependency around


# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]

deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]

# Explicit dependency required for JNI registration to be able to


# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}
deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}

deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}

deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}

if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}

declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}

if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}
# This does not currently work. See [Link]/1311822.
# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}

# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]

public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]

ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}

if (is_linux && !is_component_build && !using_sanitizer) {


version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}
if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]

# The step's output are needed at runtime, so we also need a data_dep.


data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}

if (is_chrome_branded && is_official_build) {


deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name

info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]
sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}

extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]
sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}

# Create and bundle an [Link] for the alert helper app.


# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),

# Do not --allow more libraries here without consulting with the


# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}

# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]


bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}
group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}

copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]
if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}

ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}

mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}

if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources
outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources

_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}

if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}

if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}

group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}

if (is_chrome_branded && !is_android) {


if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]

if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}

if (is_chrome_branded && !is_mac) {


public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}

# This is extracted to deserialize build dependency around


# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]

deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]

# Explicit dependency required for JNI registration to be able to


# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}

deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}

deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}

if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}

declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}
if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}

# This does not currently work. See [Link]/1311822.


# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}

# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]

public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]
ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}

if (is_linux && !is_component_build && !using_sanitizer) {


version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]

# The step's output are needed at runtime, so we also need a data_dep.


data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",
"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}

if (is_chrome_branded && is_official_build) {


deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name

info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}

extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}
# Create and bundle an [Link] for the alert helper app.
# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),

# Do not --allow more libraries here without consulting with the


# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}

# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]


bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}

group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}

copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}

ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}

mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}
if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources

outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources

_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}

if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}

if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}

group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}
if (is_chrome_branded && !is_android) {
if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]

if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}

if (is_chrome_branded && !is_mac) {


public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}
# This is extracted to deserialize build dependency around
# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]
deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]

# Explicit dependency required for JNI registration to be able to


# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}

deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}

deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}
if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}
mport("//build/config/chrome_build.gni")
import("//build/config/chromeos/[Link]")
import("//build/config/chromeos/ui_mode.gni")
import("//build/config/compiler/[Link]")
import("//build/config/compiler/pgo/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/[Link]")
import("//build/config/sanitizers/[Link]")
import("//build/config/[Link]")
import("//build/config/win/console_app.gni")
import("//build/config/win/[Link]")
import("//build/private_code_test/private_code_test.gni")
import("//build/toolchain/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/chrome_paks.gni")
import("//chrome/common/[Link]")
import("//chrome/process_version_rc_template.gni")
import("//components/nacl/[Link]")
import("//components/optimization_guide/[Link]")
import("//extensions/buildflags/[Link]")
import("//media/media_options.gni")
import("//ppapi/buildflags/[Link]")
import("//third_party/angle/gni/[Link]")
import("//third_party/blink/public/public_features.gni")
import("//third_party/widevine/cdm/[Link]")
import("//tools/resources/generate_resource_allowlist.gni")
import("//tools/v8_context_snapshot/v8_context_snapshot.gni")
import("//ui/gl/[Link]")
import("//v8/gni/[Link]")

assert(!is_fuchsia, "Fuchsia shouldn't use anything in //chrome")

if (is_android) {
import("//build/config/android/[Link]")
} else if (is_linux || is_chromeos) {
import("//build/linux/extract_symbols.gni")
import("//build/linux/strip_binary.gni")
} else if (is_mac) {
import("//build/apple/compile_entitlements.gni")
import("//build/apple/compile_plist.gni")
import("//build/apple/tweak_info_plist.gni")
import("//build/compiled_action.gni")
import("//build/config/apple/[Link]")
import("//build/config/mac/mac_sdk.gni")
import("//build/config/mac/[Link]")
import("//build/util/[Link]")
import("//chrome/browser/[Link]")
import("//chrome/updater/[Link]")
import("//chrome/[Link]")
import("//content/public/app/mac_helpers.gni")
import("//media/cdm/library_cdm/cdm_paths.gni")
import("//services/on_device_model/on_device_model.gni")
import("//third_party/icu/[Link]")
}

# b/365489014: CrOS' chrome.sections_embedded target breaks on component builds;


# there's no clear value of supporting it there, so disable it.
_cros_generate_embed_section_target = is_chromeos && !is_component_build

if (_cros_generate_embed_section_target) {
import("//build/chromeos/embed_sections.gni")
}

declare_args() {
# On macOS, `is_chrome_branded` builds that have been signed locally will not
# launch because certain entitlements are tied to the official Google code
# signing identity. If `include_branded_entitlements` is set to false, these
# entitlements will be skipped.
include_branded_entitlements = true
}

assert(!is_ios, "Chromium/iOS shouldn't use anything in //chrome")

if (is_win && enable_resource_allowlist_generation) {


_chrome_resource_allowlist = "$target_gen_dir/chrome_resource_allowlist.txt"
}

if (is_win) {
action("reorder_imports") {
script = "//build/win/[Link]"

# initialexe/ is used so that the we can reorder imports and write back to
# the final destination at $root_out_dir/.
inputs = [
"$root_out_dir/initialexe/[Link]",
"$root_out_dir/initialexe/[Link]",
]
outputs = [
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
]
args = [
"-i",
rebase_path("$root_out_dir/initialexe", root_build_dir),
"-o",
rebase_path("$root_out_dir", root_build_dir),
"-a",
current_cpu,
]
deps = [ ":chrome_initial" ]
}
}

# This does not currently work. See [Link]/1311822.


# This target exists above chrome and it's main components in the dependency
# tree as a central place to put assert_no_deps annotations. Since this depends
# on Chrome and the main DLLs it uses, it will transitively assert that those
# targets also have no deps on disallowed things.
group("assert_no_deps") {
deps = []

if (is_android) {
deps += [ "//chrome/android:chrome_public_apk" ]
} else {
deps += [ ":chrome" ]
}

if (is_win) {
deps += [ ":chrome_dll" ]
}

# This should not pull in installer strings. This is will bloat the binary
# for no reason and is easy to mess up. See the comment at the top of
# //chrome/installer/util/[Link].
assert_no_deps = [ "//chrome/installer/util:strings" ]
}

if (!is_android && !is_mac) {


group("chrome") {
public_deps = [ ":chrome_initial" ]
data_deps = [ ":chrome_initial" ]

# Do not add any more deps or data_deps to group("chrome").


# Because chrome_initial sets its output name to "chrome", running commands
# such as `ninja chrome` causes chrome_initial to be built instead. All
# deps and data_deps should be added to the chrome_initial target instead.
# Targets added here can only be built by explicitly using //chrome:chrome.
# Windows-only deps are OK because chrome_initial uses initialexe/chrome as
# the output name for that platform.
# See [Link]/1146571.
if (is_win) {
public_deps += [ ":reorder_imports" ]
data_deps += [ ":reorder_imports" ]
}
}

if (is_win) {
_chrome_output_name = "initialexe/chrome"
} else {
_chrome_output_name = "chrome"
}

executable("chrome_initial") {
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]
output_name = _chrome_output_name

# Because the sources list varies so significantly per-platform, generally


# each platform lists its own files rather than relying on filtering or
# removing unused files.
sources = [ "app/chrome_exe_resource.h" ]
defines = []
public_deps = []
deps = [
"//build:chromeos_buildflags",
"//printing/buildflags",
]
data = [ "$root_out_dir/[Link]" ]
data_deps = []

if (is_chromeos) {
data_deps += [
"//components/variations/cros_evaluate_seed:evaluate_seed",
"//sandbox/linux:chrome_sandbox",
]
if (build_mojo_proxy) {
data_deps += [ "//mojo/proxy:mojo_proxy" ]
}
deps += [
"//components/exo/wayland:test_controller_stub",
"//components/exo/wayland:ui_controls_protocol_stub",
]
}

if (is_win) {
sources += [
"app/chrome_exe.rc",
"app/chrome_exe_main_win.cc",
"app/delay_load_failure_hook_win.cc",
"app/delay_load_failure_hook_win.h",
"app/main_dll_loader_win.cc",
"app/main_dll_loader_win.h",
"common/crash_keys.cc",
"common/crash_keys.h",
]

deps += [
":chrome_dll",
":chrome_exe_version",
":copy_first_run",
":packed_resources_integrity_header",
":visual_elements_resources",
"//base",
"//build:branding_buildflags",
"//chrome/app:chrome_exe_main_exports",
"//chrome/app:exit_code_watcher",
"//chrome/app/version_assembly:chrome_exe_manifest",
"//chrome/browser:active_use_util",
"//chrome/browser:chrome_process_finder",
"//chrome/browser/policy:path_parser",
"//chrome/chrome_elf",
"//chrome/common:constants",
"//chrome/common/win:delay_load_failure_support",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//chrome/installer/util:constants",
"//chrome/installer/util:did_run_support",
"//components/crash/core/app",
"//components/crash/core/app:run_as_crashpad_handler",
"//components/crash/core/common",
"//components/crash/win:chrome_wer",
"//components/webui/flags:switches",
"//content:sandbox_helper_win",
"//content/public/common:static_switches",
"//crypto",
"//gpu/command_buffer/service",
"//sandbox",
"//sandbox/policy",
"//sandbox/policy/mojom",
"//third_party/breakpad:breakpad_handler",
"//third_party/breakpad:breakpad_sender",
"//third_party/crashpad/crashpad/util",
"//ui/gl",
]

data_deps = [
"//chrome/app/version_assembly:version_assembly_manifest",
"//chrome/browser/web_applications/chrome_pwa_launcher",
"//chrome/chrome_proxy",
"//chrome/elevation_service",
"//chrome/notification_helper",
"//chrome/windows_services/elevated_tracing_service",
]

if (enable_platform_experience) {
data_deps +=
[ "//chrome/browser/platform_experience/win:os_update_handler" ]
}

defines += [ "CHROME_EXE_MAIN" ]

if (win_console_app) {
defines += [ "WIN_CONSOLE_APP" ]
} else {
# Set /SUBSYSTEM:WINDOWS for [Link] itself, unless a console build
# has been requested.
configs -= [ "//build/config/win:console" ]
configs += [ "//build/config/win:windowed" ]
}

configs += [
"//build/config/win:delayloads",
"//build/config/win:delayloads_not_for_child_dll",
]

if (current_cpu == "x86") {
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
# Chrome's main thread. This saves significant memory on threads (like
# those in the Windows thread pool, and others) whose stack size we can
# only control through this setting. Because Chrome's main thread needs
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
# fibers to switch to a 1.5 MiB stack before running any other code.
ldflags = [ "/STACK:0x80000" ]
} else {
# Increase the initial stack size. The default is 1MB, this is 8MB.
ldflags = [ "/STACK:0x800000" ]
}
} else if (use_aura) {
# Non-Windows aura entrypoint.
sources += [ "app/chrome_exe_main_aura.cc" ]
}

if (is_linux || is_chromeos) {
sources += [
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

deps += [
# On Linux, link the dependencies (libraries) that make up actual
# Chromium functionality directly into the executable.
":dependencies",
"//chrome/common:version_header",

# For the sampling profiler.


"//chrome/common/profiler",

# Needed to use the master_preferences functions


"//chrome/installer/util:with_no_strings",
"//content/public/app",
]

public_deps = [
":xdg_mime", # Needs to be public for installer to consume files.
"//chrome/common:buildflags",
]

data_deps += [ "//components/crash/core/app:chrome_crashpad_handler" ]

ldflags = []

# On Chrome OS builds put priority to the library in the installed


# directory. This will avoid conflicting of exposed symbols.
if (is_chromeos_device) {
ldflags += [ "-L" + rebase_path(root_out_dir) ]
}

# On Chrome OS builds (for both ash-chrome and lacros-chrome), put


# a [Link] file in root directory containing Chrome version.
if (is_chromeos) {
data_deps += [ "//build:version_metadata" ]
}

# Chrome OS debug builds for arm need to pass --long-plt to the linker.
# See [Link]
if (is_chromeos && is_debug && target_cpu == "arm") {
ldflags += [ "-Wl,--long-plt" ]
}

if (is_linux && !is_component_build && !using_sanitizer) {


version_script = "//build/linux/[Link]"
inputs = [ version_script ]
ldflags += [ "-Wl,--version-script=" +
rebase_path(version_script, root_build_dir) ]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [
"//chrome/browser/ash/locale",
"//chrome/browser/ash/schedqos",
]
}

if (use_ozone) {
deps += [ "//ui/ozone" ]
if (is_linux) {
deps += [ "//ui/linux:display_server_utils" ]
}
}
}

# These files are used by the installer so we need a public dep.


public_deps += [ ":packed_resources" ]

# The step's output are needed at runtime, so we also need a data_dep.


data_deps += [ ":packed_resources" ]

# ChromeOS by design is safe to have rpath=$ORIGIN. This simplifies shared


# library usage.
if (is_chromeos && !is_component_build) {
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
}

data_deps += [
"//chrome/browser/resources/media/mei_preload:component",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component",
"//third_party/widevine/cdm",
]

if (is_linux) {
sources += [
"app/chrome_main_linux.cc",
"app/chrome_main_linux.h",
]
}
}

# TODO([Link]/40204298): Make this work on other platforms.


if (is_linux && current_toolchain == default_toolchain &&
!is_component_build) {
private_code_test("chrome_private_code_test") {
linker_inputs_dep = ":chrome_initial"
executable_name = _chrome_output_name
}
}
} # !is_android && !is_mac

if (is_win) {
shared_library("chrome_dll") {
configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

defines = []

sources = [
"//base/win/[Link]",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

output_name = "chrome"

deps = [
":chrome_dll_manifest",
":chrome_dll_version",
":dependencies",
"//chrome/app:chrome_dll_resources",
"//chrome/app:command_ids",
"//chrome/app/theme:chrome_unscaled_resources",
"//chrome/chrome_elf",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/install_static:install_static_util",
"//chrome/install_static:secondary_module",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//crypto",
"//headless:headless_non_renderer",
"//headless:headless_shell_browser_lib",
"//net:net_resources",
"//ppapi/buildflags",
"//sandbox/win:sandbox",
"//third_party/cld_3/src/src:cld_3",
"//third_party/wtl",
"//ui/views",
]

configs += [ "//build/config/win:delayloads" ]

if (use_aura) {
deps += [ "//ui/compositor" ]
}

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}
}

copy("copy_first_run") {
sources = [ "app/FirstRun" ]
outputs = [ "$root_out_dir/First Run" ]
}
} else if (is_mac) {
chrome_helper_name = chrome_product_full_name + " Helper"
chrome_framework_name = chrome_product_full_name + " Framework"
chrome_framework_version = chrome_version_full

verify_dynamic_libraries = !is_component_build && !is_asan && !is_ubsan_any


if (host_os == "mac") {
objdump_path = mac_bin_path
} else {
objdump_path = rebase_path("$clang_base_path/bin/", root_build_dir)
}

group("chrome") {
deps = [ ":chrome_app" ]

data_deps = [ ":chrome_app" ]

if (verify_dynamic_libraries) {
deps += [ ":verify_libraries_chrome_app" ]
}

if (is_chrome_branded && is_official_build) {


deps += [
":chrome_dsym_archive",
":chrome_dump_syms",
]
}
}

tweak_info_plist("chrome_app_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--scm=1",
"--bundle_id=$chrome_mac_bundle_id",
]
if (enable_updater) {
args += [ "--privileged_helper_id=$privileged_helper_name" ]
if (is_chrome_branded) {
args += [ "--keystone=1" ]
if (current_cpu == "arm64") {
args += [ "--keystone-base-tag=arm64" ]
}
} else {
args += [ "--keystone=0" ]
}
} else {
args += [ "--keystone=0" ]
}
}

mac_app_bundle("chrome_app") {
output_name = chrome_product_full_name

info_plist_target = ":chrome_app_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_CREATOR=$chrome_mac_creator_code",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

deps = [
":chrome_app_strings_bundle_data",
":chrome_resources",
":chrome_versioned_bundle_data",
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:buildflags",
"//chrome/common:version_header",
]

if (enable_updater) {
deps += [ ":chromium_updater_privileged_helper" ]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags = [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link" ]
ldflags = [ "-Wl,-rpath,@executable_path/../Frameworks" ]

# The Framework is packaged inside the .app bundle. But when using the
# component build, all the dependent shared libraries of :chrome_dll are
# not packaged within the framework. This data_deps line makes all of
# those dependent libraries runtime dependencies of the .app bundle.
# This is a bit of a hack, since GN deliberately terminates its search
# for runtime_deps at create_bundle nodes ([Link]
data_deps = [ ":chrome_framework" ]
}
}

if (verify_dynamic_libraries) {
action("verify_libraries_chrome_app") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [ "${root_out_dir}/${chrome_product_full_name}.app/Contents/MacOS/$
{chrome_product_full_name}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_app" ]
}
}

compiled_action("chrome_app_strings") {
tool = "//chrome/tools/build/mac:infoplist_strings_util"

inputs = []

outputs = []

foreach(locale, platform_pak_locales) {
inputs += [ "$root_gen_dir/chrome/branded_strings_${locale}.pak" ]
}

foreach(locale, locales_as_apple_outputs) {
outputs += [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
}

args =
[
"-b",
"branded_strings",
"-v",
chrome_version_full,
"-g",
rebase_path("$root_gen_dir/chrome", root_build_dir),
"-o",
rebase_path("$target_gen_dir/app_infoplist_strings", root_build_dir),
"-t",
"main",
] + platform_pak_locales

deps = [ "//chrome/app:branded_strings" ]
}

foreach(locale, locales_as_apple_outputs) {
bundle_data("chrome_app_strings_${locale}_bundle_data") {
sources = [
"$target_gen_dir/app_infoplist_strings/$[Link]/[Link]",
]
outputs =
[ "{{bundle_resources_dir}}/$[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_app_strings" ]
}
}
group("chrome_app_strings_bundle_data") {
public_deps = []
foreach(locale, locales_as_apple_outputs) {
public_deps += [ ":chrome_app_strings_${locale}_bundle_data" ]
}
}

bundle_data("chrome_app_icon") {
sources = [ "app/theme/$branding_path_component/mac/[Link]" ]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
}

bundle_data("chrome_resources") {
sources = [
"$root_out_dir/$chrome_mac_bundle_id.manifest",
"app/theme/$branding_path_component/mac/[Link]",
"browser/ui/cocoa/applescript/[Link]",
]
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
public_deps = [
":chrome_app_icon",
":chrome_app_strings",
"//components/policy:chrome_manifest_bundle",
]
}

bundle_data("chrome_versioned_bundle_data") {
sources = [ "$root_out_dir/$chrome_framework_name.framework" ]
outputs = [ "{{bundle_contents_dir}}/Frameworks/{{source_file_part}}" ]
public_deps = [
# Before bundling the versioned app components, delete any existing
# versions.
":clean_up_old_versions",

# verify_chrome_framework_order depends on :chrome_framework and, for


# non-component builds, will ensure the export symbol table is correct.
":verify_chrome_framework_order",
]

if (enable_widevine_cdm_host_verification) {
# The :chrome_framework_widevine_signature target copies into the
# :chrome_framework bundle. But because the signing file depends on the
# framework itself, that would cause a cyclical dependency. Instead,
# this dependency directly copies the file into the framework's
# resources directory.
public_deps += [ ":chrome_framework_widevine_signature" ]
}
}

if (enable_updater) {
bundle_data("chromium_updater_privileged_helper") {
sources = [ "$root_out_dir/$privileged_helper_name" ]
outputs = [
"{{bundle_contents_dir}}/Library/LaunchServices/{{source_file_part}}",
]

public_deps = [ "//chrome/updater/mac:privileged_helper" ]
}
}

action("clean_up_old_versions") {
script = "//chrome/tools/build/mac/clean_up_old_versions.py"

_stamp_file = "$root_gen_dir/run_$target_name.stamp"

outputs = [ _stamp_file ]

_versions_dir =
"$root_out_dir/$chrome_product_full_name.app/Contents/Frameworks/$chrome_framework_
[Link]/Versions"

args = [
"--versions-dir",
rebase_path(_versions_dir, root_build_dir),
"--stamp",
rebase_path(_stamp_file, root_build_dir),
"--keep",
chrome_framework_version,
"--keep",
"Current",
]
}

tweak_info_plist("chrome_helper_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=0",
]
}

compile_entitlements("entitlements") {
entitlements_templates = [ "app/[Link]" ]
if (is_chrome_branded && include_branded_entitlements) {
# These entitlements are bound to the official Google Chrome signing
# certificate and will not necessarily work in any other build.
entitlements_templates += [ "app/[Link]" ]
}
output_name = "$target_gen_dir/[Link]"
substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_TEAM_ID=$chrome_mac_team_id",
]
visibility = [ "//chrome/installer/mac:copies" ]
}

template("chrome_helper_app") {
mac_app_bundle(target_name) {
assert(defined(invoker.helper_name_suffix))
assert(defined(invoker.helper_bundle_id_suffix))

output_name = chrome_helper_name + invoker.helper_name_suffix

if (defined(invoker.info_plist_target)) {
info_plist_target = invoker.info_plist_target
} else {
info_plist_target = ":chrome_helper_plist"
}

extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
"CHROMIUM_HELPER_SUFFIX=${invoker.helper_name_suffix}",
"CHROMIUM_HELPER_BUNDLE_ID_SUFFIX=${invoker.helper_bundle_id_suffix}",
]

sources = [ "app/chrome_exe_main_mac.cc" ]

configs += [ "//build/config/compiler:wexit_time_destructors" ]

defines = [ "HELPER_EXECUTABLE" ]

deps = [
"//base/allocator:early_zone_registration_apple",
"//build:branding_buildflags",
"//chrome/common:version_header",
"//sandbox/mac:seatbelt",
]

if (defined([Link])) {
deps += [Link]
}

ldflags = []

if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
deps += [ ":chrome_framework+link_nested" ]

ldflags += [
# The helper is in [Link]/Contents/Frameworks/Chromium
[Link]/Versions/X/Helpers/Chromium [Link]/Contents/MacOS

# Set up an rpath to the Contents/Frameworks directory so that


# [Link] will be found there. This matches the path that
# chrome_exe_main_mac.cc uses with `dlopen` and avoids loading the
# framework into memory twice.
"-Wl,-rpath,@executable_path/../../../../../../..",

# Add an rpath up to the base for all other libraries.


"-Wl,-rpath,@loader_path/../../../../../../../../../..",
]
}

if (enable_stripping) {
# At link time, preserve the global symbols specified in the .exports
# file. All other global symbols will be marked as private. The default
# //build/config/apple:strip_all config will then remove the remaining
# local and debug symbols.
ldflags += [ "-Wl,-exported_symbols_list," +
rebase_path("app/[Link]", root_build_dir) ]
}
}
}

# The following *_helper_params are added to the ones provided by //content


# listed in content_mac_helpers (see //content/public/app/mac_helpers.gni).
# These allow //chrome to add custom helper apps in addition to the ones
# provided by //content. The params here have the same form as the content
# helpers and are defined as a tuple of these elements:
# target name - A short name to be used when defining the target for that
# helper variant.
# bundle ID suffix - A string fragment to append to the CFBundleIdentifier of
# the helper.
# app name suffix - A string fragment to append to the outer bundle name as
# well as the inner executable. This should be reflected in
# the target's output_name.

# Helper app to display alert notifications. This is necessary as an app can


# only display either banner or alert style notifications and the main app
# will display banners.
alert_helper_params = [
"alerts",
".alerts",
" (Alerts)",
]

# Merge all helper apps needed by //content and //chrome.


chrome_mac_helpers = content_mac_helpers + [ alert_helper_params ]

# Create all helper apps required by //content.


foreach(helper_params, content_mac_helpers) {
chrome_helper_app("chrome_helper_app_${helper_params[0]}") {
helper_name_suffix = helper_params[2]
helper_bundle_id_suffix = helper_params[1]
}
}

# Create app for the alert helper manually here as we want to modify the plist
# to set the alert style and add the app icon to its resources.
tweak_info_plist("chrome_helper_app_alerts_plist") {
deps = [ ":chrome_helper_plist" ]
info_plists = get_target_outputs(":chrome_helper_plist") +
[ "app/[Link]" ]
}

# Create and bundle an [Link] for the alert helper app.


# TODO([Link]/40751430): Disambiguate and localize alert helper app name.
compile_plist("chrome_helper_app_alerts_plist_strings") {
format = "binary1"
plist_templates = [ "app/[Link]" ]
substitutions = [ "CHROMIUM_FULL_NAME=$chrome_product_full_name" ]
output_name =
"$target_gen_dir/helper_alerts_infoplist_strings/[Link]/[Link]"
}
bundle_data("chrome_helper_app_alerts_resources") {
sources = get_target_outputs(":chrome_helper_app_alerts_plist_strings")
outputs = [ "{{bundle_resources_dir}}/[Link]/{{source_file_part}}" ]
public_deps = [ ":chrome_helper_app_alerts_plist_strings" ]
}

chrome_helper_app("chrome_helper_app_${alert_helper_params[0]}") {
helper_name_suffix = alert_helper_params[2]
helper_bundle_id_suffix = alert_helper_params[1]
info_plist_target = ":chrome_helper_app_alerts_plist"
deps = [
":chrome_app_icon",
":chrome_helper_app_alerts_resources",
]
}

if (verify_dynamic_libraries) {
foreach(helper_params, chrome_mac_helpers) {
_helper_target = helper_params[0]
_helper_bundle_id = helper_params[1]
_helper_suffix = helper_params[2]

action("verify_libraries_chrome_helper_app_${_helper_target}") {
script = "//chrome/tools/build/mac/verify_dynamic_libraries.py"
inputs = [
"${root_out_dir}/${chrome_helper_name}${_helper_suffix}.app/Contents/MacOS/$
{chrome_helper_name}${_helper_suffix}" ]
outputs = [ "$target_out_dir/run_$target_name.stamp" ]
args = [
"--stamp",
rebase_path(outputs[0], root_out_dir),
"-B",
objdump_path,
"--image",
rebase_path(inputs[0], root_out_dir),

# Do not --allow more libraries here without consulting with the


# security team (security-dev@[Link]).
"--allow",
"/usr/lib/[Link]",
"--allow",
"/usr/lib/[Link]",
]
deps = [ ":chrome_helper_app_${_helper_target}" ]
}
}
}

bundle_data("chrome_framework_helpers") {
sources = [
"$root_out_dir/app_mode_loader",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/web_app_shortcut_copier",
]

outputs = [ "{{bundle_contents_dir}}/Helpers/{{source_file_part}}" ]

public_deps = [
"//chrome/app_shim:app_mode_loader",

"//chrome/browser/web_applications/os_integration/mac:web_app_shortcut_copier",
"//components/crash/core/app:chrome_crashpad_handler",
]

foreach(helper_params, chrome_mac_helpers) {
sources +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.app" ]
public_deps += [ ":chrome_helper_app_${helper_params[0]}" ]
if (verify_dynamic_libraries) {
public_deps +=
[ ":verify_libraries_chrome_helper_app_${helper_params[0]}" ]
}
}

if (enable_updater) {
if (is_chrome_branded) {
sources += [ "//third_party/updater/chrome_mac_universal_prod/cipd/$
{updater_product_full_name}.app" ]
} else {
sources += [ "$root_out_dir/${updater_product_full_name}.app" ]

public_deps += [
"//chrome/updater/mac:browser_install_script",
"//chrome/updater/mac:updater_bundle",
"//chrome/updater/mac:updater_install_script",
]
}
}
}

bundle_data("chrome_framework_resources") {
sources = [
"//ui/gl/resources/angle-metal/gpu_shader_cache.bin",

# This image is used to badge the lock icon in the


# authentication dialogs, such as those used for installation
# from disk image and Keystone promotion (if so enabled). It
# needs to exist as a file on disk and not just something in a
# resource bundle because that's the interface that
# Authorization Services uses. Also, Authorization Services
# can't deal with .icns files.
"$root_gen_dir/chrome/browser/mac/[Link]",
"app/theme/default_100_percent/$branding_path_component/product_logo_32.png",
]

outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]

public_deps = [
":packed_resources",
"//chrome/app_shim:app_mode_loader_plist_bundle_data",
"//chrome/browser/mac:install",
]

if (icu_use_data_file) {
sources += [ "$root_out_dir/[Link]" ]
public_deps += [ "//third_party/icu:icudata" ]
}

if (v8_use_external_startup_data) {
public_deps += [ "//v8" ]
if (use_v8_context_snapshot) {
sources += [ "$root_out_dir/$v8_context_snapshot_filename" ]
public_deps += [ "//tools/v8_context_snapshot" ]
}
if (!use_v8_context_snapshot || include_both_v8_snapshots) {
sources += [ "$root_out_dir/snapshot_blob.bin" ]
}
}
}

if (enable_nacl) {
bundle_data("chrome_framework_plugins") {
sources = []
outputs =
[ "{{bundle_contents_dir}}/Internet Plug-Ins/{{source_file_part}}" ]
public_deps = []

if (enable_nacl) {
if (current_cpu == "x86") {
sources += [ "$root_out_dir/nacl_irt_x86_32.nexe" ]
} else if (current_cpu == "x64") {
sources += [ "$root_out_dir/nacl_irt_x86_64.nexe" ]
}
public_deps += [ "//ppapi/native_client:irt" ]
}
}
} else {
group("chrome_framework_plugins") {
}
}

# Add the ANGLE .dylibs in the MODULE_DIR of [Link]


bundle_data("angle_binaries") {
sources = [
"$root_out_dir/egl_intermediates/[Link]",
"$root_out_dir/egl_intermediates/[Link]",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:angle_library_copy" ]
}

# Add the SwiftShader .dylibs in the MODULE_DIR of [Link]


bundle_data("swiftshader_binaries") {
sources = [
"$root_out_dir/vk_intermediates/libvk_swiftshader.dylib",
"$root_out_dir/vk_intermediates/vk_swiftshader_icd.json",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [ "//ui/gl:swiftshader_vk_library_copy" ]
}

if (bundle_widevine_cdm) {
bundle_data("widevine_cdm_library_binaries") {
sources = [ "$root_out_dir/$widevine_cdm_path/[Link]" ]
if (enable_widevine_cdm_host_verification) {
sources +=
[ "$root_out_dir/$widevine_cdm_path/[Link]" ]
}
outputs = [
"{{bundle_contents_dir}}/Libraries/$widevine_cdm_path/{{source_file_part}}" ]
public_deps = [ "//third_party/widevine/cdm" ]
}

bundle_data("widevine_cdm_library_manifest_and_license_files") {
sources = [
"$root_out_dir/WidevineCdm/LICENSE",
"$root_out_dir/WidevineCdm/[Link]",
]
outputs = [
"{{bundle_contents_dir}}/Libraries/WidevineCdm/{{source_file_part}}",
]
public_deps = [ "//third_party/widevine/cdm" ]
}
}

group("widevine_cdm_library") {
if (bundle_widevine_cdm) {
deps = [
":widevine_cdm_library_binaries",
":widevine_cdm_library_manifest_and_license_files",
]
}
}

if (enable_widevine_cdm_host_verification) {
widevine_sign_file("sign_chrome_framework_for_widevine") {
file =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
flags = 1
signature_file = "$root_out_dir/$chrome_framework_name.sig"
deps = [ ":chrome_framework" ]
}

copy("chrome_framework_widevine_signature") {
deps = [ ":sign_chrome_framework_for_widevine" ]

sources = [ "$root_out_dir/$chrome_framework_name.sig" ]

outputs = [
"$root_out_dir/$chrome_framework_name.framework/Resources/{{source_file_part}}" ]
}
}

if (build_with_internal_optimization_guide) {
# Add the optimization guide .dylib in the MODULE_DIR of [Link]
bundle_data("optimization_guide_library") {
sources = [
"$root_out_dir/og_intermediates/liboptimization_guide_internal.dylib",
]
outputs = [ "{{bundle_contents_dir}}/Libraries/{{source_file_part}}" ]
public_deps = [
"//components/optimization_guide/core:optimization_guide_internal_library_copy" ]
}
} else {
group("optimization_guide_library") {
}
}

tweak_info_plist("chrome_framework_plist") {
info_plist = "app/[Link]"
args = [
"--breakpad=0",
"--keystone=0",
"--scm=1",
"--branding",
chrome_product_short_name,
]
}

# Limit the exported symbols of the framework library.


config("chrome_dll_symbol_exports") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-exported_symbols_list",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# Control the order of exported symbols in the framework library.


config("chrome_dll_symbol_order") {
inputs = [ rebase_path("app/[Link]") ]
ldflags = [
"-Wl,-order_file",
"-Wl," + rebase_path("app/[Link]", root_build_dir),
]
}

# On Mac, speed up the component build by not re-bundling the framework


# every time it changes. Instead, place all the sources and their deps in
# a library that the bundled framework links (and re-exports). That way
# only the library needs to be re-linked when it changes.
if (is_component_build) {
_dll_target_type = "shared_library"
} else {
_dll_target_type = "source_set"
}
target(_dll_target_type, "chrome_dll") {
visibility = [
":chrome_framework",
":chrome_framework_create_bundle",
":chrome_framework_shared_library",
]

sources = [
"app/chrome_crash_reporter_client.cc",
"app/chrome_crash_reporter_client.h",
"app/chrome_crash_reporter_client_mac.mm",
"app/chrome_dll_resource.h",
"app/chrome_main.cc",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/chrome_main_mac.h",
"app/chrome_main_mac.mm",
"app/startup_timestamps.h",
]

deps = [
":dependencies",
"//build:chromeos_buildflags",
"//chrome/app:command_ids",
"//chrome/common:buildflags",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//components/crash/core/app",
"//components/memory_system",
"//components/policy:generated",
"//content/public/app",
"//headless:headless_shell_lib",
"//third_party/cld_3/src/src:cld_3",
]

if (is_chromeos) {
deps += [ "//chrome/browser/ash/schedqos" ]
}

if (is_component_build) {
frameworks = [ "[Link]" ]
}
ldflags = [ "-ObjC" ]

configs += [
":chrome_dll_symbol_order",
"//build/config/compiler:wexit_time_destructors",
]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}
}

mac_framework_bundle("chrome_framework") {
output_name = chrome_framework_name

framework_version = chrome_framework_version
framework_contents = [
"Helpers",
"Libraries",
"Resources",
]

if (is_chrome_branded) {
framework_contents += [ "Default Apps" ]
}

if (enable_nacl) {
framework_contents += [ "Internet Plug-Ins" ]
}

configs += [ "//build/config/compiler:wexit_time_destructors" ]
configs -= [ "//build/config/compiler:thinlto_optimize_default" ]
configs += [ "//build/config/compiler:thinlto_optimize_max" ]

info_plist_target = ":chrome_framework_plist"
extra_substitutions = [
"CHROMIUM_BUNDLE_ID=$chrome_mac_bundle_id",
"CHROMIUM_SHORT_NAME=$chrome_product_short_name",
]

public_deps = [ ":chrome_dll" ]

bundle_deps = [
":angle_binaries",
":chrome_framework_helpers",
":chrome_framework_plugins",
":chrome_framework_resources",
":optimization_guide_library",
":swiftshader_binaries",
":widevine_cdm_library",
"//chrome/browser/resources/media/mei_preload:component_bundle",
"//chrome/browser/web_applications/isolated_web_apps/key_distribution/
preload:component_bundle",

"//components/privacy_sandbox/privacy_sandbox_attestations/preload:component_bundle
",
]

if (is_chrome_branded) {
bundle_deps += [ ":preinstalled_apps" ]
}

configs += [ ":chrome_dll_symbol_order" ]
if (!is_component_build && !using_sanitizer) {
configs += [ ":chrome_dll_symbol_exports" ]
}

ldflags = [
"-compatibility_version",
chrome_dylib_version,
"-current_version",
chrome_dylib_version,
]

if (!is_component_build) {
# Specify a sensible install_name for static builds. The library is
# dlopen()ed so this is not used to resolve the module.
ldflags += [
"-Wl,-install_name,@executable_path/../Frameworks/$chrome_framework_name.framework/
Versions/$chrome_framework_version/$chrome_framework_name" ]
} else {
# In the component build, both the :chrome_app and various
# :chrome_helper* targets directly link to the Framework target. Use
# @rpath-based loading so that the dylib ID does not have to be changed
# with install_name_tool.
ldflags += [

"-Wl,-install_name,@rpath/$chrome_framework_name.framework/$chrome_framework_name",
"-Wl,-rpath,@loader_path/../../../../../..",
"-Wl,-reexport_library,libchrome_dll.dylib",
]

data_deps = [ ":chrome_dll" ]
}
}

_framework_binary_path =
"$root_out_dir/$chrome_framework_name.framework/Versions/$chrome_framework_version/
$chrome_framework_name"
assert(_framework_binary_path != "",
"Ignore configuration-dependent unused variable warning")

# TOOD(crbug/1163903#c8) - thakis@ look into why profile and coverage


# instrumentation adds these symbols in different orders
if (!is_component_build && chrome_pgo_phase != 1 && !using_sanitizer) {
action("verify_chrome_framework_order") {
script = "//chrome/tools/build/mac/verify_order.py"
stamp_file = "$target_out_dir/run_$target_name.stamp"
inputs = [ script ]
args = [
"--stamp=" + rebase_path(stamp_file, root_out_dir),
"--binary=" + rebase_path(_framework_binary_path, root_out_dir),
"--symbol-file=" + rebase_path("app/[Link]", root_build_dir),
]
if (host_os == "mac") {
args += [ "--nm-path=$mac_bin_path/nm" ]
} else {
args += [ "--nm-path=" +
rebase_path("$clang_base_path/bin/llvm-nm", root_build_dir) ]
}
outputs = [ stamp_file ]
public_deps = [ ":chrome_framework" ]
}
} else {
group("verify_chrome_framework_order") {
if (is_component_build) {
# In a component build, the framework is directly linked to the
# executable because dlopen() and loading all the dependent dylibs
# is time-consuming, see [Link]
public_deps = [ ":chrome_framework+link" ]
} else {
public_deps = [ ":chrome_framework" ]
}
}
}

if (enable_dsyms && !is_component_build) {


# It is possible to run dump_syms on unstripped products without dSYMs, but
# doing so isn't logical and won't happen in practice. It's also pointless
# to run dump_syms or archive dSYMs in a component build, where all of the
# interesting symbols and debug info are tucked away in other libraries
# beyond the set explicitly listed here.

# This list must be updated with the two targets' deps list below, and
# the list of _dsyms in :chrome_dsym_archive.
_chrome_symbols_sources = [

"$root_out_dir/$chrome_product_full_name.app/Contents/MacOS/$chrome_product_full_na
me",
"$root_out_dir/chrome_crashpad_handler",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.dylib",
_framework_binary_path,
]
if (build_with_internal_optimization_guide) {
_chrome_symbols_sources +=
[ "$root_out_dir/liboptimization_guide_internal.dylib" ]
}

foreach(helper_params, chrome_mac_helpers) {
_chrome_symbols_sources += [ "$root_out_dir/${chrome_helper_name}$
{helper_params[2]}.app/Contents/MacOS/${chrome_helper_name}${helper_params[2]}" ]
}

action_foreach("chrome_dump_syms") {
script = "//build/redirect_stdout.py"

sources = _chrome_symbols_sources

outputs =
[ "$root_out_dir/{{source_file_part}}-$chrome_version_full.breakpad" ]

dump_syms =
"//third_party/breakpad:dump_syms($host_system_allocator_toolchain)"
args = rebase_path(outputs, root_build_dir) + [
rebase_path(get_label_info(dump_syms, "root_out_dir") + "/" +
get_label_info(dump_syms, "name"),
root_build_dir),
"-d",
"-m",
"-g",
rebase_path(

"$root_out_dir/{{source_file_part}}.dSYM/Contents/Resources/DWARF/
{{source_file_part}}",
root_build_dir),
"{{source}}",
]

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
dump_syms,
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}
}

action("chrome_dsym_archive") {
script = "//chrome/tools/build/mac/archive_symbols.py"

# These are the dSYMs that will be archived. The sources list must be
# the target outputs that correspond to the dSYMs (since a dSYM is a
# directory it cannot be listed as a source file). The targets that
# generate both the dSYM and binary image are listed in deps.
_dsyms = [
"$root_out_dir/$chrome_framework_name.dSYM",
"$root_out_dir/$chrome_product_full_name.dSYM",
"$root_out_dir/chrome_crashpad_handler.dSYM",
"$root_out_dir/[Link]",
"$root_out_dir/[Link]",
"$root_out_dir/libvk_swiftshader.[Link]",
]
if (build_with_internal_optimization_guide) {
_dsyms += [ "$root_out_dir/liboptimization_guide_internal.[Link]" ]
}

deps = [
":chrome_app",
":chrome_framework",
"//components/crash/core/app:chrome_crashpad_handler",
"//third_party/angle:libEGL",
"//third_party/angle:libGLESv2",
"//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan",
]
if (build_with_internal_optimization_guide) {
deps += [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}

foreach(helper_params, chrome_mac_helpers) {
_dsyms +=
[ "$root_out_dir/${chrome_helper_name}${helper_params[2]}.dSYM" ]
deps += [ ":chrome_helper_app_${helper_params[0]}" ]
}

sources = _chrome_symbols_sources

_output = "$root_out_dir/$chrome_product_full_name.[Link].bz2"

outputs = [ _output ]

args = [ rebase_path(_output, root_out_dir) ] +


rebase_path(_dsyms, root_out_dir)
}
} else {
group("chrome_dump_syms") {
}
group("chrome_dsym_archive") {
}
}
}

group("dependencies") {
public_deps = [
"//build:branding_buildflags",
"//build:chromeos_buildflags",
"//chrome/browser",
"//chrome/browser:buildflags",
"//chrome/browser:shell_integration",
"//chrome/browser/policy:path_parser",
"//chrome/child",
"//chrome/common",
"//chrome/gpu",
"//chrome/renderer",
"//chrome/utility",
"//components/crash/core/app",
"//components/devtools/devtools_pipe",
"//components/memory_system",
"//components/startup_metric_utils",
"//components/sync",
"//components/upload_list:upload_list",
"//components/webui/about",
"//content/public/child",
"//pdf",
"//services/tracing/public/cpp",
"//third_party/blink/public:blink_devtools_frontend_resources",
"//third_party/blink/public:blink_devtools_inspector_resources",
"//v8:v8_headers",
]

if (enable_ppapi) {
public_deps += [ "//ppapi/host" ]
}
if (enable_printing) {
public_deps += [ "//printing" ]
}

if (enable_nacl) {
public_deps += [
"//components/nacl/browser",
"//components/nacl/renderer/plugin:nacl_trusted_plugin",
]
}

if (is_chromeos) {
public_deps += [
"//ash/constants",
"//chrome/browser/ash/boot_times_recorder",
"//chrome/browser/ash/dbus",
"//chrome/browser/ash/schedqos",
"//chromeos/ash/components/memory",
"//chromeos/dbus/constants",
]
}
}

if (is_win) {
process_version_rc_template("chrome_exe_version") {
sources = [ "app/chrome_exe.ver" ]
output = "$target_gen_dir/chrome_exe_version.rc"
}

process_version_rc_template("chrome_dll_version") {
sources = [ "app/chrome_dll.ver" ]
output = "$target_gen_dir/chrome_dll_version.rc"
}

# This manifest matches what GYP produced. It may not even be necessary.
windows_manifest("chrome_dll_manifest") {
sources = [
as_invoker_manifest,
common_controls_manifest,
]
}

process_version_rc_template("other_version") {
sources = [ "app/[Link]" ]
output = "$target_gen_dir/other_version.rc"
}
}

copy("visual_elements_resources") {
sources = [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"app/visual_elements_resources/[Link]",
]

if (is_chrome_branded) {
sources += [
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
"//chrome/app/theme/$branding_path_component/win/tiles/[Link]",
]
}

outputs = [ "$root_out_dir/{{source_file_part}}" ]
}

group("resources") {
public_deps = [
"//chrome/browser:resources",
"//chrome/common:resources",
"//chrome/renderer:resources",
]
}

group("extra_resources") {
# Deps should be same as those in chrome_extra_paks() within chrome_paks.gni.
public_deps = [
"//chrome/browser/resources:resources",
"//components/autofill/core/browser:autofill_address_rewriter_resources",
]
}

if (is_chrome_branded && !is_android) {


if (!is_mac) {
_preinstalled_apps_target_type = "copy"
} else {
_preinstalled_apps_target_type = "bundle_data"
}

target(_preinstalled_apps_target_type, "preinstalled_apps") {
visibility = [ ":packed_resources" ]
if (is_mac) {
visibility += [
":chrome_framework",
":chrome_framework_shared_library",
]
}

sources = [ "browser/resources/default_apps/external_extensions.json" ]

if (!is_mac) {
outputs = [ "$root_out_dir/default_apps/{{source_file_part}}" ]
} else {
outputs = [ "{{bundle_contents_dir}}/Default Apps/{{source_file_part}}" ]
}

# Force anybody that depends on this to get the default apps as data files.
data = process_file_template(sources, outputs)
}
}

if (!is_android) {
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
copy_data_to_bundle = true
} else {
output_dir = root_out_dir
mark_as_data = true
}

if (enable_resource_allowlist_generation) {
repack_allowlist = _chrome_resource_allowlist
deps = [ ":resource_allowlist" ]
}

if (is_chrome_branded && !is_mac) {


public_deps = [ ":preinstalled_apps" ]
}

# This needs to be in-sync with //chrome/app/packed_resources_integrity.h.


files_to_hash = [
"[Link]",
"chrome_100_percent.pak",
]
if (enable_hidpi) {
files_to_hash += [ "chrome_200_percent.pak" ]
}
}

# This is extracted to deserialize build dependency around


# :packed_resources_integrity_hash and improve build parallelism.
source_set("packed_resources_integrity_header") {
sources = [ "app/packed_resources_integrity.h" ]

# chrome/app/packed_resources_integrity.cc file is generated in dependency.


deps = [ ":packed_resources_integrity" ]
}
}

repack("browser_tests_pak") {
testonly = true
sources = [ "$root_gen_dir/chrome/webui_test_resources.pak" ]
output = "$root_out_dir/browser_tests.pak"
deps = [ "//chrome/test/data/webui:resources" ]
}

group("strings") {
public_deps = [
"//chrome/app:branded_strings",
"//chrome/app:generated_resources",
"//chrome/app/resources:locale_settings",
]
}

if (is_android) {
java_cpp_enum("offline_pages_enum_javagen") {
sources = [ "browser/offline_pages/offline_page_utils.h" ]
}

java_cpp_enum("download_enum_javagen") {
sources = [
"browser/download/android/download_open_source.h",
"browser/download/download_dialog_types.h",
"browser/download/download_prompt_status.h",
]
}

source_set("chrome_android_core") {
sources = [
"app/android/chrome_jni_onload.cc",
"app/android/chrome_jni_onload.h",
"app/android/chrome_main_delegate_android.cc",
"app/android/chrome_main_delegate_android.h",
"app/chrome_main_delegate.cc",
"app/chrome_main_delegate.h",
"app/startup_timestamps.h",
]

libs = [
"android",
"jnigraphics",
]

public_deps = [
"//chrome/browser",
"//chrome/utility",
]

deps = [
":dependencies",
"//chrome/browser/flags:flags_android",
"//chrome/browser/ui",
"//chrome/child",
"//chrome/common",
"//chrome/common:version_header",
"//chrome/common/profiler",
"//chrome/gpu",
"//chrome/renderer",
"//components/crash/android:crash_android",
"//components/minidump_uploader",
"//components/safe_browsing:buildflags",
"//components/safe_browsing/android:safe_browsing_api_handler",
"//components/safe_browsing/android:safe_browsing_mobile",
"//components/stylus_handwriting/android",
"//components/variations:variations_associated_data",
"//content/public/app",
]

# Explicit dependency required for JNI registration to be able to


# find the native side functions.
if (is_component_build) {
deps += [
"//components/viz/service",
"//device/gamepad",
"//ui/events/devices",
]
}

if (is_chromeos) {
public_deps += [ "//ui/lottie" ]
deps += [ "//chrome/browser/ash/schedqos" ]
}
}
}

# Android also supports this, but uses


# //chrome/android:${_variant}_resource_allowlist.
if (is_win && enable_resource_allowlist_generation) {
generate_resource_allowlist("resource_allowlist") {
deps = [ ":chrome_dll" ]
inputs = [ "$root_out_dir/[Link]" ]
output = _chrome_resource_allowlist
}
}

if (is_linux || is_chromeos) {
if (!(is_debug && use_debug_fission)) {
group("linux_symbols") {
deps = [
":angle_egl_symbols",
":angle_gles_symbols",
":chrome_crashpad_symbols",
":chrome_symbols",
]
if (is_linux) {
deps += [ ":swiftshader_vk_symbols" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
deps += [ ":angle_libvulkan_symbols" ]
}
if (build_with_internal_optimization_guide) {
deps += [ ":optimization_guide_symbols" ]
}
}
extract_symbols("chrome_symbols") {
binary = "$root_out_dir/chrome"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ ":chrome" ]
}
extract_symbols("chrome_crashpad_symbols") {
binary = "$root_out_dir/chrome_crashpad_handler"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/[Link].ia32"
} else {
symbol_file = "$root_out_dir/[Link].$current_cpu"
}

deps = [ "//components/crash/core/app:chrome_crashpad_handler" ]
}
extract_symbols("swiftshader_vk_symbols") {
binary = "$root_out_dir/libvk_swiftshader.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/libvk_swiftshader.breakpad.$current_cpu"
}

deps = [ "//third_party/swiftshader/src/Vulkan:swiftshader_libvulkan" ]
}
extract_symbols("angle_egl_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libegl.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libegl.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libEGL" ]
}
extract_symbols("angle_gles_symbols") {
binary = "$root_out_dir/[Link]"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libgles.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libgles.breakpad.$current_cpu"
}

deps = [ "//third_party/angle:libGLESv2" ]
}
if (!is_chromeos && angle_shared_libvulkan) {
extract_symbols("angle_libvulkan_symbols") {
binary = "$root_out_dir/[Link].1"

if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.ia32"
} else {
symbol_file = "$root_out_dir/angle_libvulkan.breakpad.$current_cpu"
}

deps = [ "//third_party/vulkan-loader/src:libvulkan" ]
}
}
if (build_with_internal_optimization_guide) {
extract_symbols("optimization_guide_symbols") {
binary = "$root_out_dir/liboptimization_guide_internal.so"
if (current_cpu == "x86") {
# GYP used "ia32" so keep that naming for back-compat.
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.ia32"
} else {
symbol_file =
"$root_out_dir/optimization_guide_internal.breakpad.$current_cpu"
}
deps = [
"//components/optimization_guide/internal:optimization_guide_internal" ]
}
}
}

# Copies some scripts and resources that are used for desktop integration.
copy("xdg_mime") {
sources = [
"//chrome/tools/build/linux/chrome-wrapper",
"//third_party/xdg-utils/scripts/xdg-mime",
"//third_party/xdg-utils/scripts/xdg-settings",
]
if (is_linux) {
sources += [
"//chrome/app/theme/$branding_path_component/linux/product_logo_48.png",
]
} else {
sources +=
[ "//chrome/app/theme/$branding_path_component/product_logo_48.png" ]
}
outputs = [ "$root_out_dir/{{source_file_part}}" ]
}
}

if (_cros_generate_embed_section_target) {
embed_sections("section_embedded_chrome_binary") {
binary_input = "$root_out_dir/chrome"
sections_embedded_binary_output = "$root_out_dir/chrome.sections_embedded"
deps = [ ":chrome" ]
}
}

You might also like