Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / chrome / renderer / extensions / webstore_bindings.cc
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.
4
5 #include "chrome/renderer/extensions/webstore_bindings.h"
6
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"
20 #include "url/gurl.h"
21 #include "v8/include/v8.h"
22
23 using blink::WebDocument;
24 using blink::WebElement;
25 using blink::WebFrame;
26 using blink::WebNode;
27 using blink::WebNodeList;
28 using blink::WebUserGestureIndicator;
29
30 namespace extensions {
31
32 namespace {
33
34 const char kWebstoreLinkRelation[] = "chrome-webstore-item";
35
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.";
44
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;
49
50 } // anonymous namespace
51
52 WebstoreBindings::WebstoreBindings(ScriptContext* context)
53     : ObjectBackedNativeHandler(context), ChromeV8ExtensionHandler(context) {
54   RouteFunction("Install",
55                 base::Bind(&WebstoreBindings::Install, base::Unretained(this)));
56 }
57
58 void WebstoreBindings::Install(
59     const v8::FunctionCallbackInfo<v8::Value>& args) {
60   content::RenderView* render_view = context()->GetRenderView();
61   if (!render_view)
62     return;
63
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;
73
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]));
78   }
79
80   std::string webstore_item_id;
81   std::string error;
82   WebFrame* frame = context()->web_frame();
83
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()));
88     return;
89   }
90
91   int install_id = g_next_install_id++;
92
93   Send(new ExtensionHostMsg_InlineWebstoreInstall(render_view->GetRoutingID(),
94                                                   install_id,
95                                                   GetRoutingID(),
96                                                   webstore_item_id,
97                                                   frame->document().url(),
98                                                   listener_mask));
99
100   args.GetReturnValue().Set(static_cast<int32_t>(install_id));
101 }
102
103 // static
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;
109     return false;
110   }
111
112   if (!WebUserGestureIndicator::isProcessingUserGesture()) {
113     *error = kNotUserGestureError;
114     return false;
115   }
116
117   WebDocument document = frame->document();
118   if (document.isNull()) {
119     *error = kNoWebstoreItemLinkFoundError;
120     return false;
121   }
122
123   WebElement head = document.head();
124   if (head.isNull()) {
125     *error = kNoWebstoreItemLinkFoundError;
126     return false;
127   }
128
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())
135       continue;
136     WebElement elem = child.to<WebElement>();
137
138     if (!elem.hasHTMLTagName("link") || !elem.hasAttribute("rel") ||
139         !elem.hasAttribute("href"))
140       continue;
141
142     std::string rel = elem.getAttribute("rel").utf8();
143     if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
144       continue;
145
146     std::string webstore_url_string(elem.getAttribute("href").utf8());
147
148     if (!preferred_store_link_url.empty() &&
149         preferred_store_link_url != webstore_url_string) {
150       continue;
151     }
152
153     GURL webstore_url = GURL(webstore_url_string);
154     if (!webstore_url.is_valid()) {
155       *error = kInvalidWebstoreItemUrlError;
156       return false;
157     }
158
159     if (webstore_url.scheme() != webstore_base_url.scheme() ||
160         webstore_url.host() != webstore_base_url.host() ||
161         !StartsWithASCII(
162             webstore_url.path(), webstore_base_url.path(), true)) {
163       *error = kInvalidWebstoreItemUrlError;
164       return false;
165     }
166
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;
171       return false;
172     }
173
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;
179       return false;
180     }
181
182     *webstore_item_id = candidate_webstore_item_id;
183     return true;
184   }
185
186   *error = kNoWebstoreItemLinkFoundError;
187   return false;
188 }
189
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()
200   return true;
201 }
202
203 void WebstoreBindings::OnInlineWebstoreInstallResponse(
204     int install_id,
205     bool success,
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)])
217   };
218   context()->module_system()->CallModuleMethod(
219       "webstore", "onInstallResponse", arraysize(argv), argv);
220 }
221
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;
229       break;
230     case api::webstore::INSTALL_STAGE_INSTALLING:
231       stage_string = api::webstore::kInstallStageInstalling;
232       break;
233   }
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);
241 }
242
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);
251 }
252
253 }  // namespace extensions