Bug 1532162 - Map Scopes should be opt-in. r=loganfsmyth
authorJason Laster <jlaster@mozilla.com>
Thu, 07 Mar 2019 13:50:36 +0000
changeset 520754 4f2c7085942ec590a60395b5806de13fd2c7ef0f
parent 520753 a685ad7c44598bca47ab3389771781ce0a30c7a4
child 520755 26d4249db1c799d4312d061d656d7e65a6b78ba2
push id10862
push userffxbld-merge
push dateMon, 11 Mar 2019 13:01:11 +0000
treeherdermozilla-beta@a2e7f5c935da [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersloganfsmyth
bugs1532162
milestone67.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 1532162 - Map Scopes should be opt-in. r=loganfsmyth Differential Revision: https://phabricator.services.mozilla.com/D21870
devtools/client/debugger/new/dist/debugger.css
devtools/client/debugger/new/src/actions/pause/index.js
devtools/client/debugger/new/src/actions/pause/mapScopes.js
devtools/client/debugger/new/src/actions/tests/pending-breakpoints.spec.js
devtools/client/debugger/new/src/actions/types/PauseAction.js
devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.css
devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
devtools/client/debugger/new/src/reducers/pause.js
devtools/client/debugger/new/src/reducers/sources.js
devtools/client/debugger/new/src/utils/prefs.js
devtools/client/debugger/new/test/mochitest/helpers.js
devtools/client/locales/en-US/debugger.properties
devtools/client/preferences/debugger.js
--- a/devtools/client/debugger/new/dist/debugger.css
+++ b/devtools/client/debugger/new/dist/debugger.css
@@ -4012,16 +4012,52 @@ html[dir="rtl"] .event-listeners-content
 	margin-inline-start: 0px;
 	margin-top: 0px;
 	margin-bottom: 0px;
 }
 /* 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/>. */
 
+
+
+.scopes-content .toggle-map-scopes {
+  border-bottom: 1px solid var(--theme-splitter-color);
+  margin-bottom: 3px;
+  margin-left: 10px;
+  padding: 0.5em 0;
+}
+
+.scopes-content .toggle-map-scopes input {
+  padding-inline-start: 2px;
+  margin-inline-start: 0;
+  vertical-align: text-bottom;
+}
+
+.scopes-content .toggle-map-scopes-label {
+  padding-inline-start: 2px;
+  padding-inline-end: 8px;
+  cursor: default;
+  flex-grow: 1;
+  -moz-user-select: none;
+}
+
+.scopes-content .toggle-map-scopes a.mdn {
+  padding-inline-end: 10px;
+}
+
+.scopes-content .toggle-map-scopes .img.shortcuts {
+  background: var(--theme-comment);
+}
+
+.scopes-content .toggle-map-scopes {
+  display: flex;
+  align-items: center;
+}
+
 .object-node.default-property {
   opacity: 0.6;
 }
 
 .object-node {
   padding-left: 4px;
 }
 
--- a/devtools/client/debugger/new/src/actions/pause/index.js
+++ b/devtools/client/debugger/new/src/actions/pause/index.js
@@ -25,8 +25,9 @@ export { paused } from "./paused";
 export { resumed } from "./resumed";
 export { continueToHere } from "./continueToHere";
 export { breakOnNext } from "./breakOnNext";
 export { mapFrames } from "./mapFrames";
 export { setPopupObjectProperties } from "./setPopupObjectProperties";
 export { pauseOnExceptions } from "./pauseOnExceptions";
 export { selectFrame } from "./selectFrame";
 export { toggleSkipPausing } from "./skipPausing";
