diff options
Diffstat (limited to 'chromium/chrome/renderer/extensions/app_hooks_delegate.cc')
-rw-r--r-- | chromium/chrome/renderer/extensions/app_hooks_delegate.cc | 238 |
1 files changed, 238 insertions, 0 deletions
diff --git a/chromium/chrome/renderer/extensions/app_hooks_delegate.cc b/chromium/chrome/renderer/extensions/app_hooks_delegate.cc new file mode 100644 index 00000000000..8c2d148d292 --- /dev/null +++ b/chromium/chrome/renderer/extensions/app_hooks_delegate.cc @@ -0,0 +1,238 @@ +// Copyright 2017 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_hooks_delegate.h" + +#include <memory> + +#include "base/values.h" +#include "chrome/common/extensions/extension_constants.h" +#include "content/public/renderer/render_frame.h" +#include "content/public/renderer/v8_value_converter.h" +#include "extensions/common/constants.h" +#include "extensions/common/extension_messages.h" +#include "extensions/common/extension_set.h" +#include "extensions/common/manifest.h" +#include "extensions/renderer/api_activity_logger.h" +#include "extensions/renderer/bindings/api_request_handler.h" +#include "extensions/renderer/bindings/api_signature.h" +#include "extensions/renderer/dispatcher.h" +#include "extensions/renderer/renderer_extension_registry.h" +#include "extensions/renderer/script_context.h" +#include "extensions/renderer/script_context_set.h" +#include "gin/converter.h" +#include "third_party/blink/public/web/web_document.h" +#include "third_party/blink/public/web/web_local_frame.h" + +namespace extensions { + +namespace { + +void IsInstalledGetterCallback( + v8::Local<v8::String> property, + const v8::PropertyCallbackInfo<v8::Value>& info) { + v8::HandleScope handle_scope(info.GetIsolate()); + v8::Local<v8::Context> context = info.Holder()->CreationContext(); + ScriptContext* script_context = + ScriptContextSet::GetContextByV8Context(context); + + // The ScriptContext may have been invalidated if e.g. the frame was removed. + // Return undefined in this case. + if (!script_context) + return; + + auto* hooks_delegate = + static_cast<AppHooksDelegate*>(info.Data().As<v8::External>()->Value()); + // Since this is more-or-less an API, log it as an API call. + APIActivityLogger::LogAPICall(context, "app.getIsInstalled", + std::vector<v8::Local<v8::Value>>()); + info.GetReturnValue().Set(hooks_delegate->GetIsInstalled(script_context)); +} + +} // namespace + +AppHooksDelegate::IPCHelper::IPCHelper(AppHooksDelegate* owner) + : owner_(owner) {} +AppHooksDelegate::IPCHelper::~IPCHelper() = default; + +void AppHooksDelegate::IPCHelper::SendGetAppInstallStateMessage( + content::RenderFrame* render_frame, + const GURL& url, + int request_id) { + Send(new ExtensionHostMsg_GetAppInstallState( + render_frame->GetRoutingID(), url, GetRoutingID(), request_id)); +} + +bool AppHooksDelegate::IPCHelper::OnMessageReceived( + const IPC::Message& message) { + IPC_BEGIN_MESSAGE_MAP(AppHooksDelegate::IPCHelper, message) + IPC_MESSAGE_HANDLER(ExtensionMsg_GetAppInstallStateResponse, + OnAppInstallStateResponse) + IPC_MESSAGE_UNHANDLED(CHECK(false) << "Unhandled IPC message") + IPC_END_MESSAGE_MAP() + return true; +} + +void AppHooksDelegate::IPCHelper::OnAppInstallStateResponse( + const std::string& state, + int request_id) { + owner_->OnAppInstallStateResponse(state, request_id); +} + +AppHooksDelegate::AppHooksDelegate(Dispatcher* dispatcher, + APIRequestHandler* request_handler) + : dispatcher_(dispatcher), + request_handler_(request_handler), + ipc_helper_(this) {} +AppHooksDelegate::~AppHooksDelegate() {} + +bool AppHooksDelegate::GetIsInstalled(ScriptContext* script_context) const { + const Extension* extension = script_context->extension(); + + // TODO(aa): Why only hosted app? + return extension && extension->is_hosted_app() && + dispatcher_->IsExtensionActive(extension->id()); +} + +APIBindingHooks::RequestResult AppHooksDelegate::HandleRequest( + const std::string& method_name, + const APISignature* signature, + v8::Local<v8::Context> context, + std::vector<v8::Local<v8::Value>>* arguments, + const APITypeReferenceMap& refs) { + using RequestResult = APIBindingHooks::RequestResult; + + v8::Isolate* isolate = context->GetIsolate(); + v8::TryCatch try_catch(isolate); + APISignature::V8ParseResult parse_result = + signature->ParseArgumentsToV8(context, *arguments, refs); + if (!parse_result.succeeded()) { + if (try_catch.HasCaught()) { + try_catch.ReThrow(); + return RequestResult(RequestResult::THROWN); + } + return RequestResult(RequestResult::INVALID_INVOCATION); + } + + ScriptContext* script_context = + ScriptContextSet::GetContextByV8Context(context); + DCHECK(script_context); + + APIBindingHooks::RequestResult result( + APIBindingHooks::RequestResult::HANDLED); + if (method_name == "app.getIsInstalled") { + result.return_value = + v8::Boolean::New(isolate, GetIsInstalled(script_context)); + } else if (method_name == "app.getDetails") { + result.return_value = GetDetails(script_context); + } else if (method_name == "app.runningState") { + result.return_value = + gin::StringToSymbol(isolate, GetRunningState(script_context)); + } else if (method_name == "app.installState") { + DCHECK_EQ(1u, parse_result.arguments->size()); + DCHECK((*parse_result.arguments)[0]->IsFunction()); + int request_id = request_handler_->AddPendingRequest( + context, (*parse_result.arguments)[0].As<v8::Function>()); + GetInstallState(script_context, request_id); + } else { + NOTREACHED(); + } + + return result; +} + +void AppHooksDelegate::InitializeTemplate( + v8::Isolate* isolate, + v8::Local<v8::ObjectTemplate> object_template, + const APITypeReferenceMap& type_refs) { + // We expose a boolean isInstalled on the chrome.app API object, as well as + // the getIsInstalled() method. + // TODO(devlin): :(. Hopefully we can just remove this whole API, but this is + // particularly silly. + // This object should outlive contexts, so the |this| v8::External is safe. + // TODO(devlin): This is getting pretty common. We should find a generalized + // solution, or make gin::ObjectTemplateBuilder work for these use cases. + object_template->SetAccessor(gin::StringToSymbol(isolate, "isInstalled"), + &IsInstalledGetterCallback, nullptr, + v8::External::New(isolate, this)); +} + +v8::Local<v8::Value> AppHooksDelegate::GetDetails( + ScriptContext* script_context) const { + blink::WebLocalFrame* web_frame = script_context->web_frame(); + CHECK(web_frame); + + v8::Isolate* isolate = script_context->isolate(); + if (web_frame->GetDocument().GetSecurityOrigin().IsUnique()) + return v8::Null(isolate); + + const Extension* extension = + RendererExtensionRegistry::Get()->GetExtensionOrAppByURL( + web_frame->GetDocument().Url()); + + if (!extension) + return v8::Null(isolate); + + std::unique_ptr<base::DictionaryValue> manifest_copy = + extension->manifest()->value()->CreateDeepCopy(); + manifest_copy->SetString("id", extension->id()); + return content::V8ValueConverter::Create()->ToV8Value( + manifest_copy.get(), script_context->v8_context()); +} + +void AppHooksDelegate::GetInstallState(ScriptContext* script_context, + int request_id) { + content::RenderFrame* render_frame = script_context->GetRenderFrame(); + CHECK(render_frame); + + ipc_helper_.SendGetAppInstallStateMessage( + render_frame, script_context->web_frame()->GetDocument().Url(), + request_id); +} + +const char* AppHooksDelegate::GetRunningState( + ScriptContext* script_context) const { + // To distinguish between ready_to_run and cannot_run states, we need the app + // from the top frame. + const RendererExtensionRegistry* extensions = + RendererExtensionRegistry::Get(); + + url::Origin top_origin = + script_context->web_frame()->Top()->GetSecurityOrigin(); + // The app associated with the top level frame. + const Extension* top_app = extensions->GetHostedAppByURL(top_origin.GetURL()); + + // The app associated with this frame. + const Extension* this_app = extensions->GetHostedAppByURL( + script_context->web_frame()->GetDocument().Url()); + + if (!this_app || !top_app) + return extension_misc::kAppStateCannotRun; + + const char* state = nullptr; + if (dispatcher_->IsExtensionActive(top_app->id())) { + if (top_app == this_app) + state = extension_misc::kAppStateRunning; + else + state = extension_misc::kAppStateCannotRun; + } else if (top_app == this_app) { + state = extension_misc::kAppStateReadyToRun; + } else { + state = extension_misc::kAppStateCannotRun; + } + + return state; +} + +void AppHooksDelegate::OnAppInstallStateResponse(const std::string& state, + int request_id) { + // Note: it's kind of lame that we serialize the install state to a + // base::Value here when we're just going to later convert it to v8, but it's + // not worth the specialization on APIRequestHandler for this oddball API. + base::ListValue response; + response.AppendString(state); + request_handler_->CompleteRequest(request_id, response, std::string()); +} + +} // namespace extensions |