diff options
author | hansmuller@chromium.org <hansmuller@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-25 18:48:57 +0000 |
---|---|---|
committer | hansmuller@chromium.org <hansmuller@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-07-25 18:48:57 +0000 |
commit | 32c5a54e596d25d904a545df5787abeb5736ca5e (patch) | |
tree | 9cbb078039f24f063da477cc75ce7df3f1e931b0 | |
parent | 5264305d2205771fc7622ce831ee70f18611b770 (diff) | |
download | chromium_src-32c5a54e596d25d904a545df5787abeb5736ca5e.zip chromium_src-32c5a54e596d25d904a545df5787abeb5736ca5e.tar.gz chromium_src-32c5a54e596d25d904a545df5787abeb5736ca5e.tar.bz2 |
Validate incoming JS Message Headers: test message parser
This is the first part of the "Validate incoming JS Message Headers" CL - https://codereview.chromium.org/406993002/
It's just the test message file parser and its sanity check.
TBR=jochen@chromium.org
BUG=395801
Review URL: https://codereview.chromium.org/411553003
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@285632 0039d316-1c4b-4281-b951-d872f2087c98
18 files changed, 686 insertions, 184 deletions
diff --git a/content/browser/webui/web_ui_data_source_impl.cc b/content/browser/webui/web_ui_data_source_impl.cc index 0523967..fb995e0 100644 --- a/content/browser/webui/web_ui_data_source_impl.cc +++ b/content/browser/webui/web_ui_data_source_impl.cc @@ -31,6 +31,7 @@ WebUIDataSource* WebUIDataSource::AddMojoDataSource( const char* path; int id; } resources[] = { + { mojo::kBufferModuleName, IDR_MOJO_BUFFER_JS }, { mojo::kCodecModuleName, IDR_MOJO_CODEC_JS }, { mojo::kConnectionModuleName, IDR_MOJO_CONNECTION_JS }, { mojo::kConnectorModuleName, IDR_MOJO_CONNECTOR_JS }, diff --git a/content/browser/webui/web_ui_mojo_browsertest.cc b/content/browser/webui/web_ui_mojo_browsertest.cc index 6261316..17b1bc8 100644 --- a/content/browser/webui/web_ui_mojo_browsertest.cc +++ b/content/browser/webui/web_ui_mojo_browsertest.cc @@ -40,7 +40,8 @@ bool got_message = false; bool GetResource(const std::string& id, const WebUIDataSource::GotDataCallback& callback) { // These are handled by the WebUIDataSource that AddMojoDataSource() creates. - if (id == mojo::kCodecModuleName || + if (id == mojo::kBufferModuleName || + id == mojo::kCodecModuleName || id == mojo::kConnectionModuleName || id == mojo::kConnectorModuleName || id == mojo::kUnicodeModuleName || diff --git a/content/content_resources.grd b/content/content_resources.grd index cdb7855..32a2862 100644 --- a/content/content_resources.grd +++ b/content/content_resources.grd @@ -39,6 +39,7 @@ <include name="IDR_UTILITY_SANDBOX_PROFILE" file="utility/utility.sb" type="BINDATA" /> </if> <if expr="not is_ios"> + <include name="IDR_MOJO_BUFFER_JS" file="../mojo/public/js/bindings/buffer.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_MOJO_CODEC_JS" file="../mojo/public/js/bindings/codec.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_MOJO_CONNECTION_JS" file="../mojo/public/js/bindings/connection.js" flattenhtml="true" type="BINDATA" /> <include name="IDR_MOJO_CONNECTOR_JS" file="../mojo/public/js/bindings/connector.js" flattenhtml="true" type="BINDATA" /> diff --git a/mojo/apps/js/test/js_to_cpp_unittest.js b/mojo/apps/js/test/js_to_cpp_unittest.js index 63b5f8b..b1073c8 100644 --- a/mojo/apps/js/test/js_to_cpp_unittest.js +++ b/mojo/apps/js/test/js_to_cpp_unittest.js @@ -98,8 +98,8 @@ define('mojo/apps/js/test/js_to_cpp_unittest', [ var value; if (offset < message.buffer.arrayBuffer.byteLength) { mask = 1 << (iteration % 8); - value = message.buffer.dataView.getUint8(offset) ^ mask; - message.buffer.dataView.setUint8(offset, value); + value = message.buffer.getUint8(offset) ^ mask; + message.buffer.setUint8(offset, value); return this.realAccept(message); } stopSignalled = true; diff --git a/mojo/apps/js/test/run_apps_js_tests.cc b/mojo/apps/js/test/run_apps_js_tests.cc index f01ecc8..36b185b 100644 --- a/mojo/apps/js/test/run_apps_js_tests.cc +++ b/mojo/apps/js/test/run_apps_js_tests.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/file_util.h" #include "base/files/file_path.h" #include "base/path_service.h" #include "gin/modules/console.h" @@ -13,7 +12,6 @@ #include "mojo/apps/js/bindings/monotonic_clock.h" #include "mojo/apps/js/bindings/threading.h" #include "mojo/bindings/js/core.h" -#include "mojo/common/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -50,30 +48,10 @@ void RunTest(std::string test, bool run_until_idle) { // TODO(abarth): Should we autogenerate these stubs from GYP? TEST(JSTest, sample_test) { - // TODO(yzshen): Remove this check once isolated tests are supported on the - // Chromium waterfall. (http://crbug.com/351214) - const base::FilePath test_file_path( - test::GetFilePathForJSResource( - "mojo/public/interfaces/bindings/tests/sample_service.mojom")); - if (!base::PathExists(test_file_path)) { - LOG(WARNING) << "Mojom binding files don't exist. Skipping the test."; - return; - } - RunTest("sample_service_unittests.js", true); } TEST(JSTest, connection) { - // TODO(yzshen): Remove this check once isolated tests are supported on the - // Chromium waterfall. (http://crbug.com/351214) - const base::FilePath test_file_path( - test::GetFilePathForJSResource( - "mojo/public/interfaces/bindings/tests/sample_service.mojom")); - if (!base::PathExists(test_file_path)) { - LOG(WARNING) << "Mojom binding files don't exist. Skipping the test."; - return; - } - RunTest("connection_unittests.js", false); } diff --git a/mojo/mojo.gyp b/mojo/mojo.gyp index aa34809..1e60ea0 100644 --- a/mojo/mojo.gyp +++ b/mojo/mojo.gyp @@ -751,20 +751,6 @@ ], }, { - 'target_name': 'mojo_js_unittests', - 'type': 'executable', - 'dependencies': [ - '../gin/gin.gyp:gin_test', - 'mojo_common_test_support', - 'mojo_js_bindings_lib', - 'mojo_run_all_unittests', - 'mojo_public_test_interfaces', - ], - 'sources': [ - 'bindings/js/run_js_tests.cc', - ], - }, - { 'target_name': 'mojo_message_generator', 'type': 'executable', 'dependencies': [ diff --git a/mojo/mojo_public_tests.gypi b/mojo/mojo_public_tests.gypi index 658d594..bbd1959 100644 --- a/mojo/mojo_public_tests.gypi +++ b/mojo/mojo_public_tests.gypi @@ -189,5 +189,19 @@ 'mojo_cpp_bindings', ], }, + { + 'target_name': 'mojo_js_unittests', + 'type': 'executable', + 'dependencies': [ + '../gin/gin.gyp:gin_test', + 'mojo_common_test_support', + 'mojo_js_bindings_lib', + 'mojo_public_test_interfaces', + 'mojo_run_all_unittests', + ], + 'sources': [ + 'public/js/bindings/tests/run_js_tests.cc', + ], + }, ], } diff --git a/mojo/public/js/bindings/buffer.js b/mojo/public/js/bindings/buffer.js new file mode 100644 index 0000000..d74fef6 --- /dev/null +++ b/mojo/public/js/bindings/buffer.js @@ -0,0 +1,156 @@ +// Copyright 2014 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. + +define("mojo/public/js/bindings/buffer", function() { + + var kHostIsLittleEndian = (function () { + var endianArrayBuffer = new ArrayBuffer(2); + var endianUint8Array = new Uint8Array(endianArrayBuffer); + var endianUint16Array = new Uint16Array(endianArrayBuffer); + endianUint16Array[0] = 1; + return endianUint8Array[0] == 1; + })(); + + var kHighWordMultiplier = 0x100000000; + + function Buffer(sizeOrArrayBuffer) { + if (sizeOrArrayBuffer instanceof ArrayBuffer) + this.arrayBuffer = sizeOrArrayBuffer; + else + this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer); + + this.dataView = new DataView(this.arrayBuffer); + this.next = 0; + } + + Object.defineProperty(Buffer.prototype, "byteLength", { + get: function() { return this.arrayBuffer.byteLength; } + }); + + Buffer.prototype.alloc = function(size) { + var pointer = this.next; + this.next += size; + if (this.next > this.byteLength) { + var newSize = (1.5 * (this.byteLength + size)) | 0; + this.grow(newSize); + } + return pointer; + }; + + function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) { + (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer)); + } + + Buffer.prototype.grow = function(size) { + var newArrayBuffer = new ArrayBuffer(size); + copyArrayBuffer(newArrayBuffer, this.arrayBuffer); + this.arrayBuffer = newArrayBuffer; + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.trim = function() { + this.arrayBuffer = this.arrayBuffer.slice(0, this.next); + this.dataView = new DataView(this.arrayBuffer); + }; + + Buffer.prototype.getUint8 = function(offset) { + return this.dataView.getUint8(offset); + } + Buffer.prototype.getUint16 = function(offset) { + return this.dataView.getUint16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint32 = function(offset) { + return this.dataView.getUint32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getUint64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getUint32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getInt8 = function(offset) { + return this.dataView.getInt8(offset); + } + Buffer.prototype.getInt16 = function(offset) { + return this.dataView.getInt16(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt32 = function(offset) { + return this.dataView.getInt32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getInt64 = function(offset) { + var lo, hi; + if (kHostIsLittleEndian) { + lo = this.dataView.getUint32(offset, kHostIsLittleEndian); + hi = this.dataView.getInt32(offset + 4, kHostIsLittleEndian); + } else { + hi = this.dataView.getInt32(offset, kHostIsLittleEndian); + lo = this.dataView.getUint32(offset + 4, kHostIsLittleEndian); + } + return lo + hi * kHighWordMultiplier; + } + + Buffer.prototype.getFloat32 = function(offset) { + return this.dataView.getFloat32(offset, kHostIsLittleEndian); + } + Buffer.prototype.getFloat64 = function(offset) { + return this.dataView.getFloat64(offset, kHostIsLittleEndian); + } + + Buffer.prototype.setUint8 = function(offset, value) { + this.dataView.setUint8(offset, value); + } + Buffer.prototype.setUint16 = function(offset, value) { + this.dataView.setUint16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint32 = function(offset, value) { + this.dataView.setUint32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setUint64 = function(offset, value) { + var hi = (value / kHighWordMultiplier) | 0; + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setInt8 = function(offset, value) { + this.dataView.setInt8(offset, value); + } + Buffer.prototype.setInt16 = function(offset, value) { + this.dataView.setInt16(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt32 = function(offset, value) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setInt64 = function(offset, value) { + var hi = Math.floor(value / kHighWordMultiplier); + if (kHostIsLittleEndian) { + this.dataView.setInt32(offset, value, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, hi, kHostIsLittleEndian); + } else { + this.dataView.setInt32(offset, hi, kHostIsLittleEndian); + this.dataView.setInt32(offset + 4, value, kHostIsLittleEndian); + } + } + + Buffer.prototype.setFloat32 = function(offset, value) { + this.dataView.setFloat32(offset, value, kHostIsLittleEndian); + } + Buffer.prototype.setFloat64 = function(offset, value) { + this.dataView.setFloat64(offset, value, kHostIsLittleEndian); + } + + var exports = {}; + exports.Buffer = Buffer; + return exports; +}); diff --git a/mojo/public/js/bindings/codec.js b/mojo/public/js/bindings/codec.js index 9756f4a..dca6009 100644 --- a/mojo/public/js/bindings/codec.js +++ b/mojo/public/js/bindings/codec.js @@ -3,112 +3,20 @@ // found in the LICENSE file. define("mojo/public/js/bindings/codec", [ - "mojo/public/js/bindings/unicode" -], function(unicode) { + "mojo/public/js/bindings/unicode", + "mojo/public/js/bindings/buffer" + ], function(unicode, buffer) { var kErrorUnsigned = "Passing negative value to unsigned"; // Memory ------------------------------------------------------------------- var kAlignment = 8; - var kHighWordMultiplier = 0x100000000; - var kHostIsLittleEndian = (function () { - var endianArrayBuffer = new ArrayBuffer(2); - var endianUint8Array = new Uint8Array(endianArrayBuffer); - var endianUint16Array = new Uint16Array(endianArrayBuffer); - endianUint16Array[0] = 1; - return endianUint8Array[0] == 1; - })(); function align(size) { return size + (kAlignment - (size % kAlignment)) % kAlignment; } - function getInt64(dataView, byteOffset, value) { - var lo, hi; - if (kHostIsLittleEndian) { - lo = dataView.getUint32(byteOffset, kHostIsLittleEndian); - hi = dataView.getInt32(byteOffset + 4, kHostIsLittleEndian); - } else { - hi = dataView.getInt32(byteOffset, kHostIsLittleEndian); - lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); - } - return lo + hi * kHighWordMultiplier; - } - - function getUint64(dataView, byteOffset, value) { - var lo, hi; - if (kHostIsLittleEndian) { - lo = dataView.getUint32(byteOffset, kHostIsLittleEndian); - hi = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); - } else { - hi = dataView.getUint32(byteOffset, kHostIsLittleEndian); - lo = dataView.getUint32(byteOffset + 4, kHostIsLittleEndian); - } - return lo + hi * kHighWordMultiplier; - } - - function setInt64(dataView, byteOffset, value) { - var hi = Math.floor(value / kHighWordMultiplier); - if (kHostIsLittleEndian) { - dataView.setInt32(byteOffset, value, kHostIsLittleEndian); - dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian); - } else { - dataView.setInt32(byteOffset, hi, kHostIsLittleEndian); - dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian); - } - } - - function setUint64(dataView, byteOffset, value) { - var hi = (value / kHighWordMultiplier) | 0; - if (kHostIsLittleEndian) { - dataView.setInt32(byteOffset, value, kHostIsLittleEndian); - dataView.setInt32(byteOffset + 4, hi, kHostIsLittleEndian); - } else { - dataView.setInt32(byteOffset, hi, kHostIsLittleEndian); - dataView.setInt32(byteOffset + 4, value, kHostIsLittleEndian); - } - } - - function copyArrayBuffer(dstArrayBuffer, srcArrayBuffer) { - (new Uint8Array(dstArrayBuffer)).set(new Uint8Array(srcArrayBuffer)); - } - - // Buffer ------------------------------------------------------------------- - - function Buffer(sizeOrArrayBuffer) { - if (sizeOrArrayBuffer instanceof ArrayBuffer) { - this.arrayBuffer = sizeOrArrayBuffer; - } else { - this.arrayBuffer = new ArrayBuffer(sizeOrArrayBuffer); - }; - - this.dataView = new DataView(this.arrayBuffer); - this.next = 0; - } - - Buffer.prototype.alloc = function(size) { - var pointer = this.next; - this.next += size; - if (this.next > this.arrayBuffer.byteLength) { - var newSize = (1.5 * (this.arrayBuffer.byteLength + size)) | 0; - this.grow(newSize); - } - return pointer; - }; - - Buffer.prototype.grow = function(size) { - var newArrayBuffer = new ArrayBuffer(size); - copyArrayBuffer(newArrayBuffer, this.arrayBuffer); - this.arrayBuffer = newArrayBuffer; - this.dataView = new DataView(this.arrayBuffer); - }; - - Buffer.prototype.trim = function() { - this.arrayBuffer = this.arrayBuffer.slice(0, this.next); - this.dataView = new DataView(this.arrayBuffer); - }; - // Constants ---------------------------------------------------------------- var kArrayHeaderSize = 8; @@ -130,64 +38,61 @@ define("mojo/public/js/bindings/codec", [ }; Decoder.prototype.readInt8 = function() { - var result = this.buffer.dataView.getInt8(this.next, kHostIsLittleEndian); + var result = this.buffer.getInt8(this.next); this.next += 1; return result; }; Decoder.prototype.readUint8 = function() { - var result = this.buffer.dataView.getUint8(this.next, kHostIsLittleEndian); + var result = this.buffer.getUint8(this.next); this.next += 1; return result; }; Decoder.prototype.readInt16 = function() { - var result = this.buffer.dataView.getInt16(this.next, kHostIsLittleEndian); + var result = this.buffer.getInt16(this.next); this.next += 2; return result; }; Decoder.prototype.readUint16 = function() { - var result = this.buffer.dataView.getUint16(this.next, kHostIsLittleEndian); + var result = this.buffer.getUint16(this.next); this.next += 2; return result; }; Decoder.prototype.readInt32 = function() { - var result = this.buffer.dataView.getInt32(this.next, kHostIsLittleEndian); + var result = this.buffer.getInt32(this.next); this.next += 4; return result; }; Decoder.prototype.readUint32 = function() { - var result = this.buffer.dataView.getUint32(this.next, kHostIsLittleEndian); + var result = this.buffer.getUint32(this.next); this.next += 4; return result; }; Decoder.prototype.readInt64 = function() { - var result = getInt64(this.buffer.dataView, this.next, kHostIsLittleEndian); + var result = this.buffer.getInt64(this.next); this.next += 8; return result; }; Decoder.prototype.readUint64 = function() { - var result = getUint64( - this.buffer.dataView, this.next, kHostIsLittleEndian); + var result = this.buffer.getUint64(this.next); this.next += 8; return result; }; Decoder.prototype.readFloat = function() { - var result = this.buffer.dataView.getFloat32( - this.next, kHostIsLittleEndian); + var result = this.buffer.getFloat32(this.next); this.next += 4; return result; }; Decoder.prototype.readDouble = function() { - var result = this.buffer.dataView.getFloat64( - this.next, kHostIsLittleEndian); + var result = this.buffer.getFloat64(this.next); this.next += 8; return result; }; @@ -293,8 +198,7 @@ define("mojo/public/js/bindings/codec", [ }; Encoder.prototype.writeInt8 = function(val) { - // NOTE: Endianness doesn't come into play for single bytes. - this.buffer.dataView.setInt8(this.next, val); + this.buffer.setInt8(this.next, val); this.next += 1; }; @@ -302,13 +206,12 @@ define("mojo/public/js/bindings/codec", [ if (val < 0) { throw new Error(kErrorUnsigned); } - // NOTE: Endianness doesn't come into play for single bytes. - this.buffer.dataView.setUint8(this.next, val); + this.buffer.setUint8(this.next, val); this.next += 1; }; Encoder.prototype.writeInt16 = function(val) { - this.buffer.dataView.setInt16(this.next, val, kHostIsLittleEndian); + this.buffer.setInt16(this.next, val); this.next += 2; }; @@ -316,12 +219,12 @@ define("mojo/public/js/bindings/codec", [ if (val < 0) { throw new Error(kErrorUnsigned); } - this.buffer.dataView.setUint16(this.next, val, kHostIsLittleEndian); + this.buffer.setUint16(this.next, val); this.next += 2; }; Encoder.prototype.writeInt32 = function(val) { - this.buffer.dataView.setInt32(this.next, val, kHostIsLittleEndian); + this.buffer.setInt32(this.next, val); this.next += 4; }; @@ -329,12 +232,12 @@ define("mojo/public/js/bindings/codec", [ if (val < 0) { throw new Error(kErrorUnsigned); } - this.buffer.dataView.setUint32(this.next, val, kHostIsLittleEndian); + this.buffer.setUint32(this.next, val); this.next += 4; }; Encoder.prototype.writeInt64 = function(val) { - setInt64(this.buffer.dataView, this.next, val); + this.buffer.setInt64(this.next, val); this.next += 8; }; @@ -342,17 +245,17 @@ define("mojo/public/js/bindings/codec", [ if (val < 0) { throw new Error(kErrorUnsigned); } - setUint64(this.buffer.dataView, this.next, val); + this.buffer.setUint64(this.next, val); this.next += 8; }; Encoder.prototype.writeFloat = function(val) { - this.buffer.dataView.setFloat32(this.next, val, kHostIsLittleEndian); + this.buffer.setFloat32(this.next, val); this.next += 4; }; Encoder.prototype.writeDouble = function(val) { - this.buffer.dataView.setFloat64(this.next, val, kHostIsLittleEndian); + this.buffer.setFloat64(this.next, val); this.next += 8; }; @@ -465,11 +368,11 @@ define("mojo/public/js/bindings/codec", [ Message.prototype.setRequestID = function(requestID) { // TODO(darin): Verify that space was reserved for this field! - setUint64(this.buffer.dataView, kRequestIDOffset, requestID); + this.buffer.setUint64(kRequestIDOffset, requestID); }; Message.prototype.getFlags = function() { - return this.buffer.dataView.getUint32(kFlagsOffset, kHostIsLittleEndian); + return this.buffer.getUint32(kFlagsOffset); }; // MessageBuilder ----------------------------------------------------------- @@ -478,7 +381,7 @@ define("mojo/public/js/bindings/codec", [ // Currently, we don't compute the payload size correctly ahead of time. // Instead, we resize the buffer at the end. var numberOfBytes = kMessageHeaderSize + payloadSize; - this.buffer = new Buffer(numberOfBytes); + this.buffer = new buffer.Buffer(numberOfBytes); this.handles = []; var encoder = this.createEncoder(kMessageHeaderSize); encoder.writeUint32(kMessageHeaderSize); @@ -514,7 +417,7 @@ define("mojo/public/js/bindings/codec", [ // Currently, we don't compute the payload size correctly ahead of time. // Instead, we resize the buffer at the end. var numberOfBytes = kMessageWithRequestIDHeaderSize + payloadSize; - this.buffer = new Buffer(numberOfBytes); + this.buffer = new buffer.Buffer(numberOfBytes); this.handles = []; var encoder = this.createEncoder(kMessageWithRequestIDHeaderSize); encoder.writeUint32(kMessageWithRequestIDHeaderSize); @@ -535,8 +438,7 @@ define("mojo/public/js/bindings/codec", [ function MessageReader(message) { this.decoder = new Decoder(message.buffer, message.handles, 0); var messageHeaderSize = this.decoder.readUint32(); - this.payloadSize = - message.buffer.arrayBuffer.byteLength - messageHeaderSize; + this.payloadSize = message.buffer.byteLength - messageHeaderSize; var numFields = this.decoder.readUint32(); this.messageName = this.decoder.readUint32(); this.flags = this.decoder.readUint32(); @@ -764,7 +666,6 @@ define("mojo/public/js/bindings/codec", [ var exports = {}; exports.align = align; - exports.Buffer = Buffer; exports.Message = Message; exports.MessageBuilder = MessageBuilder; exports.MessageWithRequestIDBuilder = MessageWithRequestIDBuilder; diff --git a/mojo/bindings/js/codec_unittests.js b/mojo/public/js/bindings/codec_unittests.js index e30acb5..14cd96a 100644 --- a/mojo/bindings/js/codec_unittests.js +++ b/mojo/public/js/bindings/codec_unittests.js @@ -1,4 +1,4 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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. diff --git a/mojo/public/js/bindings/connector.js b/mojo/public/js/bindings/connector.js index 51022b0..cf7c93b 100644 --- a/mojo/public/js/bindings/connector.js +++ b/mojo/public/js/bindings/connector.js @@ -3,10 +3,11 @@ // found in the LICENSE file. define("mojo/public/js/bindings/connector", [ + "mojo/public/js/bindings/buffer", "mojo/public/js/bindings/codec", "mojo/public/js/bindings/core", "mojo/public/js/bindings/support", -], function(codec, core, support) { +], function(buffer, codec, core, support) { function Connector(handle) { this.handle_ = handle; @@ -95,8 +96,8 @@ define("mojo/public/js/bindings/connector", [ this.errorHandler_.onError(read.result); return; } - var buffer = new codec.Buffer(read.buffer); - var message = new codec.Message(buffer, read.handles); + var messageBuffer = new buffer.Buffer(read.buffer); + var message = new codec.Message(messageBuffer, read.handles); if (this.incomingReceiver_) { this.incomingReceiver_.accept(message); } diff --git a/mojo/public/js/bindings/constants.cc b/mojo/public/js/bindings/constants.cc index 239b67d..246bd36 100644 --- a/mojo/public/js/bindings/constants.cc +++ b/mojo/public/js/bindings/constants.cc @@ -6,6 +6,7 @@ namespace mojo { +const char kBufferModuleName[] = "mojo/public/js/bindings/buffer"; const char kCodecModuleName[] = "mojo/public/js/bindings/codec"; const char kConnectionModuleName[] = "mojo/public/js/bindings/connection"; const char kConnectorModuleName[] = "mojo/public/js/bindings/connector"; diff --git a/mojo/public/js/bindings/constants.h b/mojo/public/js/bindings/constants.h index 50e5d7c..c048cb2 100644 --- a/mojo/public/js/bindings/constants.h +++ b/mojo/public/js/bindings/constants.h @@ -8,6 +8,7 @@ namespace mojo { // JavaScript module names: +extern const char kBufferModuleName[]; extern const char kCodecModuleName[]; extern const char kConnectionModuleName[]; extern const char kConnectorModuleName[]; diff --git a/mojo/bindings/js/core_unittests.js b/mojo/public/js/bindings/core_unittests.js index fa6cb4f..b115cc0 100644 --- a/mojo/bindings/js/core_unittests.js +++ b/mojo/public/js/bindings/core_unittests.js @@ -1,4 +1,4 @@ -// Copyright 2013 The Chromium Authors. All rights reserved. +// Copyright 2014 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. diff --git a/mojo/public/js/bindings/tests/DEPS b/mojo/public/js/bindings/tests/DEPS new file mode 100644 index 0000000..37eb10c --- /dev/null +++ b/mojo/public/js/bindings/tests/DEPS @@ -0,0 +1,6 @@ +include_rules = [ + "+base", + "+gin", + "+v8", + "+mojo/bindings/js/core.h", +] diff --git a/mojo/bindings/js/run_js_tests.cc b/mojo/public/js/bindings/tests/run_js_tests.cc index 976a0b1..d791e0b 100644 --- a/mojo/bindings/js/run_js_tests.cc +++ b/mojo/public/js/bindings/tests/run_js_tests.cc @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/file_util.h" #include "base/files/file_path.h" #include "base/path_service.h" #include "gin/modules/console.h" @@ -11,7 +10,6 @@ #include "gin/test/file_runner.h" #include "gin/test/gtest.h" #include "mojo/bindings/js/core.h" -#include "mojo/common/test/test_utils.h" #include "testing/gtest/include/gtest/gtest.h" namespace mojo { @@ -33,8 +31,9 @@ void RunTest(std::string test, bool run_until_idle) { base::FilePath path; PathService::Get(base::DIR_SOURCE_ROOT, &path); path = path.AppendASCII("mojo") - .AppendASCII("bindings") + .AppendASCII("public") .AppendASCII("js") + .AppendASCII("bindings") .AppendASCII(test); TestRunnerDelegate delegate; gin::RunTestFromFile(path, &delegate, run_until_idle); @@ -46,19 +45,13 @@ TEST(JSTest, core) { } TEST(JSTest, codec) { - // TODO(yzshen): Remove this check once isolated tests are supported on the - // Chromium waterfall. (http://crbug.com/351214) - const base::FilePath test_file_path( - test::GetFilePathForJSResource( - "mojo/public/interfaces/bindings/tests/sample_service.mojom")); - if (!base::PathExists(test_file_path)) { - LOG(WARNING) << "Mojom binding files don't exist. Skipping the test."; - return; - } - RunTest("codec_unittests.js", true); } +TEST(JSTest, validation) { + RunTest("validation_unittests.js", true); +} + } // namespace } // namespace js } // namespace mojo diff --git a/mojo/public/js/bindings/tests/validation_test_input_parser.js b/mojo/public/js/bindings/tests/validation_test_input_parser.js new file mode 100644 index 0000000..98b1c19 --- /dev/null +++ b/mojo/public/js/bindings/tests/validation_test_input_parser.js @@ -0,0 +1,299 @@ +// Copyright 2014 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. + +// Support for parsing binary sequences encoded as readable strings +// or ".data" files. The input format is described here: +// mojo/public/cpp/bindings/tests/validation_test_input_parser.h + +define([ + "mojo/public/js/bindings/buffer" + ], function(buffer) { + + // Files and Lines represent the raw text from an input string + // or ".data" file. + + function InputError(message, line) { + this.message = message; + this.line = line; + } + + InputError.prototype.toString = function() { + var s = 'Error: ' + this.message; + if (this.line) + s += ', at line ' + + (this.line.number + 1) + ': "' + this.line.contents + '"'; + return s; + } + + function File(contents) { + this.contents = contents; + this.index = 0; + this.lineNumber = 0; + } + + File.prototype.endReached = function() { + return this.index >= this.contents.length; + } + + File.prototype.nextLine = function() { + if (this.endReached()) + return null; + var start = this.index; + var end = this.contents.indexOf('\n', start); + if (end == -1) + end = this.contents.length; + this.index = end + 1; + return new Line(this.contents.substring(start, end), this.lineNumber++); + } + + function Line(contents, number) { + var i = contents.indexOf('//'); + var s = (i == -1) ? contents.trim() : contents.substring(0, i).trim(); + this.contents = contents; + this.items = (s.length > 0) ? s.split(/\s+/) : []; + this.index = 0; + this.number = number; + } + + Line.prototype.endReached = function() { + return this.index >= this.items.length; + } + + var ITEM_TYPE_SIZES = { + u1: 1, u2: 2, u4: 4, u8: 8, s1: 1, s2: 2, s4: 4, s8: 8, b: 1, f: 4, d: 8, + dist4: 4, dist8: 8, anchr: 0, handles: 0 + }; + + function isValidItemType(type) { + return ITEM_TYPE_SIZES[type] !== undefined; + } + + Line.prototype.nextItem = function() { + if (this.endReached()) + return null; + + var itemString = this.items[this.index++]; + var type = 'u1'; + var value = itemString; + + if (itemString.charAt(0) == '[') { + var i = itemString.indexOf(']'); + if (i != -1 && i + 1 < itemString.length) { + type = itemString.substring(1, i); + value = itemString.substring(i + 1); + } else { + throw new InputError('invalid item', this); + } + } + if (!isValidItemType(type)) + throw new InputError('invalid item type', this); + + return new Item(this, type, value); + } + + // The text for each whitespace delimited binary data "item" is represented + // by an Item. + + function Item(line, type, value) { + this.line = line; + this.type = type; + this.value = value; + this.size = ITEM_TYPE_SIZES[type]; + } + + Item.prototype.isFloat = function() { + return this.type == 'f' || this.type == 'd'; + } + + Item.prototype.isInteger = function() { + return ['u1', 'u2', 'u4', 'u8', + 's1', 's2', 's4', 's8'].indexOf(this.type) != -1; + } + + Item.prototype.isNumber = function() { + return this.isFloat() || this.isInteger(); + } + + Item.prototype.isByte = function() { + return this.type == 'b'; + } + + Item.prototype.isDistance = function() { + return this.type == 'dist4' || this.type == 'dist8'; + } + + Item.prototype.isAnchor = function() { + return this.type == 'anchr'; + } + + Item.prototype.isHandles = function() { + return this.type == 'handles'; + } + + // A TestMessage represents the complete binary message loaded from an input + // string or ".data" file. The parseTestMessage() function below constructs + // a TestMessage from a File. + + function TestMessage(byteLength) { + this.index = 0; + this.buffer = new buffer.Buffer(byteLength); + this.distances = {}; + this.handleCount = 0; + } + + function checkItemNumberValue(item, n, min, max) { + if (n < min || n > max) + throw new InputError('invalid item value', item.line); + } + + TestMessage.prototype.addNumber = function(item) { + var n = item.isInteger() ? parseInt(item.value) : parseFloat(item.value); + if (Number.isNaN(n)) + throw new InputError("can't parse item value", item.line); + + switch(item.type) { + case 'u1': + checkItemNumberValue(item, n, 0, 0xFF); + this.buffer.setUint8(this.index, n); + break; + case 'u2': + checkItemNumberValue(item, n, 0, 0xFFFF); + this.buffer.setUint16(this.index, n); + break; + case 'u4': + checkItemNumberValue(item, n, 0, 0xFFFFFFFF); + this.buffer.setUint32(this.index, n); + break; + case 'u8': + checkItemNumberValue(item, n, 0, Number.MAX_SAFE_INTEGER); + this.buffer.setUint64(this.index, n); + break; + case 's1': + checkItemNumberValue(item, n, -128, 127); + this.buffer.setInt8(this.index, n); + break; + case 's2': + checkItemNumberValue(item, n, -32768, 32767); + this.buffer.setInt16(this.index, n); + break; + case 's4': + checkItemNumberValue(item, n, -2147483648, 2147483647); + this.buffer.setInt32(this.index, n); + break; + case 's8': + checkItemNumberValue(item, n, + Number.MIN_SAFE_INTEGER, + Number.MAX_SAFE_INTEGER); + this.buffer.setInt64(this.index, n); + break; + case 'f': + this.buffer.setFloat32(this.index, n); + break; + case 'd': + this.buffer.setFloat64(this.index, n); + break; + + default: + throw new InputError('unrecognized item type', item.line); + } + } + + TestMessage.prototype.addByte = function(item) { + if (!/^[01]{8}$/.test(item.value)) + throw new InputError('invalid byte item value', item.line); + function b(i) { + return (item.value.charAt(7 - i) == '1') ? 1 << i : 0; + } + var n = b(0) | b(1) | b(2) | b(3) | b(4) | b(5) | b(6) | b(7); + this.buffer.setUint8(this.index, n); + } + + TestMessage.prototype.addDistance = function(item) { + if (this.distances[item.value]) + throw new InputError('duplicate distance item', item.line); + this.distances[item.value] = {index: this.index, item: item}; + } + + TestMessage.prototype.addAnchor = function(item) { + var dist = this.distances[item.value]; + if (!dist) + throw new InputError('unmatched anchor item', item.line); + delete this.distances[item.value]; + + var n = this.index - dist.index; + // TODO(hansmuller): validate n + + if (dist.item.type == 'dist4') + this.buffer.setUint32(dist.index, n); + else if (dist.item.type == 'dist8') + this.buffer.setUint64(dist.index, n); + else + throw new InputError('unrecognzed distance item type', dist.item.line); + } + + TestMessage.prototype.addHandles = function(item) { + this.handleCount = parseInt(item.value); + if (Number.isNaN(this.handleCount)) + throw new InputError("can't parse handleCount", item.line); + } + + TestMessage.prototype.addItem = function(item) { + if (item.isNumber()) + this.addNumber(item); + else if (item.isByte()) + this.addByte(item); + else if (item.isDistance()) + this.addDistance(item); + else if (item.isAnchor()) + this.addAnchor(item); + else if (item.isHandles()) + this.addHandles(item); + else + throw new InputError('unrecognized item type', item.line); + + this.index += item.size; + } + + TestMessage.prototype.unanchoredDistances = function() { + var names = null; + for (var name in this.distances) { + if (this.distances.hasOwnProperty(name)) + names = (names === null) ? name : names + ' ' + name; + } + return names; + } + + function parseTestMessage(text) { + var file = new File(text); + var items = []; + var messageLength = 0; + while(!file.endReached()) { + var line = file.nextLine(); + while (!line.endReached()) { + var item = line.nextItem(); + if (item.isHandles() && items.length > 0) + throw new InputError('handles item is not first'); + messageLength += item.size; + items.push(item); + } + } + + var msg = new TestMessage(messageLength); + for (var i = 0; i < items.length; i++) + msg.addItem(items[i]); + + if (messageLength != msg.index) + throw new InputError('failed to compute message length'); + var names = msg.unanchoredDistances(); + if (names) + throw new InputError('no anchors for ' + names, 0); + + return msg; + } + + var exports = {}; + exports.parseTestMessage = parseTestMessage; + exports.InputError = InputError; + return exports; +}); diff --git a/mojo/public/js/bindings/validation_unittests.js b/mojo/public/js/bindings/validation_unittests.js new file mode 100644 index 0000000..6931777 --- /dev/null +++ b/mojo/public/js/bindings/validation_unittests.js @@ -0,0 +1,163 @@ +// Copyright 2014 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. + +define([ + "gin/test/expect", + "mojo/public/js/bindings/buffer", + "mojo/public/js/bindings/tests/validation_test_input_parser" + ], function(expect, buffer, parser) { + + function checkTestMessageParser() { + function TestMessageParserFailure(message, input) { + this.message = message; + this.input = input; + } + + TestMessageParserFailure.prototype.toString = function() { + return 'Error: ' + this.message + ' for "' + this.input + '"'; + } + + function checkData(data, expectedData, input) { + if (data.byteLength != expectedData.byteLength) { + var s = "message length (" + data.byteLength + ") doesn't match " + + "expected length: " + expectedData.byteLength; + throw new TestMessageParserFailure(s, input); + } + + for (var i = 0; i < data.byteLength; i++) { + if (data.getUint8(i) != expectedData.getUint8(i)) { + var s = 'message data mismatch at byte offset ' + i; + throw new TestMessageParserFailure(s, input); + } + } + } + + function testFloatItems() { + var input = '[f]+.3e9 [d]-10.03'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(12); + expectedData.setFloat32(0, +.3e9); + expectedData.setFloat64(4, -10.03); + checkData(msg.buffer, expectedData, input); + } + + function testUnsignedIntegerItems() { + var input = '[u1]0x10// hello world !! \n\r \t [u2]65535 \n' + + '[u4]65536 [u8]0xFFFFFFFFFFFFF 0 0Xff'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(17); + expectedData.setUint8(0, 0x10); + expectedData.setUint16(1, 65535); + expectedData.setUint32(3, 65536); + expectedData.setUint64(7, 0xFFFFFFFFFFFFF); + expectedData.setUint8(15, 0); + expectedData.setUint8(16, 0xff); + checkData(msg.buffer, expectedData, input); + } + + function testSignedIntegerItems() { + var input = '[s8]-0x800 [s1]-128\t[s2]+0 [s4]-40'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(15); + expectedData.setInt64(0, -0x800); + expectedData.setInt8(8, -128); + expectedData.setInt16(9, 0); + expectedData.setInt32(11, -40); + checkData(msg.buffer, expectedData, input); + } + + function testByteItems() { + var input = '[b]00001011 [b]10000000 // hello world\n [b]00000000'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(3); + expectedData.setUint8(0, 11); + expectedData.setUint8(1, 128); + expectedData.setUint8(2, 0); + checkData(msg.buffer, expectedData, input); + } + + function testAnchors() { + var input = '[dist4]foo 0 [dist8]bar 0 [anchr]foo [anchr]bar'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(14); + expectedData.setUint32(0, 14); + expectedData.setUint8(4, 0); + expectedData.setUint64(5, 9); + expectedData.setUint8(13, 0); + checkData(msg.buffer, expectedData, input); + } + + function testHandles() { + var input = '// This message has handles! \n[handles]50 [u8]2'; + var msg = parser.parseTestMessage(input); + var expectedData = new buffer.Buffer(8); + expectedData.setUint64(0, 2); + + if (msg.handleCount != 50) { + var s = 'wrong handle count (' + msg.handleCount + ')'; + throw new TestMessageParserFailure(s, input); + } + checkData(msg.buffer, expectedData, input); + } + + function testEmptyInput() { + var msg = parser.parseTestMessage(''); + if (msg.buffer.byteLength != 0) + throw new TestMessageParserFailure('expected empty message', ''); + } + + function testBlankInput() { + var input = ' \t // hello world \n\r \t// the answer is 42 '; + var msg = parser.parseTestMessage(input); + if (msg.buffer.byteLength != 0) + throw new TestMessageParserFailure('expected empty message', input); + } + + function testInvalidInput() { + function parserShouldFail(input) { + try { + parser.parseTestMessage(input); + } catch (e) { + if (e instanceof parser.InputError) + return; + throw new TestMessageParserFailure( + 'unexpected exception ' + e.toString(), input); + } + throw new TestMessageParserFailure("didn't detect invalid input", file); + } + + ['/ hello world', + '[u1]x', + '[u2]-1000', + '[u1]0x100', + '[s2]-0x8001', + '[b]1', + '[b]1111111k', + '[dist4]unmatched', + '[anchr]hello [dist8]hello', + '[dist4]a [dist4]a [anchr]a', + // '[dist4]a [anchr]a [dist4]a [anchr]a', + '0 [handles]50' + ].forEach(parserShouldFail); + } + + try { + testFloatItems(); + testUnsignedIntegerItems(); + testSignedIntegerItems(); + testByteItems(); + testInvalidInput(); + testEmptyInput(); + testBlankInput(); + testHandles(); + testAnchors(); + } catch (e) { + return e.toString(); + } + return null; + } + + expect(checkTestMessageParser()).toBeNull(); + this.result = "PASS"; +}); |