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
15 using base::Time;
16 using base::TimeDelta;
17 using content::RenderWidgetHost;
18
19 namespace {
20
21 // The amount of time we wait to accumulate visited link additions.
22 const 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.
26 const unsigned kVisitedLinkBufferThreshold = 50;
27
28 } // namespace
29
30 namespace 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.
40 class VisitedLinkUpdater {
41 public:
VisitedLinkUpdater(int render_process_id)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.
SendVisitedLinkTable(base::SharedMemory * table_memory)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->ShareReadOnlyToProcess(process->GetHandle(),
54 &handle_for_process);
55 if (base::SharedMemory::IsHandleValid(handle_for_process))
56 process->Send(new ChromeViewMsg_VisitedLink_NewTable(
57 handle_for_process));
58 }
59
60 // Buffers |links| to update, but doesn't actually relay them.
AddLinks(const VisitedLinkCommon::Fingerprints & links)61 void AddLinks(const VisitedLinkCommon::Fingerprints& links) {
62 if (reset_needed_)
63 return;
64
65 if (pending_.size() + links.size() > kVisitedLinkBufferThreshold) {
66 // Once the threshold is reached, there's no need to store pending visited
67 // link updates -- we opt for resetting the state for all links.
68 AddReset();
69 return;
70 }
71
72 pending_.insert(pending_.end(), links.begin(), links.end());
73 }
74
75 // Tells the updater that sending individual link updates is no longer
76 // necessary and the visited state for all links should be reset.
AddReset()77 void AddReset() {
78 reset_needed_ = true;
79 pending_.clear();
80 }
81
82 // Sends visited link update messages: a list of links whose visited state
83 // changed or reset of visited state for all links.
Update()84 void Update() {
85 content::RenderProcessHost* process =
86 content::RenderProcessHost::FromID(render_process_id_);
87 if (!process)
88 return; // Happens in tests
89
90 if (!process->VisibleWidgetCount())
91 return;
92
93 if (reset_needed_) {
94 process->Send(new ChromeViewMsg_VisitedLink_Reset());
95 reset_needed_ = false;
96 return;
97 }
98
99 if (pending_.empty())
100 return;
101
102 process->Send(new ChromeViewMsg_VisitedLink_Add(pending_));
103
104 pending_.clear();
105 }
106
107 private:
108 bool reset_needed_;
109 int render_process_id_;
110 VisitedLinkCommon::Fingerprints pending_;
111 };
112
VisitedLinkEventListener(VisitedLinkMaster * master,content::BrowserContext * browser_context)113 VisitedLinkEventListener::VisitedLinkEventListener(
114 VisitedLinkMaster* master,
115 content::BrowserContext* browser_context)
116 : master_(master),
117 browser_context_(browser_context) {
118 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_CREATED,
119 content::NotificationService::AllBrowserContextsAndSources());
120 registrar_.Add(this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
121 content::NotificationService::AllBrowserContextsAndSources());
122 registrar_.Add(this, content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
123 content::NotificationService::AllBrowserContextsAndSources());
124 }
125
~VisitedLinkEventListener()126 VisitedLinkEventListener::~VisitedLinkEventListener() {
127 if (!pending_visited_links_.empty())
128 pending_visited_links_.clear();
129 }
130
NewTable(base::SharedMemory * table_memory)131 void VisitedLinkEventListener::NewTable(base::SharedMemory* table_memory) {
132 if (!table_memory)
133 return;
134
135 // Send to all RenderProcessHosts.
136 for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
137 // Make sure to not send to incognito renderers.
138 content::RenderProcessHost* process =
139 content::RenderProcessHost::FromID(i->first);
140 if (!process)
141 continue;
142
143 i->second->SendVisitedLinkTable(table_memory);
144 }
145 }
146
Add(VisitedLinkMaster::Fingerprint fingerprint)147 void VisitedLinkEventListener::Add(VisitedLinkMaster::Fingerprint fingerprint) {
148 pending_visited_links_.push_back(fingerprint);
149
150 if (!coalesce_timer_.IsRunning()) {
151 coalesce_timer_.Start(FROM_HERE,
152 TimeDelta::FromMilliseconds(kCommitIntervalMs), this,
153 &VisitedLinkEventListener::CommitVisitedLinks);
154 }
155 }
156
Reset()157 void VisitedLinkEventListener::Reset() {
158 pending_visited_links_.clear();
159 coalesce_timer_.Stop();
160
161 for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
162 i->second->AddReset();
163 i->second->Update();
164 }
165 }
166
CommitVisitedLinks()167 void VisitedLinkEventListener::CommitVisitedLinks() {
168 // Send to all RenderProcessHosts.
169 for (Updaters::iterator i = updaters_.begin(); i != updaters_.end(); ++i) {
170 i->second->AddLinks(pending_visited_links_);
171 i->second->Update();
172 }
173
174 pending_visited_links_.clear();
175 }
176
Observe(int type,const content::NotificationSource & source,const content::NotificationDetails & details)177 void VisitedLinkEventListener::Observe(
178 int type,
179 const content::NotificationSource& source,
180 const content::NotificationDetails& details) {
181 switch (type) {
182 case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
183 content::RenderProcessHost* process =
184 content::Source<content::RenderProcessHost>(source).ptr();
185 if (browser_context_ != process->GetBrowserContext())
186 return;
187
188 // Happens on browser start up.
189 if (!master_->shared_memory())
190 return;
191
192 updaters_[process->GetID()] =
193 make_linked_ptr(new VisitedLinkUpdater(process->GetID()));
194 updaters_[process->GetID()]->SendVisitedLinkTable(
195 master_->shared_memory());
196 break;
197 }
198 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
199 content::RenderProcessHost* process =
200 content::Source<content::RenderProcessHost>(source).ptr();
201 if (updaters_.count(process->GetID())) {
202 updaters_.erase(process->GetID());
203 }
204 break;
205 }
206 case content::NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED: {
207 RenderWidgetHost* widget =
208 content::Source<RenderWidgetHost>(source).ptr();
209 int child_id = widget->GetProcess()->GetID();
210 if (updaters_.count(child_id))
211 updaters_[child_id]->Update();
212 break;
213 }
214 default:
215 NOTREACHED();
216 break;
217 }
218 }
219
220 } // namespace visitedlink
221