| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/renderer/extensions/webstore_bindings.h" |
| |
| #include "base/strings/string_util.h" |
| #include "chrome/common/extensions/extension.h" |
| #include "chrome/common/extensions/extension_constants.h" |
| #include "chrome/common/extensions/extension_messages.h" |
| #include "chrome/renderer/extensions/chrome_v8_context.h" |
| #include "content/public/renderer/render_view.h" |
| #include "grit/renderer_resources.h" |
| #include "third_party/WebKit/public/web/WebDocument.h" |
| #include "third_party/WebKit/public/web/WebElement.h" |
| #include "third_party/WebKit/public/web/WebNode.h" |
| #include "third_party/WebKit/public/web/WebNodeList.h" |
| #include "third_party/WebKit/public/web/WebUserGestureIndicator.h" |
| #include "url/gurl.h" |
| #include "v8/include/v8.h" |
| |
| using WebKit::WebDocument; |
| using WebKit::WebElement; |
| using WebKit::WebFrame; |
| using WebKit::WebNode; |
| using WebKit::WebNodeList; |
| using WebKit::WebUserGestureIndicator; |
| |
| namespace extensions { |
| |
| namespace { |
| |
| const char kWebstoreLinkRelation[] = "chrome-webstore-item"; |
| |
| const char kPreferredStoreLinkUrlNotAString[] = |
| "The Chrome Web Store item link URL parameter must be a string."; |
| const char kSuccessCallbackNotAFunctionError[] = |
| "The success callback parameter must be a function."; |
| const char kFailureCallbackNotAFunctionError[] = |
| "The failure callback parameter must be a function."; |
| const char kNotInTopFrameError[] = |
| "Chrome Web Store installations can only be started by the top frame."; |
| const char kNotUserGestureError[] = |
| "Chrome Web Store installations can only be initated by a user gesture."; |
| const char kNoWebstoreItemLinkFoundError[] = |
| "No Chrome Web Store item link found."; |
| const char kInvalidWebstoreItemUrlError[] = |
| "Invalid Chrome Web Store item URL."; |
| |
| // chrome.webstore.install() calls generate an install ID so that the install's |
| // callbacks may be fired when the browser notifies us of install completion |
| // (successful or not) via OnInlineWebstoreInstallResponse. |
| int g_next_install_id = 0; |
| |
| } // anonymous namespace |
| |
| WebstoreBindings::WebstoreBindings(Dispatcher* dispatcher, |
| ChromeV8Context* context) |
| : ChromeV8Extension(dispatcher, context), |
| ChromeV8ExtensionHandler(context) { |
| RouteFunction("Install", |
| base::Bind(&WebstoreBindings::Install, base::Unretained(this))); |
| } |
| |
| void WebstoreBindings::Install( |
| const v8::FunctionCallbackInfo<v8::Value>& args) { |
| WebFrame* frame = WebFrame::frameForContext(context()->v8_context()); |
| if (!frame || !frame->view()) |
| return; |
| |
| content::RenderView* render_view = |
| content::RenderView::FromWebView(frame->view()); |
| if (!render_view) |
| return; |
| |
| std::string preferred_store_link_url; |
| if (!args[0]->IsUndefined()) { |
| if (args[0]->IsString()) { |
| preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0])); |
| } else { |
| v8::ThrowException(v8::String::New(kPreferredStoreLinkUrlNotAString)); |
| return; |
| } |
| } |
| |
| std::string webstore_item_id; |
| std::string error; |
| if (!GetWebstoreItemIdFromFrame( |
| frame, preferred_store_link_url, &webstore_item_id, &error)) { |
| v8::ThrowException(v8::String::New(error.c_str())); |
| return; |
| } |
| |
| int install_id = g_next_install_id++; |
| if (!args[1]->IsUndefined() && !args[1]->IsFunction()) { |
| v8::ThrowException(v8::String::New(kSuccessCallbackNotAFunctionError)); |
| return; |
| } |
| |
| if (!args[2]->IsUndefined() && !args[2]->IsFunction()) { |
| v8::ThrowException(v8::String::New(kFailureCallbackNotAFunctionError)); |
| return; |
| } |
| |
| Send(new ExtensionHostMsg_InlineWebstoreInstall( |
| render_view->GetRoutingID(), |
| install_id, |
| GetRoutingID(), |
| webstore_item_id, |
| frame->document().url())); |
| |
| args.GetReturnValue().Set(static_cast<int32_t>(install_id)); |
| } |
| |
| // static |
| bool WebstoreBindings::GetWebstoreItemIdFromFrame( |
| WebFrame* frame, const std::string& preferred_store_link_url, |
| std::string* webstore_item_id, std::string* error) { |
| if (frame != frame->top()) { |
| *error = kNotInTopFrameError; |
| return false; |
| } |
| |
| if (!WebUserGestureIndicator::isProcessingUserGesture()) { |
| *error = kNotUserGestureError; |
| return false; |
| } |
| |
| WebDocument document = frame->document(); |
| if (document.isNull()) { |
| *error = kNoWebstoreItemLinkFoundError; |
| return false; |
| } |
| |
| WebElement head = document.head(); |
| if (head.isNull()) { |
| *error = kNoWebstoreItemLinkFoundError; |
| return false; |
| } |
| |
| GURL webstore_base_url = |
| GURL(extension_urls::GetWebstoreItemDetailURLPrefix()); |
| WebNodeList children = head.childNodes(); |
| for (unsigned i = 0; i < children.length(); ++i) { |
| WebNode child = children.item(i); |
| if (!child.isElementNode()) |
| continue; |
| WebElement elem = child.to<WebElement>(); |
| |
| if (!elem.hasTagName("link") || !elem.hasAttribute("rel") || |
| !elem.hasAttribute("href")) |
| continue; |
| |
| std::string rel = elem.getAttribute("rel").utf8(); |
| if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation)) |
| continue; |
| |
| std::string webstore_url_string(elem.getAttribute("href").utf8()); |
| |
| if (!preferred_store_link_url.empty() && |
| preferred_store_link_url != webstore_url_string) { |
| continue; |
| } |
| |
| GURL webstore_url = GURL(webstore_url_string); |
| if (!webstore_url.is_valid()) { |
| *error = kInvalidWebstoreItemUrlError; |
| return false; |
| } |
| |
| if (webstore_url.scheme() != webstore_base_url.scheme() || |
| webstore_url.host() != webstore_base_url.host() || |
| !StartsWithASCII( |
| webstore_url.path(), webstore_base_url.path(), true)) { |
| *error = kInvalidWebstoreItemUrlError; |
| return false; |
| } |
| |
| std::string candidate_webstore_item_id = webstore_url.path().substr( |
| webstore_base_url.path().length()); |
| if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) { |
| *error = kInvalidWebstoreItemUrlError; |
| return false; |
| } |
| |
| std::string reconstructed_webstore_item_url_string = |
| extension_urls::GetWebstoreItemDetailURLPrefix() + |
| candidate_webstore_item_id; |
| if (reconstructed_webstore_item_url_string != webstore_url_string) { |
| *error = kInvalidWebstoreItemUrlError; |
| return false; |
| } |
| |
| *webstore_item_id = candidate_webstore_item_id; |
| return true; |
| } |
| |
| *error = kNoWebstoreItemLinkFoundError; |
| return false; |
| } |
| |
| bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) { |
| IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message) |
| IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse, |
| OnInlineWebstoreInstallResponse) |
| IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message") |
| IPC_END_MESSAGE_MAP() |
| return true; |
| } |
| |
| void WebstoreBindings::OnInlineWebstoreInstallResponse( |
| int install_id, |
| bool success, |
| const std::string& error) { |
| v8::HandleScope handle_scope(context()->isolate()); |
| v8::Context::Scope context_scope(context()->v8_context()); |
| v8::Handle<v8::Value> argv[] = { |
| v8::Integer::New(install_id), |
| v8::Boolean::New(success), |
| v8::String::New(error.c_str()) |
| }; |
| context()->module_system()->CallModuleMethod( |
| "webstore", "onInstallResponse", arraysize(argv), argv); |
| } |
| |
| } // namespace extensions |