Chromium Code Reviews| 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..31d387d6507344f1b5cfdff1247387dec8d6f5a3 100644 |
| --- a/chrome/renderer/extensions/chrome_webstore_bindings.cc |
| +++ b/chrome/renderer/extensions/chrome_webstore_bindings.cc |
| @@ -22,8 +22,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 +43,66 @@ 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 next_install_id; |
| + |
| +// 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; |
| + |
| +CallbackMap success_callbacks; |
|
Aaron Boodman
2011/09/06 23:24:32
Consider making a PendingWebstoreInstall class tha
Mihai Parparita -not on Chrome
2011/09/07 00:05:03
As discussed, this isn't that straightforward sinc
|
| +CallbackMap failure_callbacks; |
|
Aaron Boodman
2011/09/06 23:24:32
Static non-pod variables are not allowed in Chromi
Mihai Parparita -not on Chrome
2011/09/07 00:05:03
Done.
|
| -static const char* const kWebstoreExtensionName = "v8/ChromeWebstore"; |
| +// Extra data to be passed to MakeWeak/CallbackMapWeakReferenceCallback 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 CallbackMapWeakReferenceCallback( |
| + 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(); |
| +} |
| -class ChromeWebstoreExtensionWrapper : public v8::Extension { |
| +// 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; |
| + |
| + 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, CallbackMapWeakReferenceCallback); |
| +} |
| + |
| +} // 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 = next_install_id++; |
| + if (args.Length() >= 2 && !args[1]->IsUndefined()) { |
| + if (args[1]->IsFunction()) { |
| + AddToCallbackMap(install_id, args[1], &success_callbacks); |
| + } 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], &failure_callbacks); |
| + } 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,25 @@ 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) { |
| + const CallbackMap& callback_map = |
| + success ? success_callbacks : failure_callbacks; |
| + CallbackMap::const_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); |
|
Aaron Boodman
2011/09/06 23:24:32
You can remove the function from the map after thi
Mihai Parparita -not on Chrome
2011/09/07 00:05:03
Done.
|
| +} |