| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/chromeos/backdrop_wallpaper_handlers/backdrop_wallpaper_handlers.h" |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/chromeos/backdrop_wallpaper_handlers/backdrop_wallpaper.pb.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chrome/common/extensions/api/wallpaper_private.h" |
| #include "chromeos/constants/chromeos_features.h" |
| #include "chromeos/constants/chromeos_switches.h" |
| #include "chromeos/constants/devicetype.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "net/base/load_flags.h" |
| #include "url/gurl.h" |
| |
| namespace { |
| |
| // The MIME type of the POST data sent to the server. |
| constexpr char kProtoMimeType[] = "application/x-protobuf"; |
| |
| // The url to download the proto of the complete list of wallpaper collections. |
| constexpr char kBackdropCollectionsUrl[] = |
| "https://clients3.google.com/cast/chromecast/home/wallpaper/" |
| "collections?rt=b"; |
| |
| // The url to download the proto of a specific wallpaper collection. |
| constexpr char kBackdropImagesUrl[] = |
| "https://clients3.google.com/cast/chromecast/home/wallpaper/" |
| "collection-images?rt=b"; |
| |
| // The url to download the proto of the info of a surprise me wallpaper. |
| constexpr char kBackdropSurpriseMeImageUrl[] = |
| "https://clients3.google.com/cast/chromecast/home/wallpaper/" |
| "image?rt=b"; |
| |
| // The label used to return exclusive content or filter unwanted images. |
| constexpr char kFilteringLabel[] = "chromebook"; |
| |
| // The label used to return exclusive content for Google branded chromebooks. |
| constexpr char kGoogleDeviceFilteringLabel[] = "google_branded_chromebook"; |
| |
| // Returns the corresponding test url if |kTestWallpaperServer| is present, |
| // otherwise returns |url| as is. See https://crbug.com/914144. |
| std::string MaybeConvertToTestUrl(std::string url) { |
| if (base::CommandLine::ForCurrentProcess()->HasSwitch( |
| chromeos::switches::kTestWallpaperServer)) { |
| base::ReplaceFirstSubstringAfterOffset(&url, 0, "clients3", |
| "chromecast-dev.sandbox"); |
| } else if (base::FeatureList::IsEnabled( |
| chromeos::features::kUseWallpaperStagingUrl)) { |
| base::ReplaceFirstSubstringAfterOffset(&url, 0, "clients3", |
| "chromecast-staging.sandbox"); |
| } |
| return url; |
| } |
| |
| } // namespace |
| |
| namespace backdrop_wallpaper_handlers { |
| |
| // Helper class for handling Backdrop service POST requests. |
| class BackdropFetcher { |
| public: |
| using OnFetchComplete = base::OnceCallback<void(const std::string& response)>; |
| |
| BackdropFetcher() = default; |
| ~BackdropFetcher() = default; |
| |
| // Starts downloading the proto. |request_body| is a serialized proto and |
| // will be used as the upload body. |
| void Start(const GURL& url, |
| const std::string& request_body, |
| const net::NetworkTrafficAnnotationTag& traffic_annotation, |
| OnFetchComplete callback) { |
| DCHECK(!simple_loader_ && callback_.is_null()); |
| callback_ = std::move(callback); |
| |
| SystemNetworkContextManager* system_network_context_manager = |
| g_browser_process->system_network_context_manager(); |
| // In unit tests, the browser process can return a null context manager. |
| if (!system_network_context_manager) { |
| std::move(callback_).Run(std::string()); |
| return; |
| } |
| |
| network::mojom::URLLoaderFactory* loader_factory = |
| system_network_context_manager->GetURLLoaderFactory(); |
| |
| auto resource_request = std::make_unique<network::ResourceRequest>(); |
| resource_request->url = url; |
| resource_request->method = "POST"; |
| resource_request->load_flags = |
| net::LOAD_BYPASS_CACHE | net::LOAD_DISABLE_CACHE; |
| resource_request->credentials_mode = network::mojom::CredentialsMode::kOmit; |
| |
| simple_loader_ = network::SimpleURLLoader::Create( |
| std::move(resource_request), traffic_annotation); |
| simple_loader_->AttachStringForUpload(request_body, kProtoMimeType); |
| // |base::Unretained| is safe because this instance outlives |
| // |simple_loader_|. |
| simple_loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie( |
| loader_factory, base::BindOnce(&BackdropFetcher::OnURLFetchComplete, |
| base::Unretained(this))); |
| } |
| |
| private: |
| // Called when the download completes. |
| void OnURLFetchComplete(std::unique_ptr<std::string> response_body) { |
| if (!response_body) { |
| int response_code = -1; |
| if (simple_loader_->ResponseInfo() && |
| simple_loader_->ResponseInfo()->headers) { |
| response_code = |
| simple_loader_->ResponseInfo()->headers->response_code(); |
| } |
| |
| LOG(ERROR) << "Downloading Backdrop wallpaper proto failed with error " |
| "code: " |
| << response_code; |
| simple_loader_.reset(); |
| std::move(callback_).Run(std::string()); |
| return; |
| } |
| |
| simple_loader_.reset(); |
| std::move(callback_).Run(*response_body); |
| } |
| |
| // The url loader for the Backdrop service request. |
| std::unique_ptr<network::SimpleURLLoader> simple_loader_; |
| |
| // The fetcher request callback. |
| OnFetchComplete callback_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BackdropFetcher); |
| }; |
| |
| CollectionInfoFetcher::CollectionInfoFetcher() { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| CollectionInfoFetcher::~CollectionInfoFetcher() = default; |
| |
| void CollectionInfoFetcher::Start(OnCollectionsInfoFetched callback) { |
| DCHECK(!backdrop_fetcher_ && callback_.is_null()); |
| callback_ = std::move(callback); |
| backdrop_fetcher_ = std::make_unique<BackdropFetcher>(); |
| |
| backdrop::GetCollectionsRequest request; |
| // The language field may include the country code (e.g. "en-US"). |
| request.set_language(g_browser_process->GetApplicationLocale()); |
| request.add_filtering_label(kFilteringLabel); |
| if (chromeos::IsGoogleBrandedDevice()) |
| request.add_filtering_label(kGoogleDeviceFilteringLabel); |
| std::string serialized_proto; |
| request.SerializeToString(&serialized_proto); |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("backdrop_collection_names_download", |
| R"( |
| semantics { |
| sender: "ChromeOS Wallpaper Picker" |
| description: |
| "The ChromeOS Wallpaper Picker extension displays a rich set of " |
| "wallpapers for users to choose from. Each wallpaper belongs to a " |
| "collection (e.g. Arts, Landscape etc.). The list of all available " |
| "collections is downloaded from the Backdrop wallpaper service." |
| trigger: |
| "When ChromeOS Wallpaper Picker extension is open, and " |
| "BUILDFLAG(GOOGLE_CHROME_BRANDING) is defined." |
| data: |
| "The Backdrop protocol buffer messages. No user data is included." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "NA" |
| policy_exception_justification: |
| "Not implemented, considered not necessary." |
| })"); |
| |
| // |base::Unretained| is safe because this instance outlives |
| // |backdrop_fetcher_|. |
| backdrop_fetcher_->Start( |
| GURL(MaybeConvertToTestUrl(kBackdropCollectionsUrl)), serialized_proto, |
| traffic_annotation, |
| base::BindOnce(&CollectionInfoFetcher::OnResponseFetched, |
| base::Unretained(this))); |
| } |
| |
| void CollectionInfoFetcher::OnResponseFetched(const std::string& response) { |
| std::vector<backdrop::Collection> collections; |
| backdrop::GetCollectionsResponse collections_response; |
| bool success = false; |
| if (response.empty() || !collections_response.ParseFromString(response)) { |
| LOG(ERROR) << "Deserializing Backdrop wallpaper proto for collection info " |
| "failed."; |
| } else { |
| success = true; |
| for (const auto& collection : collections_response.collections()) |
| collections.push_back(collection); |
| } |
| |
| backdrop_fetcher_.reset(); |
| std::move(callback_).Run(success, collections); |
| } |
| |
| ImageInfoFetcher::ImageInfoFetcher(const std::string& collection_id) |
| : collection_id_(collection_id) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| ImageInfoFetcher::~ImageInfoFetcher() = default; |
| |
| void ImageInfoFetcher::Start(OnImagesInfoFetched callback) { |
| DCHECK(!backdrop_fetcher_ && callback_.is_null()); |
| callback_ = std::move(callback); |
| backdrop_fetcher_ = std::make_unique<BackdropFetcher>(); |
| |
| backdrop::GetImagesInCollectionRequest request; |
| // The language field may include the country code (e.g. "en-US"). |
| request.set_language(g_browser_process->GetApplicationLocale()); |
| request.set_collection_id(collection_id_); |
| request.add_filtering_label(kFilteringLabel); |
| if (chromeos::IsGoogleBrandedDevice()) |
| request.add_filtering_label(kGoogleDeviceFilteringLabel); |
| std::string serialized_proto; |
| request.SerializeToString(&serialized_proto); |
| |
| net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("backdrop_images_info_download", R"( |
| semantics { |
| sender: "ChromeOS Wallpaper Picker" |
| description: |
| "When user clicks on a particular wallpaper collection on the " |
| "ChromeOS Wallpaper Picker, it displays the preview of the iamges " |
| "and descriptive texts for each image. Such information is " |
| "downloaded from the Backdrop wallpaper service." |
| trigger: |
| "When ChromeOS Wallpaper Picker extension is open, " |
| "BUILDFLAG(GOOGLE_CHROME_BRANDING) is defined and user clicks on a " |
| "particular collection." |
| data: |
| "The Backdrop protocol buffer messages. No user data is included." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "NA" |
| policy_exception_justification: |
| "Not implemented, considered not necessary." |
| })"); |
| |
| // |base::Unretained| is safe because this instance outlives |
| // |backdrop_fetcher_|. |
| backdrop_fetcher_->Start(GURL(MaybeConvertToTestUrl(kBackdropImagesUrl)), |
| serialized_proto, traffic_annotation, |
| base::BindOnce(&ImageInfoFetcher::OnResponseFetched, |
| base::Unretained(this))); |
| } |
| |
| void ImageInfoFetcher::OnResponseFetched(const std::string& response) { |
| std::vector<backdrop::Image> images; |
| backdrop::GetImagesInCollectionResponse images_response; |
| bool success = false; |
| if (response.empty() || !images_response.ParseFromString(response)) { |
| LOG(ERROR) << "Deserializing Backdrop wallpaper proto for collection " |
| << collection_id_ << " failed"; |
| } else { |
| success = true; |
| for (const auto& image : images_response.images()) |
| images.push_back(image); |
| } |
| |
| backdrop_fetcher_.reset(); |
| std::move(callback_).Run(success, images); |
| } |
| |
| SurpriseMeImageFetcher::SurpriseMeImageFetcher(const std::string& collection_id, |
| const std::string& resume_token) |
| : collection_id_(collection_id), resume_token_(resume_token) { |
| DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
| } |
| |
| SurpriseMeImageFetcher::~SurpriseMeImageFetcher() = default; |
| |
| void SurpriseMeImageFetcher::Start(OnSurpriseMeImageFetched callback) { |
| DCHECK(!backdrop_fetcher_ && callback_.is_null()); |
| callback_ = std::move(callback); |
| backdrop_fetcher_ = std::make_unique<BackdropFetcher>(); |
| |
| backdrop::GetImageFromCollectionRequest request; |
| // The language field may include the country code (e.g. "en-US"). |
| request.set_language(g_browser_process->GetApplicationLocale()); |
| request.add_collection_ids(collection_id_); |
| request.add_filtering_label(kFilteringLabel); |
| if (chromeos::IsGoogleBrandedDevice()) |
| request.add_filtering_label(kGoogleDeviceFilteringLabel); |
| if (!resume_token_.empty()) |
| request.set_resume_token(resume_token_); |
| std::string serialized_proto; |
| request.SerializeToString(&serialized_proto); |
| |
| const net::NetworkTrafficAnnotationTag traffic_annotation = |
| net::DefineNetworkTrafficAnnotation("backdrop_surprise_me_image_download", |
| R"( |
| semantics { |
| sender: "ChromeOS Wallpaper Picker" |
| description: |
| "POST request that fetches information about the wallpaper that " |
| "should be set next for the user that enabled surprise me feature " |
| "in the Chrome OS Wallpaper Picker. For these users, wallpaper is " |
| "periodically changed to a random wallpaper selected by the " |
| "Backdrop wallpaper service." |
| trigger: |
| "When ChromeOS Wallpaper Picker extension is open, " |
| "BUILDFLAG(GOOGLE_CHROME_BRANDING) is defined and user turns on " |
| "the surprise me feature." |
| data: |
| "The Backdrop protocol buffer messages. No user data is included." |
| destination: GOOGLE_OWNED_SERVICE |
| } |
| policy { |
| cookies_allowed: NO |
| setting: |
| "NA" |
| policy_exception_justification: |
| "Not implemented, considered not necessary." |
| })"); |
| |
| // |base::Unretained| is safe because this instance outlives |
| // |backdrop_fetcher_|. |
| backdrop_fetcher_->Start( |
| GURL(MaybeConvertToTestUrl(kBackdropSurpriseMeImageUrl)), |
| serialized_proto, traffic_annotation, |
| base::BindOnce(&SurpriseMeImageFetcher::OnResponseFetched, |
| base::Unretained(this))); |
| } |
| |
| void SurpriseMeImageFetcher::OnResponseFetched(const std::string& response) { |
| backdrop::GetImageFromCollectionResponse surprise_me_image_response; |
| if (response.empty() || |
| !surprise_me_image_response.ParseFromString(response)) { |
| LOG(ERROR) << "Deserializing surprise me wallpaper proto for collection " |
| << collection_id_ << " failed"; |
| backdrop_fetcher_.reset(); |
| std::move(callback_).Run(/*success=*/false, backdrop::Image(), |
| std::string()); |
| return; |
| } |
| |
| backdrop_fetcher_.reset(); |
| std::move(callback_).Run(/*success=*/true, surprise_me_image_response.image(), |
| surprise_me_image_response.resume_token()); |
| } |
| |
| } // namespace backdrop_wallpaper_handlers |