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/api/webstore/webstore_api_constants.h"
9 #include "chrome/common/extensions/chrome_extension_messages.h"
10 #include "components/crx_file/id_util.h"
11 #include "content/public/renderer/render_view.h"
12 #include "extensions/common/extension.h"
13 #include "extensions/common/extension_urls.h"
14 #include "extensions/renderer/script_context.h"
15 #include "third_party/WebKit/public/web/WebDocument.h"
16 #include "third_party/WebKit/public/web/WebElement.h"
17 #include "third_party/WebKit/public/web/WebNode.h"
18 #include "third_party/WebKit/public/web/WebNodeList.h"
19 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
21 #include "v8/include/v8.h"
23 using blink::WebDocument
;
24 using blink::WebElement
;
25 using blink::WebFrame
;
27 using blink::WebNodeList
;
28 using blink::WebUserGestureIndicator
;
30 namespace extensions
{
34 const char kWebstoreLinkRelation
[] = "chrome-webstore-item";
36 const char kNotInTopFrameError
[] =
37 "Chrome Web Store installations can only be started by the top frame.";
38 const char kNotUserGestureError
[] =
39 "Chrome Web Store installations can only be initated by a user gesture.";
40 const char kNoWebstoreItemLinkFoundError
[] =
41 "No Chrome Web Store item link found.";
42 const char kInvalidWebstoreItemUrlError
[] =
43 "Invalid Chrome Web Store item URL.";
45 // chrome.webstore.install() calls generate an install ID so that the install's
46 // callbacks may be fired when the browser notifies us of install completion
47 // (successful or not) via OnInlineWebstoreInstallResponse.
48 int g_next_install_id
= 0;
50 } // anonymous namespace
52 WebstoreBindings::WebstoreBindings(ScriptContext
* context
)
53 : ObjectBackedNativeHandler(context
) {
54 RouteFunction("Install",
55 base::Bind(&WebstoreBindings::Install
, base::Unretained(this)));
58 void WebstoreBindings::Install(
59 const v8::FunctionCallbackInfo
<v8::Value
>& args
) {
60 content::RenderView
* render_view
= context()->GetRenderView();
64 // The first two arguments indicate whether or not there are install stage
65 // or download progress listeners.
66 int listener_mask
= 0;
67 CHECK(args
[0]->IsBoolean());
68 if (args
[0]->BooleanValue())
69 listener_mask
|= api::webstore::INSTALL_STAGE_LISTENER
;
70 CHECK(args
[1]->IsBoolean());
71 if (args
[1]->BooleanValue())
72 listener_mask
|= api::webstore::DOWNLOAD_PROGRESS_LISTENER
;
74 std::string preferred_store_link_url
;
75 if (!args
[2]->IsUndefined()) {
76 CHECK(args
[2]->IsString());
77 preferred_store_link_url
= std::string(*v8::String::Utf8Value(args
[2]));
80 std::string webstore_item_id
;
82 WebFrame
* frame
= context()->web_frame();
84 if (!GetWebstoreItemIdFromFrame(
85 frame
, preferred_store_link_url
, &webstore_item_id
, &error
)) {
86 args
.GetIsolate()->ThrowException(
87 v8::String::NewFromUtf8(args
.GetIsolate(), error
.c_str()));
91 int install_id
= g_next_install_id
++;
93 Send(new ExtensionHostMsg_InlineWebstoreInstall(render_view
->GetRoutingID(),
97 frame
->document().url(),
100 args
.GetReturnValue().Set(static_cast<int32_t>(install_id
));
104 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
106 const std::string
& preferred_store_link_url
,
107 std::string
* webstore_item_id
,
108 std::string
* error
) {
109 if (frame
!= frame
->top()) {
110 *error
= kNotInTopFrameError
;
114 if (!WebUserGestureIndicator::isProcessingUserGesture()) {
115 *error
= kNotUserGestureError
;
119 WebDocument document
= frame
->document();
120 if (document
.isNull()) {
121 *error
= kNoWebstoreItemLinkFoundError
;
125 WebElement head
= document
.head();
127 *error
= kNoWebstoreItemLinkFoundError
;
131 GURL webstore_base_url
=
132 GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
133 WebNodeList children
= head
.childNodes();
134 for (unsigned i
= 0; i
< children
.length(); ++i
) {
135 WebNode child
= children
.item(i
);
136 if (!child
.isElementNode())
138 WebElement elem
= child
.to
<WebElement
>();
140 if (!elem
.hasHTMLTagName("link") || !elem
.hasAttribute("rel") ||
141 !elem
.hasAttribute("href"))
144 std::string rel
= elem
.getAttribute("rel").utf8();
145 if (!LowerCaseEqualsASCII(rel
, kWebstoreLinkRelation
))
148 std::string
webstore_url_string(elem
.getAttribute("href").utf8());
150 if (!preferred_store_link_url
.empty() &&
151 preferred_store_link_url
!= webstore_url_string
) {
155 GURL webstore_url
= GURL(webstore_url_string
);
156 if (!webstore_url
.is_valid()) {
157 *error
= kInvalidWebstoreItemUrlError
;
161 if (webstore_url
.scheme() != webstore_base_url
.scheme() ||
162 webstore_url
.host() != webstore_base_url
.host() ||
164 webstore_url
.path(), webstore_base_url
.path(), true)) {
165 *error
= kInvalidWebstoreItemUrlError
;
169 std::string candidate_webstore_item_id
= webstore_url
.path().substr(
170 webstore_base_url
.path().length());
171 if (!crx_file::id_util::IdIsValid(candidate_webstore_item_id
)) {
172 *error
= kInvalidWebstoreItemUrlError
;
176 std::string reconstructed_webstore_item_url_string
=
177 extension_urls::GetWebstoreItemDetailURLPrefix() +
178 candidate_webstore_item_id
;
179 if (reconstructed_webstore_item_url_string
!= webstore_url_string
) {
180 *error
= kInvalidWebstoreItemUrlError
;
184 *webstore_item_id
= candidate_webstore_item_id
;
188 *error
= kNoWebstoreItemLinkFoundError
;
192 bool WebstoreBindings::OnMessageReceived(const IPC::Message
& message
) {
193 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings
, message
)
194 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse
,
195 OnInlineWebstoreInstallResponse
)
196 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallStageChanged
,
197 OnInlineInstallStageChanged
)
198 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallDownloadProgress
,
199 OnInlineInstallDownloadProgress
)
200 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
201 IPC_END_MESSAGE_MAP()
205 void WebstoreBindings::OnInlineWebstoreInstallResponse(
208 const std::string
& error
,
209 webstore_install::Result result
) {
210 v8::Isolate
* isolate
= context()->isolate();
211 v8::HandleScope
handle_scope(isolate
);
212 v8::Context::Scope
context_scope(context()->v8_context());
213 v8::Handle
<v8::Value
> argv
[] = {
214 v8::Integer::New(isolate
, install_id
),
215 v8::Boolean::New(isolate
, success
),
216 v8::String::NewFromUtf8(isolate
, error
.c_str()),
217 v8::String::NewFromUtf8(
218 isolate
, api::webstore::kInstallResultCodes
[static_cast<int>(result
)])
220 context()->module_system()->CallModuleMethod(
221 "webstore", "onInstallResponse", arraysize(argv
), argv
);
224 void WebstoreBindings::OnInlineInstallStageChanged(int stage
) {
225 const char* stage_string
= NULL
;
226 api::webstore::InstallStage install_stage
=
227 static_cast<api::webstore::InstallStage
>(stage
);
228 switch (install_stage
) {
229 case api::webstore::INSTALL_STAGE_DOWNLOADING
:
230 stage_string
= api::webstore::kInstallStageDownloading
;
232 case api::webstore::INSTALL_STAGE_INSTALLING
:
233 stage_string
= api::webstore::kInstallStageInstalling
;
236 v8::Isolate
* isolate
= context()->isolate();
237 v8::HandleScope
handle_scope(isolate
);
238 v8::Context::Scope
context_scope(context()->v8_context());
239 v8::Handle
<v8::Value
> argv
[] = {
240 v8::String::NewFromUtf8(isolate
, stage_string
)};
241 context()->module_system()->CallModuleMethod(
242 "webstore", "onInstallStageChanged", arraysize(argv
), argv
);
245 void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded
) {
246 v8::Isolate
* isolate
= context()->isolate();
247 v8::HandleScope
handle_scope(isolate
);
248 v8::Context::Scope
context_scope(context()->v8_context());
249 v8::Handle
<v8::Value
> argv
[] = {
250 v8::Number::New(isolate
, percent_downloaded
/ 100.0)};
251 context()->module_system()->CallModuleMethod(
252 "webstore", "onDownloadProgress", arraysize(argv
), argv
);
255 } // namespace extensions