mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-06 20:11:38 +08:00
Bug: 1253323 Change-Id: Idb5150deabffe26a8c21987a55206729b3781655 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/3423898 Reviewed-by: Eric Leese <leese@chromium.org> Reviewed-by: Simon Zünd <szuend@chromium.org> Commit-Queue: Kateryna Prokopenko <kprokopenko@chromium.org>
288 lines
10 KiB
TypeScript
288 lines
10 KiB
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 type {NetworkRequest} from './NetworkRequest.js';
|
|
import {Events} from './NetworkRequest.js';
|
|
import type {ResourceTreeFrame, 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 contentEncoded(): Promise<boolean> {
|
|
await this.requestContent();
|
|
return this.#contentEncodedInternal;
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|