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.
5 // MediaFileSystemRegistry implementation.
7 #include "chrome/browser/media_galleries/media_file_system_registry.h"
12 #include "base/bind.h"
13 #include "base/callback.h"
14 #include "base/files/file_path.h"
15 #include "base/prefs/pref_service.h"
16 #include "base/stl_util.h"
17 #include "chrome/browser/chrome_notification_types.h"
18 #include "chrome/browser/extensions/extension_service.h"
19 #include "chrome/browser/extensions/extension_system.h"
20 #include "chrome/browser/media_galleries/fileapi/mtp_device_map_service.h"
21 #include "chrome/browser/media_galleries/imported_media_gallery_registry.h"
22 #include "chrome/browser/media_galleries/media_file_system_context.h"
23 #include "chrome/browser/media_galleries/media_galleries_dialog_controller.h"
24 #include "chrome/browser/media_galleries/media_galleries_histograms.h"
25 #include "chrome/browser/media_galleries/media_galleries_preferences_factory.h"
26 #include "chrome/browser/profiles/profile.h"
27 #include "chrome/browser/storage_monitor/media_storage_util.h"
28 #include "chrome/browser/storage_monitor/storage_monitor.h"
29 #include "chrome/common/chrome_paths.h"
30 #include "chrome/common/extensions/extension.h"
31 #include "chrome/common/extensions/extension_constants.h"
32 #include "chrome/common/extensions/extension_set.h"
33 #include "chrome/common/pref_names.h"
34 #include "content/public/browser/browser_thread.h"
35 #include "content/public/browser/navigation_details.h"
36 #include "content/public/browser/notification_details.h"
37 #include "content/public/browser/notification_observer.h"
38 #include "content/public/browser/notification_registrar.h"
39 #include "content/public/browser/notification_source.h"
40 #include "content/public/browser/notification_types.h"
41 #include "content/public/browser/render_process_host.h"
42 #include "content/public/browser/render_view_host.h"
43 #include "content/public/browser/web_contents.h"
44 #include "webkit/browser/fileapi/isolated_context.h"
45 #include "webkit/common/fileapi/file_system_types.h"
47 using content::BrowserThread;
48 using content::NavigationController;
49 using content::RenderProcessHost;
50 using content::WebContents;
51 using fileapi::IsolatedContext;
55 struct InvalidatedGalleriesInfo {
56 std::set<ExtensionGalleriesHost*> extension_hosts;
57 std::set<MediaGalleryPrefId> pref_ids;
60 // Tracks the liveness of multiple RenderProcessHosts that the caller is
61 // interested in. Once all of the RPHs have closed or been terminated a call
62 // back informs the caller.
63 class RPHReferenceManager : public content::NotificationObserver {
65 // |no_references_callback| is called when the last RenderViewHost reference
66 // goes away. RenderViewHost references are added through ReferenceFromRVH().
67 explicit RPHReferenceManager(const base::Closure& no_references_callback)
68 : no_references_callback_(no_references_callback) {
71 virtual ~RPHReferenceManager() {
75 // Remove all references, but don't call |no_references_callback|.
77 STLDeleteValues(&refs_);
80 // Returns true if there are no references;
85 // Adds a reference to the passed |rvh|. Calling this multiple times with
86 // the same |rvh| is a no-op.
87 void ReferenceFromRVH(const content::RenderViewHost* rvh) {
88 WebContents* contents = WebContents::FromRenderViewHost(rvh);
89 RenderProcessHost* rph = contents->GetRenderProcessHost();
90 RPHReferenceState* state = NULL;
91 if (!ContainsKey(refs_, rph)) {
92 state = new RPHReferenceState;
95 this, content::NOTIFICATION_RENDERER_PROCESS_TERMINATED,
96 content::Source<RenderProcessHost>(rph));
101 if (state->web_contents_set.insert(contents).second) {
102 state->registrar.Add(this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
103 content::Source<WebContents>(contents));
104 state->registrar.Add(this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
105 content::Source<NavigationController>(&contents->GetController()));
110 struct RPHReferenceState {
111 content::NotificationRegistrar registrar;
112 std::set<const WebContents*> web_contents_set;
114 typedef std::map<const RenderProcessHost*, RPHReferenceState*> RPHRefCount;
116 // NotificationObserver implementation.
117 virtual void Observe(int type,
118 const content::NotificationSource& source,
119 const content::NotificationDetails& details) OVERRIDE {
121 case content::NOTIFICATION_RENDERER_PROCESS_TERMINATED: {
122 OnRendererProcessTerminated(
123 content::Source<RenderProcessHost>(source).ptr());
126 case content::NOTIFICATION_WEB_CONTENTS_DESTROYED: {
127 OnWebContentsDestroyedOrNavigated(
128 content::Source<WebContents>(source).ptr());
131 case content::NOTIFICATION_NAV_ENTRY_COMMITTED: {
132 content::LoadCommittedDetails* load_details =
133 content::Details<content::LoadCommittedDetails>(details).ptr();
134 if (load_details->is_in_page)
136 NavigationController* controller =
137 content::Source<NavigationController>(source).ptr();
138 WebContents* contents = controller->GetWebContents();
139 OnWebContentsDestroyedOrNavigated(contents);
149 void OnRendererProcessTerminated(const RenderProcessHost* rph) {
150 RPHRefCount::iterator rph_info = refs_.find(rph);
151 DCHECK(rph_info != refs_.end());
152 delete rph_info->second;
153 refs_.erase(rph_info);
155 no_references_callback_.Run();
158 void OnWebContentsDestroyedOrNavigated(const WebContents* contents) {
159 RenderProcessHost* rph = contents->GetRenderProcessHost();
160 RPHRefCount::iterator rph_info = refs_.find(rph);
161 DCHECK(rph_info != refs_.end());
163 rph_info->second->registrar.Remove(
164 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED,
165 content::Source<WebContents>(contents));
166 rph_info->second->registrar.Remove(
167 this, content::NOTIFICATION_NAV_ENTRY_COMMITTED,
168 content::Source<NavigationController>(&contents->GetController()));
170 rph_info->second->web_contents_set.erase(contents);
171 if (rph_info->second->web_contents_set.empty())
172 OnRendererProcessTerminated(rph);
175 // A callback to call when the last RVH reference goes away.
176 base::Closure no_references_callback_;
178 // The set of render processes and web contents that may have references to
179 // the file system ids this instance manages.
185 MediaFileSystemInfo::MediaFileSystemInfo(const string16& fs_name,
186 const base::FilePath& fs_path,
187 const std::string& filesystem_id,
188 MediaGalleryPrefId pref_id,
189 const std::string& transient_device_id,
196 transient_device_id(transient_device_id),
197 removable(removable),
198 media_device(media_device) {
201 MediaFileSystemInfo::MediaFileSystemInfo() {}
202 MediaFileSystemInfo::~MediaFileSystemInfo() {}
204 // The main owner of this class is
205 // |MediaFileSystemRegistry::extension_hosts_map_|, but a callback may
206 // temporarily hold a reference.
207 class ExtensionGalleriesHost
208 : public base::RefCountedThreadSafe<ExtensionGalleriesHost> {
210 // |no_references_callback| is called when the last RenderViewHost reference
211 // goes away. RenderViewHost references are added through ReferenceFromRVH().
212 ExtensionGalleriesHost(MediaFileSystemContext* file_system_context,
213 const base::Closure& no_references_callback)
214 : file_system_context_(file_system_context),
215 no_references_callback_(no_references_callback),
216 rph_refs_(base::Bind(&ExtensionGalleriesHost::CleanUp,
217 base::Unretained(this))) {
220 // For each gallery in the list of permitted |galleries|, checks if the
221 // device is attached and if so looks up or creates a file system id and
222 // passes the information needed for the renderer to create those file
223 // system objects to the |callback|.
224 void GetMediaFileSystems(const MediaGalleryPrefIdSet& galleries,
225 const MediaGalleriesPrefInfoMap& galleries_info,
226 const MediaFileSystemsCallback& callback) {
227 // Extract all the device ids so we can make sure they are attached.
228 MediaStorageUtil::DeviceIdSet* device_ids =
229 new MediaStorageUtil::DeviceIdSet;
230 for (std::set<MediaGalleryPrefId>::const_iterator id = galleries.begin();
231 id != galleries.end();
233 device_ids->insert(galleries_info.find(*id)->second.device_id);
235 MediaStorageUtil::FilterAttachedDevices(device_ids, base::Bind(
236 &ExtensionGalleriesHost::GetMediaFileSystemsForAttachedDevices, this,
237 base::Owned(device_ids), galleries, galleries_info, callback));
240 void RevokeOldGalleries(const MediaGalleryPrefIdSet& new_galleries) {
241 if (new_galleries.size() == pref_id_map_.size())
244 MediaGalleryPrefIdSet old_galleries;
245 for (PrefIdFsInfoMap::const_iterator it = pref_id_map_.begin();
246 it != pref_id_map_.end();
248 old_galleries.insert(it->first);
250 MediaGalleryPrefIdSet invalid_galleries =
251 base::STLSetDifference<MediaGalleryPrefIdSet>(old_galleries,
253 for (MediaGalleryPrefIdSet::const_iterator it = invalid_galleries.begin();
254 it != invalid_galleries.end();
256 RevokeGalleryByPrefId(*it);
260 // Revoke the file system for |id| if this extension has created one for |id|.
261 void RevokeGalleryByPrefId(MediaGalleryPrefId id) {
262 PrefIdFsInfoMap::iterator gallery = pref_id_map_.find(id);
263 if (gallery == pref_id_map_.end())
266 file_system_context_->RevokeFileSystem(gallery->second.fsid);
267 pref_id_map_.erase(gallery);
269 if (pref_id_map_.empty()) {
275 // Indicate that the passed |rvh| will reference the file system ids created
277 void ReferenceFromRVH(const content::RenderViewHost* rvh) {
278 rph_refs_.ReferenceFromRVH(rvh);
282 typedef std::map<MediaGalleryPrefId, MediaFileSystemInfo> PrefIdFsInfoMap;
284 // Private destructor and friend declaration for ref counted implementation.
285 friend class base::RefCountedThreadSafe<ExtensionGalleriesHost>;
287 virtual ~ExtensionGalleriesHost() {
288 DCHECK(rph_refs_.empty());
289 DCHECK(pref_id_map_.empty());
293 void GetMediaFileSystemsForAttachedDevices(
294 const MediaStorageUtil::DeviceIdSet* attached_devices,
295 const MediaGalleryPrefIdSet& galleries,
296 const MediaGalleriesPrefInfoMap& galleries_info,
297 const MediaFileSystemsCallback& callback) {
298 std::vector<MediaFileSystemInfo> result;
299 MediaGalleryPrefIdSet new_galleries;
300 for (std::set<MediaGalleryPrefId>::const_iterator pref_id_it =
302 pref_id_it != galleries.end();
304 const MediaGalleryPrefId& pref_id = *pref_id_it;
305 const MediaGalleryPrefInfo& gallery_info =
306 galleries_info.find(pref_id)->second;
307 const std::string& device_id = gallery_info.device_id;
308 if (!ContainsKey(*attached_devices, device_id))
311 PrefIdFsInfoMap::const_iterator existing_info =
312 pref_id_map_.find(pref_id);
313 if (existing_info != pref_id_map_.end()) {
314 result.push_back(existing_info->second);
315 new_galleries.insert(pref_id);
319 base::FilePath path = gallery_info.AbsolutePath();
320 if (!MediaStorageUtil::CanCreateFileSystem(device_id, path))
324 file_system_context_->RegisterFileSystem(device_id, path);
328 MediaFileSystemInfo new_entry(
329 gallery_info.GetGalleryDisplayName(),
333 GetTransientIdForRemovableDeviceId(device_id),
334 StorageInfo::IsRemovableDevice(device_id),
335 StorageInfo::IsMediaDevice(device_id));
336 result.push_back(new_entry);
337 new_galleries.insert(pref_id);
338 pref_id_map_[pref_id] = new_entry;
341 if (result.size() == 0) {
345 RevokeOldGalleries(new_galleries);
348 callback.Run(result);
351 std::string GetTransientIdForRemovableDeviceId(const std::string& device_id) {
352 if (!StorageInfo::IsRemovableDevice(device_id))
353 return std::string();
355 return StorageMonitor::GetInstance()->GetTransientIdForDeviceId(device_id);
359 DCHECK(rph_refs_.empty());
360 for (PrefIdFsInfoMap::const_iterator it = pref_id_map_.begin();
361 it != pref_id_map_.end();
363 file_system_context_->RevokeFileSystem(it->second.fsid);
365 pref_id_map_.clear();
367 no_references_callback_.Run();
370 // MediaFileSystemRegistry owns |this| and |file_system_context_|, so it's
371 // safe to store a raw pointer.
372 MediaFileSystemContext* file_system_context_;
374 // A callback to call when the last RVH reference goes away.
375 base::Closure no_references_callback_;
377 // A map from the gallery preferences id to the file system information.
378 PrefIdFsInfoMap pref_id_map_;
380 // The set of render processes and web contents that may have references to
381 // the file system ids this instance manages.
382 RPHReferenceManager rph_refs_;
384 DISALLOW_COPY_AND_ASSIGN(ExtensionGalleriesHost);
391 void MediaFileSystemRegistry::GetMediaFileSystemsForExtension(
392 const content::RenderViewHost* rvh,
393 const extensions::Extension* extension,
394 const MediaFileSystemsCallback& callback) {
395 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
398 Profile::FromBrowserContext(rvh->GetProcess()->GetBrowserContext());
399 MediaGalleriesPreferences* preferences = GetPreferences(profile);
400 MediaGalleryPrefIdSet galleries =
401 preferences->GalleriesForExtension(*extension);
403 if (galleries.empty()) {
404 callback.Run(std::vector<MediaFileSystemInfo>());
408 ExtensionGalleriesHostMap::iterator extension_hosts =
409 extension_hosts_map_.find(profile);
410 if (extension_hosts->second.empty())
411 preferences->AddGalleryChangeObserver(this);
413 ExtensionGalleriesHost* extension_host =
414 extension_hosts->second[extension->id()].get();
415 if (!extension_host) {
416 extension_host = new ExtensionGalleriesHost(
417 file_system_context_.get(),
418 base::Bind(&MediaFileSystemRegistry::OnExtensionGalleriesHostEmpty,
419 base::Unretained(this),
422 extension_hosts_map_[profile][extension->id()] = extension_host;
424 extension_host->ReferenceFromRVH(rvh);
426 extension_host->GetMediaFileSystems(galleries, preferences->known_galleries(),
430 MediaGalleriesPreferences* MediaFileSystemRegistry::GetPreferences(
432 // Create an empty ExtensionHostMap for this profile on first initialization.
433 if (!ContainsKey(extension_hosts_map_, profile))
434 extension_hosts_map_[profile] = ExtensionHostMap();
435 media_galleries::UsageCount(media_galleries::PROFILES_WITH_USAGE);
437 return MediaGalleriesPreferencesFactory::GetForProfile(profile);
440 void MediaFileSystemRegistry::OnRemovableStorageDetached(
441 const StorageInfo& info) {
442 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
444 // Since revoking a gallery in the ExtensionGalleriesHost may cause it
445 // to be removed from the map and therefore invalidate any iterator pointing
446 // to it, this code first copies all the invalid gallery ids and the
447 // extension hosts in which they may appear (per profile) and revoked it in
449 std::vector<InvalidatedGalleriesInfo> invalid_galleries_info;
451 for (ExtensionGalleriesHostMap::iterator profile_it =
452 extension_hosts_map_.begin();
453 profile_it != extension_hosts_map_.end();
455 MediaGalleriesPreferences* preferences = GetPreferences(profile_it->first);
456 // If |preferences| is not yet initialized, it won't contain any galleries.
457 if (!preferences->IsInitialized())
460 InvalidatedGalleriesInfo invalid_galleries_in_profile;
461 invalid_galleries_in_profile.pref_ids =
462 preferences->LookUpGalleriesByDeviceId(info.device_id());
464 for (ExtensionHostMap::const_iterator extension_host_it =
465 profile_it->second.begin();
466 extension_host_it != profile_it->second.end();
467 ++extension_host_it) {
468 invalid_galleries_in_profile.extension_hosts.insert(
469 extension_host_it->second.get());
472 invalid_galleries_info.push_back(invalid_galleries_in_profile);
475 for (size_t i = 0; i < invalid_galleries_info.size(); i++) {
476 for (std::set<ExtensionGalleriesHost*>::const_iterator extension_host_it =
477 invalid_galleries_info[i].extension_hosts.begin();
478 extension_host_it != invalid_galleries_info[i].extension_hosts.end();
479 ++extension_host_it) {
480 for (std::set<MediaGalleryPrefId>::const_iterator pref_id_it =
481 invalid_galleries_info[i].pref_ids.begin();
482 pref_id_it != invalid_galleries_info[i].pref_ids.end();
484 (*extension_host_it)->RevokeGalleryByPrefId(*pref_id_it);
494 class MediaFileSystemRegistry::MediaFileSystemContextImpl
495 : public MediaFileSystemContext {
497 explicit MediaFileSystemContextImpl(MediaFileSystemRegistry* registry)
498 : registry_(registry) {
499 DCHECK(registry_); // Suppresses unused warning on Android.
501 virtual ~MediaFileSystemContextImpl() {}
503 virtual std::string RegisterFileSystem(
504 const std::string& device_id, const base::FilePath& path) OVERRIDE {
505 if (StorageInfo::IsMassStorageDevice(device_id)) {
506 return RegisterFileSystemForMassStorage(device_id, path);
508 return RegisterFileSystemForMTPDevice(device_id, path);
512 virtual void RevokeFileSystem(const std::string& fsid) OVERRIDE {
513 ImportedMediaGalleryRegistry* imported_registry =
514 ImportedMediaGalleryRegistry::GetInstance();
515 if (imported_registry->RevokeImportedFilesystemOnUIThread(fsid))
518 IsolatedContext::GetInstance()->RevokeFileSystem(fsid);
520 content::BrowserThread::PostTask(
521 content::BrowserThread::IO, FROM_HERE, base::Bind(
522 &MTPDeviceMapService::RevokeMTPFileSystem,
523 base::Unretained(MTPDeviceMapService::GetInstance()),
528 // Registers and returns the file system id for the mass storage device
529 // specified by |device_id| and |path|.
530 std::string RegisterFileSystemForMassStorage(
531 const std::string& device_id, const base::FilePath& path) {
532 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
533 DCHECK(StorageInfo::IsMassStorageDevice(device_id));
535 // Sanity checks for |path|.
536 CHECK(path.IsAbsolute());
537 CHECK(!path.ReferencesParent());
539 // TODO(gbillock): refactor ImportedMediaGalleryRegistry to delegate this
540 // call tree, probably by having it figure out by device id what
541 // registration is needed, or having per-device-type handlers at the
542 // next higher level.
544 if (StorageInfo::IsITunesDevice(device_id)) {
545 ImportedMediaGalleryRegistry* imported_registry =
546 ImportedMediaGalleryRegistry::GetInstance();
547 fsid = imported_registry->RegisterITunesFilesystemOnUIThread(path);
548 } else if (StorageInfo::IsPicasaDevice(device_id)) {
549 ImportedMediaGalleryRegistry* imported_registry =
550 ImportedMediaGalleryRegistry::GetInstance();
551 fsid = imported_registry->RegisterPicasaFilesystemOnUIThread(
553 } else if (StorageInfo::IsIPhotoDevice(device_id)) {
554 ImportedMediaGalleryRegistry* imported_registry =
555 ImportedMediaGalleryRegistry::GetInstance();
556 fsid = imported_registry->RegisterIPhotoFilesystemOnUIThread(
559 std::string fs_name(extension_misc::kMediaFileSystemPathPart);
560 fsid = IsolatedContext::GetInstance()->RegisterFileSystemForPath(
561 fileapi::kFileSystemTypeNativeMedia, path, &fs_name);
566 std::string RegisterFileSystemForMTPDevice(
567 const std::string& device_id, const base::FilePath& path) {
568 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
569 DCHECK(!StorageInfo::IsMassStorageDevice(device_id));
571 // Sanity checks for |path|.
572 CHECK(MediaStorageUtil::CanCreateFileSystem(device_id, path));
573 std::string fs_name(extension_misc::kMediaFileSystemPathPart);
574 const std::string fsid =
575 IsolatedContext::GetInstance()->RegisterFileSystemForVirtualPath(
576 fileapi::kFileSystemTypeDeviceMedia, fs_name, path);
577 CHECK(!fsid.empty());
578 content::BrowserThread::PostTask(
579 content::BrowserThread::IO, FROM_HERE, base::Bind(
580 &MTPDeviceMapService::RegisterMTPFileSystem,
581 base::Unretained(MTPDeviceMapService::GetInstance()),
582 path.value(), fsid));
586 MediaFileSystemRegistry* registry_;
588 DISALLOW_COPY_AND_ASSIGN(MediaFileSystemContextImpl);
591 // Constructor in 'private' section because depends on private class definition.
592 MediaFileSystemRegistry::MediaFileSystemRegistry()
593 : file_system_context_(new MediaFileSystemContextImpl(this)) {
594 StorageMonitor::GetInstance()->AddObserver(this);
597 MediaFileSystemRegistry::~MediaFileSystemRegistry() {
598 // TODO(gbillock): This is needed because the unit test uses the
599 // g_browser_process registry. We should create one in the unit test,
600 // and then can remove this.
601 if (StorageMonitor::GetInstance())
602 StorageMonitor::GetInstance()->RemoveObserver(this);
605 void MediaFileSystemRegistry::OnPermissionRemoved(
606 MediaGalleriesPreferences* prefs,
607 const std::string& extension_id,
608 MediaGalleryPrefId pref_id) {
609 Profile* profile = prefs->profile();
610 ExtensionGalleriesHostMap::const_iterator host_map_it =
611 extension_hosts_map_.find(profile);
612 DCHECK(host_map_it != extension_hosts_map_.end());
613 const ExtensionHostMap& extension_host_map = host_map_it->second;
614 ExtensionHostMap::const_iterator gallery_host_it =
615 extension_host_map.find(extension_id);
616 if (gallery_host_it == extension_host_map.end())
618 gallery_host_it->second->RevokeGalleryByPrefId(pref_id);
621 void MediaFileSystemRegistry::OnGalleryRemoved(
622 MediaGalleriesPreferences* prefs,
623 MediaGalleryPrefId pref_id) {
624 Profile* profile = prefs->profile();
625 // Get the Extensions, MediaGalleriesPreferences and ExtensionHostMap for
627 const ExtensionService* extension_service =
628 extensions::ExtensionSystem::Get(profile)->extension_service();
629 const ExtensionSet* extensions_set = extension_service->extensions();
630 ExtensionGalleriesHostMap::const_iterator host_map_it =
631 extension_hosts_map_.find(profile);
632 DCHECK(host_map_it != extension_hosts_map_.end());
633 const ExtensionHostMap& extension_host_map = host_map_it->second;
635 // Go through ExtensionHosts, and remove indicated gallery, if any.
636 // RevokeGalleryByPrefId() may end up deleting from |extension_host_map| and
637 // even delete |extension_host_map| altogether. So do this in two loops to
638 // avoid using an invalidated iterator or deleted map.
639 std::vector<const extensions::Extension*> extensions;
640 for (ExtensionHostMap::const_iterator it = extension_host_map.begin();
641 it != extension_host_map.end();
643 extensions.push_back(extensions_set->GetByID(it->first));
645 for (size_t i = 0; i < extensions.size(); ++i) {
646 if (!ContainsKey(extension_hosts_map_, profile))
648 ExtensionHostMap::const_iterator gallery_host_it =
649 extension_host_map.find(extensions[i]->id());
650 if (gallery_host_it == extension_host_map.end())
652 gallery_host_it->second->RevokeGalleryByPrefId(pref_id);
656 void MediaFileSystemRegistry::OnExtensionGalleriesHostEmpty(
657 Profile* profile, const std::string& extension_id) {
658 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
660 ExtensionGalleriesHostMap::iterator extension_hosts =
661 extension_hosts_map_.find(profile);
662 DCHECK(extension_hosts != extension_hosts_map_.end());
663 ExtensionHostMap::size_type erase_count =
664 extension_hosts->second.erase(extension_id);
665 DCHECK_EQ(1U, erase_count);
666 if (extension_hosts->second.empty()) {
667 // When a profile has no ExtensionGalleriesHosts left, remove the
668 // matching gallery-change-watcher since it is no longer needed. Leave the
669 // |extension_hosts| entry alone, since it indicates the profile has been
671 MediaGalleriesPreferences* preferences = GetPreferences(profile);
672 preferences->RemoveGalleryChangeObserver(this);