background_contents_service.cc revision 731df977c0511bca2206b5f333555b1205ff1f43
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/browser.h" 13#include "chrome/browser/browser_list.h" 14#include "chrome/browser/extensions/extensions_service.h" 15#include "chrome/browser/prefs/pref_service.h" 16#include "chrome/browser/profile.h" 17#include "chrome/browser/renderer_host/render_view_host.h" 18#include "chrome/browser/renderer_host/site_instance.h" 19#include "chrome/browser/tab_contents/tab_contents.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 61void BackgroundContentsService::StartObserving(Profile* profile) { 62 // On startup, load our background pages after extension-apps have loaded. 63 registrar_.Add(this, NotificationType::EXTENSIONS_READY, 64 Source<Profile>(profile)); 65 66 // Track the lifecycle of all BackgroundContents in the system to allow us 67 // to store an up-to-date list of the urls. Start tracking contents when they 68 // have been opened, and stop tracking them when they are closed by script. 69 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_OPENED, 70 Source<Profile>(profile)); 71 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED, 72 Source<Profile>(profile)); 73 74 // Stop tracking BackgroundContents when they have been deleted (happens 75 // during shutdown or if the render process dies). 76 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED, 77 Source<Profile>(profile)); 78 // Track when the BackgroundContents navigates to a new URL so we can update 79 // our persisted information as appropriate. 80 registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED, 81 Source<Profile>(profile)); 82 83 // Listen for extensions to be unloaded so we can shutdown associated 84 // BackgroundContents. 85 registrar_.Add(this, NotificationType::EXTENSION_UNLOADED, 86 Source<Profile>(profile)); 87} 88 89void BackgroundContentsService::Observe(NotificationType type, 90 const NotificationSource& source, 91 const NotificationDetails& details) { 92 switch (type.value) { 93 case NotificationType::EXTENSIONS_READY: 94 LoadBackgroundContentsFromPrefs(Source<Profile>(source).ptr()); 95 break; 96 case NotificationType::BACKGROUND_CONTENTS_DELETED: 97 BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr()); 98 break; 99 case NotificationType::BACKGROUND_CONTENTS_OPENED: 100 BackgroundContentsOpened( 101 Details<BackgroundContentsOpenedDetails>(details).ptr()); 102 break; 103 case NotificationType::BACKGROUND_CONTENTS_CLOSED: 104 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 105 UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 106 break; 107 case NotificationType::BACKGROUND_CONTENTS_NAVIGATED: 108 DCHECK(IsTracked(Details<BackgroundContents>(details).ptr())); 109 RegisterBackgroundContents(Details<BackgroundContents>(details).ptr()); 110 break; 111 case NotificationType::EXTENSION_UNLOADED: 112 ShutdownAssociatedBackgroundContents( 113 ASCIIToUTF16(Details<Extension>(details)->id())); 114 break; 115 default: 116 NOTREACHED(); 117 break; 118 } 119} 120 121// Loads all background contents whose urls have been stored in prefs. 122void BackgroundContentsService::LoadBackgroundContentsFromPrefs( 123 Profile* profile) { 124 if (!prefs_) 125 return; 126 const DictionaryValue* contents = 127 prefs_->GetDictionary(prefs::kRegisteredBackgroundContents); 128 if (!contents) 129 return; 130 for (DictionaryValue::key_iterator it = contents->begin_keys(); 131 it != contents->end_keys(); ++it) { 132 DictionaryValue* dict; 133 contents->GetDictionaryWithoutPathExpansion(*it, &dict); 134 string16 frame_name; 135 std::string url; 136 dict->GetString(kUrlKey, &url); 137 dict->GetString(kFrameNameKey, &frame_name); 138 CreateBackgroundContents(profile, 139 GURL(url), 140 frame_name, 141 UTF8ToUTF16(*it)); 142 } 143} 144 145void BackgroundContentsService::CreateBackgroundContents( 146 Profile* profile, 147 const GURL& url, 148 const string16& frame_name, 149 const string16& application_id) { 150 // We are depending on the fact that we will initialize before any user 151 // actions or session restore can take place, so no BackgroundContents should 152 // be running yet for the passed application_id. 153 DCHECK(!GetAppBackgroundContents(application_id)); 154 DCHECK(!application_id.empty()); 155 DCHECK(url.is_valid()); 156 DVLOG(1) << "Loading background content url: " << url; 157 158 // Check to make sure that the parent extension is still enabled. 159 ExtensionsService* extensions_service = profile->GetExtensionsService(); 160 Extension* extension = extensions_service->GetExtensionById( 161 UTF16ToASCII(application_id), false); 162 163 if (!extension) { 164 // We should never reach here - it should not be possible for an application 165 // to become uninstalled without the associated BackgroundContents being 166 // unregistered via the EXTENSIONS_UNLOADED notification, unless there's a 167 // crash before we could save our prefs. 168 NOTREACHED() << "No extension found for BackgroundContents - id = " 169 << application_id; 170 return; 171 } 172 173 BackgroundContents* contents = new BackgroundContents( 174 SiteInstance::CreateSiteInstanceForURL(profile, url), 175 MSG_ROUTING_NONE, 176 this); 177 178 // TODO(atwilson): Change this to send a BACKGROUND_CONTENTS_CREATED 179 // notification when we have a listener outside of BackgroundContentsService. 180 BackgroundContentsOpenedDetails details = {contents, 181 frame_name, 182 application_id}; 183 BackgroundContentsOpened(&details); 184 185 RenderViewHost* render_view_host = contents->render_view_host(); 186 // TODO(atwilson): Create RenderViews asynchronously to avoid increasing 187 // startup latency (http://crbug.com/47236). 188 render_view_host->CreateRenderView(frame_name); 189 render_view_host->NavigateToURL(url); 190} 191 192void BackgroundContentsService::RegisterBackgroundContents( 193 BackgroundContents* background_contents) { 194 DCHECK(IsTracked(background_contents)); 195 if (!prefs_) 196 return; 197 198 // We store the first URL we receive for a given application. If there's 199 // already an entry for this application, no need to do anything. 200 // TODO(atwilson): Verify that this is the desired behavior based on developer 201 // feedback (http://crbug.com/47118). 202 DictionaryValue* pref = prefs_->GetMutableDictionary( 203 prefs::kRegisteredBackgroundContents); 204 const string16& appid = GetParentApplicationId(background_contents); 205 DictionaryValue* current; 206 if (pref->GetDictionaryWithoutPathExpansion(UTF16ToUTF8(appid), ¤t)) 207 return; 208 209 // No entry for this application yet, so add one. 210 DictionaryValue* dict = new DictionaryValue(); 211 dict->SetString(kUrlKey, background_contents->GetURL().spec()); 212 dict->SetString(kFrameNameKey, contents_map_[appid].frame_name); 213 pref->SetWithoutPathExpansion(UTF16ToUTF8(appid), dict); 214 prefs_->ScheduleSavePersistentPrefs(); 215} 216 217void BackgroundContentsService::UnregisterBackgroundContents( 218 BackgroundContents* background_contents) { 219 if (!prefs_) 220 return; 221 DCHECK(IsTracked(background_contents)); 222 const string16 appid = GetParentApplicationId(background_contents); 223 DictionaryValue* pref = prefs_->GetMutableDictionary( 224 prefs::kRegisteredBackgroundContents); 225 pref->RemoveWithoutPathExpansion(UTF16ToUTF8(appid), NULL); 226 prefs_->ScheduleSavePersistentPrefs(); 227} 228 229void BackgroundContentsService::ShutdownAssociatedBackgroundContents( 230 const string16& appid) { 231 BackgroundContents* contents = GetAppBackgroundContents(appid); 232 if (contents) { 233 UnregisterBackgroundContents(contents); 234 // Background contents destructor shuts down the renderer. 235 delete contents; 236 } 237} 238 239void BackgroundContentsService::BackgroundContentsOpened( 240 BackgroundContentsOpenedDetails* details) { 241 // Add the passed object to our list. Should not already be tracked. 242 DCHECK(!IsTracked(details->contents)); 243 DCHECK(!details->application_id.empty()); 244 contents_map_[details->application_id].contents = details->contents; 245 contents_map_[details->application_id].frame_name = details->frame_name; 246} 247 248// Used by test code and debug checks to verify whether a given 249// BackgroundContents is being tracked by this instance. 250bool BackgroundContentsService::IsTracked( 251 BackgroundContents* background_contents) const { 252 return !GetParentApplicationId(background_contents).empty(); 253} 254 255void BackgroundContentsService::BackgroundContentsShutdown( 256 BackgroundContents* background_contents) { 257 // Remove the passed object from our list. 258 DCHECK(IsTracked(background_contents)); 259 string16 appid = GetParentApplicationId(background_contents); 260 contents_map_.erase(appid); 261} 262 263BackgroundContents* BackgroundContentsService::GetAppBackgroundContents( 264 const string16& application_id) { 265 BackgroundContentsMap::const_iterator it = contents_map_.find(application_id); 266 return (it != contents_map_.end()) ? it->second.contents : NULL; 267} 268 269const string16& BackgroundContentsService::GetParentApplicationId( 270 BackgroundContents* contents) const { 271 for (BackgroundContentsMap::const_iterator it = contents_map_.begin(); 272 it != contents_map_.end(); ++it) { 273 if (contents == it->second.contents) 274 return it->first; 275 } 276 return EmptyString16(); 277} 278 279// static 280void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) { 281 prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents); 282} 283 284void BackgroundContentsService::AddTabContents( 285 TabContents* new_contents, 286 WindowOpenDisposition disposition, 287 const gfx::Rect& initial_pos, 288 bool user_gesture) { 289 Browser* browser = BrowserList::GetLastActiveWithProfile( 290 new_contents->profile()); 291 if (!browser) 292 return; 293 browser->AddTabContents(new_contents, disposition, initial_pos, user_gesture); 294} 295