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/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
    254