Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

buffer: make Blob's constructor and slice method more spec-compliant #37361

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
78 changes: 38 additions & 40 deletions lib/internal/blob.js
Expand Up @@ -2,6 +2,8 @@

const {
ArrayFrom,
MathMax,
MathMin,
ObjectDefineProperty,
ObjectSetPrototypeOf,
PromiseResolve,
Expand Down Expand Up @@ -41,21 +43,20 @@ const {
codes: {
ERR_INVALID_ARG_TYPE,
ERR_BUFFER_TOO_LARGE,
ERR_OUT_OF_RANGE,
}
} = require('internal/errors');

const {
validateObject,
validateString,
validateUint32,
isUint32,
} = require('internal/validators');

const kHandle = Symbol('kHandle');
const kType = Symbol('kType');
const kLength = Symbol('kLength');

const disallowedTypeCharacters = /[^\u{0020}-\u{007E}]/u;

let Buffer;

function lazyBuffer() {
Expand All @@ -72,22 +73,10 @@ function getSource(source, encoding) {
if (isBlob(source))
return [source.size, source[kHandle]];

if (typeof source === 'string') {
source = lazyBuffer().from(source, encoding);
} else if (isAnyArrayBuffer(source)) {
if (isAnyArrayBuffer(source)) {
source = new Uint8Array(source);
} else if (!isArrayBufferView(source)) {
throw new ERR_INVALID_ARG_TYPE(
'source',
[
'string',
'ArrayBuffer',
'SharedArrayBuffer',
'Buffer',
'TypedArray',
'DataView'
],
source);
source = lazyBuffer().from(`${source}`, encoding);
}

// We copy into a new Uint8Array because the underlying
Expand All @@ -108,19 +97,16 @@ class InternalBlob extends JSTransferable {
}

class Blob extends JSTransferable {
constructor(sources = [], options) {
constructor(sources = [], options = {}) {
emitExperimentalWarning('buffer.Blob');
if (sources === null ||
typeof sources[SymbolIterator] !== 'function' ||
typeof sources === 'string') {
throw new ERR_INVALID_ARG_TYPE('sources', 'Iterable', sources);
}
if (options !== undefined)
validateObject(options, 'options');
const {
encoding = 'utf8',
type = '',
} = { ...options };
validateObject(options, 'options');
const { encoding = 'utf8' } = options;
let { type = '' } = options;

let length = 0;
const sources_ = ArrayFrom(sources, (source) => {
Expand All @@ -129,17 +115,15 @@ class Blob extends JSTransferable {
return src;
});

// This is a MIME media type but we're not actively checking the syntax.
// But, to be fair, neither does Chrome.
validateString(type, 'options.type');

if (!isUint32(length))
throw new ERR_BUFFER_TOO_LARGE(0xFFFFFFFF);

super();
this[kHandle] = createBlob(sources_, length);
this[kLength] = length;
this[kType] = RegExpPrototypeTest(/[^\u{0020}-\u{007E}]/u, type) ?

type = `${type}`;
this[kType] = RegExpPrototypeTest(disallowedTypeCharacters, type) ?
'' : StringPrototypeToLowerCase(type);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI, It might be wrong to lowercase the hole type, but there is a pending spec change about that. Currently it says to lowercase the hole type, but we can deal with that later

}

Expand Down Expand Up @@ -178,18 +162,32 @@ class Blob extends JSTransferable {

get size() { return this[kLength]; }

slice(start = 0, end = (this[kLength]), type = this[kType]) {
validateUint32(start, 'start');
if (end < 0) end = this[kLength] + end;
validateUint32(end, 'end');
validateString(type, 'type');
if (end < start)
throw new ERR_OUT_OF_RANGE('end', 'greater than start', end);
if (end > this[kLength])
throw new ERR_OUT_OF_RANGE('end', 'less than or equal to length', end);
slice(start = 0, end = this[kLength], contentType = '') {
if (start < 0) {
start = MathMax(this[kLength] + start, 0);
} else {
start = MathMin(start, this[kLength]);
}
start |= 0;

if (end < 0) {
end = MathMax(this[kLength] + end, 0);
} else {
end = MathMin(end, this[kLength]);
}
end |= 0;

contentType = `${contentType}`;
if (RegExpPrototypeTest(disallowedTypeCharacters, contentType)) {
contentType = '';
} else {
contentType = StringPrototypeToLowerCase(contentType);
}

const span = MathMax(end - start, 0);

return new InternalBlob(
this[kHandle].slice(start, end),
end - start, type);
this[kHandle].slice(start, start + span), span, contentType);
}

async arrayBuffer() {
Expand Down
53 changes: 53 additions & 0 deletions test/fixtures/wpt/FileAPI/blob/Blob-constructor-dom.window.js
@@ -0,0 +1,53 @@
// META: title=Blob constructor
// META: script=../support/Blob.js
'use strict';

var test_error = {
name: "test",
message: "test error",
};

test(function() {
var args = [
document.createElement("div"),
window,
];
args.forEach(function(arg) {
assert_throws_js(TypeError, function() {
new Blob(arg);
}, "Should throw for argument " + format_value(arg) + ".");
});
}, "Passing platform objects for blobParts should throw a TypeError.");

test(function() {
var element = document.createElement("div");
element.appendChild(document.createElement("div"));
element.appendChild(document.createElement("p"));
var list = element.children;
Object.defineProperty(list, "length", {
get: function() { throw test_error; }
});
assert_throws_exactly(test_error, function() {
new Blob(list);
});
}, "A platform object that supports indexed properties should be treated as a sequence for the blobParts argument (overwritten 'length'.)");

test_blob(function() {
var select = document.createElement("select");
select.appendChild(document.createElement("option"));
return new Blob(select);
}, {
expected: "[object HTMLOptionElement]",
type: "",
desc: "Passing an platform object that supports indexed properties as the blobParts array should work (select)."
});

test_blob(function() {
var elm = document.createElement("div");
elm.setAttribute("foo", "bar");
return new Blob(elm.attributes);
}, {
expected: "[object Attr]",
type: "",
desc: "Passing an platform object that supports indexed properties as the blobParts array should work (attributes)."
});