Obtain and store per-origin salts in the VisitedLinkReader
This CL obtains the per-origin salts sent via navigation commit_params, routes them to the correct VisitedLinkState, and stores them in the correct process' VisitedLinkReader. All per-origin salts for a given process can be found in the VisitedLinkReader's <origin,salt> map. These salts are necessary for hashing and determining if a link is :visited. Context CL: https://chromium-review.googlesource.com/c/chromium/src/+/5353594 Design Doc: https://docs.google.com/document/d/19qYPkfPNkPnmVcZ6jjl7jR8JPKY6j4rTjfoFRHll2IQ/edit?tab=t.0 Change-Id: I7fd089ea3fdf752644b0bf1b0339ca147620c7ff Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5540573 Commit-Queue: Kyra Seevers <kyraseevers@chromium.org> Reviewed-by: Alex Moshchuk <alexmos@chromium.org> Cr-Commit-Position: refs/heads/main@{#1309904}
This commit is contained in:
android_webview/renderer
chrome/renderer
components/visitedlink/renderer
content
ppapi_plugin
public
renderer
third_party/blink
public
renderer
@ -228,6 +228,13 @@ bool AwContentRendererClient::IsLinkVisited(uint64_t link_hash) {
|
||||
return visited_link_reader_->IsVisited(link_hash);
|
||||
}
|
||||
|
||||
// Android WebView does not support partitioned :visited links. Since per-origin
|
||||
// salts are only used in the partitioned hashtable, AndroidWebView clients do
|
||||
// not need to take any action if a per-origin salt is received.
|
||||
void AwContentRendererClient::AddOrUpdateVisitedLinkSalt(
|
||||
const url::Origin& origin,
|
||||
uint64_t salt) {}
|
||||
|
||||
void AwContentRendererClient::RunScriptsAtDocumentStart(
|
||||
content::RenderFrame* render_frame) {
|
||||
js_injection::JsCommunication* communication =
|
||||
|
@ -54,6 +54,8 @@ class AwContentRendererClient : public content::ContentRendererClient,
|
||||
std::string* error_html) override;
|
||||
uint64_t VisitedLinkHash(std::string_view canonical_url) override;
|
||||
bool IsLinkVisited(uint64_t link_hash) override;
|
||||
void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) override;
|
||||
void RunScriptsAtDocumentStart(content::RenderFrame* render_frame) override;
|
||||
std::unique_ptr<media::KeySystemSupportRegistration> GetSupportedKeySystems(
|
||||
content::RenderFrame* render_frame,
|
||||
|
@ -1488,6 +1488,12 @@ bool ChromeContentRendererClient::IsLinkVisited(uint64_t link_hash) {
|
||||
return chrome_observer_->visited_link_reader()->IsVisited(link_hash);
|
||||
}
|
||||
|
||||
void ChromeContentRendererClient::AddOrUpdateVisitedLinkSalt(
|
||||
const url::Origin& origin,
|
||||
uint64_t salt) {
|
||||
return chrome_observer_->visited_link_reader()->AddOrUpdateSalt(origin, salt);
|
||||
}
|
||||
|
||||
std::unique_ptr<blink::WebPrescientNetworking>
|
||||
ChromeContentRendererClient::CreatePrescientNetworking(
|
||||
content::RenderFrame* render_frame) {
|
||||
|
@ -156,6 +156,8 @@ class ChromeContentRendererClient
|
||||
bool IsPrefetchOnly(content::RenderFrame* render_frame) override;
|
||||
uint64_t VisitedLinkHash(std::string_view canonical_url) override;
|
||||
bool IsLinkVisited(uint64_t link_hash) override;
|
||||
void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) override;
|
||||
std::unique_ptr<blink::WebPrescientNetworking> CreatePrescientNetworking(
|
||||
content::RenderFrame* render_frame) override;
|
||||
bool IsExternalPepperPlugin(const std::string& module_name) override;
|
||||
|
@ -32,6 +32,13 @@ class VisitedLinkReader : public VisitedLinkCommon,
|
||||
void(mojo::PendingReceiver<mojom::VisitedLinkNotificationSink>)>
|
||||
GetBindCallback();
|
||||
|
||||
// Mutator for `salts_`. Called by Documents which received
|
||||
// per-origin salts from their navigation commit params. This function may
|
||||
// only be called on the UI (main) thread.
|
||||
void AddOrUpdateSalt(const url::Origin& origin, uint64_t salt) {
|
||||
salts_[origin] = salt;
|
||||
}
|
||||
|
||||
// mojom::VisitedLinkNotificationSink overrides.
|
||||
void UpdateVisitedLinks(
|
||||
base::ReadOnlySharedMemoryRegion table_region) override;
|
||||
@ -52,6 +59,12 @@ class VisitedLinkReader : public VisitedLinkCommon,
|
||||
|
||||
void Bind(mojo::PendingReceiver<mojom::VisitedLinkNotificationSink> receiver);
|
||||
|
||||
// Contains the per-origin salts required to hash every :visited link relevant
|
||||
// to this RenderProcess. Queries to the hashtable stored within
|
||||
// `table_mapping_` MUST provide the salt that corresponds to that link's
|
||||
// origin, otherwise :visited status cannot be determined.
|
||||
std::map<url::Origin, uint64_t> salts_;
|
||||
|
||||
base::ReadOnlySharedMemoryMapping table_mapping_;
|
||||
|
||||
mojo::Receiver<mojom::VisitedLinkNotificationSink> receiver_{this};
|
||||
|
@ -73,6 +73,15 @@ bool PpapiBlinkPlatformImpl::IsLinkVisited(uint64_t link_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// PPAPI does not support partitioned :visited links. Since per-origin
|
||||
// salts are only used in the partitioned :visited link hashtable, PPAPI clients
|
||||
// do not need to take any action if a per-origin salt is received.
|
||||
void PpapiBlinkPlatformImpl::AddOrUpdateVisitedLinkSalt(
|
||||
const url::Origin& origin,
|
||||
uint64_t salt) {
|
||||
NOTREACHED();
|
||||
}
|
||||
|
||||
blink::WebString PpapiBlinkPlatformImpl::DefaultLocale() {
|
||||
return blink::WebString::FromUTF8("en");
|
||||
}
|
||||
|
@ -31,6 +31,8 @@ class PpapiBlinkPlatformImpl : public BlinkPlatformImpl {
|
||||
blink::WebSandboxSupport* GetSandboxSupport() override;
|
||||
uint64_t VisitedLinkHash(std::string_view canonical_url) override;
|
||||
bool IsLinkVisited(uint64_t link_hash) override;
|
||||
void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) override;
|
||||
blink::WebString DefaultLocale() override;
|
||||
|
||||
private:
|
||||
|
@ -167,6 +167,10 @@ bool ContentRendererClient::IsLinkVisited(uint64_t link_hash) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void ContentRendererClient::AddOrUpdateVisitedLinkSalt(
|
||||
const url::Origin& origin,
|
||||
uint64_t salt) {}
|
||||
|
||||
std::unique_ptr<blink::WebPrescientNetworking>
|
||||
ContentRendererClient::CreatePrescientNetworking(RenderFrame* render_frame) {
|
||||
return nullptr;
|
||||
|
@ -270,6 +270,8 @@ class CONTENT_EXPORT ContentRendererClient {
|
||||
// See blink::Platform.
|
||||
virtual uint64_t VisitedLinkHash(std::string_view canonical_url);
|
||||
virtual bool IsLinkVisited(uint64_t link_hash);
|
||||
virtual void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt);
|
||||
|
||||
// Creates a WebPrescientNetworking instance for |render_frame|. The returned
|
||||
// instance is owned by the frame. May return null.
|
||||
|
@ -2968,6 +2968,7 @@ void RenderFrameImpl::CommitNavigationWithParams(
|
||||
|
||||
navigation_params->load_with_storage_access =
|
||||
commit_params->load_with_storage_access;
|
||||
navigation_params->visited_link_salt = commit_params->visited_link_salt;
|
||||
|
||||
if (!container_info) {
|
||||
// An empty network provider will always be created since it is expected in
|
||||
|
@ -290,6 +290,12 @@ bool RendererBlinkPlatformImpl::IsLinkVisited(uint64_t link_hash) {
|
||||
return GetContentClient()->renderer()->IsLinkVisited(link_hash);
|
||||
}
|
||||
|
||||
void RendererBlinkPlatformImpl::AddOrUpdateVisitedLinkSalt(
|
||||
const url::Origin& origin,
|
||||
uint64_t salt) {
|
||||
GetContentClient()->renderer()->AddOrUpdateVisitedLinkSalt(origin, salt);
|
||||
}
|
||||
|
||||
blink::WebString RendererBlinkPlatformImpl::UserAgent() {
|
||||
auto* render_thread = RenderThreadImpl::current();
|
||||
// RenderThreadImpl is null in some tests.
|
||||
|
@ -81,6 +81,8 @@ class CONTENT_EXPORT RendererBlinkPlatformImpl : public BlinkPlatformImpl {
|
||||
virtual bool sandboxEnabled();
|
||||
uint64_t VisitedLinkHash(std::string_view canonical_url) override;
|
||||
bool IsLinkVisited(uint64_t linkHash) override;
|
||||
void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) override;
|
||||
blink::WebString UserAgent() override;
|
||||
blink::UserAgentMetadata UserAgentMetadata() override;
|
||||
bool IsRedirectSafe(const GURL& from_url, const GURL& to_url) override;
|
||||
|
7
third_party/blink/public/platform/platform.h
vendored
7
third_party/blink/public/platform/platform.h
vendored
@ -229,6 +229,13 @@ class BLINK_PLATFORM_EXPORT Platform {
|
||||
// hash must have been generated by calling VisitedLinkHash().
|
||||
virtual bool IsLinkVisited(uint64_t link_hash) { return false; }
|
||||
|
||||
// Each render process has an associated VisitedLinkReader, where all
|
||||
// per-origin salts for the process are stored. Documents that receive a salt
|
||||
// via the VisitedLinkNavigationThrottle should notify the VisitedLinkReader
|
||||
// so its value can be stored in its canonical `salts_` map.
|
||||
virtual void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) {}
|
||||
|
||||
// Keep it the same as ImageDecoder::kNoDecodedImageByteLimit
|
||||
static const size_t kNoDecodedImageByteLimit = static_cast<size_t>(-1);
|
||||
|
||||
|
@ -551,6 +551,22 @@ struct BLINK_EXPORT WebNavigationParams {
|
||||
|
||||
// The cookie deprecation label for cookie deprecation facilitated testing.
|
||||
WebString cookie_deprecation_label;
|
||||
|
||||
// The :visited link hashtable is stored in shared memory and contains salted
|
||||
// hashes for all visits. Each salt corresponds to a unique origin, and
|
||||
// renderer processes are only informed of salts that correspond to their
|
||||
// origins. As a result, any given renderer process can only
|
||||
// learn about visits relevant to origins for which it has the salt.
|
||||
//
|
||||
// Here we store the salt corresponding to this navigation's origin to
|
||||
// be committed. It will allow the renderer process that commits this
|
||||
// navigation to learn about visits hashed with this salt. If the :visited
|
||||
// link hashtable is not yet initialized (or the feature is disabled), the
|
||||
// salt value will not be set here. Instead, PartitionedVisitedLinkWriter will
|
||||
// send the salt values to the renderer (specifically to VisitedLinkReader via
|
||||
// the VisitedLinkNotificationSink interface) after the :visited link
|
||||
// hashtable is initialized.
|
||||
std::optional<uint64_t> visited_link_salt;
|
||||
};
|
||||
|
||||
} // namespace blink
|
||||
|
@ -31,8 +31,11 @@
|
||||
#include "third_party/blink/renderer/core/dom/visited_link_state.h"
|
||||
|
||||
#include "third_party/blink/public/platform/platform.h"
|
||||
#include "third_party/blink/renderer/core/dom/document.h"
|
||||
#include "third_party/blink/renderer/core/dom/element_traversal.h"
|
||||
#include "third_party/blink/renderer/core/dom/shadow_root.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/security_context.h"
|
||||
#include "third_party/blink/renderer/core/frame/local_frame.h"
|
||||
#include "third_party/blink/renderer/core/html/html_anchor_element.h"
|
||||
#include "third_party/blink/renderer/core/html_names.h"
|
||||
#include "third_party/blink/renderer/core/svg/svg_element.h"
|
||||
@ -109,6 +112,21 @@ void VisitedLinkState::InvalidateStyleForLink(LinkHash link_hash) {
|
||||
InvalidateStyleForLinkRecursively(*GetDocument().firstChild(), link_hash);
|
||||
}
|
||||
|
||||
void VisitedLinkState::UpdateSalt(uint64_t visited_link_salt) {
|
||||
// Obtain the SecurityOrigin for our Document.
|
||||
// NOTE: for all Documents which have a valid VisitedLinkState, we should not
|
||||
// ever encounter an invalid `frame`, `context`, or `security_origin`.
|
||||
const LocalFrame* frame = GetDocument().GetFrame();
|
||||
DCHECK(frame);
|
||||
const SecurityContext* context = frame->GetSecurityContext();
|
||||
DCHECK(context);
|
||||
const SecurityOrigin* security_origin = context->GetSecurityOrigin();
|
||||
DCHECK(security_origin);
|
||||
// Inform VisitedLinkReader in our corresponding process of new salt value.
|
||||
Platform::Current()->AddOrUpdateVisitedLinkSalt(
|
||||
security_origin->ToUrlOrigin(), visited_link_salt);
|
||||
}
|
||||
|
||||
EInsideLink VisitedLinkState::DetermineLinkStateSlowCase(
|
||||
const Element& element) {
|
||||
DCHECK(element.IsLink());
|
||||
|
@ -47,6 +47,11 @@ class VisitedLinkState final : public GarbageCollected<VisitedLinkState> {
|
||||
void InvalidateStyleForAllLinks(bool invalidate_visited_link_hashes);
|
||||
void InvalidateStyleForLink(LinkHash);
|
||||
|
||||
// Allows Documents to set or update the per-origin salt associated with their
|
||||
// VisitedLinkState. This function informs the VisitedLinkReader in the
|
||||
// corresponding process of the new salt value.
|
||||
void UpdateSalt(uint64_t visited_link_salt);
|
||||
|
||||
EInsideLink DetermineLinkState(const Element& element) {
|
||||
if (element.IsLink())
|
||||
return DetermineLinkStateSlowCase(element);
|
||||
|
@ -81,6 +81,7 @@
|
||||
#include "third_party/blink/renderer/core/dom/document_parser.h"
|
||||
#include "third_party/blink/renderer/core/dom/events/event.h"
|
||||
#include "third_party/blink/renderer/core/dom/scriptable_document_parser.h"
|
||||
#include "third_party/blink/renderer/core/dom/visited_link_state.h"
|
||||
#include "third_party/blink/renderer/core/dom/weak_identifier_map.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/agent.h"
|
||||
#include "third_party/blink/renderer/core/execution_context/security_context_init.h"
|
||||
@ -257,6 +258,7 @@ struct SameSizeAsDocumentLoader
|
||||
bool grant_load_local_resources;
|
||||
std::optional<blink::mojom::FetchCacheMode> force_fetch_cache_mode;
|
||||
FramePolicy frame_policy;
|
||||
std::optional<uint64_t> visited_link_salt;
|
||||
Member<LocalFrame> frame;
|
||||
Member<HistoryItem> history_item;
|
||||
Member<DocumentParser> parser;
|
||||
@ -494,6 +496,7 @@ DocumentLoader::DocumentLoader(
|
||||
grant_load_local_resources_(params_->grant_load_local_resources),
|
||||
force_fetch_cache_mode_(params_->force_fetch_cache_mode),
|
||||
frame_policy_(params_->frame_policy.value_or(FramePolicy())),
|
||||
visited_link_salt_(params_->visited_link_salt),
|
||||
frame_(frame),
|
||||
// For back/forward navigations, the browser passed a history item to use
|
||||
// at commit time in |params_|. Set it as the current history item of this
|
||||
@ -717,6 +720,7 @@ DocumentLoader::CreateWebNavigationParamsToCloneDocument() {
|
||||
params->load_with_storage_access = load_with_storage_access_;
|
||||
params->modified_runtime_features = modified_runtime_features_;
|
||||
params->cookie_deprecation_label = cookie_deprecation_label_;
|
||||
params->visited_link_salt = visited_link_salt_;
|
||||
params->content_settings = content_settings_->Clone();
|
||||
return params;
|
||||
}
|
||||
@ -2842,6 +2846,16 @@ void DocumentLoader::CommitNavigation() {
|
||||
document->SetBaseURLOverride(main_resource_url);
|
||||
}
|
||||
|
||||
// For any navigations which have a per-origin salt, we need to notify the
|
||||
// resulting `document`. The `visited_link_salt_` allows the `document` to
|
||||
// hash and identify which links should be styled as :visited. Without the
|
||||
// salt, the hashtable is unreadable to the Document.
|
||||
if (visited_link_salt_.has_value() &&
|
||||
base::FeatureList::IsEnabled(
|
||||
blink::features::kPartitionVisitedLinkDatabase)) {
|
||||
document->GetVisitedLinkState().UpdateSalt(visited_link_salt_.value());
|
||||
}
|
||||
|
||||
// The navigation API is not initialized on the initial about:blank document
|
||||
// or opaque-origin documents.
|
||||
if (commit_reason_ != CommitReason::kInitialization &&
|
||||
|
@ -659,6 +659,7 @@ class CORE_EXPORT DocumentLoader : public GarbageCollected<DocumentLoader>,
|
||||
const bool grant_load_local_resources_ = false;
|
||||
const std::optional<blink::mojom::FetchCacheMode> force_fetch_cache_mode_;
|
||||
const FramePolicy frame_policy_;
|
||||
std::optional<uint64_t> visited_link_salt_;
|
||||
|
||||
Member<LocalFrame> frame_;
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
||||
#include "third_party/blink/renderer/platform/loader/fetch/url_loader/url_loader_client.h"
|
||||
#include "third_party/blink/renderer/platform/loader/static_data_navigation_body_loader.h"
|
||||
#include "third_party/blink/renderer/platform/storage/blink_storage_key.h"
|
||||
#include "third_party/blink/renderer/platform/testing/testing_platform_support.h"
|
||||
#include "third_party/blink/renderer/platform/testing/unit_test_helpers.h"
|
||||
#include "third_party/blink/renderer/platform/testing/url_loader_mock_factory.h"
|
||||
#include "third_party/blink/renderer/platform/testing/url_test_helpers.h"
|
||||
@ -101,15 +102,47 @@ class BodyLoaderTestDelegate : public URLLoaderTestDelegate {
|
||||
StaticDataNavigationBodyLoader* body_loader_raw_;
|
||||
};
|
||||
|
||||
// To test the abiltity to obtain and store the per-origin salt used in
|
||||
// partitioning visited links, we need to override the Platform::Current() used
|
||||
// in this test. Our platform will obtain and store the per-origin salt values
|
||||
// locally in `salts_`.
|
||||
class VisitedLinkSaltPlatform : public TestingPlatformSupport {
|
||||
public:
|
||||
// Override which stores our per-origin salts locally.
|
||||
void AddOrUpdateVisitedLinkSalt(const url::Origin& origin,
|
||||
uint64_t salt) override {
|
||||
salts_[origin] = salt;
|
||||
}
|
||||
|
||||
// Test cases can query whether we obtained a salt for a specific origin.
|
||||
std::optional<uint64_t> GetVisitedLinkSaltForOrigin(
|
||||
const url::Origin& origin) {
|
||||
auto it = salts_.find(origin);
|
||||
if (it != salts_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
// We do not have a corresponding salt for this origin.
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
private:
|
||||
std::map<url::Origin, uint64_t> salts_;
|
||||
};
|
||||
|
||||
class DocumentLoaderTest : public testing::TestWithParam<bool> {
|
||||
protected:
|
||||
void SetUp() override {
|
||||
if (IsThirdPartyStoragePartitioningEnabled()) {
|
||||
scoped_feature_list_.InitAndEnableFeature(
|
||||
net::features::kThirdPartyStoragePartitioning);
|
||||
if (GetParam()) {
|
||||
// Enabled the features.
|
||||
scoped_feature_list_.InitWithFeatures(
|
||||
{net::features::kThirdPartyStoragePartitioning,
|
||||
blink::features::kPartitionVisitedLinkDatabase},
|
||||
{});
|
||||
} else {
|
||||
scoped_feature_list_.InitAndDisableFeature(
|
||||
net::features::kThirdPartyStoragePartitioning);
|
||||
// Disables the features.
|
||||
scoped_feature_list_.InitWithFeatures(
|
||||
{}, {net::features::kThirdPartyStoragePartitioning,
|
||||
blink::features::kPartitionVisitedLinkDatabase});
|
||||
}
|
||||
|
||||
web_view_helper_.Initialize();
|
||||
@ -152,8 +185,6 @@ class DocumentLoaderTest : public testing::TestWithParam<bool> {
|
||||
url_test_helpers::UnregisterAllURLsAndClearMemoryCache();
|
||||
}
|
||||
|
||||
bool IsThirdPartyStoragePartitioningEnabled() const { return GetParam(); }
|
||||
|
||||
class ScopedLoaderDelegate {
|
||||
public:
|
||||
explicit ScopedLoaderDelegate(URLLoaderTestDelegate* delegate) {
|
||||
@ -164,6 +195,7 @@ class DocumentLoaderTest : public testing::TestWithParam<bool> {
|
||||
|
||||
WebLocalFrameImpl* MainFrame() { return web_view_helper_.LocalMainFrame(); }
|
||||
|
||||
ScopedTestingPlatformSupport<VisitedLinkSaltPlatform> platform_;
|
||||
test::TaskEnvironment task_environment_;
|
||||
frame_test_helpers::WebViewHelper web_view_helper_;
|
||||
base::test::ScopedFeatureList scoped_feature_list_;
|
||||
@ -850,5 +882,35 @@ TEST_P(DocumentLoaderTest, EmbeddedCredentialsNavigation) {
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(DocumentLoaderTest, VisitedLinkSalt) {
|
||||
// Generate the constants.
|
||||
const uint64_t kSalt = base::RandUint64();
|
||||
const KURL& kUrl = KURL(NullURL(), "https://www.example.com/foo.html");
|
||||
|
||||
// Load a blank slate.
|
||||
WebViewImpl* web_view_impl =
|
||||
web_view_helper_.InitializeAndLoad("about:blank");
|
||||
|
||||
// Create params for the URL we will navigate to next.
|
||||
std::unique_ptr<WebNavigationParams> params =
|
||||
WebNavigationParams::CreateWithEmptyHTMLForTesting(kUrl);
|
||||
params->visited_link_salt = kSalt;
|
||||
|
||||
// Perform the navigation and provide an empty vector for visited link state.
|
||||
LocalFrame* local_frame =
|
||||
To<LocalFrame>(web_view_impl->GetPage()->MainFrame());
|
||||
local_frame->Loader().CommitNavigation(std::move(params), nullptr);
|
||||
|
||||
// Check if the platform was notified of our salt.
|
||||
std::optional<uint64_t> result_salt =
|
||||
platform_->GetVisitedLinkSaltForOrigin(url::Origin::Create(GURL(kUrl)));
|
||||
ASSERT_EQ(result_salt.has_value(),
|
||||
base::FeatureList::IsEnabled(
|
||||
blink::features::kPartitionVisitedLinkDatabase));
|
||||
if (result_salt.has_value()) {
|
||||
EXPECT_EQ(result_salt.value(), kSalt);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace blink
|
||||
|
Reference in New Issue
Block a user