background_contents_service.cc revision 21d179b334e59e9a3bfcaed4c4430bef1bc5759d
1// Copyright (c) 2010 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 "chrome/browser/background_contents_service.h" 6 7#include "base/basictypes.h" 8#include "base/command_line.h" 9#include "base/string_util.h" 10#include "base/utf_string_conversions.h" 11#include "base/values.h" 12#include "chrome/browser/extensions/extension_service.h" 13#include "chrome/browser/prefs/pref_service.h" 14#include "chrome/browser/profiles/profile.h" 15#include "chrome/browser/renderer_host/render_view_host.h" 16#include "chrome/browser/renderer_host/site_instance.h" 17#include "chrome/browser/tab_contents/tab_contents.h" 18#include "chrome/browser/ui/browser.h" 19#include "chrome/browser/ui/browser_list.h" 20#include "chrome/common/chrome_switches.h" 21#include "chrome/common/extensions/extension.h" 22#include "chrome/common/notification_service.h" 23#include "chrome/common/notification_type.h" 24#include "chrome/common/pref_names.h" 25 26// Keys for the information we store about individual BackgroundContents in 27// prefs. There is one top-level DictionaryValue (stored at 28// prefs::kRegisteredBackgroundContents). Information about each 29// BackgroundContents is stored under that top-level DictionaryValue, keyed 30// by the parent application ID for easy lookup. 31// 32// kRegisteredBackgroundContents: 33// DictionaryValue { 34// <appid_1>: { "url": <url1>, "name": <frame_name> }, 35// <appid_2>: { "url": <url2>, "name": <frame_name> }, 36// ... etc ... 37// } 38const char kUrlKey[] = "url"; 39const char kFrameNameKey[] = "name"; 40 41BackgroundContentsService::BackgroundContentsService( 42 Profile* profile, const CommandLine* command_line) 43 : prefs_(NULL) { 44 // Don't load/store preferences if the proper switch is not enabled, or if 45 // the parent profile is off the record. 46 if (!profile->IsOffTheRecord() && 47 !command_line->HasSwitch(switches::kDisableRestoreBackgroundContents)) 48 prefs_ = profile->GetPrefs(); 49 50 // Listen for events to tell us when to load/unload persisted background 51 // contents. 52 StartObserving(profile); 53} 54 55BackgroundContentsService::~BackgroundContentsService() { 56 // BackgroundContents should be shutdown before we go away, as otherwise 57 // our browser process refcount will be off. 58 DCHECK(contents_map_.empty()); 59} 60 61std::vector<BackgroundContents*> 62BackgroundContentsService::GetBackgroundContents() const 63{ 64 std::vector<BackgroundContents*> contents; 65 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 66 it != contents_map_.end(); ++it) 67 contents.push_back(it->second.contents); 68 return contents; 69} 70 71void BackgroundContentsService::StartObserving(Profile* profile) { 72 // On startup, load our background pages after extension-apps have loaded. 73 registrar_.Add(this, NotificationType::EXTENSIONS_READY, 74 Source<Profile>(profile)); 75 76 // Track the lifecycle of all BackgroundContents in the system to allow us 77 // to store an up-to-date list of the urls. Start tracking contents when they 78 // have been opened via CreateBackgroundContents(), and stop tracking them 79 // when they are closed by script. 80 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED, 81 Source<Profile>(profile)); 82 83 // Stop tracking BackgroundContents when they have been deleted (happens 84 // during shutdown or if the render process dies). 85 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED, 86 Source<Profile>(profile)); 87 // Track when the BackgroundContents navigates to a new URL so we can update 88 // our persisted information as appropriate. 89 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED, 90 Source<Profile>(profile)); 91 92 // Listen for extensions to be unloaded so we can shutdown associated 93 // BackgroundContents. 94 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 95 Source<Profile>(profile)); 96} 97 98void BackgroundContentsService::Observe(NotificationType type, 99 const NotificationSource& source, 100 const NotificationDetails& details) { 101 switch (type.value) { 102 case NotificationType::EXTENSIONS_READY: 103 LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr()); 104 break; 105 case NotificationType::BACKGROUND_CONTENTS_DELETED: 106 BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr()); 107 break; 108 case NotificationType::BACKGROUND_CONTENTS_CLOSED: 109 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 110 UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 111 break; 112 case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: 113 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 114 RegisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 115 break; 116 case NotificationType::EXTENSION_UNLOADED: 117 ShutdownAssociatedBackgroundContents( 118 ASCIIToUTF16( 119 Details<UnloadedExtensionInfo>(details)->extension->id())); 120 break; 121 default: 122 NOTREACHED(); 123 break; 124 } 125} 126 127// Loads all background contents whose urls have been stored in prefs. 128void BackgroundContentsService::LoadBackgroundContentsFromPrefs( 129 Profile* profile) { 130 if (!prefs_) 131 return; 132 const DictionaryValue* contents = 133 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 134 if (!contents) 135 return; 136 ExtensionService* extensions_service = profile->GetExtensionService(); 137 DCHECK(extensions_service); 138 for (DictionaryValue::key_iterator it = contents->begin_keys(); 139 it != contents->end_keys(); ++it) { 140 DictionaryValue* dict; 141 contents->GetDictionaryWithoutPathExpansion(*it, &dict); 142 string16 frame_name; 143 std::string url; 144 dict->GetString(kUrlKey, &url); 145 dict->GetString(kFrameNameKey, &frame_name); 146 147 // Check to make sure that the parent extension is still enabled. 148 const Extension* extension = extensions_service->GetExtensionById( 149 *it, false); 150 151 if (!extension) { 152 // We should never reach here - it should not be possible for an app 153 // to become uninstalled without the associated BackgroundContents being 154 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a 155 // crash before we could save our prefs. 156 NOTREACHED() << "No extension found for BackgroundContents - id = " 157 << *it; 158 return; 159 } 160 LoadBackgroundContents(profile, 161 GURL(url), 162 frame_name, 163 UTF8ToUTF16(*it)); 164 } 165} 166 167void BackgroundContentsService::LoadBackgroundContents( 168 Profile* profile, 169 const GURL& url, 170 const string16& frame_name, 171 const string16& application_id) { 172 // We are depending on the fact that we will initialize before any user 173 // actions or session restore can take place, so no BackgroundContents should 174 // be running yet for the passed application_id. 175 DCHECK(!GetAppBackgroundContents(application_id)); 176 DCHECK(!application_id.empty()); 177 DCHECK(url.is_valid()); 178 DVLOG(1) << "Loading background content url: " << url; 179 180 BackgroundContents* contents = CreateBackgroundContents( 181 SiteInstance::CreateSiteInstanceForURL(profile, url), 182 MSG_ROUTING_NONE, 183 profile, 184 frame_name, 185 application_id); 186 187 RenderViewHost* render_view_host = contents->render_view_host(); 188 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing 189 // startup latency (http://crbug.com/47236). 190 render_view_host->CreateRenderView(frame_name); 191 render_view_host->NavigateToURL(url); 192} 193 194BackgroundContents* BackgroundContentsService::CreateBackgroundContents( 195 SiteInstance* site, 196 int routing_id, 197 Profile* profile, 198 const string16& frame_name, 199 const string16& application_id) { 200 BackgroundContents* contents = new BackgroundContents(site, routing_id, this); 201 202 // Register the BackgroundContents internally, then send out a notification 203 // to external listeners. 204 BackgroundContentsOpenedDetails details = {contents, 205 frame_name, 206 application_id}; 207 BackgroundContentsOpened(&details); 208 NotificationService::current()->Notify( 209 NotificationType::BACKGROUND_CONTENTS_OPENED, 210 Source<Profile>(profile), 211 Details<BackgroundContentsOpenedDetails>(&details)); 212 return contents; 213} 214 215void BackgroundContentsService::RegisterBackgroundContents( 216 BackgroundContents* background_contents) { 217 DCHECK(IsTracked(background_contents)); 218 if (!prefs_) 219 return; 220 221 // We store the first URL we receive for a given application. If there's 222 // already an entry for this application, no need to do anything. 223 // TODO(atwilson): Verify that this is the desired behavior based on developer 224 // feedback (http://crbug.com/47118). 225 DictionaryValue* pref = prefs_->GetMutableDictionary( 226 prefs::kRegisteredBackgroundContents); 227 const string16& appid = GetParentApplicationId(background_contents); 228 DictionaryValue* current; 229 if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), ¤t)) 230 return; 231 232 // No entry for this application yet, so add one. 233 DictionaryValue* dict = new DictionaryValue(); 234 dict->SetString(kUrlKey, background_contents->GetURL().spec()); 235 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name); 236 pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict); 237 prefs_->ScheduleSavePersistentPrefs(); 238} 239 240void BackgroundContentsService::UnregisterBackgroundContents( 241 BackgroundContents* background_contents) { 242 if (!prefs_) 243 return; 244 DCHECK(IsTracked(background_contents)); 245 const string16 appid = GetParentApplicationId(background_contents); 246 DictionaryValue* pref = prefs_->GetMutableDictionary( 247 prefs::kRegisteredBackgroundContents); 248 pref->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL); 249 prefs_->ScheduleSavePersistentPrefs(); 250} 251 252void BackgroundContentsService::ShutdownAssociatedBackgroundContents( 253 const string16& appid) { 254 BackgroundContents* contents = GetAppBackgroundContents(appid); 255 if (contents) { 256 UnregisterBackgroundContents(contents); 257 // Background contents destructor shuts down the renderer. 258 delete contents; 259 } 260} 261 262void BackgroundContentsService::BackgroundContentsOpened( 263 BackgroundContentsOpenedDetails* details) { 264 // Add the passed object to our list. Should not already be tracked. 265 DCHECK(!IsTracked(details->contents)); 266 DCHECK(!details->application_id.empty()); 267 contents_map_[details->application_id].contents = details->contents; 268 contents_map_[details->application_id].frame_name = details->frame_name; 269} 270 271// Used by test code and debug checks to verify whether a given 272// BackgroundContents is being tracked by this instance. 273bool BackgroundContentsService::IsTracked( 274 BackgroundContents* background_contents) const { 275 return !GetParentApplicationId(background_contents).empty(); 276} 277 278void BackgroundContentsService::BackgroundContentsShutdown( 279 BackgroundContents* background_contents) { 280 // Remove the passed object from our list. 281 DCHECK(IsTracked(background_contents)); 282 string16 appid = GetParentApplicationId(background_contents); 283 contents_map_.erase(appid); 284} 285 286BackgroundContents* BackgroundContentsService::GetAppBackgroundContents( 287 const string16& application_id) { 288 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id); 289 return (it != contents_map_.end()) ? it->second.contents : NULL; 290} 291 292const string16& BackgroundContentsService::GetParentApplicationId( 293 BackgroundContents* contents) const { 294 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 295 it != contents_map_.end(); ++it) { 296 if (contents == it->second.contents) 297 return it->first; 298 } 299 return EmptyString16(); 300} 301 302// static 303void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) { 304 prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents); 305} 306 307void BackgroundContentsService::AddTabContents( 308 TabContents* new_contents, 309 WindowOpenDisposition disposition, 310 const gfx::Rect& initial_pos, 311 bool user_gesture) { 312 Browser* browser = BrowserList::GetLastActiveWithProfile( 313 new_contents->profile()); 314 if (!browser) 315 return; 316 browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture); 317} 318