// Copyright 2013 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/chromeos/extensions/wallpaper_api.h"
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/strings/stringprintf.h"
#include "base/task/sequenced_task_runner.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/system_network_context_manager.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/extensions/extension_constants.h"
#include "components/user_manager/user.h"
#include "components/user_manager/user_manager.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/event_router.h"
#include "extensions/common/features/feature.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "services/network/public/mojom/url_loader_factory.mojom.h"
#include "services/network/public/mojom/url_response_head.mojom.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "chromeos/crosapi/mojom/wallpaper.mojom.h"
#include "chromeos/lacros/lacros_service.h"
#else
#include "chrome/browser/ash/crosapi/crosapi_ash.h"
#include "chrome/browser/ash/crosapi/crosapi_manager.h"
#include "chrome/browser/ash/crosapi/wallpaper_ash.h"
#endif
using base::Value;
using content::BrowserThread;
using FetchCallback =
base::OnceCallback<void(bool success, const std::string&)>;
namespace set_wallpaper = extensions::api::wallpaper::SetWallpaper;
namespace {
crosapi::mojom::WallpaperLayout GetMojoLayoutEnum(
extensions::api::wallpaper::WallpaperLayout layout) {
switch (layout) {
case extensions::api::wallpaper::WallpaperLayout::kStretch:
return crosapi::mojom::WallpaperLayout::kStretch;
case extensions::api::wallpaper::WallpaperLayout::kCenter:
return crosapi::mojom::WallpaperLayout::kCenter;
case extensions::api::wallpaper::WallpaperLayout::kCenterCropped:
return crosapi::mojom::WallpaperLayout::kCenterCropped;
default:
return crosapi::mojom::WallpaperLayout::kCenter;
}
}
crosapi::mojom::Wallpaper* GetWallpaperApi() {
#if BUILDFLAG(IS_CHROMEOS_LACROS)
auto* lacros_service = chromeos::LacrosService::Get();
if (!lacros_service->IsAvailable<crosapi::mojom::Wallpaper>()) {
return nullptr;
}
return lacros_service->GetRemote<crosapi::mojom::Wallpaper>().get();
#else
return crosapi::CrosapiManager::Get()->crosapi_ash()->wallpaper_ash();
#endif
}
class WallpaperFetcher {
public:
WallpaperFetcher() {}
static const char kCancelWallpaperMessage[];
void FetchWallpaper(const GURL& url, FetchCallback callback) {
CancelPreviousFetch();
original_url_ = url;
callback_ = std::move(callback);
net::NetworkTrafficAnnotationTag traffic_annotation =
net::DefineNetworkTrafficAnnotation("wallpaper_fetcher", R"(
semantics {
sender: "Wallpaper Fetcher"
description:
"Chrome OS downloads wallpaper upon user request."
trigger:
"When an app or extension requests to download "
"a wallpaper from a remote URL."
data:
"User-selected image."
destination: WEBSITE
}
policy {
cookies_allowed: YES
cookies_store: "user"
setting:
"This feature cannot be disabled by settings, but it is only "
"triggered by user request."
policy_exception_justification: "Not implemented."
})");
auto resource_request = std::make_unique<network::ResourceRequest>();
resource_request->url = original_url_;
resource_request->load_flags = net::LOAD_DISABLE_CACHE;
simple_loader_ = network::SimpleURLLoader::Create(
std::move(resource_request), traffic_annotation);
network::mojom::URLLoaderFactory* loader_factory =
g_browser_process->system_network_context_manager()
->GetURLLoaderFactory();
simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
loader_factory,
base::BindOnce(&WallpaperFetcher::OnSimpleLoaderComplete,
base::Unretained(this)));
}
private:
void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body) {
std::string response;
bool success = false;
if (response_body) {
response = std::move(*response_body);
success = true;
} else if (simple_loader_->ResponseInfo() &&
simple_loader_->ResponseInfo()->headers) {
int response_code =
simple_loader_->ResponseInfo()->headers->response_code();
response = base::StringPrintf(
"Downloading wallpaper %s failed. The response code is %d.",
original_url_.ExtractFileName().c_str(), response_code);
}
simple_loader_.reset();
std::move(callback_).Run(success, response);
}
void CancelPreviousFetch() {
if (simple_loader_.get()) {
std::move(callback_).Run(false, kCancelWallpaperMessage);
simple_loader_.reset();
}
}
GURL original_url_;
std::unique_ptr<network::SimpleURLLoader> simple_loader_;
FetchCallback callback_;
};
const char WallpaperFetcher::kCancelWallpaperMessage[] =
"Set wallpaper was canceled.";
base::LazyInstance<WallpaperFetcher>::DestructorAtExit g_wallpaper_fetcher =
LAZY_INSTANCE_INITIALIZER;
} // namespace
WallpaperSetWallpaperFunction::WallpaperSetWallpaperFunction() {
}
WallpaperSetWallpaperFunction::~WallpaperSetWallpaperFunction() {
}
ExtensionFunction::ResponseAction WallpaperSetWallpaperFunction::Run() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
params_ = set_wallpaper::Params::Create(args());
EXTENSION_FUNCTION_VALIDATE(params_);
if (params_->details.data) {
SetWallpaperOnAsh();
return RespondLater();
}
if (!params_->details.url)
return RespondNow(Error("Either url or data field is required."));
GURL wallpaper_url(*params_->details.url);
if (!wallpaper_url.is_valid())
return RespondNow(Error("URL is invalid."));
g_wallpaper_fetcher.Get().FetchWallpaper(
wallpaper_url,
base::BindOnce(&WallpaperSetWallpaperFunction::OnWallpaperFetched, this));
// FetchWallpaper() responds asynchronously.
return RespondLater();
}
void WallpaperSetWallpaperFunction::OnWallpaperFetched(
bool success,
const std::string& response) {
if (success) {
params_->details.data.emplace(response.begin(), response.end());
SetWallpaperOnAsh();
} else {
Respond(Error(response));
}
}
void WallpaperSetWallpaperFunction::OnWallpaperSetOnAsh(
const crosapi::mojom::SetWallpaperResultPtr result) {
if (result->is_thumbnail_data()) {
Respond(params_->details.thumbnail
? WithArguments(Value(std::move(result->get_thumbnail_data())))
: NoArguments());
} else {
Respond(Error(result->get_error_message()));
}
}
void WallpaperSetWallpaperFunction::SetWallpaperOnAsh() {
const extensions::Extension* ext = extension();
std::string extension_id;
std::string extension_name;
if (ext) {
extension_id = ext->id();
extension_name = ext->name();
}
crosapi::mojom::WallpaperSettingsPtr settings =
crosapi::mojom::WallpaperSettings::New();
settings->data = *params_->details.data;
settings->layout = GetMojoLayoutEnum(params_->details.layout);
settings->filename = params_->details.filename;
auto* wallpaper_api = GetWallpaperApi();
if (!wallpaper_api) {
Respond(Error("Unsupported ChromeOS version."));
return;
}
#if BUILDFLAG(IS_CHROMEOS_LACROS)
auto ash_version = chromeos::LacrosService::Get()
->GetInterfaceVersion<crosapi::mojom::Wallpaper>();
if (ash_version <
static_cast<int>(crosapi::mojom::Wallpaper::kSetWallpaperMinVersion)) {
Respond(Error("Unsupported ChromeOS version."));
return;
}
wallpaper_api->SetWallpaper(
std::move(settings), extension_id, extension_name,
base::BindOnce(&WallpaperSetWallpaperFunction::OnWallpaperSetOnAsh,
this));
#else
// Without lacros, there is never a version mismatch between this file and
// wallpaper_ash.
wallpaper_api->SetWallpaper(
std::move(settings), extension_id, extension_name,
base::BindOnce(&WallpaperSetWallpaperFunction::OnWallpaperSetOnAsh,
this));
#endif
}