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