Index: extensions/renderer/messaging_bindings.cc |
diff --git a/extensions/renderer/messaging_bindings.cc b/extensions/renderer/messaging_bindings.cc |
index 595bee1b600e7229871c148feab9b0d8c90d1aaa..4f3946304dc08476d2ac0b4f356fb6c50e6cb9b1 100644 |
--- a/extensions/renderer/messaging_bindings.cc |
+++ b/extensions/renderer/messaging_bindings.cc |
@@ -119,28 +119,73 @@ class GCCallback : public base::SupportsWeakPtr<GCCallback> { |
DISALLOW_COPY_AND_ASSIGN(GCCallback); |
}; |
-struct ExtensionData { |
- struct PortData { |
- int ref_count; // how many contexts have a handle to this port |
- PortData() : ref_count(0) {} |
- }; |
- std::map<int, PortData> ports; // port ID -> data |
-}; |
+// Maintains the set of ScriptContexts that reference each Port, by ID. |
+class PortTracker { |
+ public: |
+ PortTracker() {} |
+ |
+ // Returns true if |context| references |port_id|. |
+ bool HasReference(ScriptContext* context, int port_id) const { |
+ auto ports = contexts_to_ports_.find(context); |
+ if (ports == contexts_to_ports_.end()) |
+ return false; |
+ return ports->second.count(port_id) > 0; |
+ } |
-base::LazyInstance<ExtensionData> g_extension_data = LAZY_INSTANCE_INITIALIZER; |
+ // Marks |context| and |port_id| as referencing each other. |
+ void AddReference(ScriptContext* context, int port_id) { |
+ if (contexts_to_ports_[context].insert(port_id).second) |
+ ++port_refcount_[port_id]; |
+ } |
-bool HasPortData(int port_id) { |
- return g_extension_data.Get().ports.find(port_id) != |
- g_extension_data.Get().ports.end(); |
-} |
+ // Removes the references between |context| and |port_id|. |
+ void RemoveReference(ScriptContext* context, int port_id) { |
+ auto ports = contexts_to_ports_.find(context); |
+ if (ports == contexts_to_ports_.end()) { |
+ return; |
+ } |
+ if (ports->second.erase(port_id) == 0) |
+ return; |
+ if (ports->second.size() == 0) |
+ contexts_to_ports_.erase(context); |
+ if (--port_refcount_[port_id] == 0) |
+ port_refcount_.erase(port_id); |
+ } |
-ExtensionData::PortData& GetPortData(int port_id) { |
- return g_extension_data.Get().ports[port_id]; |
-} |
+ // Returns true if this tracker has any reference to |port_id|. |
+ bool HasPort(int port_id) const { |
+ return port_refcount_.find(port_id) != port_refcount_.end(); |
+ } |
-void ClearPortData(int port_id) { |
- g_extension_data.Get().ports.erase(port_id); |
-} |
+ // Deletes all references to |port_id|. |
+ void DeletePort(int port_id) { |
+ port_refcount_.erase(port_id); |
+ for (auto it = contexts_to_ports_.begin(); |
+ it != contexts_to_ports_.end();) { |
+ if (it->second.erase(port_id) > 0 && it->second.empty()) |
+ contexts_to_ports_.erase(it++); |
+ else |
+ ++it; |
+ } |
+ } |
+ |
+ // Gets every port ID that has a reference to |context|. |
+ std::set<int> GetPorts(ScriptContext* context) const { |
+ auto ports = contexts_to_ports_.find(context); |
+ return ports == contexts_to_ports_.end() ? std::set<int>() : ports->second; |
+ } |
+ |
+ private: |
+ // Maps ScriptContexts to the port IDs that have a reference to it. |
+ std::map<ScriptContext*, std::set<int>> contexts_to_ports_; |
+ |
+ // Maps port IDs to the number of ScriptContexts that have a reference to it. |
+ std::map<int, int> port_refcount_; |
+ |
+ DISALLOW_COPY_AND_ASSIGN(PortTracker); |
+}; |
+ |
+base::LazyInstance<PortTracker> g_port_tracker = LAZY_INSTANCE_INITIALIZER; |
const char kPortClosedError[] = "Attempting to use a disconnected port object"; |
const char kReceivingEndDoesntExistError[] = |
@@ -167,13 +212,22 @@ class ExtensionImpl : public ObjectBackedNativeHandler { |
// TODO(fsamuel, kalman): Move BindToGC out of messaging natives. |
RouteFunction("BindToGC", |
base::Bind(&ExtensionImpl::BindToGC, base::Unretained(this))); |
+ |
+ // Observe |context| so that port references to it can be cleared. |
+ context->AddInvalidationObserver(base::Bind( |
+ &ExtensionImpl::OnContextInvalidated, weak_ptr_factory_.GetWeakPtr())); |
} |
~ExtensionImpl() override {} |
private: |
+ void OnContextInvalidated() { |
+ for (int port_id : g_port_tracker.Get().GetPorts(context())) |
+ ReleasePort(port_id); |
+ } |
+ |
void ClearPortDataAndNotifyDispatcher(int port_id) { |
- ClearPortData(port_id); |
+ g_port_tracker.Get().DeletePort(port_id); |
dispatcher_->ClearPortData(port_id); |
} |
@@ -187,7 +241,7 @@ class ExtensionImpl : public ObjectBackedNativeHandler { |
CHECK(args.Length() == 2 && args[0]->IsInt32() && args[1]->IsString()); |
int port_id = args[0]->Int32Value(); |
- if (!HasPortData(port_id)) { |
+ if (!g_port_tracker.Get().HasPort(port_id)) { |
args.GetIsolate()->ThrowException(v8::Exception::Error( |
v8::String::NewFromUtf8(args.GetIsolate(), kPortClosedError))); |
return; |
@@ -207,7 +261,7 @@ class ExtensionImpl : public ObjectBackedNativeHandler { |
CHECK(args[1]->IsBoolean()); |
int port_id = args[0]->Int32Value(); |
- if (!HasPortData(port_id)) |
+ if (!g_port_tracker.Get().HasPort(port_id)) |
return; |
// Send via the RenderThread because the RenderFrame might be closing. |
@@ -228,7 +282,7 @@ class ExtensionImpl : public ObjectBackedNativeHandler { |
CHECK(args[0]->IsInt32()); |
int port_id = args[0]->Int32Value(); |
- ++GetPortData(port_id).ref_count; |
+ g_port_tracker.Get().AddReference(context(), port_id); |
} |
// The frame a port lived in has been destroyed. When there are no more |
@@ -240,10 +294,11 @@ class ExtensionImpl : public ObjectBackedNativeHandler { |
ReleasePort(args[0]->Int32Value()); |
} |
- // Implementation of both the PortRelease native handler call, and callback |
- // when contexts are invalidated to release their ports. |
+ // Releases the reference to |port_id| for this context, and clears all port |
+ // data if there are no more references. |
void ReleasePort(int port_id) { |
- if (HasPortData(port_id) && --GetPortData(port_id).ref_count == 0) { |
+ g_port_tracker.Get().RemoveReference(context(), port_id); |
+ if (!g_port_tracker.Get().HasPort(port_id)) { |
// Send via the RenderThread because the RenderFrame might be closing. |
content::RenderThread::Get()->Send( |
new ExtensionHostMsg_CloseChannel(port_id, std::string())); |