+export { toggleMapScopes } from "./mapScopes";
--- a/devtools/client/debugger/new/src/actions/pause/mapScopes.js
+++ b/devtools/client/debugger/new/src/actions/pause/mapScopes.js
@@ -1,27 +1,56 @@
 /* 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 { getCurrentThread, getSource } from "../../selectors";
+import {
+  getCurrentThread,
+  getSource,
+  getMapScopes,
+  getSelectedFrame,
+  getSelectedGeneratedScope,
+  getSelectedOriginalScope
+} from "../../selectors";
 import { loadSourceText } from "../sources/loadSourceText";
 import { PROMISE } from "../utils/middleware/promise";
 
 import { features } from "../../utils/prefs";
 import { log } from "../../utils/log";
 import { isGenerated } from "../../utils/source";
 import type { Frame, Scope } from "../../types";
 
 import type { ThunkArgs } from "../types";
 
 import { buildMappedScopes } from "../../utils/pause/mapScopes";
 
+export function toggleMapScopes() {
+  return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
+    if (getMapScopes(getState())) {
+      return dispatch({ type: "TOGGLE_MAP_SCOPES", mapScopes: false });
+    }
+
+    dispatch({ type: "TOGGLE_MAP_SCOPES", mapScopes: true });
+
+    if (getSelectedOriginalScope(getState())) {
+      return;
+    }
+
+    const scopes = getSelectedGeneratedScope(getState());
+    const frame = getSelectedFrame(getState());
+    if (!scopes || !frame) {
+      return;
+    }
+
+    dispatch(mapScopes(Promise.resolve(scopes.scope), frame));
+  };
+}
+
 export function mapScopes(scopes: Promise<Scope>, frame: Frame) {
   return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
     const generatedSource = getSource(
       getState(),
       frame.generatedLocation.sourceId
     );
 
     const source = getSource(getState(), frame.location.sourceId);
--- a/devtools/client/debugger/new/src/actions/tests/pending-breakpoints.spec.js
+++ b/devtools/client/debugger/new/src/actions/tests/pending-breakpoints.spec.js
@@ -10,18 +10,18 @@ import {
   mockPendingBreakpoint
 } from "./helpers/breakpoints.js";
 
 import { simpleMockThreadClient } from "./helpers/threadClient.js";
 
 import { asyncStore } from "../../utils/prefs";
 
 function loadInitialState(opts = {}) {
-  const mockedPendingBreakpoint = mockPendingBreakpoint({...opts, column: 2});
-  const id = makePendingLocationId(mockedPendingBreakpoint.location)
+  const mockedPendingBreakpoint = mockPendingBreakpoint({ ...opts, column: 2 });
+  const id = makePendingLocationId(mockedPendingBreakpoint.location);
   asyncStore.pendingBreakpoints = { [id]: mockedPendingBreakpoint };
 
   return { pendingBreakpoints: asyncStore.pendingBreakpoints };
 }
 
 jest.mock("../../utils/prefs", () => ({
   prefs: {
     clientSourceMapsEnabled: true,
@@ -56,17 +56,21 @@ function mockClient(bpPos = {}) {
 
     getBreakpointPositions: async () => bpPos
   };
 }
 
 function mockSourceMaps() {
   return {
     ...sourceMaps,
-    getOriginalSourceText: async (source) => ({id: source.id, text: "", contentType: "text/javascript"}),
+    getOriginalSourceText: async source => ({
+      id: source.id,
+      text: "",
+      contentType: "text/javascript"
+    }),
     getGeneratedRangesForOriginal: async () => [
       { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
     ]
   };
 }
 
 describe("when adding breakpoints", () => {
   it("a corresponding pending breakpoint should be added", async () => {
@@ -359,33 +363,29 @@ describe("adding sources", () => {
 
     await waitForState(store, state => selectors.getBreakpointCount(state) > 0);
 
     expect(selectors.getBreakpointCount(getState())).toEqual(1);
   });
 
   it("corresponding breakpoints are added to the original source", async () => {
     const source = makeOriginalSource("bar.js", { sourceMapURL: "foo" });
-    const store = createStore(
-      mockClient({ "5": [2] }), 
-      loadInitialState(), 
-      {
-        getOriginalURLs: async () => [source.url],
-        getOriginalSourceText: async () => ({ source: "" }),
-        getGeneratedLocation: async (location, _source) => ({
-          line: location.line,
-          column: location.column,
-          sourceId: _source.id
-        }),
-        getOriginalLocation: async location => location,
-        getGeneratedRangesForOriginal: async () => [
-          { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
-        ]
-      }
-    );
+    const store = createStore(mockClient({ "5": [2] }), loadInitialState(), {
+      getOriginalURLs: async () => [source.url],
+      getOriginalSourceText: async () => ({ source: "" }),
+      getGeneratedLocation: async (location, _source) => ({
+        line: location.line,
+        column: location.column,
+        sourceId: _source.id
+      }),
+      getOriginalLocation: async location => location,
+      getGeneratedRangesForOriginal: async () => [
+        { start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
+      ]
+    });
 
     const { getState, dispatch } = store;
 
     expect(selectors.getBreakpointCount(getState())).toEqual(0);
 
     await dispatch(actions.newSource(makeSource("bar.js")));
     await dispatch(actions.newSource(source));
 
--- a/devtools/client/debugger/new/src/actions/types/PauseAction.js
+++ b/devtools/client/debugger/new/src/actions/types/PauseAction.js
@@ -125,9 +125,13 @@ export type PauseAction =
         +frame: Frame
       |},
       Scope
     >
   | {|
       +type: "TOGGLE_SKIP_PAUSING",
       +thread: string,
       skipPausing: boolean
+    |}
+  | {|
+      +type: "TOGGLE_MAP_SCOPES",
+      +mapScopes: boolean
     |};
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.css
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.css
@@ -1,12 +1,48 @@
 /* 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/>. */
 
