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 "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
    250