blob: a96a0e3aa558132f2e2c3d8e9729fbb8413f42a1 [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/memory/shared_memory.h"
#include "components/visitedlink/browser/visitedlink_delegate.h"
#include "components/visitedlink/common/visitedlink_messages.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"
using base::Time;
using base::TimeDelta;
using content::RenderWidgetHost;
namespace {
// The amount of time we wait to accumulate visited link additions.
const 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), render_process_id_(render_process_id) {
}
// Informs the renderer about a new visited link table.
void SendVisitedLinkTable(base::SharedMemory* table_memory) {
content::RenderProcessHost* process =
content::RenderProcessHost::FromID(render_process_id_);
if (!process)
return; // Happens in tests
base::SharedMemoryHandle handle_for_process;
table_memory->ShareToProcess(process->GetHandle(), &handle_for_process);
if (base::SharedMemory::IsHandleValid(handle_for_process))
process->Send(new ChromeViewMsg_VisitedLink_NewTable(
handle_for_process));
}
// 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();
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.
void AddReset() {
reset_needed_ = 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->VisibleWidgetCount())
return;
if (reset_needed_) {
process->Send(new ChromeViewMsg_VisitedLink_Reset());
reset_needed_ = false;
return;
}
if (pending_.empty())
return;
process->Send(new ChromeViewMsg_VisitedLink_Add(pending_));
pending_.clear();
}
private:
bool reset_needed_;
int render_process_id_;
VisitedLinkCommon::Fingerprints pending_;
};
VisitedLinkEventListener::VisitedLinkEventListener(
VisitedLinkMaster* master,
content::BrowserContext* browser_context)
: master_(master),
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::SharedMemory* table_memory) {
if (!table_memory)
return;
// Send to all RenderProcessHosts.
for (Updaters::iterator 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_memory);
}
}
void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) {
pending_visited_links_.push_back(fingerprint);
if (!coalesce_timer_.IsRunning()) {
coalesce_timer_.Start(FROM_HERE,
TimeDelta::FromMilliseconds(kCommitIntervalMs), this,
&VisitedLinkEventListener::CommitVisitedLinks);
}
}
void VisitedLinkEventListener::Reset() {
pending_visited_links_.clear();
coalesce_timer_.Stop();
for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
i->second->AddReset();
i->second->Update();
}
}
void VisitedLinkEventListener::CommitVisitedLinks() {
// Send to all RenderProcessHosts.
for (Updaters::iterator 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 (!master_->shared_memory())
return;
updaters_[process->GetID()] =
make_linked_ptr(new VisitedLinkUpdater(process->GetID()));
updaters_[process->GetID()]->SendVisitedLinkTable(
master_->shared_memory());
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