blob: bd3a7b6bf55becdb4263aa7754341c0de52c1b94 [file] [log] [blame]
// Copyright 2021 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/cart/commerce_hint_service.h"
#include <map>
#include <memory>
#include "base/no_destructor.h"
#include "base/time/time.h"
#include "chrome/browser/cart/cart_db_content.pb.h"
#include "chrome/browser/cart/cart_service.h"
#include "chrome/browser/cart/cart_service_factory.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service.h"
#include "chrome/browser/optimization_guide/optimization_guide_keyed_service_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/frame_service_base.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
namespace cart {
namespace {
// TODO(crbug/1164236): support multiple cart systems in the same domain.
// Returns eTLB+1 domain.
std::string GetDomain(const GURL& url) {
return net::registry_controlled_domains::GetDomainAndRegistry(
url, net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}
void ConstructCartProto(cart_db::ChromeCartContentProto* proto,
const GURL& navigation_url,
std::vector<mojom::ProductPtr> products) {
const std::string& domain = GetDomain(navigation_url);
proto->set_key(domain);
proto->set_merchant(domain);
proto->set_merchant_cart_url(navigation_url.spec());
proto->set_timestamp(base::Time::Now().ToDoubleT());
for (auto& product : products) {
if (product->image_url.spec().size() != 0) {
proto->add_product_image_urls(product->image_url.spec());
}
if (product->product_id.size() != 0) {
cart_db::ChromeCartProductProto product_proto;
product_proto.set_product_id(std::move(product->product_id));
cart_db::ChromeCartProductProto* added_product =
proto->add_product_infos();
*added_product = std::move(product_proto);
}
}
}
} // namespace
// Implementation of the Mojo CommerceHintObserver. This is called by the
// renderer to notify the browser that a commerce hint happens.
class CommerceHintObserverImpl
: public content::FrameServiceBase<mojom::CommerceHintObserver> {
public:
explicit CommerceHintObserverImpl(
content::RenderFrameHost* render_frame_host,
mojo::PendingReceiver<mojom::CommerceHintObserver> receiver,
base::WeakPtr<CommerceHintService> service)
: FrameServiceBase(render_frame_host, std::move(receiver)),
binding_url_(render_frame_host->GetLastCommittedURL()),
service_(std::move(service)) {}
~CommerceHintObserverImpl() override = default;
void OnAddToCart(const base::Optional<GURL>& cart_url,
const std::string& product_id) override {
DVLOG(1) << "Received OnAddToCart in the browser process on "
<< binding_url_;
if (!service_ || !binding_url_.SchemeIsHTTPOrHTTPS())
return;
service_->OnAddToCart(binding_url_, cart_url, product_id);
}
void OnVisitCart() override {
DVLOG(1) << "Received OnVisitCart in the browser process";
if (!service_ || !binding_url_.SchemeIsHTTPOrHTTPS())
return;
service_->OnAddToCart(binding_url_, binding_url_);
}
void OnCartProductUpdated(std::vector<mojom::ProductPtr> products) override {
DVLOG(1) << "Received OnCartProductUpdated in the browser process, with "
<< products.size() << " product(s).";
if (!service_ || !binding_url_.SchemeIsHTTPOrHTTPS())
return;
if (products.empty()) {
service_->OnRemoveCart(binding_url_);
} else {
service_->OnCartUpdated(binding_url_, std::move(products));
}
}
void OnVisitCheckout() override {
DVLOG(1) << "Received OnVisitCheckout in the browser process";
if (!service_ || !binding_url_.SchemeIsHTTPOrHTTPS())
return;
service_->OnRemoveCart(binding_url_);
}
void OnPurchase() override {
DVLOG(1) << "Received OnPurchase in the browser process";
if (!service_ || !binding_url_.SchemeIsHTTPOrHTTPS())
return;
service_->OnRemoveCart(binding_url_);
}
private:
GURL binding_url_;
base::WeakPtr<CommerceHintService> service_;
};
CommerceHintService::CommerceHintService(content::WebContents* web_contents)
: web_contents_(web_contents) {
DCHECK(!web_contents->GetBrowserContext()->IsOffTheRecord());
Profile* profile =
Profile::FromBrowserContext(web_contents_->GetBrowserContext());
service_ = CartServiceFactory::GetInstance()->GetForProfile(profile);
optimization_guide_decider_ =
OptimizationGuideKeyedServiceFactory::GetForProfile(profile);
if (optimization_guide_decider_) {
optimization_guide_decider_->RegisterOptimizationTypes(
{optimization_guide::proto::SHOPPING_PAGE_PREDICTOR});
}
}
CommerceHintService::~CommerceHintService() = default;
content::WebContents* CommerceHintService::WebContents() {
return web_contents_;
}
void CommerceHintService::BindCommerceHintObserver(
content::RenderFrameHost* host,
mojo::PendingReceiver<mojom::CommerceHintObserver> receiver) {
// The object is bound to the lifetime of |host| and the mojo
// connection. See FrameServiceBase for details.
new CommerceHintObserverImpl(host, std::move(receiver),
weak_factory_.GetWeakPtr());
}
bool CommerceHintService::ShouldSkip(const GURL& url) {
if (!optimization_guide_decider_) {
return false;
}
optimization_guide::OptimizationMetadata metadata;
auto decision = optimization_guide_decider_->CanApplyOptimization(
url, optimization_guide::proto::SHOPPING_PAGE_PREDICTOR, &metadata);
DVLOG(1) << "SHOPPING_PAGE_PREDICTOR = " << static_cast<int>(decision);
return optimization_guide::OptimizationGuideDecision::kFalse == decision;
}
void CommerceHintService::OnAddToCart(const GURL& navigation_url,
const base::Optional<GURL>& cart_url,
const std::string& product_id) {
if (ShouldSkip(navigation_url))
return;
base::Optional<GURL> validated_cart = cart_url;
if (cart_url && GetDomain(*cart_url) != GetDomain(navigation_url)) {
DVLOG(1) << "Reject cart URL with different eTLD+1 domain.";
validated_cart = base::nullopt;
}
cart_db::ChromeCartContentProto proto;
std::vector<mojom::ProductPtr> products;
if (!product_id.empty()) {
mojom::ProductPtr product_ptr(mojom::Product::New());
product_ptr->product_id = product_id;
products.push_back(std::move(product_ptr));
}
ConstructCartProto(&proto, navigation_url, std::move(products));
service_->AddCart(GetDomain(navigation_url), validated_cart,
std::move(proto));
}
void CommerceHintService::OnRemoveCart(const GURL& url) {
service_->DeleteCart(GetDomain(url));
}
void CommerceHintService::OnCartUpdated(
const GURL& cart_url,
std::vector<mojom::ProductPtr> products) {
if (ShouldSkip(cart_url))
return;
cart_db::ChromeCartContentProto proto;
ConstructCartProto(&proto, cart_url, std::move(products));
service_->AddCart(proto.key(), cart_url, std::move(proto));
}
WEB_CONTENTS_USER_DATA_KEY_IMPL(CommerceHintService)
} // namespace cart