1 // Copyright 2017 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 "extensions/browser/extension_registrar.h"
6 
7 #include "base/bind.h"
8 #include "base/callback_helpers.h"
9 #include "base/check_op.h"
10 #include "base/metrics/histogram_macros.h"
11 #include "base/notreached.h"
12 #include "base/stl_util.h"
13 #include "content/public/browser/browser_context.h"
14 #include "content/public/browser/browser_thread.h"
15 #include "content/public/browser/devtools_agent_host.h"
16 #include "content/public/browser/notification_service.h"
17 #include "extensions/browser/app_sorting.h"
18 #include "extensions/browser/extension_host.h"
19 #include "extensions/browser/extension_prefs.h"
20 #include "extensions/browser/extension_registry.h"
21 #include "extensions/browser/extension_system.h"
22 #include "extensions/browser/lazy_context_id.h"
23 #include "extensions/browser/lazy_context_task_queue.h"
24 #include "extensions/browser/notification_types.h"
25 #include "extensions/browser/process_manager.h"
26 #include "extensions/browser/renderer_startup_helper.h"
27 #include "extensions/browser/runtime_data.h"
28 #include "extensions/browser/service_worker_task_queue.h"
29 #include "extensions/browser/task_queue_util.h"
30 #include "extensions/common/manifest_handlers/background_info.h"
31 
32 using content::DevToolsAgentHost;
33 
34 namespace extensions {
35 
ExtensionRegistrar(content::BrowserContext * browser_context,Delegate * delegate)36 ExtensionRegistrar::ExtensionRegistrar(content::BrowserContext* browser_context,
37                                        Delegate* delegate)
38     : browser_context_(browser_context),
39       delegate_(delegate),
40       extension_system_(ExtensionSystem::Get(browser_context)),
41       extension_prefs_(ExtensionPrefs::Get(browser_context)),
42       registry_(ExtensionRegistry::Get(browser_context)),
43       renderer_helper_(
44           RendererStartupHelperFactory::GetForBrowserContext(browser_context)) {
45 }
46 
47 ExtensionRegistrar::~ExtensionRegistrar() = default;
48 
AddExtension(scoped_refptr<const Extension> extension)49 void ExtensionRegistrar::AddExtension(
50     scoped_refptr<const Extension> extension) {
51   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
52 
53   bool is_extension_upgrade = false;
54   bool is_extension_loaded = false;
55   const Extension* old = registry_->GetInstalledExtension(extension->id());
56   if (old) {
57     is_extension_loaded = true;
58     int version_compare_result = extension->version().CompareTo(old->version());
59     is_extension_upgrade = version_compare_result > 0;
60     // Other than for unpacked extensions, we should not be downgrading.
61     if (!Manifest::IsUnpackedLocation(extension->location()) &&
62         version_compare_result < 0) {
63       UMA_HISTOGRAM_ENUMERATION(
64           "Extensions.AttemptedToDowngradeVersionLocation",
65           extension->location(), Manifest::NUM_LOCATIONS);
66       UMA_HISTOGRAM_ENUMERATION("Extensions.AttemptedToDowngradeVersionType",
67                                 extension->GetType(), Manifest::NUM_LOAD_TYPES);
68 
69       // TODO(https://crbug.com/810799): It would be awfully nice to CHECK this,
70       // but that's caused problems. There are apparently times when this
71       // happens that we aren't accounting for. We should track those down and
72       // fix them, but it can be tricky.
73       NOTREACHED() << "Attempted to downgrade extension."
74                    << "\nID: " << extension->id()
75                    << "\nOld Version: " << old->version()
76                    << "\nNew Version: " << extension->version()
77                    << "\nLocation: " << extension->location();
78       return;
79     }
80   }
81 
82   // If the extension was disabled for a reload, we will enable it.
83   bool was_reloading = reloading_extensions_.erase(extension->id()) > 0;
84 
85   // Set the upgraded bit; we consider reloads upgrades.
86   extension_system_->runtime_data()->SetBeingUpgraded(
87       extension->id(), is_extension_upgrade || was_reloading);
88 
89   // The extension is now loaded; remove its data from unloaded extension map.
90   unloaded_extension_paths_.erase(extension->id());
91 
92   // If a terminated extension is loaded, remove it from the terminated list.
93   UntrackTerminatedExtension(extension->id());
94 
95   // Notify the delegate we will add the extension.
96   delegate_->PreAddExtension(extension.get(), old);
97 
98   if (was_reloading) {
99     failed_to_reload_unpacked_extensions_.erase(extension->path());
100     ReplaceReloadedExtension(extension);
101   } else {
102     if (is_extension_loaded) {
103       // To upgrade an extension in place, remove the old one and then activate
104       // the new one. ReloadExtension disables the extension, which is
105       // sufficient.
106       RemoveExtension(extension->id(), UnloadedExtensionReason::UPDATE);
107     }
108     AddNewExtension(extension);
109   }
110 
111   extension_system_->runtime_data()->SetBeingUpgraded(extension->id(), false);
112 }
113 
AddNewExtension(scoped_refptr<const Extension> extension)114 void ExtensionRegistrar::AddNewExtension(
115     scoped_refptr<const Extension> extension) {
116   if (extension_prefs_->IsExtensionBlocklisted(extension->id())) {
117     DCHECK(!Manifest::IsComponentLocation(extension->location()));
118     // Only prefs is checked for the blocklist. We rely on callers to check the
119     // blocklist before calling into here, e.g. CrxInstaller checks before
120     // installation then threads through the install and pending install flow
121     // of this class, and ExtensionService checks when loading installed
122     // extensions.
123     registry_->AddBlocklisted(extension);
124   } else if (delegate_->ShouldBlockExtension(extension.get())) {
125     DCHECK(!Manifest::IsComponentLocation(extension->location()));
126     registry_->AddBlocked(extension);
127   } else if (extension_prefs_->IsExtensionDisabled(extension->id())) {
128     registry_->AddDisabled(extension);
129     // Notify that a disabled extension was added or updated.
130     content::NotificationService::current()->Notify(
131         extensions::NOTIFICATION_EXTENSION_UPDATE_DISABLED,
132         content::Source<content::BrowserContext>(browser_context_),
133         content::Details<const Extension>(extension.get()));
134   } else {  // Extension should be enabled.
135     // All apps that are displayed in the launcher are ordered by their ordinals
136     // so we must ensure they have valid ordinals.
137     if (extension->RequiresSortOrdinal()) {
138       AppSorting* app_sorting = extension_system_->app_sorting();
139       app_sorting->SetExtensionVisible(extension->id(),
140                                        extension->ShouldDisplayInNewTabPage());
141       app_sorting->EnsureValidOrdinals(extension->id(),
142                                        syncer::StringOrdinal());
143     }
144     registry_->AddEnabled(extension);
145     ActivateExtension(extension.get(), true);
146   }
147 }
148 
RemoveExtension(const ExtensionId & extension_id,UnloadedExtensionReason reason)149 void ExtensionRegistrar::RemoveExtension(const ExtensionId& extension_id,
150                                          UnloadedExtensionReason reason) {
151   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
152 
153   int include_mask =
154       ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::TERMINATED;
155   scoped_refptr<const Extension> extension(
156       registry_->GetExtensionById(extension_id, include_mask));
157 
158   // If the extension was already removed, just notify of the new unload reason.
159   // TODO: It's unclear when this needs to be called given that it may be a
160   // duplicate notification. See crbug.com/708230.
161   if (!extension) {
162     extension_system_->UnregisterExtensionWithRequestContexts(extension_id,
163                                                               reason);
164     return;
165   }
166 
167   // Keep information about the extension so that we can reload it later
168   // even if it's not permanently installed.
169   unloaded_extension_paths_[extension->id()] = extension->path();
170 
171   // Stop tracking whether the extension was meant to be enabled after a reload.
172   reloading_extensions_.erase(extension->id());
173 
174   if (registry_->disabled_extensions().Contains(extension_id)) {
175     // The extension is already deactivated.
176     registry_->RemoveDisabled(extension->id());
177     extension_system_->UnregisterExtensionWithRequestContexts(extension_id,
178                                                               reason);
179   } else {
180     // TODO(michaelpg): The extension may be blocked or blocklisted, in which
181     // case it shouldn't need to be "deactivated". Determine whether the removal
182     // notifications are necessary (crbug.com/708230).
183     registry_->RemoveEnabled(extension_id);
184     DeactivateExtension(extension.get(), reason);
185   }
186 
187   content::NotificationService::current()->Notify(
188       extensions::NOTIFICATION_EXTENSION_REMOVED,
189       content::Source<content::BrowserContext>(browser_context_),
190       content::Details<const Extension>(extension.get()));
191 }
192 
EnableExtension(const ExtensionId & extension_id)193 void ExtensionRegistrar::EnableExtension(const ExtensionId& extension_id) {
194   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
195 
196   // If the extension is currently reloading, it will be enabled once the reload
197   // is complete.
198   if (reloading_extensions_.count(extension_id) > 0)
199     return;
200 
201   // First, check that the extension can be enabled.
202   if (IsExtensionEnabled(extension_id) ||
203       extension_prefs_->IsExtensionBlocklisted(extension_id) ||
204       registry_->blocked_extensions().Contains(extension_id)) {
205     return;
206   }
207 
208   const Extension* extension =
209       registry_->disabled_extensions().GetByID(extension_id);
210   if (extension && !delegate_->CanEnableExtension(extension))
211     return;
212 
213   // Now that we know the extension can be enabled, update the prefs.
214   extension_prefs_->SetExtensionEnabled(extension_id);
215 
216   // This can happen if sync enables an extension that is not installed yet.
217   if (!extension)
218     return;
219 
220   // Actually enable the extension.
221   registry_->AddEnabled(extension);
222   registry_->RemoveDisabled(extension->id());
223   ActivateExtension(extension, false);
224 }
225 
DisableExtension(const ExtensionId & extension_id,int disable_reasons)226 void ExtensionRegistrar::DisableExtension(const ExtensionId& extension_id,
227                                           int disable_reasons) {
228   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
229   DCHECK_NE(disable_reason::DISABLE_NONE, disable_reasons);
230 
231   if (extension_prefs_->IsExtensionBlocklisted(extension_id))
232     return;
233 
234   // The extension may have been disabled already. Just add the disable reasons.
235   // TODO(michaelpg): Move this after the policy check, below, to ensure that
236   // disable reasons disallowed by policy are not added here.
237   if (!IsExtensionEnabled(extension_id)) {
238     extension_prefs_->AddDisableReasons(extension_id, disable_reasons);
239     return;
240   }
241 
242   scoped_refptr<const Extension> extension =
243       registry_->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
244 
245   bool is_controlled_extension =
246       !delegate_->CanDisableExtension(extension.get());
247 
248   if (is_controlled_extension) {
249     // Remove disallowed disable reasons.
250     // Certain disable reasons are always allowed, since they are more internal
251     // to the browser (rather than the user choosing to disable the extension).
252     int internal_disable_reason_mask =
253         extensions::disable_reason::DISABLE_RELOAD |
254         extensions::disable_reason::DISABLE_CORRUPTED |
255         extensions::disable_reason::DISABLE_UPDATE_REQUIRED_BY_POLICY |
256         extensions::disable_reason::DISABLE_BLOCKED_BY_POLICY |
257         extensions::disable_reason::DISABLE_CUSTODIAN_APPROVAL_REQUIRED |
258         extensions::disable_reason::DISABLE_REINSTALL;
259     disable_reasons &= internal_disable_reason_mask;
260 
261     if (disable_reasons == disable_reason::DISABLE_NONE)
262       return;
263   }
264 
265   extension_prefs_->SetExtensionDisabled(extension_id, disable_reasons);
266 
267   int include_mask =
268       ExtensionRegistry::EVERYTHING & ~ExtensionRegistry::DISABLED;
269   extension = registry_->GetExtensionById(extension_id, include_mask);
270   if (!extension)
271     return;
272 
273   // The extension is either enabled or terminated.
274   DCHECK(registry_->enabled_extensions().Contains(extension->id()) ||
275          registry_->terminated_extensions().Contains(extension->id()));
276 
277   // Move the extension to the disabled list.
278   registry_->AddDisabled(extension);
279   if (registry_->enabled_extensions().Contains(extension->id())) {
280     registry_->RemoveEnabled(extension->id());
281     DeactivateExtension(extension.get(), UnloadedExtensionReason::DISABLE);
282   } else {
283     // The extension must have been terminated. Don't send additional
284     // notifications for it being disabled.
285     bool removed = registry_->RemoveTerminated(extension->id());
286     DCHECK(removed);
287   }
288 }
289 
ReloadExtension(const ExtensionId extension_id,LoadErrorBehavior load_error_behavior)290 void ExtensionRegistrar::ReloadExtension(
291     const ExtensionId extension_id,  // Passed by value because reloading can
292                                      // invalidate a reference to the ID.
293     LoadErrorBehavior load_error_behavior) {
294   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
295 
296   base::FilePath path;
297 
298   const Extension* disabled_extension =
299       registry_->disabled_extensions().GetByID(extension_id);
300 
301   if (disabled_extension) {
302     path = disabled_extension->path();
303   }
304 
305   // If the extension is already reloading, don't reload again.
306   if (extension_prefs_->HasDisableReason(extension_id,
307                                          disable_reason::DISABLE_RELOAD)) {
308     DCHECK(disabled_extension);
309     // If an unpacked extension previously failed to reload, it will still be
310     // marked as disabled, but we can try to reload it again - the developer
311     // may have fixed the issue.
312     if (failed_to_reload_unpacked_extensions_.count(path) == 0)
313       return;
314     failed_to_reload_unpacked_extensions_.erase(path);
315   }
316   // Ignore attempts to reload a blocklisted or blocked extension. Sometimes
317   // this can happen in a convoluted reload sequence triggered by the
318   // termination of a blocklisted or blocked extension and a naive attempt to
319   // reload it. For an example see http://crbug.com/373842.
320   if (registry_->blocklisted_extensions().Contains(extension_id) ||
321       registry_->blocked_extensions().Contains(extension_id)) {
322     return;
323   }
324 
325   const Extension* enabled_extension =
326       registry_->enabled_extensions().GetByID(extension_id);
327 
328   // Disable the extension if it's loaded. It might not be loaded if it crashed.
329   if (enabled_extension) {
330     // If the extension has an inspector open for its background page, detach
331     // the inspector and hang onto a cookie for it, so that we can reattach
332     // later.
333     // TODO(yoz): this is not incognito-safe!
334     ProcessManager* manager = ProcessManager::Get(browser_context_);
335     ExtensionHost* host = manager->GetBackgroundHostForExtension(extension_id);
336     if (host && content::DevToolsAgentHost::HasFor(host->host_contents())) {
337       // Look for an open inspector for the background page.
338       scoped_refptr<content::DevToolsAgentHost> agent_host =
339           content::DevToolsAgentHost::GetOrCreateFor(host->host_contents());
340       agent_host->DisconnectWebContents();
341       orphaned_dev_tools_[extension_id] = agent_host;
342     }
343 
344     path = enabled_extension->path();
345     // BeingUpgraded is set back to false when the extension is added.
346     extension_system_->runtime_data()->SetBeingUpgraded(enabled_extension->id(),
347                                                         true);
348     DisableExtension(extension_id, disable_reason::DISABLE_RELOAD);
349     DCHECK(registry_->disabled_extensions().Contains(extension_id));
350     reloading_extensions_.insert(extension_id);
351   } else if (!disabled_extension) {
352     std::map<ExtensionId, base::FilePath>::const_iterator iter =
353         unloaded_extension_paths_.find(extension_id);
354     if (iter == unloaded_extension_paths_.end()) {
355       return;
356     }
357     path = unloaded_extension_paths_[extension_id];
358   }
359 
360   delegate_->LoadExtensionForReload(extension_id, path, load_error_behavior);
361 }
362 
OnUnpackedExtensionReloadFailed(const base::FilePath & path)363 void ExtensionRegistrar::OnUnpackedExtensionReloadFailed(
364     const base::FilePath& path) {
365   failed_to_reload_unpacked_extensions_.insert(path);
366 }
367 
TerminateExtension(const ExtensionId & extension_id)368 void ExtensionRegistrar::TerminateExtension(const ExtensionId& extension_id) {
369   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
370 
371   scoped_refptr<const Extension> extension =
372       registry_->enabled_extensions().GetByID(extension_id);
373   if (!extension)
374     return;
375 
376   // Keep information about the extension so that we can reload it later
377   // even if it's not permanently installed.
378   unloaded_extension_paths_[extension->id()] = extension->path();
379 
380   DCHECK(!base::Contains(reloading_extensions_, extension->id()))
381       << "Enabled extension shouldn't be marked for reloading";
382 
383   registry_->AddTerminated(extension);
384   registry_->RemoveEnabled(extension_id);
385   DeactivateExtension(extension.get(), UnloadedExtensionReason::TERMINATE);
386 }
387 
UntrackTerminatedExtension(const ExtensionId & extension_id)388 void ExtensionRegistrar::UntrackTerminatedExtension(
389     const ExtensionId& extension_id) {
390   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
391 
392   scoped_refptr<const Extension> extension =
393       registry_->terminated_extensions().GetByID(extension_id);
394   if (!extension)
395     return;
396 
397   registry_->RemoveTerminated(extension_id);
398 
399   // TODO(michaelpg): This notification was already sent when the extension was
400   // unloaded as part of being terminated. But we send it again as observers
401   // may be tracking the terminated extension. See crbug.com/708230.
402   content::NotificationService::current()->Notify(
403       extensions::NOTIFICATION_EXTENSION_REMOVED,
404       content::Source<content::BrowserContext>(browser_context_),
405       content::Details<const Extension>(extension.get()));
406 }
407 
IsExtensionEnabled(const ExtensionId & extension_id) const408 bool ExtensionRegistrar::IsExtensionEnabled(
409     const ExtensionId& extension_id) const {
410   if (registry_->enabled_extensions().Contains(extension_id) ||
411       registry_->terminated_extensions().Contains(extension_id)) {
412     return true;
413   }
414 
415   if (registry_->disabled_extensions().Contains(extension_id) ||
416       registry_->blocklisted_extensions().Contains(extension_id) ||
417       registry_->blocked_extensions().Contains(extension_id)) {
418     return false;
419   }
420 
421   if (delegate_->ShouldBlockExtension(nullptr))
422     return false;
423 
424   // If the extension hasn't been loaded yet, check the prefs for it. Assume
425   // enabled unless otherwise noted.
426   return !extension_prefs_->IsExtensionDisabled(extension_id) &&
427          !extension_prefs_->IsExtensionBlocklisted(extension_id) &&
428          !extension_prefs_->IsExternalExtensionUninstalled(extension_id);
429 }
430 
DidCreateRenderViewForBackgroundPage(ExtensionHost * host)431 void ExtensionRegistrar::DidCreateRenderViewForBackgroundPage(
432     ExtensionHost* host) {
433   auto iter = orphaned_dev_tools_.find(host->extension_id());
434   if (iter == orphaned_dev_tools_.end())
435     return;
436   // Keepalive count is reset on extension reload. This re-establishes the
437   // keepalive that was added when the DevTools agent was initially attached.
438   ProcessManager::Get(browser_context_)
439       ->IncrementLazyKeepaliveCount(host->extension(), Activity::DEV_TOOLS,
440                                     std::string());
441   iter->second->ConnectWebContents(host->host_contents());
442   orphaned_dev_tools_.erase(iter);
443 }
444 
ActivateExtension(const Extension * extension,bool is_newly_added)445 void ExtensionRegistrar::ActivateExtension(const Extension* extension,
446                                            bool is_newly_added) {
447   // The URLRequestContexts need to be first to know that the extension
448   // was loaded. Otherwise a race can arise where a renderer that is created
449   // for the extension may try to load an extension URL with an extension id
450   // that the request context doesn't yet know about. The BrowserContext should
451   // ensure its URLRequestContexts appropriately discover the loaded extension.
452   extension_system_->RegisterExtensionWithRequestContexts(
453       extension,
454       base::BindOnce(
455           &ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts,
456           weak_factory_.GetWeakPtr(), WrapRefCounted(extension)));
457 
458   // Activate the extension before calling
459   // RendererStartupHelper::OnExtensionLoaded() below, so that we have
460   // activation information ready while we send ExtensionMsg_Load IPC.
461   //
462   // TODO(lazyboy): We should move all logic that is required to start up an
463   // extension to a separate class, instead of calling adhoc methods like
464   // service worker ones below.
465   ActivateTaskQueueForExtension(browser_context_, extension);
466 
467   renderer_helper_->OnExtensionLoaded(*extension);
468 
469   // Tell subsystems that use the ExtensionRegistryObserver::OnExtensionLoaded
470   // about the new extension.
471   //
472   // NOTE: It is important that this happen after notifying the renderers about
473   // the new extensions so that if we navigate to an extension URL in
474   // ExtensionRegistryObserver::OnExtensionLoaded the renderer is guaranteed to
475   // know about it.
476   registry_->TriggerOnLoaded(extension);
477 
478   delegate_->PostActivateExtension(extension);
479 
480   // When an existing extension is re-enabled, it may be necessary to spin up
481   // its lazy background page.
482   if (!is_newly_added)
483     MaybeSpinUpLazyBackgroundPage(extension);
484 }
485 
DeactivateExtension(const Extension * extension,UnloadedExtensionReason reason)486 void ExtensionRegistrar::DeactivateExtension(const Extension* extension,
487                                              UnloadedExtensionReason reason) {
488   registry_->TriggerOnUnloaded(extension, reason);
489   renderer_helper_->OnExtensionUnloaded(*extension);
490   extension_system_->UnregisterExtensionWithRequestContexts(extension->id(),
491                                                             reason);
492   DeactivateTaskQueueForExtension(browser_context_, extension);
493 
494   delegate_->PostDeactivateExtension(extension);
495 }
496 
ReplaceReloadedExtension(scoped_refptr<const Extension> extension)497 bool ExtensionRegistrar::ReplaceReloadedExtension(
498     scoped_refptr<const Extension> extension) {
499   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
500 
501   // The extension must already be disabled, and the original extension has
502   // been unloaded.
503   CHECK(registry_->disabled_extensions().Contains(extension->id()));
504   if (!delegate_->CanEnableExtension(extension.get()))
505     return false;
506 
507   // TODO(michaelpg): Other disable reasons might have been added after the
508   // reload started. We may want to keep the extension disabled and just remove
509   // the DISABLE_RELOAD reason in that case.
510   extension_prefs_->SetExtensionEnabled(extension->id());
511 
512   // Move it over to the enabled list.
513   CHECK(registry_->RemoveDisabled(extension->id()));
514   CHECK(registry_->AddEnabled(extension));
515 
516   ActivateExtension(extension.get(), false);
517 
518   return true;
519 }
520 
OnExtensionRegisteredWithRequestContexts(scoped_refptr<const Extension> extension)521 void ExtensionRegistrar::OnExtensionRegisteredWithRequestContexts(
522     scoped_refptr<const Extension> extension) {
523   DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
524 
525   registry_->AddReady(extension);
526   if (registry_->enabled_extensions().Contains(extension->id()))
527     registry_->TriggerOnReady(extension.get());
528 }
529 
MaybeSpinUpLazyBackgroundPage(const Extension * extension)530 void ExtensionRegistrar::MaybeSpinUpLazyBackgroundPage(
531     const Extension* extension) {
532   if (!BackgroundInfo::HasLazyBackgroundPage(extension))
533     return;
534 
535   // For orphaned devtools, we will reconnect devtools to it later in
536   // DidCreateRenderViewForBackgroundPage().
537   bool has_orphaned_dev_tools =
538       base::Contains(orphaned_dev_tools_, extension->id());
539 
540   // Reloading component extension does not trigger install, so RuntimeAPI won't
541   // be able to detect its loading. Therefore, we need to spin up its lazy
542   // background page.
543   bool is_component_extension =
544       Manifest::IsComponentLocation(extension->location());
545 
546   if (!has_orphaned_dev_tools && !is_component_extension)
547     return;
548 
549   // Wake up the event page by posting a dummy task.
550   const LazyContextId context_id(browser_context_, extension->id());
551   context_id.GetTaskQueue()->AddPendingTask(context_id, base::DoNothing());
552 }
553 
554 }  // namespace extensions
555