blob: e844fac40d525869938c0fe794669153b040107e [file] [log] [blame]
rob3e2a0732016-01-06 21:22:091// Copyright 2015 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 "extensions/browser/extension_api_frame_id_map.h"
6
7#include <tuple>
8
9#include "content/public/browser/browser_thread.h"
10#include "content/public/browser/render_frame_host.h"
11#include "content/public/browser/render_process_host.h"
12#include "content/public/browser/web_contents.h"
13#include "content/public/common/child_process_host.h"
14
15namespace extensions {
16
17namespace {
18
19// The map is accessed on the IO and UI thread, so construct it once and never
20// delete it.
21base::LazyInstance<ExtensionApiFrameIdMap>::Leaky g_map_instance =
22 LAZY_INSTANCE_INITIALIZER;
23
24} // namespace
25
26const int ExtensionApiFrameIdMap::kInvalidFrameId = -1;
rob52277c82016-02-07 17:28:5727const int ExtensionApiFrameIdMap::kTopFrameId = 0;
rob3e2a0732016-01-06 21:22:0928
29ExtensionApiFrameIdMap::CachedFrameIdPair::CachedFrameIdPair()
30 : frame_id(kInvalidFrameId), parent_frame_id(kInvalidFrameId) {}
31
32ExtensionApiFrameIdMap::CachedFrameIdPair::CachedFrameIdPair(
33 int frame_id,
34 int parent_frame_id)
35 : frame_id(frame_id), parent_frame_id(parent_frame_id) {}
36
37ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey()
38 : render_process_id(content::ChildProcessHost::kInvalidUniqueID),
39 frame_routing_id(MSG_ROUTING_NONE) {}
40
41ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey(
42 int render_process_id,
43 int frame_routing_id)
44 : render_process_id(render_process_id),
45 frame_routing_id(frame_routing_id) {}
46
47ExtensionApiFrameIdMap::FrameIdCallbacks::FrameIdCallbacks()
48 : is_iterating(false) {}
49
50ExtensionApiFrameIdMap::FrameIdCallbacks::~FrameIdCallbacks() {}
51
52bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator<(
53 const RenderFrameIdKey& other) const {
54 return std::tie(render_process_id, frame_routing_id) <
55 std::tie(other.render_process_id, other.frame_routing_id);
56}
57
58bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator==(
59 const RenderFrameIdKey& other) const {
60 return render_process_id == other.render_process_id &&
61 frame_routing_id == other.frame_routing_id;
62}
63
64ExtensionApiFrameIdMap::ExtensionApiFrameIdMap() {}
65
66ExtensionApiFrameIdMap::~ExtensionApiFrameIdMap() {}
67
68ExtensionApiFrameIdMap* ExtensionApiFrameIdMap::Get() {
69 return g_map_instance.Pointer();
70}
71
72int ExtensionApiFrameIdMap::GetFrameId(content::RenderFrameHost* rfh) {
73 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
74
75 if (!rfh)
76 return kInvalidFrameId;
77 if (rfh->GetParent())
78 return rfh->GetFrameTreeNodeId();
rob52277c82016-02-07 17:28:5779 return kTopFrameId;
rob3e2a0732016-01-06 21:22:0980}
81
82int ExtensionApiFrameIdMap::GetParentFrameId(content::RenderFrameHost* rfh) {
83 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
84
85 return rfh ? GetFrameId(rfh->GetParent()) : kInvalidFrameId;
86}
87
88content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostById(
89 content::WebContents* web_contents,
90 int frame_id) {
91 // Although it is technically possible to map |frame_id| to a RenderFrameHost
92 // without WebContents, we choose to not do that because in the extension API
93 // frameIds are only guaranteed to be meaningful in combination with a tabId.
94 if (!web_contents)
95 return nullptr;
96
97 if (frame_id == kInvalidFrameId)
98 return nullptr;
99
rob52277c82016-02-07 17:28:57100 if (frame_id == kTopFrameId)
rob3e2a0732016-01-06 21:22:09101 return web_contents->GetMainFrame();
102
103 DCHECK_GE(frame_id, 1);
104 return web_contents->FindFrameByFrameTreeNodeId(frame_id);
105}
106
107ExtensionApiFrameIdMap::CachedFrameIdPair ExtensionApiFrameIdMap::KeyToValue(
108 const RenderFrameIdKey& key) const {
109 content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
110 key.render_process_id, key.frame_routing_id);
111 return CachedFrameIdPair(GetFrameId(rfh), GetParentFrameId(rfh));
112}
113
114ExtensionApiFrameIdMap::CachedFrameIdPair
115ExtensionApiFrameIdMap::LookupFrameIdOnUI(const RenderFrameIdKey& key) {
116 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
117
118 FrameIdMap::const_iterator frame_id_iter = frame_id_map_.find(key);
119 if (frame_id_iter != frame_id_map_.end())
120 return frame_id_iter->second;
121
122 CachedFrameIdPair cached_frame_id_pair = KeyToValue(key);
123 // Don't save invalid values in the map.
124 if (cached_frame_id_pair.frame_id == kInvalidFrameId)
125 return cached_frame_id_pair;
126
127 auto kvpair = FrameIdMap::value_type(key, cached_frame_id_pair);
128 base::AutoLock lock(frame_id_map_lock_);
129 return frame_id_map_.insert(kvpair).first->second;
130}
131
132void ExtensionApiFrameIdMap::ReceivedFrameIdOnIO(
133 const RenderFrameIdKey& key,
134 const CachedFrameIdPair& cached_frame_id_pair) {
135 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
136
137 FrameIdCallbacksMap::iterator map_iter = callbacks_map_.find(key);
138 if (map_iter == callbacks_map_.end()) {
139 // Can happen if ReceivedFrameIdOnIO was called after the frame ID was
140 // resolved (e.g. via GetFrameIdOnIO), but before PostTaskAndReply replied.
141 return;
142 }
143
144 FrameIdCallbacks& callbacks = map_iter->second;
145
146 if (callbacks.is_iterating)
147 return;
148 callbacks.is_iterating = true;
149
150 // Note: Extra items can be appended to |callbacks| during this loop if a
151 // callback calls GetFrameIdOnIO().
152 for (std::list<FrameIdCallback>::iterator it = callbacks.callbacks.begin();
153 it != callbacks.callbacks.end(); ++it) {
154 it->Run(cached_frame_id_pair.frame_id,
155 cached_frame_id_pair.parent_frame_id);
156 }
157 callbacks_map_.erase(key);
158}
159
160void ExtensionApiFrameIdMap::GetFrameIdOnIO(int render_process_id,
161 int frame_routing_id,
162 const FrameIdCallback& callback) {
163 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
164
165 if (frame_routing_id <= -1) {
166 // frame_routing_id == -2 = MSG_ROUTING_NONE -> not a RenderFrameHost.
167 // frame_routing_id == -1 -> should be MSG_ROUTING_NONE, but there are
168 // callers that use "-1" for unknown frames.
169 // TODO(robwu): Enable assertion when all callers have been fixed.
170 // DCHECK_EQ(MSG_ROUTING_NONE, -1);
171 callback.Run(kInvalidFrameId, kInvalidFrameId);
172 return;
173 }
174 // A valid routing ID is only meaningful with a valid process ID.
175 DCHECK_GE(render_process_id, 0);
176
177 const RenderFrameIdKey key(render_process_id, frame_routing_id);
178 CachedFrameIdPair cached_frame_id_pair;
179 bool did_find_cached_frame_id_pair = false;
180
181 {
182 base::AutoLock lock(frame_id_map_lock_);
183 FrameIdMap::const_iterator frame_id_iter = frame_id_map_.find(key);
184 if (frame_id_iter != frame_id_map_.end()) {
185 // This is very likely to happen because CacheFrameId() is called as soon
186 // as the frame is created.
187 cached_frame_id_pair = frame_id_iter->second;
188 did_find_cached_frame_id_pair = true;
189 }
190 }
191
192 FrameIdCallbacksMap::iterator map_iter = callbacks_map_.find(key);
193
194 if (did_find_cached_frame_id_pair) {
195 // Value already cached, thread hopping is not needed.
196 if (map_iter == callbacks_map_.end()) {
197 // If the frame ID was cached, then it is likely that there are no pending
198 // callbacks. So do not unnecessarily copy the callback, but run it.
199 callback.Run(cached_frame_id_pair.frame_id,
200 cached_frame_id_pair.parent_frame_id);
201 } else {
202 map_iter->second.callbacks.push_back(callback);
203 ReceivedFrameIdOnIO(key, cached_frame_id_pair);
204 }
205 return;
206 }
207
208 // The key was seen for the first time (or the frame has been removed).
209 // Hop to the UI thread to look up the extension API frame ID.
210 callbacks_map_[key].callbacks.push_back(callback);
211 content::BrowserThread::PostTaskAndReplyWithResult(
212 content::BrowserThread::UI, FROM_HERE,
213 base::Bind(&ExtensionApiFrameIdMap::LookupFrameIdOnUI,
214 base::Unretained(this), key),
215 base::Bind(&ExtensionApiFrameIdMap::ReceivedFrameIdOnIO,
216 base::Unretained(this), key));
217}
218
219void ExtensionApiFrameIdMap::CacheFrameId(content::RenderFrameHost* rfh) {
220 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
221
222 const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
223 CacheFrameId(key);
224 DCHECK(frame_id_map_.find(key) != frame_id_map_.end());
225}
226
227void ExtensionApiFrameIdMap::CacheFrameId(const RenderFrameIdKey& key) {
228 LookupFrameIdOnUI(key);
229}
230
231void ExtensionApiFrameIdMap::RemoveFrameId(content::RenderFrameHost* rfh) {
232 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
233 DCHECK(rfh);
234
235 const RenderFrameIdKey key(rfh->GetProcess()->GetID(), rfh->GetRoutingID());
236 RemoveFrameId(key);
237}
238
239void ExtensionApiFrameIdMap::RemoveFrameId(const RenderFrameIdKey& key) {
240 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
241
242 base::AutoLock lock(frame_id_map_lock_);
243 frame_id_map_.erase(key);
244}
245
246} // namespace extensions