Allow serialization of ArrayBuffer params in extension/apps API methods
This changes the V8ValueConverter to do ArrayBuffer<->BinaryValue conversions,
as well as supporting ArrayBufferView subclasses as request parameters (but
not response ones, since that's unnecessary).
Also adds an experimental API for testing ArrayBuffers in request/response
parameters.
(This is re-landing https://chromiumcodereview.appspot.com/10161038, which
ran into some test failures fixed by crrev.com/135933)
[email protected]
BUG=122675
TEST=Included browser tests should pass
Review URL: https://chromiumcodereview.appspot.com/10377063
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@136114 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/idltest/idltest_api.cc b/chrome/browser/extensions/api/idltest/idltest_api.cc
new file mode 100644
index 0000000..1feff0a
--- /dev/null
+++ b/chrome/browser/extensions/api/idltest/idltest_api.cc
@@ -0,0 +1,44 @@
+// 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/idltest/idltest_api.h"
+
+#include "base/values.h"
+
+using base::BinaryValue;
+
+namespace {
+
+ListValue* CopyBinaryValueToIntegerList(const BinaryValue* input) {
+ ListValue* output = new ListValue();
+ const char* input_buffer = input->GetBuffer();
+ for (size_t i = 0; i < input->GetSize(); i++) {
+ output->Append(Value::CreateIntegerValue(input_buffer[i]));
+ }
+ return output;
+}
+
+}
+
+bool IdltestSendArrayBufferFunction::RunImpl() {
+ BinaryValue* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_ != NULL && args_->GetBinary(0, &input));
+ result_.reset(CopyBinaryValueToIntegerList(input));
+ return true;
+}
+
+bool IdltestSendArrayBufferViewFunction::RunImpl() {
+ BinaryValue* input = NULL;
+ EXTENSION_FUNCTION_VALIDATE(args_ != NULL && args_->GetBinary(0, &input));
+ result_.reset(CopyBinaryValueToIntegerList(input));
+ return true;
+}
+
+bool IdltestGetArrayBufferFunction::RunImpl() {
+ std::string hello = "hello world";
+ BinaryValue* output =
+ BinaryValue::CreateWithCopiedBuffer(hello.c_str(), hello.size());
+ result_.reset(output);
+ return true;
+}
diff --git a/chrome/browser/extensions/api/idltest/idltest_api.h b/chrome/browser/extensions/api/idltest/idltest_api.h
new file mode 100644
index 0000000..a8a6a51d
--- /dev/null
+++ b/chrome/browser/extensions/api/idltest/idltest_api.h
@@ -0,0 +1,32 @@
+// 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IDLTEST_IDLTEST_API_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IDLTEST_IDLTEST_API_H_
+#pragma once
+
+#include "chrome/browser/extensions/extension_function.h"
+
+class IdltestSendArrayBufferFunction : public SyncExtensionFunction {
+ public:
+ virtual bool RunImpl() OVERRIDE;
+ private:
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.idltest.sendArrayBuffer")
+};
+
+class IdltestSendArrayBufferViewFunction : public SyncExtensionFunction {
+ public:
+ virtual bool RunImpl() OVERRIDE;
+ private:
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.idltest.sendArrayBufferView")
+};
+
+class IdltestGetArrayBufferFunction : public SyncExtensionFunction {
+ public:
+ virtual bool RunImpl() OVERRIDE;
+ private:
+ DECLARE_EXTENSION_FUNCTION_NAME("experimental.idltest.getArrayBuffer")
+};
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_IDLTEST_IDLTEST_API_H_
diff --git a/chrome/browser/extensions/api/idltest/idltest_apitest.cc b/chrome/browser/extensions/api/idltest/idltest_apitest.cc
new file mode 100644
index 0000000..eecfb63
--- /dev/null
+++ b/chrome/browser/extensions/api/idltest/idltest_apitest.cc
@@ -0,0 +1,21 @@
+// 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/extension_apitest.h"
+#include "chrome/common/chrome_switches.h"
+
+class ExtensionIdltestApiTest : public ExtensionApiTest {
+ public:
+ ExtensionIdltestApiTest() {}
+ virtual ~ExtensionIdltestApiTest() {}
+
+ virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
+ ExtensionApiTest::SetUpCommandLine(command_line);
+ command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis);
+ }
+};
+
+IN_PROC_BROWSER_TEST_F(ExtensionIdltestApiTest, BinaryData) {
+ EXPECT_TRUE(RunExtensionSubtest("idltest/binary_data", "binary.html"));
+};
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 27af60a..bece4a8 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -108,6 +108,8 @@
'browser/extensions/api/extension_action/extension_page_actions_api.h',
'browser/extensions/api/extension_action/extension_page_actions_api_constants.cc',
'browser/extensions/api/extension_action/extension_page_actions_api_constants.h',
+ 'browser/extensions/api/idltest/idltest_api.cc',
+ 'browser/extensions/api/idltest/idltest_api.h',
'browser/extensions/api/identity/identity_api.cc',
'browser/extensions/api/identity/identity_api.h',
'browser/extensions/api/offscreen_tabs/offscreen_tabs_api.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index ec51b3e8..72c6dda 100644
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -2703,6 +2703,7 @@
'browser/extensions/api/extension_action/page_action_apitest.cc',
'browser/extensions/api/extension_action/page_as_browser_action_apitest.cc',
'browser/extensions/api/identity/identity_apitest.cc',
+ 'browser/extensions/api/idltest/idltest_apitest.cc',
'browser/extensions/api/offscreen_tabs/offscreen_tabs_apitest.cc',
'browser/extensions/api/omnibox/omnibox_apitest.cc',
'browser/extensions/api/permissions/permissions_apitest.cc',
diff --git a/chrome/common/extensions/api/api.gyp b/chrome/common/extensions/api/api.gyp
index bf23f22..76c69bba 100644
--- a/chrome/common/extensions/api/api.gyp
+++ b/chrome/common/extensions/api/api.gyp
@@ -31,6 +31,7 @@
'alarms.idl',
'experimental.bluetooth.idl',
'experimental.dns.idl',
+ 'experimental.idltest.idl',
'experimental.serial.idl',
'experimental.socket.idl',
'experimental.usb.idl',
diff --git a/chrome/common/extensions/api/experimental.idltest.idl b/chrome/common/extensions/api/experimental.idltest.idl
new file mode 100644
index 0000000..4e496c1
--- /dev/null
+++ b/chrome/common/extensions/api/experimental.idltest.idl
@@ -0,0 +1,26 @@
+// 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.
+
+// An API to test IDL schema specifications.
+
+[nodoc] namespace experimental.idltest {
+
+ callback LongArrayCallback = void(long[] array);
+ callback ArrayBufferCallback = void([instanceOf=ArrayBuffer] object buffer);
+
+ interface Functions {
+ // Functions for testing binary data request/response parameters. The first
+ // two just return back the bytes they were passed in an array.
+ static void sendArrayBuffer([instanceOf=ArrayBuffer] object input,
+ LongArrayCallback cb);
+ // TODO(asargent) - we currently can't have [instanceOf=ArrayBufferView],
+ // I think because ArrayBufferView isn't an instantiable type. The best
+ // we might be able to do is have a 'choices' list including all the
+ // typed array subclasses like Uint8Array, Uint16Array, Float32Array, etc.
+ static void sendArrayBufferView([instanceOf=Uint8Array] object input,
+ LongArrayCallback cb);
+ static void getArrayBuffer(ArrayBufferCallback cb);
+ };
+
+};
diff --git a/chrome/renderer/extensions/send_request_natives.cc b/chrome/renderer/extensions/send_request_natives.cc
index e5377ce3..17e0ba9 100644
--- a/chrome/renderer/extensions/send_request_natives.cc
+++ b/chrome/renderer/extensions/send_request_natives.cc
@@ -5,8 +5,11 @@
#include "chrome/renderer/extensions/send_request_natives.h"
#include "base/json/json_reader.h"
+#include "content/public/renderer/v8_value_converter.h"
#include "chrome/renderer/extensions/extension_request_sender.h"
+using content::V8ValueConverter;
+
namespace extensions {
SendRequestNatives::SendRequestNatives(
@@ -32,21 +35,19 @@
// callback will be dispatched to EventBindings::HandleResponse.
v8::Handle<v8::Value> SendRequestNatives::StartRequest(
const v8::Arguments& args) {
- std::string str_args = *v8::String::Utf8Value(args[1]);
- scoped_ptr<Value> value_args(base::JSONReader::Read(str_args));
-
- // 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();
- }
-
std::string name = *v8::String::AsciiValue(args[0]);
int request_id = args[2]->Int32Value();
bool has_callback = args[3]->BooleanValue();
bool for_io_thread = args[4]->BooleanValue();
+ scoped_ptr<V8ValueConverter> converter(V8ValueConverter::create());
+ scoped_ptr<Value> value_args(
+ converter->FromV8Value(args[1], v8::Context::GetCurrent()));
+ if (!value_args.get() || !value_args->IsType(Value::TYPE_LIST)) {
+ NOTREACHED() << "Unable to convert args passed to StartRequest";
+ return v8::Undefined();
+ }
+
request_sender_->StartRequest(name, request_id, has_callback, for_io_thread,
static_cast<ListValue*>(value_args.get()));
return v8::Undefined();
diff --git a/chrome/renderer/resources/extensions/send_request.js b/chrome/renderer/resources/extensions/send_request.js
index 045e270..2e79b8a 100644
--- a/chrome/renderer/resources/extensions/send_request.js
+++ b/chrome/renderer/resources/extensions/send_request.js
@@ -107,8 +107,13 @@
if (request.args === undefined)
request.args = null;
- var sargs = opt_args.noStringify ?
- request.args : chromeHidden.JSON.stringify(request.args);
+ // TODO(asargent) - convert all optional native functions to accept raw
+ // v8 values instead of expecting JSON strings.
+ var doStringify = false;
+ if (opt_args.nativeFunction && !opt_args.noStringify)
+ doStringify = true;
+ var requestArgs = doStringify ?
+ chromeHidden.JSON.stringify(request.args) : request.args;
var nativeFunction = opt_args.nativeFunction || natives.StartRequest;
var requestId = natives.GetNextRequestId();
@@ -116,7 +121,7 @@
requests[requestId] = request;
var hasCallback =
(request.callback || opt_args.customCallback) ? true : false;
- return nativeFunction(functionName, sargs, requestId, hasCallback,
+ return nativeFunction(functionName, requestArgs, requestId, hasCallback,
opt_args.forIOThread);
}
diff --git a/chrome/test/data/extensions/api_test/idltest/binary_data/binary.html b/chrome/test/data/extensions/api_test/idltest/binary_data/binary.html
new file mode 100644
index 0000000..0f52baaa
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/idltest/binary_data/binary.html
@@ -0,0 +1 @@
+<script src="binary.js"></script>
diff --git a/chrome/test/data/extensions/api_test/idltest/binary_data/binary.js b/chrome/test/data/extensions/api_test/idltest/binary_data/binary.js
new file mode 100644
index 0000000..950db1c
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/idltest/binary_data/binary.js
@@ -0,0 +1,62 @@
+// 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.
+
+var assertEq = chrome.test.assertEq;
+var assertTrue = chrome.test.assertTrue;
+var callbackPass = chrome.test.callbackPass;
+
+function makeCompareCallback(buf) {
+ return function(array) {
+ assertEq(buf.byteLength, array.length);
+ for (var i = 0; i < buf.length; i++) {
+ assertEq(buf[i], array[i]);
+ }
+ };
+}
+
+function makeBuffer() {
+ var bufferSize = 128;
+ var ab = new ArrayBuffer(bufferSize);
+ var view = new Uint8Array(ab);
+ for (var i = 0; i < bufferSize; i++) {
+ view[i] = i+3;
+ }
+ return view;
+}
+
+var tests = [
+ function sendBuffer() {
+ var view = makeBuffer();
+ chrome.experimental.idltest.sendArrayBuffer(
+ view.buffer, callbackPass(makeCompareCallback(view.buffer)));
+ },
+
+ function sendBufferView() {
+ var view = makeBuffer();
+ chrome.experimental.idltest.sendArrayBufferView(
+ view, callbackPass(makeCompareCallback(view.buffer)));
+ },
+
+ function sendBufferSlice() {
+ var view = makeBuffer();
+ var bufferSlice = view.buffer.slice(64);
+ assertEq(64, bufferSlice.byteLength);
+ chrome.experimental.idltest.sendArrayBuffer(
+ bufferSlice, callbackPass(makeCompareCallback(bufferSlice)));
+ },
+
+ function getBuffer() {
+ chrome.experimental.idltest.getArrayBuffer(callbackPass(function(buffer) {
+ assertTrue(buffer.__proto__ == (new ArrayBuffer()).__proto__);
+ var view = new Uint8Array(buffer);
+ var expected = "hello world";
+ assertEq(view.byteLength, expected.length);
+ for (var i = 0; i < view.byteLength; i++) {
+ assertTrue(expected[i] == String.fromCharCode(view[i]));
+ }
+ }));
+ }
+];
+
+chrome.test.runTests(tests);
diff --git a/chrome/test/data/extensions/api_test/idltest/binary_data/manifest.json b/chrome/test/data/extensions/api_test/idltest/binary_data/manifest.json
new file mode 100644
index 0000000..b6b7767
--- /dev/null
+++ b/chrome/test/data/extensions/api_test/idltest/binary_data/manifest.json
@@ -0,0 +1,6 @@
+{
+ "name": "IDLTest API test extension",
+ "version": "0.5",
+ "permissions": ["experimental"],
+ "manifest_version": 2
+}
diff --git a/content/public/renderer/v8_value_converter.h b/content/public/renderer/v8_value_converter.h
index e8fad2a..6e785c1 100644
--- a/content/public/renderer/v8_value_converter.h
+++ b/content/public/renderer/v8_value_converter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -18,8 +18,10 @@
// values (from base/values.h). Lists and dictionaries are converted
// recursively.
//
-// Only the JSON types (null, boolean, string, number, array, and object) are
-// supported.
+// The JSON types (null, boolean, string, number, array, and object) as well as
+// binary values are supported. For binary values, we convert to WebKit
+// ArrayBuffers, and support converting from an ArrayBuffer or any of the
+// ArrayBufferView subclasses (Uint8Array, etc.).
class CONTENT_EXPORT V8ValueConverter {
public:
static V8ValueConverter* create();
diff --git a/content/renderer/v8_value_converter_impl.cc b/content/renderer/v8_value_converter_impl.cc
index b0762ef1..c457377 100644
--- a/content/renderer/v8_value_converter_impl.cc
+++ b/content/renderer/v8_value_converter_impl.cc
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -9,8 +9,17 @@
#include "base/logging.h"
#include "base/memory/scoped_ptr.h"
#include "base/values.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/WebArrayBuffer.h"
+#include "third_party/WebKit/Source/WebKit/chromium/public/platform/WebArrayBufferView.h"
#include "v8/include/v8.h"
+using base::BinaryValue;
+using base::DictionaryValue;
+using base::ListValue;
+using base::StringValue;
+using base::Value;
+
+
namespace content {
V8ValueConverter* V8ValueConverter::create() {
@@ -77,6 +86,9 @@
case Value::TYPE_DICTIONARY:
return ToV8Object(static_cast<const DictionaryValue*>(value));
+ case Value::TYPE_BINARY:
+ return ToArrayBuffer(static_cast<const BinaryValue*>(value));
+
default:
LOG(ERROR) << "Unexpected value type: " << value->GetType();
return v8::Null();
@@ -127,6 +139,14 @@
return result;
}
+v8::Handle<v8::Value> V8ValueConverterImpl::ToArrayBuffer(
+ const BinaryValue* value) const {
+ WebKit::WebArrayBuffer buffer =
+ WebKit::WebArrayBuffer::create(value->GetSize(), 1);
+ memcpy(buffer.data(), value->GetBuffer(), value->GetSize());
+ return buffer.toV8Value();
+}
+
Value* V8ValueConverterImpl::FromV8ValueImpl(v8::Handle<v8::Value> val) const {
CHECK(!val.IsEmpty());
@@ -164,9 +184,14 @@
if (val->IsArray())
return FromV8Array(val.As<v8::Array>());
- if (val->IsObject())
- return FromV8Object(val->ToObject());
-
+ if (val->IsObject()) {
+ BinaryValue* binary_value = FromV8Buffer(val);
+ if (binary_value) {
+ return binary_value;
+ } else {
+ return FromV8Object(val->ToObject());
+ }
+ }
LOG(ERROR) << "Unexpected v8 value type encountered.";
return Value::CreateNullValue();
}
@@ -194,6 +219,31 @@
return result;
}
+base::BinaryValue* V8ValueConverterImpl::FromV8Buffer(
+ v8::Handle<v8::Value> val) const {
+ char* data = NULL;
+ size_t length = 0;
+
+ WebKit::WebArrayBuffer* array_buffer =
+ WebKit::WebArrayBuffer::createFromV8Value(val);
+ if (array_buffer) {
+ data = reinterpret_cast<char*>(array_buffer->data());
+ length = array_buffer->byteLength();
+ } else {
+ WebKit::WebArrayBufferView* view =
+ WebKit::WebArrayBufferView::createFromV8Value(val);
+ if (view) {
+ data = reinterpret_cast<char*>(view->baseAddress()) + view->byteOffset();
+ length = view->byteLength();
+ }
+ }
+
+ if (data)
+ return base::BinaryValue::CreateWithCopiedBuffer(data, length);
+ else
+ return NULL;
+}
+
DictionaryValue* V8ValueConverterImpl::FromV8Object(
v8::Handle<v8::Object> val) const {
DictionaryValue* result = new DictionaryValue();
diff --git a/content/renderer/v8_value_converter_impl.h b/content/renderer/v8_value_converter_impl.h
index ac6e7b7..c43da46 100644
--- a/content/renderer/v8_value_converter_impl.h
+++ b/content/renderer/v8_value_converter_impl.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2011 The Chromium Authors. All rights reserved.
+// 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.
@@ -10,6 +10,7 @@
#include "content/public/renderer/v8_value_converter.h"
namespace base {
+class BinaryValue;
class DictionaryValue;
class ListValue;
class Value;
@@ -43,9 +44,16 @@
v8::Handle<v8::Value> ToV8Array(const base::ListValue* list) const;
v8::Handle<v8::Value> ToV8Object(
const base::DictionaryValue* dictionary) const;
+ v8::Handle<v8::Value> ToArrayBuffer(const base::BinaryValue* value) const;
base::Value* FromV8ValueImpl(v8::Handle<v8::Value> value) const;
base::ListValue* FromV8Array(v8::Handle<v8::Array> array) const;
+
+ // This will convert objects of type ArrayBuffer or any of the
+ // ArrayBufferView subclasses. The return value will be NULL if |value| is
+ // not one of these types.
+ base::BinaryValue* FromV8Buffer(v8::Handle<v8::Value> value) const;
+
base::DictionaryValue* FromV8Object(v8::Handle<v8::Object> object) const;
// If true, we will convert undefined JavaScript values to null.