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