Home | History | Annotate | Download | only in browser
      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