blob: 10b3aacabc1aa117ceb81dc6a73236eb8e64d92d [file] [log] [blame]
// Copyright (c) 2012 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 "components/visitedlink/browser/visitedlink_event_listener.h"
#include "base/bind.h"
#include "components/visitedlink/browser/visitedlink_delegate.h"
#include "components/visitedlink/common/visitedlink.mojom.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_types.h"
#include "content/public/browser/render_process_host.h"
#include "content/public/browser/render_widget_host.h"
#include "services/service_manager/public/cpp/interface_provider.h"
using base::Time;
using base::TimeDelta;
using content::RenderWidgetHost;
namespace {
// The amount of time we wait to accumulate visited link additions.
constexpr int kCommitIntervalMs = 100;
// Size of the buffer after which individual link updates deemed not warranted
// and the overall update should be used instead.
const unsigned kVisitedLinkBufferThreshold = 50;
} // namespace
namespace visitedlink {
// This class manages buffering and sending visited link hashes (fingerprints)
// to renderer based on widget visibility.
// As opposed to the VisitedLinkEventListener, which coalesces to
// reduce the rate of messages being sent to render processes, this class
// ensures that the updates occur only when explicitly requested. This is
// used for RenderProcessHostImpl to only send Add/Reset link events to the
// renderers when their tabs are visible and the corresponding RenderViews are
// created.
class VisitedLinkUpdater {
public:
explicit VisitedLinkUpdater(int render_process_id)
: reset_needed_(false),
invalidate_hashes_(false),
render_process_id_(render_process_id) {
BindInterface(content::RenderProcessHost::FromID(render_process_id),
&sink_);
}
// Informs the renderer about a new visited link table.
void SendVisitedLinkTable(base::ReadOnlySharedMemoryRegion* region) {
if (region->IsValid())
sink_->UpdateVisitedLinks(region->Duplicate());
}
// Buffers |links| to update, but doesn't actually relay them.
void AddLinks(const VisitedLinkCommon::Fingerprints& links) {
if (reset_needed_)
return;
if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) {
// Once the threshold is reached, there's no need to store pending visited
// link updates -- we opt for resetting the state for all links.
AddReset(false);
return;
}
pending_.insert(pending_.end(), links.begin(), links.end());
}
// Tells the updater that sending individual link updates is no longer
// necessary and the visited state for all links should be reset. If
// |invalidateHashes| is true all cached visited links hashes should be
// dropped.
void AddReset(bool invalidate_hashes) {
reset_needed_ = true;
// Do not set to false. If tab is invisible the reset message will not be
// sent until tab became visible.
if (invalidate_hashes)
invalidate_hashes_ = true;
pending_.clear();
}
// Sends visited link update messages: a list of links whose visited state
// changed or reset of visited state for all links.
void Update() {
content::RenderProcessHost* process =
content::RenderProcessHost::FromID(render_process_id_);
if (!process)
return; // Happens in tests
if (!process->VisibleClientCount())
return;
if (reset_needed_) {
sink_->ResetVisitedLinks(invalidate_hashes_);
reset_needed_ = false;
invalidate_hashes_ = false;
return;
}
if (pending_.empty())
return;
sink_->AddVisitedLinks(pending_);
pending_.clear();
}
private:
bool reset_needed_;
bool invalidate_hashes_;
int render_process_id_;
mojom::VisitedLinkNotificationSinkPtr sink_;
VisitedLinkCommon::Fingerprints pending_;
};
VisitedLinkEventListener::VisitedLinkEventListener(
content::BrowserContext* browser_context)
: coalesce_timer_(&default_coalesce_timer_),
browser_context_(browser_context) {
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
content::NotificationService::AllBrowserContextsAndSources());
registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
content::NotificationService::AllBrowserContextsAndSources());
}
VisitedLinkEventListener::~VisitedLinkEventListener() {
if (!pending_visited_links_.empty())
pending_visited_links_.clear();
}
void VisitedLinkEventListener::NewTable(
base::ReadOnlySharedMemoryRegion* table_region) {
DCHECK(table_region && table_region->IsValid());
table_region_ = table_region->Duplicate();
if (!table_region_.IsValid())
return;
// Send to all RenderProcessHosts.
for (auto i = updaters_.begin(); i != updaters_.end(); ++i) {
// Make sure to not send to incognito renderers.
content::RenderProcessHost* process =
content::RenderProcessHost::FromID(i->first);
if (!process)
continue;
i->second->SendVisitedLinkTable(&table_region_);
}
}
void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) {
pending_visited_links_.push_back(fingerprint);
if (!coalesce_timer_->IsRunning()) {
coalesce_timer_->Start(
FROM_HERE, TimeDelta::FromMilliseconds(kCommitIntervalMs),
base::BindOnce(&VisitedLinkEventListener::CommitVisitedLinks,
base::Unretained(this)));
}
}
void VisitedLinkEventListener::Reset(bool invalidate_hashes) {
pending_visited_links_.clear();
coalesce_timer_->Stop();
for (auto i = updaters_.begin(); i != updaters_.end(); ++i) {
i->second->AddReset(invalidate_hashes);
i->second->Update();
}
}
void VisitedLinkEventListener::SetCoalesceTimerForTest(
base::OneShotTimer* coalesce_timer_override) {
coalesce_timer_ = coalesce_timer_override;
}
void VisitedLinkEventListener::CommitVisitedLinks() {
// Send to all RenderProcessHosts.
for (auto i = updaters_.begin(); i != updaters_.end(); ++i) {
i->second->AddLinks(pending_visited_links_);
i->second->Update();
}
pending_visited_links_.clear();
}
void VisitedLinkEventListener::Observe(
int type,
const content::NotificationSource& source,
const content::NotificationDetails& details) {
switch (type) {
case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
content::RenderProcessHost* process =
content::Source<content::RenderProcessHost>(source).ptr();
if (browser_context_ != process->GetBrowserContext())
return;
// Happens on browser start up.
if (!table_region_.IsValid())
return;
updaters_[process->GetID()].reset(
new VisitedLinkUpdater(process->GetID()));
updaters_[process->GetID()]->SendVisitedLinkTable(&table_region_);
break;
}
case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
content::RenderProcessHost* process =
content::Source<content::RenderProcessHost>(source).ptr();
if (updaters_.count(process->GetID())) {
updaters_.erase(process->GetID());
}
break;
}
case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
RenderWidgetHost* widget =
content::Source<RenderWidgetHost>(source).ptr();
int child_id = widget->GetProcess()->GetID();
if (updaters_.count(child_id))
updaters_[child_id]->Update();
break;
}
default:
NOTREACHED();
break;
}
}
} // namespace visitedlink