diff --git a/filenames.gypi b/filenames.gypi index 4cecf4b896a..ae23be7a0f1 100644 --- a/filenames.gypi +++ b/filenames.gypi @@ -59,6 +59,7 @@ 'lib/common/api/native-image.js', 'lib/common/api/shell.js', 'lib/common/atom-binding-setup.js', + 'lib/common/buffer-utils.js', 'lib/common/init.js', 'lib/common/parse-features-string.js', 'lib/common/reset-search-paths.js', diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index 5784d2fd31e..585d9636e72 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -1,6 +1,5 @@ 'use strict' -const {Buffer} = require('buffer') const electron = require('electron') const {EventEmitter} = require('events') const fs = require('fs') @@ -9,6 +8,7 @@ const v8Util = process.atomBinding('v8_util') const {ipcMain, isPromise, webContents} = electron const objectsRegistry = require('./objects-registry') +const bufferUtils = require('../common/buffer-utils') const hasProp = {}.hasOwnProperty @@ -63,7 +63,7 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) { // Recognize certain types of objects. if (value === null) { meta.type = 'value' - } else if (ArrayBuffer.isView(value)) { + } else if (bufferUtils.isBuffer(value)) { meta.type = 'buffer' } else if (Array.isArray(value)) { meta.type = 'array' @@ -95,7 +95,7 @@ let valueToMeta = function (sender, value, optimizeSimpleObject = false) { meta.members = getObjectMembers(value) meta.proto = getObjectPrototype(value) } else if (meta.type === 'buffer') { - meta.value = Buffer.from(value) + meta.value = bufferUtils.bufferToMeta(value) } else if (meta.type === 'promise') { // Add default handler to prevent unhandled rejections in main process // Instead they should appear in the renderer process @@ -180,7 +180,7 @@ const unwrapArgs = function (sender, args) { case 'array': return unwrapArgs(sender, meta.value) case 'buffer': - return Buffer.from(meta.value) + return bufferUtils.metaToBuffer(meta.value) case 'date': return new Date(meta.value) case 'promise': diff --git a/lib/common/buffer-utils.js b/lib/common/buffer-utils.js new file mode 100644 index 00000000000..bd1303f4e90 --- /dev/null +++ b/lib/common/buffer-utils.js @@ -0,0 +1,58 @@ +// Note: Don't use destructuring assignment for `Buffer`, or we'll hit a +// browserify bug that makes the statement invalid, throwing an error in +// sandboxed renderer. +const Buffer = require('buffer').Buffer + +const typedArrays = { + Buffer, + ArrayBuffer, + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +} + +function getType (value) { + for (const type of Object.keys(typedArrays)) { + if (value instanceof typedArrays[type]) { + return type + } + } + return null +} + +exports.isBuffer = function (value) { + return ArrayBuffer.isView(value) || value instanceof ArrayBuffer +} + +exports.bufferToMeta = function (value) { + const buffer = (value instanceof ArrayBuffer) + ? Buffer.from(value) + : Buffer.from(value.buffer, value.byteOffset, value.byteLength) + + return { + type: getType(value), + data: buffer.toString('base64'), + length: value.length + } +} + +exports.metaToBuffer = function (value) { + const constructor = typedArrays[value.type] + const data = Buffer.from(value.data, 'base64') + + if (constructor === Buffer) { + return data + } else if (constructor === ArrayBuffer) { + return data.buffer + } else if (constructor) { + return new constructor(data.buffer, data.byteOffset, value.length) + } else { + return data + } +} diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index e1f7156f1ce..107ebfe4707 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -1,13 +1,11 @@ 'use strict' -// Note: Don't use destructuring assignment for `Buffer`, or we'll hit a -// browserify bug that makes the statement invalid, throwing an error in -// sandboxed renderer. -const Buffer = require('buffer').Buffer const v8Util = process.atomBinding('v8_util') const {ipcRenderer, isPromise, CallbacksRegistry} = require('electron') const resolvePromise = Promise.resolve.bind(Promise) +const bufferUtils = require('../../common/buffer-utils') + const callbacksRegistry = new CallbacksRegistry() const remoteObjectCache = v8Util.createIDWeakMap() @@ -30,10 +28,10 @@ function wrapArgs (args, visited = new Set()) { } visited.delete(value) return meta - } else if (ArrayBuffer.isView(value)) { + } else if (bufferUtils.isBuffer(value)) { return { type: 'buffer', - value: Buffer.from(value) + value: bufferUtils.bufferToMeta(value) } } else if (value instanceof Date) { return { @@ -202,7 +200,7 @@ function metaToValue (meta) { const types = { value: () => meta.value, array: () => meta.members.map((member) => metaToValue(member)), - buffer: () => Buffer.from(meta.value), + buffer: () => bufferUtils.metaToBuffer(meta.value), promise: () => resolvePromise({then: metaToValue(meta.then)}), error: () => metaToPlainObject(meta), date: () => new Date(meta.value), diff --git a/spec/api-remote-spec.js b/spec/api-remote-spec.js index 7c634e1740f..85a31af0a2b 100644 --- a/spec/api-remote-spec.js +++ b/spec/api-remote-spec.js @@ -259,12 +259,94 @@ describe('remote module', () => { assert.ok(arrayWithBuffer[2].equals(printName.echo(arrayWithBuffer)[2])) }) - it('supports TypedArray', () => { - const values = [1, 2, 3, 4] - assert.deepEqual(printName.typedArray(values), values) + it('supports instanceof ArrayBuffer', () => { + const buffer = new ArrayBuffer(8) + const view = new DataView(buffer) - const int16values = new Int16Array([1, 2, 3, 4]) - assert.deepEqual(printName.typedArray(int16values), int16values) + view.setFloat64(0, Math.PI) + assert.deepEqual(printName.echo(buffer), buffer) + assert.equal(printName.print(buffer), 'ArrayBuffer') + }) + + it('supports instanceof Int8Array', () => { + const values = [1, 2, 3, 4] + assert.deepEqual(printName.typedArray('Int8Array', values), values) + + const int8values = new Int8Array(values) + assert.deepEqual(printName.typedArray('Int8Array', int8values), int8values) + assert.equal(printName.print(int8values), 'Int8Array') + }) + + it('supports instanceof Uint8Array', () => { + const values = [1, 2, 3, 4] + assert.deepEqual(printName.typedArray('Uint8Array', values), values) + + const uint8values = new Uint8Array(values) + assert.deepEqual(printName.typedArray('Uint8Array', uint8values), uint8values) + assert.equal(printName.print(uint8values), 'Uint8Array') + }) + + it('supports instanceof Uint8ClampedArray', () => { + const values = [1, 2, 3, 4] + assert.deepEqual(printName.typedArray('Uint8ClampedArray', values), values) + + const uint8values = new Uint8ClampedArray(values) + assert.deepEqual(printName.typedArray('Uint8ClampedArray', uint8values), uint8values) + assert.equal(printName.print(uint8values), 'Uint8ClampedArray') + }) + + it('supports instanceof Int16Array', () => { + const values = [0x1234, 0x2345, 0x3456, 0x4567] + assert.deepEqual(printName.typedArray('Int16Array', values), values) + + const int16values = new Int16Array(values) + assert.deepEqual(printName.typedArray('Int16Array', int16values), int16values) + assert.equal(printName.print(int16values), 'Int16Array') + }) + + it('supports instanceof Uint16Array', () => { + const values = [0x1234, 0x2345, 0x3456, 0x4567] + assert.deepEqual(printName.typedArray('Uint16Array', values), values) + + const uint16values = new Uint16Array(values) + assert.deepEqual(printName.typedArray('Uint16Array', uint16values), uint16values) + assert.equal(printName.print(uint16values), 'Uint16Array') + }) + + it('supports instanceof Int32Array', () => { + const values = [0x12345678, 0x23456789] + assert.deepEqual(printName.typedArray('Int32Array', values), values) + + const int32values = new Int32Array(values) + assert.deepEqual(printName.typedArray('Int32Array', int32values), int32values) + assert.equal(printName.print(int32values), 'Int32Array') + }) + + it('supports instanceof Uint32Array', () => { + const values = [0x12345678, 0x23456789] + assert.deepEqual(printName.typedArray('Uint32Array', values), values) + + const uint32values = new Uint32Array(values) + assert.deepEqual(printName.typedArray('Uint32Array', uint32values), uint32values) + assert.equal(printName.print(uint32values), 'Uint32Array') + }) + + it('supports instanceof Float32Array', () => { + const values = [0.5, 1.0, 1.5] + assert.deepEqual(printName.typedArray('Float32Array', values), values) + + const float32values = new Float32Array() + assert.deepEqual(printName.typedArray('Float32Array', float32values), float32values) + assert.equal(printName.print(float32values), 'Float32Array') + }) + + it('supports instanceof Float64Array', () => { + const values = [0.5, 1.0, 1.5] + assert.deepEqual(printName.typedArray('Float64Array', values), values) + + const float64values = new Float64Array([0.5, 1.0, 1.5]) + assert.deepEqual(printName.typedArray('Float64Array', float64values), float64values) + assert.equal(printName.print(float64values), 'Float64Array') }) }) diff --git a/spec/fixtures/module/print_name.js b/spec/fixtures/module/print_name.js index fff59200eda..3985b88bf5f 100644 --- a/spec/fixtures/module/print_name.js +++ b/spec/fixtures/module/print_name.js @@ -6,10 +6,23 @@ exports.echo = function (obj) { return obj } -exports.typedArray = function (name) { - const int16 = new Int16Array(name.length) - for (let i = 0; i < name.length; ++i) { - int16[i] = name[i] - } - return int16 +const typedArrays = { + Int8Array, + Uint8Array, + Uint8ClampedArray, + Int16Array, + Uint16Array, + Int32Array, + Uint32Array, + Float32Array, + Float64Array +} + +exports.typedArray = function (type, values) { + const constructor = typedArrays[type] + const array = new constructor(values.length) + for (let i = 0; i < values.length; ++i) { + array[i] = values[i] + } + return array }