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.
5 #include "chrome/renderer/extensions/webstore_bindings.h"
7 #include "base/strings/string_util.h"
8 #include "chrome/common/extensions/extension.h"
9 #include "chrome/common/extensions/extension_constants.h"
10 #include "chrome/common/extensions/extension_messages.h"
11 #include "chrome/renderer/extensions/chrome_v8_context.h"
12 #include "content/public/renderer/render_view.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"
20 #include "v8/include/v8.h"
22 using WebKit::WebDocument;
23 using WebKit::WebElement;
24 using WebKit::WebFrame;
25 using WebKit::WebNode;
26 using WebKit::WebNodeList;
27 using WebKit::WebUserGestureIndicator;
29 namespace extensions {
33 const char kWebstoreLinkRelation[] = "chrome-webstore-item";
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.";
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;
55 } // anonymous namespace
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)));
65 void WebstoreBindings::Install(
66 const v8::FunctionCallbackInfo<v8::Value>& args) {
67 WebFrame* frame = WebFrame::frameForContext(context()->v8_context());
68 if (!frame || !frame->view())
71 content::RenderView* render_view =
72 content::RenderView::FromWebView(frame->view());
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]));
81 v8::ThrowException(v8::String::New(kPreferredStoreLinkUrlNotAString));
86 std::string webstore_item_id;
88 if (!GetWebstoreItemIdFromFrame(
89 frame, preferred_store_link_url, &webstore_item_id, &error)) {
90 v8::ThrowException(v8::String::New(error.c_str()));
94 int install_id = g_next_install_id++;
95 if (!args[1]->IsUndefined() && !args[1]->IsFunction()) {
96 v8::ThrowException(v8::String::New(kSuccessCallbackNotAFunctionError));
100 if (!args[2]->IsUndefined() && !args[2]->IsFunction()) {
101 v8::ThrowException(v8::String::New(kFailureCallbackNotAFunctionError));
105 Send(new ExtensionHostMsg_InlineWebstoreInstall(
106 render_view->GetRoutingID(),
110 frame->document().url()));
112 args.GetReturnValue().Set(static_cast<int32_t>(install_id));
116 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
117 WebFrame* frame, const std::string& preferred_store_link_url,
118 std::string* webstore_item_id, std::string* error) {
119 if (frame != frame->top()) {
120 *error = kNotInTopFrameError;
124 if (!WebUserGestureIndicator::isProcessingUserGesture()) {
125 *error = kNotUserGestureError;
129 WebDocument document = frame->document();
130 if (document.isNull()) {
131 *error = kNoWebstoreItemLinkFoundError;
135 WebElement head = document.head();
137 *error = kNoWebstoreItemLinkFoundError;
141 GURL webstore_base_url =
142 GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
143 WebNodeList children = head.childNodes();
144 for (unsigned i = 0; i < children.length(); ++i) {
145 WebNode child = children.item(i);
146 if (!child.isElementNode())
148 WebElement elem = child.to<WebElement>();
150 if (!elem.hasTagName("link") || !elem.hasAttribute("rel") ||
151 !elem.hasAttribute("href"))
154 std::string rel = elem.getAttribute("rel").utf8();
155 if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
158 std::string webstore_url_string(elem.getAttribute("href").utf8());
160 if (!preferred_store_link_url.empty() &&
161 preferred_store_link_url != webstore_url_string) {
165 GURL webstore_url = GURL(webstore_url_string);
166 if (!webstore_url.is_valid()) {
167 *error = kInvalidWebstoreItemUrlError;
171 if (webstore_url.scheme() != webstore_base_url.scheme() ||
172 webstore_url.host() != webstore_base_url.host() ||
174 webstore_url.path(), webstore_base_url.path(), true)) {
175 *error = kInvalidWebstoreItemUrlError;
179 std::string candidate_webstore_item_id = webstore_url.path().substr(
180 webstore_base_url.path().length());
181 if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) {
182 *error = kInvalidWebstoreItemUrlError;
186 std::string reconstructed_webstore_item_url_string =
187 extension_urls::GetWebstoreItemDetailURLPrefix() +
188 candidate_webstore_item_id;
189 if (reconstructed_webstore_item_url_string != webstore_url_string) {
190 *error = kInvalidWebstoreItemUrlError;
194 *webstore_item_id = candidate_webstore_item_id;
198 *error = kNoWebstoreItemLinkFoundError;
202 bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) {
203 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message)
204 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse,
205 OnInlineWebstoreInstallResponse)
206 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
207 IPC_END_MESSAGE_MAP()
211 void WebstoreBindings::OnInlineWebstoreInstallResponse(
214 const std::string& error) {
215 v8::HandleScope handle_scope(context()->isolate());
216 v8::Context::Scope context_scope(context()->v8_context());
217 v8::Handle<v8::Value> argv[] = {
218 v8::Integer::New(install_id),
219 v8::Boolean::New(success),
220 v8::String::New(error.c_str())
222 context()->module_system()->CallModuleMethod(
223 "webstore", "onInstallResponse", arraysize(argv), argv);
226 } // namespace extensions