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/extension_constants.h" 9 #include "chrome/common/extensions/extension_messages.h" 10 #include "chrome/renderer/extensions/chrome_v8_context.h" 11 #include "content/public/renderer/render_view.h" 12 #include "extensions/common/extension.h" 13 #include "grit/renderer_resources.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 kPreferredStoreLinkUrlNotAString[] = 36 "The Chrome Web Store item link URL parameter must be a string."; 37 const char kSuccessCallbackNotAFunctionError[] = 38 "The success callback parameter must be a function."; 39 const char kFailureCallbackNotAFunctionError[] = 40 "The failure callback parameter must be a function."; 41 const char kNotInTopFrameError[] = 42 "Chrome Web Store installations can only be started by the top frame."; 43 const char kNotUserGestureError[] = 44 "Chrome Web Store installations can only be initated by a user gesture."; 45 const char kNoWebstoreItemLinkFoundError[] = 46 "No Chrome Web Store item link found."; 47 const char kInvalidWebstoreItemUrlError[] = 48 "Invalid Chrome Web Store item URL."; 49 50 // chrome.webstore.install() calls generate an install ID so that the install's 51 // callbacks may be fired when the browser notifies us of install completion 52 // (successful or not) via OnInlineWebstoreInstallResponse. 53 int g_next_install_id = 0; 54 55 } // anonymous namespace 56 57 WebstoreBindings::WebstoreBindings(Dispatcher* dispatcher, 58 ChromeV8Context* context) 59 : ChromeV8Extension(dispatcher, context), 60 ChromeV8ExtensionHandler(context) { 61 RouteFunction("Install", 62 base::Bind(&WebstoreBindings::Install, base::Unretained(this))); 63 } 64 65 void WebstoreBindings::Install( 66 const v8::FunctionCallbackInfo<v8::Value>& args) { 67 WebFrame* frame = WebFrame::frameForContext(context()->v8_context()); 68 if (!frame || !frame->view()) 69 return; 70 71 content::RenderView* render_view = 72 content::RenderView::FromWebView(frame->view()); 73 if (!render_view) 74 return; 75 76 std::string preferred_store_link_url; 77 if (!args[0]->IsUndefined()) { 78 if (args[0]->IsString()) { 79 preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0])); 80 } else { 81 args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( 82 args.GetIsolate(), kPreferredStoreLinkUrlNotAString)); 83 return; 84 } 85 } 86 87 std::string webstore_item_id; 88 std::string error; 89 if (!GetWebstoreItemIdFromFrame( 90 frame, preferred_store_link_url, &webstore_item_id, &error)) { 91 args.GetIsolate()->ThrowException( 92 v8::String::NewFromUtf8(args.GetIsolate(), error.c_str())); 93 return; 94 } 95 96 int install_id = g_next_install_id++; 97 if (!args[1]->IsUndefined() && !args[1]->IsFunction()) { 98 args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( 99 args.GetIsolate(), kSuccessCallbackNotAFunctionError)); 100 return; 101 } 102 103 if (!args[2]->IsUndefined() && !args[2]->IsFunction()) { 104 args.GetIsolate()->ThrowException(v8::String::NewFromUtf8( 105 args.GetIsolate(), kFailureCallbackNotAFunctionError)); 106 return; 107 } 108 109 Send(new ExtensionHostMsg_InlineWebstoreInstall( 110 render_view->GetRoutingID(), 111 install_id, 112 GetRoutingID(), 113 webstore_item_id, 114 frame->document().url())); 115 116 args.GetReturnValue().Set(static_cast<int32_t>(install_id)); 117 } 118 119 // static 120 bool WebstoreBindings::GetWebstoreItemIdFromFrame( 121 WebFrame* frame, const std::string& preferred_store_link_url, 122 std::string* webstore_item_id, std::string* error) { 123 if (frame != frame->top()) { 124 *error = kNotInTopFrameError; 125 return false; 126 } 127 128 if (!WebUserGestureIndicator::isProcessingUserGesture()) { 129 *error = kNotUserGestureError; 130 return false; 131 } 132 133 WebDocument document = frame->document(); 134 if (document.isNull()) { 135 *error = kNoWebstoreItemLinkFoundError; 136 return false; 137 } 138 139 WebElement head = document.head(); 140 if (head.isNull()) { 141 *error = kNoWebstoreItemLinkFoundError; 142 return false; 143 } 144 145 GURL webstore_base_url = 146 GURL(extension_urls::GetWebstoreItemDetailURLPrefix()); 147 WebNodeList children = head.childNodes(); 148 for (unsigned i = 0; i < children.length(); ++i) { 149 WebNode child = children.item(i); 150 if (!child.isElementNode()) 151 continue; 152 WebElement elem = child.to<WebElement>(); 153 154 if (!elem.hasTagName("link") || !elem.hasAttribute("rel") || 155 !elem.hasAttribute("href")) 156 continue; 157 158 std::string rel = elem.getAttribute("rel").utf8(); 159 if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation)) 160 continue; 161 162 std::string webstore_url_string(elem.getAttribute("href").utf8()); 163 164 if (!preferred_store_link_url.empty() && 165 preferred_store_link_url != webstore_url_string) { 166 continue; 167 } 168 169 GURL webstore_url = GURL(webstore_url_string); 170 if (!webstore_url.is_valid()) { 171 *error = kInvalidWebstoreItemUrlError; 172 return false; 173 } 174 175 if (webstore_url.scheme() != webstore_base_url.scheme() || 176 webstore_url.host() != webstore_base_url.host() || 177 !StartsWithASCII( 178 webstore_url.path(), webstore_base_url.path(), true)) { 179 *error = kInvalidWebstoreItemUrlError; 180 return false; 181 } 182 183 std::string candidate_webstore_item_id = webstore_url.path().substr( 184 webstore_base_url.path().length()); 185 if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) { 186 *error = kInvalidWebstoreItemUrlError; 187 return false; 188 } 189 190 std::string reconstructed_webstore_item_url_string = 191 extension_urls::GetWebstoreItemDetailURLPrefix() + 192 candidate_webstore_item_id; 193 if (reconstructed_webstore_item_url_string != webstore_url_string) { 194 *error = kInvalidWebstoreItemUrlError; 195 return false; 196 } 197 198 *webstore_item_id = candidate_webstore_item_id; 199 return true; 200 } 201 202 *error = kNoWebstoreItemLinkFoundError; 203 return false; 204 } 205 206 bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) { 207 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message) 208 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse, 209 OnInlineWebstoreInstallResponse) 210 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message") 211 IPC_END_MESSAGE_MAP() 212 return true; 213 } 214 215 void WebstoreBindings::OnInlineWebstoreInstallResponse( 216 int install_id, 217 bool success, 218 const std::string& error) { 219 v8::Isolate* isolate = context()->isolate(); 220 v8::HandleScope handle_scope(isolate); 221 v8::Context::Scope context_scope(context()->v8_context()); 222 v8::Handle<v8::Value> argv[] = { 223 v8::Integer::New(install_id), 224 v8::Boolean::New(isolate, success), 225 v8::String::NewFromUtf8(isolate, error.c_str()) 226 }; 227 context()->module_system()->CallModuleMethod( 228 "webstore", "onInstallResponse", arraysize(argv), argv); 229 } 230 231 } // namespace extensions 232