+
+
+.scopes-content .toggle-map-scopes {
+  border-bottom: 1px solid var(--theme-splitter-color);
+  margin-bottom: 3px;
+  margin-left: 10px;
+  padding: 0.5em 0;
+}
+
+.scopes-content .toggle-map-scopes input {
+  padding-inline-start: 2px;
+  margin-inline-start: 0;
+  vertical-align: text-bottom;
+}
+
+.scopes-content .toggle-map-scopes-label {
+  padding-inline-start: 2px;
+  padding-inline-end: 8px;
+  cursor: default;
+  flex-grow: 1;
+  -moz-user-select: none;
+}
+
+.scopes-content .toggle-map-scopes a.mdn {
+  padding-inline-end: 10px;
+}
+
+.scopes-content .toggle-map-scopes .img.shortcuts {
+  background: var(--theme-comment);
+}
+
+.scopes-content .toggle-map-scopes {
+  display: flex;
+  align-items: center;
+}
+
 .object-node.default-property {
   opacity: 0.6;
 }
 
 .object-node {
   padding-left: 4px;
 }
 
--- a/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
+++ b/devtools/client/debugger/new/src/components/SecondaryPanes/Scopes.js
@@ -1,46 +1,54 @@
 /* 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 React, { PureComponent } from "react";
+import { isGeneratedId } from "devtools-source-map";
 import { connect } from "../../utils/connect";
+import { features } from "../../utils/prefs";
 import actions from "../../actions";
 import { createObjectClient } from "../../client/firefox";
 
 import {
   getSelectedSource,
   getSelectedFrame,
   getGeneratedFrameScope,
   getOriginalFrameScope,
   isPaused as getIsPaused,
-  getPauseReason
+  getPauseReason,
+  getMapScopes
 } from "../../selectors";
 import { getScopes } from "../../utils/pause/scopes";
 
 import { objectInspector } from "devtools-reps";
+import AccessibleImage from "../shared/AccessibleImage";
 
 import type { Why } from "../../types";
 import type { NamedValue } from "../../utils/pause/scopes/types";
 
 import "./Scopes.css";
 
+const mdnLink = "https://developer.mozilla.org/en-US/docs/Tools/Debugger";
+
 const { ObjectInspector } = objectInspector;
 
 type Props = {
   isPaused: boolean,
   selectedFrame: Object,
   generatedFrameScopes: Object,
   originalFrameScopes: Object | null,
   isLoading: boolean,
   why: Why,
+  shouldMapScopes: boolean,
   openLink: typeof actions.openLink,
-  openElementInInspector: typeof actions.openElementInInspectorCommand
+  openElementInInspector: typeof actions.openElementInInspectorCommand,
+  toggleMapScopes: typeof actions.toggleMapScopes
 };
 
 type State = {
   originalScopes: ?(NamedValue[]),
   generatedScopes: ?(NamedValue[]),
   showOriginal: boolean
 };
 
@@ -92,42 +100,72 @@ class Scopes extends PureComponent<Props
           nextProps.why,
           nextProps.selectedFrame,
           nextProps.generatedFrameScopes
         )
       });
     }
   }
 
-  render() {
+  onToggleMapScopes = () => {
+    this.props.toggleMapScopes();
+  };
+
+  renderMapScopes() {
+    const { selectedFrame, shouldMapScopes } = this.props;
+
+    if (!features.mapScopes || isGeneratedId(selectedFrame.location.sourceId)) {
+      return null;
+    }
+
+    return (
+      <div className="toggle-map-scopes" onClick={this.onToggleMapScopes}>
+        <input
+          type="checkbox"
+          checked={shouldMapScopes ? "checked" : ""}
+          onChange={e => e.stopPropagation() && this.onToggleMapScopes()}
+        />
+        <div className="toggle-map-scopes-label">
+          <span>{L10N.getStr("scopes.mapScopes")}</span>
+        </div>
+        <a className="mdn" target="_blank" href={mdnLink}>
+          <AccessibleImage className="shortcuts" />
+        </a>
+      </div>
+    );
+  }
+
+  renderScopesList() {
     const {
       isPaused,
       isLoading,
       openLink,
-      openElementInInspector
+      openElementInInspector,
+      shouldMapScopes
     } = this.props;
     const { originalScopes, generatedScopes, showOriginal } = this.state;
 
-    const scopes = (showOriginal && originalScopes) || generatedScopes;
+    const scopes =
+      (showOriginal && shouldMapScopes && originalScopes) || generatedScopes;
 
     if (scopes && !isLoading) {
       return (
         <div className="pane scopes-list">
           <ObjectInspector
             roots={scopes}
             autoExpandAll={false}
             autoExpandDepth={1}
             disableWrap={true}
             dimTopLevelWindow={true}
             openLink={openLink}
             createObjectClient={grip => createObjectClient(grip)}
             onDOMNodeClick={grip => openElementInInspector(grip)}
             onInspectIconClick={grip => openElementInInspector(grip)}
           />
-          {originalScopes ? (
+          {originalScopes && shouldMapScopes ? (
             <div className="scope-type-toggle">
               <button
                 onClick={e => {
                   e.preventDefault();
                   this.setState({ showOriginal: !showOriginal });
                 }}
               >
                 {showOriginal
@@ -150,16 +188,25 @@ class Scopes extends PureComponent<Props
     }
 
     return (
       <div className="pane scopes-list">
         <div className="pane-info">{stateText}</div>
       </div>
     );
   }
+
+  render() {
+    return (
+      <div className="scopes-content">
+        {this.renderMapScopes()}
+        {this.renderScopesList()}
+      </div>
+    );
+  }
 }
 
 const mapStateToProps = state => {
   const selectedFrame = getSelectedFrame(state);
   const selectedSource = getSelectedSource(state);
 
   const {
     scope: originalFrameScopes,
@@ -175,23 +222,25 @@ const mapStateToProps = state => {
     pending: generatedPending
   } = getGeneratedFrameScope(state, selectedFrame && selectedFrame.id) || {
     scope: null,
     pending: false
   };
 
   return {
     selectedFrame,
+    shouldMapScopes: getMapScopes(state),
     isPaused: getIsPaused(state),
     isLoading: generatedPending || originalPending,
     why: getPauseReason(state),
     originalFrameScopes,
     generatedFrameScopes
   };
 };
 
 export default connect(
   mapStateToProps,
   {
     openLink: actions.openLink,
-    openElementInInspector: actions.openElementInInspectorCommand
+    openElementInInspector: actions.openElementInInspectorCommand,
+    toggleMapScopes: actions.toggleMapScopes
   }
 )(Scopes);
--- a/devtools/client/debugger/new/src/reducers/pause.js
+++ b/devtools/client/debugger/new/src/reducers/pause.js
@@ -8,17 +8,17 @@
 /**
  * Pause reducer
  * @module reducers/pause
  */
 
 import { createSelector } from "reselect";
 import { isGeneratedId } from "devtools-source-map";
 import { prefs } from "../utils/prefs";
