1// Copyright (c) 2013 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_renderer_state.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "chrome/browser/chrome_notification_types.h"
10#include "chrome/browser/sessions/session_tab_helper.h"
11#include "chrome/browser/tab_contents/retargeting_details.h"
12#include "content/public/browser/browser_thread.h"
13#include "content/public/browser/navigation_details.h"
14#include "content/public/browser/notification_observer.h"
15#include "content/public/browser/notification_registrar.h"
16#include "content/public/browser/notification_service.h"
17#include "content/public/browser/notification_types.h"
18#include "content/public/browser/render_process_host.h"
19#include "content/public/browser/render_view_host.h"
20#include "content/public/browser/resource_request_info.h"
21#include "content/public/browser/web_contents.h"
22#include "content/public/browser/web_contents_observer.h"
23#include "content/public/common/process_type.h"
24
25using content::BrowserThread;
26using content::RenderProcessHost;
27using content::RenderViewHost;
28using content::WebContents;
29
30//
31// ExtensionRendererState::RenderViewHostObserver
32//
33
34class ExtensionRendererState::RenderViewHostObserver
35    : public content::WebContentsObserver {
36 public:
37  RenderViewHostObserver(RenderViewHost* host, WebContents* web_contents)
38      : content::WebContentsObserver(web_contents),
39        render_view_host_(host) {
40  }
41
42  virtual void RenderViewDeleted(content::RenderViewHost* host) OVERRIDE {
43    if (host != render_view_host_)
44      return;
45    BrowserThread::PostTask(
46        BrowserThread::IO, FROM_HERE,
47        base::Bind(
48            &ExtensionRendererState::ClearTabAndWindowId,
49            base::Unretained(ExtensionRendererState::GetInstance()),
50            host->GetProcess()->GetID(), host->GetRoutingID()));
51
52    delete this;
53  }
54
55 private:
56  RenderViewHost* render_view_host_;
57
58  DISALLOW_COPY_AND_ASSIGN(RenderViewHostObserver);
59};
60
61//
62// ExtensionRendererState::TabObserver
63//
64
65// This class listens for notifications about changes in renderer state on the
66// UI thread, and notifies the ExtensionRendererState on the IO thread. It
67// should only ever be accessed on the UI thread.
68class ExtensionRendererState::TabObserver
69    : public content::NotificationObserver {
70 public:
71  TabObserver();
72  virtual ~TabObserver();
73
74 private:
75  // content::NotificationObserver interface.
76  virtual void Observe(int type,
77                       const content::NotificationSource& source,
78                       const content::NotificationDetails& details) OVERRIDE;
79
80  content::NotificationRegistrar registrar_;
81};
82
83ExtensionRendererState::TabObserver::TabObserver() {
84  DCHECK_CURRENTLY_ON(BrowserThread::UI);
85  registrar_.Add(this,
86                 content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED,
87                 content::NotificationService::AllBrowserContextsAndSources());
88  registrar_.Add(this, chrome::NOTIFICATION_TAB_PARENTED,
89                 content::NotificationService::AllBrowserContextsAndSources());
90  registrar_.Add(this, chrome::NOTIFICATION_RETARGETING,
91                 content::NotificationService::AllBrowserContextsAndSources());
92}
93
94ExtensionRendererState::TabObserver::~TabObserver() {
95  DCHECK_CURRENTLY_ON(BrowserThread::UI);
96}
97
98void ExtensionRendererState::TabObserver::Observe(
99    int type, const content::NotificationSource& source,
100    const content::NotificationDetails& details) {
101  switch (type) {
102    case content::NOTIFICATION_WEB_CONTENTS_RENDER_VIEW_HOST_CREATED: {
103      WebContents* web_contents = content::Source<WebContents>(source).ptr();
104      SessionTabHelper* session_tab_helper =
105          SessionTabHelper::FromWebContents(web_contents);
106      if (!session_tab_helper)
107        return;
108      RenderViewHost* host = content::Details<RenderViewHost>(details).ptr();
109      // TODO(mpcomplete): How can we tell if window_id is bogus? It may not
110      // have been set yet.
111      BrowserThread::PostTask(
112          BrowserThread::IO, FROM_HERE,
113          base::Bind(
114              &ExtensionRendererState::SetTabAndWindowId,
115              base::Unretained(ExtensionRendererState::GetInstance()),
116              host->GetProcess()->GetID(), host->GetRoutingID(),
117              session_tab_helper->session_id().id(),
118              session_tab_helper->window_id().id()));
119
120      // The observer deletes itself.
121      new ExtensionRendererState::RenderViewHostObserver(host, web_contents);
122
123      break;
124    }
125    case chrome::NOTIFICATION_TAB_PARENTED: {
126      WebContents* web_contents = content::Source<WebContents>(source).ptr();
127      SessionTabHelper* session_tab_helper =
128          SessionTabHelper::FromWebContents(web_contents);
129      if (!session_tab_helper)
130        return;
131      RenderViewHost* host = web_contents->GetRenderViewHost();
132      BrowserThread::PostTask(
133          BrowserThread::IO, FROM_HERE,
134          base::Bind(
135              &ExtensionRendererState::SetTabAndWindowId,
136              base::Unretained(ExtensionRendererState::GetInstance()),
137              host->GetProcess()->GetID(), host->GetRoutingID(),
138              session_tab_helper->session_id().id(),
139              session_tab_helper->window_id().id()));
140      break;
141    }
142    case chrome::NOTIFICATION_RETARGETING: {
143      RetargetingDetails* retargeting_details =
144          content::Details<RetargetingDetails>(details).ptr();
145      WebContents* web_contents = retargeting_details->target_web_contents;
146      SessionTabHelper* session_tab_helper =
147          SessionTabHelper::FromWebContents(web_contents);
148      if (!session_tab_helper)
149        return;
150      RenderViewHost* host = web_contents->GetRenderViewHost();
151      BrowserThread::PostTask(
152          BrowserThread::IO, FROM_HERE,
153          base::Bind(
154              &ExtensionRendererState::SetTabAndWindowId,
155              base::Unretained(ExtensionRendererState::GetInstance()),
156              host->GetProcess()->GetID(), host->GetRoutingID(),
157              session_tab_helper->session_id().id(),
158              session_tab_helper->window_id().id()));
159      break;
160    }
161    default:
162      NOTREACHED();
163      return;
164  }
165}
166
167//
168// ExtensionRendererState
169//
170
171ExtensionRendererState::ExtensionRendererState() : observer_(NULL) {
172}
173
174ExtensionRendererState::~ExtensionRendererState() {
175}
176
177// static
178ExtensionRendererState* ExtensionRendererState::GetInstance() {
179  return Singleton<ExtensionRendererState>::get();
180}
181
182void ExtensionRendererState::Init() {
183  observer_ = new TabObserver;
184}
185
186void ExtensionRendererState::Shutdown() {
187  delete observer_;
188}
189
190void ExtensionRendererState::SetTabAndWindowId(
191    int render_process_host_id, int routing_id, int tab_id, int window_id) {
192  DCHECK_CURRENTLY_ON(BrowserThread::IO);
193  RenderId render_id(render_process_host_id, routing_id);
194  map_[render_id] = TabAndWindowId(tab_id, window_id);
195}
196
197void ExtensionRendererState::ClearTabAndWindowId(
198    int render_process_host_id, int routing_id) {
199  DCHECK_CURRENTLY_ON(BrowserThread::IO);
200  RenderId render_id(render_process_host_id, routing_id);
201  map_.erase(render_id);
202}
203
204bool ExtensionRendererState::GetTabAndWindowId(
205    const  content::ResourceRequestInfo* info, int* tab_id, int* window_id) {
206  DCHECK_CURRENTLY_ON(BrowserThread::IO);
207  int render_process_id;
208  if (info->GetProcessType() == content::PROCESS_TYPE_PLUGIN) {
209    render_process_id = info->GetOriginPID();
210  } else {
211    render_process_id = info->GetChildID();
212  }
213  int render_view_id = info->GetRouteID();
214  RenderId render_id(render_process_id, render_view_id);
215  TabAndWindowIdMap::iterator iter = map_.find(render_id);
216  if (iter != map_.end()) {
217    *tab_id = iter->second.first;
218    *window_id = iter->second.second;
219    return true;
220  }
221  return false;
222}
223