Commit bab55bde authored by Lexi Stavrakos's avatar Lexi Stavrakos Committed by Commit Bot

Quota: Implement quotachange event and corresponding dispatcher.

This change implements the subscribe mechanism for the QuotaChange event. Listeners are plumbed through to a new class, QuotaChangeDispatcher, which is owned by QuotaContext. Events are dispatched with a randomized delay; by adding noise, we can prevent bad actors from using this event as a signal to correlate users across profiles/origins or to identify the size of cross-origin resources. Listeners are organized by origin, so that all events for a single origin will be dispatched with the same randomized delay.

Bug: 1088004
Change-Id: Ie2513f127eeedf9422f5bb463f2dd515dfa90bea
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2270667
Commit-Queue: Lexi Stavrakos <astavrakos@google.com>
Reviewed-by: default avatarJarryd Goodman <jarrydg@chromium.org>
Reviewed-by: default avatarMatthew Denton <mpdenton@chromium.org>
Reviewed-by: default avatarDarwin Huang <huangdarwin@chromium.org>
Reviewed-by: default avatarVictor Costan <pwnall@chromium.org>
Cr-Commit-Position: refs/heads/master@{#797849}
parent 605b74ac
......@@ -1371,6 +1371,8 @@ source_set("browser") {
"push_messaging/push_messaging_manager.h",
"push_messaging/push_messaging_router.cc",
"push_messaging/push_messaging_router.h",
"quota/quota_change_dispatcher.cc",
"quota/quota_change_dispatcher.h",
"quota/quota_context.cc",
"quota/quota_context.h",
"quota/quota_manager_host.cc",
......
// Copyright 2020 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 "content/browser/quota/quota_change_dispatcher.h"
#include <map>
#include <utility>
#include "base/bind.h"
#include "base/rand_util.h"
#include "base/task/post_task.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "content/browser/quota/quota_manager_host.h"
#include "mojo/public/cpp/bindings/receiver.h"
#include "mojo/public/cpp/bindings/remote.h"
namespace {
base::TimeDelta GetRandomDelay() {
int64_t delay_micros = static_cast<int64_t>(
base::RandInt(0, 2 * base::Time::kMicrosecondsPerSecond));
return base::TimeDelta::FromMicroseconds(delay_micros);
}
} // namespace
namespace content {
QuotaChangeDispatcher::DelayedOriginListener::DelayedOriginListener()
: delay(GetRandomDelay()) {}
QuotaChangeDispatcher::DelayedOriginListener::~DelayedOriginListener() =
default;
QuotaChangeDispatcher::QuotaChangeDispatcher() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
QuotaChangeDispatcher::~QuotaChangeDispatcher() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
}
void QuotaChangeDispatcher::DispatchEvents() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
for (auto& kvp : listeners_by_origin_) {
const url::Origin& origin = kvp.first;
DelayedOriginListener& origin_listener = kvp.second;
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE,
base::BindOnce(&QuotaChangeDispatcher::DispatchEventsForOrigin,
weak_ptr_factory_.GetWeakPtr(), origin),
origin_listener.delay);
}
}
void QuotaChangeDispatcher::DispatchEventsForOrigin(const url::Origin& origin) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
// Handle the case where all the listeners for an origin were removed
// during the delay.
auto it = listeners_by_origin_.find(origin);
if (it == listeners_by_origin_.end()) {
return;
}
for (auto& listener : it->second.listeners) {
listener->OnQuotaChange();
}
}
void QuotaChangeDispatcher::AddChangeListener(
const url::Origin& origin,
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (origin.opaque()) {
return;
}
// operator[] will default-construct a DelayedOriginListener if `origin`
// does not exist in the map. This serves our needs here.
DelayedOriginListener* origin_listener = &(listeners_by_origin_[origin]);
origin_listener->listeners.Add(std::move(mojo_listener));
// Using base::Unretained on `origin_listener` and `listeners_by_origin_`
// is safe because the lifetime of |origin_listener| is tied to the
// lifetime of `listeners_by_origin_` and the lifetime of
// `listeners_by_origin_` is tied to the QuotaChangeDispatcher. This function
// will be called when the remote is disconnected and at that point the
// QuotaChangeDispatcher is still alive.
origin_listener->listeners.set_disconnect_handler(
base::BindRepeating(&QuotaChangeDispatcher::OnRemoteDisconnect,
base::Unretained(this), origin));
}
void QuotaChangeDispatcher::OnRemoteDisconnect(const url::Origin& origin,
mojo::RemoteSetElementId id) {
DCHECK_GE(listeners_by_origin_.count(origin), 0U);
if (listeners_by_origin_[origin].listeners.empty()) {
listeners_by_origin_.erase(origin);
}
}
} // namespace content
\ No newline at end of file
// Copyright 2020 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.
#ifndef CONTENT_BROWSER_QUOTA_QUOTA_CHANGE_DISPATCHER_H_
#define CONTENT_BROWSER_QUOTA_QUOTA_CHANGE_DISPATCHER_H_
#include <map>
#include <utility>
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "content/common/content_export.h"
#include "mojo/public/cpp/bindings/remote_set.h"
#include "third_party/blink/public/mojom/quota/quota_manager_host.mojom.h"
#include "url/origin.h"
class TimeDelta;
namespace content {
// Dispatches a storage pressure event to listeners across multiple origins.
//
// This class handles dispatching the event with randomized delays,
// to avoid creating a cross-origin user identifier.
//
// There is one instance per QuotaContext instance. All methods must
// be called on the same sequence.
class CONTENT_EXPORT QuotaChangeDispatcher
: public base::RefCountedThreadSafe<QuotaChangeDispatcher> {
public:
QuotaChangeDispatcher();
QuotaChangeDispatcher(const QuotaChangeDispatcher&) = delete;
QuotaChangeDispatcher& operator=(const QuotaChangeDispatcher&) = delete;
// Dispatch OnQuotaChange for every origin and its corresponding listeners.
void DispatchEvents();
void DispatchEventsForOrigin(const url::Origin& origin);
void AddChangeListener(
const url::Origin& origin,
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener);
void OnRemoteDisconnect(const url::Origin& origin,
mojo::RemoteSetElementId id);
private:
friend class QuotaChangeDispatcherTest;
friend class QuotaContext;
friend class base::RefCountedThreadSafe<QuotaChangeDispatcher>;
~QuotaChangeDispatcher();
struct DelayedOriginListener {
DelayedOriginListener();
~DelayedOriginListener();
// This delay is used to introduce noise to the event, to prevent
// bad actors from using the event to determine cross-origin
// resource size or to correlate and identify users across origins/profiles.
base::TimeDelta delay;
mojo::RemoteSet<blink::mojom::QuotaChangeListener> listeners;
};
// Stores all of the listeners associated with a unique origin
// corresponding to a randomized delay for that origin.
std::map<url::Origin, DelayedOriginListener> listeners_by_origin_;
SEQUENCE_CHECKER(sequence_checker_);
base::WeakPtrFactory<QuotaChangeDispatcher> weak_ptr_factory_{this};
};
} // namespace content
#endif // CONTENT_BROWSER_QUOTA_QUOTA_CHANGE_DISPATCHER_H_
\ No newline at end of file
// Copyright 2020 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 "content/browser/quota/quota_change_dispatcher.h"
#include <utility>
#include "base/barrier_closure.h"
#include "base/run_loop.h"
#include "base/task/post_task.h"
#include "base/task/task_traits.h"
#include "base/test/task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "testing/gmock/include/gmock/gmock-matchers.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/mojom/quota/quota_manager_host.mojom.h"
#include "url/gurl.h"
#include "url/origin.h"
namespace content {
class MockQuotaChangeListener : public blink::mojom::QuotaChangeListener {
public:
explicit MockQuotaChangeListener(
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver)
: receiver_(this, std::move(receiver)) {}
~MockQuotaChangeListener() override = default;
void SetQuotaChangeCallback(base::OnceClosure callback) {
callback_ = std::move(callback);
}
void OnQuotaChange() override {
DCHECK(callback_);
++quota_change_call_count_;
std::move(callback_).Run();
}
void RemoveReceivers() { receiver_.reset(); }
int quota_change_call_count() const { return quota_change_call_count_; }
private:
mojo::Receiver<blink::mojom::QuotaChangeListener> receiver_;
base::OnceClosure callback_;
int quota_change_call_count_ = 0;
};
class QuotaChangeDispatcherTest : public testing::Test {
public:
QuotaChangeDispatcherTest() = default;
~QuotaChangeDispatcherTest() override = default;
void SetUp() override {
quota_change_dispatcher_ = base::MakeRefCounted<QuotaChangeDispatcher>();
}
std::map<url::Origin, QuotaChangeDispatcher::DelayedOriginListener>*
listeners_by_origin() {
return &(quota_change_dispatcher_->listeners_by_origin_);
}
mojo::RemoteSet<blink::mojom::QuotaChangeListener>* GetListeners(
const url::Origin& origin) {
DCHECK_GT(listeners_by_origin()->count(origin), 0U);
return &(listeners_by_origin()->find(origin)->second.listeners);
}
base::TimeDelta GetDelay(const url::Origin& origin) {
DCHECK_GT(listeners_by_origin()->count(origin), 0U);
return listeners_by_origin()->find(origin)->second.delay;
}
void DispatchCompleted() {
if (run_loop_) // Set in WaitForChange().
run_loop_->Quit();
}
void WaitForChange() {
base::RunLoop loop;
run_loop_ = &loop;
loop.Run();
run_loop_ = nullptr;
}
protected:
base::test::SingleThreadTaskEnvironment task_environment_;
scoped_refptr<QuotaChangeDispatcher> quota_change_dispatcher_;
private:
// If not null, will be stopped when a quota change notification is received.
base::RunLoop* run_loop_ = nullptr;
};
TEST_F(QuotaChangeDispatcherTest, AddChangeListener) {
const url::Origin& url_foo_ = url::Origin::Create(GURL("http://foo.com/"));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver =
mojo_listener.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener(std::move(receiver));
EXPECT_EQ(0U, listeners_by_origin()->size());
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener));
EXPECT_EQ(1U, listeners_by_origin()->size());
EXPECT_TRUE(base::Contains(*(listeners_by_origin()), url_foo_));
}
TEST_F(QuotaChangeDispatcherTest, AddChangeListener_DuplicateOrigins) {
const url::Origin& url_foo_ = url::Origin::Create(GURL("http://foo.com/"));
constexpr double kMinDelay = 0;
constexpr double kMaxDelay = 2;
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_1;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_1 =
mojo_listener_1.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_1(std::move(receiver_1));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_2;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_2 =
mojo_listener_2.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_2(std::move(receiver_2));
EXPECT_EQ(0U, listeners_by_origin()->size());
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener_1));
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener_2));
EXPECT_EQ(1U, listeners_by_origin()->size());
EXPECT_EQ(2U, GetListeners(url_foo_)->size());
EXPECT_LE(kMinDelay, GetDelay(url_foo_).InSecondsF());
EXPECT_GE(kMaxDelay, GetDelay(url_foo_).InSecondsF());
}
TEST_F(QuotaChangeDispatcherTest, DispatchEvents) {
const url::Origin& url_foo_ = url::Origin::Create(GURL("http://foo.com/"));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver =
mojo_listener.InitWithNewPipeAndPassReceiver();
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener));
MockQuotaChangeListener listener(std::move(receiver));
EXPECT_EQ(0, listener.quota_change_call_count());
base::RepeatingClosure barrier = base::BarrierClosure(
1, base::BindOnce(&QuotaChangeDispatcherTest::DispatchCompleted,
base::Unretained(this)));
listener.SetQuotaChangeCallback(barrier);
quota_change_dispatcher_->DispatchEvents();
WaitForChange();
EXPECT_EQ(1, listener.quota_change_call_count());
}
TEST_F(QuotaChangeDispatcherTest, DispatchEvents_Multiple) {
const url::Origin& url_foo_ = url::Origin::Create(GURL("http://foo.com/"));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_1;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_1 =
mojo_listener_1.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_1(std::move(receiver_1));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_2;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_2 =
mojo_listener_2.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_2(std::move(receiver_2));
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener_1));
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener_2));
EXPECT_EQ(0, listener_1.quota_change_call_count());
EXPECT_EQ(0, listener_2.quota_change_call_count());
base::RepeatingClosure barrier = base::BarrierClosure(
2, base::BindOnce(&QuotaChangeDispatcherTest::DispatchCompleted,
base::Unretained(this)));
listener_1.SetQuotaChangeCallback(barrier);
listener_2.SetQuotaChangeCallback(barrier);
quota_change_dispatcher_->DispatchEvents();
WaitForChange();
EXPECT_EQ(1, listener_1.quota_change_call_count());
EXPECT_EQ(1, listener_2.quota_change_call_count());
barrier = base::BarrierClosure(
2, base::BindOnce(&QuotaChangeDispatcherTest::DispatchCompleted,
base::Unretained(this)));
listener_1.SetQuotaChangeCallback(barrier);
listener_2.SetQuotaChangeCallback(barrier);
quota_change_dispatcher_->DispatchEvents();
WaitForChange();
EXPECT_EQ(2, listener_1.quota_change_call_count());
EXPECT_EQ(2, listener_2.quota_change_call_count());
}
TEST_F(QuotaChangeDispatcherTest, RemoveThenDispatch) {
const url::Origin& url_foo_ = url::Origin::Create(GURL("http://foo.com/"));
const url::Origin& url_bar_ = url::Origin::Create(GURL("http://bar.com/"));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_1;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_1 =
mojo_listener_1.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_1(std::move(receiver_1));
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener_2;
mojo::PendingReceiver<blink::mojom::QuotaChangeListener> receiver_2 =
mojo_listener_2.InitWithNewPipeAndPassReceiver();
MockQuotaChangeListener listener_2(std::move(receiver_2));
quota_change_dispatcher_->AddChangeListener(url_foo_,
std::move(mojo_listener_1));
quota_change_dispatcher_->AddChangeListener(url_bar_,
std::move(mojo_listener_2));
EXPECT_EQ(0, listener_1.quota_change_call_count());
EXPECT_EQ(0, listener_2.quota_change_call_count());
base::RepeatingClosure barrier = base::BarrierClosure(
2, base::BindOnce(&QuotaChangeDispatcherTest::DispatchCompleted,
base::Unretained(this)));
listener_1.SetQuotaChangeCallback(barrier);
listener_2.SetQuotaChangeCallback(barrier);
quota_change_dispatcher_->DispatchEvents();
WaitForChange();
EXPECT_EQ(1, listener_1.quota_change_call_count());
EXPECT_EQ(1, listener_2.quota_change_call_count());
listener_2.RemoveReceivers();
barrier = base::BarrierClosure(
1, base::BindOnce(&QuotaChangeDispatcherTest::DispatchCompleted,
base::Unretained(this)));
listener_1.SetQuotaChangeCallback(barrier);
quota_change_dispatcher_->DispatchEvents();
WaitForChange();
EXPECT_EQ(2, listener_1.quota_change_call_count());
EXPECT_EQ(1, listener_2.quota_change_call_count());
}
} // namespace content
\ No newline at end of file
......@@ -63,10 +63,14 @@ void QuotaContext::BindQuotaManagerHostOnIOThread(
mojo::PendingReceiver<blink::mojom::QuotaManagerHost> receiver) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!quota_change_dispatcher_) {
quota_change_dispatcher_ = base::MakeRefCounted<QuotaChangeDispatcher>();
}
// The quota manager currently runs on the I/O thread.
auto host = std::make_unique<QuotaManagerHost>(process_id, render_frame_id,
origin, quota_manager_.get(),
permission_context_.get());
auto host = std::make_unique<QuotaManagerHost>(
process_id, render_frame_id, origin, quota_manager_.get(),
permission_context_.get(), quota_change_dispatcher_);
auto* host_ptr = host.get();
receivers_.Add(host_ptr, std::move(receiver), std::move(host));
}
......
......@@ -10,6 +10,7 @@
#include "base/memory/ref_counted_delete_on_sequence.h"
#include "base/memory/scoped_refptr.h"
#include "base/sequence_checker.h"
#include "content/browser/quota/quota_change_dispatcher.h"
#include "content/public/browser/quota_permission_context.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "mojo/public/cpp/bindings/receiver_set.h"
......@@ -86,6 +87,9 @@ class QuotaContext : public base::RefCountedDeleteOnSequence<QuotaContext> {
// This is not const because of OverrideQuotaManagerForTesting().
scoped_refptr<storage::QuotaManager> quota_manager_;
// Owning reference for the QuotaChangeDispatcher.
scoped_refptr<QuotaChangeDispatcher> quota_change_dispatcher_;
// Owning reference for the QuotaPermissionContext.
const scoped_refptr<QuotaPermissionContext> permission_context_;
......
......@@ -13,6 +13,7 @@
#include "base/memory/weak_ptr.h"
#include "base/numerics/safe_conversions.h"
#include "base/task/post_task.h"
#include "content/browser/quota/quota_change_dispatcher.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
......@@ -28,21 +29,34 @@ using storage::QuotaClient;
namespace content {
QuotaManagerHost::QuotaManagerHost(int process_id,
int render_frame_id,
const url::Origin& origin,
storage::QuotaManager* quota_manager,
QuotaPermissionContext* permission_context)
QuotaManagerHost::QuotaManagerHost(
int process_id,
int render_frame_id,
const url::Origin& origin,
storage::QuotaManager* quota_manager,
QuotaPermissionContext* permission_context,
scoped_refptr<QuotaChangeDispatcher> quota_change_dispatcher)
: process_id_(process_id),
render_frame_id_(render_frame_id),
origin_(origin),
quota_manager_(quota_manager),
permission_context_(permission_context) {
permission_context_(permission_context),
quota_change_dispatcher_(quota_change_dispatcher) {
DCHECK(quota_manager);
// TODO(sashab): Work out the conditions for permission_context to be set and
// add a DCHECK for it here.
}
void QuotaManagerHost::AddChangeListener(
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener,
AddChangeListenerCallback callback) {
if (quota_change_dispatcher_) {
quota_change_dispatcher_->AddChangeListener(origin_,
std::move(mojo_listener));
}
std::move(callback).Run();
}
void QuotaManagerHost::QueryStorageUsageAndQuota(
StorageType storage_type,
QueryStorageUsageAndQuotaCallback callback) {
......
......@@ -7,6 +7,7 @@
#include "base/memory/scoped_refptr.h"
#include "base/memory/weak_ptr.h"
#include "content/browser/quota/quota_change_dispatcher.h"
#include "content/public/browser/quota_permission_context.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "storage/browser/quota/quota_manager.h"
......@@ -38,11 +39,13 @@ class QuotaManagerHost : public blink::mojom::QuotaManagerHost {
public:
// The owner must guarantee that |quota_manager| and |permission_context|
// outlive this instance.
QuotaManagerHost(int process_id,
int render_frame_id,
const url::Origin& origin,
storage::QuotaManager* quota_manager,
QuotaPermissionContext* permission_context);
QuotaManagerHost(
int process_id,
int render_frame_id,
const url::Origin& origin,
storage::QuotaManager* quota_manager,
QuotaPermissionContext* permission_context,
scoped_refptr<QuotaChangeDispatcher> quota_change_dispatcher);
QuotaManagerHost(const QuotaManagerHost&) = delete;
QuotaManagerHost& operator=(const QuotaManagerHost&) = delete;
......@@ -50,6 +53,9 @@ class QuotaManagerHost : public blink::mojom::QuotaManagerHost {
~QuotaManagerHost() override;
// blink::mojom::QuotaManagerHost:
void AddChangeListener(
mojo::PendingRemote<blink::mojom::QuotaChangeListener> mojo_listener,
AddChangeListenerCallback callback) override;
void QueryStorageUsageAndQuota(
blink::mojom::StorageType storage_type,
QueryStorageUsageAndQuotaCallback callback) override;
......@@ -107,6 +113,8 @@ class QuotaManagerHost : public blink::mojom::QuotaManagerHost {
// QuotaManagerHost.
QuotaPermissionContext* const permission_context_;
scoped_refptr<QuotaChangeDispatcher> quota_change_dispatcher_;
base::WeakPtrFactory<QuotaManagerHost> weak_factory_{this};
};
......
......@@ -1824,6 +1824,7 @@ test("content_unittests") {
"../browser/picture_in_picture/picture_in_picture_service_impl_unittest.cc",
"../browser/plugin_list_unittest.cc",
"../browser/presentation/presentation_service_impl_unittest.cc",
"../browser/quota/quota_change_dispatcher_unittest.cc",
"../browser/renderer_host/cursor_manager_unittest.cc",
"../browser/renderer_host/direct_manipulation_test_helper_win.cc",
"../browser/renderer_host/direct_manipulation_test_helper_win.h",
......
......@@ -29,6 +29,7 @@ struct UsageInfo;
using UsageInfoEntries = std::vector<UsageInfo>;
// Common callback types that are used throughout in the quota module.
using AddChangeListenerCallback = base::OnceCallback<void()>;
using GlobalUsageCallback =
base::OnceCallback<void(int64_t usage, int64_t unlimited_usage)>;
using QuotaCallback =
......
......@@ -6,12 +6,26 @@ module blink.mojom;
import "third_party/blink/public/mojom/quota/quota_types.mojom";
// Receives quota change notifications for a frame or worker.
//
// The interface is used to by the quota system in the browser process, to
// send notifications to Blink in the renderer process.
interface QuotaChangeListener {
// Triggered due to storage pressure.
OnQuotaChange();
};
// Implements the Web Platform's quota ("Storage") API.
//
// The interface is consumed by Blink in the renderer process. The interface is
// currently implemented in the browser process, and will eventually move to the
// Storage Service.
interface QuotaManagerHost {
// Subscribes to changes in the quota transmitted in a request to an URL.
//
// The subscription is canceled by closing the pipe.
AddChangeListener(pending_remote<QuotaChangeListener> listener) => ();
// Retrieves the origin's current usage and quota.
//
// This method can be called from frames and workers.
......
......@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/modules/quota/navigator_storage_quota.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/core/frame/navigator.h"
#include "third_party/blink/renderer/modules/quota/deprecated_storage_quota.h"
......@@ -84,8 +85,20 @@ DeprecatedStorageQuota* NavigatorStorageQuota::webkitPersistentStorage() const {
StorageManager* NavigatorStorageQuota::storage() const {
if (!storage_manager_) {
storage_manager_ =
MakeGarbageCollected<StorageManager>(GetSupplementable()->DomWindow());
mojo::Remote<mojom::blink::QuotaManagerHost> backend;
auto* supplementable = GetSupplementable();
auto* execution_context =
supplementable ? supplementable->GetExecutionContext() : nullptr;
if (execution_context) {
if (&execution_context->GetBrowserInterfaceBroker() !=
&GetEmptyBrowserInterfaceBroker()) {
execution_context->GetBrowserInterfaceBroker().GetInterface(
backend.BindNewPipeAndPassReceiver());
}
}
storage_manager_ = MakeGarbageCollected<StorageManager>(execution_context,
std::move(backend));
}
return storage_manager_.Get();
}
......
......@@ -85,10 +85,15 @@ void QueryStorageUsageAndQuotaCallback(
} // namespace
StorageManager::StorageManager(ExecutionContext* execution_context)
StorageManager::StorageManager(
ExecutionContext* execution_context,
mojo::Remote<mojom::blink::QuotaManagerHost> backend)
: ExecutionContextClient(execution_context),
permission_service_(execution_context),
quota_host_(execution_context) {}
quota_host_(execution_context),
change_listener_receiver_(this, execution_context) {}
StorageManager::~StorageManager() = default;
ScriptPromise StorageManager::persist(ScriptState* script_state) {
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
......@@ -161,6 +166,7 @@ ScriptPromise StorageManager::estimate(ScriptState* script_state) {
}
void StorageManager::Trace(Visitor* visitor) const {
visitor->Trace(change_listener_receiver_);
visitor->Trace(permission_service_);
visitor->Trace(quota_host_);
EventTargetWithInlineData::Trace(visitor);
......@@ -176,6 +182,27 @@ ExecutionContext* StorageManager::GetExecutionContext() const {
return ExecutionContextClient::GetExecutionContext();
}
void StorageManager::OnQuotaChange() {
DispatchEvent(*Event::Create(event_type_names::kQuotachange));
}
void StorageManager::AddedEventListener(
const AtomicString& event_type,
RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::AddedEventListener(event_type,
registered_listener);
StartObserving();
}
void StorageManager::RemovedEventListener(
const AtomicString& event_type,
const RegisteredEventListener& registered_listener) {
EventTargetWithInlineData::RemovedEventListener(event_type,
registered_listener);
if (!HasEventListeners())
StopObserving();
}
PermissionService* StorageManager::GetPermissionService(
ExecutionContext* execution_context) {
if (!permission_service_.is_bound()) {
......@@ -202,6 +229,24 @@ void StorageManager::PermissionRequestComplete(ScriptPromiseResolver* resolver,
resolver->Resolve(status == PermissionStatus::GRANTED);
}
void StorageManager::StartObserving() {
if (change_listener_receiver_.is_bound() || quota_host_.is_bound())
return;
// Using kMiscPlatformAPI because the Storage specification does not
// specify a dedicated task queue yet.
auto task_runner =
GetExecutionContext()->GetTaskRunner(TaskType::kMiscPlatformAPI);
quota_host_->AddChangeListener(
change_listener_receiver_.BindNewPipeAndPassRemote(task_runner), {});
}
void StorageManager::StopObserving() {
if (!change_listener_receiver_.is_bound())
return;
change_listener_receiver_.reset();
}
mojom::blink::QuotaManagerHost* StorageManager::GetQuotaHost(
ExecutionContext* execution_context) {
if (!quota_host_.is_bound()) {
......
......@@ -5,12 +5,14 @@
#ifndef THIRD_PARTY_BLINK_RENDERER_MODULES_QUOTA_STORAGE_MANAGER_H_
#define THIRD_PARTY_BLINK_RENDERER_MODULES_QUOTA_STORAGE_MANAGER_H_
#include "mojo/public/cpp/bindings/remote.h"
#include "third_party/blink/public/mojom/permissions/permission.mojom-blink.h"
#include "third_party/blink/public/mojom/quota/quota_manager_host.mojom-blink.h"
#include "third_party/blink/renderer/core/dom/events/event_target.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/platform/bindings/script_wrappable.h"
#include "third_party/blink/renderer/platform/heap/heap.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_receiver.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_remote.h"
#include "third_party/blink/renderer/platform/mojo/heap_mojo_wrapper_mode.h"
......@@ -22,11 +24,14 @@ class ScriptPromiseResolver;
class ScriptState;
class StorageManager final : public EventTargetWithInlineData,
public ExecutionContextClient {
public ExecutionContextClient,
public mojom::blink::QuotaChangeListener {
DEFINE_WRAPPERTYPEINFO();
public:
explicit StorageManager(ExecutionContext*);
explicit StorageManager(ExecutionContext*,
mojo::Remote<mojom::blink::QuotaManagerHost> backend);
~StorageManager() override;
ScriptPromise persisted(ScriptState*);
ScriptPromise persist(ScriptState*);
......@@ -40,6 +45,16 @@ class StorageManager final : public EventTargetWithInlineData,
const AtomicString& InterfaceName() const override;
ExecutionContext* GetExecutionContext() const override;
// network::mojom::blink::QuotaChangeListener
void OnQuotaChange() override;
protected:
// EventTarget overrides.
void AddedEventListener(const AtomicString& event_type,
RegisteredEventListener&) final;
void RemovedEventListener(const AtomicString& event_type,
const RegisteredEventListener&) final;
private:
mojom::blink::PermissionService* GetPermissionService(ExecutionContext*);
......@@ -47,6 +62,12 @@ class StorageManager final : public EventTargetWithInlineData,
void PermissionRequestComplete(ScriptPromiseResolver*,
mojom::blink::PermissionStatus);
// Called when a quota change event listener is added.
void StartObserving();
// Called when all the change event listeners have been removed.
void StopObserving();
// Binds the interface (if not already bound) with the given interface
// provider, and returns it,
mojom::blink::QuotaManagerHost* GetQuotaHost(ExecutionContext*);
......@@ -57,6 +78,9 @@ class StorageManager final : public EventTargetWithInlineData,
HeapMojoRemote<mojom::blink::QuotaManagerHost,
HeapMojoWrapperMode::kWithoutContextObserver>
quota_host_;
HeapMojoReceiver<mojom::blink::QuotaChangeListener, StorageManager>
change_listener_receiver_;
};
} // namespace blink
......
......@@ -30,6 +30,7 @@
#include "third_party/blink/renderer/modules/quota/worker_navigator_storage_quota.h"
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
#include "third_party/blink/renderer/modules/quota/deprecated_storage_quota.h"
#include "third_party/blink/renderer/modules/quota/storage_manager.h"
......@@ -58,9 +59,20 @@ StorageManager* WorkerNavigatorStorageQuota::storage(
StorageManager* WorkerNavigatorStorageQuota::storage() const {
if (!storage_manager_) {
storage_manager_ = MakeGarbageCollected<StorageManager>(
GetSupplementable() ? GetSupplementable()->GetExecutionContext()
: nullptr);
mojo::Remote<mojom::blink::QuotaManagerHost> backend;
auto* supplementable = GetSupplementable();
auto* execution_context =
supplementable ? supplementable->GetExecutionContext() : nullptr;
if (execution_context) {
if (&execution_context->GetBrowserInterfaceBroker() !=
&GetEmptyBrowserInterfaceBroker()) {
execution_context->GetBrowserInterfaceBroker().GetInterface(
backend.BindNewPipeAndPassReceiver());
}
}
storage_manager_ = MakeGarbageCollected<StorageManager>(execution_context,
std::move(backend));
}
return storage_manager_.Get();
}
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment