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