blob: d96743cc9298ed88d9403869523f2d681120cdc1 [file] [log] [blame]
// 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/extension_process_bindings.h"
#include <set>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/command_line.h"
#include "base/json/json_reader.h"
#include "base/memory/scoped_ptr.h"
#include "base/string_number_conversions.h"
#include "base/string_util.h"
#include "chrome/common/extensions/extension.h"
#include "chrome/common/extensions/extension_action.h"
#include "chrome/common/extensions/extension_constants.h"
#include "chrome/common/extensions/extension_messages.h"
#include "chrome/common/extensions/extension_set.h"
#include "chrome/common/extensions/url_pattern.h"
#include "chrome/common/render_messages.h"
#include "chrome/common/url_constants.h"
#include "chrome/renderer/chrome_render_process_observer.h"
#include "chrome/renderer/extensions/bindings_utils.h"
#include "chrome/renderer/extensions/event_bindings.h"
#include "chrome/renderer/extensions/extension_dispatcher.h"
#include "chrome/renderer/extensions/extension_helper.h"
#include "chrome/renderer/extensions/js_only_v8_extensions.h"
#include "chrome/renderer/extensions/renderer_extension_bindings.h"
#include "chrome/renderer/extensions/user_script_slave.h"
#include "content/renderer/render_view.h"
#include "content/renderer/render_view_visitor.h"
#include "grit/common_resources.h"
#include "grit/renderer_resources.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebDocument.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebFrame.h"
#include "third_party/WebKit/Source/WebKit/chromium/public/WebView.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkColor.h"
#include "webkit/glue/webkit_glue.h"
using bindings_utils::GetStringResource;
using bindings_utils::GetPendingRequestMap;
using bindings_utils::PendingRequest;
using bindings_utils::PendingRequestMap;
using bindings_utils::ExtensionBase;
using WebKit::WebFrame;
using WebKit::WebView;
namespace {
const char kExtensionName[] = "chrome/ExtensionProcessBindings";
const char* kExtensionDeps[] = {
EventBindings::kName,
JsonSchemaJsV8Extension::kName,
RendererExtensionBindings::kName,
ExtensionApiTestV8Extension::kName,
};
// A RenderViewVisitor class that iterates through the set of available
// views, looking for a view of the given type, in the given browser window
// and within the given extension.
// Used to accumulate the list of views associated with an extension.
class ExtensionViewAccumulator : public RenderViewVisitor {
public:
ExtensionViewAccumulator(const std::string& extension_id,
int browser_window_id,
ViewType::Type view_type)
: extension_id_(extension_id),
browser_window_id_(browser_window_id),
view_type_(view_type),
views_(v8::Array::New()),
index_(0) {
}
v8::Local<v8::Array> views() { return views_; }
virtual bool Visit(RenderView* render_view) {
ExtensionHelper* helper = ExtensionHelper::Get(render_view);
if (!ViewTypeMatches(helper->view_type(), view_type_))
return true;
GURL url = render_view->webview()->mainFrame()->document().url();
if (!url.SchemeIs(chrome::kExtensionScheme))
return true;
const std::string& extension_id = url.host();
if (extension_id != extension_id_)
return true;
if (browser_window_id_ != extension_misc::kUnknownWindowId &&
helper->browser_window_id() != browser_window_id_) {
return true;
}
v8::Local<v8::Context> context =
render_view->webview()->mainFrame()->mainWorldScriptContext();
if (!context.IsEmpty()) {
v8::Local<v8::Value> window = context->Global();
DCHECK(!window.IsEmpty());
if (!OnMatchedView(window))
return false;
}
return true;
}
private:
// Called on each view found matching the search criteria. Returns false
// to terminate the iteration.
bool OnMatchedView(const v8::Local<v8::Value>& view_window) {
views_->Set(v8::Integer::New(index_), view_window);
index_++;
if (view_type_ == ViewType::EXTENSION_BACKGROUND_PAGE)
return false; // There can be only one...
return true;
}
// Returns true is |type| "isa" |match|.
static bool ViewTypeMatches(ViewType::Type type, ViewType::Type match) {
if (type == match)
return true;
// INVALID means match all.
if (match == ViewType::INVALID)
return true;
return false;
}
std::string extension_id_;
int browser_window_id_;
ViewType::Type view_type_;
v8::Local<v8::Array> views_;
int index_;
};
class ExtensionImpl : public ExtensionBase {
public:
explicit ExtensionImpl(ExtensionDispatcher* extension_dispatcher)
: ExtensionBase(kExtensionName,
GetStringResource(IDR_EXTENSION_PROCESS_BINDINGS_JS),
arraysize(kExtensionDeps),
kExtensionDeps,
extension_dispatcher) {
}
~ExtensionImpl() {}
virtual v8::Handle<v8::FunctionTemplate> GetNativeFunction(
v8::Handle<v8::String> name) {
if (name->Equals(v8::String::New("GetExtensionAPIDefinition"))) {
return v8::FunctionTemplate::New(GetExtensionAPIDefinition);
} else if (name->Equals(v8::String::New("GetExtensionViews"))) {
return v8::FunctionTemplate::New(GetExtensionViews,
v8::External::New(this));
} else if (name->Equals(v8::String::New("GetNextRequestId"))) {
return v8::FunctionTemplate::New(GetNextRequestId);
} else if (name->Equals(v8::String::New("OpenChannelToTab"))) {
return v8::FunctionTemplate::New(OpenChannelToTab);
} else if (name->Equals(v8::String::New("GetNextContextMenuId"))) {
return v8::FunctionTemplate::New(GetNextContextMenuId);
} else if (name->Equals(v8::String::New("GetNextTtsEventId"))) {
return v8::FunctionTemplate::New(GetNextTtsEventId);
} else if (name->Equals(v8::String::New("GetCurrentPageActions"))) {
return v8::FunctionTemplate::New(GetCurrentPageActions,
v8::External::New(this));
} else if (name->Equals(v8::String::New("StartRequest"))) {
return v8::FunctionTemplate::New(StartRequest,
v8::External::New(this));
} else if (name->Equals(v8::String::New("GetRenderViewId"))) {
return v8::FunctionTemplate::New(GetRenderViewId);
} else if (name->Equals(v8::String::New("SetIconCommon"))) {
return v8::FunctionTemplate::New(SetIconCommon,
v8::External::New(this));
} else if (name->Equals(v8::String::New("IsExtensionProcess"))) {
return v8::FunctionTemplate::New(IsExtensionProcess,
v8::External::New(this));
} else if (name->Equals(v8::String::New("IsIncognitoProcess"))) {
return v8::FunctionTemplate::New(IsIncognitoProcess);
} else if (name->Equals(v8::String::New("GetUniqueSubEventName"))) {
return v8::FunctionTemplate::New(GetUniqueSubEventName);
} else if (name->Equals(v8::String::New("GetLocalFileSystem"))) {
return v8::FunctionTemplate::New(GetLocalFileSystem);
} else if (name->Equals(v8::String::New("DecodeJPEG"))) {
return v8::FunctionTemplate::New(DecodeJPEG, v8::External::New(this));
}
return ExtensionBase::GetNativeFunction(name);
}
private:
static v8::Handle<v8::Value> GetExtensionAPIDefinition(
const v8::Arguments& args) {
return v8::String::New(GetStringResource(IDR_EXTENSION_API_JSON));
}
static v8::Handle<v8::Value> GetExtensionViews(const v8::Arguments& args) {
if (args.Length() != 2)
return v8::Undefined();
if (!args[0]->IsInt32() || !args[1]->IsString())
return v8::Undefined();
// |browser_window_id| == extension_misc::kUnknownWindowId means getting
// views attached to any browser window.
int browser_window_id = args[0]->Int32Value();
std::string view_type_string = *v8::String::Utf8Value(args[1]->ToString());
StringToUpperASCII(&view_type_string);
// |view_type| == ViewType::INVALID means getting any type of views.
ViewType::Type view_type = ViewType::INVALID;
if (view_type_string == ViewType::kBackgroundPage) {
view_type = ViewType::EXTENSION_BACKGROUND_PAGE;
} else if (view_type_string == ViewType::kInfobar) {
view_type = ViewType::EXTENSION_INFOBAR;
} else if (view_type_string == ViewType::kNotification) {
view_type = ViewType::NOTIFICATION;
} else if (view_type_string == ViewType::kTabContents) {
view_type = ViewType::TAB_CONTENTS;
} else if (view_type_string == ViewType::kPopup) {
view_type = ViewType::EXTENSION_POPUP;
} else if (view_type_string == ViewType::kExtensionDialog) {
view_type = ViewType::EXTENSION_DIALOG;
} else if (view_type_string != ViewType::kAll) {
return v8::Undefined();
}
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
const ::Extension* extension =
v8_extension->GetExtensionForCurrentContext();
if (!extension)
return v8::Undefined();
ExtensionViewAccumulator accumulator(extension->id(), browser_window_id,
view_type);
RenderView::ForEach(&accumulator);
return accumulator.views();
}
static v8::Handle<v8::Value> GetNextRequestId(const v8::Arguments& args) {
static int next_request_id = 0;
return v8::Integer::New(next_request_id++);
}
// Attach an event name to an object.
static v8::Handle<v8::Value> GetUniqueSubEventName(
const v8::Arguments& args) {
static int next_event_id = 0;
DCHECK(args.Length() == 1);
DCHECK(args[0]->IsString());
std::string event_name(*v8::String::AsciiValue(args[0]));
std::string unique_event_name =
event_name + "/" + base::IntToString(++next_event_id);
return v8::String::New(unique_event_name.c_str());
}
static v8::Handle<v8::Value> GetLocalFileSystem(
const v8::Arguments& args) {
DCHECK(args.Length() == 2);
DCHECK(args[0]->IsString());
DCHECK(args[1]->IsString());
std::string name(*v8::String::Utf8Value(args[0]));
std::string path(*v8::String::Utf8Value(args[1]));
WebFrame* webframe = WebFrame::frameForCurrentContext();
return webframe->createFileSystem(WebKit::WebFileSystem::TypeExternal,
WebKit::WebString::fromUTF8(name.c_str()),
WebKit::WebString::fromUTF8(path.c_str()));
}
// Decodes supplied JPEG byte array to image pixel array.
static v8::Handle<v8::Value> DecodeJPEG(const v8::Arguments& args) {
static const char* kAllowedIds[] = {
"haiffjcadagjlijoggckpgfnoeiflnem",
"gnedhmakppccajfpfiihfcdlnpgomkcf",
"fjcibdnjlbfnbfdjneajpipnlcppleek",
"oflbaaikkabfdfkimeclgkackhdkpnip" // Testing extension.
};
static const std::vector<std::string> allowed_ids(
kAllowedIds, kAllowedIds + arraysize(kAllowedIds));
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
const ::Extension* extension =
v8_extension->GetExtensionForCurrentContext();
if (!extension)
return v8::Undefined();
if (allowed_ids.end() == std::find(
allowed_ids.begin(), allowed_ids.end(), extension->id())) {
return v8::Undefined();
}
DCHECK(args.Length() == 1);
DCHECK(args[0]->IsArray());
v8::Local<v8::Object> jpeg_array = args[0]->ToObject();
size_t jpeg_length =
jpeg_array->Get(v8::String::New("length"))->Int32Value();
// Put input JPEG array into string for DecodeImage().
std::string jpeg_array_string;
jpeg_array_string.reserve(jpeg_length);
// Unfortunately we cannot request continuous backing store of the
// |jpeg_array| object as it might not have one. So we make
// element copy here.
// Note(mnaganov): If it is not fast enough
// (and main constraints might be in repetition of v8 API calls),
// change the argument type from Array to String and use
// String::Write().
// Note(vitalyr): Another option is to use Int8Array for inputs and
// Int32Array for output.
for (size_t i = 0; i != jpeg_length; ++i) {
jpeg_array_string.push_back(
jpeg_array->Get(v8::Integer::New(i))->Int32Value());
}
// Decode and verify resulting image metrics.
SkBitmap bitmap;
if (!webkit_glue::DecodeImage(jpeg_array_string, &bitmap))
return v8::Undefined();
if (bitmap.config() != SkBitmap::kARGB_8888_Config)
return v8::Undefined();
const int width = bitmap.width();
const int height = bitmap.height();
SkAutoLockPixels lockpixels(bitmap);
const uint32_t* pixels = static_cast<uint32_t*>(bitmap.getPixels());
if (!pixels)
return v8::Undefined();
// Compose output array. This API call only accepts kARGB_8888_Config images
// so we rely on each pixel occupying 4 bytes.
// Note(mnaganov): to speed this up, you may use backing store
// technique from CreateExternalArray() of samples/shell.cc.
v8::Local<v8::Object> bitmap_array = v8::Array::New(width * height);
for (int i = 0; i != width * height; ++i) {
bitmap_array->Set(v8::Integer::New(i),
v8::Integer::New(pixels[i] & 0xFFFFFF));
}
return bitmap_array;
}
// Creates a new messaging channel to the tab with the given ID.
static v8::Handle<v8::Value> OpenChannelToTab(const v8::Arguments& args) {
// Get the current RenderView so that we can send a routed IPC message from
// the correct source.
RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
if (!renderview)
return v8::Undefined();
if (args.Length() >= 3 && args[0]->IsInt32() && args[1]->IsString() &&
args[2]->IsString()) {
int tab_id = args[0]->Int32Value();
std::string extension_id = *v8::String::Utf8Value(args[1]->ToString());
std::string channel_name = *v8::String::Utf8Value(args[2]->ToString());
int port_id = -1;
renderview->Send(new ExtensionHostMsg_OpenChannelToTab(
renderview->routing_id(), tab_id, extension_id, channel_name,
&port_id));
return v8::Integer::New(port_id);
}
return v8::Undefined();
}
static v8::Handle<v8::Value> GetNextContextMenuId(const v8::Arguments& args) {
// Note: this works because contextMenus.create() only works in the
// extension process. If that API is opened up to content scripts, this
// will need to change. See crbug.com/77023
static int next_context_menu_id = 1;
return v8::Integer::New(next_context_menu_id++);
}
static v8::Handle<v8::Value> GetNextTtsEventId(const v8::Arguments& args) {
// Note: this works because the TTS API only works in the
// extension process, not content scripts.
static int next_tts_event_id = 1;
return v8::Integer::New(next_tts_event_id++);
}
static v8::Handle<v8::Value> GetCurrentPageActions(
const v8::Arguments& args) {
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
std::string extension_id = *v8::String::Utf8Value(args[0]->ToString());
const ::Extension* extension =
v8_extension->extension_dispatcher_->extensions()->GetByID(
extension_id);
CHECK(!extension_id.empty());
if (!extension)
return v8::Undefined();
v8::Local<v8::Array> page_action_vector = v8::Array::New();
if (extension->page_action()) {
std::string id = extension->page_action()->id();
page_action_vector->Set(v8::Integer::New(0),
v8::String::New(id.c_str(), id.size()));
}
return page_action_vector;
}
// Common code for starting an API request to the browser. |value_args|
// contains the request's arguments.
// Steals value_args contents for efficiency.
static v8::Handle<v8::Value> StartRequestCommon(
const v8::Arguments& args, ListValue* value_args) {
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
// Get the current RenderView so that we can send a routed IPC message from
// the correct source.
RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
if (!renderview)
return v8::Undefined();
std::string name = *v8::String::AsciiValue(args[0]);
const std::set<std::string>& function_names =
v8_extension->extension_dispatcher_->function_names();
if (function_names.find(name) == function_names.end()) {
NOTREACHED() << "Unexpected function " << name;
return v8::Undefined();
}
if (!v8_extension->CheckPermissionForCurrentContext(name))
return v8::Undefined();
GURL source_url;
WebFrame* webframe = WebFrame::frameForCurrentContext();
if (webframe)
source_url = webframe->document().url();
int request_id = args[2]->Int32Value();
bool has_callback = args[3]->BooleanValue();
bool for_io_thread = args[4]->BooleanValue();
v8::Persistent<v8::Context> current_context =
v8::Persistent<v8::Context>::New(v8::Context::GetCurrent());
DCHECK(!current_context.IsEmpty());
GetPendingRequestMap()[request_id].reset(new PendingRequest(
current_context, name));
ExtensionHostMsg_Request_Params params;
params.name = name;
params.arguments.Swap(value_args);
params.source_url = source_url;
params.request_id = request_id;
params.has_callback = has_callback;
params.user_gesture =
webframe ? webframe->isProcessingUserGesture() : false;
if (for_io_thread) {
renderview->Send(new ExtensionHostMsg_RequestForIOThread(
renderview->routing_id(), params));
} else {
renderview->Send(new ExtensionHostMsg_Request(
renderview->routing_id(), params));
}
return v8::Undefined();
}
// Starts an API request to the browser, with an optional callback. The
// callback will be dispatched to EventBindings::HandleResponse.
static v8::Handle<v8::Value> StartRequest(const v8::Arguments& args) {
std::string str_args = *v8::String::Utf8Value(args[1]);
base::JSONReader reader;
scoped_ptr<Value> value_args;
value_args.reset(reader.JsonToValue(str_args, false, false));
// Since we do the serialization in the v8 extension, we should always get
// valid JSON.
if (!value_args.get() || !value_args->IsType(Value::TYPE_LIST)) {
NOTREACHED() << "Invalid JSON passed to StartRequest.";
return v8::Undefined();
}
return StartRequestCommon(args, static_cast<ListValue*>(value_args.get()));
}
static bool ConvertImageDataToBitmapValue(
const v8::Arguments& args, Value** bitmap_value) {
v8::Local<v8::Object> extension_args = args[1]->ToObject();
v8::Local<v8::Object> details =
extension_args->Get(v8::String::New("0"))->ToObject();
v8::Local<v8::Object> image_data =
details->Get(v8::String::New("imageData"))->ToObject();
v8::Local<v8::Object> data =
image_data->Get(v8::String::New("data"))->ToObject();
int width = image_data->Get(v8::String::New("width"))->Int32Value();
int height = image_data->Get(v8::String::New("height"))->Int32Value();
int data_length = data->Get(v8::String::New("length"))->Int32Value();
if (data_length != 4 * width * height) {
NOTREACHED() << "Invalid argument to setIcon. Expecting ImageData.";
return false;
}
SkBitmap bitmap;
bitmap.setConfig(SkBitmap::kARGB_8888_Config, width, height);
bitmap.allocPixels();
bitmap.eraseARGB(0, 0, 0, 0);
uint32_t* pixels = bitmap.getAddr32(0, 0);
for (int t = 0; t < width*height; t++) {
// |data| is RGBA, pixels is ARGB.
pixels[t] = SkPreMultiplyColor(
((data->Get(v8::Integer::New(4*t + 3))->Int32Value() & 0xFF) << 24) |
((data->Get(v8::Integer::New(4*t + 0))->Int32Value() & 0xFF) << 16) |
((data->Get(v8::Integer::New(4*t + 1))->Int32Value() & 0xFF) << 8) |
((data->Get(v8::Integer::New(4*t + 2))->Int32Value() & 0xFF) << 0));
}
// Construct the Value object.
IPC::Message bitmap_pickle;
IPC::WriteParam(&bitmap_pickle, bitmap);
*bitmap_value = base::BinaryValue::CreateWithCopiedBuffer(
static_cast<const char*>(bitmap_pickle.data()), bitmap_pickle.size());
return true;
}
// A special request for setting the extension action icon and the sidebar
// mini tab icon. This function accepts a canvas ImageData object, so it needs
// to do extra processing before sending the request to the browser.
static v8::Handle<v8::Value> SetIconCommon(
const v8::Arguments& args) {
Value* bitmap_value = NULL;
if (!ConvertImageDataToBitmapValue(args, &bitmap_value))
return v8::Undefined();
v8::Local<v8::Object> extension_args = args[1]->ToObject();
v8::Local<v8::Object> details =
extension_args->Get(v8::String::New("0"))->ToObject();
DictionaryValue* dict = new DictionaryValue();
dict->Set("imageData", bitmap_value);
if (details->Has(v8::String::New("tabId"))) {
dict->SetInteger("tabId",
details->Get(v8::String::New("tabId"))->Int32Value());
}
ListValue list_value;
list_value.Append(dict);
return StartRequestCommon(args, &list_value);
}
static v8::Handle<v8::Value> GetRenderViewId(const v8::Arguments& args) {
RenderView* renderview = bindings_utils::GetRenderViewForCurrentContext();
if (!renderview)
return v8::Undefined();
return v8::Integer::New(renderview->routing_id());
}
static v8::Handle<v8::Value> IsExtensionProcess(const v8::Arguments& args) {
ExtensionImpl* v8_extension = GetFromArguments<ExtensionImpl>(args);
return v8::Boolean::New(
v8_extension->extension_dispatcher_->is_extension_process());
}
static v8::Handle<v8::Value> IsIncognitoProcess(const v8::Arguments& args) {
return v8::Boolean::New(
ChromeRenderProcessObserver::is_incognito_process());
}
};
} // namespace
v8::Extension* ExtensionProcessBindings::Get(
ExtensionDispatcher* extension_dispatcher) {
static v8::Extension* extension = new ExtensionImpl(extension_dispatcher);
CHECK_EQ(extension_dispatcher,
static_cast<ExtensionImpl*>(extension)->extension_dispatcher());
return extension;
}
// static
void ExtensionProcessBindings::HandleResponse(int request_id, bool success,
const std::string& response,
const std::string& error) {
PendingRequestMap& pending_requests = GetPendingRequestMap();
PendingRequestMap::iterator request = pending_requests.find(request_id);
if (request == pending_requests.end())
return; // The frame went away.
v8::HandleScope handle_scope;
v8::Handle<v8::Value> argv[5];
argv[0] = v8::Integer::New(request_id);
argv[1] = v8::String::New(request->second->name.c_str());
argv[2] = v8::Boolean::New(success);
argv[3] = v8::String::New(response.c_str());
argv[4] = v8::String::New(error.c_str());
v8::Handle<v8::Value> retval = bindings_utils::CallFunctionInContext(
request->second->context, "handleResponse", arraysize(argv), argv);
// In debug, the js will validate the callback parameters and return a
// string if a validation error has occured.
#ifndef NDEBUG
if (!retval.IsEmpty() && !retval->IsUndefined()) {
std::string error = *v8::String::AsciiValue(retval);
DCHECK(false) << error;
}
#endif
request->second->context.Dispose();
request->second->context.Clear();
pending_requests.erase(request);
}