tor-browser

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

sources.js (12342B)


      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 import { createSelector } from "devtools/client/shared/vendor/reselect";
      6 
      7 import { getPrettySourceURL, isJavaScript } from "../utils/source";
      8 
      9 import { findPosition } from "../utils/breakpoint/breakpointPositions";
     10 import { isFulfilled } from "../utils/async-value";
     11 
     12 import { prefs } from "../utils/prefs";
     13 import { UNDEFINED_LOCATION, NO_LOCATION } from "../reducers/sources";
     14 
     15 import {
     16  hasSourceActor,
     17  getSourceActor,
     18  getBreakableLinesForSourceActors,
     19  isSourceActorWithSourceMap,
     20 } from "./source-actors";
     21 import {
     22  getSourceTextContentForLocation,
     23  getSourceTextContentForSource,
     24 } from "./sources-content";
     25 
     26 export function hasSource(state, id) {
     27  return state.sources.mutableSources.has(id);
     28 }
     29 
     30 export function getSource(state, id) {
     31  return state.sources.mutableSources.get(id);
     32 }
     33 
     34 export function getSourceFromId(state, id) {
     35  const source = getSource(state, id);
     36  if (!source) {
     37    console.warn(`source ${id} does not exist`);
     38    dump(`>> ${new Error().stack}\n`);
     39  }
     40  return source;
     41 }
     42 
     43 export function getSourceByActorId(state, actorId) {
     44  if (!hasSourceActor(state, actorId)) {
     45    return null;
     46  }
     47 
     48  return getSource(state, getSourceActor(state, actorId).source);
     49 }
     50 
     51 function getSourcesByURL(state, url) {
     52  return state.sources.mutableSourcesPerUrl.get(url) || [];
     53 }
     54 
     55 export function getSourceByURL(state, url) {
     56  const foundSources = getSourcesByURL(state, url);
     57  return foundSources[0];
     58 }
     59 
     60 // This is used by tabs selectors
     61 export function getSpecificSourceByURL(state, url, isOriginal) {
     62  const foundSources = getSourcesByURL(state, url);
     63  return foundSources.find(source => source.isOriginal == isOriginal);
     64 }
     65 
     66 function getOriginalSourceByURL(state, url) {
     67  return getSpecificSourceByURL(state, url, true);
     68 }
     69 
     70 export function getGeneratedSourceByURL(state, url) {
     71  return getSpecificSourceByURL(state, url, false);
     72 }
     73 
     74 export function getPendingSelectedLocation(state) {
     75  return state.sources.pendingSelectedLocation;
     76 }
     77 
     78 export function getPrettySource(state, id) {
     79  if (!id) {
     80    return null;
     81  }
     82 
     83  const source = getSource(state, id);
     84  if (!source) {
     85    return null;
     86  }
     87 
     88  return getOriginalSourceByURL(state, getPrettySourceURL(source.url));
     89 }
     90 
     91 // This is only used by Project Search and tests.
     92 export function getSourceList(state) {
     93  return [...state.sources.mutableSources.values()];
     94 }
     95 
     96 // This is only used by tests and create.js
     97 export function getSourceCount(state) {
     98  return state.sources.mutableSources.size;
     99 }
    100 
    101 export function getSelectedLocation(state) {
    102  return state.sources.selectedLocation;
    103 }
    104 
    105 /**
    106 * Return the "mapped" location for the currently selected location:
    107 * - When selecting a location in an original source, returns
    108 *   the related location in the bundle source.
    109 *
    110 * - When selecting a location in a bundle source, returns
    111 *   the related location in the original source. This may return undefined
    112 *   while we are still computing this information. (we need to query the asynchronous SourceMap service)
    113 *
    114 * - Otherwise, when selecting a location in a source unrelated to source map
    115 *   or a pretty printed source, returns null.
    116 */
    117 export function getSelectedMappedSource(state) {
    118  const selectedLocation = getSelectedLocation(state);
    119  if (!selectedLocation) {
    120    return null;
    121  }
    122 
    123  // Don't map pretty printed to its related compressed source
    124  if (selectedLocation.source.isPrettyPrinted) {
    125    return null;
    126  }
    127 
    128  // If we are on a bundle with a functional source-map,
    129  // the `selectLocation` action should compute the `selectedOriginalLocation` field.
    130  if (
    131    !selectedLocation.source.isOriginal &&
    132    isSourceActorWithSourceMap(state, selectedLocation.sourceActor.id)
    133  ) {
    134    const { selectedOriginalLocation } = state.sources;
    135    // Return undefined if we are still loading the source map.
    136    // `selectedOriginalLocation` will be set to undefined instead of null
    137    if (
    138      selectedOriginalLocation &&
    139      selectedOriginalLocation != UNDEFINED_LOCATION &&
    140      selectedOriginalLocation != NO_LOCATION
    141    ) {
    142      return selectedOriginalLocation.source;
    143    }
    144    return null;
    145  }
    146 
    147  // For non original source, which don't have selectedOriginalLocation provided,
    148  // don't try to map to anything.
    149  if (!selectedLocation.source.isOriginal) {
    150    return null;
    151  }
    152 
    153  // Otherwise, for original source, simply map to their related generated source
    154  return selectedLocation.source.generatedSource;
    155 }
    156 
    157 /**
    158 * Helps knowing if we are still computing the mapped location for the currently selected source.
    159 */
    160 export function isSelectedMappedSourceLoading(state) {
    161  const { selectedOriginalLocation } = state.sources;
    162  // This `selectedOriginalLocation` attribute is set to UNDEFINED_LOCATION when selecting a new source attribute
    163  // and later on, when the source map is processed, it will switch to either a valid location object, or NO_LOCATION if no valid one if found.
    164  return selectedOriginalLocation === UNDEFINED_LOCATION;
    165 }
    166 
    167 export const getSelectedSource = createSelector(
    168  getSelectedLocation,
    169  selectedLocation => {
    170    if (!selectedLocation) {
    171      return undefined;
    172    }
    173 
    174    return selectedLocation.source;
    175  }
    176 );
    177 
    178 // This is used by tests and pause reducers
    179 export function getSelectedSourceId(state) {
    180  const source = getSelectedSource(state);
    181  return source?.id;
    182 }
    183 
    184 export function getShouldSelectOriginalLocation(state) {
    185  return state.sources.shouldSelectOriginalLocation;
    186 }
    187 
    188 export function getShouldHighlightSelectedLocation(state) {
    189  return state.sources.shouldHighlightSelectedLocation;
    190 }
    191 
    192 export function getShouldScrollToSelectedLocation(state) {
    193  return state.sources.shouldScrollToSelectedLocation;
    194 }
    195 
    196 /**
    197 * Gets the first source actor for the source and/or thread
    198 * provided.
    199 *
    200 * @param {object} state
    201 * @param {string} sourceId
    202 *         The source used
    203 * @param {string} [threadId]
    204 *         The thread to check, this is optional.
    205 * @param {object} sourceActor
    206 */
    207 export function getFirstSourceActorForGeneratedSource(
    208  state,
    209  sourceId,
    210  threadId
    211 ) {
    212  let source = getSource(state, sourceId);
    213  // The source may have been removed if we are being called by async code
    214  if (!source) {
    215    return null;
    216  }
    217  if (source.isOriginal) {
    218    source = source.generatedSource;
    219  }
    220  const actors = getSourceActorsForSource(state, source.id);
    221  if (threadId) {
    222    return actors.find(actorInfo => actorInfo.thread == threadId) || null;
    223  }
    224  return actors[0] || null;
    225 }
    226 
    227 /**
    228 * Get the source actor of the source
    229 *
    230 * @param {object} state
    231 * @param {string} id
    232 *        The source id
    233 * @return {Array<object>}
    234 *         List of source actors
    235 */
    236 export function getSourceActorsForSource(state, id) {
    237  return state.sources.mutableSourceActors.get(id) || [];
    238 }
    239 
    240 export function isSourceWithMap(state, id) {
    241  const actors = getSourceActorsForSource(state, id);
    242  return actors.some(actor => isSourceActorWithSourceMap(state, actor.id));
    243 }
    244 
    245 export function canPrettyPrintSource(state, source, sourceActor) {
    246  if (
    247    !source ||
    248    source.isPrettyPrinted ||
    249    source.isOriginal ||
    250    (prefs.clientSourceMapsEnabled && isSourceWithMap(state, source.id))
    251  ) {
    252    return false;
    253  }
    254 
    255  const content = getSourceTextContentForSource(state, source, sourceActor);
    256  const sourceContent = content && isFulfilled(content) ? content.value : null;
    257 
    258  if (
    259    !sourceContent ||
    260    (!isJavaScript(source, sourceContent) && !source.isHTML)
    261  ) {
    262    return false;
    263  }
    264 
    265  return true;
    266 }
    267 
    268 export function getPrettyPrintMessage(state, location) {
    269  const source = location.source;
    270  if (!source) {
    271    return L10N.getStr("sourceTabs.prettyPrint");
    272  }
    273 
    274  if (source.isPrettyPrinted) {
    275    return L10N.getStr("sourceTabs.removePrettyPrint");
    276  }
    277 
    278  if (source.isOriginal) {
    279    return L10N.getStr("sourceFooter.prettyPrint.isOriginalMessage");
    280  }
    281 
    282  if (prefs.clientSourceMapsEnabled && isSourceWithMap(state, source.id)) {
    283    return L10N.getStr("sourceFooter.prettyPrint.hasSourceMapMessage");
    284  }
    285 
    286  const content = getSourceTextContentForLocation(state, location);
    287 
    288  const sourceContent = content && isFulfilled(content) ? content.value : null;
    289  if (!sourceContent) {
    290    return L10N.getStr("sourceFooter.prettyPrint.noContentMessage");
    291  }
    292 
    293  if (!isJavaScript(source, sourceContent) && !source.isHTML) {
    294    return L10N.getStr("sourceFooter.prettyPrint.isNotJavascriptMessage");
    295  }
    296 
    297  return L10N.getStr("sourceTabs.prettyPrint");
    298 }
    299 
    300 export function getBreakpointPositionsForSource(state, sourceId) {
    301  return state.sources.mutableBreakpointPositions.get(sourceId);
    302 }
    303 
    304 // This is only used by one test
    305 export function hasBreakpointPositions(state, sourceId) {
    306  return !!getBreakpointPositionsForSource(state, sourceId);
    307 }
    308 
    309 export function getBreakpointPositionsForLine(state, sourceId, line) {
    310  const positions = getBreakpointPositionsForSource(state, sourceId);
    311  return positions?.[line];
    312 }
    313 
    314 export function getBreakpointPositionsForLocation(state, location) {
    315  const sourceId = location.source.id;
    316  const positions = getBreakpointPositionsForSource(state, sourceId);
    317  return findPosition(positions, location);
    318 }
    319 
    320 export function getBreakableLines(state, sourceId) {
    321  if (!sourceId) {
    322    return null;
    323  }
    324  const source = getSource(state, sourceId);
    325  if (!source) {
    326    return null;
    327  }
    328 
    329  if (source.isOriginal) {
    330    return state.sources.mutableOriginalBreakableLines.get(sourceId);
    331  }
    332 
    333  const sourceActors = getSourceActorsForSource(state, sourceId);
    334  if (!sourceActors.length) {
    335    return null;
    336  }
    337 
    338  // We pull generated file breakable lines directly from the source actors
    339  // so that breakable lines can be added as new source actors on HTML loads.
    340  return getBreakableLinesForSourceActors(state, sourceActors, source.isHTML);
    341 }
    342 
    343 export const getSelectedBreakableLines = createSelector(
    344  state => {
    345    const sourceId = getSelectedSourceId(state);
    346    if (!sourceId) {
    347      return null;
    348    }
    349    const breakableLines = getBreakableLines(state, sourceId);
    350    // Ignore the breakable lines if they are still being fetched from the server
    351    if (!breakableLines || breakableLines instanceof Promise) {
    352      return null;
    353    }
    354    return breakableLines;
    355  },
    356  breakableLines => new Set(breakableLines || [])
    357 );
    358 
    359 export function isSourceOverridden(toolboxState, source) {
    360  if (!source || !source.url) {
    361    return false;
    362  }
    363  return !!toolboxState.networkOverrides.mutableOverrides[source.url];
    364 }
    365 
    366 /**
    367 * Compute the list of source actors and source objects to be removed
    368 * when removing a given target/thread.
    369 *
    370 * @param {string} threadActorID
    371 *        The thread to be removed.
    372 * @return {object}
    373 *         An object with two arrays:
    374 *         - actors: list of source actor objects to remove
    375 *         - sources: list of source objects to remove
    376 */
    377 export function getSourcesToRemoveForThread(state, threadActorID) {
    378  const sourcesToRemove = [];
    379  const actorsToRemove = [];
    380 
    381  for (const [
    382    sourceId,
    383    actorsForSource,
    384  ] of state.sources.mutableSourceActors.entries()) {
    385    let removedActorsCount = 0;
    386    // Find all actors for the current source which belongs to the given thread actor
    387    for (const actor of actorsForSource) {
    388      if (actor.thread == threadActorID) {
    389        actorsToRemove.push(actor);
    390        removedActorsCount++;
    391      }
    392    }
    393 
    394    // If we are about to remove all source actors for the current source,
    395    // or if for some unexpected reason we have a source with no actors,
    396    // notify the caller to also remove this source.
    397    if (
    398      removedActorsCount == actorsForSource.length ||
    399      !actorsForSource.length
    400    ) {
    401      sourcesToRemove.push(state.sources.mutableSources.get(sourceId));
    402 
    403      // Also remove any original sources related to this generated source
    404      const originalSourceIds =
    405        state.sources.mutableOriginalSources.get(sourceId);
    406      if (originalSourceIds?.length > 0) {
    407        for (const originalSourceId of originalSourceIds) {
    408          sourcesToRemove.push(
    409            state.sources.mutableSources.get(originalSourceId)
    410          );
    411        }
    412      }
    413    }
    414  }
    415 
    416  return {
    417    actors: actorsToRemove,
    418    sources: sourcesToRemove,
    419  };
    420 }