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