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