- add sources.
[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/extension.h"
9 #include "chrome/common/extensions/extension_constants.h"
10 #include "chrome/common/extensions/extension_messages.h"
11 #include "chrome/renderer/extensions/chrome_v8_context.h"
12 #include "content/public/renderer/render_view.h"
13 #include "grit/renderer_resources.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 WebKit::WebDocument;
23 using WebKit::WebElement;
24 using WebKit::WebFrame;
25 using WebKit::WebNode;
26 using WebKit::WebNodeList;
27 using WebKit::WebUserGestureIndicator;
28
29 namespace extensions {
30
31 namespace {
32
33 const char kWebstoreLinkRelation[] = "chrome-webstore-item";
34
35 const char kPreferredStoreLinkUrlNotAString[] =
36     "The Chrome Web Store item link URL parameter must be a string.";
37 const char kSuccessCallbackNotAFunctionError[] =
38     "The success callback parameter must be a function.";
39 const char kFailureCallbackNotAFunctionError[] =
40     "The failure callback parameter must be a function.";
41 const char kNotInTopFrameError[] =
42     "Chrome Web Store installations can only be started by the top frame.";
43 const char kNotUserGestureError[] =
44     "Chrome Web Store installations can only be initated by a user gesture.";
45 const char kNoWebstoreItemLinkFoundError[] =
46     "No Chrome Web Store item link found.";
47 const char kInvalidWebstoreItemUrlError[] =
48     "Invalid Chrome Web Store item URL.";
49
50 // chrome.webstore.install() calls generate an install ID so that the install's
51 // callbacks may be fired when the browser notifies us of install completion
52 // (successful or not) via OnInlineWebstoreInstallResponse.
53 int g_next_install_id = 0;
54
55 } // anonymous namespace
56
57 WebstoreBindings::WebstoreBindings(Dispatcher* dispatcher,
58                                    ChromeV8Context* context)
59     : ChromeV8Extension(dispatcher, context),
60       ChromeV8ExtensionHandler(context) {
61   RouteFunction("Install",
62                 base::Bind(&WebstoreBindings::Install, base::Unretained(this)));
63 }
64
65 void WebstoreBindings::Install(
66     const v8::FunctionCallbackInfo<v8::Value>& args) {
67   WebFrame* frame = WebFrame::frameForContext(context()->v8_context());
68   if (!frame || !frame->view())
69     return;
70
71   content::RenderView* render_view =
72       content::RenderView::FromWebView(frame->view());
73   if (!render_view)
74     return;
75
76   std::string preferred_store_link_url;
77   if (!args[0]->IsUndefined()) {
78     if (args[0]->IsString()) {
79       preferred_store_link_url = std::string(*v8::String::Utf8Value(args[0]));
80     } else {
81       v8::ThrowException(v8::String::New(kPreferredStoreLinkUrlNotAString));
82       return;
83     }
84   }
85
86   std::string webstore_item_id;
87   std::string error;
88   if (!GetWebstoreItemIdFromFrame(
89       frame, preferred_store_link_url, &webstore_item_id, &error)) {
90     v8::ThrowException(v8::String::New(error.c_str()));
91     return;
92   }
93
94   int install_id = g_next_install_id++;
95   if (!args[1]->IsUndefined() && !args[1]->IsFunction()) {
96     v8::ThrowException(v8::String::New(kSuccessCallbackNotAFunctionError));
97     return;
98   }
99
100   if (!args[2]->IsUndefined() && !args[2]->IsFunction()) {
101     v8::ThrowException(v8::String::New(kFailureCallbackNotAFunctionError));
102     return;
103   }
104
105   Send(new ExtensionHostMsg_InlineWebstoreInstall(
106       render_view->GetRoutingID(),
107       install_id,
108       GetRoutingID(),
109       webstore_item_id,
110       frame->document().url()));
111
112   args.GetReturnValue().Set(static_cast<int32_t>(install_id));
113 }
114
115 // static
116 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
117       WebFrame* frame, const std::string& preferred_store_link_url,
118       std::string* webstore_item_id, std::string* error) {
119   if (frame != frame->top()) {
120     *error = kNotInTopFrameError;
121     return false;
122   }
123
124   if (!WebUserGestureIndicator::isProcessingUserGesture()) {
125     *error = kNotUserGestureError;
126     return false;
127   }
128
129   WebDocument document = frame->document();
130   if (document.isNull()) {
131     *error = kNoWebstoreItemLinkFoundError;
132     return false;
133   }
134
135   WebElement head = document.head();
136   if (head.isNull()) {
137     *error = kNoWebstoreItemLinkFoundError;
138     return false;
139   }
140
141   GURL webstore_base_url =
142       GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
143   WebNodeList children = head.childNodes();
144   for (unsigned i = 0; i < children.length(); ++i) {
145     WebNode child = children.item(i);
146     if (!child.isElementNode())
147       continue;
148     WebElement elem = child.to<WebElement>();
149
150     if (!elem.hasTagName("link") || !elem.hasAttribute("rel") ||
151         !elem.hasAttribute("href"))
152       continue;
153
154     std::string rel = elem.getAttribute("rel").utf8();
155     if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
156       continue;
157
158     std::string webstore_url_string(elem.getAttribute("href").utf8());
159
160     if (!preferred_store_link_url.empty() &&
161         preferred_store_link_url != webstore_url_string) {
162       continue;
163     }
164
165     GURL webstore_url = GURL(webstore_url_string);
166     if (!webstore_url.is_valid()) {
167       *error = kInvalidWebstoreItemUrlError;
168       return false;
169     }
170
171     if (webstore_url.scheme() != webstore_base_url.scheme() ||
172         webstore_url.host() != webstore_base_url.host() ||
173         !StartsWithASCII(
174             webstore_url.path(), webstore_base_url.path(), true)) {
175       *error = kInvalidWebstoreItemUrlError;
176       return false;
177     }
178
179     std::string candidate_webstore_item_id = webstore_url.path().substr(
180         webstore_base_url.path().length());
181     if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) {
182       *error = kInvalidWebstoreItemUrlError;
183       return false;
184     }
185
186     std::string reconstructed_webstore_item_url_string =
187         extension_urls::GetWebstoreItemDetailURLPrefix() +
188             candidate_webstore_item_id;
189     if (reconstructed_webstore_item_url_string != webstore_url_string) {
190       *error = kInvalidWebstoreItemUrlError;
191       return false;
192     }
193
194     *webstore_item_id = candidate_webstore_item_id;
195     return true;
196   }
197
198   *error = kNoWebstoreItemLinkFoundError;
199   return false;
200 }
201
202 bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) {
203   IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message)
204     IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse,
205                         OnInlineWebstoreInstallResponse)
206     IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
207   IPC_END_MESSAGE_MAP()
208   return true;
209 }
210
211 void WebstoreBindings::OnInlineWebstoreInstallResponse(
212     int install_id,
213     bool success,
214     const std::string& error) {
215   v8::HandleScope handle_scope(context()->isolate());
216   v8::Context::Scope context_scope(context()->v8_context());
217   v8::Handle<v8::Value> argv[] = {
218     v8::Integer::New(install_id),
219     v8::Boolean::New(success),
220     v8::String::New(error.c_str())
221   };
222   context()->module_system()->CallModuleMethod(
223       "webstore", "onInstallResponse", arraysize(argv), argv);
224 }
225
226 }  // namespace extensions