1// Copyright (c) 2012 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5#include "components/visitedlink/browser/visitedlink_event_listener.h"
6
7#include "base/memory/shared_memory.h"
8#include "components/visitedlink/browser/visitedlink_delegate.h"
9#include "components/visitedlink/common/visitedlink_messages.h"
10#include "content/public/browser/notification_service.h"
11#include "content/public/browser/notification_types.h"
12#include "content/public/browser/render_process_host.h"
13#include "content/public/browser/render_widget_host.h"
14
15using base::Time;
16using base::TimeDelta;
17using content::RenderWidgetHost;
18
19namespace {
20
21// The amount of time we wait to accumulate visited link additions.
22const int kCommitIntervalMs = 100;
23
24// Size of the buffer after which individual link updates deemed not warranted
25// and the overall update should be used instead.
26const unsigned kVisitedLinkBufferThreshold = 50;
27
28}  // namespace
29
30namespace visitedlink {
31
32// This class manages buffering and sending visited link hashes (fingerprints)
33// to renderer based on widget visibility.
34// As opposed to the VisitedLinkEventListener, which coalesces to
35// reduce the rate of messages being sent to render processes, this class
36// ensures that the updates occur only when explicitly requested. This is
37// used for RenderProcessHostImpl to only send Add/Reset link events to the
38// renderers when their tabs are visible and the corresponding RenderViews are
39// created.
40class VisitedLinkUpdater {
41 public:
42  explicit VisitedLinkUpdater(int render_process_id)
43      : reset_needed_(false), render_process_id_(render_process_id) {
44  }
45
46  // Informs the renderer about a new visited link table.
47  void SendVisitedLinkTable(base::SharedMemory* table_memory) {
48    content::RenderProcessHost* process =
49        content::RenderProcessHost::FromID(render_process_id_);
50    if (!process)
51      return;  // Happens in tests
52    base::SharedMemoryHandle handle_for_process;
53    table_memory->ShareToProcess(process->GetHandle(), &handle_for_process);
54    if (base::SharedMemory::IsHandleValid(handle_for_process))
55      process->Send(new ChromeViewMsg_VisitedLink_NewTable(
56          handle_for_process));
57  }
58
59  // Buffers |links| to update, but doesn't actually relay them.
60  void AddLinks(const VisitedLinkCommon::Fingerprints& links) {
61    if (reset_needed_)
62      return;
63
64    if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) {
65      // Once the threshold is reached, there's no need to store pending visited
66      // link updates -- we opt for resetting the state for all links.
67      AddReset();
68      return;
69    }
70
71    pending_.insert(pending_.end(), links.begin(), links.end());
72  }
73
74  // Tells the updater that sending individual link updates is no longer
75  // necessary and the visited state for all links should be reset.
76  void AddReset() {
77    reset_needed_ = true;
78    pending_.clear();
79  }
80
81  // Sends visited link update messages: a list of links whose visited state
82  // changed or reset of visited state for all links.
83  void Update() {
84    content::RenderProcessHost* process =
85        content::RenderProcessHost::FromID(render_process_id_);
86    if (!process)
87      return;  // Happens in tests
88
89    if (!process->VisibleWidgetCount())
90      return;
91
92    if (reset_needed_) {
93      process->Send(new ChromeViewMsg_VisitedLink_Reset());
94      reset_needed_ = false;
95      return;
96    }
97
98    if (pending_.empty())
99      return;
100
101    process->Send(new ChromeViewMsg_VisitedLink_Add(pending_));
102
103    pending_.clear();
104  }
105
106 private:
107  bool reset_needed_;
108  int render_process_id_;
109  VisitedLinkCommon::Fingerprints pending_;
110};
111
112VisitedLinkEventListener::VisitedLinkEventListener(
113    VisitedLinkMaster* master,
114    content::BrowserContext* browser_context)
115    : master_(master),
116      browser_context_(browser_context) {
117  registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
118                 content::NotificationService::AllBrowserContextsAndSources());
119  registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
120                 content::NotificationService::AllBrowserContextsAndSources());
121  registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
122                 content::NotificationService::AllBrowserContextsAndSources());
123}
124
125VisitedLinkEventListener::~VisitedLinkEventListener() {
126  if (!pending_visited_links_.empty())
127    pending_visited_links_.clear();
128}
129
130void VisitedLinkEventListener::NewTable(base::SharedMemory* table_memory) {
131  if (!table_memory)
132    return;
133
134  // Send to all RenderProcessHosts.
135  for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
136    // Make sure to not send to incognito renderers.
137    content::RenderProcessHost* process =
138        content::RenderProcessHost::FromID(i->first);
139    if (!process)
140      continue;
141
142    i->second->SendVisitedLinkTable(table_memory);
143  }
144}
145
146void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) {
147  pending_visited_links_.push_back(fingerprint);
148
149  if (!coalesce_timer_.IsRunning()) {
150    coalesce_timer_.Start(FROM_HERE,
151        TimeDelta::FromMilliseconds(kCommitIntervalMs), this,
152        &VisitedLinkEventListener::CommitVisitedLinks);
153  }
154}
155
156void VisitedLinkEventListener::Reset() {
157  pending_visited_links_.clear();
158  coalesce_timer_.Stop();
159
160  for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
161    i->second->AddReset();
162    i->second->Update();
163  }
164}
165
166void VisitedLinkEventListener::CommitVisitedLinks() {
167  // Send to all RenderProcessHosts.
168  for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
169    i->second->AddLinks(pending_visited_links_);
170    i->second->Update();
171  }
172
173  pending_visited_links_.clear();
174}
175
176void VisitedLinkEventListener::Observe(
177    int type,
178    const content::NotificationSource& source,
179    const content::NotificationDetails& details) {
180  switch (type) {
181    case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
182      content::RenderProcessHost* process =
183          content::Source<content::RenderProcessHost>(source).ptr();
184      if (browser_context_ != process->GetBrowserContext())
185        return;
186
187      // Happens on browser start up.
188      if (!master_->shared_memory())
189        return;
190
191      updaters_[process->GetID()] =
192          make_linked_ptr(new VisitedLinkUpdater(process->GetID()));
193      updaters_[process->GetID()]->SendVisitedLinkTable(
194          master_->shared_memory());
195      break;
196    }
197    case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
198      content::RenderProcessHost* process =
199          content::Source<content::RenderProcessHost>(source).ptr();
200      if (updaters_.count(process->GetID())) {
201        updaters_.erase(process->GetID());
202      }
203      break;
204    }
205    case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
206      RenderWidgetHost* widget =
207          content::Source<RenderWidgetHost>(source).ptr();
208      int child_id = widget->GetProcess()->GetID();
209      if (updaters_.count(child_id))
210        updaters_[child_id]->Update();
211      break;
212    }
213    default:
214      NOTREACHED();
215      break;
216  }
217}
218
219}  // namespace visitedlink
220