tor-browser

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

history.js (6900B)


      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 "use strict";
      5 
      6 const {
      7  APPEND_TO_HISTORY,
      8  CLEAR_HISTORY,
      9  EVALUATE_EXPRESSION,
     10  HISTORY_LOADED,
     11  UPDATE_HISTORY_POSITION,
     12  HISTORY_BACK,
     13  HISTORY_FORWARD,
     14  REVERSE_SEARCH_INPUT_TOGGLE,
     15  REVERSE_SEARCH_INPUT_CHANGE,
     16  REVERSE_SEARCH_BACK,
     17  REVERSE_SEARCH_NEXT,
     18  SET_TERMINAL_INPUT,
     19  SET_TERMINAL_EAGER_RESULT,
     20 } = require("resource://devtools/client/webconsole/constants.js");
     21 
     22 /**
     23 * Create default initial state for this reducer.
     24 */
     25 function getInitialState() {
     26  return {
     27    // Array with history entries
     28    entries: [],
     29 
     30    // Holds position (index) in history entries that the user is
     31    // currently viewing. This is reset to this.entries.length when
     32    // APPEND_TO_HISTORY action is fired.
     33    position: undefined,
     34 
     35    // Backups the original user value (if any) that can be set in
     36    // the input field. It might be used again if the user doesn't
     37    // pick up anything from the history and wants to return all
     38    // the way back to see the original input text.
     39    originalUserValue: null,
     40 
     41    reverseSearchEnabled: false,
     42    currentReverseSearchResults: null,
     43    currentReverseSearchResultsPosition: null,
     44 
     45    terminalInput: null,
     46    terminalEagerResult: null,
     47  };
     48 }
     49 
     50 function history(state = getInitialState(), action, prefsState) {
     51  switch (action.type) {
     52    case APPEND_TO_HISTORY:
     53    case EVALUATE_EXPRESSION:
     54      return appendToHistory(state, prefsState, action.expression);
     55    case CLEAR_HISTORY:
     56      return clearHistory(state);
     57    case HISTORY_LOADED:
     58      return historyLoaded(state, action.entries);
     59    case UPDATE_HISTORY_POSITION:
     60      return updateHistoryPosition(state, action.direction, action.expression);
     61    case REVERSE_SEARCH_INPUT_TOGGLE:
     62      return reverseSearchInputToggle(state, action);
     63    case REVERSE_SEARCH_INPUT_CHANGE:
     64      return reverseSearchInputChange(state, action.value);
     65    case REVERSE_SEARCH_BACK:
     66      return reverseSearchBack(state);
     67    case REVERSE_SEARCH_NEXT:
     68      return reverseSearchNext(state);
     69    case SET_TERMINAL_INPUT:
     70      return setTerminalInput(state, action.expression);
     71    case SET_TERMINAL_EAGER_RESULT:
     72      return setTerminalEagerResult(state, action.result);
     73  }
     74  return state;
     75 }
     76 
     77 function appendToHistory(state, prefsState, expression) {
     78  // Clone state
     79  state = { ...state };
     80  state.entries = [...state.entries];
     81 
     82  // Append new expression only if it isn't the same as
     83  // the one recently added.
     84  if (expression.trim() != state.entries[state.entries.length - 1]) {
     85    state.entries.push(expression);
     86  }
     87 
     88  // Remove entries if the limit is reached
     89  if (state.entries.length > prefsState.historyCount) {
     90    state.entries.splice(0, state.entries.length - prefsState.historyCount);
     91  }
     92 
     93  state.position = state.entries.length;
     94  state.originalUserValue = null;
     95 
     96  return state;
     97 }
     98 
     99 function clearHistory() {
    100  return getInitialState();
    101 }
    102 
    103 /**
    104 * Handling HISTORY_LOADED action that is fired when history
    105 * entries created in previous Firefox session are loaded
    106 * from async-storage.
    107 *
    108 * Loaded entries are appended before the ones that were
    109 * added to the state in this session.
    110 */
    111 function historyLoaded(state, entries) {
    112  const newEntries = [...entries, ...state.entries];
    113  return {
    114    ...state,
    115    entries: newEntries,
    116    // Default position is at the end of the list
    117    // (at the latest inserted item).
    118    position: newEntries.length,
    119    originalUserValue: null,
    120  };
    121 }
    122 
    123 function updateHistoryPosition(state, direction, expression) {
    124  // Handle UP arrow key => HISTORY_BACK
    125  // Handle DOWN arrow key => HISTORY_FORWARD
    126  if (direction == HISTORY_BACK) {
    127    if (state.position <= 0) {
    128      return state;
    129    }
    130 
    131    // Clone state
    132    state = { ...state };
    133 
    134    // Store the current input value when the user starts
    135    // browsing through the history.
    136    if (state.position == state.entries.length) {
    137      state.originalUserValue = expression || "";
    138    }
    139 
    140    state.position--;
    141  } else if (direction == HISTORY_FORWARD) {
    142    if (state.position >= state.entries.length) {
    143      return state;
    144    }
    145 
    146    state = {
    147      ...state,
    148      position: state.position + 1,
    149    };
    150  }
    151 
    152  return state;
    153 }
    154 
    155 function reverseSearchInputToggle(state, action) {
    156  const { initialValue = "" } = action;
    157 
    158  // We're going to close the reverse search, let's clean the state
    159  if (state.reverseSearchEnabled) {
    160    return {
    161      ...state,
    162      reverseSearchEnabled: false,
    163      position: undefined,
    164      currentReverseSearchResults: null,
    165      currentReverseSearchResultsPosition: null,
    166    };
    167  }
    168 
    169  // If we're enabling the reverse search, we treat it as a reverse search input change,
    170  // since we can have an initial value.
    171  return reverseSearchInputChange(state, initialValue);
    172 }
    173 
    174 function reverseSearchInputChange(state, searchString) {
    175  if (searchString === "") {
    176    return {
    177      ...state,
    178      position: undefined,
    179      currentReverseSearchResults: null,
    180      currentReverseSearchResultsPosition: null,
    181    };
    182  }
    183 
    184  searchString = searchString.toLocaleLowerCase();
    185  const matchingEntries = state.entries.filter(entry =>
    186    entry.toLocaleLowerCase().includes(searchString)
    187  );
    188  // We only return unique entries, but we want to keep the latest entry in the array if
    189  // it's duplicated (e.g. if we have [1,2,1], we want to get [2,1], not [1,2]).
    190  // To do that, we need to reverse the matching entries array, provide it to a Set,
    191  // transform it back to an array and reverse it again.
    192  const uniqueEntries = new Set(matchingEntries.reverse());
    193  const currentReverseSearchResults = Array.from(
    194    new Set(uniqueEntries)
    195  ).reverse();
    196 
    197  return {
    198    ...state,
    199    position: undefined,
    200    currentReverseSearchResults,
    201    currentReverseSearchResultsPosition: currentReverseSearchResults.length - 1,
    202  };
    203 }
    204 
    205 function reverseSearchBack(state) {
    206  let nextPosition = state.currentReverseSearchResultsPosition - 1;
    207  if (nextPosition < 0) {
    208    nextPosition = state.currentReverseSearchResults.length - 1;
    209  }
    210 
    211  return {
    212    ...state,
    213    currentReverseSearchResultsPosition: nextPosition,
    214  };
    215 }
    216 
    217 function reverseSearchNext(state) {
    218  let previousPosition = state.currentReverseSearchResultsPosition + 1;
    219  if (previousPosition >= state.currentReverseSearchResults.length) {
    220    previousPosition = 0;
    221  }
    222 
    223  return {
    224    ...state,
    225    currentReverseSearchResultsPosition: previousPosition,
    226  };
    227 }
    228 
    229 function setTerminalInput(state, expression) {
    230  return {
    231    ...state,
    232    terminalInput: expression,
    233    terminalEagerResult: !expression ? null : state.terminalEagerResult,
    234  };
    235 }
    236 
    237 function setTerminalEagerResult(state, result) {
    238  return {
    239    ...state,
    240    terminalEagerResult: result,
    241  };
    242 }
    243 
    244 exports.history = history;