blob: 3e7db7188c3a08b82857a2c11c761b8ec12de629 [file] [log] [blame]
// Copyright (c) 2012 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/browser/extensions/api/terminal/terminal_private_api.h"
#include <utility>
#include "base/bind.h"
#include "base/json/json_writer.h"
#include "base/memory/ptr_util.h"
#include "base/sys_info.h"
#include "base/values.h"
#include "chrome/browser/extensions/api/terminal/terminal_extension_helper.h"
#include "chrome/browser/extensions/extension_service.h"
#include "chrome/browser/extensions/extension_tab_util.h"
#include "chrome/common/extensions/api/terminal_private.h"
#include "chromeos/process_proxy/process_proxy_registry.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/event_router.h"
namespace terminal_private = extensions::api::terminal_private;
namespace OnTerminalResize =
extensions::api::terminal_private::OnTerminalResize;
namespace OpenTerminalProcess =
extensions::api::terminal_private::OpenTerminalProcess;
namespace CloseTerminalProcess =
extensions::api::terminal_private::CloseTerminalProcess;
namespace SendInput = extensions::api::terminal_private::SendInput;
namespace AckOutput = extensions::api::terminal_private::AckOutput;
namespace {
const char kCroshName[] = "crosh";
const char kCroshCommand[] = "/usr/bin/crosh";
// We make stubbed crosh just echo back input.
const char kStubbedCroshCommand[] = "cat";
const char* GetCroshPath() {
if (base::SysInfo::IsRunningOnChromeOS())
return kCroshCommand;
else
return kStubbedCroshCommand;
}
const char* GetProcessCommandForName(const std::string& name) {
if (name == kCroshName)
return GetCroshPath();
else
return NULL;
}
void NotifyProcessOutput(content::BrowserContext* browser_context,
const std::string& extension_id,
int tab_id,
int terminal_id,
const std::string& output_type,
const std::string& output) {
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&NotifyProcessOutput, browser_context, extension_id, tab_id,
terminal_id, output_type, output));
return;
}
std::unique_ptr<base::ListValue> args(new base::ListValue());
args->AppendInteger(tab_id);
args->AppendInteger(terminal_id);
args->AppendString(output_type);
args->AppendString(output);
extensions::EventRouter* event_router =
extensions::EventRouter::Get(browser_context);
if (event_router) {
std::unique_ptr<extensions::Event> event(new extensions::Event(
extensions::events::TERMINAL_PRIVATE_ON_PROCESS_OUTPUT,
terminal_private::OnProcessOutput::kEventName, std::move(args)));
event_router->DispatchEventToExtension(extension_id, std::move(event));
}
}
// Returns tab ID, or window session ID (for platform apps) for |web_contents|.
int GetTabOrWindowSessionId(content::BrowserContext* browser_context,
content::WebContents* web_contents) {
int tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
if (tab_id >= 0)
return tab_id;
extensions::AppWindow* window =
extensions::AppWindowRegistry::Get(browser_context)
->GetAppWindowForWebContents(web_contents);
return window ? window->session_id().id() : -1;
}
} // namespace
namespace extensions {
TerminalPrivateOpenTerminalProcessFunction::
TerminalPrivateOpenTerminalProcessFunction() : command_(NULL) {}
TerminalPrivateOpenTerminalProcessFunction::
~TerminalPrivateOpenTerminalProcessFunction() {}
ExtensionFunction::ResponseAction
TerminalPrivateOpenTerminalProcessFunction::Run() {
std::unique_ptr<OpenTerminalProcess::Params> params(
OpenTerminalProcess::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
command_ = GetProcessCommandForName(params->process_name);
if (!command_)
return RespondNow(Error("Invalid process name."));
content::WebContents* caller_contents = GetSenderWebContents();
if (!caller_contents)
return RespondNow(Error("No web contents."));
// Passed to terminalPrivate.ackOutput, which is called from the API's custom
// bindings after terminalPrivate.onProcessOutput is dispatched. It is used to
// determine whether ackOutput call should be handled or not. ackOutput will
// be called from every web contents in which a onProcessOutput listener
// exists (because the API custom bindings hooks are run in every web contents
// with a listener). Only ackOutput called from the web contents that has the
// target terminal instance should be handled.
// TODO(tbarzic): Instead of passing tab/app window session id around, keep
// mapping from web_contents to terminal ID running in it. This will be
// needed to fix crbug.com/210295.
int tab_id = GetTabOrWindowSessionId(browser_context(), caller_contents);
if (tab_id < 0)
return RespondNow(Error("Not called from a tab or app window"));
// Registry lives on FILE thread.
content::BrowserThread::PostTask(
content::BrowserThread::FILE, FROM_HERE,
base::Bind(
&TerminalPrivateOpenTerminalProcessFunction::OpenOnFileThread, this,
base::Bind(&NotifyProcessOutput, browser_context(), extension_id(),
tab_id),
base::Bind(
&TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread,
this)));
return RespondLater();
}
void TerminalPrivateOpenTerminalProcessFunction::OpenOnFileThread(
const ProcessOutputCallback& output_callback,
const OpenProcessCallback& callback) {
DCHECK(command_);
chromeos::ProcessProxyRegistry* registry =
chromeos::ProcessProxyRegistry::Get();
int terminal_id = registry->OpenProcess(command_, output_callback);
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(callback, terminal_id));
}
TerminalPrivateSendInputFunction::~TerminalPrivateSendInputFunction() {}
void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread(
int terminal_id) {
if (terminal_id < 0) {
Respond(Error("Failed to open process."));
return;
}
Respond(OneArgument(base::MakeUnique<base::FundamentalValue>(terminal_id)));
}
ExtensionFunction::ResponseAction TerminalPrivateSendInputFunction::Run() {
std::unique_ptr<SendInput::Params> params(SendInput::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
// Registry lives on the FILE thread.
content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
base::Bind(&TerminalPrivateSendInputFunction::SendInputOnFileThread,
this, params->pid, params->input));
return RespondLater();
}
void TerminalPrivateSendInputFunction::SendInputOnFileThread(
int terminal_id,
const std::string& text) {
bool success =
chromeos::ProcessProxyRegistry::Get()->SendInput(terminal_id, text);
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE,
base::Bind(&TerminalPrivateSendInputFunction::RespondOnUIThread, this,
success));
}
void TerminalPrivateSendInputFunction::RespondOnUIThread(bool success) {
Respond(OneArgument(base::MakeUnique<base::FundamentalValue>(success)));
}
TerminalPrivateCloseTerminalProcessFunction::
~TerminalPrivateCloseTerminalProcessFunction() {}
ExtensionFunction::ResponseAction
TerminalPrivateCloseTerminalProcessFunction::Run() {
std::unique_ptr<CloseTerminalProcess::Params> params(
CloseTerminalProcess::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
// Registry lives on the FILE thread.
content::BrowserThread::PostTask(
content::BrowserThread::FILE, FROM_HERE,
base::Bind(
&TerminalPrivateCloseTerminalProcessFunction::CloseOnFileThread, this,
params->pid));
return RespondLater();
}
void TerminalPrivateCloseTerminalProcessFunction::CloseOnFileThread(
int terminal_id) {
bool success =
chromeos::ProcessProxyRegistry::Get()->CloseProcess(terminal_id);
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&TerminalPrivateCloseTerminalProcessFunction::
RespondOnUIThread, this, success));
}
void TerminalPrivateCloseTerminalProcessFunction::RespondOnUIThread(
bool success) {
Respond(OneArgument(base::MakeUnique<base::FundamentalValue>(success)));
}
TerminalPrivateOnTerminalResizeFunction::
~TerminalPrivateOnTerminalResizeFunction() {}
ExtensionFunction::ResponseAction
TerminalPrivateOnTerminalResizeFunction::Run() {
std::unique_ptr<OnTerminalResize::Params> params(
OnTerminalResize::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
// Registry lives on the FILE thread.
content::BrowserThread::PostTask(content::BrowserThread::FILE, FROM_HERE,
base::Bind(&TerminalPrivateOnTerminalResizeFunction::OnResizeOnFileThread,
this, params->pid, params->width, params->height));
return RespondLater();
}
void TerminalPrivateOnTerminalResizeFunction::OnResizeOnFileThread(
int terminal_id,
int width,
int height) {
bool success = chromeos::ProcessProxyRegistry::Get()->OnTerminalResize(
terminal_id, width, height);
content::BrowserThread::PostTask(content::BrowserThread::UI, FROM_HERE,
base::Bind(&TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread,
this, success));
}
void TerminalPrivateOnTerminalResizeFunction::RespondOnUIThread(bool success) {
Respond(OneArgument(base::MakeUnique<base::FundamentalValue>(success)));
}
TerminalPrivateAckOutputFunction::~TerminalPrivateAckOutputFunction() {}
ExtensionFunction::ResponseAction TerminalPrivateAckOutputFunction::Run() {
std::unique_ptr<AckOutput::Params> params(AckOutput::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get());
content::WebContents* caller_contents = GetSenderWebContents();
if (!caller_contents)
return RespondNow(Error("No web contents."));
int tab_id = GetTabOrWindowSessionId(browser_context(), caller_contents);
if (tab_id < 0)
return RespondNow(Error("Not called from a tab or app window"));
if (tab_id != params->tab_id)
return RespondNow(NoArguments());
// Registry lives on the FILE thread.
content::BrowserThread::PostTask(
content::BrowserThread::FILE, FROM_HERE,
base::Bind(&TerminalPrivateAckOutputFunction::AckOutputOnFileThread, this,
params->pid));
return RespondNow(NoArguments());
}
void TerminalPrivateAckOutputFunction::AckOutputOnFileThread(int terminal_id) {
chromeos::ProcessProxyRegistry::Get()->AckOutput(terminal_id);
}
} // namespace extensions