app_bindings.cc revision ca12bfac764ba476d6cd062bf1dde12cc64c3f40
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/app_bindings.h"
6
7#include "base/command_line.h"
8#include "base/strings/string16.h"
9#include "base/strings/string_util.h"
10#include "base/strings/utf_string_conversions.h"
11#include "base/values.h"
12#include "chrome/common/chrome_switches.h"
13#include "chrome/common/extensions/extension_messages.h"
14#include "chrome/common/extensions/extension_set.h"
15#include "chrome/common/extensions/manifest.h"
16#include "chrome/renderer/extensions/chrome_v8_context.h"
17#include "chrome/renderer/extensions/console.h"
18#include "chrome/renderer/extensions/dispatcher.h"
19#include "chrome/renderer/extensions/extension_helper.h"
20#include "content/public/renderer/render_view.h"
21#include "content/public/renderer/v8_value_converter.h"
22#include "grit/renderer_resources.h"
23#include "third_party/WebKit/public/web/WebDocument.h"
24#include "third_party/WebKit/public/web/WebFrame.h"
25#include "v8/include/v8.h"
26
27using WebKit::WebFrame;
28using content::V8ValueConverter;
29
30namespace extensions {
31
32namespace {
33
34bool IsCheckoutURL(const std::string& url_spec) {
35  std::string checkout_url_prefix =
36      CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
37          switches::kAppsCheckoutURL);
38  if (checkout_url_prefix.empty())
39    checkout_url_prefix = "https://checkout.google.com/";
40
41  return StartsWithASCII(url_spec, checkout_url_prefix, false);
42}
43
44bool CheckAccessToAppDetails(WebFrame* frame) {
45  if (!IsCheckoutURL(frame->document().url().spec())) {
46    std::string error("Access denied for URL: ");
47    error += frame->document().url().spec();
48    v8::ThrowException(v8::String::New(error.c_str()));
49    return false;
50  }
51
52  return true;
53}
54
55const char* kInvalidCallbackIdError = "Invalid callbackId";
56
57}  // namespace
58
59AppBindings::AppBindings(Dispatcher* dispatcher, ChromeV8Context* context)
60    : ChromeV8Extension(dispatcher, context),
61      ChromeV8ExtensionHandler(context) {
62  RouteFunction("GetIsInstalled",
63      base::Bind(&AppBindings::GetIsInstalled, base::Unretained(this)));
64  RouteFunction("GetDetails",
65      base::Bind(&AppBindings::GetDetails, base::Unretained(this)));
66  RouteFunction("GetDetailsForFrame",
67      base::Bind(&AppBindings::GetDetailsForFrame, base::Unretained(this)));
68  RouteFunction("GetInstallState",
69      base::Bind(&AppBindings::GetInstallState, base::Unretained(this)));
70  RouteFunction("GetRunningState",
71      base::Bind(&AppBindings::GetRunningState, base::Unretained(this)));
72}
73
74void AppBindings::GetIsInstalled(
75    const v8::FunctionCallbackInfo<v8::Value>& args) {
76  const Extension* extension = context()->extension();
77
78  // TODO(aa): Why only hosted app?
79  bool result = extension && extension->is_hosted_app() &&
80      dispatcher_->IsExtensionActive(extension->id());
81  args.GetReturnValue().Set(result);
82}
83
84void AppBindings::GetDetails(
85    const v8::FunctionCallbackInfo<v8::Value>& args) {
86  CHECK(context()->web_frame());
87  args.GetReturnValue().Set(GetDetailsForFrameImpl(context()->web_frame()));
88}
89
90void AppBindings::GetDetailsForFrame(
91    const v8::FunctionCallbackInfo<v8::Value>& args) {
92  CHECK(context()->web_frame());
93  if (!CheckAccessToAppDetails(context()->web_frame()))
94    return;
95
96  if (args.Length() < 0) {
97    v8::ThrowException(v8::String::New("Not enough arguments."));
98    return;
99  }
100
101  if (!args[0]->IsObject()) {
102    v8::ThrowException(v8::String::New("Argument 0 must be an object."));
103    return;
104  }
105
106  v8::Local<v8::Context> context =
107      v8::Local<v8::Object>::Cast(args[0])->CreationContext();
108  CHECK(!context.IsEmpty());
109
110  WebFrame* target_frame = WebFrame::frameForContext(context);
111  if (!target_frame) {
112    console::Error(v8::Context::GetCalling(),
113                   "Could not find frame for specified object.");
114    return;
115  }
116
117  args.GetReturnValue().Set(GetDetailsForFrameImpl(target_frame));
118}
119
120v8::Handle<v8::Value> AppBindings::GetDetailsForFrameImpl(
121    WebFrame* frame) {
122  if (frame->document().securityOrigin().isUnique())
123    return v8::Null();
124
125  const Extension* extension =
126      dispatcher_->extensions()->GetExtensionOrAppByURL(
127          frame->document().url());
128
129  if (!extension)
130    return v8::Null();
131
132  scoped_ptr<base::DictionaryValue> manifest_copy(
133      extension->manifest()->value()->DeepCopy());
134  manifest_copy->SetString("id", extension->id());
135  scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
136  return converter->ToV8Value(manifest_copy.get(),
137                              frame->mainWorldScriptContext());
138}
139
140void AppBindings::GetInstallState(
141    const v8::FunctionCallbackInfo<v8::Value>& args) {
142  // Get the callbackId.
143  int callback_id = 0;
144  if (args.Length() == 1) {
145    if (!args[0]->IsInt32()) {
146      v8::ThrowException(v8::String::New(kInvalidCallbackIdError));
147      return;
148    }
149    callback_id = args[0]->Int32Value();
150  }
151
152  content::RenderView* render_view = context()->GetRenderView();
153  CHECK(render_view);
154
155  Send(new ExtensionHostMsg_GetAppInstallState(
156      render_view->GetRoutingID(), context()->web_frame()->document().url(),
157      GetRoutingID(), callback_id));
158}
159
160void AppBindings::GetRunningState(
161    const v8::FunctionCallbackInfo<v8::Value>& args) {
162  // To distinguish between ready_to_run and cannot_run states, we need the top
163  // level frame.
164  const WebFrame* parent_frame = context()->web_frame();
165  while (parent_frame->parent())
166    parent_frame = parent_frame->parent();
167
168  const ExtensionSet* extensions = dispatcher_->extensions();
169
170  // The app associated with the top level frame.
171  const Extension* parent_app = extensions->GetHostedAppByURL(
172      parent_frame->document().url());
173
174  // The app associated with this frame.
175  const Extension* this_app = extensions->GetHostedAppByURL(
176      context()->web_frame()->document().url());
177
178  if (!this_app || !parent_app) {
179    args.GetReturnValue().Set(
180        v8::String::New(extension_misc::kAppStateCannotRun));
181    return;
182  }
183
184  const char* state = NULL;
185  if (dispatcher_->IsExtensionActive(parent_app->id())) {
186    if (parent_app == this_app)
187      state = extension_misc::kAppStateRunning;
188    else
189      state = extension_misc::kAppStateCannotRun;
190  } else if (parent_app == this_app) {
191    state = extension_misc::kAppStateReadyToRun;
192  } else {
193    state = extension_misc::kAppStateCannotRun;
194  }
195
196  args.GetReturnValue().Set(v8::String::New(state));
197}
198
199bool AppBindings::OnMessageReceived(const IPC::Message& message) {
200  IPC_BEGIN_MESSAGE_MAP(AppBindings, message)
201    IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppInstallStateResponse,
202                        OnAppInstallStateResponse)
203    IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message")
204  IPC_END_MESSAGE_MAP()
205  return true;
206}
207
208void AppBindings::OnAppInstallStateResponse(
209    const std::string& state, int callback_id) {
210  v8::HandleScope handle_scope;
211  v8::Context::Scope context_scope(context()->v8_context());
212  v8::Handle<v8::Value> argv[] = {
213    v8::String::New(state.c_str()),
214    v8::Integer::New(callback_id)
215  };
216  context()->module_system()->CallModuleMethod(
217      "app", "onInstallStateResponse", arraysize(argv), argv);
218}
219
220}  // namespace extensions
221