Index: extensions/browser/extension_api_frame_id_map.cc |
diff --git a/extensions/browser/extension_api_frame_id_map.cc b/extensions/browser/extension_api_frame_id_map.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..5d947b70057fcf512b4318c89749148ad0302b7d |
--- /dev/null |
+++ b/extensions/browser/extension_api_frame_id_map.cc |
@@ -0,0 +1,269 @@ |
+// Copyright 2015 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "extensions/browser/extension_api_frame_id_map.h" |
+ |
+#include <tuple> |
+ |
+#include "base/time/time.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/render_frame_host.h" |
+#include "content/public/browser/render_process_host.h" |
+#include "content/public/browser/web_contents.h" |
+ |
+namespace extensions { |
+ |
+namespace { |
+ |
+// The map is accessed on the IO and UI thread, so construct it once and never |
+// delete it. |
+base::LazyInstance<ExtensionApiFrameIdMap>::Leaky g_map_instance = |
+ LAZY_INSTANCE_INITIALIZER; |
+ |
+int GetFrameIdFromFrame(content::RenderFrameHost* rfh) { |
+ if (!rfh) |
+ return ExtensionApiFrameId::kInvalidFrameId; |
+ if (rfh->GetParent()) |
+ return rfh->GetFrameTreeNodeId(); |
+ return 0; // Main frame. |
+} |
+ |
+} // namespace |
+ |
+ExtensionApiFrameId::ExtensionApiFrameId() |
+ : frame_id(kInvalidFrameId), parent_frame_id(kInvalidFrameId) {} |
+ |
+ExtensionApiFrameId::ExtensionApiFrameId(int frame_id, int parent_frame_id) |
+ : frame_id(frame_id), parent_frame_id(parent_frame_id) {} |
+ |
+ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey() |
+ : render_process_id(-1), frame_routing_id(-1) {} |
+ |
+ExtensionApiFrameIdMap::RenderFrameIdKey::RenderFrameIdKey( |
+ int render_process_id, |
+ int frame_routing_id) |
+ : render_process_id(render_process_id), |
+ frame_routing_id(frame_routing_id) {} |
+ |
+ExtensionApiFrameIdMap::FrameIdCallbackInfo::FrameIdCallbackInfo( |
+ const RenderFrameIdKey& key, |
+ const FrameIdCallback& callback) |
+ : key(key), callback(callback), has_value(false) {} |
+ |
+ExtensionApiFrameIdMap::FrameIdCallbackInfo::~FrameIdCallbackInfo() {} |
+ |
+bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator<( |
+ const RenderFrameIdKey& other) const { |
+ return std::tie(render_process_id, frame_routing_id) < |
+ std::tie(other.render_process_id, other.frame_routing_id); |
+} |
+ |
+bool ExtensionApiFrameIdMap::RenderFrameIdKey::operator==( |
+ const RenderFrameIdKey& other) const { |
+ return render_process_id == other.render_process_id && |
+ frame_routing_id == other.frame_routing_id; |
+} |
+ |
+ExtensionApiFrameIdMap::FrameIdRemovalTask::FrameIdRemovalTask( |
+ const RenderFrameIdKey& key, |
+ const base::TimeTicks& creation_time) |
+ : key(key), creation_time(creation_time) {} |
+ |
+ExtensionApiFrameIdMap::ExtensionApiFrameIdMap() {} |
+ |
+ExtensionApiFrameIdMap::~ExtensionApiFrameIdMap() {} |
+ |
+ExtensionApiFrameIdMap* ExtensionApiFrameIdMap::Get() { |
+ return g_map_instance.Pointer(); |
+} |
+ |
+ExtensionApiFrameId ExtensionApiFrameIdMap::KeyToValue( |
+ const RenderFrameIdKey& key) const { |
+ content::RenderFrameHost* rfh = content::RenderFrameHost::FromID( |
+ key.render_process_id, key.frame_routing_id); |
+ return ExtensionApiFrameId( |
+ GetFrameIdFromFrame(rfh), |
+ GetFrameIdFromFrame(rfh ? rfh->GetParent() : nullptr)); |
+} |
+ |
+ExtensionApiFrameId ExtensionApiFrameIdMap::LookupFrameIdOnUI( |
+ const RenderFrameIdKey& key) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ FrameIdMap::const_iterator frame_id_iter = frame_id_map_.find(key); |
+ if (frame_id_iter != frame_id_map_.end()) |
+ return frame_id_iter->second; |
+ |
+ const ExtensionApiFrameId& extension_api_frame_id = KeyToValue(key); |
+ // Don't save invalid values in the map. |
nasko
2015/12/22 22:12:09
nit: Comment above the variable or empty line betw
|
+ if (extension_api_frame_id.frame_id == ExtensionApiFrameId::kInvalidFrameId) |
+ return extension_api_frame_id; |
+ |
+ auto kvpair = FrameIdMap::value_type(key, extension_api_frame_id); |
+ base::AutoLock lock(frame_id_map_lock_); |
+ return frame_id_map_.insert(kvpair).first->second; |
+} |
+ |
+void ExtensionApiFrameIdMap::GotFrameIdOnIO( |
nasko
2015/12/22 22:12:09
nit: GetFrameIdOnIO and GotFrameIdOnIO differ by o
|
+ const RenderFrameIdKey& key, |
+ const ExtensionApiFrameId& extension_api_frame_id) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ // BrowserThread::PostTaskAndReplyWithResult runs the tasks in order, so the |
nasko
2015/12/22 22:12:10
nit: Empty line before the comment.
|
+ // order of calling GotFrameIdOnIO() should match the order of the callbacks. |
+ // This implies that whenever GotFrameIdOnIO() is called, the key should be |
+ // identical to the key at the front of the queue. |
+ DCHECK(!callbacks_.empty()); |
+ DCHECK(callbacks_.front().key == key); |
+ |
+ // Note: Extra items can be appended to |callbacks_| during this loop if the |
+ // callback calls GetFrameIdOnIO(). |
+ while (!callbacks_.empty() && |
+ (callbacks_.front().has_value || callbacks_.front().key == key)) { |
+ FrameIdCallbackInfo callback_info = callbacks_.front(); |
+ callbacks_.pop_front(); |
+ if (callback_info.has_value) |
+ callback_info.callback.Run(callback_info.value); |
+ else // callback_info.key == key |
+ callback_info.callback.Run(extension_api_frame_id); |
+ } |
+ |
+ // Not all callbacks of |key| are immediately called, because it is possible |
+ // that GetFrameIdOnIO() is called with a different argument. E.g. in the |
+ // following scenario: |
+ // GetFrameIdOnIO(key1, cb1); // cb1 run by GotFrameIdOnIO for key1. |
+ // GetFrameIdOnIO(key2, cb2); // waiting for GotFrameIdOnIO for key2. |
+ // GetFrameIdOnIO(key1, cb3); // waiting for GetFrameIdOnIO for key2. |
+ // In the above example, |cb3| will be called as soon as |cb2| is removed from |
+ // the callbacks queue. |
nasko
2015/12/22 22:12:10
Why do we need all of this complexity? Why can't w
|
+ for (auto& callback_info : callbacks_) { |
+ if (callback_info.key == key) { |
+ callback_info.has_value = true; |
+ callback_info.value = extension_api_frame_id; |
+ } |
+ } |
+} |
+ |
+void ExtensionApiFrameIdMap::GetFrameIdOnIO(int render_process_id, |
+ int frame_routing_id, |
+ const FrameIdCallback& callback) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ |
+ const RenderFrameIdKey key(render_process_id, frame_routing_id); |
+ FrameIdCallbackInfo callback_info(key, callback); |
+ |
+ { |
+ base::AutoLock lock(frame_id_map_lock_); |
+ FrameIdMap::const_iterator frame_id_iter = frame_id_map_.find(key); |
+ if (frame_id_iter != frame_id_map_.end()) { |
+ callback_info.value = frame_id_iter->second; |
+ callback_info.has_value = true; |
+ } |
+ } |
+ |
+ if (callback_info.has_value) { |
+ // Value already cached, thread hopping is not needed. Run the callback if |
+ // there are no pending callbacks, otherwise queue it. |
+ if (callbacks_.empty()) |
+ callback_info.callback.Run(callback_info.value); |
+ else |
+ callbacks_.push_back(callback_info); |
+ return; |
+ } |
+ |
+ // Check whether the frame ID lookup was requested before. If yes, then there |
+ // is no need for posting a task to the UI thread. |
+ for (const auto& other : callbacks_) { |
+ if (other.key == key) { |
+ callbacks_.push_back(callback_info); |
+ return; |
+ } |
+ } |
+ |
+ // The key was seen for the first time, hop to the UI thread to look up the |
+ // extension frame ID. |
+ callbacks_.push_back(callback_info); |
+ content::BrowserThread::PostTaskAndReplyWithResult( |
+ content::BrowserThread::UI, FROM_HERE, |
+ base::Bind(&ExtensionApiFrameIdMap::LookupFrameIdOnUI, |
+ base::Unretained(this), key), |
+ base::Bind(&ExtensionApiFrameIdMap::GotFrameIdOnIO, |
+ base::Unretained(this), key)); |
+} |
+ |
+const ExtensionApiFrameId ExtensionApiFrameIdMap::GetFrameId( |
+ int render_process_id, |
+ int frame_routing_id) { |
+ return LookupFrameIdOnUI( |
+ RenderFrameIdKey(render_process_id, frame_routing_id)); |
+} |
+ |
+const ExtensionApiFrameId ExtensionApiFrameIdMap::GetFrameId( |
+ content::RenderFrameHost* rfh) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!rfh) |
+ return ExtensionApiFrameId(); |
+ |
+ return LookupFrameIdOnUI( |
+ RenderFrameIdKey(rfh->GetProcess()->GetID(), rfh->GetRoutingID())); |
+} |
+ |
+void ExtensionApiFrameIdMap::RemoveFrameId(int render_process_id, |
+ int frame_routing_id) { |
+ // Defer removal of the key, so that RemoveFrameId() can be called when the |
+ // RenderFrameHost is destroyed, without causing frame mappings to fail for |
+ // other in-flight events / notifications. |
+ pending_deletions_.push_back( |
+ FrameIdRemovalTask(RenderFrameIdKey(render_process_id, frame_routing_id), |
+ base::TimeTicks::Now())); |
+ |
+ // Minimum number of seconds to wait before removing the frame ID. |
nasko
2015/12/22 22:12:10
I found this comment to be confusing. I was expect
|
+ const int kSecondsUntilRemoval = 1; |
nasko
2015/12/22 22:12:09
This should be declared at the top of the file. Al
|
+ base::TimeTicks expired_creation_time = |
+ base::TimeTicks::Now() - |
+ base::TimeDelta::FromSeconds(kSecondsUntilRemoval); |
+ |
+ // Remove previously queued removal tasks. RenderFrameId() is usually called |
+ // whenever a frame is removed, so this clean up eventually happens. In the |
+ // worst case scenario, lots of frames are created and immediately removed |
+ // before |kSecondsUntilRemoval| seconds have passed. This is extremely |
nasko
2015/12/22 22:12:09
nit: I'd remove the "extremely" qualifier, as you'
|
+ // unlikely to happen. Even if it were to occur, the impact is just a slight |
+ // waste of memory due to the retention of useless map entries. |
+ base::AutoLock lock(frame_id_map_lock_); |
+ while (!pending_deletions_.empty() && |
+ pending_deletions_.front().creation_time < expired_creation_time) { |
+ frame_id_map_.erase(pending_deletions_.front().key); |
+ pending_deletions_.pop_front(); |
+ } |
+} |
+ |
+void ExtensionApiFrameIdMap::RemoveFrameId(content::RenderFrameHost* rfh) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::UI); |
+ |
+ if (!rfh) |
+ return; |
+ |
+ RemoveFrameId(rfh->GetProcess()->GetID(), rfh->GetRoutingID()); |
+} |
+ |
+content::RenderFrameHost* ExtensionApiFrameIdMap::GetRenderFrameHostById( |
+ content::WebContents* web_contents, |
+ int frame_id) { |
+ // Although it is technically possible to map |frame_id| to a RenderFrameHost |
+ // without WebContents, we choose to not do that because in the extension API |
+ // frameIds are only guaranteed to be meaningful in combination with a tabId. |
+ if (!web_contents) |
+ return nullptr; |
+ |
+ if (frame_id == -1) |
+ return nullptr; |
+ |
+ if (frame_id == 0) |
+ return web_contents->GetMainFrame(); |
+ |
+ DCHECK_GE(frame_id, 1); |
+ return web_contents->FindFrameByFrameTreeNodeId(frame_id); |
+} |
+ |
+} // namespace extensions |