blob: b9de9d54446322baaa5d6e6d61c4079614937e7b [file] [log] [blame]
// 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();
std::vector<v8::Local<v8::Value>> arguments_out;
std::string error;
v8::TryCatch try_catch(isolate);
if (!signature->ParseArgumentsToV8(context, *arguments, refs, &arguments_out,
&error)) {
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, arguments_out.size());
DCHECK(arguments_out[0]->IsFunction());
int request_id = request_handler_->AddPendingRequest(
context, arguments_out[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