tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

utils.ts (6438B)


      1 /**
      2 * @license
      3 * Copyright 2017 Google Inc.
      4 * SPDX-License-Identifier: Apache-2.0
      5 */
      6 
      7 import type {Protocol} from 'devtools-protocol';
      8 
      9 import {PuppeteerURL, evaluationString} from '../common/util.js';
     10 import {assert} from '../util/assert.js';
     11 
     12 /**
     13 * @internal
     14 */
     15 export function createEvaluationError(
     16  details: Protocol.Runtime.ExceptionDetails,
     17 ): unknown {
     18  let name: string;
     19  let message: string;
     20  if (!details.exception) {
     21    name = 'Error';
     22    message = details.text;
     23  } else if (
     24    (details.exception.type !== 'object' ||
     25      details.exception.subtype !== 'error') &&
     26    !details.exception.objectId
     27  ) {
     28    return valueFromRemoteObject(details.exception);
     29  } else {
     30    const detail = getErrorDetails(details);
     31    name = detail.name;
     32    message = detail.message;
     33  }
     34  const messageHeight = message.split('\n').length;
     35  const error = new Error(message);
     36  error.name = name;
     37  const stackLines = error.stack!.split('\n');
     38  const messageLines = stackLines.splice(0, messageHeight);
     39 
     40  // The first line is this function which we ignore.
     41  stackLines.shift();
     42  if (details.stackTrace && stackLines.length < Error.stackTraceLimit) {
     43    for (const frame of details.stackTrace.callFrames.reverse()) {
     44      if (
     45        PuppeteerURL.isPuppeteerURL(frame.url) &&
     46        frame.url !== PuppeteerURL.INTERNAL_URL
     47      ) {
     48        const url = PuppeteerURL.parse(frame.url);
     49        stackLines.unshift(
     50          `    at ${frame.functionName || url.functionName} (${
     51            url.functionName
     52          } at ${url.siteString}, <anonymous>:${frame.lineNumber}:${
     53            frame.columnNumber
     54          })`,
     55        );
     56      } else {
     57        stackLines.push(
     58          `    at ${frame.functionName || '<anonymous>'} (${frame.url}:${
     59            frame.lineNumber
     60          }:${frame.columnNumber})`,
     61        );
     62      }
     63      if (stackLines.length >= Error.stackTraceLimit) {
     64        break;
     65      }
     66    }
     67  }
     68 
     69  error.stack = [...messageLines, ...stackLines].join('\n');
     70  return error;
     71 }
     72 
     73 const getErrorDetails = (details: Protocol.Runtime.ExceptionDetails) => {
     74  let name = '';
     75  let message: string;
     76  const lines = details.exception?.description?.split('\n    at ') ?? [];
     77  const size = Math.min(
     78    details.stackTrace?.callFrames.length ?? 0,
     79    lines.length - 1,
     80  );
     81  lines.splice(-size, size);
     82  if (details.exception?.className) {
     83    name = details.exception.className;
     84  }
     85  message = lines.join('\n');
     86  if (name && message.startsWith(`${name}: `)) {
     87    message = message.slice(name.length + 2);
     88  }
     89  return {message, name};
     90 };
     91 
     92 /**
     93 * @internal
     94 */
     95 export function createClientError(
     96  details: Protocol.Runtime.ExceptionDetails,
     97 ): Error {
     98  let name: string;
     99  let message: string;
    100  if (!details.exception) {
    101    name = 'Error';
    102    message = details.text;
    103  } else if (
    104    (details.exception.type !== 'object' ||
    105      details.exception.subtype !== 'error') &&
    106    !details.exception.objectId
    107  ) {
    108    return valueFromRemoteObject(details.exception);
    109  } else {
    110    const detail = getErrorDetails(details);
    111    name = detail.name;
    112    message = detail.message;
    113  }
    114  const error = new Error(message);
    115  error.name = name;
    116 
    117  const messageHeight = error.message.split('\n').length;
    118  const messageLines = error.stack!.split('\n').splice(0, messageHeight);
    119 
    120  const stackLines = [];
    121  if (details.stackTrace) {
    122    for (const frame of details.stackTrace.callFrames) {
    123      // Note we need to add `1` because the values are 0-indexed.
    124      stackLines.push(
    125        `    at ${frame.functionName || '<anonymous>'} (${frame.url}:${
    126          frame.lineNumber + 1
    127        }:${frame.columnNumber + 1})`,
    128      );
    129      if (stackLines.length >= Error.stackTraceLimit) {
    130        break;
    131      }
    132    }
    133  }
    134 
    135  error.stack = [...messageLines, ...stackLines].join('\n');
    136  return error;
    137 }
    138 
    139 /**
    140 * @internal
    141 */
    142 export function valueFromRemoteObject(
    143  remoteObject: Protocol.Runtime.RemoteObject,
    144 ): any {
    145  assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
    146  if (remoteObject.unserializableValue) {
    147    if (remoteObject.type === 'bigint') {
    148      return BigInt(remoteObject.unserializableValue.replace('n', ''));
    149    }
    150    switch (remoteObject.unserializableValue) {
    151      case '-0':
    152        return -0;
    153      case 'NaN':
    154        return NaN;
    155      case 'Infinity':
    156        return Infinity;
    157      case '-Infinity':
    158        return -Infinity;
    159      default:
    160        throw new Error(
    161          'Unsupported unserializable value: ' +
    162            remoteObject.unserializableValue,
    163        );
    164    }
    165  }
    166  return remoteObject.value;
    167 }
    168 
    169 /**
    170 * @internal
    171 */
    172 export function addPageBinding(
    173  type: string,
    174  name: string,
    175  prefix: string,
    176 ): void {
    177  // Depending on the frame loading state either Runtime.evaluate or
    178  // Page.addScriptToEvaluateOnNewDocument might succeed. Let's check that we
    179  // don't re-wrap Puppeteer's binding.
    180  // @ts-expect-error: In a different context.
    181  if (globalThis[name]) {
    182    return;
    183  }
    184 
    185  // We replace the CDP binding with a Puppeteer binding.
    186  Object.assign(globalThis, {
    187    [name](...args: unknown[]): Promise<unknown> {
    188      // This is the Puppeteer binding.
    189      // @ts-expect-error: In a different context.
    190      const callPuppeteer = globalThis[name];
    191      callPuppeteer.args ??= new Map();
    192      callPuppeteer.callbacks ??= new Map();
    193 
    194      const seq = (callPuppeteer.lastSeq ?? 0) + 1;
    195      callPuppeteer.lastSeq = seq;
    196      callPuppeteer.args.set(seq, args);
    197 
    198      // @ts-expect-error: In a different context.
    199      // Needs to be the same as CDP_BINDING_PREFIX.
    200      globalThis[prefix + name](
    201        JSON.stringify({
    202          type,
    203          name,
    204          seq,
    205          args,
    206          isTrivial: !args.some(value => {
    207            return value instanceof Node;
    208          }),
    209        }),
    210      );
    211 
    212      return new Promise((resolve, reject) => {
    213        callPuppeteer.callbacks.set(seq, {
    214          resolve(value: unknown) {
    215            callPuppeteer.args.delete(seq);
    216            resolve(value);
    217          },
    218          reject(value?: unknown) {
    219            callPuppeteer.args.delete(seq);
    220            reject(value);
    221          },
    222        });
    223      });
    224    },
    225  });
    226 }
    227 
    228 /**
    229 * @internal
    230 */
    231 export const CDP_BINDING_PREFIX = 'puppeteer_';
    232 
    233 /**
    234 * @internal
    235 */
    236 export function pageBindingInitString(type: string, name: string): string {
    237  return evaluationString(addPageBinding, type, name, CDP_BINDING_PREFIX);
    238 }