tor-browser

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

loadSourceText.js (7375B)


      1 /* This Source Code Form is subject to the terms of the Mozilla Public
      2 * License, v. 2.0. If a copy of the MPL was not distributed with this
      3 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
      4 
      5 const {
      6  PROMISE,
      7 } = require("resource://devtools/client/shared/redux/middleware/promise.js");
      8 import {
      9  getSourceTextContentForSource,
     10  getSettledSourceTextContent,
     11  getSourcesEpoch,
     12  getBreakpointsForSource,
     13  getSourceActorsForSource,
     14  getFirstSourceActorForGeneratedSource,
     15 } from "../../selectors/index";
     16 import { addBreakpoint } from "../breakpoints/index";
     17 
     18 import { prettyPrintSourceTextContent } from "./prettyPrint";
     19 import { isFulfilled, fulfilled } from "../../utils/async-value";
     20 
     21 import { createLocation } from "../../utils/location";
     22 import { memoizeableAction } from "../../utils/memoizableAction";
     23 
     24 import { getEditor } from "../../utils/editor/index";
     25 
     26 async function loadGeneratedSource(sourceActor, { client }) {
     27  // If no source actor can be found then the text for the
     28  // source cannot be loaded.
     29  if (!sourceActor) {
     30    throw new Error("Source actor is null or not defined");
     31  }
     32 
     33  let response;
     34  try {
     35    response = await client.sourceContents(sourceActor);
     36  } catch (e) {
     37    throw new Error(`sourceContents failed: ${e}`);
     38  }
     39 
     40  return {
     41    text: response.source,
     42    contentType: response.contentType || "text/javascript",
     43  };
     44 }
     45 
     46 async function loadOriginalSource(
     47  source,
     48  { getState, sourceMapLoader, prettyPrintWorker }
     49 ) {
     50  if (source.isPrettyPrinted) {
     51    const { generatedSource } = source;
     52    if (!generatedSource) {
     53      throw new Error("Unable to find minified original.");
     54    }
     55 
     56    const content = getSettledSourceTextContent(
     57      getState(),
     58      createLocation({
     59        source: generatedSource,
     60      })
     61    );
     62 
     63    return prettyPrintSourceTextContent(
     64      sourceMapLoader,
     65      prettyPrintWorker,
     66      generatedSource,
     67      content,
     68      getSourceActorsForSource(getState(), generatedSource.id)
     69    );
     70  }
     71 
     72  const result = await sourceMapLoader.getOriginalSourceText(source.id);
     73  if (!result) {
     74    // The way we currently try to load and select a pending
     75    // selected location, it is possible that we will try to fetch the
     76    // original source text right after the source map has been cleared
     77    // after a navigation event.
     78    throw new Error("Original source text unavailable");
     79  }
     80  return result;
     81 }
     82 
     83 async function loadGeneratedSourceTextPromise(sourceActor, thunkArgs) {
     84  const { dispatch, getState } = thunkArgs;
     85  const epoch = getSourcesEpoch(getState());
     86 
     87  await dispatch({
     88    type: "LOAD_GENERATED_SOURCE_TEXT",
     89    sourceActor,
     90    epoch,
     91    [PROMISE]: loadGeneratedSource(sourceActor, thunkArgs),
     92  });
     93 
     94  await onSourceTextContentAvailable(
     95    sourceActor.sourceObject,
     96    sourceActor,
     97    thunkArgs
     98  );
     99 }
    100 
    101 async function loadOriginalSourceTextPromise(source, thunkArgs) {
    102  const { dispatch, getState } = thunkArgs;
    103  const epoch = getSourcesEpoch(getState());
    104  await dispatch({
    105    type: "LOAD_ORIGINAL_SOURCE_TEXT",
    106    source,
    107    epoch,
    108    [PROMISE]: loadOriginalSource(source, thunkArgs),
    109  });
    110 
    111  await onSourceTextContentAvailable(source, null, thunkArgs);
    112 }
    113 
    114 /**
    115 * Function called everytime a new original or generated source gets its text content
    116 * fetched from the server and registered in the reducer.
    117 *
    118 * @param {object} source
    119 * @param {object} sourceActor (optional)
    120 *        If this is a generated source, we expect a precise source actor.
    121 * @param {object} thunkArgs
    122 */
    123 async function onSourceTextContentAvailable(
    124  source,
    125  sourceActor,
    126  { dispatch, getState, parserWorker }
    127 ) {
    128  const location = createLocation({
    129    source,
    130    sourceActor,
    131  });
    132  const content = getSettledSourceTextContent(getState(), location);
    133  if (!content) {
    134    return;
    135  }
    136 
    137  const contentValue = isFulfilled(content)
    138    ? content.value
    139    : { type: "text", value: "", contentType: undefined };
    140 
    141  // Lezer parser uses the sources to map the original frame names
    142  const editor = getEditor();
    143  if (!editor.isWasm) {
    144    editor.addSource(source.id, contentValue.value);
    145    // The babel parser needs the sources to parse scopes
    146    parserWorker.setSource(source.id, contentValue);
    147  }
    148 
    149  // Update the text in any breakpoints for this source by re-adding them.
    150  const breakpoints = getBreakpointsForSource(getState(), source);
    151  for (const breakpoint of breakpoints) {
    152    await dispatch(
    153      addBreakpoint(
    154        breakpoint.location,
    155        breakpoint.options,
    156        breakpoint.disabled
    157      )
    158    );
    159  }
    160 }
    161 
    162 /**
    163 * Loads the source text for the generated source based of the source actor
    164 *
    165 * @param {object} sourceActor
    166 *                 There can be more than one source actor per source
    167 *                 so the source actor needs to be specified. This is
    168 *                 required for generated sources but will be null for
    169 *                 original/pretty printed sources.
    170 */
    171 export const loadGeneratedSourceText = memoizeableAction(
    172  "loadGeneratedSourceText",
    173  {
    174    getValue: (sourceActor, { getState }) => {
    175      if (!sourceActor) {
    176        return null;
    177      }
    178 
    179      const sourceTextContent = getSourceTextContentForSource(
    180        getState(),
    181        sourceActor.sourceObject,
    182        sourceActor
    183      );
    184 
    185      if (!sourceTextContent || sourceTextContent.state === "pending") {
    186        return sourceTextContent;
    187      }
    188 
    189      // This currently swallows source-load-failure since we return fulfilled
    190      // here when content.state === "rejected". In an ideal world we should
    191      // propagate that error upward.
    192      return fulfilled(sourceTextContent);
    193    },
    194    createKey: (sourceActor, { getState }) => {
    195      const epoch = getSourcesEpoch(getState());
    196      return `${epoch}:${sourceActor.actor}`;
    197    },
    198    action: (sourceActor, thunkArgs) =>
    199      loadGeneratedSourceTextPromise(sourceActor, thunkArgs),
    200  }
    201 );
    202 
    203 /**
    204 * Loads the source text for an original source and source actor
    205 *
    206 * @param {object} source
    207 *                 The original source to load the source text
    208 */
    209 export const loadOriginalSourceText = memoizeableAction(
    210  "loadOriginalSourceText",
    211  {
    212    getValue: (source, { getState }) => {
    213      if (!source) {
    214        return null;
    215      }
    216 
    217      const sourceTextContent = getSourceTextContentForSource(
    218        getState(),
    219        source
    220      );
    221      if (!sourceTextContent || sourceTextContent.state === "pending") {
    222        return sourceTextContent;
    223      }
    224 
    225      // This currently swallows source-load-failure since we return fulfilled
    226      // here when content.state === "rejected". In an ideal world we should
    227      // propagate that error upward.
    228      return fulfilled(sourceTextContent);
    229    },
    230    createKey: (source, { getState }) => {
    231      const epoch = getSourcesEpoch(getState());
    232      return `${epoch}:${source.id}`;
    233    },
    234    action: (source, thunkArgs) =>
    235      loadOriginalSourceTextPromise(source, thunkArgs),
    236  }
    237 );
    238 
    239 export function loadSourceText(source, sourceActor) {
    240  return async ({ dispatch, getState }) => {
    241    if (!source) {
    242      return null;
    243    }
    244    if (source.isOriginal) {
    245      return dispatch(loadOriginalSourceText(source));
    246    }
    247    if (!sourceActor) {
    248      sourceActor = getFirstSourceActorForGeneratedSource(
    249        getState(),
    250        source.id
    251      );
    252    }
    253    return dispatch(loadGeneratedSourceText(sourceActor));
    254  };
    255 }