summaryrefslogtreecommitdiffstats
path: root/chrome/renderer
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/renderer')
-rw-r--r--chrome/renderer/extensions/bindings_utils.cc110
-rw-r--r--chrome/renderer/extensions/bindings_utils.h71
-rw-r--r--chrome/renderer/extensions/event_bindings.cc171
-rw-r--r--chrome/renderer/extensions/event_bindings.h6
-rw-r--r--chrome/renderer/extensions/extension_api_client_unittest.cc5
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.cc175
-rw-r--r--chrome/renderer/extensions/extension_process_bindings.h4
-rw-r--r--chrome/renderer/extensions/renderer_extension_bindings.cc13
-rw-r--r--chrome/renderer/js_only_v8_extensions.cc2
-rw-r--r--chrome/renderer/render_view.cc10
-rw-r--r--chrome/renderer/renderer_resources.grd2
-rw-r--r--chrome/renderer/resources/event_bindings.js98
-rw-r--r--chrome/renderer/resources/extension_process_bindings.js55
-rw-r--r--chrome/renderer/resources/greasemonkey_api.js4
-rw-r--r--chrome/renderer/resources/renderer_extension_bindings.js37
15 files changed, 418 insertions, 345 deletions
diff --git a/chrome/renderer/extensions/bindings_utils.cc b/chrome/renderer/extensions/bindings_utils.cc
index 81e2058..fc9e98d 100644
--- a/chrome/renderer/extensions/bindings_utils.cc
+++ b/chrome/renderer/extensions/bindings_utils.cc
@@ -8,6 +8,103 @@
#include "chrome/renderer/render_view.h"
#include "webkit/glue/webframe.h"
+namespace bindings_utils {
+
+const char* kChromeHidden = "chromeHidden";
+
+struct SingletonData {
+ ContextList contexts;
+ PendingRequestMap pending_requests;
+};
+
+// ExtensionBase
+
+v8::Handle<v8::FunctionTemplate>
+ ExtensionBase::GetNativeFunction(v8::Handle<v8::String> name) {
+ if (name->Equals(v8::String::New("GetChromeHidden"))) {
+ return v8::FunctionTemplate::New(GetChromeHidden);
+ }
+
+ return v8::Handle<v8::FunctionTemplate>();
+}
+
+v8::Handle<v8::Value> ExtensionBase::GetChromeHidden(
+ const v8::Arguments& args) {
+ v8::Local<v8::Context> context = v8::Context::GetCurrent();
+ v8::Local<v8::Object> global = context->Global();
+ v8::Local<v8::Value> hidden = global->GetHiddenValue(
+ v8::String::New(kChromeHidden));
+
+ if (hidden.IsEmpty() || hidden->IsUndefined()) {
+ hidden = v8::Object::New();
+ global->SetHiddenValue(v8::String::New(kChromeHidden), hidden);
+ }
+
+ DCHECK(hidden->IsObject());
+ return hidden;
+}
+
+v8::Handle<v8::Value> ExtensionBase::StartRequest(
+ const v8::Arguments& args) {
+ // Get the current RenderView so that we can send a routed IPC message from
+ // the correct source.
+ RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
+ if (!renderview)
+ return v8::Undefined();
+
+ if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsInt32() ||
+ !args[2]->IsBoolean())
+ return v8::Undefined();
+
+ std::string name = *v8::String::AsciiValue(args.Data());
+ std::string json_args = *v8::String::Utf8Value(args[0]);
+ int request_id = args[1]->Int32Value();
+ bool has_callback = args[2]->BooleanValue();
+
+ v8::Persistent<v8::Context> current_context =
+ v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
+ DCHECK(!current_context.IsEmpty());
+ GetPendingRequestMap()[request_id].reset(new PendingRequest(
+ current_context, *v8::String::AsciiValue(args.Data())));
+
+ renderview->SendExtensionRequest(name, json_args, request_id, has_callback);
+
+ return v8::Undefined();
+}
+
+ContextList& GetContexts() {
+ return Singleton<SingletonData>::get()->contexts;
+}
+
+ContextList GetContextsForExtension(const std::string& extension_id) {
+ ContextList& all_contexts = GetContexts();
+ ContextList contexts;
+
+ for (ContextList::iterator it = all_contexts.begin();
+ it != all_contexts.end(); ++it) {
+ if ((*it)->extension_id == extension_id)
+ contexts.push_back(*it);
+ }
+
+ return contexts;
+}
+
+ContextList::iterator FindContext(v8::Handle<v8::Context> context) {
+ ContextList& all_contexts = GetContexts();
+
+ ContextList::iterator it = all_contexts.begin();
+ for (; it != all_contexts.end(); ++it) {
+ if ((*it)->context == context)
+ break;
+ }
+
+ return it;
+}
+
+PendingRequestMap& GetPendingRequestMap() {
+ return Singleton<SingletonData>::get()->pending_requests;
+}
+
RenderView* GetRenderViewForCurrentContext() {
WebFrame* webframe = WebFrame::RetrieveFrameForCurrentContext();
DCHECK(webframe) << "RetrieveCurrentFrame called when not in a V8 context.";
@@ -29,18 +126,23 @@ void CallFunctionInContext(v8::Handle<v8::Context> context,
v8::Context::Scope context_scope(context);
// Look up the function name, which may be a sub-property like
- // "chrome.handleResponse_" in the global variable.
- v8::Local<v8::Value> value = context->Global();
+ // "Port.dispatchOnMessage" in the hidden global variable.
+ v8::Local<v8::Value> value =
+ context->Global()->GetHiddenValue(v8::String::New(kChromeHidden));
std::vector<std::string> components;
SplitStringDontTrim(function_name, '.', &components);
for (size_t i = 0; i < components.size(); ++i) {
- if (value->IsObject())
+ if (!value.IsEmpty() && value->IsObject())
value = value->ToObject()->Get(v8::String::New(components[i].c_str()));
}
- if (!value->IsFunction())
+ if (value.IsEmpty() || !value->IsFunction()) {
+ NOTREACHED();
return;
+ }
v8::Local<v8::Function> function = v8::Local<v8::Function>::Cast(value);
if (!function.IsEmpty())
function->Call(v8::Object::New(), argc, argv);
}
+
+} // namespace bindings_utils
diff --git a/chrome/renderer/extensions/bindings_utils.h b/chrome/renderer/extensions/bindings_utils.h
index b498cc5..1b21023 100644
--- a/chrome/renderer/extensions/bindings_utils.h
+++ b/chrome/renderer/extensions/bindings_utils.h
@@ -6,13 +6,44 @@
#define CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_
#include "app/resource_bundle.h"
+#include "base/linked_ptr.h"
#include "base/singleton.h"
#include "base/string_piece.h"
#include "v8/include/v8.h"
+#include <list>
#include <string>
class RenderView;
+class WebFrame;
+
+namespace bindings_utils {
+
+// This is a base class for chrome extension bindings. Common features that
+// are shared by different modules go here.
+class ExtensionBase : public v8::Extension {
+ public:
+ ExtensionBase(const char* name,
+ const char* source,
+ int dep_count,
+ const char** deps)
+ : v8::Extension(name, source, dep_count, deps) {}
+
+ // Derived classes should call this at the end of their implementation in
+ // order to expose common native functions, like GetChromeHidden, to the
+ // v8 extension.
+ virtual v8::Handle<v8::FunctionTemplate>
+ GetNativeFunction(v8::Handle<v8::String> name);
+
+ protected:
+ // Returns a hidden variable for use by the bindings that is unreachable
+ // by the page.
+ static v8::Handle<v8::Value> GetChromeHidden(const v8::Arguments& args);
+
+ // Starts an API request to the browser, with an optional callback. The
+ // callback will be dispatched to EventBindings::HandleResponse.
+ static v8::Handle<v8::Value> StartRequest(const v8::Arguments& args);
+};
template<int kResourceId>
struct StringResourceTemplate {
@@ -29,15 +60,51 @@ const char* GetStringResource() {
Singleton< StringResourceTemplate<kResourceId> >::get()->resource.c_str();
}
+// Contains information about a single javascript context.
+struct ContextInfo {
+ v8::Persistent<v8::Context> context;
+ std::string extension_id; // empty if the context is not an extension
+
+ ContextInfo(v8::Persistent<v8::Context> context,
+ const std::string& extension_id)
+ : context(context), extension_id(extension_id) {}
+};
+typedef std::list< linked_ptr<ContextInfo> > ContextList;
+
+// Returns a mutable reference to the ContextList.
+ContextList& GetContexts();
+
+// Returns a (copied) list of contexts that have the given extension_id.
+ContextList GetContextsForExtension(const std::string& extension_id);
+
+// Returns the ContextInfo item that has the given context.
+ContextList::iterator FindContext(v8::Handle<v8::Context> context);
+
+// Contains info relevant to a pending API request.
+struct PendingRequest {
+ public :
+ PendingRequest(v8::Persistent<v8::Context> context, const std::string& name)
+ : context(context), name(name) {
+ }
+ v8::Persistent<v8::Context> context;
+ std::string name;
+};
+typedef std::map<int, linked_ptr<PendingRequest> > PendingRequestMap;
+
+// Returns a mutable reference to the PendingRequestMap.
+PendingRequestMap& GetPendingRequestMap();
+
// Returns the current RenderView, based on which V8 context is current. It is
// an error to call this when not in a V8 context.
RenderView* GetRenderViewForCurrentContext();
// Call the named javascript function with the given arguments in a context.
-// The function name should be reachable from the global object, and can be a
-// sub-property like "chrome.handleResponse_".
+// The function name should be reachable from the chromeHidden object, and can
+// be a sub-property like "Port.dispatchOnMessage".
void CallFunctionInContext(v8::Handle<v8::Context> context,
const std::string& function_name, int argc,
v8::Handle<v8::Value>* argv);
+} // namespace bindings_utils
+
#endif // CHROME_RENDERER_EXTENSIONS_BINDINGS_UTILS_H_
diff --git a/chrome/renderer/extensions/event_bindings.cc b/chrome/renderer/extensions/event_bindings.cc
index 7c988e2..240c330 100644
--- a/chrome/renderer/extensions/event_bindings.cc
+++ b/chrome/renderer/extensions/event_bindings.cc
@@ -7,32 +7,37 @@
#include "base/basictypes.h"
#include "base/singleton.h"
#include "chrome/common/render_messages.h"
+#include "chrome/common/url_constants.h"
#include "chrome/renderer/extensions/bindings_utils.h"
#include "chrome/renderer/extensions/event_bindings.h"
#include "chrome/renderer/js_only_v8_extensions.h"
#include "chrome/renderer/render_thread.h"
+#include "chrome/renderer/render_view.h"
#include "grit/renderer_resources.h"
#include "webkit/glue/webframe.h"
+using bindings_utils::CallFunctionInContext;
+using bindings_utils::ContextInfo;
+using bindings_utils::ContextList;
+using bindings_utils::GetContexts;
+using bindings_utils::GetStringResource;
+using bindings_utils::ExtensionBase;
+using bindings_utils::GetPendingRequestMap;
+using bindings_utils::PendingRequest;
+using bindings_utils::PendingRequestMap;
+
namespace {
// Keep a local cache of RenderThread so that we can mock it out for unit tests.
static RenderThreadBase* render_thread = NULL;
-static RenderThreadBase* GetRenderThread() {
- return render_thread ? render_thread : RenderThread::current();
-}
+// Set to true if these bindings are registered. Will be false when extensions
+// are disabled.
+static bool bindings_registered = false;
-// Keep a list of contexts that have registered themselves with us. This lets
-// us know where to dispatch events when we receive them.
-typedef std::list< v8::Persistent<v8::Context> > ContextList;
struct ExtensionData {
- ContextList contexts;
std::map<std::string, int> listener_count;
};
-ContextList& GetRegisteredContexts() {
- return Singleton<ExtensionData>::get()->contexts;
-}
int EventIncrementListenerCount(const std::string& event_name) {
ExtensionData *data = Singleton<ExtensionData>::get();
return ++(data->listener_count[event_name]);
@@ -42,12 +47,10 @@ int EventDecrementListenerCount(const std::string& event_name) {
return --(data->listener_count[event_name]);
}
-const char* kContextAttachCount = "chromium.attachCount";
-
-class ExtensionImpl : public v8::Extension {
+class ExtensionImpl : public ExtensionBase {
public:
ExtensionImpl()
- : v8::Extension(EventBindings::kName,
+ : ExtensionBase(EventBindings::kName,
GetStringResource<IDR_EVENT_BINDINGS_JS>(),
0, NULL) {
}
@@ -59,8 +62,10 @@ class ExtensionImpl : public v8::Extension {
return v8::FunctionTemplate::New(AttachEvent);
} else if (name->Equals(v8::String::New("DetachEvent"))) {
return v8::FunctionTemplate::New(DetachEvent);
+ } else if (name->Equals(v8::String::New("GetNextRequestId"))) {
+ return v8::FunctionTemplate::New(GetNextRequestId);
}
- return v8::Handle<v8::FunctionTemplate>();
+ return ExtensionBase::GetNativeFunction(name);
}
// Attach an event name to an object.
@@ -69,30 +74,10 @@ class ExtensionImpl : public v8::Extension {
// TODO(erikkay) should enforce that event name is a string in the bindings
DCHECK(args[0]->IsString() || args[0]->IsUndefined());
- v8::Persistent<v8::Context> context =
- v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
- v8::Local<v8::Object> global = context->Global();
-
- // Remember how many times this context has been attached, so we can
- // register the context on first attach and unregister on last detach.
- v8::Local<v8::Value> attach_count = global->GetHiddenValue(
- v8::String::New(kContextAttachCount));
- int32_t account_count_value =
- (!attach_count.IsEmpty() && attach_count->IsNumber()) ?
- attach_count->Int32Value() : 0;
- if (account_count_value == 0) {
- // First time attaching.
- GetRegisteredContexts().push_back(context);
- context.MakeWeak(NULL, WeakContextCallback);
- }
- global->SetHiddenValue(
- v8::String::New(kContextAttachCount),
- v8::Integer::New(account_count_value + 1));
-
if (args[0]->IsString()) {
std::string event_name(*v8::String::AsciiValue(args[0]));
if (EventIncrementListenerCount(event_name) == 1) {
- GetRenderThread()->Send(
+ EventBindings::GetRenderThread()->Send(
new ViewHostMsg_ExtensionAddListener(event_name));
}
}
@@ -105,25 +90,10 @@ class ExtensionImpl : public v8::Extension {
// TODO(erikkay) should enforce that event name is a string in the bindings
DCHECK(args[0]->IsString() || args[0]->IsUndefined());
- v8::Local<v8::Context> context = v8::Context::GetCurrent();
- v8::Local<v8::Object> global = context->Global();
- v8::Local<v8::Value> attach_count = global->GetHiddenValue(
- v8::String::New(kContextAttachCount));
- DCHECK(!attach_count.IsEmpty() && attach_count->IsNumber());
- int32_t account_count_value = attach_count->Int32Value();
- DCHECK(account_count_value > 0);
- if (account_count_value == 1) {
- // Clean up after last detach.
- UnregisterContext(context);
- }
- global->SetHiddenValue(
- v8::String::New(kContextAttachCount),
- v8::Integer::New(account_count_value - 1));
-
if (args[0]->IsString()) {
std::string event_name(*v8::String::AsciiValue(args[0]));
if (EventDecrementListenerCount(event_name) == 0) {
- GetRenderThread()->Send(
+ EventBindings::GetRenderThread()->Send(
new ViewHostMsg_ExtensionRemoveListener(event_name));
}
}
@@ -131,24 +101,9 @@ class ExtensionImpl : public v8::Extension {
return v8::Undefined();
}
- // Called when a registered context is garbage collected.
- static void UnregisterContext(v8::Handle<void> context) {
- ContextList& contexts = GetRegisteredContexts();
- ContextList::iterator it = std::find(contexts.begin(), contexts.end(),
- context);
- if (it == contexts.end()) {
- NOTREACHED();
- return;
- }
-
- it->Dispose();
- it->Clear();
- contexts.erase(it);
- }
-
- // Called when a registered context is garbage collected.
- static void WeakContextCallback(v8::Persistent<v8::Value> obj, void*) {
- UnregisterContext(obj);
+ static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) {
+ static int next_request_id = 0;
+ return v8::Integer::New(next_request_id++);
}
};
@@ -157,6 +112,7 @@ class ExtensionImpl : public v8::Extension {
const char* EventBindings::kName = "chrome/EventBindings";
v8::Extension* EventBindings::Get() {
+ bindings_registered = true;
return new ExtensionImpl();
}
@@ -166,25 +122,90 @@ void EventBindings::SetRenderThread(RenderThreadBase* thread) {
}
// static
+RenderThreadBase* EventBindings::GetRenderThread() {
+ return render_thread ? render_thread : RenderThread::current();
+}
+
void EventBindings::HandleContextCreated(WebFrame* frame) {
+ if (!bindings_registered)
+ return;
+
v8::HandleScope handle_scope;
v8::Local<v8::Context> context = frame->GetScriptContext();
DCHECK(!context.IsEmpty());
- // TODO(mpcomplete): register it
+ DCHECK(bindings_utils::FindContext(context) == GetContexts().end());
+
+ GURL url = frame->GetView()->GetMainFrame()->GetURL();
+ std::string extension_id;
+ if (url.SchemeIs(chrome::kExtensionScheme))
+ extension_id = url.host();
+
+ v8::Persistent<v8::Context> persistent_context =
+ v8::Persistent<v8::Context>::New(context);
+ GetContexts().push_back(linked_ptr<ContextInfo>(
+ new ContextInfo(persistent_context, extension_id)));
}
// static
void EventBindings::HandleContextDestroyed(WebFrame* frame) {
+ if (!bindings_registered)
+ return;
+
v8::HandleScope handle_scope;
v8::Local<v8::Context> context = frame->GetScriptContext();
DCHECK(!context.IsEmpty());
- // TODO(mpcomplete): unregister it, dispatch event
+
+ ContextList::iterator it = bindings_utils::FindContext(context);
+ DCHECK(it != GetContexts().end());
+
+ // Notify the bindings that they're going away.
+ CallFunctionInContext(context, "dispatchOnUnload", 0, NULL);
+
+ // Remove all pending requests for this context.
+ PendingRequestMap& pending_requests = GetPendingRequestMap();
+ for (PendingRequestMap::iterator it = pending_requests.begin();
+ it != pending_requests.end(); ) {
+ PendingRequestMap::iterator current = it++;
+ if (current->second->context == context) {
+ current->second->context.Dispose();
+ current->second->context.Clear();
+ pending_requests.erase(current);
+ }
+ }
+
+ // Remove it from our registered contexts.
+ (*it)->context.Dispose();
+ (*it)->context.Clear();
+ GetContexts().erase(it);
}
+// static
void EventBindings::CallFunction(const std::string& function_name,
int argc, v8::Handle<v8::Value>* argv) {
- for (ContextList::iterator it = GetRegisteredContexts().begin();
- it != GetRegisteredContexts().end(); ++it) {
- CallFunctionInContext(*it, function_name, argc, argv);
+ v8::HandleScope handle_scope;
+ for (ContextList::iterator it = GetContexts().begin();
+ it != GetContexts().end(); ++it) {
+ CallFunctionInContext((*it)->context, function_name, argc, argv);
}
}
+
+// static
+void EventBindings::HandleResponse(int request_id, bool success,
+ const std::string& response,
+ const std::string& error) {
+ PendingRequest* request = GetPendingRequestMap()[request_id].get();
+ if (!request)
+ return; // The frame went away.
+
+ v8::HandleScope handle_scope;
+ v8::Handle<v8::Value> argv[5];
+ argv[0] = v8::Integer::New(request_id);
+ argv[1] = v8::String::New(request->name.c_str());
+ argv[2] = v8::Boolean::New(success);
+ argv[3] = v8::String::New(response.c_str());
+ argv[4] = v8::String::New(error.c_str());
+ CallFunctionInContext(
+ request->context, "handleResponse", arraysize(argv), argv);
+
+ GetPendingRequestMap().erase(request_id);
+}
diff --git a/chrome/renderer/extensions/event_bindings.h b/chrome/renderer/extensions/event_bindings.h
index a026c19..f2c5f88 100644
--- a/chrome/renderer/extensions/event_bindings.h
+++ b/chrome/renderer/extensions/event_bindings.h
@@ -20,6 +20,7 @@ class EventBindings {
// Allow RenderThread to be mocked out.
static void SetRenderThread(RenderThreadBase* thread);
+ static RenderThreadBase* GetRenderThread();
// Handle a script context coming / going away.
static void HandleContextCreated(WebFrame* frame);
@@ -30,6 +31,11 @@ class EventBindings {
// "chromium.Event.dispatch_".
static void CallFunction(const std::string& function_name, int argc,
v8::Handle<v8::Value>* argv);
+
+ // Handles a response to an API request.
+ static void HandleResponse(int request_id, bool success,
+ const std::string& response,
+ const std::string& error);
};
#endif // CHROME_RENDERER_EXTENSIONS_EVENT_BINDINGS_H_
diff --git a/chrome/renderer/extensions/extension_api_client_unittest.cc b/chrome/renderer/extensions/extension_api_client_unittest.cc
index dc37c5f..9638b27 100644
--- a/chrome/renderer/extensions/extension_api_client_unittest.cc
+++ b/chrome/renderer/extensions/extension_api_client_unittest.cc
@@ -7,7 +7,7 @@
#include "base/string_util.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/render_messages.h"
-#include "chrome/renderer/extensions/extension_process_bindings.h"
+#include "chrome/renderer/extensions/event_bindings.h"
#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/test/render_view_test.h"
#include "testing/gtest/include/gtest/gtest.h"
@@ -91,8 +91,7 @@ TEST_F(ExtensionAPIClientTest, CallbackDispatching) {
ASSERT_GE(callback_id, 0);
// Now send the callback a response
- ExtensionProcessBindings::HandleResponse(
- callback_id, true, "{\"foo\":\"bar\"}", "");
+ EventBindings::HandleResponse(callback_id, true, "{\"foo\":\"bar\"}", "");
// And verify that it worked
ASSERT_EQ("pass", GetConsoleMessage());
diff --git a/chrome/renderer/extensions/extension_process_bindings.cc b/chrome/renderer/extensions/extension_process_bindings.cc
index 1ce7ea2..6b568a1 100644
--- a/chrome/renderer/extensions/extension_process_bindings.cc
+++ b/chrome/renderer/extensions/extension_process_bindings.cc
@@ -5,7 +5,6 @@
#include "chrome/renderer/extensions/extension_process_bindings.h"
#include "base/singleton.h"
-#include "base/stl_util-inl.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/url_constants.h"
#include "chrome/renderer/extensions/bindings_utils.h"
@@ -14,11 +13,13 @@
#include "chrome/renderer/js_only_v8_extensions.h"
#include "chrome/renderer/render_view.h"
#include "grit/renderer_resources.h"
-#include "webkit/api/public/WebScriptSource.h"
#include "webkit/glue/webframe.h"
-using WebKit::WebScriptSource;
-using WebKit::WebString;
+using bindings_utils::GetStringResource;
+using bindings_utils::ContextInfo;
+using bindings_utils::ContextList;
+using bindings_utils::GetContexts;
+using bindings_utils::ExtensionBase;
namespace {
@@ -30,48 +31,17 @@ const char* kExtensionDeps[] = {
RendererExtensionBindings::kName,
};
-// Types for storage of per-renderer-singleton data structure that maps
-// |extension_id| -> <List of v8 Contexts for the "views" of that extension>
-typedef std::list< v8::Persistent<v8::Context> > ContextList;
-typedef std::map<std::string, ContextList> ExtensionIdContextsMap;
-
-// Contains info relevant to a pending request.
-struct CallContext {
- public :
- CallContext(v8::Persistent<v8::Context> context, const std::string& name)
- : context(context), name(name) {
- }
- v8::Persistent<v8::Context> context;
- std::string name;
-};
-typedef std::map<int, CallContext*> PendingRequestMap;
-
struct SingletonData {
std::set<std::string> function_names_;
- ExtensionIdContextsMap contexts;
- PendingRequestMap pending_requests;
-
- ~SingletonData() {
- STLDeleteContainerPairSecondPointers(pending_requests.begin(),
- pending_requests.end());
- }
};
static std::set<std::string>* GetFunctionNameSet() {
return &Singleton<SingletonData>()->function_names_;
}
-static ContextList& GetRegisteredContexts(std::string extension_id) {
- return Singleton<SingletonData>::get()->contexts[extension_id];
-}
-
-static PendingRequestMap& GetPendingRequestMap() {
- return Singleton<SingletonData>::get()->pending_requests;
-}
-
-class ExtensionImpl : public v8::Extension {
+class ExtensionImpl : public ExtensionBase {
public:
- ExtensionImpl() : v8::Extension(
+ ExtensionImpl() : ExtensionBase(
kExtensionName, GetStringResource<IDR_EXTENSION_PROCESS_BINDINGS_JS>(),
arraysize(kExtensionDeps), kExtensionDeps) {}
@@ -86,86 +56,31 @@ class ExtensionImpl : public v8::Extension {
v8::Handle<v8::String> name) {
std::set<std::string>* names = GetFunctionNameSet();
- if (name->Equals(v8::String::New("GetNextRequestId")))
- return v8::FunctionTemplate::New(GetNextRequestId);
- else if (name->Equals(v8::String::New("RegisterExtension")))
- return v8::FunctionTemplate::New(RegisterExtension);
- else if (name->Equals(v8::String::New("UnregisterExtension")))
- return v8::FunctionTemplate::New(UnregisterExtension);
- else if (name->Equals(v8::String::New("GetViews")))
+ if (name->Equals(v8::String::New("GetViews"))) {
return v8::FunctionTemplate::New(GetViews);
- else if (names->find(*v8::String::AsciiValue(name)) != names->end())
- return v8::FunctionTemplate::New(StartRequest, name);
-
- return v8::Handle<v8::FunctionTemplate>();
- }
-
- private:
- static v8::Handle<v8::Value> RegisterExtension(const v8::Arguments& args) {
- RenderView* renderview = GetRenderViewForCurrentContext();
- DCHECK(renderview);
- GURL url = renderview->webview()->GetMainFrame()->GetURL();
- DCHECK(url.scheme() == chrome::kExtensionScheme);
-
- v8::Persistent<v8::Context> current_context =
- v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
- DCHECK(!current_context.IsEmpty());
-
- std::string extension_id = url.host();
- GetRegisteredContexts(extension_id).push_back(current_context);
- return v8::String::New(extension_id.c_str());
- }
-
- static v8::Handle<v8::Value> UnregisterExtension(const v8::Arguments& args) {
- DCHECK_EQ(args.Length(), 1);
- DCHECK(args[0]->IsString());
-
- v8::Local<v8::Context> current_context = v8::Context::GetCurrent();
- DCHECK(!current_context.IsEmpty());
-
- // Remove all pending requests for this context.
- PendingRequestMap& pending_requests = GetPendingRequestMap();
- for (PendingRequestMap::iterator it = pending_requests.begin();
- it != pending_requests.end(); ) {
- PendingRequestMap::iterator current = it++;
- if (current->second->context == current_context) {
- current->second->context.Dispose();
- current->second->context.Clear();
- delete current->second;
- pending_requests.erase(current);
- }
+ } else if (names->find(*v8::String::AsciiValue(name)) != names->end()) {
+ return v8::FunctionTemplate::New(ExtensionBase::StartRequest, name);
}
- std::string extension_id(*v8::String::Utf8Value(args[0]));
- ContextList& contexts = GetRegisteredContexts(extension_id);
- ContextList::iterator it = std::find(contexts.begin(), contexts.end(),
- current_context);
- if (it == contexts.end()) {
- NOTREACHED();
- return v8::Undefined();
- }
-
- it->Dispose();
- it->Clear();
- contexts.erase(it);
-
- return v8::Undefined();
+ return ExtensionBase::GetNativeFunction(name);
}
+ private:
static v8::Handle<v8::Value> GetViews(const v8::Arguments& args) {
- RenderView* renderview = GetRenderViewForCurrentContext();
+ RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
DCHECK(renderview);
GURL url = renderview->webview()->GetMainFrame()->GetURL();
std::string extension_id = url.host();
- ContextList& contexts = GetRegisteredContexts(extension_id);
+ ContextList contexts =
+ bindings_utils::GetContextsForExtension(extension_id);
DCHECK(contexts.size() > 0);
v8::Local<v8::Array> views = v8::Array::New(contexts.size());
int index = 0;
ContextList::const_iterator it = contexts.begin();
for (; it != contexts.end(); ++it) {
- v8::Local<v8::Value> window = (*it)->Global()->Get(
+ v8::Local<v8::Value> window = (*it)->context->Global()->Get(
v8::String::New("window"));
DCHECK(!window.IsEmpty());
views->Set(v8::Integer::New(index), window);
@@ -173,38 +88,6 @@ class ExtensionImpl : public v8::Extension {
}
return views;
}
-
- static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) {
- static int next_request_id = 0;
- return v8::Integer::New(next_request_id++);
- }
-
- static v8::Handle<v8::Value> StartRequest(const v8::Arguments& args) {
- // Get the current RenderView so that we can send a routed IPC message from
- // the correct source.
- RenderView* renderview = GetRenderViewForCurrentContext();
- if (!renderview)
- return v8::Undefined();
-
- if (args.Length() != 3 || !args[0]->IsString() || !args[1]->IsInt32() ||
- !args[2]->IsBoolean())
- return v8::Undefined();
-
- std::string name = *v8::String::AsciiValue(args.Data());
- std::string json_args = *v8::String::Utf8Value(args[0]);
- int request_id = args[1]->Int32Value();
- bool has_callback = args[2]->BooleanValue();
-
- v8::Persistent<v8::Context> current_context =
- v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
- DCHECK(!current_context.IsEmpty());
- GetPendingRequestMap()[request_id] =
- new CallContext(current_context, *v8::String::AsciiValue(args.Data()));
-
- renderview->SendExtensionRequest(name, json_args, request_id, has_callback);
-
- return v8::Undefined();
- }
};
} // namespace
@@ -217,29 +100,3 @@ void ExtensionProcessBindings::SetFunctionNames(
const std::vector<std::string>& names) {
ExtensionImpl::SetFunctionNames(names);
}
-
-void ExtensionProcessBindings::RegisterExtensionContext(WebFrame* frame) {
- frame->ExecuteScript(WebScriptSource(WebString::fromUTF8(
- "chrome.self.register_();")));
-}
-
-void ExtensionProcessBindings::HandleResponse(int request_id, bool success,
- const std::string& response,
- const std::string& error) {
- CallContext* call = GetPendingRequestMap()[request_id];
- if (!call)
- return; // The frame went away.
-
- v8::HandleScope handle_scope;
- v8::Handle<v8::Value> argv[5];
- argv[0] = v8::Integer::New(request_id);
- argv[1] = v8::String::New(call->name.c_str());
- argv[2] = v8::Boolean::New(success);
- argv[3] = v8::String::New(response.c_str());
- argv[4] = v8::String::New(error.c_str());
- CallFunctionInContext(call->context, "chrome.handleResponse_",
- arraysize(argv), argv);
-
- GetPendingRequestMap().erase(request_id);
- delete call;
-}
diff --git a/chrome/renderer/extensions/extension_process_bindings.h b/chrome/renderer/extensions/extension_process_bindings.h
index 9639090..f703914 100644
--- a/chrome/renderer/extensions/extension_process_bindings.h
+++ b/chrome/renderer/extensions/extension_process_bindings.h
@@ -18,10 +18,6 @@ class ExtensionProcessBindings {
public:
static void SetFunctionNames(const std::vector<std::string>& names);
static v8::Extension* Get();
- static void RegisterExtensionContext(WebFrame* frame);
- static void HandleResponse(int request_id, bool success,
- const std::string& response,
- const std::string& error);
};
#endif // CHROME_RENDERER_EXTENSIONS_EXTENSION_PROCESS_BINDINGS_H_
diff --git a/chrome/renderer/extensions/renderer_extension_bindings.cc b/chrome/renderer/extensions/renderer_extension_bindings.cc
index 0931c8c..f719797 100644
--- a/chrome/renderer/extensions/renderer_extension_bindings.cc
+++ b/chrome/renderer/extensions/renderer_extension_bindings.cc
@@ -14,6 +14,9 @@
#include "chrome/renderer/render_view.h"
#include "grit/renderer_resources.h"
+using bindings_utils::GetStringResource;
+using bindings_utils::ExtensionBase;
+
// Message passing API example (in a content script):
// var extension =
// new chrome.Extension('00123456789abcdef0123456789abcdef0123456');
@@ -28,10 +31,10 @@ namespace {
const char* kExtensionDeps[] = { EventBindings::kName };
-class ExtensionImpl : public v8::Extension {
+class ExtensionImpl : public ExtensionBase {
public:
ExtensionImpl()
- : v8::Extension(RendererExtensionBindings::kName,
+ : ExtensionBase(RendererExtensionBindings::kName,
GetStringResource<IDR_RENDERER_EXTENSION_BINDINGS_JS>(),
arraysize(kExtensionDeps), kExtensionDeps) {
}
@@ -44,7 +47,7 @@ class ExtensionImpl : public v8::Extension {
} else if (name->Equals(v8::String::New("PostMessage"))) {
return v8::FunctionTemplate::New(PostMessage);
}
- return v8::Handle<v8::FunctionTemplate>();
+ return ExtensionBase::GetNativeFunction(name);
}
// Creates a new messaging channel to the given extension.
@@ -52,7 +55,7 @@ class ExtensionImpl : public v8::Extension {
const v8::Arguments& args) {
// Get the current RenderView so that we can send a routed IPC message from
// the correct source.
- RenderView* renderview = GetRenderViewForCurrentContext();
+ RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
if (!renderview)
return v8::Undefined();
@@ -68,7 +71,7 @@ class ExtensionImpl : public v8::Extension {
// Sends a message along the given channel.
static v8::Handle<v8::Value> PostMessage(const v8::Arguments& args) {
- RenderView* renderview = GetRenderViewForCurrentContext();
+ RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
if (!renderview)
return v8::Undefined();
diff --git a/chrome/renderer/js_only_v8_extensions.cc b/chrome/renderer/js_only_v8_extensions.cc
index 577f170..6d94b92 100644
--- a/chrome/renderer/js_only_v8_extensions.cc
+++ b/chrome/renderer/js_only_v8_extensions.cc
@@ -8,6 +8,8 @@
#include "grit/renderer_resources.h"
#include "grit/webkit_resources.h"
+using bindings_utils::GetStringResource;
+
// BaseJsV8Extension
const char* BaseJsV8Extension::kName = "chrome/base";
v8::Extension* BaseJsV8Extension::Get() {
diff --git a/chrome/renderer/render_view.cc b/chrome/renderer/render_view.cc
index f62e9a8..b2c505a 100644
--- a/chrome/renderer/render_view.cc
+++ b/chrome/renderer/render_view.cc
@@ -36,7 +36,6 @@
#include "chrome/renderer/devtools_agent.h"
#include "chrome/renderer/devtools_client.h"
#include "chrome/renderer/extensions/event_bindings.h"
-#include "chrome/renderer/extensions/extension_process_bindings.h"
#include "chrome/renderer/localized_error.h"
#include "chrome/renderer/media/audio_renderer_impl.h"
#include "chrome/renderer/media/buffered_data_source.h"
@@ -1458,12 +1457,6 @@ void RenderView::DocumentElementAvailable(WebFrame* frame) {
if (frame->GetURL().SchemeIs(chrome::kExtensionScheme))
frame->GrantUniversalAccess();
- // Tell extensions to self-register their js contexts.
- // TODO(rafaelw): This is kind of gross. We need a way to call through
- // the glue layer to retrieve the current v8::Context.
- if (frame->GetURL().SchemeIs(chrome::kExtensionScheme))
- ExtensionProcessBindings::RegisterExtensionContext(frame);
-
if (RenderThread::current()) // Will be NULL during unit tests.
RenderThread::current()->user_script_slave()->InjectScripts(
frame, UserScript::DOCUMENT_START);
@@ -2824,8 +2817,7 @@ void RenderView::OnExtensionResponse(int request_id,
bool success,
const std::string& response,
const std::string& error) {
- ExtensionProcessBindings::HandleResponse(request_id, success, response,
- error);
+ EventBindings::HandleResponse(request_id, success, response, error);
}
// Dump all load time histograms.
diff --git a/chrome/renderer/renderer_resources.grd b/chrome/renderer/renderer_resources.grd
index ab04582..874f57a 100644
--- a/chrome/renderer/renderer_resources.grd
+++ b/chrome/renderer/renderer_resources.grd
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- This comment is only here because changes to resources are not picked up
-without changes to the corresponding grd file. 0 -->
+without changes to the corresponding grd file. mp2 -->
<grit latest_public_release="0" current_release="1">
<outputs>
<output filename="grit/renderer_resources.h" type="rc_header">
diff --git a/chrome/renderer/resources/event_bindings.js b/chrome/renderer/resources/event_bindings.js
index f11ec78..78a1ceb 100644
--- a/chrome/renderer/resources/event_bindings.js
+++ b/chrome/renderer/resources/event_bindings.js
@@ -1,5 +1,5 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
+// Copyright (c) 2009 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.
// -----------------------------------------------------------------------------
@@ -9,8 +9,12 @@
var chrome = chrome || {};
(function () {
+ native function GetChromeHidden();
native function AttachEvent(eventName);
native function DetachEvent(eventName);
+ native function GetNextRequestId();
+
+ var chromeHidden = GetChromeHidden();
// Event object. If opt_eventName is provided, this object represents
// the unique instance of that named event, and dispatching an event
@@ -19,7 +23,7 @@ var chrome = chrome || {};
// Example:
// chrome.tabs.onChanged = new chrome.Event("tab-changed");
// chrome.tabs.onChanged.addListener(function(data) { alert(data); });
- // chrome.Event.dispatch_("tab-changed", "hi");
+ // chromeHidden.Event.dispatch("tab-changed", "hi");
// will result in an alert dialog that says 'hi'.
chrome.Event = function(opt_eventName) {
this.eventName_ = opt_eventName;
@@ -27,26 +31,31 @@ var chrome = chrome || {};
};
// A map of event names to the event object that is registered to that name.
- chrome.Event.attached_ = {};
+ var attachedNamedEvents = {};
+
+ // An array of all attached event objects, used for detaching on unload.
+ var allAttachedEvents = [];
+
+ chromeHidden.Event = {};
// Dispatches a named event with the given JSON array, which is deserialized
// before dispatch. The JSON array is the list of arguments that will be
// sent with the event callback.
- chrome.Event.dispatchJSON_ = function(name, args) {
- if (chrome.Event.attached_[name]) {
+ chromeHidden.Event.dispatchJSON = function(name, args) {
+ if (attachedNamedEvents[name]) {
if (args) {
args = JSON.parse(args);
}
- chrome.Event.attached_[name].dispatch.apply(
- chrome.Event.attached_[name], args);
+ attachedNamedEvents[name].dispatch.apply(
+ attachedNamedEvents[name], args);
}
};
// Dispatches a named event with the given arguments, supplied as an array.
- chrome.Event.dispatch_ = function(name, args) {
- if (chrome.Event.attached_[name]) {
- chrome.Event.attached_[name].dispatch.apply(
- chrome.Event.attached_[name], args);
+ chromeHidden.Event.dispatch = function(name, args) {
+ if (attachedNamedEvents[name]) {
+ attachedNamedEvents[name].dispatch.apply(
+ attachedNamedEvents[name], args);
}
};
@@ -105,31 +114,82 @@ var chrome = chrome || {};
// name.
chrome.Event.prototype.attach_ = function() {
AttachEvent(this.eventName_);
- this.unloadHandler_ = this.detach_.bind(this);
- window.addEventListener('unload', this.unloadHandler_, false);
+ allAttachedEvents[allAttachedEvents.length] = this;
if (!this.eventName_)
return;
- if (chrome.Event.attached_[this.eventName_]) {
+ if (attachedNamedEvents[this.eventName_]) {
throw new Error("chrome.Event '" + this.eventName_ +
"' is already attached.");
}
- chrome.Event.attached_[this.eventName_] = this;
+ attachedNamedEvents[this.eventName_] = this;
};
// Detaches this event object from its name.
chrome.Event.prototype.detach_ = function() {
- window.removeEventListener('unload', this.unloadHandler_, false);
+ var i = allAttachedEvents.indexOf(this);
+ if (i >= 0)
+ delete allAttachedEvents[i];
DetachEvent(this.eventName_);
if (!this.eventName_)
return;
- if (!chrome.Event.attached_[this.eventName_]) {
+ if (!attachedNamedEvents[this.eventName_]) {
throw new Error("chrome.Event '" + this.eventName_ +
"' is not attached.");
}
- delete chrome.Event.attached_[this.eventName_];
+ delete attachedNamedEvents[this.eventName_];
};
+
+ // Callback handling.
+ var callbacks = [];
+ chromeHidden.handleResponse = function(requestId, name,
+ success, response, error) {
+ try {
+ if (!success) {
+ if (!error)
+ error = "Unknown error."
+ console.error("Error during " + name + ": " + error);
+ return;
+ }
+
+ if (callbacks[requestId]) {
+ if (response) {
+ callbacks[requestId](JSON.parse(response));
+ } else {
+ callbacks[requestId]();
+ }
+ }
+ } finally {
+ delete callbacks[requestId];
+ }
+ };
+
+ // Send an API request and optionally register a callback.
+ chromeHidden.sendRequest = function(request, args, callback) {
+ // JSON.stringify doesn't support a root object which is undefined.
+ if (args === undefined)
+ args = null;
+ var sargs = JSON.stringify(args);
+ var requestId = GetNextRequestId();
+ var hasCallback = false;
+ if (callback) {
+ hasCallback = true;
+ callbacks[requestId] = callback;
+ }
+ request(sargs, requestId, hasCallback);
+ }
+
+ // Special unload event: we don't use the DOM unload because that slows
+ // down tab shutdown. On the other hand, this might not always fire, since
+ // Chrome will terminate renderers on shutdown (SuddenTermination).
+ chromeHidden.onUnload = new chrome.Event();
+
+ chromeHidden.dispatchOnUnload = function() {
+ chromeHidden.onUnload.dispatch();
+ for (var i in allAttachedEvents)
+ allAttachedEvents[i].detach_();
+ }
})();
diff --git a/chrome/renderer/resources/extension_process_bindings.js b/chrome/renderer/resources/extension_process_bindings.js
index 2cdf661..0fbec3f 100644
--- a/chrome/renderer/resources/extension_process_bindings.js
+++ b/chrome/renderer/resources/extension_process_bindings.js
@@ -9,9 +9,6 @@
var chrome;
(function() {
- native function GetNextRequestId();
- native function RegisterExtension();
- native function UnregisterExtension();
native function GetViews();
native function GetWindow();
native function GetCurrentWindow();
@@ -37,10 +34,13 @@ var chrome;
native function CreateBookmark();
native function MoveBookmark();
native function SetBookmarkTitle();
+ native function GetChromeHidden();
if (!chrome)
chrome = {};
+ var chromeHidden = GetChromeHidden();
+
// Validate arguments.
function validate(args, schemas) {
if (args.length > schemas.length)
@@ -72,45 +72,7 @@ var chrome;
}
}
- // Callback handling.
- // TODO(aa): This function should not be publicly exposed. Pass it into V8
- // instead and hold one per-context. See the way event_bindings.js works.
- var callbacks = [];
- chrome.handleResponse_ = function(requestId, name, success, response, error) {
- try {
- if (!success) {
- if (!error)
- error = "Unknown error."
- console.error("Error during " + name + ": " + error);
- return;
- }
-
- if (callbacks[requestId]) {
- if (response) {
- callbacks[requestId](JSON.parse(response));
- } else {
- callbacks[requestId]();
- }
- }
- } finally {
- delete callbacks[requestId];
- }
- };
-
- // Send an API request and optionally register a callback.
- function sendRequest(request, args, callback) {
- // JSON.stringify doesn't support a root object which is undefined.
- if (args === undefined)
- args = null;
- var sargs = JSON.stringify(args);
- var requestId = GetNextRequestId();
- var hasCallback = false;
- if (callback) {
- hasCallback = true;
- callbacks[requestId] = callback;
- }
- request(sargs, requestId, hasCallback);
- }
+ var sendRequest = chromeHidden.sendRequest;
//----------------------------------------------------------------------------
@@ -528,16 +490,7 @@ var chrome;
chrome.self = chrome.self || {};
chrome.self.onConnect = new chrome.Event("channel-connect");
- // Register
- chrome.self.register_ = function() {
- var extensionId = RegisterExtension();
- window.addEventListener('unload', function() {
- UnregisterExtension(extensionId); }, false);
- delete chrome.self.register_;
- }
-
chrome.self.getViews = function() {
return GetViews();
}
})();
-
diff --git a/chrome/renderer/resources/greasemonkey_api.js b/chrome/renderer/resources/greasemonkey_api.js
index 3ce1d38..0ad24f0 100644
--- a/chrome/renderer/resources/greasemonkey_api.js
+++ b/chrome/renderer/resources/greasemonkey_api.js
@@ -1,5 +1,5 @@
-// Copyright (c) 2009 The Chromium Authors. All rights reserved.
-// Use of this source code is governed by a BSD-style license that can be
+// Copyright (c) 2009 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.
// -----------------------------------------------------------------------------
diff --git a/chrome/renderer/resources/renderer_extension_bindings.js b/chrome/renderer/resources/renderer_extension_bindings.js
index 31b97ed..bf59671 100644
--- a/chrome/renderer/resources/renderer_extension_bindings.js
+++ b/chrome/renderer/resources/renderer_extension_bindings.js
@@ -11,44 +11,53 @@ var chrome = chrome || {};
(function () {
native function OpenChannelToExtension(id);
native function PostMessage(portId, msg);
+ native function GetChromeHidden();
+
+ var chromeHidden = GetChromeHidden();
+
+ // Map of port IDs to port object.
+ var ports = {};
// Port object. Represents a connection to another script context through
// which messages can be passed.
chrome.Port = function(portId) {
- if (chrome.Port.ports_[portId]) {
+ if (ports[portId]) {
throw new Error("Port '" + portId + "' already exists.");
}
this.portId_ = portId; // TODO(mpcomplete): readonly
this.onDisconnect = new chrome.Event();
this.onMessage = new chrome.Event();
- chrome.Port.ports_[portId] = this;
+ ports[portId] = this;
+
+ chromeHidden.onUnload.addListener(function() {
+ this.disconnect();
+ });
};
- // Map of port IDs to port object.
- chrome.Port.ports_ = {};
+ chromeHidden.Port = {};
// Called by native code when a channel has been opened to this context.
- chrome.Port.dispatchOnConnect_ = function(portId, tab) {
+ chromeHidden.Port.dispatchOnConnect = function(portId, tab) {
var port = new chrome.Port(portId);
if (tab) {
tab = JSON.parse(tab);
}
port.tab = tab;
- chrome.Event.dispatch_("channel-connect", [port]);
+ chromeHidden.Event.dispatch("channel-connect", [port]);
};
// Called by native code when a channel has been closed.
- chrome.Port.dispatchOnDisconnect_ = function(portId) {
- var port = chrome.Port.ports_[portId];
+ chromeHidden.Port.dispatchOnDisconnect = function(portId) {
+ var port = ports[portId];
if (port) {
port.onDisconnect.dispatch(port);
- delete chrome.Port.ports_[portId];
+ delete ports[portId];
}
};
// Called by native code when a message has been sent to the given port.
- chrome.Port.dispatchOnMessage_ = function(msg, portId) {
- var port = chrome.Port.ports_[portId];
+ chromeHidden.Port.dispatchOnMessage = function(msg, portId) {
+ var port = ports[portId];
if (port) {
if (msg) {
msg = JSON.parse(msg);
@@ -66,6 +75,12 @@ var chrome = chrome || {};
PostMessage(this.portId_, JSON.stringify(msg));
};
+ // Disconnects the port from the other end.
+ chrome.Port.prototype.disconnect = function() {
+ delete ports[this.portId_];
+ //CloseChannel(this.portId_); // TODO(mpcomplete)
+ }
+
// Extension object.
chrome.Extension = function(id) {
this.id_ = id;