1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/renderer/extensions/webstore_bindings.h" 6 7 #include "base/strings/string_util.h" 8 #include "chrome/common/extensions/api/webstore/webstore_api_constants.h" 9 #include "chrome/common/extensions/chrome_extension_messages.h" 10 #include "components/crx_file/id_util.h" 11 #include "content/public/renderer/render_view.h" 12 #include "extensions/common/extension.h" 13 #include "extensions/common/extension_urls.h" 14 #include "extensions/renderer/script_context.h" 15 #include "third_party/WebKit/public/web/WebDocument.h" 16 #include "third_party/WebKit/public/web/WebElement.h" 17 #include "third_party/WebKit/public/web/WebNode.h" 18 #include "third_party/WebKit/public/web/WebNodeList.h" 19 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h" 20 #include "url/gurl.h" 21 #include "v8/include/v8.h" 22 23 using blink::WebDocument; 24 using blink::WebElement; 25 using blink::WebFrame; 26 using blink::WebNode; 27 using blink::WebNodeList; 28 using blink::WebUserGestureIndicator; 29 30 namespace extensions { 31 32 namespace { 33 34 const char kWebstoreLinkRelation[] = "chrome-webstore-item"; 35 36 const char kNotInTopFrameError[] = 37 "Chrome Web Store installations can only be started by the top frame."; 38 const char kNotUserGestureError[] = 39 "Chrome Web Store installations can only be initated by a user gesture."; 40 const char kNoWebstoreItemLinkFoundError[] = 41 "No Chrome Web Store item link found."; 42 const char kInvalidWebstoreItemUrlError[] = 43 "Invalid Chrome Web Store item URL."; 44 45 // chrome.webstore.install() calls generate an install ID so that the install's 46 // callbacks may be fired when the browser notifies us of install completion 47 // (successful or not) via OnInlineWebstoreInstallResponse. 48 int g_next_install_id = 0; 49 50 } // anonymous namespace 51 52 WebstoreBindings::WebstoreBindings(ScriptContext* context) 53 : ObjectBackedNativeHandler(context), ChromeV8ExtensionHandler(context) { 54 RouteFunction("Install", 55 base::Bind(&WebstoreBindings::Install, base::Unretained(this))); 56 } 57 58 void WebstoreBindings::Install( 59 const v8::FunctionCallbackInfo<v8::Value>& args) { 60 content::RenderView* render_view = context()->GetRenderView(); 61 if (!render_view) 62 return; 63 64 // The first two arguments indicate whether or not there are install stage 65 // or download progress listeners. 66 int listener_mask = 0; 67 CHECK(args[0]->IsBoolean()); 68 if (args[0]->BooleanValue()) 69 listener_mask |= api::webstore::INSTALL_STAGE_LISTENER; 70 CHECK(args[1]->IsBoolean()); 71 if (args[1]->BooleanValue()) 72 listener_mask |= api::webstore::DOWNLOAD_PROGRESS_LISTENER; 73 74 std::string preferred_store_link_url; 75 if (!args[2]->IsUndefined()) { 76 CHECK(args[2]->IsString()); 77 preferred_store_link_url = std::string(*v8::String::Utf8Value(args[2])); 78 } 79 80 std::string webstore_item_id; 81 std::string error; 82 WebFrame* frame = context()->web_frame(); 83 84 if (!GetWebstoreItemIdFromFrame( 85 frame, preferred_store_link_url, &webstore_item_id, &error)) { 86 args.GetIsolate()->ThrowException( 87 v8::String::NewFromUtf8(args.GetIsolate(), error.c_str())); 88 return; 89 } 90 91 int install_id = g_next_install_id++; 92 93 Send(new ExtensionHostMsg_InlineWebstoreInstall(render_view->GetRoutingID(), 94 install_id, 95 GetRoutingID(), 96 webstore_item_id, 97 frame->document().url(), 98 listener_mask)); 99 100 args.GetReturnValue().Set(static_cast<int32_t>(install_id)); 101 } 102 103 // static 104 bool WebstoreBindings::GetWebstoreItemIdFromFrame( 105 WebFrame* frame, const std::string& preferred_store_link_url, 106 std::string* webstore_item_id, std::string* error) { 107 if (frame != frame->top()) { 108 *error = kNotInTopFrameError; 109 return false; 110 } 111 112 if (!WebUserGestureIndicator::isProcessingUserGesture()) { 113 *error = kNotUserGestureError; 114 return false; 115 } 116 117 WebDocument document = frame->document(); 118 if (document.isNull()) { 119 *error = kNoWebstoreItemLinkFoundError; 120 return false; 121 } 122 123 WebElement head = document.head(); 124 if (head.isNull()) { 125 *error = kNoWebstoreItemLinkFoundError; 126 return false; 127 } 128 129 GURL webstore_base_url = 130 GURL(extension_urls::GetWebstoreItemDetailURLPrefix()); 131 WebNodeList children = head.childNodes(); 132 for (unsigned i = 0; i < children.length(); ++i) { 133 WebNode child = children.item(i); 134 if (!child.isElementNode()) 135 continue; 136 WebElement elem = child.to<WebElement>(); 137 138 if (!elem.hasHTMLTagName("link") || !elem.hasAttribute("rel") || 139 !elem.hasAttribute("href")) 140 continue; 141 142 std::string rel = elem.getAttribute("rel").utf8(); 143 if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation)) 144 continue; 145 146 std::string webstore_url_string(elem.getAttribute("href").utf8()); 147 148 if (!preferred_store_link_url.empty() && 149 preferred_store_link_url != webstore_url_string) { 150 continue; 151 } 152 153 GURL webstore_url = GURL(webstore_url_string); 154 if (!webstore_url.is_valid()) { 155 *error = kInvalidWebstoreItemUrlError; 156 return false; 157 } 158 159 if (webstore_url.scheme() != webstore_base_url.scheme() || 160 webstore_url.host() != webstore_base_url.host() || 161 !StartsWithASCII( 162 webstore_url.path(), webstore_base_url.path(), true)) { 163 *error = kInvalidWebstoreItemUrlError; 164 return false; 165 } 166 167 std::string candidate_webstore_item_id = webstore_url.path().substr( 168 webstore_base_url.path().length()); 169 if (!crx_file::id_util::IdIsValid(candidate_webstore_item_id)) { 170 *error = kInvalidWebstoreItemUrlError; 171 return false; 172 } 173 174 std::string reconstructed_webstore_item_url_string = 175 extension_urls::GetWebstoreItemDetailURLPrefix() + 176 candidate_webstore_item_id; 177 if (reconstructed_webstore_item_url_string != webstore_url_string) { 178 *error = kInvalidWebstoreItemUrlError; 179 return false; 180 } 181 182 *webstore_item_id = candidate_webstore_item_id; 183 return true; 184 } 185 186 *error = kNoWebstoreItemLinkFoundError; 187 return false; 188 } 189 190 bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) { 191 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message) 192 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse, 193 OnInlineWebstoreInstallResponse) 194 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallStageChanged, 195 OnInlineInstallStageChanged) 196 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallDownloadProgress, 197 OnInlineInstallDownloadProgress) 198 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message") 199 IPC_END_MESSAGE_MAP() 200 return true; 201 } 202 203 void WebstoreBindings::OnInlineWebstoreInstallResponse( 204 int install_id, 205 bool success, 206 const std::string& error, 207 webstore_install::Result result) { 208 v8::Isolate* isolate = context()->isolate(); 209 v8::HandleScope handle_scope(isolate); 210 v8::Context::Scope context_scope(context()->v8_context()); 211 v8::Handle<v8::Value> argv[] = { 212 v8::Integer::New(isolate, install_id), 213 v8::Boolean::New(isolate, success), 214 v8::String::NewFromUtf8(isolate, error.c_str()), 215 v8::String::NewFromUtf8( 216 isolate, api::webstore::kInstallResultCodes[static_cast<int>(result)]) 217 }; 218 context()->module_system()->CallModuleMethod( 219 "webstore", "onInstallResponse", arraysize(argv), argv); 220 } 221 222 void WebstoreBindings::OnInlineInstallStageChanged(int stage) { 223 const char* stage_string = NULL; 224 api::webstore::InstallStage install_stage = 225 static_cast<api::webstore::InstallStage>(stage); 226 switch (install_stage) { 227 case api::webstore::INSTALL_STAGE_DOWNLOADING: 228 stage_string = api::webstore::kInstallStageDownloading; 229 break; 230 case api::webstore::INSTALL_STAGE_INSTALLING: 231 stage_string = api::webstore::kInstallStageInstalling; 232 break; 233 } 234 v8::Isolate* isolate = context()->isolate(); 235 v8::HandleScope handle_scope(isolate); 236 v8::Context::Scope context_scope(context()->v8_context()); 237 v8::Handle<v8::Value> argv[] = { 238 v8::String::NewFromUtf8(isolate, stage_string)}; 239 context()->module_system()->CallModuleMethod( 240 "webstore", "onInstallStageChanged", arraysize(argv), argv); 241 } 242 243 void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded) { 244 v8::Isolate* isolate = context()->isolate(); 245 v8::HandleScope handle_scope(isolate); 246 v8::Context::Scope context_scope(context()->v8_context()); 247 v8::Handle<v8::Value> argv[] = { 248 v8::Number::New(isolate, percent_downloaded / 100.0)}; 249 context()->module_system()->CallModuleMethod( 250 "webstore", "onDownloadProgress", arraysize(argv), argv); 251 } 252 253 } // namespace extensions 254