Upstream version 7.36.149.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 "chrome/common/extensions/extension_constants.h"
11 #include "content/public/renderer/render_view.h"
12 #include "extensions/common/extension.h"
13 #include "extensions/renderer/script_context.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"
19 #include "url/gurl.h"
20 #include "v8/include/v8.h"
21
22 using blink::WebDocument;
23 using blink::WebElement;
24 using blink::WebFrame;
25 using blink::WebNode;
26 using blink::WebNodeList;
27 using blink::WebUserGestureIndicator;
28
29 namespace extensions {
30
31 namespace {
32
33 const char kWebstoreLinkRelation[] = "chrome-webstore-item";
34
35 const char kNotInTopFrameError[] =
36     "Chrome Web Store installations can only be started by the top frame.";
37 const char kNotUserGestureError[] =
38     "Chrome Web Store installations can only be initated by a user gesture.";
39 const char kNoWebstoreItemLinkFoundError[] =
40     "No Chrome Web Store item link found.";
41 const char kInvalidWebstoreItemUrlError[] =
42     "Invalid Chrome Web Store item URL.";
43
44 // chrome.webstore.install() calls generate an install ID so that the install's
45 // callbacks may be fired when the browser notifies us of install completion
46 // (successful or not) via OnInlineWebstoreInstallResponse.
47 int g_next_install_id = 0;
48
49 } // anonymous namespace
50
51 WebstoreBindings::WebstoreBindings(ScriptContext* context)
52     : ObjectBackedNativeHandler(context), ChromeV8ExtensionHandler(context) {
53   RouteFunction("Install",
54                 base::Bind(&WebstoreBindings::Install, base::Unretained(this)));
55 }
56
57 void WebstoreBindings::Install(
58     const v8::FunctionCallbackInfo<v8::Value>& args) {
59   content::RenderView* render_view = context()->GetRenderView();
60   if (!render_view)
61     return;
62
63   // The first two arguments indicate whether or not there are install stage
64   // or download progress listeners.
65   int listener_mask = 0;
66   CHECK(args[0]->IsBoolean());
67   if (args[0]->BooleanValue())
68     listener_mask |= api::webstore::INSTALL_STAGE_LISTENER;
69   CHECK(args[1]->IsBoolean());
70   if (args[1]->BooleanValue())
71     listener_mask |= api::webstore::DOWNLOAD_PROGRESS_LISTENER;
72
73   std::string preferred_store_link_url;
74   if (!args[2]->IsUndefined()) {
75     CHECK(args[2]->IsString());
76     preferred_store_link_url = std::string(*v8::String::Utf8Value(args[2]));
77   }
78
79   std::string webstore_item_id;
80   std::string error;
81   WebFrame* frame = context()->web_frame();
82
83   if (!GetWebstoreItemIdFromFrame(
84       frame, preferred_store_link_url, &webstore_item_id, &error)) {
85     args.GetIsolate()->ThrowException(
86         v8::String::NewFromUtf8(args.GetIsolate(), error.c_str()));
87     return;
88   }
89
90   int install_id = g_next_install_id++;
91
92   Send(new ExtensionHostMsg_InlineWebstoreInstall(render_view->GetRoutingID(),
93                                                   install_id,
94                                                   GetRoutingID(),
95                                                   webstore_item_id,
96                                                   frame->document().url(),
97                                                   listener_mask));
98
99   args.GetReturnValue().Set(static_cast<int32_t>(install_id));
100 }
101
102 // static
103 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
104       WebFrame* frame, const std::string& preferred_store_link_url,
105       std::string* webstore_item_id, std::string* error) {
106   if (frame != frame->top()) {
107     *error = kNotInTopFrameError;
108     return false;
109   }
110
111   if (!WebUserGestureIndicator::isProcessingUserGesture()) {
112     *error = kNotUserGestureError;
113     return false;
114   }
115
116   WebDocument document = frame->document();
117   if (document.isNull()) {
118     *error = kNoWebstoreItemLinkFoundError;
119     return false;
120   }
121
122   WebElement head = document.head();
123   if (head.isNull()) {
124     *error = kNoWebstoreItemLinkFoundError;
125     return false;
126   }
127
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())
134       continue;
135     WebElement elem = child.to<WebElement>();
136
137     if (!elem.hasTagName("link") || !elem.hasAttribute("rel") ||
138         !elem.hasAttribute("href"))
139       continue;
140
141     std::string rel = elem.getAttribute("rel").utf8();
142     if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
143       continue;
144
145     std::string webstore_url_string(elem.getAttribute("href").utf8());
146
147     if (!preferred_store_link_url.empty() &&
148         preferred_store_link_url != webstore_url_string) {
149       continue;
150     }
151
152     GURL webstore_url = GURL(webstore_url_string);
153     if (!webstore_url.is_valid()) {
154       *error = kInvalidWebstoreItemUrlError;
155       return false;
156     }
157
158     if (webstore_url.scheme() != webstore_base_url.scheme() ||
159         webstore_url.host() != webstore_base_url.host() ||
160         !StartsWithASCII(
161             webstore_url.path(), webstore_base_url.path(), true)) {
162       *error = kInvalidWebstoreItemUrlError;
163       return false;
164     }
165
166     std::string candidate_webstore_item_id = webstore_url.path().substr(
167         webstore_base_url.path().length());
168     if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) {
169       *error = kInvalidWebstoreItemUrlError;
170       return false;
171     }
172
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;
178       return false;
179     }
180
181     *webstore_item_id = candidate_webstore_item_id;
182     return true;
183   }
184
185   *error = kNoWebstoreItemLinkFoundError;
186   return false;
187 }
188
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()
199   return true;
200 }
201
202 void WebstoreBindings::OnInlineWebstoreInstallResponse(
203     int install_id,
204     bool success,
205     const std::string& error) {
206   v8::Isolate* isolate = context()->isolate();
207   v8::HandleScope handle_scope(isolate);
208   v8::Context::Scope context_scope(context()->v8_context());
209   v8::Handle<v8::Value> argv[] = {
210     v8::Integer::New(isolate, install_id),
211     v8::Boolean::New(isolate, success),
212     v8::String::NewFromUtf8(isolate, error.c_str())
213   };
214   context()->module_system()->CallModuleMethod(
215       "webstore", "onInstallResponse", arraysize(argv), argv);
216 }
217
218 void WebstoreBindings::OnInlineInstallStageChanged(int stage) {
219   const char* stage_string = NULL;
220   api::webstore::InstallStage install_stage =
221       static_cast<api::webstore::InstallStage>(stage);
222   switch (install_stage) {
223     case api::webstore::INSTALL_STAGE_DOWNLOADING:
224       stage_string = api::webstore::kInstallStageDownloading;
225       break;
226     case api::webstore::INSTALL_STAGE_INSTALLING:
227       stage_string = api::webstore::kInstallStageInstalling;
228       break;
229   }
230   v8::Isolate* isolate = context()->isolate();
231   v8::HandleScope handle_scope(isolate);
232   v8::Context::Scope context_scope(context()->v8_context());
233   v8::Handle<v8::Value> argv[] = {
234       v8::String::NewFromUtf8(isolate, stage_string)};
235   context()->module_system()->CallModuleMethod(
236       "webstore", "onInstallStageChanged", arraysize(argv), argv);
237 }
238
239 void WebstoreBindings::OnInlineInstallDownloadProgress(int percent_downloaded) {
240   v8::Isolate* isolate = context()->isolate();
241   v8::HandleScope handle_scope(isolate);
242   v8::Context::Scope context_scope(context()->v8_context());
243   v8::Handle<v8::Value> argv[] = {
244       v8::Number::New(isolate, percent_downloaded / 100.0)};
245   context()->module_system()->CallModuleMethod(
246       "webstore", "onDownloadProgress", arraysize(argv), argv);
247 }
248
249 }  // namespace extensions