OLD | NEW |
---|---|
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/renderer/extensions/chrome_webstore_bindings.h" | 5 #include "chrome/renderer/extensions/chrome_webstore_bindings.h" |
6 | 6 |
7 #include "base/string_util.h" | 7 #include "base/string_util.h" |
8 #include "chrome/common/extensions/extension.h" | 8 #include "chrome/common/extensions/extension.h" |
9 #include "chrome/renderer/extensions/extension_render_view_helper.h" | 9 #include "chrome/renderer/extensions/extension_render_view_helper.h" |
10 #include "content/renderer/render_view.h" | 10 #include "content/renderer/render_view.h" |
11 #include "googleurl/src/gurl.h" | 11 #include "googleurl/src/gurl.h" |
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" | 12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" |
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" | 13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" |
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" | 14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" |
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" | 15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" |
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h" | 16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h" |
17 #include "v8/include/v8.h" | 17 #include "v8/include/v8.h" |
18 | 18 |
19 using WebKit::WebDocument; | 19 using WebKit::WebDocument; |
20 using WebKit::WebElement; | 20 using WebKit::WebElement; |
21 using WebKit::WebFrame; | 21 using WebKit::WebFrame; |
22 using WebKit::WebNode; | 22 using WebKit::WebNode; |
23 using WebKit::WebNodeList; | 23 using WebKit::WebNodeList; |
24 | 24 |
25 namespace { | |
26 | |
27 const char kWebstoreV8ExtensionName[] = "v8/ChromeWebstore"; | |
28 | |
25 const char kWebstoreLinkRelation[] = "chrome-webstore-item"; | 29 const char kWebstoreLinkRelation[] = "chrome-webstore-item"; |
26 | 30 |
31 const char kPreferredStoreLinkUrlNotAString[] = | |
32 "The Chrome Web Store item link URL parameter must be a string."; | |
33 const char kSuccessCallbackNotAFunctionError[] = | |
34 "The success callback parameter must be a function."; | |
35 const char kFailureCallbackNotAFunctionError[] = | |
36 "The failure callback parameter must be a function."; | |
27 const char kNotInTopFrameError[] = | 37 const char kNotInTopFrameError[] = |
28 "Chrome Web Store installations can only be started by the top frame."; | 38 "Chrome Web Store installations can only be started by the top frame."; |
29 const char kNotUserGestureError[] = | 39 const char kNotUserGestureError[] = |
30 "Chrome Web Store installations can only be initated by a user gesture."; | 40 "Chrome Web Store installations can only be initated by a user gesture."; |
31 const char kNoWebstoreItemLinkFoundError[] = | 41 const char kNoWebstoreItemLinkFoundError[] = |
32 "No Chrome Web Store item link found."; | 42 "No Chrome Web Store item link found."; |
33 const char kInvalidWebstoreItemUrlError[] = | 43 const char kInvalidWebstoreItemUrlError[] = |
34 "Invalid Chrome Web Store item URL."; | 44 "Invalid Chrome Web Store item URL."; |
35 | 45 |
36 namespace extensions_v8 { | 46 // chrome.webstore.install() calls generate an install ID so that the install's |
47 // callbacks may be fired when the browser notifies us of install completion | |
48 // (successful or not) via HandleInstallResponse. | |
49 int next_install_id; | |
37 | 50 |
38 static const char* const kWebstoreExtensionName = "v8/ChromeWebstore"; | 51 // Callbacks are kept track of in maps keyed by install ID. Values are weak |
52 // references to functions. Entries are automatically removed when functions | |
53 // get garbage collected. | |
54 typedef std::map<int, v8::Persistent<v8::Function> > CallbackMap; | |
39 | 55 |
40 class ChromeWebstoreExtensionWrapper : public v8::Extension { | 56 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
| |
57 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.
| |
58 | |
59 // Extra data to be passed to MakeWeak/CallbackMapWeakReferenceCallback to know | |
60 // which entry to remove from which map. | |
61 struct CallbackMapData { | |
62 CallbackMap* callback_map; | |
63 int install_id; | |
64 }; | |
65 | |
66 // Disposes of a callback function and its corresponding entry in the callback | |
67 // map. | |
68 static void CallbackMapWeakReferenceCallback( | |
69 v8::Persistent<v8::Value> context, void* data) { | |
70 CallbackMapData* callback_map_data = static_cast<CallbackMapData*>(data); | |
71 callback_map_data->callback_map->erase(callback_map_data->install_id); | |
72 | |
73 delete callback_map_data; | |
74 context.Dispose(); | |
75 } | |
76 | |
77 // Adds |callback_param| (assumed to be a function) to |callback_map| under the | |
78 // |install_id| key. Will be removed from the map when the value is about to be | |
79 // GCed. | |
80 static void AddToCallbackMap(int install_id, | |
81 v8::Local<v8::Value> callback_param, | |
82 CallbackMap* callback_map) { | |
83 CHECK(callback_param->IsFunction()); | |
84 CallbackMapData* callback_map_data = new CallbackMapData(); | |
85 callback_map_data->install_id = install_id; | |
86 callback_map_data->callback_map = callback_map; | |
87 | |
88 v8::Local<v8::Function> function = v8::Function::Cast(*callback_param); | |
89 v8::Persistent<v8::Function> wrapper = | |
90 v8::Persistent<v8::Function>::New(function); | |
91 (*callback_map)[install_id] = wrapper; | |
92 wrapper.MakeWeak(callback_map_data, CallbackMapWeakReferenceCallback); | |
93 } | |
94 | |
95 } // anonymous namespace | |
96 | |
97 class ExtensionImpl : public v8::Extension { | |
41 public: | 98 public: |
42 ChromeWebstoreExtensionWrapper() : | 99 ExtensionImpl() : |
43 v8::Extension( | 100 v8::Extension( |
44 kWebstoreExtensionName, | 101 kWebstoreV8ExtensionName, |
45 "var chrome;" | 102 "var chrome = chrome || {};" |
46 "if (!chrome)" | |
47 " chrome = {};" | |
48 "if (!chrome.webstore) {" | 103 "if (!chrome.webstore) {" |
49 " chrome.webstore = new function() {" | 104 " chrome.webstore = new function() {" |
50 " native function Install();" | 105 " native function Install(preferredStoreUrl, onSuccess, onFailure);" |
51 " this.install = Install;" | 106 " this.install = Install;" |
52 " };" | 107 " };" |
53 "}") { | 108 "}") { |
54 } | 109 } |
55 | 110 |
56 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( | 111 virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction( |
57 v8::Handle<v8::String> name) { | 112 v8::Handle<v8::String> name) { |
58 if (name->Equals(v8::String::New("Install"))) { | 113 if (name->Equals(v8::String::New("Install"))) { |
59 return v8::FunctionTemplate::New(Install); | 114 return v8::FunctionTemplate::New(Install); |
60 } else { | 115 } else { |
61 return v8::Handle<v8::FunctionTemplate>(); | 116 return v8::Handle<v8::FunctionTemplate>(); |
62 } | 117 } |
63 } | 118 } |
64 | 119 |
65 static v8::Handle<v8::Value> Install(const v8::Arguments& args) { | 120 static v8::Handle<v8::Value> Install(const v8::Arguments& args) { |
66 WebFrame* frame = WebFrame::frameForCurrentContext(); | 121 WebFrame* frame = WebFrame::frameForCurrentContext(); |
67 if (!frame || !frame->view()) | 122 if (!frame || !frame->view()) |
68 return v8::Undefined(); | 123 return v8::Undefined(); |
69 | 124 |
70 RenderView* render_view = RenderView::FromWebView(frame->view()); | 125 RenderView* render_view = RenderView::FromWebView(frame->view()); |
71 if (!render_view) | 126 if (!render_view) |
72 return v8::Undefined(); | 127 return v8::Undefined(); |
73 | 128 |
129 std::string preferred_store_link_url; | |
130 if (args.Length() >= 1 && !args[0]->IsUndefined()) { | |
131 if (args[0]->IsString()) { | |
132 preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0])); | |
133 } else { | |
134 v8::ThrowException(v8::String::New(kPreferredStoreLinkUrlNotAString)); | |
135 return v8::Undefined(); | |
136 } | |
137 } | |
138 | |
74 std::string webstore_item_id; | 139 std::string webstore_item_id; |
75 std::string error; | 140 std::string error; |
76 if (GetWebstoreItemIdFromFrame(frame, &webstore_item_id, &error)) { | 141 if (!GetWebstoreItemIdFromFrame( |
77 ExtensionRenderViewHelper* helper = | 142 frame, preferred_store_link_url, &webstore_item_id, &error)) { |
78 ExtensionRenderViewHelper::Get(render_view); | |
79 helper->InlineWebstoreInstall(webstore_item_id); | |
80 } else { | |
81 v8::ThrowException(v8::String::New(error.c_str())); | 143 v8::ThrowException(v8::String::New(error.c_str())); |
144 return v8::Undefined(); | |
82 } | 145 } |
83 | 146 |
147 int install_id = next_install_id++; | |
148 if (args.Length() >= 2 && !args[1]->IsUndefined()) { | |
149 if (args[1]->IsFunction()) { | |
150 AddToCallbackMap(install_id, args[1], &success_callbacks); | |
151 } else { | |
152 v8::ThrowException(v8::String::New(kSuccessCallbackNotAFunctionError)); | |
153 return v8::Undefined(); | |
154 } | |
155 } | |
156 if (args.Length() >= 3 && !args[2]->IsUndefined()) { | |
157 if (args[2]->IsFunction()) { | |
158 AddToCallbackMap(install_id, args[2], &failure_callbacks); | |
159 } else { | |
160 v8::ThrowException(v8::String::New(kFailureCallbackNotAFunctionError)); | |
161 return v8::Undefined(); | |
162 } | |
163 } | |
164 | |
165 ExtensionRenderViewHelper* helper = | |
166 ExtensionRenderViewHelper::Get(render_view); | |
167 helper->InlineWebstoreInstall( | |
168 install_id, webstore_item_id, frame->document().url()); | |
84 return v8::Undefined(); | 169 return v8::Undefined(); |
85 } | 170 } |
86 | 171 |
87 private: | 172 private: |
88 // Extracts a Web Store item ID from a <link rel="chrome-webstore-item" | 173 // Extracts a Web Store item ID from a <link rel="chrome-webstore-item" |
89 // href="https://chrome.google.com/webstore/detail/id"> node found in the | 174 // href="https://chrome.google.com/webstore/detail/id"> node found in the |
90 // frame. On success, true will be returned and the |webstore_item_id| | 175 // frame. On success, true will be returned and the |webstore_item_id| |
91 // parameter will be populated with the ID. On failure, false will be returned | 176 // parameter will be populated with the ID. On failure, false will be returned |
92 // and |error| will be populated with the error. | 177 // and |error| will be populated with the error. |
93 static bool GetWebstoreItemIdFromFrame( | 178 static bool GetWebstoreItemIdFromFrame( |
94 WebFrame* frame, std::string* webstore_item_id, std::string* error) { | 179 WebFrame* frame, const std::string& preferred_store_link_url, |
180 std::string* webstore_item_id, std::string* error) { | |
95 if (frame != frame->top()) { | 181 if (frame != frame->top()) { |
96 *error = kNotInTopFrameError; | 182 *error = kNotInTopFrameError; |
97 return false; | 183 return false; |
98 } | 184 } |
99 | 185 |
100 if (!frame->isProcessingUserGesture()) { | 186 if (!frame->isProcessingUserGesture()) { |
101 *error = kNotUserGestureError; | 187 *error = kNotUserGestureError; |
102 return false; | 188 return false; |
103 } | 189 } |
104 | 190 |
(...skipping 20 matching lines...) Expand all Loading... | |
125 | 211 |
126 if (!elem.hasTagName("link") || !elem.hasAttribute("rel") || | 212 if (!elem.hasTagName("link") || !elem.hasAttribute("rel") || |
127 !elem.hasAttribute("href")) | 213 !elem.hasAttribute("href")) |
128 continue; | 214 continue; |
129 | 215 |
130 std::string rel = elem.getAttribute("rel").utf8(); | 216 std::string rel = elem.getAttribute("rel").utf8(); |
131 if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation)) | 217 if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation)) |
132 continue; | 218 continue; |
133 | 219 |
134 std::string webstore_url_string(elem.getAttribute("href").utf8()); | 220 std::string webstore_url_string(elem.getAttribute("href").utf8()); |
221 | |
222 if (!preferred_store_link_url.empty() && | |
223 preferred_store_link_url != webstore_url_string) { | |
224 continue; | |
225 } | |
226 | |
135 GURL webstore_url = GURL(webstore_url_string); | 227 GURL webstore_url = GURL(webstore_url_string); |
136 if (!webstore_url.is_valid()) { | 228 if (!webstore_url.is_valid()) { |
137 *error = kInvalidWebstoreItemUrlError; | 229 *error = kInvalidWebstoreItemUrlError; |
138 return false; | 230 return false; |
139 } | 231 } |
140 | 232 |
141 if (webstore_url.scheme() != webstore_base_url.scheme() || | 233 if (webstore_url.scheme() != webstore_base_url.scheme() || |
142 webstore_url.host() != webstore_base_url.host() || | 234 webstore_url.host() != webstore_base_url.host() || |
143 !StartsWithASCII( | 235 !StartsWithASCII( |
144 webstore_url.path(), webstore_base_url.path(), true)) { | 236 webstore_url.path(), webstore_base_url.path(), true)) { |
(...skipping 18 matching lines...) Expand all Loading... | |
163 | 255 |
164 *webstore_item_id = candidate_webstore_item_id; | 256 *webstore_item_id = candidate_webstore_item_id; |
165 return true; | 257 return true; |
166 } | 258 } |
167 | 259 |
168 *error = kNoWebstoreItemLinkFoundError; | 260 *error = kNoWebstoreItemLinkFoundError; |
169 return false; | 261 return false; |
170 } | 262 } |
171 }; | 263 }; |
172 | 264 |
265 // static | |
173 v8::Extension* ChromeWebstoreExtension::Get() { | 266 v8::Extension* ChromeWebstoreExtension::Get() { |
174 return new ChromeWebstoreExtensionWrapper(); | 267 return new ExtensionImpl(); |
175 } | 268 } |
176 | 269 |
177 } // namespace extensions_v8 | 270 // static |
271 void ChromeWebstoreExtension::HandleInstallResponse(int install_id, | |
272 bool success, | |
273 const std::string& error) { | |
274 const CallbackMap& callback_map = | |
275 success ? success_callbacks : failure_callbacks; | |
276 CallbackMap::const_iterator iter = callback_map.find(install_id); | |
277 | |
278 if (iter == callback_map.end() || iter->second.IsEmpty()) return; | |
279 const v8::Persistent<v8::Function>& function = (*iter).second; | |
280 | |
281 v8::HandleScope handle_scope; | |
282 v8::Context::Scope context_scope(function->CreationContext()); | |
283 v8::Handle<v8::Value> argv[1]; | |
284 argv[0] = v8::String::New(error.c_str()); | |
285 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.
| |
286 } | |
OLD | NEW |