-import { getSelectedSource } from "./sources";
+import { getSelectedSourceId } from "./sources";
 
 import type { OriginalScope } from "../utils/pause/mapScopes";
 import type { Action } from "../actions/types";
 import type { Selector, State } from "./types";
 import type {
   Why,
   Scope,
   SourceId,
@@ -73,24 +73,26 @@ type ThreadPauseState = {
   previousLocation: ?MappedLocation
 };
 
 // Pause state describing all threads.
 export type PauseState = {
   currentThread: string,
   canRewind: boolean,
   threads: { [string]: ThreadPauseState },
-  skipPausing: boolean
+  skipPausing: boolean,
+  mapScopes: boolean
 };
 
 export const createPauseState = (): PauseState => ({
   currentThread: "UnknownThread",
   threads: {},
   canRewind: false,
-  skipPausing: prefs.skipPausing
+  skipPausing: prefs.skipPausing,
+  mapScopes: prefs.mapScopes
 });
 
 const resumedPauseState = {
   frames: null,
   frameScopes: {
     generated: {},
     original: {},
     mappings: {}
@@ -301,16 +303,22 @@ function update(
       };
 
     case "TOGGLE_SKIP_PAUSING": {
       const { skipPausing } = action;
       prefs.skipPausing = skipPausing;
 
       return { ...state, skipPausing };
     }
+
+    case "TOGGLE_MAP_SCOPES": {
+      const { mapScopes } = action;
+      prefs.mapScopes = mapScopes;
+      return { ...state, mapScopes };
+    }
   }
 
   return state;
 }
 
 function getPauseLocation(state, action) {
   const { frames, previousLocation } = state;
 
@@ -498,31 +506,38 @@ export function getFrameScope(
 } {
   return (
     getOriginalFrameScope(state, sourceId, frameId) ||
     getGeneratedFrameScope(state, frameId)
   );
 }
 
 export function getSelectedScope(state: OuterState) {
-  const source = getSelectedSource(state);
+  const sourceId = getSelectedSourceId(state);
   const frameId = getSelectedFrameId(state);
 
-  if (!source) {
-    return null;
-  }
-
-  const frameScope = getFrameScope(state, source.id, frameId);
+  const frameScope = getFrameScope(state, sourceId, frameId);
   if (!frameScope) {
     return null;
   }
 
   return frameScope.scope || null;
 }
 
+export function getSelectedOriginalScope(state: OuterState) {
+  const sourceId = getSelectedSourceId(state);
+  const frameId = getSelectedFrameId(state);
+  return getOriginalFrameScope(state, sourceId, frameId);
+}
+
+export function getSelectedGeneratedScope(state: OuterState) {
+  const frameId = getSelectedFrameId(state);
+  return getGeneratedFrameScope(state, frameId);
+}
+
 export function getSelectedScopeMappings(
   state: OuterState
 ): {
   [string]: string | null
 } | null {
   const frameId = getSelectedFrameId(state);
   if (!frameId) {
     return null;
@@ -551,15 +566,19 @@ export const getSelectedFrame: Selector<
     return frames.find(frame => frame.id == selectedFrameId);
   }
 );
 
 export function getSkipPausing(state: OuterState) {
   return state.pause.skipPausing;
 }
 
+export function getMapScopes(state: OuterState) {
+  return state.pause.mapScopes;
+}
+
 // NOTE: currently only used for chrome
 export function getChromeScopes(state: OuterState) {
   const frame: ?ChromeFrame = (getSelectedFrame(state): any);
   return frame ? frame.scopeChain : undefined;
 }
 
 export default update;
--- a/devtools/client/debugger/new/src/reducers/sources.js
+++ b/devtools/client/debugger/new/src/reducers/sources.js
@@ -534,16 +534,21 @@ export const getSelectedSource: Selector
     if (!selectedLocation) {
       return;
     }
 
     return sources[selectedLocation.sourceId];
   }
 );
 
