Home | History | Annotate | Download | only in worker_host
      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 "content/browser/worker_host/worker_service_impl.h"
      6 
      7 #include <string>
      8 
      9 #include "base/command_line.h"
     10 #include "base/logging.h"
     11 #include "base/threading/thread.h"
     12 #include "content/browser/devtools/worker_devtools_manager.h"
     13 #include "content/browser/renderer_host/render_widget_host_impl.h"
     14 #include "content/browser/shared_worker/shared_worker_service_impl.h"
     15 #include "content/browser/worker_host/worker_message_filter.h"
     16 #include "content/browser/worker_host/worker_process_host.h"
     17 #include "content/common/view_messages.h"
     18 #include "content/common/worker_messages.h"
     19 #include "content/public/browser/child_process_data.h"
     20 #include "content/public/browser/notification_service.h"
     21 #include "content/public/browser/notification_types.h"
     22 #include "content/public/browser/render_frame_host.h"
     23 #include "content/public/browser/render_process_host.h"
     24 #include "content/public/browser/render_view_host.h"
     25 #include "content/public/browser/render_widget_host.h"
     26 #include "content/public/browser/render_widget_host_iterator.h"
     27 #include "content/public/browser/render_widget_host_view.h"
     28 #include "content/public/browser/resource_context.h"
     29 #include "content/public/browser/web_contents.h"
     30 #include "content/public/browser/worker_service_observer.h"
     31 #include "content/public/common/content_switches.h"
     32 #include "content/public/common/process_type.h"
     33 
     34 namespace content {
     35 
     36 namespace {
     37 void AddRenderFrameID(std::set<std::pair<int, int> >* visible_frame_ids,
     38                       RenderFrameHost* rfh) {
     39   visible_frame_ids->insert(
     40       std::pair<int, int>(rfh->GetProcess()->GetID(),
     41                           rfh->GetRoutingID()));
     42 }
     43 }
     44 
     45 const int WorkerServiceImpl::kMaxWorkersWhenSeparate = 64;
     46 const int WorkerServiceImpl::kMaxWorkersPerFrameWhenSeparate = 16;
     47 
     48 class WorkerPrioritySetter
     49     : public NotificationObserver,
     50       public base::RefCountedThreadSafe<WorkerPrioritySetter,
     51                                         BrowserThread::DeleteOnUIThread> {
     52  public:
     53   WorkerPrioritySetter();
     54 
     55   // Posts a task to the UI thread to register to receive notifications.
     56   void Initialize();
     57 
     58   // Invoked by WorkerServiceImpl when a worker process is created.
     59   void NotifyWorkerProcessCreated();
     60 
     61  private:
     62   friend class base::RefCountedThreadSafe<WorkerPrioritySetter>;
     63   friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>;
     64   friend class base::DeleteHelper<WorkerPrioritySetter>;
     65   virtual ~WorkerPrioritySetter();
     66 
     67   // Posts a task to perform a worker priority update.
     68   void PostTaskToGatherAndUpdateWorkerPriorities();
     69 
     70   // Gathers up a list of the visible tabs and then updates priorities for
     71   // all the shared workers.
     72   void GatherVisibleIDsAndUpdateWorkerPriorities();
     73 
     74   // Registers as an observer to receive notifications about
     75   // widgets being shown.
     76   void RegisterObserver();
     77 
     78   // Sets priorities for shared workers given a set of visible frames (as a
     79   // std::set of std::pair<render_process, render_frame> ids.
     80   void UpdateWorkerPrioritiesFromVisibleSet(
     81       const std::set<std::pair<int, int> >* visible);
     82 
     83   // Called to refresh worker priorities when focus changes between tabs.
     84   void OnRenderWidgetVisibilityChanged(std::pair<int, int>);
     85 
     86   // NotificationObserver implementation.
     87   virtual void Observe(int type,
     88                        const NotificationSource& source,
     89                        const NotificationDetails& details) OVERRIDE;
     90 
     91   NotificationRegistrar registrar_;
     92 };
     93 
     94 WorkerPrioritySetter::WorkerPrioritySetter() {
     95 }
     96 
     97 WorkerPrioritySetter::~WorkerPrioritySetter() {
     98   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
     99 }
    100 
    101 void WorkerPrioritySetter::Initialize() {
    102   BrowserThread::PostTask(
    103       BrowserThread::UI, FROM_HERE,
    104       base::Bind(&WorkerPrioritySetter::RegisterObserver, this));
    105 }
    106 
    107 void WorkerPrioritySetter::NotifyWorkerProcessCreated() {
    108   PostTaskToGatherAndUpdateWorkerPriorities();
    109 }
    110 
    111 void WorkerPrioritySetter::PostTaskToGatherAndUpdateWorkerPriorities() {
    112   BrowserThread::PostTask(
    113       BrowserThread::UI, FROM_HERE,
    114       base::Bind(
    115           &WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities,
    116           this));
    117 }
    118 
    119 void WorkerPrioritySetter::GatherVisibleIDsAndUpdateWorkerPriorities() {
    120   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    121   std::set<std::pair<int, int> >* visible_frame_ids =
    122       new std::set<std::pair<int, int> >();
    123 
    124   // Gather up all the visible renderer process/view pairs
    125   scoped_ptr<RenderWidgetHostIterator> widgets(
    126       RenderWidgetHost::GetRenderWidgetHosts());
    127   while (RenderWidgetHost* widget = widgets->GetNextHost()) {
    128     if (widget->GetProcess()->VisibleWidgetCount() == 0)
    129       continue;
    130     if (!widget->IsRenderView())
    131       continue;
    132 
    133     RenderWidgetHostView* widget_view = widget->GetView();
    134     if (!widget_view || !widget_view->IsShowing())
    135       continue;
    136     RenderViewHost* rvh = RenderViewHost::From(widget);
    137     WebContents* web_contents = WebContents::FromRenderViewHost(rvh);
    138     if (!web_contents)
    139       continue;
    140     web_contents->ForEachFrame(
    141         base::Bind(&AddRenderFrameID, visible_frame_ids));
    142   }
    143 
    144   BrowserThread::PostTask(
    145       BrowserThread::IO, FROM_HERE,
    146       base::Bind(&WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet,
    147                  this, base::Owned(visible_frame_ids)));
    148 }
    149 
    150 void WorkerPrioritySetter::UpdateWorkerPrioritiesFromVisibleSet(
    151     const std::set<std::pair<int, int> >* visible_frame_ids) {
    152   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    153 
    154   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    155     if (!iter->process_launched())
    156       continue;
    157     bool throttle = true;
    158 
    159     for (WorkerProcessHost::Instances::const_iterator instance =
    160         iter->instances().begin(); instance != iter->instances().end();
    161         ++instance) {
    162 
    163       // This code assumes one worker per process
    164       WorkerProcessHost::Instances::const_iterator first_instance =
    165           iter->instances().begin();
    166       if (first_instance == iter->instances().end())
    167         continue;
    168 
    169       WorkerDocumentSet::DocumentInfoSet::const_iterator info =
    170           first_instance->worker_document_set()->documents().begin();
    171 
    172       for (; info != first_instance->worker_document_set()->documents().end();
    173           ++info) {
    174         std::pair<int, int> id(
    175             info->render_process_id(), info->render_frame_id());
    176         if (visible_frame_ids->find(id) != visible_frame_ids->end()) {
    177           throttle = false;
    178           break;
    179         }
    180       }
    181 
    182       if (!throttle ) {
    183         break;
    184       }
    185     }
    186 
    187     iter->SetBackgrounded(throttle);
    188   }
    189 }
    190 
    191 void WorkerPrioritySetter::OnRenderWidgetVisibilityChanged(
    192     std::pair<int, int> id) {
    193   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    194   std::set<std::pair<int, int> > visible_frame_ids;
    195 
    196   visible_frame_ids.insert(id);
    197 
    198   UpdateWorkerPrioritiesFromVisibleSet(&visible_frame_ids);
    199 }
    200 
    201 void WorkerPrioritySetter::RegisterObserver() {
    202   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
    203   registrar_.Add(this, NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED,
    204                  NotificationService::AllBrowserContextsAndSources());
    205   registrar_.Add(this, NOTIFICATION_RENDERER_PROCESS_CREATED,
    206                  NotificationService::AllBrowserContextsAndSources());
    207 }
    208 
    209 void WorkerPrioritySetter::Observe(int type,
    210     const NotificationSource& source, const NotificationDetails& details) {
    211   if (type == NOTIFICATION_RENDER_WIDGET_VISIBILITY_CHANGED) {
    212     bool visible = *Details<bool>(details).ptr();
    213 
    214     if (visible) {
    215       int render_widget_id =
    216           Source<RenderWidgetHost>(source).ptr()->GetRoutingID();
    217       int render_process_pid =
    218           Source<RenderWidgetHost>(source).ptr()->GetProcess()->GetID();
    219 
    220       BrowserThread::PostTask(
    221           BrowserThread::IO, FROM_HERE,
    222           base::Bind(&WorkerPrioritySetter::OnRenderWidgetVisibilityChanged,
    223               this, std::pair<int, int>(render_process_pid, render_widget_id)));
    224     }
    225   }
    226   else if (type == NOTIFICATION_RENDERER_PROCESS_CREATED) {
    227     PostTaskToGatherAndUpdateWorkerPriorities();
    228   }
    229 }
    230 
    231 WorkerService* WorkerService::GetInstance() {
    232   if (EmbeddedSharedWorkerEnabled())
    233     return SharedWorkerServiceImpl::GetInstance();
    234   else
    235     return WorkerServiceImpl::GetInstance();
    236 }
    237 
    238 bool WorkerService::EmbeddedSharedWorkerEnabled() {
    239   static bool disabled = CommandLine::ForCurrentProcess()->HasSwitch(
    240       switches::kDisableEmbeddedSharedWorker);
    241   return !disabled;
    242 }
    243 
    244 WorkerServiceImpl* WorkerServiceImpl::GetInstance() {
    245   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    246   return Singleton<WorkerServiceImpl>::get();
    247 }
    248 
    249 WorkerServiceImpl::WorkerServiceImpl()
    250     : priority_setter_(new WorkerPrioritySetter()),
    251       next_worker_route_id_(0) {
    252   priority_setter_->Initialize();
    253 }
    254 
    255 WorkerServiceImpl::~WorkerServiceImpl() {
    256   // The observers in observers_ can't be used here because they might be
    257   // gone already.
    258 }
    259 
    260 void WorkerServiceImpl::PerformTeardownForTesting() {
    261   priority_setter_ = NULL;
    262 }
    263 
    264 void WorkerServiceImpl::OnWorkerMessageFilterClosing(
    265     WorkerMessageFilter* filter) {
    266   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    267     iter->FilterShutdown(filter);
    268   }
    269 
    270   // See if that process had any queued workers.
    271   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
    272        i != queued_workers_.end();) {
    273     i->RemoveFilters(filter);
    274     if (i->NumFilters() == 0) {
    275       i = queued_workers_.erase(i);
    276     } else {
    277       ++i;
    278     }
    279   }
    280 
    281   // Either a worker proceess has shut down, in which case we can start one of
    282   // the queued workers, or a renderer has shut down, in which case it doesn't
    283   // affect anything.  We call this function in both scenarios because then we
    284   // don't have to keep track which filters are from worker processes.
    285   TryStartingQueuedWorker();
    286 }
    287 
    288 void WorkerServiceImpl::CreateWorker(
    289     const ViewHostMsg_CreateWorker_Params& params,
    290     int route_id,
    291     WorkerMessageFilter* filter,
    292     ResourceContext* resource_context,
    293     const WorkerStoragePartition& partition,
    294     bool* url_mismatch) {
    295   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    296   *url_mismatch = false;
    297   WorkerProcessHost::WorkerInstance* existing_instance =
    298       FindSharedWorkerInstance(
    299           params.url, params.name, partition, resource_context);
    300   if (existing_instance) {
    301     if (params.url != existing_instance->url()) {
    302       *url_mismatch = true;
    303       return;
    304     }
    305     if (existing_instance->load_failed()) {
    306       filter->Send(new ViewMsg_WorkerScriptLoadFailed(route_id));
    307       return;
    308     }
    309     existing_instance->AddFilter(filter, route_id);
    310     existing_instance->worker_document_set()->Add(
    311         filter, params.document_id, filter->render_process_id(),
    312         params.render_frame_route_id);
    313     filter->Send(new ViewMsg_WorkerCreated(route_id));
    314     return;
    315   }
    316   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
    317        i != queued_workers_.end(); ++i) {
    318     if (i->Matches(params.url, params.name, partition, resource_context) &&
    319         params.url != i->url()) {
    320       *url_mismatch = true;
    321       return;
    322     }
    323   }
    324 
    325   // Generate a unique route id for the browser-worker communication that's
    326   // unique among all worker processes.  That way when the worker process sends
    327   // a wrapped IPC message through us, we know which WorkerProcessHost to give
    328   // it to.
    329   WorkerProcessHost::WorkerInstance instance(
    330       params.url,
    331       params.name,
    332       params.content_security_policy,
    333       params.security_policy_type,
    334       next_worker_route_id(),
    335       params.render_frame_route_id,
    336       resource_context,
    337       partition);
    338   instance.AddFilter(filter, route_id);
    339   instance.worker_document_set()->Add(
    340       filter, params.document_id, filter->render_process_id(),
    341       params.render_frame_route_id);
    342 
    343   CreateWorkerFromInstance(instance);
    344 }
    345 
    346 void WorkerServiceImpl::ForwardToWorker(const IPC::Message& message,
    347                                         WorkerMessageFilter* filter) {
    348   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    349     if (iter->FilterMessage(message, filter))
    350       return;
    351   }
    352 
    353   // TODO(jabdelmalek): tell filter that callee is gone
    354 }
    355 
    356 void WorkerServiceImpl::DocumentDetached(unsigned long long document_id,
    357                                          WorkerMessageFilter* filter) {
    358   // Any associated shared workers can be shut down.
    359   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter)
    360     iter->DocumentDetached(filter, document_id);
    361 
    362   // Remove any queued shared workers for this document.
    363   for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
    364        iter != queued_workers_.end();) {
    365 
    366     iter->worker_document_set()->Remove(filter, document_id);
    367     if (iter->worker_document_set()->IsEmpty()) {
    368       iter = queued_workers_.erase(iter);
    369       continue;
    370     }
    371     ++iter;
    372   }
    373 }
    374 
    375 bool WorkerServiceImpl::CreateWorkerFromInstance(
    376     WorkerProcessHost::WorkerInstance instance) {
    377   if (!CanCreateWorkerProcess(instance)) {
    378     queued_workers_.push_back(instance);
    379     return true;
    380   }
    381 
    382   // Remove any queued instances of this worker and copy over the filter to
    383   // this instance.
    384   for (WorkerProcessHost::Instances::iterator iter = queued_workers_.begin();
    385        iter != queued_workers_.end();) {
    386     if (iter->Matches(instance.url(), instance.name(),
    387                       instance.partition(), instance.resource_context())) {
    388       DCHECK(iter->NumFilters() == 1);
    389       DCHECK_EQ(instance.url(), iter->url());
    390       WorkerProcessHost::WorkerInstance::FilterInfo filter_info =
    391           iter->GetFilter();
    392       instance.AddFilter(filter_info.filter(), filter_info.route_id());
    393       iter = queued_workers_.erase(iter);
    394     } else {
    395       ++iter;
    396     }
    397   }
    398 
    399   WorkerMessageFilter* first_filter = instance.filters().begin()->filter();
    400   WorkerProcessHost* worker = new WorkerProcessHost(
    401       instance.resource_context(), instance.partition());
    402   // TODO(atwilson): This won't work if the message is from a worker process.
    403   // We don't support that yet though (this message is only sent from
    404   // renderers) but when we do, we'll need to add code to pass in the current
    405   // worker's document set for nested workers.
    406   if (!worker->Init(first_filter->render_process_id(),
    407                     instance.render_frame_id())) {
    408     delete worker;
    409     return false;
    410   }
    411 
    412   worker->CreateWorker(
    413       instance,
    414       WorkerDevToolsManager::GetInstance()->WorkerCreated(worker, instance));
    415   FOR_EACH_OBSERVER(
    416       WorkerServiceObserver, observers_,
    417       WorkerCreated(instance.url(), instance.name(), worker->GetData().id,
    418                     instance.worker_route_id()));
    419   return true;
    420 }
    421 
    422 bool WorkerServiceImpl::CanCreateWorkerProcess(
    423     const WorkerProcessHost::WorkerInstance& instance) {
    424   // Worker can be fired off if *any* parent has room.
    425   const WorkerDocumentSet::DocumentInfoSet& parents =
    426         instance.worker_document_set()->documents();
    427 
    428   for (WorkerDocumentSet::DocumentInfoSet::const_iterator parent_iter =
    429            parents.begin();
    430        parent_iter != parents.end(); ++parent_iter) {
    431     bool hit_total_worker_limit = false;
    432     if (FrameCanCreateWorkerProcess(parent_iter->render_process_id(),
    433                                     parent_iter->render_frame_id(),
    434                                     &hit_total_worker_limit)) {
    435       return true;
    436     }
    437     // Return false if already at the global worker limit (no need to continue
    438     // checking parent tabs).
    439     if (hit_total_worker_limit)
    440       return false;
    441   }
    442   // If we've reached here, none of the parent tabs is allowed to create an
    443   // instance.
    444   return false;
    445 }
    446 
    447 bool WorkerServiceImpl::FrameCanCreateWorkerProcess(
    448     int render_process_id,
    449     int render_frame_id,
    450     bool* hit_total_worker_limit) {
    451   int total_workers = 0;
    452   int workers_per_tab = 0;
    453   *hit_total_worker_limit = false;
    454   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    455     for (WorkerProcessHost::Instances::const_iterator cur_instance =
    456              iter->instances().begin();
    457          cur_instance != iter->instances().end(); ++cur_instance) {
    458       total_workers++;
    459       if (total_workers >= kMaxWorkersWhenSeparate) {
    460         *hit_total_worker_limit = true;
    461         return false;
    462       }
    463       if (cur_instance->FrameIsParent(render_process_id, render_frame_id)) {
    464         workers_per_tab++;
    465         if (workers_per_tab >= kMaxWorkersPerFrameWhenSeparate)
    466           return false;
    467       }
    468     }
    469   }
    470 
    471   return true;
    472 }
    473 
    474 void WorkerServiceImpl::TryStartingQueuedWorker() {
    475   if (queued_workers_.empty())
    476     return;
    477 
    478   for (WorkerProcessHost::Instances::iterator i = queued_workers_.begin();
    479        i != queued_workers_.end();) {
    480     if (CanCreateWorkerProcess(*i)) {
    481       WorkerProcessHost::WorkerInstance instance = *i;
    482       queued_workers_.erase(i);
    483       CreateWorkerFromInstance(instance);
    484 
    485       // CreateWorkerFromInstance can modify the queued_workers_ list when it
    486       // coalesces queued instances after starting a shared worker, so we
    487       // have to rescan the list from the beginning (our iterator is now
    488       // invalid). This is not a big deal as having any queued workers will be
    489       // rare in practice so the list will be small.
    490       i = queued_workers_.begin();
    491     } else {
    492       ++i;
    493     }
    494   }
    495 }
    496 
    497 bool WorkerServiceImpl::GetRendererForWorker(int worker_process_id,
    498                                              int* render_process_id,
    499                                              int* render_frame_id) const {
    500   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    501     if (iter.GetData().id != worker_process_id)
    502       continue;
    503 
    504     // This code assumes one worker per process, see function comment in header!
    505     WorkerProcessHost::Instances::const_iterator first_instance =
    506         iter->instances().begin();
    507     if (first_instance == iter->instances().end())
    508       return false;
    509 
    510     WorkerDocumentSet::DocumentInfoSet::const_iterator info =
    511         first_instance->worker_document_set()->documents().begin();
    512     *render_process_id = info->render_process_id();
    513     *render_frame_id = info->render_frame_id();
    514     return true;
    515   }
    516   return false;
    517 }
    518 
    519 const WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindWorkerInstance(
    520       int worker_process_id) {
    521   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    522     if (iter.GetData().id != worker_process_id)
    523         continue;
    524 
    525     WorkerProcessHost::Instances::const_iterator instance =
    526         iter->instances().begin();
    527     return instance == iter->instances().end() ? NULL : &*instance;
    528   }
    529   return NULL;
    530 }
    531 
    532 bool WorkerServiceImpl::TerminateWorker(int process_id, int route_id) {
    533   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    534     if (iter.GetData().id == process_id) {
    535       iter->TerminateWorker(route_id);
    536       return true;
    537     }
    538   }
    539   return false;
    540 }
    541 
    542 std::vector<WorkerService::WorkerInfo> WorkerServiceImpl::GetWorkers() {
    543   std::vector<WorkerService::WorkerInfo> results;
    544   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    545     const WorkerProcessHost::Instances& instances = (*iter)->instances();
    546     for (WorkerProcessHost::Instances::const_iterator i = instances.begin();
    547          i != instances.end(); ++i) {
    548       WorkerService::WorkerInfo info;
    549       info.url = i->url();
    550       info.name = i->name();
    551       info.route_id = i->worker_route_id();
    552       info.process_id = iter.GetData().id;
    553       info.handle = iter.GetData().handle;
    554       results.push_back(info);
    555     }
    556   }
    557   return results;
    558 }
    559 
    560 void WorkerServiceImpl::AddObserver(WorkerServiceObserver* observer) {
    561   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    562   observers_.AddObserver(observer);
    563 }
    564 
    565 void WorkerServiceImpl::RemoveObserver(WorkerServiceObserver* observer) {
    566   DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
    567   observers_.RemoveObserver(observer);
    568 }
    569 
    570 void WorkerServiceImpl::NotifyWorkerDestroyed(
    571     WorkerProcessHost* process,
    572     int worker_route_id) {
    573   WorkerDevToolsManager::GetInstance()->WorkerDestroyed(
    574       process, worker_route_id);
    575   FOR_EACH_OBSERVER(WorkerServiceObserver, observers_,
    576                     WorkerDestroyed(process->GetData().id, worker_route_id));
    577 }
    578 
    579 void WorkerServiceImpl::NotifyWorkerProcessCreated() {
    580   priority_setter_->NotifyWorkerProcessCreated();
    581 }
    582 
    583 WorkerProcessHost::WorkerInstance* WorkerServiceImpl::FindSharedWorkerInstance(
    584     const GURL& url,
    585     const base::string16& name,
    586     const WorkerStoragePartition& partition,
    587     ResourceContext* resource_context) {
    588   for (WorkerProcessHostIterator iter; !iter.Done(); ++iter) {
    589     for (WorkerProcessHost::Instances::iterator instance_iter =
    590              iter->mutable_instances().begin();
    591          instance_iter != iter->mutable_instances().end();
    592          ++instance_iter) {
    593       if (instance_iter->Matches(url, name, partition, resource_context))
    594         return &(*instance_iter);
    595     }
    596   }
    597   return NULL;
    598 }
    599 
    600 }  // namespace content
    601