普通文本  |  227行  |  7.6 KB

// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/renderer/extensions/app_bindings.h"

#include "base/command_line.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_set.h"
#include "chrome/renderer/extensions/chrome_v8_context.h"
#include "chrome/renderer/extensions/console.h"
#include "chrome/renderer/extensions/dispatcher.h"
#include "chrome/renderer/extensions/extension_helper.h"
#include "content/public/renderer/render_view.h"
#include "content/public/renderer/v8_value_converter.h"
#include "extensions/common/manifest.h"
#include "grit/renderer_resources.h"
#include "third_party/WebKit/public/web/WebDocument.h"
#include "third_party/WebKit/public/web/WebFrame.h"
#include "v8/include/v8.h"

using blink::WebFrame;
using content::V8ValueConverter;

namespace extensions {

namespace {

bool IsCheckoutURL(const std::string& url_spec) {
  std::string checkout_url_prefix =
      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
          switches::kAppsCheckoutURL);
  if (checkout_url_prefix.empty())
    checkout_url_prefix = "https://checkout.google.com/";

  return StartsWithASCII(url_spec, checkout_url_prefix, false);
}

bool CheckAccessToAppDetails(WebFrame* frame, v8::Isolate* isolate) {
  if (!IsCheckoutURL(frame->document().url().spec())) {
    std::string error("Access denied for URL: ");
    error += frame->document().url().spec();
    isolate->ThrowException(v8::String::NewFromUtf8(isolate, error.c_str()));
    return false;
  }

  return true;
}

const char* kInvalidCallbackIdError = "Invalid callbackId";

}  // namespace

AppBindings::AppBindings(Dispatcher* dispatcher, ChromeV8Context* context)
    : ChromeV8Extension(dispatcher, context),
      ChromeV8ExtensionHandler(context) {
  RouteFunction("GetIsInstalled",
      base::Bind(&AppBindings::GetIsInstalled, base::Unretained(this)));
  RouteFunction("GetDetails",
      base::Bind(&AppBindings::GetDetails, base::Unretained(this)));
  RouteFunction("GetDetailsForFrame",
      base::Bind(&AppBindings::GetDetailsForFrame, base::Unretained(this)));
  RouteFunction("GetInstallState",
      base::Bind(&AppBindings::GetInstallState, base::Unretained(this)));
  RouteFunction("GetRunningState",
      base::Bind(&AppBindings::GetRunningState, base::Unretained(this)));
}

void AppBindings::GetIsInstalled(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  const Extension* extension = context()->extension();

  // TODO(aa): Why only hosted app?
  bool result = extension && extension->is_hosted_app() &&
      dispatcher_->IsExtensionActive(extension->id());
  args.GetReturnValue().Set(result);
}

void AppBindings::GetDetails(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  CHECK(context()->web_frame());
  args.GetReturnValue().Set(GetDetailsForFrameImpl(context()->web_frame()));
}

void AppBindings::GetDetailsForFrame(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  CHECK(context()->web_frame());
  if (!CheckAccessToAppDetails(context()->web_frame(), context()->isolate()))
    return;

  if (args.Length() < 0) {
    context()->isolate()->ThrowException(
        v8::String::NewFromUtf8(context()->isolate(), "Not enough arguments."));
    return;
  }

  if (!args[0]->IsObject()) {
    context()->isolate()->ThrowException(v8::String::NewFromUtf8(
        context()->isolate(), "Argument 0 must be an object."));
    return;
  }

  v8::Local<v8::Context> context =
      v8::Local<v8::Object>::Cast(args[0])->CreationContext();
  CHECK(!context.IsEmpty());

  WebFrame* target_frame = WebFrame::frameForContext(context);
  if (!target_frame) {
    console::Error(args.GetIsolate()->GetCallingContext(),
                   "Could not find frame for specified object.");
    return;
  }

  args.GetReturnValue().Set(GetDetailsForFrameImpl(target_frame));
}

v8::Handle<v8::Value> AppBindings::GetDetailsForFrameImpl(
    WebFrame* frame) {
  v8::Isolate* isolate = frame->mainWorldScriptContext()->GetIsolate();
  if (frame->document().securityOrigin().isUnique())
    return v8::Null(isolate);

  const Extension* extension =
      dispatcher_->extensions()->GetExtensionOrAppByURL(
          frame->document().url());

  if (!extension)
    return v8::Null(isolate);

  scoped_ptr<base::DictionaryValue> manifest_copy(
      extension->manifest()->value()->DeepCopy());
  manifest_copy->SetString("id", extension->id());
  scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
  return converter->ToV8Value(manifest_copy.get(),
                              frame->mainWorldScriptContext());
}

void AppBindings::GetInstallState(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  // Get the callbackId.
  int callback_id = 0;
  if (args.Length() == 1) {
    if (!args[0]->IsInt32()) {
      context()->isolate()->ThrowException(v8::String::NewFromUtf8(
          context()->isolate(), kInvalidCallbackIdError));
      return;
    }
    callback_id = args[0]->Int32Value();
  }

  content::RenderView* render_view = context()->GetRenderView();
  CHECK(render_view);

  Send(new ExtensionHostMsg_GetAppInstallState(
      render_view->GetRoutingID(), context()->web_frame()->document().url(),
      GetRoutingID(), callback_id));
}

void AppBindings::GetRunningState(
    const v8::FunctionCallbackInfo<v8::Value>& args) {
  // To distinguish between ready_to_run and cannot_run states, we need the top
  // level frame.
  const WebFrame* parent_frame = context()->web_frame();
  while (parent_frame->parent())
    parent_frame = parent_frame->parent();

  const ExtensionSet* extensions = dispatcher_->extensions();

  // The app associated with the top level frame.
  const Extension* parent_app = extensions->GetHostedAppByURL(
      parent_frame->document().url());

  // The app associated with this frame.
  const Extension* this_app = extensions->GetHostedAppByURL(
      context()->web_frame()->document().url());

  if (!this_app || !parent_app) {
    args.GetReturnValue().Set(v8::String::NewFromUtf8(
        context()->isolate(), extension_misc::kAppStateCannotRun));
    return;
  }

  const char* state = NULL;
  if (dispatcher_->IsExtensionActive(parent_app->id())) {
    if (parent_app == this_app)
      state = extension_misc::kAppStateRunning;
    else
      state = extension_misc::kAppStateCannotRun;
  } else if (parent_app == this_app) {
    state = extension_misc::kAppStateReadyToRun;
  } else {
    state = extension_misc::kAppStateCannotRun;
  }

  args.GetReturnValue()
      .Set(v8::String::NewFromUtf8(context()->isolate(), state));
}

bool AppBindings::OnMessageReceived(const IPC::Message& message) {
  IPC_BEGIN_MESSAGE_MAP(AppBindings, message)
    IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppInstallStateResponse,
                        OnAppInstallStateResponse)
    IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
  IPC_END_MESSAGE_MAP()
  return true;
}

void AppBindings::OnAppInstallStateResponse(
    const std::string& state, int callback_id) {
  v8::HandleScope handle_scope(context()->isolate());
  v8::Context::Scope context_scope(context()->v8_context());
  v8::Handle<v8::Value> argv[] = {
    v8::String::NewFromUtf8(context()->isolate(), state.c_str()),
    v8::Integer::New(callback_id)
  };
  context()->module_system()->CallModuleMethod(
      "app", "onInstallStateResponse", arraysize(argv), argv);
}

}  // namespace extensions