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