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/lazy_instance.h" |
7 #include "base/string_util.h" | 8 #include "base/string_util.h" |
8 #include "chrome/common/extensions/extension.h" | 9 #include "chrome/common/extensions/extension.h" |
9 #include "chrome/renderer/extensions/extension_render_view_helper.h" | 10 #include "chrome/renderer/extensions/extension_render_view_helper.h" |
10 #include "content/renderer/render_view.h" | 11 #include "content/renderer/render_view.h" |
11 #include "googleurl/src/gurl.h" | 12 #include "googleurl/src/gurl.h" |
12 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" | 13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" |
13 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" | 14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebElement.h" |
14 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" | 15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" |
15 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" | 16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNode.h" |
16 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h" | 17 #include "third_party/WebKit/Source/WebKit/chromium/public/WebNodeList.h" |
17 #include "v8/include/v8.h" | 18 #include "v8/include/v8.h" |
18 | 19 |
19 using WebKit::WebDocument; | 20 using WebKit::WebDocument; |
20 using WebKit::WebElement; | 21 using WebKit::WebElement; |
21 using WebKit::WebFrame; | 22 using WebKit::WebFrame; |
22 using WebKit::WebNode; | 23 using WebKit::WebNode; |
23 using WebKit::WebNodeList; | 24 using WebKit::WebNodeList; |
24 | 25 |
| 26 namespace { |
| 27 |
| 28 const char kWebstoreV8ExtensionName[] = "v8/ChromeWebstore"; |
| 29 |
25 const char kWebstoreLinkRelation[] = "chrome-webstore-item"; | 30 const char kWebstoreLinkRelation[] = "chrome-webstore-item"; |
26 | 31 |
| 32 const char kPreferredStoreLinkUrlNotAString[] = |
| 33 "The Chrome Web Store item link URL parameter must be a string."; |
| 34 const char kSuccessCallbackNotAFunctionError[] = |
| 35 "The success callback parameter must be a function."; |
| 36 const char kFailureCallbackNotAFunctionError[] = |
| 37 "The failure callback parameter must be a function."; |
27 const char kNotInTopFrameError[] = | 38 const char kNotInTopFrameError[] = |
28 "Chrome Web Store installations can only be started by the top frame."; | 39 "Chrome Web Store installations can only be started by the top frame."; |
29 const char kNotUserGestureError[] = | 40 const char kNotUserGestureError[] = |
30 "Chrome Web Store installations can only be initated by a user gesture."; | 41 "Chrome Web Store installations can only be initated by a user gesture."; |
31 const char kNoWebstoreItemLinkFoundError[] = | 42 const char kNoWebstoreItemLinkFoundError[] = |
32 "No Chrome Web Store item link found."; | 43 "No Chrome Web Store item link found."; |
33 const char kInvalidWebstoreItemUrlError[] = | 44 const char kInvalidWebstoreItemUrlError[] = |
34 "Invalid Chrome Web Store item URL."; | 45 "Invalid Chrome Web Store item URL."; |
35 | 46 |
36 namespace extensions_v8 { | 47 // chrome.webstore.install() calls generate an install ID so that the install's |
| 48 // callbacks may be fired when the browser notifies us of install completion |
| 49 // (successful or not) via HandleInstallResponse. |
| 50 int g_next_install_id = 0; |
37 | 51 |
38 static const char* const kWebstoreExtensionName = "v8/ChromeWebstore"; | 52 // Callbacks are kept track of in maps keyed by install ID. Values are weak |
| 53 // references to functions. Entries are automatically removed when functions |
| 54 // get garbage collected. |
| 55 typedef std::map<int, v8::Persistent<v8::Function> > CallbackMap; |
39 | 56 |
40 class ChromeWebstoreExtensionWrapper : public v8::Extension { | 57 base::LazyInstance<CallbackMap> g_success_callbacks(base::LINKER_INITIALIZED); |
| 58 base::LazyInstance<CallbackMap> g_failure_callbacks(base::LINKER_INITIALIZED); |
| 59 |
| 60 // Extra data to be passed to MakeWeak/RemoveFromCallbackMap to know which entry |
| 61 // to remove from which map. |
| 62 struct CallbackMapData { |
| 63 CallbackMap* callback_map; |
| 64 int install_id; |
| 65 }; |
| 66 |
| 67 // Disposes of a callback function and its corresponding entry in the callback |
| 68 // map. |
| 69 static void RemoveFromCallbackMap(v8::Persistent<v8::Value> context, |
| 70 void* data) { |
| 71 CallbackMapData* callback_map_data = static_cast<CallbackMapData*>(data); |
| 72 callback_map_data->callback_map->erase(callback_map_data->install_id); |
| 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, RemoveFromCallbackMap); |
| 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 = g_next_install_id++; |
| 148 if (args.Length() >= 2 && !args[1]->IsUndefined()) { |
| 149 if (args[1]->IsFunction()) { |
| 150 AddToCallbackMap(install_id, args[1], g_success_callbacks.Pointer()); |
| 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], g_failure_callbacks.Pointer()); |
| 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 CallbackMap* callback_map = |
| 275 success ? g_success_callbacks.Pointer() : g_failure_callbacks.Pointer(); |
| 276 CallbackMap::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); |
| 286 |
| 287 callback_map->erase(iter); |
| 288 } |
OLD | NEW |