| Index: chrome/renderer/extensions/chrome_webstore_bindings.cc
|
| diff --git a/chrome/renderer/extensions/chrome_webstore_bindings.cc b/chrome/renderer/extensions/chrome_webstore_bindings.cc
|
| index feba7c4c2fea107a72079e2c403cccb40f819e78..ecc4d57906335fea855ee3b582004a636cd07d62 100644
|
| --- a/chrome/renderer/extensions/chrome_webstore_bindings.cc
|
| +++ b/chrome/renderer/extensions/chrome_webstore_bindings.cc
|
| @@ -4,6 +4,7 @@
|
|
|
| #include "chrome/renderer/extensions/chrome_webstore_bindings.h"
|
|
|
| +#include "base/lazy_instance.h"
|
| #include "base/string_util.h"
|
| #include "chrome/common/extensions/extension.h"
|
| #include "chrome/renderer/extensions/extension_render_view_helper.h"
|
| @@ -22,8 +23,18 @@ using WebKit::WebFrame;
|
| using WebKit::WebNode;
|
| using WebKit::WebNodeList;
|
|
|
| +namespace {
|
| +
|
| +const char kWebstoreV8ExtensionName[] = "v8/ChromeWebstore";
|
| +
|
| const char kWebstoreLinkRelation[] = "chrome-webstore-item";
|
|
|
| +const char kPreferredStoreLinkUrlNotAString[] =
|
| + "The Chrome Web Store item link URL parameter must be a string.";
|
| +const char kSuccessCallbackNotAFunctionError[] =
|
| + "The success callback parameter must be a function.";
|
| +const char kFailureCallbackNotAFunctionError[] =
|
| + "The failure callback parameter must be a function.";
|
| const char kNotInTopFrameError[] =
|
| "Chrome Web Store installations can only be started by the top frame.";
|
| const char kNotUserGestureError[] =
|
| @@ -33,21 +44,65 @@ const char kNoWebstoreItemLinkFoundError[] =
|
| const char kInvalidWebstoreItemUrlError[] =
|
| "Invalid Chrome Web Store item URL.";
|
|
|
| -namespace extensions_v8 {
|
| +// chrome.webstore.install() calls generate an install ID so that the install's
|
| +// callbacks may be fired when the browser notifies us of install completion
|
| +// (successful or not) via HandleInstallResponse.
|
| +int g_next_install_id = 0;
|
| +
|
| +// Callbacks are kept track of in maps keyed by install ID. Values are weak
|
| +// references to functions. Entries are automatically removed when functions
|
| +// get garbage collected.
|
| +typedef std::map<int, v8::Persistent<v8::Function> > CallbackMap;
|
| +
|
| +base::LazyInstance<CallbackMap> g_success_callbacks(base::LINKER_INITIALIZED);
|
| +base::LazyInstance<CallbackMap> g_failure_callbacks(base::LINKER_INITIALIZED);
|
| +
|
| +// Extra data to be passed to MakeWeak/RemoveFromCallbackMap to know which entry
|
| +// to remove from which map.
|
| +struct CallbackMapData {
|
| + CallbackMap* callback_map;
|
| + int install_id;
|
| +};
|
| +
|
| +// Disposes of a callback function and its corresponding entry in the callback
|
| +// map.
|
| +static void RemoveFromCallbackMap(v8::Persistent<v8::Value> context,
|
| + void* data) {
|
| + CallbackMapData* callback_map_data = static_cast<CallbackMapData*>(data);
|
| + callback_map_data->callback_map->erase(callback_map_data->install_id);
|
| + delete callback_map_data;
|
| + context.Dispose();
|
| +}
|
|
|
| -static const char* const kWebstoreExtensionName = "v8/ChromeWebstore";
|
| +// Adds |callback_param| (assumed to be a function) to |callback_map| under the
|
| +// |install_id| key. Will be removed from the map when the value is about to be
|
| +// GCed.
|
| +static void AddToCallbackMap(int install_id,
|
| + v8::Local<v8::Value> callback_param,
|
| + CallbackMap* callback_map) {
|
| + CHECK(callback_param->IsFunction());
|
| + CallbackMapData* callback_map_data = new CallbackMapData();
|
| + callback_map_data->install_id = install_id;
|
| + callback_map_data->callback_map = callback_map;
|
|
|
| -class ChromeWebstoreExtensionWrapper : public v8::Extension {
|
| + v8::Local<v8::Function> function = v8::Function::Cast(*callback_param);
|
| + v8::Persistent<v8::Function> wrapper =
|
| + v8::Persistent<v8::Function>::New(function);
|
| + (*callback_map)[install_id] = wrapper;
|
| + wrapper.MakeWeak(callback_map_data, RemoveFromCallbackMap);
|
| +}
|
| +
|
| +} // anonymous namespace
|
| +
|
| +class ExtensionImpl : public v8::Extension {
|
| public:
|
| - ChromeWebstoreExtensionWrapper() :
|
| + ExtensionImpl() :
|
| v8::Extension(
|
| - kWebstoreExtensionName,
|
| - "var chrome;"
|
| - "if (!chrome)"
|
| - " chrome = {};"
|
| + kWebstoreV8ExtensionName,
|
| + "var chrome = chrome || {};"
|
| "if (!chrome.webstore) {"
|
| " chrome.webstore = new function() {"
|
| - " native function Install();"
|
| + " native function Install(preferredStoreUrl, onSuccess, onFailure);"
|
| " this.install = Install;"
|
| " };"
|
| "}") {
|
| @@ -71,16 +126,46 @@ class ChromeWebstoreExtensionWrapper : public v8::Extension {
|
| if (!render_view)
|
| return v8::Undefined();
|
|
|
| + std::string preferred_store_link_url;
|
| + if (args.Length() >= 1 && !args[0]->IsUndefined()) {
|
| + if (args[0]->IsString()) {
|
| + preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0]));
|
| + } else {
|
| + v8::ThrowException(v8::String::New(kPreferredStoreLinkUrlNotAString));
|
| + return v8::Undefined();
|
| + }
|
| + }
|
| +
|
| std::string webstore_item_id;
|
| std::string error;
|
| - if (GetWebstoreItemIdFromFrame(frame, &webstore_item_id, &error)) {
|
| - ExtensionRenderViewHelper* helper =
|
| - ExtensionRenderViewHelper::Get(render_view);
|
| - helper->InlineWebstoreInstall(webstore_item_id);
|
| - } else {
|
| + if (!GetWebstoreItemIdFromFrame(
|
| + frame, preferred_store_link_url, &webstore_item_id, &error)) {
|
| v8::ThrowException(v8::String::New(error.c_str()));
|
| + return v8::Undefined();
|
| }
|
|
|
| + int install_id = g_next_install_id++;
|
| + if (args.Length() >= 2 && !args[1]->IsUndefined()) {
|
| + if (args[1]->IsFunction()) {
|
| + AddToCallbackMap(install_id, args[1], g_success_callbacks.Pointer());
|
| + } else {
|
| + v8::ThrowException(v8::String::New(kSuccessCallbackNotAFunctionError));
|
| + return v8::Undefined();
|
| + }
|
| + }
|
| + if (args.Length() >= 3 && !args[2]->IsUndefined()) {
|
| + if (args[2]->IsFunction()) {
|
| + AddToCallbackMap(install_id, args[2], g_failure_callbacks.Pointer());
|
| + } else {
|
| + v8::ThrowException(v8::String::New(kFailureCallbackNotAFunctionError));
|
| + return v8::Undefined();
|
| + }
|
| + }
|
| +
|
| + ExtensionRenderViewHelper* helper =
|
| + ExtensionRenderViewHelper::Get(render_view);
|
| + helper->InlineWebstoreInstall(
|
| + install_id, webstore_item_id, frame->document().url());
|
| return v8::Undefined();
|
| }
|
|
|
| @@ -91,7 +176,8 @@ class ChromeWebstoreExtensionWrapper : public v8::Extension {
|
| // parameter will be populated with the ID. On failure, false will be returned
|
| // and |error| will be populated with the error.
|
| static bool GetWebstoreItemIdFromFrame(
|
| - WebFrame* frame, std::string* webstore_item_id, std::string* error) {
|
| + WebFrame* frame, const std::string& preferred_store_link_url,
|
| + std::string* webstore_item_id, std::string* error) {
|
| if (frame != frame->top()) {
|
| *error = kNotInTopFrameError;
|
| return false;
|
| @@ -132,6 +218,12 @@ class ChromeWebstoreExtensionWrapper : public v8::Extension {
|
| continue;
|
|
|
| std::string webstore_url_string(elem.getAttribute("href").utf8());
|
| +
|
| + if (!preferred_store_link_url.empty() &&
|
| + preferred_store_link_url != webstore_url_string) {
|
| + continue;
|
| + }
|
| +
|
| GURL webstore_url = GURL(webstore_url_string);
|
| if (!webstore_url.is_valid()) {
|
| *error = kInvalidWebstoreItemUrlError;
|
| @@ -170,8 +262,27 @@ class ChromeWebstoreExtensionWrapper : public v8::Extension {
|
| }
|
| };
|
|
|
| +// static
|
| v8::Extension* ChromeWebstoreExtension::Get() {
|
| - return new ChromeWebstoreExtensionWrapper();
|
| + return new ExtensionImpl();
|
| }
|
|
|
| -} // namespace extensions_v8
|
| +// static
|
| +void ChromeWebstoreExtension::HandleInstallResponse(int install_id,
|
| + bool success,
|
| + const std::string& error) {
|
| + CallbackMap* callback_map =
|
| + success ? g_success_callbacks.Pointer() : g_failure_callbacks.Pointer();
|
| + CallbackMap::iterator iter = callback_map->find(install_id);
|
| +
|
| + if (iter == callback_map->end() || iter->second.IsEmpty()) return;
|
| + const v8::Persistent<v8::Function>& function = (*iter).second;
|
| +
|
| + v8::HandleScope handle_scope;
|
| + v8::Context::Scope context_scope(function->CreationContext());
|
| + v8::Handle<v8::Value> argv[1];
|
| + argv[0] = v8::String::New(error.c_str());
|
| + function->Call(v8::Object::New(), arraysize(argv), argv);
|
| +
|
| + callback_map->erase(iter);
|
| +}
|
|
|