Home | History | Annotate | Download | only in extensions
      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_constants.h"
      9 #include "chrome/common/extensions/extension_messages.h"
     10 #include "chrome/renderer/extensions/chrome_v8_context.h"
     11 #include "content/public/renderer/render_view.h"
     12 #include "extensions/common/extension.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 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 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       args.GetIsolate()->ThrowException(v8::String::NewFromUtf8(
     82           args.GetIsolate(), kPreferredStoreLinkUrlNotAString));
     83       return;
     84     }
     85   }
     86 
     87   std::string webstore_item_id;
     88   std::string error;
     89   if (!GetWebstoreItemIdFromFrame(
     90       frame, preferred_store_link_url, &webstore_item_id, &error)) {
     91     args.GetIsolate()->ThrowException(
     92         v8::String::NewFromUtf8(args.GetIsolate(), error.c_str()));
     93     return;
     94   }
     95 
     96   int install_id = g_next_install_id++;
     97   if (!args[1]->IsUndefined() && !args[1]->IsFunction()) {
     98     args.GetIsolate()->ThrowException(v8::String::NewFromUtf8(
     99         args.GetIsolate(), kSuccessCallbackNotAFunctionError));
    100     return;
    101   }
    102 
    103   if (!args[2]->IsUndefined() && !args[2]->IsFunction()) {
    104     args.GetIsolate()->ThrowException(v8::String::NewFromUtf8(
    105         args.GetIsolate(), kFailureCallbackNotAFunctionError));
    106     return;
    107   }
    108 
    109   Send(new ExtensionHostMsg_InlineWebstoreInstall(
    110       render_view->GetRoutingID(),
    111       install_id,
    112       GetRoutingID(),
    113       webstore_item_id,
    114       frame->document().url()));
    115 
    116   args.GetReturnValue().Set(static_cast<int32_t>(install_id));
    117 }
    118 
    119 // static
    120 bool WebstoreBindings::GetWebstoreItemIdFromFrame(
    121       WebFrame* frame, const std::string& preferred_store_link_url,
    122       std::string* webstore_item_id, std::string* error) {
    123   if (frame != frame->top()) {
    124     *error = kNotInTopFrameError;
    125     return false;
    126   }
    127 
    128   if (!WebUserGestureIndicator::isProcessingUserGesture()) {
    129     *error = kNotUserGestureError;
    130     return false;
    131   }
    132 
    133   WebDocument document = frame->document();
    134   if (document.isNull()) {
    135     *error = kNoWebstoreItemLinkFoundError;
    136     return false;
    137   }
    138 
    139   WebElement head = document.head();
    140   if (head.isNull()) {
    141     *error = kNoWebstoreItemLinkFoundError;
    142     return false;
    143   }
    144 
    145   GURL webstore_base_url =
    146       GURL(extension_urls::GetWebstoreItemDetailURLPrefix());
    147   WebNodeList children = head.childNodes();
    148   for (unsigned i = 0; i < children.length(); ++i) {
    149     WebNode child = children.item(i);
    150     if (!child.isElementNode())
    151       continue;
    152     WebElement elem = child.to<WebElement>();
    153 
    154     if (!elem.hasTagName("link") || !elem.hasAttribute("rel") ||
    155         !elem.hasAttribute("href"))
    156       continue;
    157 
    158     std::string rel = elem.getAttribute("rel").utf8();
    159     if (!LowerCaseEqualsASCII(rel, kWebstoreLinkRelation))
    160       continue;
    161 
    162     std::string webstore_url_string(elem.getAttribute("href").utf8());
    163 
    164     if (!preferred_store_link_url.empty() &&
    165         preferred_store_link_url != webstore_url_string) {
    166       continue;
    167     }
    168 
    169     GURL webstore_url = GURL(webstore_url_string);
    170     if (!webstore_url.is_valid()) {
    171       *error = kInvalidWebstoreItemUrlError;
    172       return false;
    173     }
    174 
    175     if (webstore_url.scheme() != webstore_base_url.scheme() ||
    176         webstore_url.host() != webstore_base_url.host() ||
    177         !StartsWithASCII(
    178             webstore_url.path(), webstore_base_url.path(), true)) {
    179       *error = kInvalidWebstoreItemUrlError;
    180       return false;
    181     }
    182 
    183     std::string candidate_webstore_item_id = webstore_url.path().substr(
    184         webstore_base_url.path().length());
    185     if (!extensions::Extension::IdIsValid(candidate_webstore_item_id)) {
    186       *error = kInvalidWebstoreItemUrlError;
    187       return false;
    188     }
    189 
    190     std::string reconstructed_webstore_item_url_string =
    191         extension_urls::GetWebstoreItemDetailURLPrefix() +
    192             candidate_webstore_item_id;
    193     if (reconstructed_webstore_item_url_string != webstore_url_string) {
    194       *error = kInvalidWebstoreItemUrlError;
    195       return false;
    196     }
    197 
    198     *webstore_item_id = candidate_webstore_item_id;
    199     return true;
    200   }
    201 
    202   *error = kNoWebstoreItemLinkFoundError;
    203   return false;
    204 }
    205 
    206 bool WebstoreBindings::OnMessageReceived(const IPC::Message& message) {
    207   IPC_BEGIN_MESSAGE_MAP(WebstoreBindings, message)
    208     IPC_MESSAGE_HANDLER(ExtensionMsg_InlineWebstoreInstallResponse,
    209                         OnInlineWebstoreInstallResponse)
    210     IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
    211   IPC_END_MESSAGE_MAP()
    212   return true;
    213 }
    214 
    215 void WebstoreBindings::OnInlineWebstoreInstallResponse(
    216     int install_id,
    217     bool success,
    218     const std::string& error) {
    219   v8::Isolate* isolate = context()->isolate();
    220   v8::HandleScope handle_scope(isolate);
    221   v8::Context::Scope context_scope(context()->v8_context());
    222   v8::Handle<v8::Value> argv[] = {
    223     v8::Integer::New(install_id),
    224     v8::Boolean::New(isolate, success),
    225     v8::String::NewFromUtf8(isolate, error.c_str())
    226   };
    227   context()->module_system()->CallModuleMethod(
    228       "webstore", "onInstallResponse", arraysize(argv), argv);
    229 }
    230 
    231 }  // namespace extensions
    232