1// Copyright (c) 2011 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/extensions/extension_process_manager.h"
6
7#include "chrome/browser/ui/browser_window.h"
8#include "content/browser/browsing_instance.h"
9#if defined(OS_MACOSX)
10#include "chrome/browser/extensions/extension_host_mac.h"
11#endif
12#include "chrome/browser/extensions/extension_host.h"
13#include "chrome/browser/extensions/extension_service.h"
14#include "chrome/browser/profiles/profile.h"
15#include "chrome/browser/ui/browser.h"
16#include "chrome/common/extensions/extension.h"
17#include "chrome/common/extensions/extension_action.h"
18#include "chrome/common/extensions/extension_messages.h"
19#include "chrome/common/url_constants.h"
20#include "content/browser/renderer_host/render_view_host.h"
21#include "content/browser/site_instance.h"
22#include "content/browser/tab_contents/tab_contents.h"
23#include "content/common/notification_service.h"
24#include "content/common/notification_type.h"
25
26namespace {
27
28// Incognito profiles use this process manager. It is mostly a shim that decides
29// whether to fall back on the original profile's ExtensionProcessManager based
30// on whether a given extension uses "split" or "spanning" incognito behavior.
31class IncognitoExtensionProcessManager : public ExtensionProcessManager {
32 public:
33  explicit IncognitoExtensionProcessManager(Profile* profile);
34  virtual ~IncognitoExtensionProcessManager() {}
35  virtual ExtensionHost* CreateView(const Extension* extension,
36                                    const GURL& url,
37                                    Browser* browser,
38                                    ViewType::Type view_type);
39  virtual void CreateBackgroundHost(const Extension* extension,
40                                    const GURL& url);
41  virtual SiteInstance* GetSiteInstanceForURL(const GURL& url);
42  virtual RenderProcessHost* GetExtensionProcess(const GURL& url);
43
44 private:
45  // NotificationObserver:
46  virtual void Observe(NotificationType type,
47                       const NotificationSource& source,
48                       const NotificationDetails& details);
49
50  // Returns the extension for an URL, which can either be a chrome-extension
51  // URL or a web app URL.
52  const Extension* GetExtensionOrAppByURL(const GURL& url);
53
54  // Returns true if the extension is allowed to run in incognito mode.
55  bool IsIncognitoEnabled(const Extension* extension);
56
57  ExtensionProcessManager* original_manager_;
58};
59
60static void CreateBackgroundHost(
61    ExtensionProcessManager* manager, const Extension* extension) {
62  // Start the process for the master page, if it exists.
63  if (extension->background_url().is_valid())
64    manager->CreateBackgroundHost(extension, extension->background_url());
65}
66
67static void CreateBackgroundHosts(
68    ExtensionProcessManager* manager, const ExtensionList* extensions) {
69  for (ExtensionList::const_iterator extension = extensions->begin();
70       extension != extensions->end(); ++extension) {
71    CreateBackgroundHost(manager, *extension);
72  }
73}
74
75}  // namespace
76
77extern bool g_log_bug53991;
78
79//
80// ExtensionProcessManager
81//
82
83// static
84ExtensionProcessManager* ExtensionProcessManager::Create(Profile* profile) {
85  return (profile->IsOffTheRecord()) ?
86      new IncognitoExtensionProcessManager(profile) :
87      new ExtensionProcessManager(profile);
88}
89
90ExtensionProcessManager::ExtensionProcessManager(Profile* profile)
91    : browsing_instance_(new BrowsingInstance(profile)) {
92  Profile* original_profile = profile->GetOriginalProfile();
93  registrar_.Add(this, NotificationType::EXTENSIONS_READY,
94                 Source<Profile>(original_profile));
95  registrar_.Add(this, NotificationType::EXTENSION_LOADED,
96                 Source<Profile>(original_profile));
97  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
98                 Source<Profile>(original_profile));
99  registrar_.Add(this, NotificationType::EXTENSION_HOST_DESTROYED,
100                 Source<Profile>(profile));
101  registrar_.Add(this, NotificationType::RENDERER_PROCESS_TERMINATED,
102                 NotificationService::AllSources());
103  registrar_.Add(this, NotificationType::RENDERER_PROCESS_CLOSED,
104                 NotificationService::AllSources());
105  registrar_.Add(this, NotificationType::APP_TERMINATING,
106                 NotificationService::AllSources());
107}
108
109ExtensionProcessManager::~ExtensionProcessManager() {
110  VLOG_IF(1, g_log_bug53991) << "~ExtensionProcessManager: " << this;
111  CloseBackgroundHosts();
112  DCHECK(background_hosts_.empty());
113}
114
115ExtensionHost* ExtensionProcessManager::CreateView(const Extension* extension,
116                                                   const GURL& url,
117                                                   Browser* browser,
118                                                   ViewType::Type view_type) {
119  DCHECK(extension);
120  // A NULL browser may only be given for pop-up views.
121  DCHECK(browser || (!browser && view_type == ViewType::EXTENSION_POPUP));
122  ExtensionHost* host =
123#if defined(OS_MACOSX)
124      new ExtensionHostMac(extension, GetSiteInstanceForURL(url), url,
125                           view_type);
126#else
127      new ExtensionHost(extension, GetSiteInstanceForURL(url), url, view_type);
128#endif
129  host->CreateView(browser);
130  OnExtensionHostCreated(host, false);
131  return host;
132}
133
134ExtensionHost* ExtensionProcessManager::CreateView(const GURL& url,
135                                                   Browser* browser,
136                                                   ViewType::Type view_type) {
137  // A NULL browser may only be given for pop-up views.
138  DCHECK(browser || (!browser && view_type == ViewType::EXTENSION_POPUP));
139  ExtensionService* service =
140      browsing_instance_->profile()->GetExtensionService();
141  if (service) {
142    const Extension* extension = service->GetExtensionByURL(url);
143    if (extension)
144      return CreateView(extension, url, browser, view_type);
145  }
146  return NULL;
147}
148
149ExtensionHost* ExtensionProcessManager::CreatePopup(const Extension* extension,
150                                                    const GURL& url,
151                                                    Browser* browser) {
152  return CreateView(extension, url, browser, ViewType::EXTENSION_POPUP);
153}
154
155ExtensionHost* ExtensionProcessManager::CreatePopup(const GURL& url,
156                                                    Browser* browser) {
157  return CreateView(url, browser, ViewType::EXTENSION_POPUP);
158}
159
160ExtensionHost* ExtensionProcessManager::CreateInfobar(
161    const Extension* extension, const GURL& url, Browser* browser) {
162  return CreateView(extension, url, browser, ViewType::EXTENSION_INFOBAR);
163}
164
165ExtensionHost* ExtensionProcessManager::CreateInfobar(const GURL& url,
166                                                      Browser* browser) {
167  return CreateView(url, browser, ViewType::EXTENSION_INFOBAR);
168}
169
170void ExtensionProcessManager::CreateBackgroundHost(
171    const Extension* extension, const GURL& url) {
172  // Hosted apps are taken care of from BackgroundContentsService. Ignore them
173  // here.
174  if (extension->is_hosted_app())
175    return;
176
177  // Don't create multiple background hosts for an extension.
178  if (GetBackgroundHostForExtension(extension))
179    return;
180
181  ExtensionHost* host =
182#if defined(OS_MACOSX)
183      new ExtensionHostMac(extension, GetSiteInstanceForURL(url), url,
184                           ViewType::EXTENSION_BACKGROUND_PAGE);
185#else
186      new ExtensionHost(extension, GetSiteInstanceForURL(url), url,
187                        ViewType::EXTENSION_BACKGROUND_PAGE);
188#endif
189
190  host->CreateRenderViewSoon(NULL);  // create a RenderViewHost with no view
191  OnExtensionHostCreated(host, true);
192}
193
194void ExtensionProcessManager::OpenOptionsPage(const Extension* extension,
195                                              Browser* browser) {
196  DCHECK(!extension->options_url().is_empty());
197
198  // Force the options page to open in non-OTR window, because it won't be
199  // able to save settings from OTR.
200  if (!browser || browser->profile()->IsOffTheRecord()) {
201    browser = Browser::GetOrCreateTabbedBrowser(
202        browsing_instance_->profile()->GetOriginalProfile());
203  }
204
205  browser->OpenURL(extension->options_url(), GURL(), SINGLETON_TAB,
206                   PageTransition::LINK);
207  browser->window()->Show();
208  browser->GetSelectedTabContents()->Activate();
209}
210
211ExtensionHost* ExtensionProcessManager::GetBackgroundHostForExtension(
212    const Extension* extension) {
213  for (ExtensionHostSet::iterator iter = background_hosts_.begin();
214       iter != background_hosts_.end(); ++iter) {
215    ExtensionHost* host = *iter;
216    if (host->extension() == extension)
217      return host;
218  }
219  return NULL;
220}
221
222void ExtensionProcessManager::RegisterExtensionProcess(
223    const std::string& extension_id, int process_id) {
224  // TODO(mpcomplete): This is the only place we actually read process_ids_.
225  // Is it necessary?
226  ProcessIDMap::const_iterator it = process_ids_.find(extension_id);
227  if (it != process_ids_.end() && (*it).second == process_id)
228    return;
229
230  // Extension ids should get removed from the map before the process ids get
231  // reused from a dead renderer.
232  DCHECK(it == process_ids_.end());
233  process_ids_[extension_id] = process_id;
234
235  ExtensionService* extension_service =
236      browsing_instance_->profile()->GetExtensionService();
237
238  std::vector<std::string> page_action_ids;
239  const Extension* extension =
240      extension_service->GetExtensionById(extension_id, false);
241  if (extension->page_action())
242    page_action_ids.push_back(extension->page_action()->id());
243
244  RenderProcessHost* rph = RenderProcessHost::FromID(process_id);
245  rph->Send(new ExtensionMsg_UpdatePageActions(extension_id, page_action_ids));
246}
247
248void ExtensionProcessManager::UnregisterExtensionProcess(int process_id) {
249  ProcessIDMap::iterator it = process_ids_.begin();
250  while (it != process_ids_.end()) {
251    if (it->second == process_id)
252      process_ids_.erase(it++);
253    else
254      ++it;
255  }
256}
257
258RenderProcessHost* ExtensionProcessManager::GetExtensionProcess(
259    const GURL& url) {
260  if (!browsing_instance_->HasSiteInstance(url))
261    return NULL;
262  scoped_refptr<SiteInstance> site(
263      browsing_instance_->GetSiteInstanceForURL(url));
264  if (site->HasProcess())
265    return site->GetProcess();
266  return NULL;
267}
268
269RenderProcessHost* ExtensionProcessManager::GetExtensionProcess(
270    const std::string& extension_id) {
271  return GetExtensionProcess(
272      Extension::GetBaseURLFromExtensionId(extension_id));
273}
274
275SiteInstance* ExtensionProcessManager::GetSiteInstanceForURL(const GURL& url) {
276  return browsing_instance_->GetSiteInstanceForURL(url);
277}
278
279bool ExtensionProcessManager::HasExtensionHost(ExtensionHost* host) const {
280  return all_hosts_.find(host) != all_hosts_.end();
281}
282
283void ExtensionProcessManager::Observe(NotificationType type,
284                                      const NotificationSource& source,
285                                      const NotificationDetails& details) {
286  switch (type.value) {
287    case NotificationType::EXTENSIONS_READY: {
288      CreateBackgroundHosts(this,
289          Source<Profile>(source).ptr()->GetExtensionService()->extensions());
290      break;
291    }
292
293    case NotificationType::EXTENSION_LOADED: {
294      ExtensionService* service =
295          Source<Profile>(source).ptr()->GetExtensionService();
296      if (service->is_ready()) {
297        const Extension* extension = Details<const Extension>(details).ptr();
298        ::CreateBackgroundHost(this, extension);
299      }
300      break;
301    }
302
303    case NotificationType::EXTENSION_UNLOADED: {
304      const Extension* extension =
305          Details<UnloadedExtensionInfo>(details)->extension;
306      for (ExtensionHostSet::iterator iter = background_hosts_.begin();
307           iter != background_hosts_.end(); ++iter) {
308        ExtensionHost* host = *iter;
309        if (host->extension_id() == extension->id()) {
310          delete host;
311          // |host| should deregister itself from our structures.
312          DCHECK(background_hosts_.find(host) == background_hosts_.end());
313          break;
314        }
315      }
316      break;
317    }
318
319    case NotificationType::EXTENSION_HOST_DESTROYED: {
320      ExtensionHost* host = Details<ExtensionHost>(details).ptr();
321      all_hosts_.erase(host);
322      background_hosts_.erase(host);
323      break;
324    }
325
326    case NotificationType::RENDERER_PROCESS_TERMINATED:
327    case NotificationType::RENDERER_PROCESS_CLOSED: {
328      RenderProcessHost* host = Source<RenderProcessHost>(source).ptr();
329      UnregisterExtensionProcess(host->id());
330      break;
331    }
332
333    case NotificationType::APP_TERMINATING: {
334      // Close background hosts when the last browser is closed so that they
335      // have time to shutdown various objects on different threads. Our
336      // destructor is called too late in the shutdown sequence.
337      CloseBackgroundHosts();
338      break;
339    }
340
341    default:
342      NOTREACHED();
343  }
344}
345
346void ExtensionProcessManager::OnExtensionHostCreated(ExtensionHost* host,
347                                                     bool is_background) {
348  DCHECK_EQ(browsing_instance_->profile(), host->profile());
349
350  all_hosts_.insert(host);
351  if (is_background)
352    background_hosts_.insert(host);
353  NotificationService::current()->Notify(
354      NotificationType::EXTENSION_HOST_CREATED,
355      Source<ExtensionProcessManager>(this),
356      Details<ExtensionHost>(host));
357}
358
359void ExtensionProcessManager::CloseBackgroundHosts() {
360  VLOG_IF(1, g_log_bug53991) << "CloseBackgroundHosts: " << this;
361  for (ExtensionHostSet::iterator iter = background_hosts_.begin();
362       iter != background_hosts_.end(); ) {
363    ExtensionHostSet::iterator current = iter++;
364    delete *current;
365  }
366}
367
368//
369// IncognitoExtensionProcessManager
370//
371
372IncognitoExtensionProcessManager::IncognitoExtensionProcessManager(
373    Profile* profile)
374    : ExtensionProcessManager(profile),
375      original_manager_(profile->GetOriginalProfile()->
376                            GetExtensionProcessManager()) {
377  DCHECK(profile->IsOffTheRecord());
378
379  registrar_.Add(this, NotificationType::BROWSER_WINDOW_READY,
380                 NotificationService::AllSources());
381}
382
383ExtensionHost* IncognitoExtensionProcessManager::CreateView(
384    const Extension* extension,
385    const GURL& url,
386    Browser* browser,
387    ViewType::Type view_type) {
388  if (extension->incognito_split_mode()) {
389    if (IsIncognitoEnabled(extension)) {
390      return ExtensionProcessManager::CreateView(extension, url,
391                                                 browser, view_type);
392    } else {
393      NOTREACHED() <<
394          "We shouldn't be trying to create an incognito extension view unless "
395          "it has been enabled for incognito.";
396      return NULL;
397    }
398  } else {
399    return original_manager_->CreateView(extension, url, browser, view_type);
400  }
401}
402
403void IncognitoExtensionProcessManager::CreateBackgroundHost(
404    const Extension* extension, const GURL& url) {
405  if (extension->incognito_split_mode()) {
406    if (IsIncognitoEnabled(extension))
407      ExtensionProcessManager::CreateBackgroundHost(extension, url);
408  } else {
409    // Do nothing. If an extension is spanning, then its original-profile
410    // background page is shared with incognito, so we don't create another.
411  }
412}
413
414SiteInstance* IncognitoExtensionProcessManager::GetSiteInstanceForURL(
415    const GURL& url) {
416  const Extension* extension = GetExtensionOrAppByURL(url);
417  if (!extension || extension->incognito_split_mode()) {
418    return ExtensionProcessManager::GetSiteInstanceForURL(url);
419  } else {
420    return original_manager_->GetSiteInstanceForURL(url);
421  }
422}
423
424RenderProcessHost* IncognitoExtensionProcessManager::GetExtensionProcess(
425    const GURL& url) {
426  const Extension* extension = GetExtensionOrAppByURL(url);
427  if (!extension || extension->incognito_split_mode()) {
428    return ExtensionProcessManager::GetExtensionProcess(url);
429  } else {
430    return original_manager_->GetExtensionProcess(url);
431  }
432}
433
434const Extension* IncognitoExtensionProcessManager::GetExtensionOrAppByURL(
435    const GURL& url) {
436  ExtensionService* service =
437      browsing_instance_->profile()->GetExtensionService();
438  if (!service)
439    return NULL;
440  return (url.SchemeIs(chrome::kExtensionScheme)) ?
441      service->GetExtensionByURL(url) : service->GetExtensionByWebExtent(url);
442}
443
444bool IncognitoExtensionProcessManager::IsIncognitoEnabled(
445    const Extension* extension) {
446  ExtensionService* service =
447      browsing_instance_->profile()->GetExtensionService();
448  return service && service->IsIncognitoEnabled(extension->id());
449}
450
451void IncognitoExtensionProcessManager::Observe(
452    NotificationType type,
453    const NotificationSource& source,
454    const NotificationDetails& details) {
455  switch (type.value) {
456    case NotificationType::BROWSER_WINDOW_READY: {
457      // We want to spawn our background hosts as soon as the user opens an
458      // incognito window. Watch for new browsers and create the hosts if
459      // it matches our profile.
460      Browser* browser = Source<Browser>(source).ptr();
461      if (browser->profile() == browsing_instance_->profile()) {
462        // On Chrome OS, a login screen is implemented as a browser.
463        // This browser has no extension service.  In this case,
464        // service will be NULL.
465        ExtensionService* service =
466            browsing_instance_->profile()->GetExtensionService();
467        if (service && service->is_ready())
468          CreateBackgroundHosts(this, service->extensions());
469      }
470      break;
471    }
472    default:
473      ExtensionProcessManager::Observe(type, source, details);
474      break;
475  }
476}
477