+export function getSelectedSourceId(state: OuterState) {
+  const source = getSelectedSource((state: any));
+  return source && source.id;
+}
+
 export function getProjectDirectoryRoot(state: OuterState): string {
   return state.sources.projectDirectoryRoot;
 }
 
 function getAllDisplayedSources(state: OuterState) {
   return state.sources.displayed;
 }
 
--- a/devtools/client/debugger/new/src/utils/prefs.js
+++ b/devtools/client/debugger/new/src/utils/prefs.js
@@ -37,16 +37,17 @@ if (isDevelopment()) {
   pref("devtools.debugger.ui.editor-wrapping", false);
   pref("devtools.debugger.ui.framework-grouping-on", true);
   pref("devtools.debugger.pending-selected-location", "{}");
   pref("devtools.debugger.expressions", "[]");
   pref("devtools.debugger.file-search-case-sensitive", false);
   pref("devtools.debugger.file-search-whole-word", false);
   pref("devtools.debugger.file-search-regex-match", false);
   pref("devtools.debugger.project-directory-root", "");
+  pref("devtools.debugger.map-scopes-enabled", false);
   pref("devtools.debugger.prefs-schema-version", "1.0.1");
   pref("devtools.debugger.skip-pausing", false);
   pref("devtools.debugger.features.workers", true);
   pref("devtools.debugger.features.async-stepping", true);
   pref("devtools.debugger.features.wasm", true);
   pref("devtools.debugger.features.shortcuts", true);
   pref("devtools.debugger.features.root", true);
   pref("devtools.debugger.features.map-scopes", true);
@@ -92,17 +93,18 @@ export const prefs = new PrefsHelper("de
   tabsBlackBoxed: ["Json", "debugger.tabsBlackBoxed", []],
   pendingSelectedLocation: ["Json", "debugger.pending-selected-location", {}],
   expressions: ["Json", "debugger.expressions", []],
   fileSearchCaseSensitive: ["Bool", "debugger.file-search-case-sensitive"],
   fileSearchWholeWord: ["Bool", "debugger.file-search-whole-word"],
   fileSearchRegexMatch: ["Bool", "debugger.file-search-regex-match"],
   debuggerPrefsSchemaVersion: ["Char", "debugger.prefs-schema-version"],
   projectDirectoryRoot: ["Char", "debugger.project-directory-root", ""],
-  skipPausing: ["Bool", "debugger.skip-pausing"]
+  skipPausing: ["Bool", "debugger.skip-pausing"],
+  mapScopes: ["Bool", "debugger.map-scopes-enabled"]
 });
 
 export const features = new PrefsHelper("devtools.debugger.features", {
   asyncStepping: ["Bool", "async-stepping"],
   wasm: ["Bool", "wasm"],
   shortcuts: ["Bool", "shortcuts"],
   root: ["Bool", "root"],
   columnBreakpoints: ["Bool", "column-breakpoints"],
--- a/devtools/client/debugger/new/test/mochitest/helpers.js
+++ b/devtools/client/debugger/new/test/mochitest/helpers.js
@@ -510,16 +510,17 @@ function clearDebuggerPreferences() {
   Services.prefs.clearUserPref("devtools.debugger.pause-on-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.pause-on-caught-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.ignore-caught-exceptions");
   Services.prefs.clearUserPref("devtools.debugger.pending-selected-location");
   Services.prefs.clearUserPref("devtools.debugger.expressions");
   Services.prefs.clearUserPref("devtools.debugger.call-stack-visible");
   Services.prefs.clearUserPref("devtools.debugger.scopes-visible");
   Services.prefs.clearUserPref("devtools.debugger.skip-pausing");
+  pushPref("devtools.debugger.map-scopes-enabled", true);
 }
 
 /**
  * Intilializes the debugger.
  *
  * @memberof mochitest
  * @param {String} url
  * @return {Promise} dbg
--- a/devtools/client/locales/en-US/debugger.properties
+++ b/devtools/client/locales/en-US/debugger.properties
@@ -708,16 +708,19 @@ scopes.toggleToOriginal=Show original sc
 scopes.block=Block
 
 # LOCALIZATION NOTE (sources.header): Sources left sidebar header
 sources.header=Sources
 
 # LOCALIZATION NOTE (outline.header): Outline left sidebar header
 outline.header=Outline
 
+# LOCALIZATION NOTE (scopes.mapScopes): Label for toggling scope mappings
+scopes.mapScopes=Map Scopes
+
 # LOCALIZATION NOTE (outline.placeholder): Placeholder text for the filter input
 # element
 outline.placeholder=Filter functions
 
 # LOCALIZATION NOTE (outline.sortLabel): Label for the sort button
 outline.sortLabel=Sort by name
 
 # LOCALIZATION NOTE (outline.noFunctions): Outline text when there are no functions to display
--- a/devtools/client/preferences/debugger.js
+++ b/devtools/client/preferences/debugger.js
@@ -47,16 +47,17 @@ pref("devtools.debugger.pending-breakpoi
 pref("devtools.debugger.expressions", "[]");
 pref("devtools.debugger.event-listener-breakpoints", "[]");
 pref("devtools.debugger.file-search-case-sensitive", false);
 pref("devtools.debugger.file-search-whole-word", false);
 pref("devtools.debugger.file-search-regex-match", false);
 pref("devtools.debugger.project-directory-root", "");
 pref("devtools.debugger.skip-pausing", false);
 pref("devtools.debugger.logging", false);
+pref("devtools.debugger.map-scopes-enabled", false);
 
 pref("devtools.debugger.features.wasm", true);
 pref("devtools.debugger.features.shortcuts", true);
 pref("devtools.debugger.features.root", true);
 pref("devtools.debugger.features.column-breakpoints", false);
 pref("devtools.debugger.features.chrome-scopes", false);
 pref("devtools.debugger.features.map-scopes", true);
 pref("devtools.debugger.features.remove-command-bar-options", false);