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