blob: 9458e1ecc72f626a463c53b2194c124f1a07f9e9 [file] [log] [blame]
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/commerce/core/webui/shopping_service_handler.h"
#include <memory>
#include <vector>
#include "base/check_is_test.h"
#include "base/memory/weak_ptr.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/uuid.h"
#include "components/bookmarks/browser/bookmark_node.h"
#include "components/commerce/core/commerce_constants.h"
#include "components/commerce/core/commerce_feature_list.h"
#include "components/commerce/core/metrics/metrics_utils.h"
#include "components/commerce/core/price_tracking_utils.h"
#include "components/commerce/core/shopping_service.h"
#include "components/commerce/core/subscriptions/commerce_subscription.h"
#include "components/commerce/core/webui/webui_utils.h"
#include "components/feature_engagement/public/tracker.h"
#include "components/payments/core/currency_formatter.h"
#include "components/power_bookmarks/core/power_bookmark_utils.h"
#include "components/power_bookmarks/core/proto/power_bookmark_meta.pb.h"
#include "components/power_bookmarks/core/proto/shopping_specifics.pb.h"
#include "components/prefs/pref_service.h"
#include "components/url_formatter/elide_url.h"
#include "url/gurl.h"
namespace commerce {
namespace {
shopping_service::mojom::BookmarkProductInfoPtr BookmarkNodeToMojoProduct(
bookmarks::BookmarkModel& model,
const bookmarks::BookmarkNode* node,
const std::string& locale) {
auto bookmark_info = shopping_service::mojom::BookmarkProductInfo::New();
bookmark_info->bookmark_id = node->id();
std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
power_bookmarks::GetNodePowerBookmarkMeta(&model, node);
const power_bookmarks::ShoppingSpecifics specifics =
meta->shopping_specifics();
bookmark_info->info = shopping_service::mojom::ProductInfo::New();
bookmark_info->info->title = specifics.title();
bookmark_info->info->domain = base::UTF16ToUTF8(
url_formatter::FormatUrlForDisplayOmitSchemePathAndTrivialSubdomains(
GURL(node->url())));
bookmark_info->info->product_url = node->url();
bookmark_info->info->image_url = GURL(meta->lead_image().url());
bookmark_info->info->cluster_id = specifics.product_cluster_id();
const power_bookmarks::ProductPrice price = specifics.current_price();
std::string currency_code = price.currency_code();
std::unique_ptr<payments::CurrencyFormatter> formatter =
std::make_unique<payments::CurrencyFormatter>(currency_code, locale);
formatter->SetMaxFractionalDigits(2);
bookmark_info->info->current_price =
base::UTF16ToUTF8(formatter->Format(base::NumberToString(
static_cast<float>(price.amount_micros()) / kToMicroCurrency)));
// Only send the previous price if it is higher than the current price. This
// is exclusively used to decide whether to show the price drop chip in the
// UI.
if (specifics.has_previous_price() &&
specifics.previous_price().amount_micros() >
specifics.current_price().amount_micros()) {
const power_bookmarks::ProductPrice previous_price =
specifics.previous_price();
bookmark_info->info->previous_price =
base::UTF16ToUTF8(formatter->Format(base::NumberToString(
static_cast<float>(previous_price.amount_micros()) /
kToMicroCurrency)));
}
return bookmark_info;
}
std::vector<shopping_service::mojom::UrlInfoPtr> UrlInfoToMojo(
const std::vector<UrlInfo>& url_infos) {
std::vector<shopping_service::mojom::UrlInfoPtr> url_info_ptr_list;
for (const UrlInfo& url_info : url_infos) {
auto url_info_ptr = shopping_service::mojom::UrlInfo::New();
url_info_ptr->url = url_info.url;
url_info_ptr->title = base::UTF16ToUTF8(url_info.title);
url_info_ptr_list.push_back(std::move(url_info_ptr));
}
return url_info_ptr_list;
}
shopping_service::mojom::PriceInsightsInfoPtr PriceInsightsInfoToMojoObject(
const std::optional<PriceInsightsInfo>& info,
const std::string& locale) {
auto insights_info = shopping_service::mojom::PriceInsightsInfo::New();
if (!info.has_value()) {
return insights_info;
}
if (info->product_cluster_id.has_value()) {
insights_info->cluster_id = info->product_cluster_id.value();
} else {
CHECK_IS_TEST();
}
std::unique_ptr<payments::CurrencyFormatter> formatter =
std::make_unique<payments::CurrencyFormatter>(info->currency_code,
locale);
formatter->SetMaxFractionalDigits(2);
if (info->typical_low_price_micros.has_value() &&
info->typical_high_price_micros.has_value()) {
insights_info->typical_low_price =
base::UTF16ToUTF8(formatter->Format(base::NumberToString(
static_cast<float>(info->typical_low_price_micros.value()) /
kToMicroCurrency)));
insights_info->typical_high_price =
base::UTF16ToUTF8(formatter->Format(base::NumberToString(
static_cast<float>(info->typical_high_price_micros.value()) /
kToMicroCurrency)));
}
if (info->catalog_attributes.has_value()) {
insights_info->catalog_attributes = info->catalog_attributes.value();
}
if (info->jackpot_url.has_value()) {
insights_info->jackpot = info->jackpot_url.value();
}
shopping_service::mojom::PriceInsightsInfo::PriceBucket bucket;
switch (info->price_bucket) {
case PriceBucket::kUnknown:
bucket =
shopping_service::mojom::PriceInsightsInfo::PriceBucket::kUnknown;
break;
case PriceBucket::kLowPrice:
bucket = shopping_service::mojom::PriceInsightsInfo::PriceBucket::kLow;
break;
case PriceBucket::kTypicalPrice:
bucket =
shopping_service::mojom::PriceInsightsInfo::PriceBucket::kTypical;
break;
case PriceBucket::kHighPrice:
bucket = shopping_service::mojom::PriceInsightsInfo::PriceBucket::kHigh;
break;
}
insights_info->bucket = bucket;
insights_info->has_multiple_catalogs = info->has_multiple_catalogs;
for (auto history_price : info->catalog_history_prices) {
auto point = shopping_service::mojom::PricePoint::New();
point->date = std::get<0>(history_price);
auto price =
static_cast<float>(std::get<1>(history_price)) / kToMicroCurrency;
point->price = price;
point->formatted_price =
base::UTF16ToUTF8(formatter->Format(base::NumberToString(price)));
insights_info->history.push_back(std::move(point));
}
insights_info->locale = locale;
insights_info->currency_code = info->currency_code;
return insights_info;
}
shopping_service::mojom::ProductSpecificationsPtr ProductSpecsToMojo(
const ProductSpecifications& specs) {
auto specs_ptr = shopping_service::mojom::ProductSpecifications::New();
for (const auto& [id, name] : specs.product_dimension_map) {
specs_ptr->product_dimension_map[id] = name;
}
for (const auto& product : specs.products) {
auto product_ptr =
shopping_service::mojom::ProductSpecificationsProduct::New();
product_ptr->product_cluster_id = product.product_cluster_id;
product_ptr->title = product.title;
product_ptr->image_url = product.image_url;
product_ptr->summary = product.summary;
for (const auto& [dimen_id, value] : product.product_dimension_values) {
product_ptr->product_dimension_values[dimen_id] = value;
}
specs_ptr->products.push_back(std::move(product_ptr));
}
return specs_ptr;
}
shopping_service::mojom::ProductSpecificationsSetPtr ProductSpecsSetToMojo(
const ProductSpecificationsSet& set) {
auto set_ptr = shopping_service::mojom::ProductSpecificationsSet::New();
set_ptr->name = set.name();
set_ptr->uuid = set.uuid();
for (const auto& url : set.urls()) {
set_ptr->urls.push_back(url);
}
return set_ptr;
}
} // namespace
using shopping_service::mojom::BookmarkProductInfo;
using shopping_service::mojom::BookmarkProductInfoPtr;
ShoppingServiceHandler::ShoppingServiceHandler(
mojo::PendingRemote<shopping_service::mojom::Page> remote_page,
mojo::PendingReceiver<
shopping_service::mojom::ShoppingServiceHandler> receiver,
bookmarks::BookmarkModel* bookmark_model,
ShoppingService* shopping_service,
PrefService* prefs,
feature_engagement::Tracker* tracker,
std::unique_ptr<Delegate> delegate)
: remote_page_(std::move(remote_page)),
receiver_(this, std::move(receiver)),
bookmark_model_(bookmark_model),
shopping_service_(shopping_service),
pref_service_(prefs),
tracker_(tracker),
delegate_(std::move(delegate)) {
scoped_subscriptions_observation_.Observe(shopping_service_);
scoped_bookmark_model_observation_.Observe(bookmark_model_);
// It is safe to schedule updates and observe bookmarks. If the feature is
// disabled, no new information will be fetched or provided to the frontend.
shopping_service_->ScheduleSavedProductUpdate();
if (shopping_service_->GetAccountChecker()) {
locale_ = shopping_service_->GetAccountChecker()->GetLocale();
}
}
ShoppingServiceHandler::~ShoppingServiceHandler() = default;
void ShoppingServiceHandler::GetAllPriceTrackedBookmarkProductInfo(
GetAllPriceTrackedBookmarkProductInfoCallback callback) {
shopping_service_->WaitForReady(base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetAllPriceTrackedBookmarkProductInfoCallback callback,
ShoppingService* service) {
if (!service || !service->IsShoppingListEligible() ||
handler.WasInvalidated()) {
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
std::vector<BookmarkProductInfoPtr>()));
return;
}
service->GetAllPriceTrackedBookmarks(
base::BindOnce(
&ShoppingServiceHandler::OnFetchPriceTrackedBookmarks,
handler, std::move(callback)));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::OnFetchPriceTrackedBookmarks(
GetAllPriceTrackedBookmarkProductInfoCallback callback,
std::vector<const bookmarks::BookmarkNode*> bookmarks) {
std::vector<BookmarkProductInfoPtr> info_list =
BookmarkListToMojoList(*bookmark_model_, bookmarks, locale_);
if (!info_list.empty()) {
// Record usage for price tracking promo.
tracker_->NotifyEvent("price_tracking_side_panel_shown");
}
std::move(callback).Run(std::move(info_list));
}
void ShoppingServiceHandler::GetAllShoppingBookmarkProductInfo(
GetAllShoppingBookmarkProductInfoCallback callback) {
shopping_service_->WaitForReady(base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetAllShoppingBookmarkProductInfoCallback callback,
ShoppingService* service) {
if (!service || !service->IsShoppingListEligible() ||
handler.WasInvalidated()) {
std::move(callback).Run({});
return;
}
std::vector<const bookmarks::BookmarkNode*> bookmarks =
service->GetAllShoppingBookmarks();
std::vector<BookmarkProductInfoPtr> info_list = BookmarkListToMojoList(
*(handler->bookmark_model_), bookmarks, handler->locale_);
std::move(callback).Run(std::move(info_list));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::TrackPriceForBookmark(int64_t bookmark_id) {
commerce::SetPriceTrackingStateForBookmark(
shopping_service_, bookmark_model_,
bookmarks::GetBookmarkNodeByID(bookmark_model_, bookmark_id), true,
base::BindOnce(&ShoppingServiceHandler::onPriceTrackResult,
weak_ptr_factory_.GetWeakPtr(), bookmark_id,
bookmark_model_, true));
}
void ShoppingServiceHandler::UntrackPriceForBookmark(int64_t bookmark_id) {
commerce::SetPriceTrackingStateForBookmark(
shopping_service_, bookmark_model_,
bookmarks::GetBookmarkNodeByID(bookmark_model_, bookmark_id), false,
base::BindOnce(&ShoppingServiceHandler::onPriceTrackResult,
weak_ptr_factory_.GetWeakPtr(), bookmark_id,
bookmark_model_, false));
}
void ShoppingServiceHandler::OnSubscribe(
const CommerceSubscription& subscription,
bool succeeded) {
if (succeeded) {
HandleSubscriptionChange(subscription, true);
}
}
void ShoppingServiceHandler::OnUnsubscribe(
const CommerceSubscription& subscription,
bool succeeded) {
if (succeeded) {
HandleSubscriptionChange(subscription, false);
}
}
void ShoppingServiceHandler::BookmarkModelChanged() {}
void ShoppingServiceHandler::BookmarkNodeMoved(
const bookmarks::BookmarkNode* old_parent,
size_t old_index,
const bookmarks::BookmarkNode* new_parent,
size_t new_index) {
const bookmarks::BookmarkNode* node = new_parent->children()[new_index].get();
if (!node) {
return;
}
std::unique_ptr<power_bookmarks::PowerBookmarkMeta> meta =
power_bookmarks::GetNodePowerBookmarkMeta(bookmark_model_, node);
if (!meta || !meta->has_shopping_specifics() ||
!meta->shopping_specifics().has_product_cluster_id()) {
return;
}
remote_page_->OnProductBookmarkMoved(
BookmarkNodeToMojoProduct(*bookmark_model_, node, locale_));
}
void ShoppingServiceHandler::HandleSubscriptionChange(
const CommerceSubscription& sub,
bool is_tracking) {
if (sub.id_type != IdentifierType::kProductClusterId) {
return;
}
uint64_t cluster_id;
if (!base::StringToUint64(sub.id, &cluster_id)) {
return;
}
std::vector<const bookmarks::BookmarkNode*> bookmarks =
GetBookmarksWithClusterId(bookmark_model_, cluster_id);
// Special handling when the unsubscription is caused by bookmark deletion and
// therefore the bookmark can no longer be retrieved.
// TODO(crbug.com/40066977): Update mojo call to pass cluster ID and make
// BookmarkProductInfo a nullable parameter.
if (!bookmarks.size()) {
auto bookmark_info = shopping_service::mojom::BookmarkProductInfo::New();
bookmark_info->info = shopping_service::mojom::ProductInfo::New();
bookmark_info->info->cluster_id = cluster_id;
remote_page_->PriceUntrackedForBookmark(std::move(bookmark_info));
return;
}
for (auto* node : bookmarks) {
auto product = BookmarkNodeToMojoProduct(*bookmark_model_, node, locale_);
if (is_tracking) {
remote_page_->PriceTrackedForBookmark(std::move(product));
} else {
remote_page_->PriceUntrackedForBookmark(std::move(product));
}
}
}
std::vector<BookmarkProductInfoPtr>
ShoppingServiceHandler::BookmarkListToMojoList(
bookmarks::BookmarkModel& model,
const std::vector<const bookmarks::BookmarkNode*>& bookmarks,
const std::string& locale) {
std::vector<BookmarkProductInfoPtr> info_list;
for (const bookmarks::BookmarkNode* node : bookmarks) {
info_list.push_back(BookmarkNodeToMojoProduct(model, node, locale));
}
return info_list;
}
void ShoppingServiceHandler::onPriceTrackResult(int64_t bookmark_id,
bookmarks::BookmarkModel* model,
bool is_tracking,
bool success) {
if (success)
return;
// We only do work here if price tracking failed. When the UI is interacted
// with, we assume success. In the event it failed, we switch things back.
// So in this case, if we were trying to untrack and that action failed, set
// the UI back to "tracking".
auto* node = bookmarks::GetBookmarkNodeByID(bookmark_model_, bookmark_id);
auto product = BookmarkNodeToMojoProduct(*bookmark_model_, node, locale_);
if (!is_tracking) {
remote_page_->PriceTrackedForBookmark(std::move(product));
} else {
remote_page_->PriceUntrackedForBookmark(std::move(product));
}
// Pass in whether the failed operation was to track or untrack price. It
// should be the reverse of the current tracking status since the operation
// failed.
remote_page_->OperationFailedForBookmark(
BookmarkNodeToMojoProduct(*bookmark_model_, node, locale_), is_tracking);
}
void ShoppingServiceHandler::GetProductInfoForCurrentUrl(
GetProductInfoForCurrentUrlCallback callback) {
if (!shopping_service_->IsPriceInsightsEligible() || !delegate_ ||
!delegate_->GetCurrentTabUrl().has_value()) {
std::move(callback).Run(shopping_service::mojom::ProductInfo::New());
return;
}
shopping_service_->GetProductInfoForUrl(
delegate_->GetCurrentTabUrl().value(),
base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetProductInfoForCurrentUrlCallback callback, const GURL& url,
const std::optional<const ProductInfo>& info) {
if (!handler) {
std::move(callback).Run(
shopping_service::mojom::ProductInfo::New());
return;
}
std::move(callback).Run(
ProductInfoToMojoProduct(url, info, handler->locale_));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::GetProductInfoForUrl(
const GURL& url,
GetProductInfoForUrlCallback callback) {
shopping_service_->GetProductInfoForUrl(
url, base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetProductInfoForUrlCallback callback, const GURL& url,
const std::optional<const ProductInfo>& info) {
if (!handler) {
std::move(callback).Run(
url, shopping_service::mojom::ProductInfo::New());
return;
}
std::move(callback).Run(url, ProductInfoToMojoProduct(
url, info, handler->locale_));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::IsShoppingListEligible(
IsShoppingListEligibleCallback callback) {
std::move(callback).Run(shopping_service_->IsShoppingListEligible());
}
void ShoppingServiceHandler::GetShoppingCollectionBookmarkFolderId(
GetShoppingCollectionBookmarkFolderIdCallback callback) {
const bookmarks::BookmarkNode* collection =
commerce::GetShoppingCollectionBookmarkFolder(bookmark_model_);
std::move(callback).Run(collection ? collection->id() : -1);
}
void ShoppingServiceHandler::GetPriceTrackingStatusForCurrentUrl(
GetPriceTrackingStatusForCurrentUrlCallback callback) {
// The URL may or may not have a bookmark associated with it. Prioritize
// accessing the product info for the URL before looking at an existing
// bookmark.
shopping_service_->GetProductInfoForUrl(
delegate_->GetCurrentTabUrl().value(),
base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetPriceTrackingStatusForCurrentUrlCallback callback,
const GURL& url, const std::optional<const ProductInfo>& info) {
if (!info.has_value() || !info->product_cluster_id.has_value() ||
!handler || !CanTrackPrice(info)) {
std::move(callback).Run(false);
return;
}
CommerceSubscription sub(
SubscriptionType::kPriceTrack,
IdentifierType::kProductClusterId,
base::NumberToString(info->product_cluster_id.value()),
ManagementType::kUserManaged);
handler->shopping_service_->IsSubscribed(
sub,
base::BindOnce(
[](GetPriceTrackingStatusForCurrentUrlCallback callback,
bool subscribed) {
std::move(callback).Run(subscribed);
},
std::move(callback)));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::SetPriceTrackingStatusForCurrentUrl(bool track) {
if (track) {
// If the product on the page isn't already tracked, create a bookmark for
// it and start tracking.
TrackPriceForBookmark(delegate_->GetOrAddBookmarkForCurrentUrl()->id());
commerce::metrics::RecordShoppingActionUKM(
delegate_->GetCurrentTabUkmSourceId(),
commerce::metrics::ShoppingAction::kPriceTracked);
} else {
// If the product is already tracked, there must be a bookmark, but it's not
// necessarily the page the user is currently on (i.e. multi-merchant
// tracking). Prioritize accessing the product info for the URL before
// attempting to access the bookmark.
base::OnceCallback<void(uint64_t)> unsubscribe = base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler, uint64_t id) {
if (!handler) {
return;
}
commerce::SetPriceTrackingStateForClusterId(
handler->shopping_service_, handler->bookmark_model_, id, false,
base::BindOnce([](bool success) {}));
},
weak_ptr_factory_.GetWeakPtr());
shopping_service_->GetProductInfoForUrl(
delegate_->GetCurrentTabUrl().value(),
base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
base::OnceCallback<void(uint64_t)> unsubscribe, const GURL& url,
const std::optional<const ProductInfo>& info) {
if (!handler) {
return;
}
if (!info.has_value() || !info->product_cluster_id.has_value()) {
std::optional<uint64_t> cluster_id =
GetProductClusterIdFromBookmark(url,
handler->bookmark_model_);
if (cluster_id.has_value()) {
std::move(unsubscribe).Run(cluster_id.value());
}
return;
}
std::move(unsubscribe).Run(info->product_cluster_id.value());
},
weak_ptr_factory_.GetWeakPtr(), std::move(unsubscribe)));
}
}
void ShoppingServiceHandler::GetParentBookmarkFolderNameForCurrentUrl(
GetParentBookmarkFolderNameForCurrentUrlCallback callback) {
const GURL current_url = delegate_->GetCurrentTabUrl().value();
std::move(callback).Run(
commerce::GetBookmarkParentName(bookmark_model_, current_url)
.value_or(std::u16string()));
}
void ShoppingServiceHandler::ShowBookmarkEditorForCurrentUrl() {
delegate_->ShowBookmarkEditorForCurrentUrl();
}
void ShoppingServiceHandler::GetPriceInsightsInfoForCurrentUrl(
GetPriceInsightsInfoForCurrentUrlCallback callback) {
if (!shopping_service_->IsPriceInsightsEligible() || !delegate_ ||
!delegate_->GetCurrentTabUrl().has_value()) {
std::move(callback).Run(shopping_service::mojom::PriceInsightsInfo::New());
return;
}
shopping_service_->GetPriceInsightsInfoForUrl(
delegate_->GetCurrentTabUrl().value(),
base::BindOnce(
&ShoppingServiceHandler::OnFetchPriceInsightsInfoForCurrentUrl,
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::GetProductSpecificationsForUrls(
const std::vector<::GURL>& urls,
GetProductSpecificationsForUrlsCallback callback) {
if (!shopping_service_ || urls.empty()) {
std::move(callback).Run(
shopping_service::mojom::ProductSpecifications::New());
return;
}
shopping_service_->GetProductSpecificationsForUrls(
urls, base::BindOnce(
[](base::WeakPtr<ShoppingServiceHandler> handler,
GetProductSpecificationsForUrlsCallback callback,
std::vector<uint64_t> ids,
std::optional<ProductSpecifications> specs) {
if (!handler || !specs.has_value()) {
std::move(callback).Run(
shopping_service::mojom::ProductSpecifications::New());
return;
}
std::move(callback).Run(ProductSpecsToMojo(specs.value()));
},
weak_ptr_factory_.GetWeakPtr(), std::move(callback)));
}
void ShoppingServiceHandler::GetUrlInfosForOpenTabs(
GetUrlInfosForOpenTabsCallback callback) {
if (!shopping_service_) {
std::move(callback).Run({});
return;
}
std::move(callback).Run(
UrlInfoToMojo(shopping_service_->GetUrlInfosForActiveWebWrappers()));
}
void ShoppingServiceHandler::GetUrlInfosForRecentlyViewedTabs(
GetUrlInfosForRecentlyViewedTabsCallback callback) {
if (!shopping_service_) {
std::move(callback).Run({});
return;
}
std::move(callback).Run(UrlInfoToMojo(
shopping_service_->GetUrlInfosForRecentlyViewedWebWrappers()));
}
void ShoppingServiceHandler::OnFetchPriceInsightsInfoForCurrentUrl(
GetPriceInsightsInfoForCurrentUrlCallback callback,
const GURL& url,
const std::optional<PriceInsightsInfo>& info) {
std::move(callback).Run(PriceInsightsInfoToMojoObject(info, locale_));
}
void ShoppingServiceHandler::ShowInsightsSidePanelUI() {
if (delegate_) {
delegate_->ShowInsightsSidePanelUI();
}
}
void ShoppingServiceHandler::OnGetPriceTrackingStatusForCurrentUrl(
GetPriceTrackingStatusForCurrentUrlCallback callback,
bool tracked) {
std::move(callback).Run(tracked);
}
void ShoppingServiceHandler::OpenUrlInNewTab(const GURL& url) {
if (delegate_) {
delegate_->OpenUrlInNewTab(url);
}
}
void ShoppingServiceHandler::ShowFeedback() {
if (delegate_) {
delegate_->ShowFeedback();
}
}
void ShoppingServiceHandler::GetAllProductSpecificationsSets(
GetAllProductSpecificationsSetsCallback callback) {
if (!shopping_service_ ||
!shopping_service_->GetProductSpecificationsService()) {
std::move(callback).Run({});
return;
}
const auto all_sets = shopping_service_->GetProductSpecificationsService()
->GetAllProductSpecifications();
std::vector<shopping_service::mojom::ProductSpecificationsSetPtr>
all_sets_mojo;
for (const auto& set : all_sets) {
all_sets_mojo.push_back(ProductSpecsSetToMojo(set));
}
std::move(callback).Run(std::move(all_sets_mojo));
}
void ShoppingServiceHandler::AddProductSpecificationsSet(
const std::string& name,
const std::vector<GURL>& urls,
AddProductSpecificationsSetCallback callback) {
if (!shopping_service_ ||
!shopping_service_->GetProductSpecificationsService()) {
std::move(callback).Run(nullptr);
return;
}
std::optional<ProductSpecificationsSet> new_set =
shopping_service_->GetProductSpecificationsService()
->AddProductSpecificationsSet(name, urls);
std::move(callback).Run(
new_set.has_value() ? ProductSpecsSetToMojo(new_set.value()) : nullptr);
}
void ShoppingServiceHandler::DeleteProductSpecificationsSet(
const base::Uuid& uuid) {
if (!shopping_service_ ||
!shopping_service_->GetProductSpecificationsService()) {
return;
}
shopping_service_->GetProductSpecificationsService()
->DeleteProductSpecificationsSet(uuid.AsLowercaseString());
}
void ShoppingServiceHandler::OnProductSpecificationsSetAdded(
const ProductSpecificationsSet& set) {
remote_page_->OnProductSpecificationsSetAdded(ProductSpecsSetToMojo(set));
}
void ShoppingServiceHandler::OnProductSpecificationsSetUpdate(
const ProductSpecificationsSet& set) {
remote_page_->OnProductSpecificationsSetUpdated(ProductSpecsSetToMojo(set));
}
void ShoppingServiceHandler::OnProductSpecificationsSetRemoved(
const base::Uuid& uuid) {
remote_page_->OnProductSpecificationsSetRemoved(uuid);
}
} // namespace commerce