chrome-devtools-frontend
Version:
Chrome DevTools UI
282 lines (243 loc) • 10.2 kB
text/typescript
// Copyright 2021 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.
/*
* Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
import * as TextUtils from '../../models/text_utils/text_utils.js';
import * as Common from '../common/common.js';
import * as Platform from '../platform/platform.js';
import type * as Protocol from '../../generated/protocol.js';
import {Events, type NetworkRequest} from './NetworkRequest.js';
import {type ResourceTreeFrame, type ResourceTreeModel} from './ResourceTreeModel.js';
export class Resource implements TextUtils.ContentProvider.ContentProvider {
readonly #resourceTreeModel: ResourceTreeModel;
#requestInternal: NetworkRequest|null;
#urlInternal!: Platform.DevToolsPath.UrlString;
readonly #documentURLInternal: Platform.DevToolsPath.UrlString;
readonly #frameIdInternal: Protocol.Page.FrameId|null;
readonly #loaderIdInternal: Protocol.Network.LoaderId|null;
readonly #type: Common.ResourceType.ResourceType;
#mimeTypeInternal: string;
#isGeneratedInternal: boolean;
#lastModifiedInternal: Date|null;
readonly #contentSizeInternal: number|null;
#contentInternal!: string|null;
#contentEncodedInternal!: boolean;
readonly #pendingContentCallbacks: ((arg0: Object|null) => void)[];
#parsedURLInternal?: Common.ParsedURL.ParsedURL;
#contentRequested?: boolean;
constructor(
resourceTreeModel: ResourceTreeModel, request: NetworkRequest|null, url: Platform.DevToolsPath.UrlString,
documentURL: Platform.DevToolsPath.UrlString, frameId: Protocol.Page.FrameId|null,
loaderId: Protocol.Network.LoaderId|null, type: Common.ResourceType.ResourceType, mimeType: string,
lastModified: Date|null, contentSize: number|null) {
this.#resourceTreeModel = resourceTreeModel;
this.#requestInternal = request;
this.url = url;
this.#documentURLInternal = documentURL;
this.#frameIdInternal = frameId;
this.#loaderIdInternal = loaderId;
this.#type = type || Common.ResourceType.resourceTypes.Other;
this.#mimeTypeInternal = mimeType;
this.#isGeneratedInternal = false;
this.#lastModifiedInternal = lastModified && Platform.DateUtilities.isValid(lastModified) ? lastModified : null;
this.#contentSizeInternal = contentSize;
this.#pendingContentCallbacks = [];
if (this.#requestInternal && !this.#requestInternal.finished) {
this.#requestInternal.addEventListener(Events.FinishedLoading, this.requestFinished, this);
}
}
lastModified(): Date|null {
if (this.#lastModifiedInternal || !this.#requestInternal) {
return this.#lastModifiedInternal;
}
const lastModifiedHeader = this.#requestInternal.responseLastModified();
const date = lastModifiedHeader ? new Date(lastModifiedHeader) : null;
this.#lastModifiedInternal = date && Platform.DateUtilities.isValid(date) ? date : null;
return this.#lastModifiedInternal;
}
contentSize(): number|null {
if (typeof this.#contentSizeInternal === 'number' || !this.#requestInternal) {
return this.#contentSizeInternal;
}
return this.#requestInternal.resourceSize;
}
get request(): NetworkRequest|null {
return this.#requestInternal;
}
get url(): Platform.DevToolsPath.UrlString {
return this.#urlInternal;
}
set url(x: Platform.DevToolsPath.UrlString) {
this.#urlInternal = x;
this.#parsedURLInternal = new Common.ParsedURL.ParsedURL(x);
}
get parsedURL(): Common.ParsedURL.ParsedURL|undefined {
return this.#parsedURLInternal;
}
get documentURL(): Platform.DevToolsPath.UrlString {
return this.#documentURLInternal;
}
get frameId(): Protocol.Page.FrameId|null {
return this.#frameIdInternal;
}
get loaderId(): Protocol.Network.LoaderId|null {
return this.#loaderIdInternal;
}
get displayName(): string {
return this.#parsedURLInternal ? this.#parsedURLInternal.displayName : '';
}
resourceType(): Common.ResourceType.ResourceType {
return this.#requestInternal ? this.#requestInternal.resourceType() : this.#type;
}
get mimeType(): string {
return this.#requestInternal ? this.#requestInternal.mimeType : this.#mimeTypeInternal;
}
get content(): string|null {
return this.#contentInternal;
}
get isGenerated(): boolean {
return this.#isGeneratedInternal;
}
set isGenerated(val: boolean) {
this.#isGeneratedInternal = val;
}
contentURL(): Platform.DevToolsPath.UrlString {
return this.#urlInternal;
}
contentType(): Common.ResourceType.ResourceType {
if (this.resourceType() === Common.ResourceType.resourceTypes.Document &&
this.mimeType.indexOf('javascript') !== -1) {
return Common.ResourceType.resourceTypes.Script;
}
return this.resourceType();
}
async requestContent(): Promise<TextUtils.ContentProvider.DeferredContent> {
if (typeof this.#contentInternal !== 'undefined') {
return {
content: (this.#contentInternal as string),
isEncoded: this.#contentEncodedInternal,
};
}
return new Promise(resolve => {
this.#pendingContentCallbacks.push((resolve as (arg0: Object|null) => void));
if (!this.#requestInternal || this.#requestInternal.finished) {
void this.innerRequestContent();
}
});
}
canonicalMimeType(): string {
return this.contentType().canonicalMimeType() || this.mimeType;
}
async searchInContent(query: string, caseSensitive: boolean, isRegex: boolean):
Promise<TextUtils.ContentProvider.SearchMatch[]> {
if (!this.frameId) {
return [];
}
if (this.request) {
return this.request.searchInContent(query, caseSensitive, isRegex);
}
const result = await this.#resourceTreeModel.target().pageAgent().invoke_searchInResource(
{frameId: this.frameId, url: this.url, query, caseSensitive, isRegex});
return result.result || [];
}
async populateImageSource(image: HTMLImageElement): Promise<void> {
const {content} = await this.requestContent();
const encoded = this.#contentEncodedInternal;
image.src =
TextUtils.ContentProvider.contentAsDataURL(content, this.#mimeTypeInternal, encoded) || this.#urlInternal;
}
private requestFinished(): void {
if (this.#requestInternal) {
this.#requestInternal.removeEventListener(Events.FinishedLoading, this.requestFinished, this);
}
if (this.#pendingContentCallbacks.length) {
void this.innerRequestContent();
}
}
private async innerRequestContent(): Promise<void> {
if (this.#contentRequested) {
return;
}
this.#contentRequested = true;
let loadResult: {
content: string,
isEncoded: boolean,
}|{
content: null,
error: string,
isEncoded: false,
}|{
content: string,
isEncoded: boolean,
}|null = null;
if (this.request) {
const contentData = await this.request.contentData();
if (!contentData.error) {
this.#contentInternal = contentData.content;
this.#contentEncodedInternal = contentData.encoded;
loadResult = {content: (contentData.content as string), isEncoded: contentData.encoded};
}
}
if (!loadResult) {
const response = await this.#resourceTreeModel.target().pageAgent().invoke_getResourceContent(
{frameId: this.frameId as Protocol.Page.FrameId, url: this.url});
const protocolError = response.getError();
if (protocolError) {
this.#contentInternal = null;
loadResult = {content: null, error: protocolError, isEncoded: false};
} else {
this.#contentInternal = response.content;
loadResult = {content: response.content, isEncoded: response.base64Encoded};
}
this.#contentEncodedInternal = response.base64Encoded;
}
if (this.#contentInternal === null) {
this.#contentEncodedInternal = false;
}
for (const callback of this.#pendingContentCallbacks.splice(0)) {
callback(loadResult);
}
this.#contentRequested = undefined;
}
hasTextContent(): boolean {
if (this.#type.isTextType()) {
return true;
}
if (this.#type === Common.ResourceType.resourceTypes.Other) {
return Boolean(this.#contentInternal) && !this.#contentEncodedInternal;
}
return false;
}
frame(): ResourceTreeFrame|null {
return this.#frameIdInternal ? this.#resourceTreeModel.frameForId(this.#frameIdInternal) : null;
}
statusCode(): number {
return this.#requestInternal ? this.#requestInternal.statusCode : 0;
}
}