From 030d980c6b0f4868eeff1c14eab03c93214732be Mon Sep 17 00:00:00 2001 From: Marijn Kruisselbrink Date: Tue, 19 May 2020 17:38:40 -0700 Subject: [PATCH 1/2] Rewrite a lot of the internals of Blobs to make reading more explicit. This redefines how blobs work by adding internal slots and hooks to support Blob objects backed by various sources of data. By doing so this tries to make more explicit how various edge cases behave, as well as making it clear that blobs are immutable. This fixes #75, fixes #99, and fixes #47. It also lays the ground work to address w3c/webappsec-clear-site-date#49. This also fixes #71. --- index.bs | 817 ++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 533 insertions(+), 284 deletions(-) diff --git a/index.bs b/index.bs index b2e321a..5ce7a41 100644 --- a/index.bs +++ b/index.bs @@ -84,6 +84,26 @@ spec: media-source; urlPrefix: http://w3c.github.io/media-source/ text: MediaSource; url: #mediasource + + # Introduction # {#intro} *This section is informative.* @@ -209,16 +229,6 @@ this is the same time that is conceptually "0" in ECMA-262 [[ECMA-2 # The Blob Interface and Binary Data # {#blob-section} -A {{Blob}} object refers to a byte sequence, -and has a {{Blob/size}} attribute which is the total number of bytes in the byte sequence, -and a {{Blob/type}} attribute, -which is an ASCII-encoded string in lower case representing the media type of the byte sequence. - -Each {{Blob}} must have an internal snapshot state, -which must be initially set to the state of the underlying storage, -if any such underlying storage exists. -Further normative definition of snapshot state can be found for {{File}}s. - [Exposed=(Window,Worker), Serializable] interface Blob { @@ -249,18 +259,43 @@ dictionary BlobPropertyBag { typedef (BufferSource or Blob or USVString) BlobPart; -{{Blob}} objects are [=serializable objects=]. Their [=serialization steps=], -given |value| and |serialized|, are: +A {{Blob}} has an associated \[[type]] internal slot, +an ASCII-encoded string in lower case representing the media type of the byte sequence. -1. Set |serialized|.\[[SnapshotState]] to |value|'s [=snapshot state=]. +A {{Blob}} has an associated \[[data]] internal slot, +a [=blob data description=]. -2. Set |serialized|.\[[ByteSequence]] to |value|'s underlying byte sequence. +
+{{Blob}} objects are [=serializable objects=]. Their [=serialization steps=] +(the blob serialization steps), +given |value|, |serialized| and |forStorage|, are: -Their [=deserialization step=], given |serialized| and |value|, are: +1. If |forStorage| is true: + 1. Let |bytes| be the result of [=read all bytes|reading all bytes=] from |value|. + 1. Set |serialized|.\[[BlobData]] to the result of [=creating blob data from bytes=] given |bytes|. + +1. Otherwise: + 1. Set |serialized|.\[[BlobData]] to |value|.[=[[data]]=]. + +1. Set |serialized|.\[[Type]] to |value|.[=[[type]]=]. + +
+ +
+Their [=deserialization step=] (the blob deserialization steps), +given |serialized| and |value|, are: -1. Set |value|'s [=snapshot state=] to |serialized|.\[[SnapshotState]]. +1. Set |value|.[=[[data]]=] to |serialized|.\[[BlobData]]. -2. Set |value|'s underlying byte sequence to |serialized|.\[[ByteSequence]]. +1. Set |value|.[=[[type]]=] to |serialized|.\[[Type]]. + +
+ +Issue(w3c/webappsec-clear-site-data#49): The actual storage API |serialized| was persisted in will need a way of +modifying the read algorithms for deserialized blobs. I.e. a Blob that was +deserialized from IndexedDB should start throwing in its read steps after +clear-site-data clears all IndexedDB data. Somehow let StructuredDeserialize +pass along a hook from the storage API to here?
A {{Blob}} |blob| has an associated get stream algorithm, @@ -269,88 +304,146 @@ which runs these steps: 1. Let |stream| be the result of [=construct a ReadableStream object|constructing=] a {{ReadableStream}} object. 1. Run the following steps [=in parallel=]: - 1. While not all bytes of |blob| have been read: - 1. Let |bytes| be the [=byte sequence=] that results from reading a [=chunk=] from |blob|. - 1. If a [=file read error=] occured while reading |bytes|, [$ReadableStream/error$] - |stream| with a [=failure reason=] and abort these steps. + 1. Let |blob data| be |blob|.[=[[data]]=]. + 1. Let |read state| be the result of calling |blob data|'s [=read initialization algorithm=] + given |blob data|'s [=snapshot state=] and 0. + If that threw an exception, [$ReadableStream/error$] |stream| with that exception + and abort these steps. + 1. While true: + 1. Let |read result| be the result of calling |blob data|'s [=read algorithm=] given |read state|. + 1. If that threw an exception (a [=file read error=]), + [$ReadableStream/error$] |stream| with that [=failure reason=] and abort these steps. + 1. If |read result| is [=end of blob=], [=break=]. 1. [=ReadableStream/Enqueue=] a `Uint8Array` object wrapping an `ArrayBuffer` - containing |bytes| into |stream|. - If that threw an exception, [$ReadableStream/error$] |stream| with that exception - and abort these steps. + containing |read result| into |stream|. + If that threw an exception, [$ReadableStream/error$] |stream| with that exception + and abort these steps. - Issue: We need to specify more concretely what reading from a Blob actually does, + Issue(144): We need to specify more concretely what reading from a Blob actually does, what possible errors can happen, perhaps something about chunk sizes, etc. 1. Return |stream|.
-## Constructors ## {#constructorBlob} +## Concepts ## {#blobConcepts} -
-The {{Blob()}} constructor can be invoked with zero or more parameters. -When the {{Blob()}} constructor is invoked, -user agents must run the following steps: +The data represented by a {{Blob}} is described by a blob data description, +consisting of some representation of the data, combined with a set of algorithms to +return the actual data as a series of [=byte sequences=]. -1. If invoked with zero parameters, - return a new {{Blob}} object consisting of 0 bytes, - with {{Blob/size}} set to 0, - and with {{Blob/type}} set to the empty string. +A [=blob data description=] has an associated size, +a number specifying the total number of bytes in the byte sequence represented by the blob. -1. Let |bytes| be the result of [=processing blob parts=] given {{blobParts}} and {{Blob/Blob(blobParts, options)/options}}. +A [=blob data description=] has an associated snapshot state. +This is a [=map=] that represents the data stored in the blob. -1. If the {{BlobPropertyBag/type}} member of the {{Blob/Blob(blobParts, options)/options}} argument is not the empty string, - run the following sub-steps: +A [=blob data description=] has an associated read initialization algorithm. +This algorithm takes two arguments: the snapshot state, and a byte offset. +It returns a [=struct=] which will be used as input for the [=read algorithm=]. + +A [=blob data description=] has an associated read algorithm. +This algorithm takes one argument (the [=struct=] returned by the [=read initialization algorithm=]). +It returns either a [=byte sequence=] or the special end of blob value. + +
+To read all bytes from a {{Blob}} |blob|, run these steps: - 1. Let |t| be the {{BlobPropertyBag/type}} dictionary member. - If |t| contains any characters outside the range U+0020 to U+007E, - then set |t| to the empty string and return from these substeps. - 1. Convert every character in |t| to [=ASCII lowercase=]. +1. Let |bytes| be an empty [=byte sequence=]. +1. Let |blob data| be |blob|.[=[[data]]=]. +1. Let |read state| be the result of calling |blob data|'s [=read initialization algorithm=] + given |blob data|'s [=snapshot state=] and 0. +1. While true: + 1. Let |read result| be the result of calling |blob data|'s [=read algorithm=] given |read state|. + 1. If |read result| is [=end of blob=], [=break=]. + 1. Append |read result| to |bytes|. +1. Return |bytes|. + +
+ +Conceptually a {{Blob}} represents a snapshot of some amount of data, and +is frozen in time at the time a {{Blob}} instance is created. +As such for any specific instance of a {{Blob}}, every invocation of [=read all bytes=] +should either return the exact same [=byte sequence=], or throw an exception. +Additionally the returned [=byte sequence=]'s [=byte sequence/length=] should be equal to +the blob [=[[data]]=]'s [=blob data/size=]. + +Note: This is a non-trivial requirement to implement for user agents, especially +when a blob is backed by a file on disk (i.e. was created by the +[=create a file backed File object=] algorithm). +User agents can use modification time stamps and other mechanisms to maintain +this requirement, but this is left as an implementation detail. + +### Byte Sequence backed blobs ### {#byte-sequence-backed-blobs} + +The [=snapshot state=] for a byte sequence backed blob contains a +"data" member, a [=byte sequence=]. + +
+To create blob data from bytes +given |bytes| (a [=byte sequence=]), run the following steps: + +1. Let |blob data| be a new [=blob data description=]. +1. Set |blob data|.[=snapshot state=]["data"] to |bytes|. + +1. Set |blob data|.[=read initialization algorithm=] to the [=bytes blob read initialization steps=]. +1. Set |blob data|.[=read algorithm=] to the [=bytes blob read steps=]. -1. Return a {{Blob}} object referring to |bytes| as its associated byte sequence, - with its {{Blob/size}} set to the length of |bytes|, - and its {{Blob/type}} set to the value of |t| from the substeps above. +1. Return |blob data|.
-### Constructor Parameters ### {#constructorParams} +A bytes blob read state is a [=struct=] conssting of +bytes (a [=byte sequence=]). -The {{Blob()}} constructor can be invoked with the parameters below: +
+The bytes blob read initialization steps, +given a |snapshot state| and |offset| are: -
-
A blobParts sequence -
which takes any number of the following types of elements, and in any order: - * {{BufferSource}} elements. - * {{Blob}} elements. - * {{USVString}} elements. - -
An *optional* {{BlobPropertyBag}} -
which takes these optional members: - * type, - the ASCII-encoded string in lower case representing the media type of the {{Blob}}. - Normative conditions for this member are provided in the [[#constructorBlob]]. - * endings, - an enum which can take the values {{"transparent"}} or {{"native"}}. - By default this is set to {{"transparent"}}. If set to {{"native"}}, - [=convert line endings to native|line endings will be converted to native=] - in any {{USVString}} elements in {{blobParts}}. -
+1. Let |read state| be a new [=bytes blob read state=]. +1. If |offset| is larger than |snapshot state|["data"]'s [=byte sequence/length=], + set |read state|.[=bytes blob read state/bytes=] to an empty [=byte sequence=]. +1. Otherwise, set |read state|.[=bytes blob read state/bytes=] to + a copy of |snapshot state|["data"] with the first |offset| bytes removed. +1. Return |read state|. + +
+ +
+The bytes blob read steps, +given |read state| (a [=bytes blob read state=]) are: + +1. Let |result| be |read state|.[=bytes blob read state/bytes=]. +1. Set |read state|.[=bytes blob read state/bytes=] to an empty [=byte sequence=]. +1. If |result| is empty, return [=end of blob=]. +1. Return |result|. + +
+ +### Multipart blobs ### {#multipart-blobs} + +Blobs created by the {{Blob}} and {{File}} constructors are made up of multiple parts, +where each part could be a blob itself. + +The [=snapshot state=] for a multipart blob contains a +"parts" member, +a [=list=] of [=blob data descriptions=].
-To process blob parts given a sequence of {{BlobPart}}'s |parts| -and {{BlobPropertyBag}} |options|, +To process blob parts +given a sequence of {{BlobPart}}'s |blobParts| and {{BlobPropertyBag}} |options|, run the following steps: -1. Let |bytes| be an empty sequence of bytes. +1. Let |size| be 0. +1. Let |parts| be an empty [=list=]. -1. For each |element| in |parts|: +1. Let |bytes| be an empty [=byte sequence=]. +1. For each |element| in |blobParts|: 1. If |element| is a {{USVString}}, run the following substeps: 1. Let |s| be |element|. - 1. If the {{BlobPropertyBag/endings}} member of |options| is {{"native"}}, set |s| to the result of [=converting line endings to native=] of |element|. - 1. Append the result of [=UTF-8 encoding=] |s| to |bytes|. Note: The algorithm from WebIDL [[WebIDL]] replaces unmatched surrogates in an invalid utf-16 string @@ -361,16 +454,179 @@ run the following steps: 1. If |element| is a {{BufferSource}}, get a copy of the bytes held by the buffer source, and append those bytes to |bytes|. - 1. If |element| is a {{Blob}}, - append the bytes it represents to |bytes|. + 1. If |element| is a {{Blob}}: + 1. If |bytes| is not empty: + 1. Let |part| be the result of [=creating blob data from bytes=] given |bytes|. + 1. [=list/Append=] |part| to |parts|. + 1. Set |size| to |size| + |part|.[=blob data/size=]. + 1. Set |bytes| to an empty [=byte sequence=]. + + 1. Let |part| be |element|.[=[[data]]=]. + 1. [=list/Append=] |part| to |parts|. + 1. Set |size| to |size| + |part|.[=blob data/size=]. Note: The {{Blob/type}} of the {{Blob}} array element is ignored and will not affect {{Blob/type}} of returned {{Blob}} object. -1. Return |bytes|. +1. If |bytes| is not empty: + 1. Let |part| be the result of [=creating blob data from bytes=] given |bytes|. + 1. [=list/Append=] |part| to |parts|. + 1. Set |size| to |size| + |part|.[=blob data/size=]. + +1. Let |result| be a [=blob data description=]. +1. Set |result|.[=blob data/size=] to |size|. +1. Set |result|.[=snapshot state=]["parts"] to |parts|. +1. Set |result|.[=read initialization algorithm=] to the [=multipart blob read initialization steps=]. +1. Set |result|.[=read algorithm=] to the [=multipart blob read steps=]. + +1. Return |result|. + +
+ +A multipart blob read state is a [=struct=] consisting of: + +
+: parts +:: A [=queue=] of [=blob data descriptions=], representing the not yet read parts of the blob. +: offset +:: A number, representing the byte offset in the remaining blob parts + from which to start returning data. +: nested blob data +:: `undefined` or a [=blob data description=]. This is `undefined` unless otherwise specified. +: nested read state +:: `undefined` or a [=struct=], representing the read state for a nested read operation. + This is `undefined` unless otherwise specified. + +
+ +
+The multipart blob read initialization steps, given a |snapshot state| and |offset| are: + +1. Let |read state| be a new [=multipart blob read state=]. +1. Set |read state|.[=multipart blob read state/parts=] to + a [=queue/clone=] of |snapshot state|["parts"]. +1. Set |read state|.[=multipart blob read state/offset=] to |offset|. +1. Return |read state|. + +
+ +
+The multipart blob read steps, given |read state| (a [=multipart blob read state=]) are: + +1. Let |result| be [=end of blob=]. +1. While |result| is [=end of blob=]: + + 1. If |read state|.[=multipart blob read state/nested read state=] is not `undefined`: + 1. [=Assert=]: |read state|.[=multipart blob read state/offset=] is 0. + 1. Set |result| to the result of calling + |read state|.[=multipart blob read state/nested blob data=]'s [=read algorithm=] + given |read state|.[=multipart blob read state/nested read state=]. + 1. If |result| is [=end of blob=]: + 1. Set |read state|.[=multipart blob read state/nested read state=] to `undefined`. + 1. Set |read state|.[=multipart blob read state/nested blob data=] to `undefined`. + + 1. Otherwise: + 1. If |read state|.[=multipart blob read state/parts=] is empty: + 1. Return [=end of blob=]. + + 1. Let |current part| be the result of [=dequeueing=] from |read state|.[=multipart blob read state/parts=]. + 1. If |read state|.[=multipart blob read state/offset=] >= |current part|.[=blob data/size=]: + 1. Set |read state|.[=multipart blob read state/offset=] to + |read state|.[=multipart blob read state/offset=] - |current part|.[=blob data/size=]. + 1. [=Continue=]. + + 1. Set |read state|.[=multipart blob read state/nested blob data=] to |current part|. + 1. Set |read state|.[=multipart blob read state/nested read state=] to the result of calling + |current part|'s [=read initialization algorithm=] + given |current part|'s [=snapshot state=] + and |read state|.[=multipart blob read state/offset=]. + 1. Set |read state|.[=multipart blob read state/offset=] to 0. + +1. Return |result|. + +
+ +### Sliced blobs ### {#sliced-blobs} + +Blobs created by the {{Blob/slice()}} method are also known as sliced blobs. + +The [=snapshot state=] for a sliced blob contains +a "offset" member (a number), +a "span" member (a number), +and a "source" member (a [=blob data description=]). + +A sliced blob read state is a [=struct=] consisting of: + +
+: source +:: A [=blob data description=], representing the blob that was sliced. +: bytes remaining +:: A number, representing the remaining number of bytes to be returned. +: nested read state +:: A [=struct=], representing the read state of the nested read operation. + +
+ +
+The sliced blob read initialization steps, given a |snapshot state| and |offset| are: + +1. Let |read state| be a new [=sliced blob read state=]. +1. Set |read state|.[=sliced blob read state/source=] to + |snapshot state|["source"]. +1. Set |read state|.[=sliced blob read state/bytes remaining=] to + |snapshot state|["span"] - |offset|. +1. Let |read offset| be |snapshot state|["offset"] + |offset|. +1. Set |read state|.[=sliced blob read state/nested read state=] to the result of calling + |read state|.[=sliced blob read state/source=]'s [=read initialization algorithm=] + given |read state|.[=sliced blob read state/source=]'s [=snapshot state=] + and |read offset|. +1. Return |read state|. + +
+ +
+The sliced blob read steps, given |read state| (a [=sliced blob read state=]) are: + +1. If |read state|.[=sliced blob read state/bytes remaining=] <= 0: + 1. Return [=end of blob=]. + +1. Let |result| be the result of calling + |read state|.[=sliced blob read state/source=]'s [=read algorithm=] + given |read state|.[=sliced blob read state/nested read state=]. +1. If |result| is not [=end of blob=]: + 1. If |result|'s [=byte sequence/length=] is larger than |read state|.[=sliced blob read state/bytes remaining=]: + 1. Truncate |result| to be |read state|.[=sliced blob read state/bytes remaining=] bytes long. + + 1. Set |read state|.[=sliced blob read state/bytes remaining=] to + |read state|.[=sliced blob read state/bytes remaining=] - |result|'s [=byte sequence/length=]. + +1. Return |result|.
+## Constructors ## {#constructorBlob} + + +
+The new Blob(|blobParts|, |options|) constructor steps are: + +1. Let |blob data| be the result of [=processing blob parts=] given |blobParts| and |options|. +1. Set [=this=].[=[[data]]=] to |blob data|. + +1. Let |type| be an empty string. +1. If the {{BlobPropertyBag/type}} member of the {{Blob/Blob(blobParts, options)/options}} argument is not the empty string, + run the following sub-steps: + + 1. Let |type| be the {{BlobPropertyBag/type}} dictionary member. + If |type| contains any characters outside the range U+0020 to U+007E, + then set |type| to the empty string and return from these substeps. + 1. Convert every character in |type| to [=ASCII lowercase=]. + +1. Set [=this=].[=[[type]]=] to |type|. + +
+ +
To convert line endings to native in a [=string=] |s|, @@ -449,93 +705,82 @@ run the following steps: ## Attributes ## {#attributes-blob} -
-
size -
Returns the size of the byte sequence in number of bytes. - On getting, conforming user agents must return the total number of bytes that can be read by a {{FileReader}} or {{FileReaderSync}} object, - or 0 if the {{Blob}} has no bytes to be read. - -
type -
The ASCII-encoded string in lower case representing the media type of the {{Blob}}. - On getting, user agents must return the type of a {{Blob}} - as an ASCII-encoded string in lower case, - such that when it is converted to a byte sequence, - it is a parsable MIME type, - or the empty string – 0 bytes – if the type cannot be determined. +
+: |blob| . {{Blob/size}} +:: Returns the size of the [=byte sequence=] represented by |blob| in number of bytes. - The {{Blob/type}} attribute can be set by the web application itself through constructor invocation - and through the {{Blob/slice()}} call; - in these cases, further normative conditions for this attribute are in [[#constructorBlob]], - [[#file-constructor]], - and [[#slice-method-algo]] respectively. - User agents can also determine the {{Blob/type}} of a {{Blob}}, - especially if the byte sequence is from an on-disk file; - in this case, further normative conditions are in the file type guidelines. - - Note: The type t of a {{Blob}} is considered a parsable MIME type, - if performing the parse a MIME type algorithm to a byte sequence converted from - the ASCII-encoded string representing the Blob object's type does not return failure. - - Note: Use of the {{Blob/type}} attribute informs the [=package data=] algorithm - and determines the `Content-Type` header when [=/fetching=] [=blob URLs=]. -
+
+ +The size getter steps are to return [=this=].[=[[data]]=].[=blob data/size=]. + +
+: |blob| . {{Blob/type}} +:: The ASCII-encoded string in lower case representing the media type of the {{Blob}}, + or an empty string if the type cannot be determined. + + The {{Blob/type}} attribute can be set by the web application itself through constructor invocation + and through the {{Blob/slice()}} call; + + Note: The type of a {{Blob}} is considered a parsable MIME type, + if performing the parse a MIME type algorithm to a byte sequence converted from + the ASCII-encoded string representing the Blob object's type does not return failure. + + Note: Use of the {{Blob/type}} attribute informs the [=package data=] algorithm + and determines the `Content-Type` header when [=/fetching=] [=blob URLs=]. + +
+ +The type getter steps are to return [=this=].[=[[type]]=]. ## Methods and Parameters ## {#methodsandparams-blob} ### The {{Blob/slice()}} method ### {#slice-method-algo} -The slice() method -returns a new {{Blob}} object with bytes ranging from -the optional {{start}} parameter -up to but not including the optional {{end}} parameter, -and with a {{Blob/type}} attribute that is the value of the optional {{contentType!!argument}} parameter. -It must act as follows: - -1. Let |O| be the {{Blob}} context object on which the {{slice()}} method is being called. -2. The optional start parameter - is a value for the start point of a {{slice()}} call, - and must be treated as a byte-order position, - with the zeroth position representing the first byte. - User agents must process {{Blob/slice()}} with {{start}} normalized according to the following: - -
    -
  1. If the optional {{start}} parameter is not used as a parameter when making this call, let |relativeStart| be 0. -
  2. If {{start}} is negative, let |relativeStart| be max(({{Blob/size}} + {{start}}), 0). -
  3. Else, let |relativeStart| be min(start, size). -
- -3. The optional end parameter - is a value for the end point of a {{slice()}} call. - User agents must process {{Blob/slice()}} with {{end}} normalized according to the following: - -
    -
  1. If the optional {{end}} parameter is not used as a parameter when making this call, let |relativeEnd| be {{Blob/size}}. -
  2. If {{end}} is negative, let |relativeEnd| be max((size + end), 0). -
  3. Else, let |relativeEnd| be min(end, size). -
- -4. The optional contentType parameter - is used to set the ASCII-encoded string in lower case representing the media type of the Blob. - User agents must process the {{Blob/slice()}} with {{contentType}} normalized according to the following: - -
    -
  1. If the {{contentType}} parameter is not provided, let |relativeContentType| be set to the empty string. -
  2. Else let |relativeContentType| be set to {{contentType}} and run the substeps below: - 1. If |relativeContentType| contains any characters outside the range of U+0020 to U+007E, - then set |relativeContentType| to the empty string and return from these substeps. - 2. Convert every character in |relativeContentType| to [=ASCII lowercase=]. -
- -5. Let |span| be max((relativeEnd - relativeStart), 0). - -6. Return a new {{Blob}} object |S| with the following characteristics: - -
    -
  1. |S| refers to |span| consecutive bytes from |O|, - beginning with the byte at byte-order position |relativeStart|. -
  2. |S|.{{Blob/size}} = |span|. -
  3. |S|.{{Blob/type}} = |relativeContentType|. -
+
+: |slice| = |blob| . {{Blob/slice()|slice}}( |start|, |end|, |contentType| ) +:: |slice| is a new {{Blob}} object, sharing storage with |blob|, with bytes ranging from + the optional |start| parameter + up to but not including the optional |end| parameter, + and with a {{Blob/type}} attribute that is the value of the optional |contentType| parameter. + + Negative values for |start| and |end| are interpreted as relative to the end of the |blob|. + +
+ +
+The slice(|start|, |end|, |contentType|) method steps are: + +1. Let |relativeStart| be 0. +1. If |start| is not `undefined`: + 1. If |start| < 0, set |relativeStart| to max([=this=].[=[[data]]=].[=blob data/size=] + |start|, 0). + 1. Otherwise, set |relativeStart| to min(|start|, [=this=].[=[[data]]=].[=blob data/size=]). + +1. Let |relativeEnd| be [=this=].[=[[data]]=].[=blob data/size=]. +1. If |end| is not `undefined`: + 1. If |end| < 0, set |relativeEnd| to max([=this=].[=[[data]]=].[=blob data/size=] + |end|, 0). + 1. Otherwise, set |relativeEnd| to min(|end|, [=this=].[=[[data]]=].[=blob data/size=]). + +1. Let |span| be max((relativeEnd - relativeStart), 0). + +1. Let |relativeContentType| be an empty string. +1. If |contentType| is not `undefined`: + 1. If |contentType| does not contain any characters outside the range of U+0x0020 to U+0x007E: + 1. Set |relativeContentType| to [=ASCII lowercase=] of |contentType|. + +1. Let |snapshot state| be a new [=map=]. +1. Set |snapshot state|["offset"] to |relativeStart|. +1. Set |snapshot state|["span"] to |span|. +1. Set |snapshot state|["source"] to [=this=].[=[[data]]=]. + +1. Let |result| be a new {{Blob}} object. +1. Set |result|.[=[[type]]=] to |relativeContentType|. +1. Set |result|.[=[[data]]=].[=blob data/size=] to |span|. +1. Set |result|.[=[[data]]=].[=snapshot state=] to |snapshot state|. +1. Set |result|.[=[[data]]=].[=read initialization algorithm=] to the [=sliced blob read initialization steps=]. +1. Set |result|.[=[[data]]=].[=read algorithm=] to the [=sliced blob read steps=]. +1. Return |result|. + +
The examples below illustrate the different types of {{slice()}} calls possible. Since the @@ -578,12 +823,12 @@ the result of calling [=get stream=] on the [=context object=]. The text() method, when invoked, must run these steps: -1. Let |stream| be the result of calling [=get stream=] on the [=context object=]. -1. Let |reader| be the result of [=get a reader|getting a reader=] from |stream|. - If that threw an exception, return a new promise rejected with that exception. -1. Let |promise| be the result of [=read all bytes|reading all bytes=] from |stream| with |reader|. -1. Return the result of transforming |promise| by a fulfillment handler that returns the result of - running [=UTF-8 decode=] on its first argument. +1. Let |promise| be [=a new Promise=]. +1. Run the following steps [=in parallel=]: + 1. Let |bytes| be the result of [=read all bytes|reading all bytes=] from [=this=]. + If that threw an exception, [=/reject=] |promise| with that exception and abort. + 1. [=/Resolve=] |promise| with the result of running [=UTF-8 decode=] on |bytes|. +1. Return |promise|. Note: This is different from the behavior of {{FileReader/readAsText()}} to align better with the behavior of {{Body/text()|Fetch's text()}}. Specifically this method will always @@ -594,12 +839,13 @@ the blob's type and passed in encoding name. The arrayBuffer() method, when invoked, must run these steps: -1. Let |stream| be the result of calling [=get stream=] on the [=context object=]. -1. Let |reader| be the result of [=get a reader|getting a reader=] from |stream|. - If that threw an exception, return a new promise rejected with that exception. -1. Let |promise| be the result of [=read all bytes|reading all bytes=] from |stream| with |reader|. -1. Return the result of transforming |promise| by a fulfillment handler that returns - a new {{ArrayBuffer}} whose contents are its first argument. +1. Let |promise| be [=a new Promise=]. +1. Run the following steps [=in parallel=]: + 1. Let |bytes| be the result of [=read all bytes|reading all bytes=] from [=this=]. + If that threw an exception, [=/reject=] |promise| with that exception and abort. + 1. [=/Resolve=] |promise| with a new {{ArrayBuffer}} whose contents are |bytes|. +1. Return |promise|. +