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_constants.h"
9 #include "chrome/renderer/extensions/chrome_v8_context.h"
10 #include "content/public/renderer/render_view.h"
11 #include "extensions/common/extension.h"
12 #include "extensions/common/extension_messages.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 blink::WebDocument
;
23 using blink::WebElement
;
24 using blink::WebFrame
;
26 using blink::WebNodeList
;
27 using blink::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 args
.GetIsolate()->ThrowException(v8::String::NewFromUtf8(
82 args
.GetIsolate(), kPreferredStoreLinkUrlNotAString
));
87 std::string webstore_item_id
;
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()));
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
));
103 if (!args
[2]->IsUndefined() && !args
[2]->IsFunction()) {
104 args
.GetIsolate()->ThrowException(v8::String::NewFromUtf8(
105 args
.GetIsolate(), kFailureCallbackNotAFunctionError
));
109 Send(new ExtensionHostMsg_InlineWebstoreInstall(
110 render_view
->GetRoutingID(),
114 frame
->document().url()));
116 args
.GetReturnValue().Set(static_cast<int32_t>(install_id
));
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
;
128 if (!WebUserGestureIndicator::isProcessingUserGesture()) {
129 *error
= kNotUserGestureError
;
133 WebDocument document
= frame
->document();
134 if (document
.isNull()) {
135 *error
= kNoWebstoreItemLinkFoundError
;
139 WebElement head
= document
.head();
141 *error
= kNoWebstoreItemLinkFoundError
;
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())
152 WebElement elem
= child
.to
<WebElement
>();
154 if (!elem
.hasTagName("link") || !elem
.hasAttribute("rel") ||
155 !elem
.hasAttribute("href"))
158 std::string rel
= elem
.getAttribute("rel").utf8();
159 if (!LowerCaseEqualsASCII(rel
, kWebstoreLinkRelation
))
162 std::string
webstore_url_string(elem
.getAttribute("href").utf8());
164 if (!preferred_store_link_url
.empty() &&
165 preferred_store_link_url
!= webstore_url_string
) {
169 GURL webstore_url
= GURL(webstore_url_string
);
170 if (!webstore_url
.is_valid()) {
171 *error
= kInvalidWebstoreItemUrlError
;
175 if (webstore_url
.scheme() != webstore_base_url
.scheme() ||
176 webstore_url
.host() != webstore_base_url
.host() ||
178 webstore_url
.path(), webstore_base_url
.path(), true)) {
179 *error
= kInvalidWebstoreItemUrlError
;
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
;
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
;
198 *webstore_item_id
= candidate_webstore_item_id
;
202 *error
= kNoWebstoreItemLinkFoundError
;
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()
215 void WebstoreBindings::OnInlineWebstoreInstallResponse(
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(isolate
, install_id
),
224 v8::Boolean::New(isolate
, success
),
225 v8::String::NewFromUtf8(isolate
, error
.c_str())
227 context()->module_system()->CallModuleMethod(
228 "webstore", "onInstallResponse", arraysize(argv
), argv
);
231 } // namespace extensions