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
), ChromeV8ExtensionHandler(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(
105 WebFrame
* frame
, const std::string
& preferred_store_link_url
,
106 std::string
* webstore_item_id
, std::string
* error
) {
107 if (frame
!= frame
->top()) {
108 *error
= kNotInTopFrameError
;
112 if (!WebUserGestureIndicator::isProcessingUserGesture()) {
113 *error
= kNotUserGestureError
;
117 WebDocument document
= frame
->document();
118 if (document
.isNull()) {
119 *error
= kNoWebstoreItemLinkFoundError
;
123 WebElement head
= document
.head();
125 *error
= kNoWebstoreItemLinkFoundError
;
129 GURL webstore_base_url
=
130 GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
131 WebNodeList children
= head
.childNodes();
132 for (unsigned i
= 0; i
< children
.length(); ++i
) {
133 WebNode child
= children
.item(i
);
134 if (!child
.isElementNode())
136 WebElement elem
= child
.to
<WebElement
>();
138 if (!elem
.hasHTMLTagName("link") || !elem
.hasAttribute("rel") ||
139 !elem
.hasAttribute("href"))
142 std::string rel
= elem
.getAttribute("rel").utf8();
143 if (!LowerCaseEqualsASCII(rel
, kWebstoreLinkRelation
))
146 std::string
webstore_url_string(elem
.getAttribute("href").utf8());
148 if (!preferred_store_link_url
.empty() &&
149 preferred_store_link_url
!= webstore_url_string
) {
153 GURL webstore_url
= GURL(webstore_url_string
);
154 if (!webstore_url
.is_valid()) {
155 *error
= kInvalidWebstoreItemUrlError
;
159 if (webstore_url
.scheme() != webstore_base_url
.scheme() ||
160 webstore_url
.host() != webstore_base_url
.host() ||
162 webstore_url
.path(), webstore_base_url
.path(), true)) {
163 *error
= kInvalidWebstoreItemUrlError
;
167 std::string candidate_webstore_item_id
= webstore_url
.path().substr(
168 webstore_base_url
.path().length());
169 if (!crx_file::id_util::IdIsValid(candidate_webstore_item_id
)) {
170 *error
= kInvalidWebstoreItemUrlError
;
174 std::string reconstructed_webstore_item_url_string
=
175 extension_urls::GetWebstoreItemDetailURLPrefix() +
176 candidate_webstore_item_id
;
177 if (reconstructed_webstore_item_url_string
!= webstore_url_string
) {
178 *error
= kInvalidWebstoreItemUrlError
;
182 *webstore_item_id
= candidate_webstore_item_id
;
186 *error
= kNoWebstoreItemLinkFoundError
;
190 bool WebstoreBindings::OnMessageReceived(const IPC::Message
& message
) {
191 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings
, message
)
192 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse
,
193 OnInlineWebstoreInstallResponse
)
194 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallStageChanged
,
195 OnInlineInstallStageChanged
)
196 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallDownloadProgress
,
197 OnInlineInstallDownloadProgress
)
198 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
199 IPC_END_MESSAGE_MAP()
203 void WebstoreBindings::OnInlineWebstoreInstallResponse(
206 const std::string
& error
,
207 webstore_install::Result result
) {
208 v8::Isolate
* isolate
= context()->isolate();
209 v8::HandleScope
handle_scope(isolate
);
210 v8::Context::Scope
context_scope(context()->v8_context());
211 v8::Handle
<v8::Value
> argv
[] = {
212 v8::Integer::New(isolate
, install_id
),
213 v8::Boolean::New(isolate
, success
),
214 v8::String::NewFromUtf8(isolate
, error
.c_str()),
215 v8::String::NewFromUtf8(
216 isolate
, api::webstore::kInstallResultCodes
[static_cast<int>(result
)])
218 context()->module_system()->CallModuleMethod(
219 "webstore", "onInstallResponse", arraysize(argv
), argv
);
222 void WebstoreBindings::OnInlineInstallStageChanged(int stage
) {
223 const char* stage_string
= NULL
;
224 api::webstore::InstallStage install_stage
=
225 static_cast<api::webstore::InstallStage
>(stage
);
226 switch (install_stage
) {
227 case api::webstore::INSTALL_STAGE_DOWNLOADING
:
228 stage_string
= api::webstore::kInstallStageDownloading
;
230 case api::webstore::INSTALL_STAGE_INSTALLING
:
231 stage_string
= api::webstore::kInstallStageInstalling
;
234 v8::Isolate
* isolate
= context()->isolate();
235 v8::HandleScope
handle_scope(isolate
);
236 v8::Context::Scope
context_scope(context()->v8_context());
237 v8::Handle
<v8::Value
> argv
[] = {
238 v8::String::NewFromUtf8(isolate
, stage_string
)};
239 context()->module_system()->CallModuleMethod(
240 "webstore", "onInstallStageChanged", arraysize(argv
), argv
);
243 void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded
) {
244 v8::Isolate
* isolate
= context()->isolate();
245 v8::HandleScope
handle_scope(isolate
);
246 v8::Context::Scope
context_scope(context()->v8_context());
247 v8::Handle
<v8::Value
> argv
[] = {
248 v8::Number::New(isolate
, percent_downloaded
/ 100.0)};
249 context()->module_system()->CallModuleMethod(
250 "webstore", "onDownloadProgress", arraysize(argv
), argv
);
253 } // namespace extensions