summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/reducers/sources-content.js
blob: e71330dc5c252bc6d34056416d2ef663bd7f1ab1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */

/**
 * Sources content reducer.
 *
 * This store the textual content for each source.
 */

import { pending, fulfilled, rejected } from "../utils/async-value";

export function initialSourcesContentState() {
  return {
    /**
     * Text content of all the original sources.
     * This is large data, so this is only fetched on-demand for a subset of sources.
     * This state attribute is mutable in order to avoid cloning this possibly large map
     * on each new source. But selectors are never based on the map. Instead they only
     * query elements of the map.
     *
     * Map(source id => AsyncValue<String>)
     */
    mutableOriginalSourceTextContentMapBySourceId: new Map(),

    /**
     * Text content of all the generated sources.
     *
     * Map(source actor is => AsyncValue<String>)
     */
    mutableGeneratedSourceTextContentMapBySourceActorId: new Map(),

    /**
     * Incremental number that is bumped each time we navigate to a new page.
     *
     * This is used to better handle async race condition where we mix previous page data
     * with the new page. As sources are keyed by URL we may easily conflate the two page loads data.
     */
    epoch: 1,
  };
}

function update(state = initialSourcesContentState(), action) {
  switch (action.type) {
    case "LOAD_ORIGINAL_SOURCE_TEXT":
      if (!action.source) {
        throw new Error("Missing source");
      }
      return updateSourceTextContent(state, action);

    case "LOAD_GENERATED_SOURCE_TEXT":
      if (!action.sourceActor) {
        throw new Error("Missing source actor.");
      }
      return updateSourceTextContent(state, action);

    case "REMOVE_THREAD":
      return removeThread(state, action);
  }

  return state;
}

/*
 * Update a source's loaded text content.
 */
function updateSourceTextContent(state, action) {
  // If there was a navigation between the time the action was started and
  // completed, we don't want to update the store.
  if (action.epoch !== state.epoch) {
    return state;
  }

  let content;
  if (action.status === "start") {
    content = pending();
  } else if (action.status === "error") {
    content = rejected(action.error);
  } else if (typeof action.value.text === "string") {
    content = fulfilled({
      type: "text",
      value: action.value.text,
      contentType: action.value.contentType,
    });
  } else {
    content = fulfilled({
      type: "wasm",
      value: action.value.text,
    });
  }

  if (action.source && action.sourceActor) {
    throw new Error(
      "Both the source and the source actor should not exist at the same time"
    );
  }

  if (action.source) {
    state.mutableOriginalSourceTextContentMapBySourceId.set(
      action.source.id,
      content
    );
  }

  if (action.sourceActor) {
    state.mutableGeneratedSourceTextContentMapBySourceActorId.set(
      action.sourceActor.id,
      content
    );
  }

  return {
    ...state,
  };
}

function removeThread(state, action) {
  const originalSizeBefore =
    state.mutableOriginalSourceTextContentMapBySourceId.size;
  for (const source of action.sources) {
    state.mutableOriginalSourceTextContentMapBySourceId.delete(source.id);
  }
  const generatedSizeBefore =
    state.mutableGeneratedSourceTextContentMapBySourceActorId.size;
  for (const actor of action.actors) {
    state.mutableGeneratedSourceTextContentMapBySourceActorId.delete(actor.id);
  }
  if (
    originalSizeBefore !=
      state.mutableOriginalSourceTextContentMapBySourceId.size ||
    generatedSizeBefore !=
      state.mutableGeneratedSourceTextContentMapBySourceActorId.size
  ) {
    return { ...state };
  }
  return state;
}

export default update;