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: 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 112 VisitedLinkEventListener::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 125 VisitedLinkEventListener::~VisitedLinkEventListener() { 126 if (!pending_visited_links_.empty()) 127 pending_visited_links_.clear(); 128 } 129 130 void 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 146 void 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 156 void 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 166 void 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 176 void 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