Bug 1547811 - Part 1: Use Resource utility for Source type. r=jlast
authorLogan Smyth <loganfsmyth@gmail.com>
Mon, 29 Apr 2019 21:48:10 +0000
changeset 530800 d1f430d47a6b4b4e531beb33e43239ba60c1b4f2
parent 530799 f844160f4144e693bc080b276a6aa2b35908f439
child 530801 25683968bb2f06914f19f47555c5af83e6801fb9
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjlast
bugs1547811
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1547811 - Part 1: Use Resource utility for Source type. r=jlast Depends on D29260 Differential Revision: https://phabricator.services.mozilla.com/D29261
devtools/client/debugger/src/actions/navigation.js
devtools/client/debugger/src/components/SecondaryPanes/Frames/tests/Frames.spec.js
devtools/client/debugger/src/reducers/sources.js
devtools/client/debugger/src/reducers/tabs.js
devtools/client/debugger/src/reducers/tests/sources.spec.js
devtools/client/debugger/src/reducers/types.js
devtools/client/debugger/src/selectors/breakpointSources.js
devtools/client/debugger/src/selectors/getCallStackFrames.js
devtools/client/debugger/src/selectors/test/getCallStackFrames.spec.js
devtools/client/debugger/src/utils/resource/compare.js
devtools/client/debugger/test/mochitest/helpers.js
testing/talos/talos/tests/devtools/addon/content/tests/debugger/debugger-helpers.js
--- a/devtools/client/debugger/src/actions/navigation.js
+++ b/devtools/client/debugger/src/actions/navigation.js
@@ -1,17 +1,17 @@
 /* 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/>. */
 
 // @flow
 
 import { clearDocuments } from "../utils/editor";
 import sourceQueue from "../utils/source-queue";
