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_frame.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/WebLocalFrame.h"
18 #include "third_party/WebKit/public/web/WebNode.h"
19 #include "third_party/WebKit/public/web/WebNodeList.h"
20 #include "third_party/WebKit/public/web/WebUserGestureIndicator.h"
22 #include "v8/include/v8.h"
24 using blink::WebDocument
;
25 using blink::WebElement
;
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::RenderFrame
* render_frame
= context()->GetRenderFrame();
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 blink::WebLocalFrame
* 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(
94 render_frame
->GetRoutingID(), install_id
, GetRoutingID(),
95 webstore_item_id
, frame
->document().url(), listener_mask
));
97 args
.GetReturnValue().Set(static_cast<int32_t>(install_id
));
101 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
102 blink::WebLocalFrame
* frame
,
103 const std::string
& preferred_store_link_url
,
104 std::string
* webstore_item_id
,
105 std::string
* error
) {
106 if (frame
!= frame
->top()) {
107 *error
= kNotInTopFrameError
;
111 if (!WebUserGestureIndicator::isProcessingUserGesture()) {
112 *error
= kNotUserGestureError
;
116 WebDocument document
= frame
->document();
117 if (document
.isNull()) {
118 *error
= kNoWebstoreItemLinkFoundError
;
122 WebElement head
= document
.head();
124 *error
= kNoWebstoreItemLinkFoundError
;
128 GURL webstore_base_url
=
129 GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
130 WebNodeList children
= head
.childNodes();
131 for (unsigned i
= 0; i
< children
.length(); ++i
) {
132 WebNode child
= children
.item(i
);
133 if (!child
.isElementNode())
135 WebElement elem
= child
.to
<WebElement
>();
137 if (!elem
.hasHTMLTagName("link") || !elem
.hasAttribute("rel") ||
138 !elem
.hasAttribute("href"))
141 std::string rel
= elem
.getAttribute("rel").utf8();
142 if (!base::LowerCaseEqualsASCII(rel
, kWebstoreLinkRelation
))
145 std::string
webstore_url_string(elem
.getAttribute("href").utf8());
147 if (!preferred_store_link_url
.empty() &&
148 preferred_store_link_url
!= webstore_url_string
) {
152 GURL webstore_url
= GURL(webstore_url_string
);
153 if (!webstore_url
.is_valid()) {
154 *error
= kInvalidWebstoreItemUrlError
;
158 if (webstore_url
.scheme() != webstore_base_url
.scheme() ||
159 webstore_url
.host() != webstore_base_url
.host() ||
160 !base::StartsWith(webstore_url
.path(), webstore_base_url
.path(),
161 base::CompareCase::SENSITIVE
)) {
162 *error
= kInvalidWebstoreItemUrlError
;
166 std::string candidate_webstore_item_id
= webstore_url
.path().substr(
167 webstore_base_url
.path().length());
168 if (!crx_file::id_util::IdIsValid(candidate_webstore_item_id
)) {
169 *error
= kInvalidWebstoreItemUrlError
;
173 std::string reconstructed_webstore_item_url_string
=
174 extension_urls::GetWebstoreItemDetailURLPrefix() +
175 candidate_webstore_item_id
;
176 if (reconstructed_webstore_item_url_string
!= webstore_url_string
) {
177 *error
= kInvalidWebstoreItemUrlError
;
181 *webstore_item_id
= candidate_webstore_item_id
;
185 *error
= kNoWebstoreItemLinkFoundError
;
189 bool WebstoreBindings::OnMessageReceived(const IPC::Message
& message
) {
190 IPC_BEGIN_MESSAGE_MAP(WebstoreBindings
, message
)
191 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse
,
192 OnInlineWebstoreInstallResponse
)
193 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallStageChanged
,
194 OnInlineInstallStageChanged
)
195 IPC_MESSAGE_HANDLER(ExtensionMsg_InlineInstallDownloadProgress
,
196 OnInlineInstallDownloadProgress
)
197 IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
198 IPC_END_MESSAGE_MAP()
202 void WebstoreBindings::OnInlineWebstoreInstallResponse(
205 const std::string
& error
,
206 webstore_install::Result result
) {
207 v8::Isolate
* isolate
= context()->isolate();
208 v8::HandleScope
handle_scope(isolate
);
209 v8::Context::Scope
context_scope(context()->v8_context());
210 v8::Local
<v8::Value
> argv
[] = {
211 v8::Integer::New(isolate
, install_id
),
212 v8::Boolean::New(isolate
, success
),
213 v8::String::NewFromUtf8(isolate
, error
.c_str()),
214 v8::String::NewFromUtf8(
215 isolate
, api::webstore::kInstallResultCodes
[static_cast<int>(result
)])
217 context()->module_system()->CallModuleMethod(
218 "webstore", "onInstallResponse", arraysize(argv
), argv
);
221 void WebstoreBindings::OnInlineInstallStageChanged(int stage
) {
222 const char* stage_string
= NULL
;
223 api::webstore::InstallStage install_stage
=
224 static_cast<api::webstore::InstallStage
>(stage
);
225 switch (install_stage
) {
226 case api::webstore::INSTALL_STAGE_DOWNLOADING
:
227 stage_string
= api::webstore::kInstallStageDownloading
;
229 case api::webstore::INSTALL_STAGE_INSTALLING
:
230 stage_string
= api::webstore::kInstallStageInstalling
;
233 v8::Isolate
* isolate
= context()->isolate();
234 v8::HandleScope
handle_scope(isolate
);
235 v8::Context::Scope
context_scope(context()->v8_context());
236 v8::Local
<v8::Value
> argv
[] = {
237 v8::String::NewFromUtf8(isolate
, stage_string
)};
238 context()->module_system()->CallModuleMethod(
239 "webstore", "onInstallStageChanged", arraysize(argv
), argv
);
242 void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded
) {
243 v8::Isolate
* isolate
= context()->isolate();
244 v8::HandleScope
handle_scope(isolate
);
245 v8::Context::Scope
context_scope(context()->v8_context());
246 v8::Local
<v8::Value
> argv
[] = {
247 v8::Number::New(isolate
, percent_downloaded
/ 100.0)};
248 context()->module_system()->CallModuleMethod(
249 "webstore", "onDownloadProgress", arraysize(argv
), argv
);
252 } // namespace extensions