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), &current))
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