-import { getSources } from "../reducers/sources";
+import { getSourceList } from "../reducers/sources";
 import { waitForMs } from "../utils/utils";
 
 import { newGeneratedSources } from "./sources";
 import { updateWorkers } from "./debuggee";
 
 import {
   clearASTs,
   clearSymbols,
@@ -76,15 +76,15 @@ export function connect(url: string, act
  * @static
  */
 export function navigated() {
   return async function({ dispatch, getState, client, panel }: ThunkArgs) {
     // this time out is used to wait for sources. If we have 0 sources,
     // it is likely that the sources are being loaded from the bfcache,
     // and we should make an explicit request to the server to load them.
     await waitForMs(100);
-    if (Object.keys(getSources(getState())).length == 0) {
+    if (getSourceList(getState()).length == 0) {
       const sources = await client.fetchSources();
       dispatch(newGeneratedSources(sources));
     }
     panel.emit("reloaded");
   };
 }
--- a/devtools/client/debugger/src/components/SecondaryPanes/Frames/tests/Frames.spec.js
+++ b/devtools/client/debugger/src/components/SecondaryPanes/Frames/tests/Frames.spec.js
@@ -5,16 +5,18 @@
 // @flow
 
 import React from "react";
 import { mount, shallow } from "enzyme";
 import Frames from "../index.js";
 // eslint-disable-next-line
 import { formatCallStackFrames } from "../../../../selectors/getCallStackFrames";
 import { makeMockFrame, makeMockSource } from "../../../../utils/test-mockup";
+import { createInitial, insertResources } from "../../../../utils/resource";
+import type { SourceResourceState } from "../../../../reducers/sources";
 
 function render(overrides = {}) {
   const defaultProps = {
     frames: null,
     selectedFrame: null,
     frameworkGroupingOn: false,
     toggleFrameworkGrouping: jest.fn(),
     contextTypes: {},
@@ -198,20 +200,20 @@ describe("Frames", () => {
 
       const frames = [
         makeMockFrame("1", source1),
         makeMockFrame("2", source2),
         makeMockFrame("3", source1),
         makeMockFrame("8", source2)
       ];
 
-      const sources = {
-        "1": source1,
-        "2": source2
-      };
+      const sources: SourceResourceState = insertResources(createInitial(), [
+        source1,
+        source2
+      ]);
 
       const processedFrames = formatCallStackFrames(frames, sources, source1);
       const selectedFrame = frames[0];
 
       const component = render({
         frames: processedFrames,
         frameworkGroupingOn: false,
         selectedFrame
--- a/devtools/client/debugger/src/reducers/sources.js
+++ b/devtools/client/debugger/src/reducers/sources.js
@@ -13,16 +13,28 @@ import { createSelector } from "reselect
 import {
   getPrettySourceURL,
   underRoot,
   getRelativeUrl,
   isGenerated,
   isOriginal as isOriginalSource,
   getPlainUrl
 } from "../utils/source";
+import {
+  createInitial,
+  insertResources,
+  updateResources,
+  hasResource,
+  getResource,
+  getResourceIds,
+  makeReduceAllQuery,
+  type Resource,
+  type ResourceState,
+  type ReduceAllQuery
+} from "../utils/resource";
 
 import { findBreakableLines } from "../utils/breakable-lines";
 import { findPosition } from "../utils/breakpoint/breakpointPositions";
 import * as asyncValue from "../utils/async-value";
 import type { AsyncValue, SettledValue } from "../utils/async-value";
 import { originalToGeneratedId } from "devtools-source-map";
 import { prefs } from "../utils/prefs";
 
@@ -55,21 +67,26 @@ type SourcesContentMap = { [SourceId]: S
 export type BreakpointPositionsMap = { [SourceId]: BreakpointPositions };
 export type SourcesMapByThread = { [ThreadId]: SourcesMap };
 type SourceActorMap = { [SourceId]: Array<SourceActorId> };
 
 type UrlsMap = { [string]: SourceId[] };
 type DisplayedSources = Set<SourceId>;
 type PlainUrlsMap = { [string]: string[] };
 
+type SourceResource = Resource<{
+  ...Source
+}>;
+export type SourceResourceState = ResourceState<SourceResource>;
+
 export type SourcesState = {
   epoch: number,
 
   // All known sources.
-  sources: SourcesMap,
+  sources: SourceResourceState,
 
   content: SourcesContentMap,
 
   breakpointPositions: BreakpointPositionsMap,
   breakableLines: { [SourceId]: Array<number> },
 
   // A link between each source object and the source actor they wrap over.
   actors: SourceActorMap,
@@ -91,17 +108,17 @@ export type SourcesState = {
   selectedLocation: ?SourceLocation,
   projectDirectoryRoot: string,
   chromeAndExtenstionsEnabled: boolean,
   focusedItem: ?FocusItem
 };
 
 export function initialSourcesState(): SourcesState {
   return {
-    sources: {},
+    sources: createInitial(),
     displayed: new Set(),
     urls: {},
     plainUrls: {},
     content: {},
     actors: {},
     breakpointPositions: {},
     breakableLines: {},
     epoch: 1,
@@ -211,33 +228,37 @@ function update(
 
     case "SET_FOCUSED_SOURCE_ITEM":
       return { ...state, focusedItem: action.item };
   }
 
   return state;
 }
 
+function resourceAsSource(r: SourceResource): Source {
+  return r;
+}
+
 /*
  * Add sources to the sources store
  * - Add the source to the sources store
  * - Add the source URL to the urls map
  */
 function addSources(state: SourcesState, sources: Source[]): SourcesState {
   state = {
     ...state,
     content: { ...state.content },
-    sources: { ...state.sources },
     urls: { ...state.urls },
     plainUrls: { ...state.plainUrls }
   };
 
+  state.sources = insertResources(state.sources, sources);
+
   for (const source of sources) {
     // 1. Add the source to the sources map
-    state.sources[source.id] = source;
     state.content[source.id] = null;
 
     // 2. Update the source url map
     const existing = state.urls[source.url] || [];
     if (!existing.includes(source.id)) {
       state.urls[source.url] = [...existing, source.id];
     }
 
@@ -324,43 +345,45 @@ function updateProjectDirectoryRoot(stat
 }
 
 function updateRootRelativeValues(
   state: SourcesState,
   sources?: Array<Source>
 ) {
   const ids = sources
     ? sources.map(source => source.id)
-    : Object.keys(state.sources);
+    : getResourceIds(state.sources);
 
   state = {
     ...state,
-    sources: { ...state.sources },
     displayed: new Set(state.displayed)
   };
 
+  const relativeURLUpdates = [];
   for (const id of ids) {
-    const source = state.sources[id];
+    const source = getResource(state.sources, id);
 
     state.displayed.delete(source.id);
 
     if (
       underRoot(source, state.projectDirectoryRoot) &&
       (!source.isExtension ||
         getChromeAndExtenstionsEnabled({ sources: state }))
     ) {
       state.displayed.add(source.id);
     }
 
-    state.sources[id] = {
-      ...source,
+    relativeURLUpdates.push({
+      id,
       relativeUrl: getRelativeUrl(source, state.projectDirectoryRoot)
-    };
+    });
   }
 
+  state.sources = updateResources(state.sources, relativeURLUpdates);
+
   return state;
 }
 
 /*
  * Update a source's loaded text content.
  */
 function updateLoadedState(
   state: SourcesState,
@@ -400,61 +423,57 @@ function updateLoadedState(
     }
   };
 }
 
 function clearSourceMaps(
   state: SourcesState,
   sourceId: SourceId
 ): SourcesState {
-  const existingSource = state.sources[sourceId];
-  if (existingSource) {
-    state = {
-      ...state,
-      sources: {
-        ...state.sources,
-        [sourceId]: {
-          ...existingSource,
-          sourceMapURL: ""
-        }
-      }
-    };
+  if (!hasResource(state.sources, sourceId)) {
+    return state;
   }
 
-  return state;
+  return {
+    ...state,
+    sources: updateResources(state.sources, [
+      {
+        id: sourceId,
+        sourceMapURL: ""
+      }
+    ])
+  };
 }
 
 /*
  * Update a source when its state changes
  * e.g. the text was loaded, it was blackboxed
  */
 function updateBlackboxFlag(
   state: SourcesState,
   sourceId: SourceId,
   isBlackBoxed: boolean
 ): SourcesState {
-  const existingSource = state.sources[sourceId];
-
   // If there is no existing version of the source, it means that we probably
   // ended up here as a result of an async action, and the sources were cleared
   // between the action starting and the source being updated.
-  if (!existingSource) {
+  if (!hasResource(state.sources, sourceId)) {
     // TODO: We may want to consider throwing here once we have a better
     // handle on async action flow control.
     return state;
   }
+
   return {
     ...state,
-    sources: {
-      ...state.sources,
-      [sourceId]: {
-        ...existingSource,
+    sources: updateResources(state.sources, [
+      {
+        id: sourceId,
         isBlackBoxed
       }
-    }
+    ])
   };
 }
 
 function updateBlackBoxList(url, isBlackBoxed) {
   const tabs = getBlackBoxList();
   const i = tabs.indexOf(url);
   if (i >= 0) {
     if (!isBlackBoxed) {
@@ -489,18 +508,21 @@ export function getSourceThreads(
 ): ThreadId[] {
   return uniq(
     getSourceActors(state, state.sources.actors[source.id]).map(
       actor => actor.thread
     )
   );
 }
 
-export function getSourceInSources(sources: SourcesMap, id: string): ?Source {
-  return sources[id];
+export function getSourceInSources(
+  sources: SourceResourceState,
+  id: string
+): ?Source {
+  return hasResource(sources, id) ? getResource(sources, id) : null;
 }
 
 export function getSource(state: OuterState, id: SourceId): ?Source {
   return getSourceInSources(getSources(state), id);
 }
 
 export function getSourceFromId(state: OuterState, id: string): Source {
   const source = getSource(state, id);
@@ -517,37 +539,37 @@ export function getSourceByActorId(
   if (!hasSourceActor(state, actorId)) {
     return null;
   }
 
   return getSource(state, getSourceActor(state, actorId).source);
 }
 
 export function getSourcesByURLInSources(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   urls: UrlsMap,
   url: string
 ): Source[] {
   if (!url || !urls[url]) {
     return [];
   }
-  return urls[url].map(id => sources[id]);
+  return urls[url].map(id => getResource(sources, id));
 }
 
 export function getSourcesByURL(state: OuterState, url: string): Source[] {
   return getSourcesByURLInSources(getSources(state), getUrls(state), url);
 }
 
 export function getSourceByURL(state: OuterState, url: string): ?Source {
   const foundSources = getSourcesByURL(state, url);
   return foundSources ? foundSources[0] : null;
 }
 
 export function getSpecificSourceByURLInSources(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   urls: UrlsMap,
   url: string,
   isOriginal: boolean
 ): ?Source {
   const foundSources = getSourcesByURLInSources(sources, urls, url);
   if (foundSources) {
     return foundSources.find(source => isOriginalSource(source) == isOriginal);
   }
@@ -640,34 +662,39 @@ export function getSourcesUrlsInSources(
 export function getHasSiblingOfSameName(state: OuterState, source: ?Source) {
   if (!source) {
     return false;
   }
 
   return getSourcesUrlsInSources(state, source.url).length > 1;
 }
 
-export function getSources(state: OuterState) {
+const querySourceList: ReduceAllQuery<
+  SourceResource,
+  Array<Source>
+> = makeReduceAllQuery(resourceAsSource, sources => sources.slice());
+
+export function getSources(state: OuterState): SourceResourceState {
   return state.sources.sources;
 }
 
 export function getSourcesEpoch(state: OuterState) {
   return state.sources.epoch;
 }
 
 export function getUrls(state: OuterState) {
   return state.sources.urls;
 }
 
 export function getPlainUrls(state: OuterState) {
   return state.sources.plainUrls;
 }
 
 export function getSourceList(state: OuterState): Source[] {
-  return (Object.values(getSources(state)): any);
+  return querySourceList(getSources(state));
 }
 
 export function getDisplayedSourcesList(
   state: OuterState & SourceActorOuterState
 ): Source[] {
   return ((Object.values(getDisplayedSources(state)): any).flatMap(
     Object.values
   ): any);
@@ -680,36 +707,41 @@ export function getSourceCount(state: Ou
 export const getSelectedLocation: Selector<?SourceLocation> = createSelector(
   getSourcesState,
   sources => sources.selectedLocation
 );
 
 export const getSelectedSource: Selector<?Source> = createSelector(
   getSelectedLocation,
   getSources,
-  (selectedLocation: ?SourceLocation, sources: SourcesMap): ?Source => {
+  (
+    selectedLocation: ?SourceLocation,
+    sources: SourceResourceState
+  ): ?Source => {
     if (!selectedLocation) {
       return;
     }
 
-    return sources[selectedLocation.sourceId];
+    return getSourceInSources(sources, selectedLocation.sourceId);
   }
 );
 
 type GSSWC = Selector<?SourceWithContent>;
 export const getSelectedSourceWithContent: GSSWC = createSelector(
   getSelectedLocation,
   getSources,
   state => state.sources.content,
   (
     selectedLocation: ?SourceLocation,
-    sources: SourcesMap,
+    sources: SourceResourceState,
     content: SourcesContentMap
   ): SourceWithContent | null => {
-    const source = selectedLocation && sources[selectedLocation.sourceId];
+    const source =
+      selectedLocation &&
+      getSourceInSources(sources, selectedLocation.sourceId);
     return source
       ? getSourceWithContentInner(sources, content, source.id)
       : null;
   }
 );
 export function getSourceWithContent(
   state: OuterState,
   id: SourceId
@@ -719,34 +751,29 @@ export function getSourceWithContent(
     state.sources.content,
     id
   );
 }
 export function getSourceContent(
   state: OuterState,
   id: SourceId
 ): AsyncValue<SourceContent> | null {
-  const source = state.sources.sources[id];
-  if (!source) {
-    throw new Error("Unknown Source ID");
-  }
+  // Assert the resource exists.
+  getResource(state.sources.sources, id);
 
   return state.sources.content[id] || null;
 }
 
 const contentLookup: WeakMap<Source, SourceWithContent> = new WeakMap();
 function getSourceWithContentInner(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   content: SourcesContentMap,
   id: SourceId
 ): SourceWithContent {
-  const source = sources[id];
-  if (!source) {
-    throw new Error("Unknown Source ID");
-  }
+  const source = getResource(sources, id);
   const contentValue = content[source.id];
 
   let result = contentLookup.get(source);
   if (!result || result.content !== contentValue) {
     result = {
       source,
       content: contentValue
     };
@@ -807,17 +834,17 @@ export const getDisplayedSources: GetDis
   (sources, idsByThread) => {
     const result = {};
 
     for (const thread of Object.keys(idsByThread)) {
       for (const id of idsByThread[thread]) {
         if (!result[thread]) {
           result[thread] = {};
         }
-        result[thread][id] = sources[id];
+        result[thread][id] = getResource(sources, id);
       }
     }
 
     return result;
   }
 );
 
 export function getDisplayedSourcesForThread(
--- a/devtools/client/debugger/src/reducers/tabs.js
+++ b/devtools/client/debugger/src/reducers/tabs.js
@@ -12,16 +12,17 @@
 import { createSelector } from "reselect";
 import { isOriginalId } from "devtools-source-map";
 import move from "lodash-move";
 
 import { asyncStore } from "../utils/prefs";
 import {
   getSource,
   getSources,
+  getSourceInSources,
   getUrls,
   getSpecificSourceByURL,
   getSpecificSourceByURLInSources
 } from "./sources";
 
 import type { Action } from "../actions/types";
 import type { SourcesState } from "./sources";
 import type { Source } from "../types";
@@ -215,12 +216,12 @@ function getTabWithOrWithoutUrl(tab, sou
     return getSpecificSourceByURLInSources(
       sources,
       urls,
       tab.url,
       tab.isOriginal
     );
   }
 
-  return tab.sourceId ? sources[tab.sourceId] : null;
+  return tab.sourceId ? getSourceInSources(sources, tab.sourceId) : null;
 }
 
 export default update;
--- a/devtools/client/debugger/src/reducers/tests/sources.spec.js
+++ b/devtools/client/debugger/src/reducers/tests/sources.spec.js
@@ -7,16 +7,17 @@ declare var describe: (name: string, fun
 declare var it: (desc: string, func: () => void) => void;
 declare var expect: (value: any) => any;
 
 import update, { initialSourcesState, getDisplayedSources } from "../sources";
 import updateSourceActors from "../source-actors";
 import type { Source, SourceActor } from "../../types";
 import { prefs } from "../../utils/prefs";
 import { makeMockSource, mockcx } from "../../utils/test-mockup";
+import { getResourceIds } from "../../utils/resource";
 
 const extensionSource = {
   ...makeMockSource(),
   id: "extensionId",
   url: "http://example.com/script.js"
 };
 
 const firefoxExtensionSource = {
@@ -63,17 +64,17 @@ const mockSourceActors: Array<SourceActo
 describe("sources reducer", () => {
   it("should work", () => {
     let state = initialSourcesState();
     state = update(state, {
       type: "ADD_SOURCE",
       cx: mockcx,
       source: makeMockSource()
     });
-    expect(Object.keys(state.sources)).toHaveLength(1);
+    expect(getResourceIds(state.sources)).toHaveLength(1);
   });
 });
 
 describe("sources selectors", () => {
   it("should return all extensions when chrome preference enabled", () => {
     prefs.chromeAndExtenstionsEnabled = true;
     let state = initialSourcesState();
     state = {
--- a/devtools/client/debugger/src/reducers/types.js
+++ b/devtools/client/debugger/src/reducers/types.js
@@ -43,13 +43,17 @@ export type State = {
 export type Selector<T> = State => T;
 
 export type PendingSelectedLocation = {
   url: string,
   line?: number,
   column?: number
 };
 
-export type { SourcesMap, SourcesMapByThread } from "./sources";
+export type {
+  SourcesMap,
+  SourcesMapByThread,
+  SourceResourceState
+} from "./sources";
 export type { ActiveSearchType, OrientationType } from "./ui";
 export type { BreakpointsMap, XHRBreakpointsList } from "./breakpoints";
 export type { Command } from "./pause";
 export type { LoadedSymbols, Symbols, Preview, PreviewValue } from "./ast";
--- a/devtools/client/debugger/src/selectors/breakpointSources.js
+++ b/devtools/client/debugger/src/selectors/breakpointSources.js
@@ -3,25 +3,26 @@
  * file, You can obtain one at <http://mozilla.org/MPL/2.0/>. */
 
 // @flow
 
 import { sortBy, uniq } from "lodash";
 import { createSelector } from "reselect";
 import {
   getSources,
+  getSourceInSources,
   getBreakpointsList,
   getSelectedSource
 } from "../selectors";
 import { getFilename } from "../utils/source";
 import { getSelectedLocation } from "../utils/selected-location";
 import { sortSelectedBreakpoints } from "../utils/breakpoint";
 
 import type { Source, Breakpoint } from "../types";
-import type { Selector, SourcesMap } from "../reducers/types";
+import type { Selector, SourceResourceState } from "../reducers/types";
 
 export type BreakpointSources = Array<{
   source: Source,
   breakpoints: Breakpoint[]
 }>;
 
 function getBreakpointsForSource(
   source: Source,
@@ -35,36 +36,44 @@ function getBreakpointsForSource(
         (bp.text || bp.originalText || bp.options.condition || bp.disabled)
     )
     .filter(
       bp => getSelectedLocation(bp, selectedSource).sourceId == source.id
     );
 }
 
 function findBreakpointSources(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   breakpoints: Breakpoint[],
   selectedSource: ?Source
 ): Source[] {
   const sourceIds: string[] = uniq(
     breakpoints.map(bp => getSelectedLocation(bp, selectedSource).sourceId)
   );
 
-  const breakpointSources = sourceIds
-    .map(id => sources[id])
-    .filter(source => source && !source.isBlackBoxed);
+  const breakpointSources = sourceIds.reduce((acc, id) => {
+    const source = getSourceInSources(sources, id);
+    if (source && !source.isBlackBoxed) {
+      acc.push(source);
+    }
+    return acc;
+  }, []);
 
   return sortBy(breakpointSources, (source: Source) => getFilename(source));
 }
 
 export const getBreakpointSources: Selector<BreakpointSources> = createSelector(
   getBreakpointsList,
   getSources,
   getSelectedSource,
-  (breakpoints: Breakpoint[], sources: SourcesMap, selectedSource: ?Source) =>
+  (
+    breakpoints: Breakpoint[],
+    sources: SourceResourceState,
+    selectedSource: ?Source
+  ) =>
     findBreakpointSources(sources, breakpoints, selectedSource)
       .map(source => ({
         source,
         breakpoints: getBreakpointsForSource(
           source,
           selectedSource,
           breakpoints
         )
--- a/devtools/client/debugger/src/selectors/getCallStackFrames.js
+++ b/devtools/client/debugger/src/selectors/getCallStackFrames.js
@@ -8,52 +8,51 @@ import {
   getSources,
   getSelectedSource,
   getSourceInSources
 } from "../reducers/sources";
 import { getCurrentThreadFrames } from "../reducers/pause";
 import { annotateFrames } from "../utils/pause/frames";
 import { isOriginal } from "../utils/source";
 import { get } from "lodash";
-import type { State } from "../reducers/types";
+import type { State, SourceResourceState } from "../reducers/types";
 import type { Frame, Source } from "../types";
-import type { SourcesMap } from "../reducers/sources";
 import { createSelector } from "reselect";
 
 function getLocation(frame, isGeneratedSource) {
   return isGeneratedSource
     ? frame.generatedLocation || frame.location
     : frame.location;
 }
 
 function getSourceForFrame(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   frame: Frame,
   isGeneratedSource
 ) {
   const sourceId = getLocation(frame, isGeneratedSource).sourceId;
   return getSourceInSources(sources, sourceId);
 }
 
 function appendSource(
-  sources: SourcesMap,
+  sources: SourceResourceState,
   frame: Frame,
   selectedSource: ?Source
 ): Frame {
   const isGeneratedSource = selectedSource && !isOriginal(selectedSource);
   return {
     ...frame,
     location: getLocation(frame, isGeneratedSource),
     source: getSourceForFrame(sources, frame, isGeneratedSource)
   };
 }
 
 export function formatCallStackFrames(
   frames: Frame[],
-  sources: SourcesMap,
+  sources: SourceResourceState,
   selectedSource: Source
 ) {
   if (!frames) {
     return null;
   }
 
   const formattedFrames: Frame[] = frames
     .filter(frame => getSourceForFrame(sources, frame))
--- a/devtools/client/debugger/src/selectors/test/getCallStackFrames.spec.js
+++ b/devtools/client/debugger/src/selectors/test/getCallStackFrames.spec.js
@@ -1,34 +1,35 @@
 /* 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/>. */
 
 // @flow
 
 import { getCallStackFrames } from "../getCallStackFrames";
 import { pullAt } from "lodash";
+import { insertResources, createInitial } from "../../utils/resource";
 
 describe("getCallStackFrames selector", () => {
   describe("library annotation", () => {
     it("annotates React frames", () => {
       const state = {
         frames: [
           { location: { sourceId: "source1" } },
           { location: { sourceId: "source2" } },
           { location: { sourceId: "source2" } }
         ],
-        sources: {
-          source1: { id: "source1", url: "webpack:///src/App.js" },
-          source2: {
+        sources: insertResources(createInitial(), [
+          { id: "source1", url: "webpack:///src/App.js" },
+          {
             id: "source2",
             url:
               "webpack:///foo/node_modules/react-dom/lib/ReactCompositeComponent.js"
           }
-        },
+        ]),
         selectedSource: {
           id: "sourceId-originalSource"
         }
       };
 
       const frames = getCallStackFrames.resultFunc(
         state.frames,
         state.sources,
@@ -122,32 +123,32 @@ describe("getCallStackFrames selector", 
         {
           displayName: "flush",
           location: { sourceId: "microtask" }
         }
       ];
 
       const state = {
         frames: [...preAwaitGroup, ...postAwaitGroup],
-        sources: {
-          app: { id: "app", url: "webpack///app.js" },
-          bundle: { id: "bundle", url: "https://foo.com/bundle.js" },
-          regenerator: {
+        sources: insertResources(createInitial(), [
+          { id: "app", url: "webpack///app.js" },
+          { id: "bundle", url: "https://foo.com/bundle.js" },
+          {
             id: "regenerator",
             url: "webpack:///foo/node_modules/regenerator-runtime/runtime.js"
           },
-          microtask: {
+          {
             id: "microtask",
             url: "webpack:///foo/node_modules/core-js/modules/_microtask.js"
           },
-          promise: {
+          {
             id: "promise",
             url: "webpack///foo/node_modules/core-js/modules/es6.promise.js"
           }
-        },
+        ]),
         selectedSource: {
           id: "sourceId-originalSource"
         }
       };
 
       const frames = getCallStackFrames.resultFunc(
         state.frames,
         state.sources,
--- a/devtools/client/debugger/src/utils/resource/compare.js
+++ b/devtools/client/debugger/src/utils/resource/compare.js
@@ -30,15 +30,15 @@ function objectShallowEqual(
   other: { [string]: mixed }
 ): boolean {
   const existingKeys = Object.keys(other);
   const keys = Object.keys(value);
 
   return (
     keys.length === existingKeys.length &&
     keys.every((k, i) => k === existingKeys[i]) &&
-    keys.every((k, i) => value[k] === other[existingKeys[i]])
+    keys.every(k => value[k] === other[k])
   );
 }
 
 function isObject(value: mixed): boolean %checks {
   return typeof value === "object" && !!value;
 }
--- a/devtools/client/debugger/test/mochitest/helpers.js
+++ b/devtools/client/debugger/test/mochitest/helpers.js
@@ -149,20 +149,16 @@ function waitForState(dbg, predicate, ms
  *
  * @memberof mochitest/waits
  * @param {Object} dbg
  * @param {Array} sources
  * @return {Promise}
  * @static
  */
 async function waitForSources(dbg, ...sources) {
-  const {
-    selectors: { getSources },
-    store
-  } = dbg;
   if (sources.length === 0) {
     return Promise.resolve();
   }
 
   info(`Waiting on sources: ${sources.join(", ")}`);
   await Promise.all(
     sources.map(url => {
       if (!sourceExists(dbg, url)) {
@@ -592,17 +588,17 @@ function pauseTest() {
 function findSource(dbg, url, { silent } = { silent: false }) {
   if (typeof url !== "string") {
     // Support passing in a source object itelf all APIs that use this
     // function support both styles
     const source = url;
     return source;
   }
 
-  const sources = Object.values(dbg.selectors.getSources());
+  const sources = dbg.selectors.getSourceList();
   const source = sources.find(s => (s.url || "").includes(url));
 
   if (!source) {
     if (silent) {
       return false;
     }
 
     throw new Error(`Unable to find source: ${url}`);
@@ -643,17 +639,17 @@ function waitForLoadedSource(dbg, url) {
     "loaded source"
   );
 }
 
 function waitForLoadedSources(dbg) {
   return waitForState(
     dbg,
     state => {
-      const sources = Object.values(dbg.selectors.getSources());
+      const sources = dbg.selectors.getSourceList();
       return sources.every(source => !!dbg.selectors.getSourceContent(source.id));
     },
     "loaded source"
   );
 }
 
 function getContext(dbg) {
   return dbg.selectors.getContext();
--- a/testing/talos/talos/tests/devtools/addon/content/tests/debugger/debugger-helpers.js
+++ b/testing/talos/talos/tests/devtools/addon/content/tests/debugger/debugger-helpers.js
@@ -73,18 +73,18 @@ async function waitUntil(predicate, msg)
         }
         resolve();
       }
     }, DEBUGGER_POLLING_INTERVAL);
   });
 }
 
 function findSource(dbg, url) {
-  const sources = dbg.selectors.getSources(dbg.getState());
-  return Object.values(sources).find(s => (s.url || "").includes(url));
+  const sources = dbg.selectors.getSourceList(dbg.getState());
+  return sources.find(s => (s.url || "").includes(url));
 }
 
 function getCM(dbg) {
   const el = dbg.win.document.querySelector(".CodeMirror");
   return el.CodeMirror;
 }
 
 function waitForText(dbg, url, text) {