| // Copyright 2024 The Chromium Authors | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| import * as Common from '../../core/common/common.js'; | |
| import * as Platform from '../../core/platform/platform.js'; | |
| import {ContentData, type ContentDataOrError} from './ContentData.js'; | |
| /** | |
| * Usage of this class is mostly intended for content that is never "complete". | |
| * E.g. streaming XHR/fetch requests. | |
| * | |
| * Due to the streaming nature this class only supports base64-encoded binary data. | |
| * Decoding to text only happens on-demand by clients. This ensures that at most we have | |
| * incomplete unicode at the end and not in-between chunks. | |
| */ | |
| export class StreamingContentData extends Common.ObjectWrapper.ObjectWrapper<EventTypes> { | |
| readonly mimeType: string; | |
| readonly #charset?: string; | |
| readonly #disallowStreaming: boolean; | |
| #chunks: string[] = []; | |
| #contentData?: ContentData; | |
| private constructor(mimeType: string, charset?: string, initialContent?: ContentData) { | |
| super(); | |
| this.mimeType = mimeType; | |
| this.#charset = charset; | |
| this.#disallowStreaming = Boolean(initialContent && !initialContent.createdFromBase64); | |
| this.#contentData = initialContent; | |
| } | |
| /** | |
| * Creates a new StreamingContentData with the given MIME type/charset. | |
| */ | |
| static create(mimeType: string, charset?: string): StreamingContentData { | |
| return new StreamingContentData(mimeType, charset); | |
| } | |
| /** | |
| * Creates a new StringContentData from an existing ContentData instance. | |
| * | |
| * Calling `addChunk` is on the resulting `StreamingContentData` is illegal if | |
| * `content` was not created from base64 data. The reason is that JavaScript TextEncoder | |
| * only supports UTF-8. We can't convert text with arbitrary encoding back to base64 for concatenation. | |
| */ | |
| static from(content: ContentData): StreamingContentData { | |
| return new StreamingContentData(content.mimeType, content.charset, content); | |
| } | |
| /** @returns true, if this `ContentData` was constructed from text content or the mime type indicates text that can be decoded */ | |
| get isTextContent(): boolean { | |
| if (this.#contentData) { | |
| return this.#contentData.isTextContent; | |
| } | |
| return Platform.MimeType.isTextType(this.mimeType); | |
| } | |
| /** @param chunk base64 encoded data */ | |
| addChunk(chunk: string): void { | |
| if (this.#disallowStreaming) { | |
| throw new Error('Cannot add base64 data to a text-only ContentData.'); | |
| } | |
| this.#chunks.push(chunk); | |
| this.dispatchEventToListeners(Events.CHUNK_ADDED, {content: this, chunk}); | |
| } | |
| /** @returns An immutable ContentData with all the bytes received so far */ | |
| content(): ContentData { | |
| if (this.#contentData && this.#chunks.length === 0) { | |
| return this.#contentData; | |
| } | |
| const initialBase64 = this.#contentData?.base64 ?? ''; | |
| const base64Content = | |
| this.#chunks.reduce((acc, chunk) => Platform.StringUtilities.concatBase64(acc, chunk), initialBase64); | |
| this.#contentData = new ContentData(base64Content, /* isBase64=*/ true, this.mimeType, this.#charset); | |
| this.#chunks = []; | |
| return this.#contentData; | |
| } | |
| } | |
| export type StreamingContentDataOrError = StreamingContentData|{error: string}; | |
| export const isError = function(contentDataOrError: StreamingContentDataOrError): contentDataOrError is { | |
| error: string, | |
| } { | |
| return 'error' in contentDataOrError; | |
| }; | |
| export const asContentDataOrError = function(contentDataOrError: StreamingContentDataOrError): ContentDataOrError { | |
| if (isError(contentDataOrError)) { | |
| return contentDataOrError; | |
| } | |
| return contentDataOrError.content(); | |
| }; | |
| export const enum Events { | |
| CHUNK_ADDED = 'ChunkAdded', | |
| } | |
| export interface EventTypes { | |
| [Events.CHUNK_ADDED]: {content: StreamingContentData, chunk: string}; | |
| } |