// Copyright (c) 2011 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/chrome_app_bindings.h" #include "base/command_line.h" #include "base/json/json_writer.h" #include "base/string16.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/extensions/extension_set.h" #include "chrome/renderer/extensions/extension_dispatcher.h" #include "chrome/renderer/extensions/extension_helper.h" #include "chrome/renderer/weak_v8_function_map.h" #include "content/public/renderer/v8_value_converter.h" #include "content/renderer/render_view.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h" #include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h" #include "v8/include/v8.h" using WebKit::WebFrame; using content::V8ValueConverter; 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 = WebFrame::frameForCurrentContext(); if (!frame) { LOG(ERROR) << "Could not get frame for current context."; return false; } if (!IsCheckoutURL(frame->document().url().spec())) { std::string error("Access denied for URL: "); error += frame->document().url().spec(); v8::ThrowException(v8::String::New(error.c_str())); return false; } return true; } int g_next_request_id = 0; base::LazyInstance g_callbacks(base::LINKER_INITIALIZED); const char* kMissingClientIdError = "Missing clientId parameter"; const char* kInvalidClientIdError = "Invalid clientId"; const char* kCallbackNotAFunctionError = "The callback that was passed is not a function"; } // namespace const char kAppExtensionName[] = "v8/ChromeApp"; namespace extensions_v8 { class ChromeAppExtensionWrapper : public v8::Extension { public: explicit ChromeAppExtensionWrapper(ExtensionDispatcher* extension_dispatcher) : v8::Extension( kAppExtensionName, "var chrome;" "if (!chrome)" " chrome = {};" "if (!chrome.app) {" " chrome.app = new function() {" " native function GetIsInstalled();" " native function Install();" " native function GetDetails();" " native function GetDetailsForFrame();" " native function GetAppNotifyChannel();" " this.__defineGetter__('isInstalled', GetIsInstalled);" " this.install = Install;" " this.getDetails = GetDetails;" " this.getDetailsForFrame = GetDetailsForFrame;" " this.experimental = {};" " this.experimental.getNotificationChannel =" " GetAppNotifyChannel;" " };" "}") { extension_dispatcher_ = extension_dispatcher; } ~ChromeAppExtensionWrapper() { extension_dispatcher_ = NULL; } virtual v8::Handle GetNativeFunction( v8::Handle name) { if (name->Equals(v8::String::New("GetIsInstalled"))) { return v8::FunctionTemplate::New(GetIsInstalled); } else if (name->Equals(v8::String::New("Install"))) { return v8::FunctionTemplate::New(Install); } else if (name->Equals(v8::String::New("GetDetails"))) { return v8::FunctionTemplate::New(GetDetails); } else if (name->Equals(v8::String::New("GetDetailsForFrame"))) { return v8::FunctionTemplate::New(GetDetailsForFrame); } else if (name->Equals(v8::String::New("GetAppNotifyChannel"))) { return v8::FunctionTemplate::New(GetAppNotifyChannel); } else { return v8::Handle(); } } static v8::Handle GetIsInstalled(const v8::Arguments& args) { WebFrame* frame = WebFrame::frameForCurrentContext(); if (!frame) return v8::False(); GURL url(frame->document().url()); if (url.is_empty() || !url.is_valid() || !(url.SchemeIs("http") || url.SchemeIs("https"))) return v8::False(); const ::Extension* extension = extension_dispatcher_->extensions()->GetByURL(frame->document().url()); bool has_web_extent = extension && extension_dispatcher_->IsApplicationActive(extension->id()); return v8::Boolean::New(has_web_extent); } static v8::Handle Install(const v8::Arguments& args) { WebFrame* frame = WebFrame::frameForCurrentContext(); if (!frame || !frame->view()) return v8::Undefined(); RenderView* render_view = RenderView::FromWebView(frame->view()); if (!render_view) return v8::Undefined(); string16 error; ExtensionHelper* helper = ExtensionHelper::Get(render_view); if (!helper->InstallWebApplicationUsingDefinitionFile(frame, &error)) v8::ThrowException(v8::String::New(UTF16ToUTF8(error).c_str())); return v8::Undefined(); } static v8::Handle GetDetails(const v8::Arguments& args) { return GetDetailsForFrameImpl(WebFrame::frameForCurrentContext()); } static v8::Handle GetDetailsForFrame( const v8::Arguments& args) { if (!CheckAccessToAppDetails()) return v8::Undefined(); if (args.Length() < 0) return v8::ThrowException(v8::String::New("Not enough arguments.")); if (!args[0]->IsObject()) { return v8::ThrowException( v8::String::New("Argument 0 must be an object.")); } v8::Local context = v8::Local::Cast(args[0])->CreationContext(); CHECK(!context.IsEmpty()); WebFrame* target_frame = WebFrame::frameForContext(context); if (!target_frame) { return v8::ThrowException( v8::String::New("Could not find frame for specified object.")); } return GetDetailsForFrameImpl(target_frame); } static v8::Handle GetDetailsForFrameImpl(const WebFrame* frame) { const ::Extension* extension = extension_dispatcher_->extensions()->GetByURL(frame->document().url()); if (!extension) return v8::Null(); scoped_ptr manifest_copy( extension->manifest_value()->DeepCopy()); manifest_copy->SetString("id", extension->id()); scoped_ptr converter(V8ValueConverter::create()); return converter->ToV8Value(manifest_copy.get(), frame->mainWorldScriptContext()); } static v8::Handle GetAppNotifyChannel(const v8::Arguments& args) { WebFrame* frame = WebFrame::frameForCurrentContext(); if (!frame || !frame->view()) return v8::Undefined(); RenderView* render_view = RenderView::FromWebView(frame->view()); if (!render_view) return v8::Undefined(); if (g_next_request_id < 0) return v8::Undefined(); int request_id = g_next_request_id++; // Read the required 'clientId' value out of the object at args[0]. std::string client_id; if (args.Length() < 1 || !args[0]->IsObject()) { v8::ThrowException(v8::String::New(kMissingClientIdError)); return v8::Undefined(); } v8::Local obj = v8::Local::Cast(args[0]); v8::Local client_id_key = v8::String::New("clientId"); if (obj->Has(client_id_key)) { v8::String::Utf8Value id_value(obj->Get(client_id_key)); if (id_value.length() > 0) client_id = std::string(*id_value); } if (client_id.empty()) { v8::ThrowException(v8::String::New(kInvalidClientIdError)); return v8::Undefined(); } // Hang on to the callback if there was one. if (args.Length() >= 2) { if (args[1]->IsFunction()) { g_callbacks.Get().Add(request_id, v8::Function::Cast(*args[1])); } else { v8::ThrowException(v8::String::New(kCallbackNotAFunctionError)); return v8::Undefined(); } } ExtensionHelper* helper = ExtensionHelper::Get(render_view); helper->GetAppNotifyChannel( request_id, frame->document().url(), client_id); return v8::Undefined(); } static ExtensionDispatcher* extension_dispatcher_; }; ExtensionDispatcher* ChromeAppExtensionWrapper::extension_dispatcher_; v8::Extension* ChromeAppExtension::Get( ExtensionDispatcher* extension_dispatcher) { return new ChromeAppExtensionWrapper(extension_dispatcher); } void ChromeAppExtension::HandleGetAppNotifyChannelResponse( int request_id, const std::string& channel_id, const std::string& error) { v8::Persistent function = g_callbacks.Get().Remove(request_id); if (function.IsEmpty()) return; v8::HandleScope handle_scope; v8::Context::Scope context_scope(function->CreationContext()); v8::Handle argv[2]; argv[0] = v8::String::New(channel_id.c_str()); argv[1] = v8::String::New(error.c_str()); function->Call(v8::Object::New(), arraysize(argv), argv); } } // namespace extensions_v8