From c4524619ee9f0ca50bda5c73505f08ce19085119 Mon Sep 17 00:00:00 2001
From: David Walsh
Date: Wed, 20 Mar 2019 19:59:20 -0500
Subject: [PATCH] Sync 130 from MC
---
assets/panel/debugger.properties | 37 ++--
assets/panel/panel.js | 11 +-
packages/devtools-components/package.json | 2 +-
packages/devtools-components/src/tree.css | 6 +-
packages/devtools-reps/package.json | 2 +-
.../src/launchpad/components/Console.css | 6 +-
.../components/ObjectInspector.css | 2 +-
packages/devtools-reps/src/reps/reps.css | 2 +-
packages/devtools-source-map/src/index.js | 6 +
.../devtools-source-map/src/source-map.js | 17 +-
packages/devtools-source-map/src/worker.js | 2 +
packages/devtools-splitter/src/SplitBox.css | 2 -
postcss.config.js | 1 +
src/actions/ast.js | 11 +-
src/actions/breakpoints/addBreakpoint.js | 4 +-
.../breakpoints/breakpointPositions.js | 25 ++-
src/actions/breakpoints/index.js | 4 +-
src/actions/breakpoints/syncBreakpoint.js | 25 ++-
src/actions/expressions.js | 22 ++-
src/actions/pause/breakOnNext.js | 2 +-
src/actions/pause/commands.js | 74 ++++---
src/actions/pause/continueToHere.js | 4 +-
src/actions/pause/fetchScopes.js | 13 +-
src/actions/pause/mapFrames.js | 18 +-
src/actions/pause/mapScopes.js | 18 +-
src/actions/pause/pauseOnExceptions.js | 8 +-
src/actions/pause/paused.js | 10 +-
src/actions/pause/resumed.js | 7 +-
src/actions/pause/selectFrame.js | 5 +-
src/actions/pause/setPopupObjectProperties.js | 6 +-
src/actions/pause/tests/pause.spec.js | 41 ++--
src/actions/preview.js | 8 +-
src/actions/sources/loadSourceText.js | 138 ++++++++-----
src/actions/sources/newSources.js | 15 +-
src/actions/sources/prettyPrint.js | 56 +++---
src/actions/sources/tests/loadSource.spec.js | 20 +-
src/actions/sources/tests/newSources.spec.js | 45 ++---
.../sources/tests/querystrings.spec.js | 38 ++++
src/actions/sources/tests/select.spec.js | 2 +
src/actions/tests/ast.spec.js | 10 +-
src/actions/tests/pending-breakpoints.spec.js | 6 +-
src/actions/tests/project-text-search.spec.js | 3 +-
src/actions/types/BreakpointAction.js | 3 +-
src/actions/types/PauseAction.js | 1 -
src/actions/types/SourceAction.js | 3 +-
src/actions/types/index.js | 22 +--
src/client/firefox/commands.js | 12 +-
src/client/firefox/types.js | 8 +-
src/client/index.js | 15 +-
src/components/App.js | 2 -
src/components/Editor/Breakpoints.css | 154 +++++++++++++++
src/components/Editor/ColumnBreakpoint.js | 3 +-
src/components/Editor/ColumnBreakpoints.js | 1 -
src/components/Editor/DebugLine.js | 5 +-
src/components/Editor/Editor.css | 91 +--------
src/components/Editor/EditorMenu.js | 8 +-
src/components/Editor/Footer.css | 3 +-
src/components/Editor/Footer.js | 68 +++----
src/components/Editor/HighlightLine.js | 5 +-
src/components/Editor/Preview/Popup.js | 10 +-
src/components/Editor/Preview/index.js | 9 +-
src/components/Editor/SearchBar.css | 2 +-
src/components/Editor/Tabs.js | 5 +-
src/components/Editor/index.js | 71 ++++---
src/components/Editor/menus/breakpoints.js | 8 +-
.../tests/__snapshots__/Editor.spec.js.snap | 2 +-
.../tests/__snapshots__/Footer.spec.js.snap | 12 +-
src/components/PrimaryPanes/Sources.css | 3 +-
src/components/PrimaryPanes/SourcesTree.js | 10 +-
.../PrimaryPanes/tests/SourcesTree.spec.js | 8 -
.../__snapshots__/SourcesTree.spec.js.snap | 26 ---
src/components/QuickOpenModal.js | 2 +-
.../SecondaryPanes/Breakpoints/Breakpoint.js | 5 +-
.../Breakpoints/Breakpoints.css | 99 ++++------
.../Breakpoints/BreakpointsContextMenu.js | 10 +-
.../SecondaryPanes/Breakpoints/index.js | 62 +++---
.../__snapshots__/Breakpoint.spec.js.snap | 10 +-
src/components/SecondaryPanes/CommandBar.js | 9 +-
.../SecondaryPanes/EventListeners.css | 64 +++---
src/components/SecondaryPanes/Expressions.css | 20 +-
src/components/SecondaryPanes/Frames/index.js | 7 +-
src/components/SecondaryPanes/Scopes.css | 33 +---
src/components/SecondaryPanes/Scopes.js | 69 ++-----
.../SecondaryPanes/SecondaryPanes.css | 5 -
src/components/SecondaryPanes/Worker.js | 4 +-
.../SecondaryPanes/XHRBreakpoints.css | 39 ++--
src/components/SecondaryPanes/index.js | 82 ++++++--
.../tests/XHRBreakpoints.spec.js | 6 +-
src/components/ShortcutsModal.js | 8 +-
src/components/WelcomeBox.css | 14 +-
src/components/WelcomeBox.js | 18 ++
src/components/shared/Accordion.css | 2 +-
src/components/shared/SearchInput.css | 17 +-
src/components/test/QuickOpenModal.spec.js | 2 +-
.../__snapshots__/ShortcutsModal.spec.js.snap | 15 +-
.../__snapshots__/WelcomeBox.spec.js.snap | 6 +
src/reducers/ast.js | 17 --
src/reducers/breakpoints.js | 39 +++-
src/reducers/expressions.js | 20 --
src/reducers/pause.js | 186 +++++++++---------
src/reducers/pending-breakpoints.js | 9 +-
src/reducers/sources.js | 84 ++++----
src/selectors/breakpointSources.js | 8 +-
src/selectors/breakpoints.js | 6 +
src/selectors/getCallStackFrames.js | 4 +-
src/selectors/inComponent.js | 5 +-
src/selectors/index.js | 6 +-
src/selectors/isSelectedFrameVisible.js | 6 +-
src/selectors/pause.js | 57 ++++++
src/selectors/visibleColumnBreakpoints.js | 25 +--
src/utils/breakpoint/breakpointPositions.js | 13 +-
src/utils/breakpoint/index.js | 2 +-
src/utils/dbg.js | 26 ++-
src/utils/editor/index.js | 18 +-
src/utils/editor/tests/editor.spec.js | 24 +++
src/utils/empty-lines.js | 30 +++
src/utils/prefs.js | 20 +-
src/utils/source-maps.js | 15 +-
src/utils/source.js | 5 -
src/utils/test-head.js | 32 ++-
src/utils/tests/empty-lines.spec.js | 33 ++++
src/workers/parser/getSymbols.js | 19 +-
test/mochitest/browser.ini | 6 +-
.../browser_dbg-breakpoints-actions.js | 33 ++--
.../mochitest/browser_dbg-breakpoints-cond.js | 2 +-
test/mochitest/browser_dbg-breakpoints.js | 20 +-
test/mochitest/browser_dbg-call-stack.js | 8 +-
test/mochitest/browser_dbg-console-eval.js | 16 +-
test/mochitest/browser_dbg-console.js | 4 +
test/mochitest/browser_dbg-debug-line.js | 2 +-
test/mochitest/browser_dbg-editor-gutter.js | 16 --
.../browser_dbg-expressions-focus.js | 12 +-
test/mochitest/browser_dbg-log-points.js | 29 +++
test/mochitest/browser_dbg-navigation.js | 4 +-
test/mochitest/browser_dbg-pause-on-next.js | 5 +-
test/mochitest/browser_dbg-pause-points.js | 6 +-
.../browser_dbg-pretty-print-breakpoints.js | 21 ++
test/mochitest/browser_dbg-react-app.js | 7 +-
.../browser_dbg-scroll-run-to-completion.js | 4 +-
.../browser_dbg-tabs-without-urls.js | 7 +
.../browser_dbg-windowless-workers.js | 12 ++
test/mochitest/examples/doc-pretty.html | 14 ++
test/mochitest/examples/pretty.js | 7 +
test/mochitest/helpers.js | 99 ++++++----
144 files changed, 1717 insertions(+), 1238 deletions(-)
create mode 100644 src/actions/sources/tests/querystrings.spec.js
create mode 100644 src/components/Editor/Breakpoints.css
create mode 100644 src/selectors/pause.js
create mode 100644 src/utils/empty-lines.js
create mode 100644 src/utils/tests/empty-lines.spec.js
create mode 100644 test/mochitest/browser_dbg-log-points.js
create mode 100644 test/mochitest/browser_dbg-pretty-print-breakpoints.js
create mode 100644 test/mochitest/examples/doc-pretty.html
create mode 100644 test/mochitest/examples/pretty.js
diff --git a/assets/panel/debugger.properties b/assets/panel/debugger.properties
index 01559d8716..8b795d2293 100644
--- a/assets/panel/debugger.properties
+++ b/assets/panel/debugger.properties
@@ -243,17 +243,11 @@ functionSearch.key=CmdOrCtrl+Shift+O
# key identifiers, not messages displayed to the user.
toggleBreakpoint.key=CmdOrCtrl+B
-# LOCALIZATION NOTE (toggleCondPanel.breakpoint.key): A key shortcut to toggle
-# the conditional panel for breakpoints.
+# LOCALIZATION NOTE (toggleCondPanel.key): A key shortcut to toggle
+# the conditional breakpoint panel.
# Do not localize "CmdOrCtrl+Shift+B", or change the format of the string. These are
# key identifiers, not messages displayed to the user.
-toggleCondPanel.breakpoint.key=CmdOrCtrl+Shift+B
-
-# LOCALIZATION NOTE (toggleCondPanel.logPoint.key): A key shortcut to toggle
-# the conditional panel for log points.
-# Do not localize "CmdOrCtrl+Shift+Y", or change the format of the string. These are
-# key identifiers, not messages displayed to the user.
-toggleCondPanel.logPoint.key=CmdOrCtrl+Shift+Y
+toggleCondPanel.key=CmdOrCtrl+Shift+B
# LOCALIZATION NOTE (stepOut.key): A key shortcut to
# step out.
@@ -697,13 +691,16 @@ scopes.notAvailable=Scopes unavailable
# for when the debugger is not paused.
scopes.notPaused=Not paused
-# LOCALIZATION NOTE (scopes.toggleToGenerated): Link displayed in the right
-# sidebar scope pane to update the view to show generated scope data.
-scopes.toggleToGenerated=Show generated scope
+# LOCALIZATION NOTE (scopes.mapping.label): Scopes right sidebar pane
+# tooltip for checkbox and label
+scopes.mapping.label=Map original variable names
-# LOCALIZATION NOTE (scopes.toggleToOriginal): Link displayed in the right
-# sidebar scope pane to update the view to show original scope data.
-scopes.toggleToOriginal=Show original scope
+# LOCALIZATION NOTE (scopes.helpTooltip.label): Scopes right sidebar pane
+# icon tooltip for link to MDN
+scopes.helpTooltip.label=Learn more about map scopes
+
+# LOCALIZATION NOTE (scopes.map.label): Checkbox label to map scopes
+scopes.map.label=Map
# LOCALIZATION NOTE (scopes.block): Refers to a block of code in
# the scopes pane when the debugger is paused.
@@ -1031,13 +1028,9 @@ anonymousFunction=
shortcuts.toggleBreakpoint=Toggle Breakpoint
shortcuts.toggleBreakpoint.accesskey=B
-# LOCALIZATION NOTE (shortcuts.toggleCondPanel.breakpoint): text describing
-# keyboard shortcut action for toggling conditional panel for breakpoints
-shortcuts.toggleCondPanel.breakpoint=Edit Conditional Breakpoint
-
-# LOCALIZATION NOTE (shortcuts.toggleCondPanel.logPoint): text describing
-# keyboard shortcut action for toggling conditional panel for log points
-shortcuts.toggleCondPanel.logPoint=Edit Log Point
+# LOCALIZATION NOTE (shortcuts.toggleCondPanel): text describing
+# keyboard shortcut action for toggling conditional panel keyboard
+shortcuts.toggleCondPanel=Toggle Conditional Panel
# LOCALIZATION NOTE (shortcuts.pauseOrResume): text describing
# keyboard shortcut action for pause of resume
diff --git a/assets/panel/panel.js b/assets/panel/panel.js
index 1367dbd2b0..34941c6bc2 100644
--- a/assets/panel/panel.js
+++ b/assets/panel/panel.js
@@ -100,7 +100,8 @@ DebuggerPanel.prototype = {
},
getFrames: function() {
- const frames = this._selectors.getFrames(this._getState());
+ const thread = this._selectors.getCurrentThread(this._getState());
+ const frames = this._selectors.getFrames(this._getState(), thread);
// Frames is null when the debugger is not paused.
if (!frames) {
@@ -110,7 +111,10 @@ DebuggerPanel.prototype = {
};
}
- const selectedFrame = this._selectors.getSelectedFrame(this._getState());
+ const selectedFrame = this._selectors.getSelectedFrame(
+ this._getState(),
+ thread
+ );
const selected = frames.findIndex(frame => frame.id == selectedFrame.id);
frames.forEach(frame => {
@@ -125,7 +129,8 @@ DebuggerPanel.prototype = {
},
isPaused() {
- return this._selectors.isPaused(this._getState());
+ const thread = this._selectors.getCurrentThread(this._getState());
+ return this._selectors.getIsPaused(this._getState(), thread);
},
selectSourceURL(url, line) {
diff --git a/packages/devtools-components/package.json b/packages/devtools-components/package.json
index 0fef689a18..dc5b0fbe0b 100644
--- a/packages/devtools-components/package.json
+++ b/packages/devtools-components/package.json
@@ -26,7 +26,7 @@
"enzyme": "^3.3.0",
"enzyme-adapter-react-16": "^1.1.1",
"eslint": "^5.0.0",
- "eslint-plugin-mozilla": "1.1.2",
+ "eslint-plugin-mozilla": "1.1.1",
"fs-extra": "^7.0.0",
"lodash": "^4.17.2"
}
diff --git a/packages/devtools-components/src/tree.css b/packages/devtools-components/src/tree.css
index 66becfd418..fa343d0ea7 100644
--- a/packages/devtools-components/src/tree.css
+++ b/packages/devtools-components/src/tree.css
@@ -2,7 +2,7 @@
* 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/. */
-/* We can remove the outline since we do add our own focus style on nodes */
+ /* We can remove the outline since we do add our own focus style on nodes */
.tree:focus {
outline: none;
}
@@ -35,7 +35,7 @@
display: inline-block;
width: 12px;
margin-inline-start: 5px;
- border-inline-start: 1px solid #a2d1ff;
+ border-inline-start: 1px solid #A2D1FF;
flex-shrink: 0;
}
@@ -47,7 +47,7 @@
/* For non expandable root nodes, we don't have .tree-indent elements, so we declare
the margin on the start of the node */
.tree-node[data-expandable="false"][aria-level="1"] {
- padding-inline-start: 15px;
+ padding-inline-start: 15px
}
.tree .tree-node[data-expandable="true"] {
diff --git a/packages/devtools-reps/package.json b/packages/devtools-reps/package.json
index 979faf5dc9..19e768e089 100644
--- a/packages/devtools-reps/package.json
+++ b/packages/devtools-reps/package.json
@@ -46,7 +46,7 @@
"enzyme-adapter-react-16": "^1.1.1",
"enzyme-to-json": "^3.3.1",
"eslint": "^5.0.0",
- "eslint-plugin-mozilla": "1.1.2",
+ "eslint-plugin-mozilla": "1.1.1",
"fs-extra": "^7.0.0",
"immutable": "^3.8.2",
"postcss-url-mapper": "^1.2.0",
diff --git a/packages/devtools-reps/src/launchpad/components/Console.css b/packages/devtools-reps/src/launchpad/components/Console.css
index 7748afecd7..c163071a76 100644
--- a/packages/devtools-reps/src/launchpad/components/Console.css
+++ b/packages/devtools-reps/src/launchpad/components/Console.css
@@ -27,7 +27,7 @@ main {
padding: 0.5rem;
}
-.rep-input::before {
+.rep-input:before {
content: "➜ ";
}
@@ -48,7 +48,7 @@ main {
}
.rep-element[data-mode]::before {
- content: attr(data-mode) ":";
+ content: attr(data-mode)":";
background-color: var(--theme-toolbar-background);
font-family: monospace;
display: inline-block;
@@ -73,7 +73,7 @@ main {
.packet header .copy-label {
margin: 0 0.5em;
padding-inline-start: 0.5em;
- border-inline-start: 1px solid rgba(0, 0, 0, 0.2);
+ border-inline-start: 1px solid rgba(0,0,0, 0.2);
}
.packet header.packet-expanded::before {
diff --git a/packages/devtools-reps/src/object-inspector/components/ObjectInspector.css b/packages/devtools-reps/src/object-inspector/components/ObjectInspector.css
index cd312a0203..fc1fc8d71b 100644
--- a/packages/devtools-reps/src/object-inspector/components/ObjectInspector.css
+++ b/packages/devtools-reps/src/object-inspector/components/ObjectInspector.css
@@ -27,7 +27,7 @@
color: var(--theme-body-color);
}
-.tree.object-inspector .block .object-label::before {
+.tree.object-inspector .block .object-label:before {
content: "☲ ";
font-size: 1.1em;
}
diff --git a/packages/devtools-reps/src/reps/reps.css b/packages/devtools-reps/src/reps/reps.css
index 5d9c9a8d82..3d4d4bd7bb 100644
--- a/packages/devtools-reps/src/reps/reps.css
+++ b/packages/devtools-reps/src/reps/reps.css
@@ -288,7 +288,7 @@ button.invoke-getter {
background-color: var(--theme-icon-color);
height: 10px;
vertical-align: middle;
- border: none;
+ border:none;
}
.invoke-getter:hover {
diff --git a/packages/devtools-source-map/src/index.js b/packages/devtools-source-map/src/index.js
index eaa752fe5e..b87d2c12ba 100644
--- a/packages/devtools-source-map/src/index.js
+++ b/packages/devtools-source-map/src/index.js
@@ -77,6 +77,12 @@ export const getOriginalLocation = async (
options: locationOptions = {}
): Promise => _getOriginalLocation(location, options);
+export const getOriginalLocations = async (
+ locations: SourceLocation[],
+ options: locationOptions = {}
+): Promise =>
+ dispatcher.invoke("getOriginalLocations", locations, options);
+
export const getGeneratedRangesForOriginal = async (
sourceId: SourceId,
url: string,
diff --git a/packages/devtools-source-map/src/source-map.js b/packages/devtools-source-map/src/source-map.js
index 2a5a4dca20..7cf098e59b 100644
--- a/packages/devtools-source-map/src/source-map.js
+++ b/packages/devtools-source-map/src/source-map.js
@@ -46,6 +46,10 @@ type Range = {
}
};
+export type locationOptions = {
+ search?: "LEAST_UPPER_BOUND" | "GREATEST_LOWER_BOUND"
+};
+
async function getOriginalURLs(
generatedSource: Source
): Promise {
@@ -257,9 +261,15 @@ async function getAllGeneratedLocations(
}));
}
-export type locationOptions = {
- search?: "LEAST_UPPER_BOUND" | "GREATEST_LOWER_BOUND"
-};
+function getOriginalLocations(
+ locations: SourceLocation[],
+ options: locationOptions = {}
+) {
+ return Promise.all(
+ locations.map(location => getOriginalLocation(location, options))
+ );
+}
+
async function getOriginalLocation(
location: SourceLocation,
{ search }: locationOptions = {}
@@ -546,6 +556,7 @@ module.exports = {
getGeneratedLocation,
getAllGeneratedLocations,
getOriginalLocation,
+ getOriginalLocations,
getOriginalSourceText,
getGeneratedRangesForOriginal,
getFileGeneratedRange,
diff --git a/packages/devtools-source-map/src/worker.js b/packages/devtools-source-map/src/worker.js
index 1bd5f4b7cd..7308f8ae3e 100644
--- a/packages/devtools-source-map/src/worker.js
+++ b/packages/devtools-source-map/src/worker.js
@@ -11,6 +11,7 @@ const {
getGeneratedLocation,
getAllGeneratedLocations,
getOriginalLocation,
+ getOriginalLocations,
getOriginalSourceText,
getGeneratedRangesForOriginal,
getFileGeneratedRange,
@@ -37,6 +38,7 @@ self.onmessage = workerHandler({
getGeneratedLocation,
getAllGeneratedLocations,
getOriginalLocation,
+ getOriginalLocations,
getOriginalSourceText,
getOriginalStackFrames,
getGeneratedRangesForOriginal,
diff --git a/packages/devtools-splitter/src/SplitBox.css b/packages/devtools-splitter/src/SplitBox.css
index 42c42d257b..ecfbbe1b26 100644
--- a/packages/devtools-splitter/src/SplitBox.css
+++ b/packages/devtools-splitter/src/SplitBox.css
@@ -48,7 +48,6 @@
}
.split-box.vert > .splitter {
- /* prettier-ignore */
min-width: calc(var(--devtools-splitter-inline-start-width) +
var(--devtools-splitter-inline-end-width) + 1px);
@@ -62,7 +61,6 @@
}
.split-box.horz > .splitter {
- /* prettier-ignore */
min-height: calc(var(--devtools-splitter-top-width) +
var(--devtools-splitter-bottom-width) + 1px);
border-top-width: var(--devtools-splitter-top-width);
diff --git a/postcss.config.js b/postcss.config.js
index 95b4bab2e5..581fa131ce 100644
--- a/postcss.config.js
+++ b/postcss.config.js
@@ -34,6 +34,7 @@ module.exports = ({ file, options, env }) => {
return {
plugins: [
+ require("postcss-bidirection"),
require("autoprefixer")({
browsers: ["last 2 Firefox versions", "last 2 Chrome versions"],
flexbox: false,
diff --git a/src/actions/ast.js b/src/actions/ast.js
index a3a7e8c17e..01d42754fa 100644
--- a/src/actions/ast.js
+++ b/src/actions/ast.js
@@ -7,9 +7,9 @@
import {
getSource,
getSourceFromId,
+ getSourceThreads,
getSymbols,
- getSelectedLocation,
- isPaused
+ getSelectedLocation
} from "../selectors";
import { mapFrames } from "./pause";
@@ -64,9 +64,8 @@ export function setSymbols(sourceId: SourceId) {
[PROMISE]: parser.getSymbols(sourceId)
});
- if (isPaused(getState())) {
- await dispatch(mapFrames());
- }
+ const threads = getSourceThreads(getState(), source);
+ await Promise.all(threads.map(thread => dispatch(mapFrames(thread))));
await dispatch(setSourceMetaData(sourceId));
};
@@ -86,7 +85,7 @@ export function setOutOfScopeLocations() {
}
let locations = null;
- if (location.line && source && !source.isWasm && isPaused(getState())) {
+ if (location.line && source && !source.isWasm) {
locations = await parser.findOutOfScopeLocations(
source.id,
((location: any): parser.AstPosition)
diff --git a/src/actions/breakpoints/addBreakpoint.js b/src/actions/breakpoints/addBreakpoint.js
index cf6a92cd71..41f6ecb63a 100644
--- a/src/actions/breakpoints/addBreakpoint.js
+++ b/src/actions/breakpoints/addBreakpoint.js
@@ -16,7 +16,7 @@ import {
import { PROMISE } from "../utils/middleware/promise";
import {
getSymbols,
- getFirstVisibleBreakpointPosition,
+ getFirstBreakpointPosition,
getBreakpointPositionsForSource,
getSourceFromId
} from "../../selectors";
@@ -105,7 +105,7 @@ export function addBreakpoint(
const { sourceId, column } = location;
if (column === undefined) {
- position = getFirstVisibleBreakpointPosition(getState(), location);
+ position = getFirstBreakpointPosition(getState(), location);
} else {
const positions = getBreakpointPositionsForSource(getState(), sourceId);
position = findPosition(positions, location);
diff --git a/src/actions/breakpoints/breakpointPositions.js b/src/actions/breakpoints/breakpointPositions.js
index 9e1f9aa9fd..760fba6dd3 100644
--- a/src/actions/breakpoints/breakpointPositions.js
+++ b/src/actions/breakpoints/breakpointPositions.js
@@ -5,7 +5,7 @@
// @flow
import { isOriginalId, originalToGeneratedId } from "devtools-source-map";
-import { uniqBy } from "lodash";
+import { uniqBy, zip } from "lodash";
import {
getSource,
@@ -16,7 +16,6 @@ import {
import type { MappedLocation, SourceLocation } from "../../types";
import type { ThunkArgs } from "../../actions/types";
-import { getOriginalLocation } from "../../utils/source-maps";
import { makeBreakpointId } from "../../utils/breakpoint";
import typeof SourceMaps from "../../../packages/devtools-source-map/src";
@@ -26,12 +25,12 @@ async function mapLocations(
generatedLocations: SourceLocation[],
{ sourceMaps }: { sourceMaps: SourceMaps }
) {
- return Promise.all(
- (generatedLocations: any).map(async (generatedLocation: SourceLocation) => {
- const location = await getOriginalLocation(generatedLocation, sourceMaps);
+ const originalLocations = await sourceMaps.getOriginalLocations(
+ generatedLocations
+ );
- return { location, generatedLocation };
- })
+ return zip(originalLocations, generatedLocations).map(
+ ([location, generatedLocation]) => ({ location, generatedLocation })
);
}
@@ -98,7 +97,17 @@ async function _setBreakpointPositions(sourceId, thunkArgs) {
let positions = convertToList(results, generatedSource);
positions = await mapLocations(positions, thunkArgs);
positions = filterByUniqLocation(positions);
- dispatch({ type: "ADD_BREAKPOINT_POSITIONS", sourceId, positions });
+
+ const source = getSource(getState(), sourceId);
+ // NOTE: it's possible that the source was removed during a navigate
+ if (!source) {
+ return;
+ }
+ dispatch({
+ type: "ADD_BREAKPOINT_POSITIONS",
+ source: source,
+ positions
+ });
}
export function setBreakpointPositions(sourceId: string) {
diff --git a/src/actions/breakpoints/index.js b/src/actions/breakpoints/index.js
index 444203c789..00ba53032c 100644
--- a/src/actions/breakpoints/index.js
+++ b/src/actions/breakpoints/index.js
@@ -17,7 +17,8 @@ import {
getSelectedSource,
getBreakpointAtLocation,
getConditionalPanelLocation,
- getBreakpointsForSource
+ getBreakpointsForSource,
+ isEmptyLineInSource
} from "../../selectors";
import {
assertBreakpoint,
@@ -32,7 +33,6 @@ import {
import remapLocations from "./remapLocations";
import { syncBreakpoint } from "./syncBreakpoint";
import { closeConditionalPanel } from "../ui";
-import { isEmptyLineInSource } from "../../reducers/ast";
// this will need to be changed so that addCLientBreakpoint is removed
diff --git a/src/actions/breakpoints/syncBreakpoint.js b/src/actions/breakpoints/syncBreakpoint.js
index f9cc75baf9..50f3158928 100644
--- a/src/actions/breakpoints/syncBreakpoint.js
+++ b/src/actions/breakpoints/syncBreakpoint.js
@@ -18,7 +18,8 @@ import { getTextAtPosition } from "../../utils/source";
import { comparePosition } from "../../utils/location";
import { originalToGeneratedId, isOriginalId } from "devtools-source-map";
-import { getSource } from "../../selectors";
+import { getSource, getBreakpointsList } from "../../selectors";
+import { removeBreakpoint } from ".";
import type { ThunkArgs, Action } from "../types";
@@ -87,6 +88,19 @@ function createSyncData(
return { breakpoint, previousLocation };
}
+// Look for an existing breakpoint at the specified generated location.
+function findExistingBreakpoint(state, generatedLocation) {
+ const breakpoints = getBreakpointsList(state);
+
+ return breakpoints.find(bp => {
+ return (
+ bp.generatedLocation.sourceUrl == generatedLocation.sourceUrl &&
+ bp.generatedLocation.line == generatedLocation.line &&
+ bp.generatedLocation.column == generatedLocation.column
+ );
+ });
+}
+
// we have three forms of syncing: disabled syncing, existing server syncing
// and adding a new breakpoint
export async function syncBreakpointPromise(
@@ -94,7 +108,7 @@ export async function syncBreakpointPromise(
sourceId: SourceId,
pendingBreakpoint: PendingBreakpoint
): Promise {
- const { getState, client } = thunkArgs;
+ const { getState, client, dispatch } = thunkArgs;
assertPendingBreakpoint(pendingBreakpoint);
const source = getSource(getState(), sourceId);
@@ -152,8 +166,11 @@ export async function syncBreakpointPromise(
);
}
- // clear server breakpoints if they exist and we have moved
- await client.removeBreakpoint(generatedLocation);
+ // Clear any breakpoint for the generated location.
+ const bp = findExistingBreakpoint(getState(), generatedLocation);
+ if (bp) {
+ await dispatch(removeBreakpoint(bp));
+ }
if (!newGeneratedLocation) {
return { previousLocation, breakpoint: null };
diff --git a/src/actions/expressions.js b/src/actions/expressions.js
index e1e4b094b0..396545d158 100644
--- a/src/actions/expressions.js
+++ b/src/actions/expressions.js
@@ -5,7 +5,6 @@
// @flow
import {
- getCurrentThread,
getExpression,
getExpressions,
getSelectedFrame,
@@ -14,7 +13,8 @@ import {
getSelectedSource,
getSelectedScopeMappings,
getSelectedFrameBindings,
- isPaused
+ getCurrentThread,
+ getIsPaused
} from "../selectors";
import { PROMISE } from "./utils/middleware/promise";
import { wrapExpression } from "../utils/expressions";
@@ -60,7 +60,8 @@ export function autocomplete(input: string, cursor: number) {
if (!input) {
return;
}
- const frameId = getSelectedFrameId(getState());
+ const thread = getCurrentThread(getState());
+ const frameId = getSelectedFrameId(getState(), thread);
const result = await client.autocomplete(input, cursor, frameId);
await dispatch({ type: "AUTOCOMPLETE", input, result });
};
@@ -118,8 +119,8 @@ export function evaluateExpressions() {
return async function({ dispatch, getState, client }: ThunkArgs) {
const expressions = getExpressions(getState()).toJS();
const inputs = expressions.map(({ input }) => input);
- const frameId = getSelectedFrameId(getState());
const thread = getCurrentThread(getState());
+ const frameId = getSelectedFrameId(getState(), thread);
const results = await client.evaluateExpressions(inputs, {
frameId,
thread
@@ -136,7 +137,8 @@ function evaluateExpression(expression: Expression) {
}
let input = expression.input;
- const frame = getSelectedFrame(getState());
+ const thread = getCurrentThread(getState());
+ const frame = getSelectedFrame(getState(), thread);
if (frame) {
const { location } = frame;
@@ -152,8 +154,7 @@ function evaluateExpression(expression: Expression) {
}
}
- const frameId = getSelectedFrameId(getState());
- const thread = getCurrentThread(getState());
+ const frameId = getSelectedFrameId(getState(), thread);
return dispatch({
type: "EVALUATE_EXPRESSION",
@@ -174,8 +175,9 @@ function evaluateExpression(expression: Expression) {
export function getMappedExpression(expression: string) {
return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
const state = getState();
- const mappings = getSelectedScopeMappings(state);
- const bindings = getSelectedFrameBindings(state);
+ const thread = getCurrentThread(getState());
+ const mappings = getSelectedScopeMappings(state, thread);
+ const bindings = getSelectedFrameBindings(state, thread);
// We bail early if we do not need to map the expression. This is important
// because mapping an expression can be slow if the parser worker is
@@ -192,7 +194,7 @@ export function getMappedExpression(expression: string) {
expression,
mappings,
bindings || [],
- features.mapExpressionBindings && isPaused(state),
+ features.mapExpressionBindings && getIsPaused(state, thread),
features.mapAwaitExpression
);
};
diff --git a/src/actions/pause/breakOnNext.js b/src/actions/pause/breakOnNext.js
index 70b4a7c328..8f5bcbefff 100644
--- a/src/actions/pause/breakOnNext.js
+++ b/src/actions/pause/breakOnNext.js
@@ -4,8 +4,8 @@
// @flow
-import type { ThunkArgs } from "../types";
import { getCurrentThread } from "../../selectors";
+import type { ThunkArgs } from "../types";
/**
* Debugger breakOnNext command.
diff --git a/src/actions/pause/commands.js b/src/actions/pause/commands.js
index 38bd3ff5ed..8971eb5549 100644
--- a/src/actions/pause/commands.js
+++ b/src/actions/pause/commands.js
@@ -6,23 +6,34 @@
// @flow
import {
- isPaused,
+ getIsPaused,
getCurrentThread,
getSource,
- getTopFrame
+ getTopFrame,
+ getSelectedFrame
} from "../../selectors";
import { PROMISE } from "../utils/middleware/promise";
import { getNextStep } from "../../workers/parser";
import { addHiddenBreakpoint } from "../breakpoints";
+import { evaluateExpressions } from "../expressions";
+import { selectLocation } from "../sources";
import { features } from "../../utils/prefs";
import { recordEvent } from "../../utils/telemetry";
-import type { Source } from "../../types";
+import type { Source, ThreadId } from "../../types";
import type { ThunkArgs } from "../types";
import type { Command } from "../../reducers/types";
-export function selectThread(thread: string) {
- return { type: "SELECT_THREAD", thread };
+export function selectThread(thread: ThreadId) {
+ return async ({ dispatch, getState, client }: ThunkArgs) => {
+ await dispatch({ type: "SELECT_THREAD", thread });
+ dispatch(evaluateExpressions());
+
+ const frame = getSelectedFrame(getState(), thread);
+ if (frame) {
+ dispatch(selectLocation(frame.location));
+ }
+ };
}
/**
@@ -32,10 +43,9 @@ export function selectThread(thread: string) {
* @memberof actions/pause
* @static
*/
-export function command(type: Command) {
+export function command(thread: ThreadId, type: Command) {
return async ({ dispatch, getState, client }: ThunkArgs) => {
if (type) {
- const thread = getCurrentThread(getState());
return dispatch({
type: "COMMAND",
command: type,
@@ -54,8 +64,9 @@ export function command(type: Command) {
*/
export function stepIn() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(command("stepIn"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(command(thread, "stepIn"));
}
};
}
@@ -68,8 +79,9 @@ export function stepIn() {
*/
export function stepOver() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(astCommand("stepOver"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(astCommand(thread, "stepOver"));
}
};
}
@@ -82,8 +94,9 @@ export function stepOver() {
*/
export function stepOut() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(command("stepOut"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(command(thread, "stepOut"));
}
};
}
@@ -96,9 +109,10 @@ export function stepOut() {
*/
export function resume() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
recordEvent("continue");
- return dispatch(command("resume"));
+ return dispatch(command(thread, "resume"));
}
};
}
@@ -111,8 +125,9 @@ export function resume() {
*/
export function rewind() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(command("rewind"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(command(thread, "rewind"));
}
};
}
@@ -125,8 +140,9 @@ export function rewind() {
*/
export function reverseStepIn() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(command("reverseStepIn"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(command(thread, "reverseStepIn"));
}
};
}
@@ -139,8 +155,9 @@ export function reverseStepIn() {
*/
export function reverseStepOver() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(astCommand("reverseStepOver"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(astCommand(thread, "reverseStepOver"));
}
};
}
@@ -153,8 +170,9 @@ export function reverseStepOver() {
*/
export function reverseStepOut() {
return ({ dispatch, getState }: ThunkArgs) => {
- if (isPaused(getState())) {
- return dispatch(command("reverseStepOut"));
+ const thread = getCurrentThread(getState());
+ if (getIsPaused(getState(), thread)) {
+ return dispatch(command(thread, "reverseStepOut"));
}
};
}
@@ -187,26 +205,26 @@ function hasAwait(source: Source, pauseLocation) {
* @param stepType
* @returns {function(ThunkArgs)}
*/
-export function astCommand(stepType: Command) {
+export function astCommand(thread: ThreadId, stepType: Command) {
return async ({ dispatch, getState, sourceMaps }: ThunkArgs) => {
if (!features.asyncStepping) {
- return dispatch(command(stepType));
+ return dispatch(command(thread, stepType));
}
if (stepType == "stepOver") {
// This type definition is ambiguous:
- const frame: any = getTopFrame(getState());
+ const frame: any = getTopFrame(getState(), thread);
const source = getSource(getState(), frame.location.sourceId);
if (source && hasAwait(source, frame.location)) {
const nextLocation = await getNextStep(source.id, frame.location);
if (nextLocation) {
await dispatch(addHiddenBreakpoint(nextLocation));
- return dispatch(command("resume"));
+ return dispatch(command(thread, "resume"));
}
}
}
- return dispatch(command(stepType));
+ return dispatch(command(thread, stepType));
};
}
diff --git a/src/actions/pause/continueToHere.js b/src/actions/pause/continueToHere.js
index 7e8813aba1..0122726d8a 100644
--- a/src/actions/pause/continueToHere.js
+++ b/src/actions/pause/continueToHere.js
@@ -5,6 +5,7 @@
// @flow
import {
+ getCurrentThread,
getSelectedSource,
getSelectedFrame,
getCanRewind
@@ -16,8 +17,9 @@ import type { ThunkArgs } from "../types";
export function continueToHere(line: number, column?: number) {
return async function({ dispatch, getState }: ThunkArgs) {
+ const thread = getCurrentThread(getState());
const selectedSource = getSelectedSource(getState());
- const selectedFrame = getSelectedFrame(getState());
+ const selectedFrame = getSelectedFrame(getState(), thread);
if (!selectedFrame || !selectedSource) {
return;
diff --git a/src/actions/pause/fetchScopes.js b/src/actions/pause/fetchScopes.js
index d5820914ae..0661c9eca4 100644
--- a/src/actions/pause/fetchScopes.js
+++ b/src/actions/pause/fetchScopes.js
@@ -4,25 +4,22 @@
// @flow
-import {
- getCurrentThread,
- getSelectedFrame,
- getGeneratedFrameScope
-} from "../../selectors";
+import { getSelectedFrame, getGeneratedFrameScope } from "../../selectors";
import { mapScopes } from "./mapScopes";
import { PROMISE } from "../utils/middleware/promise";
+import type { ThreadId } from "../../types";
import type { ThunkArgs } from "../types";
-export function fetchScopes() {
+export function fetchScopes(thread: ThreadId) {
return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) {
- const frame = getSelectedFrame(getState());
+ const frame = getSelectedFrame(getState(), thread);
if (!frame || getGeneratedFrameScope(getState(), frame.id)) {
return;
}
const scopes = dispatch({
type: "ADD_SCOPES",
- thread: getCurrentThread(getState()),
+ thread,
frame,
[PROMISE]: client.getFrameScopes(frame)
});
diff --git a/src/actions/pause/mapFrames.js b/src/actions/pause/mapFrames.js
index 3ef975693c..26c437a3e8 100644
--- a/src/actions/pause/mapFrames.js
+++ b/src/actions/pause/mapFrames.js
@@ -5,7 +5,6 @@
// @flow
import {
- getCurrentThread,
getFrames,
getSymbols,
getSource,
@@ -15,7 +14,7 @@ import {
import assert from "../../utils/assert";
import { findClosestFunction } from "../../utils/ast";
-import type { Frame } from "../../types";
+import type { Frame, ThreadId } from "../../types";
import type { State } from "../../reducers/types";
import type { ThunkArgs } from "../types";
@@ -26,8 +25,8 @@ function isFrameBlackboxed(state, frame) {
return source && source.isBlackBoxed;
}
-function getSelectedFrameId(state, frames) {
- let selectedFrame = getSelectedFrame(state);
+function getSelectedFrameId(state, thread, frames) {
+ let selectedFrame = getSelectedFrame(state, thread);
if (selectedFrame && !isFrameBlackboxed(state, selectedFrame)) {
return selectedFrame.id;
}
@@ -162,9 +161,9 @@ async function expandFrames(
* @memberof actions/pause
* @static
*/
-export function mapFrames() {
+export function mapFrames(thread: ThreadId) {
return async function({ dispatch, getState, sourceMaps }: ThunkArgs) {
- const frames = getFrames(getState());
+ const frames = getFrames(getState(), thread);
if (!frames) {
return;
}
@@ -173,8 +172,11 @@ export function mapFrames() {
mappedFrames = await expandFrames(mappedFrames, sourceMaps, getState);
mappedFrames = mapDisplayNames(mappedFrames, getState);
- const thread = getCurrentThread(getState());
- const selectedFrameId = getSelectedFrameId(getState(), mappedFrames);
+ const selectedFrameId = getSelectedFrameId(
+ getState(),
+ thread,
+ mappedFrames
+ );
dispatch({
type: "MAP_FRAMES",
thread,
diff --git a/src/actions/pause/mapScopes.js b/src/actions/pause/mapScopes.js
index c9f1cdeb79..31428992fc 100644
--- a/src/actions/pause/mapScopes.js
+++ b/src/actions/pause/mapScopes.js
@@ -5,19 +5,19 @@
// @flow
import {
- getCurrentThread,
getSource,
getMapScopes,
getSelectedFrame,
getSelectedGeneratedScope,
- getSelectedOriginalScope
+ getSelectedOriginalScope,
+ getCurrentThread
} 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 { isGenerated, isOriginal } from "../../utils/source";
import type { Frame, Scope } from "../../types";
import type { ThunkArgs } from "../types";
@@ -32,12 +32,13 @@ export function toggleMapScopes() {
dispatch({ type: "TOGGLE_MAP_SCOPES", mapScopes: true });
- if (getSelectedOriginalScope(getState())) {
+ const thread = getCurrentThread(getState());
+ if (getSelectedOriginalScope(getState(), thread)) {
return;
}
- const scopes = getSelectedGeneratedScope(getState());
- const frame = getSelectedFrame(getState());
+ const scopes = getSelectedGeneratedScope(getState(), thread);
+ const frame = getSelectedFrame(getState(), thread);
if (!scopes || !frame) {
return;
}
@@ -57,7 +58,7 @@ export function mapScopes(scopes: Promise, frame: Frame) {
await dispatch({
type: "MAP_SCOPES",
- thread: getCurrentThread(getState()),
+ thread: frame.thread,
frame,
[PROMISE]: (async function() {
if (
@@ -72,6 +73,9 @@ export function mapScopes(scopes: Promise, frame: Frame) {
}
await dispatch(loadSourceText(source));
+ if (isOriginal(source)) {
+ await dispatch(loadSourceText(generatedSource));
+ }
try {
return await buildMappedScopes(
diff --git a/src/actions/pause/pauseOnExceptions.js b/src/actions/pause/pauseOnExceptions.js
index 63728a13e3..d217d5461d 100644
--- a/src/actions/pause/pauseOnExceptions.js
+++ b/src/actions/pause/pauseOnExceptions.js
@@ -7,7 +7,6 @@
import { PROMISE } from "../utils/middleware/promise";
import { recordEvent } from "../../utils/telemetry";
import type { ThunkArgs } from "../types";
-import { getCurrentThread } from "../../selectors";
/**
*
@@ -19,22 +18,17 @@ export function pauseOnExceptions(
shouldPauseOnCaughtExceptions: boolean
) {
return ({ dispatch, getState, client }: ThunkArgs) => {
- /* eslint-disable camelcase */
recordEvent("pause_on_exceptions", {
exceptions: shouldPauseOnExceptions,
// There's no "n" in the key below (#1463117)
- caught_exceptio: shouldPauseOnCaughtExceptions
+ ["caught_exceptio"]: shouldPauseOnCaughtExceptions
});
- /* eslint-enable camelcase */
- const thread = getCurrentThread(getState());
return dispatch({
type: "PAUSE_ON_EXCEPTIONS",
- thread,
shouldPauseOnExceptions,
shouldPauseOnCaughtExceptions,
[PROMISE]: client.pauseOnExceptions(
- thread,
shouldPauseOnExceptions,
shouldPauseOnCaughtExceptions
)
diff --git a/src/actions/pause/paused.js b/src/actions/pause/paused.js
index 575175f167..1b330edea3 100644
--- a/src/actions/pause/paused.js
+++ b/src/actions/pause/paused.js
@@ -47,23 +47,23 @@ export function paused(pauseInfo: Pause) {
dispatch(removeBreakpoint(hiddenBreakpoint));
}
- await dispatch(mapFrames());
+ await dispatch(mapFrames(thread));
- const selectedFrame = getSelectedFrame(getState());
+ const selectedFrame = getSelectedFrame(getState(), thread);
if (selectedFrame) {
await dispatch(selectLocation(selectedFrame.location));
}
- if (!wasStepping(getState())) {
+ if (!wasStepping(getState(), thread)) {
dispatch(togglePaneCollapse("end", false));
}
- await dispatch(fetchScopes());
+ await dispatch(fetchScopes(thread));
// Run after fetching scoping data so that it may make use of the sourcemap
// expression mappings for local variables.
const atException = why.type == "exception";
- if (!atException || !isEvaluatingExpression(getState())) {
+ if (!atException || !isEvaluatingExpression(getState(), thread)) {
await dispatch(evaluateExpressions());
}
};
diff --git a/src/actions/pause/resumed.js b/src/actions/pause/resumed.js
index 2ab468cc5a..56e6ddbbd9 100644
--- a/src/actions/pause/resumed.js
+++ b/src/actions/pause/resumed.js
@@ -19,11 +19,12 @@ import type { ResumedPacket } from "../../client/firefox/types";
*/
export function resumed(packet: ResumedPacket) {
return async ({ dispatch, client, getState }: ThunkArgs) => {
- const why = getPauseReason(getState());
+ const thread = packet.from;
+ const why = getPauseReason(getState(), thread);
const wasPausedInEval = inDebuggerEval(why);
- const wasStepping = isStepping(getState());
+ const wasStepping = isStepping(getState(), thread);
- dispatch({ type: "RESUME", thread: packet.from, wasStepping });
+ dispatch({ type: "RESUME", thread, wasStepping });
if (!wasStepping && !wasPausedInEval) {
await dispatch(evaluateExpressions());
diff --git a/src/actions/pause/selectFrame.js b/src/actions/pause/selectFrame.js
index 0483c6cdb6..103dd94a3e 100644
--- a/src/actions/pause/selectFrame.js
+++ b/src/actions/pause/selectFrame.js
@@ -7,7 +7,6 @@
import { selectLocation } from "../sources";
import { evaluateExpressions } from "../expressions";
import { fetchScopes } from "./fetchScopes";
-import { getCurrentThread } from "../../selectors";
import type { Frame } from "../../types";
import type { ThunkArgs } from "../types";
@@ -20,13 +19,13 @@ export function selectFrame(frame: Frame) {
return async ({ dispatch, client, getState, sourceMaps }: ThunkArgs) => {
dispatch({
type: "SELECT_FRAME",
- thread: getCurrentThread(getState()),
+ thread: frame.thread,
frame
});
dispatch(selectLocation(frame.location));
dispatch(evaluateExpressions());
- dispatch(fetchScopes());
+ dispatch(fetchScopes(frame.thread));
};
}
diff --git a/src/actions/pause/setPopupObjectProperties.js b/src/actions/pause/setPopupObjectProperties.js
index 9cffee228b..e3de7a0eeb 100644
--- a/src/actions/pause/setPopupObjectProperties.js
+++ b/src/actions/pause/setPopupObjectProperties.js
@@ -4,7 +4,7 @@
// @flow
-import { getCurrentThread, getPopupObjectProperties } from "../../selectors";
+import { getPopupObjectProperties, getCurrentThread } from "../../selectors";
import type { ThunkArgs } from "../types";
/**
@@ -14,12 +14,12 @@ import type { ThunkArgs } from "../types";
export function setPopupObjectProperties(object: any, properties: Object) {
return ({ dispatch, client, getState }: ThunkArgs) => {
const objectId = object.actor || object.objectId;
+ const thread = getCurrentThread(getState());
- if (getPopupObjectProperties(getState(), object.actor)) {
+ if (getPopupObjectProperties(getState(), thread, object.actor)) {
return;
}
- const thread = getCurrentThread(getState());
dispatch({
type: "SET_POPUP_OBJECT_PROPERTIES",
thread,
diff --git a/src/actions/pause/tests/pause.spec.js b/src/actions/pause/tests/pause.spec.js
index 0a6dd76ed4..a8f9b2c651 100644
--- a/src/actions/pause/tests/pause.spec.js
+++ b/src/actions/pause/tests/pause.spec.js
@@ -110,13 +110,13 @@ describe("pause", () => {
await dispatch(actions.newSource(makeSource("foo1")));
await dispatch(actions.paused(mockPauseInfo));
const stepped = dispatch(actions.stepIn());
- expect(isStepping(getState())).toBeTruthy();
+ expect(isStepping(getState(), "FakeThread")).toBeTruthy();
if (!stepInResolve) {
throw new Error("no stepInResolve");
}
await stepInResolve();
await stepped;
- expect(isStepping(getState())).toBeFalsy();
+ expect(isStepping(getState(), "FakeThread")).toBeFalsy();
});
it("should only step when paused", async () => {
@@ -134,7 +134,7 @@ describe("pause", () => {
await dispatch(actions.newSource(makeSource("foo1")));
await dispatch(actions.paused(mockPauseInfo));
dispatch(actions.stepIn());
- expect(isStepping(getState())).toBeTruthy();
+ expect(isStepping(getState(), "FakeThread")).toBeTruthy();
});
it("should step over when paused", async () => {
@@ -147,7 +147,7 @@ describe("pause", () => {
const getNextStepSpy = jest.spyOn(parser, "getNextStep");
dispatch(actions.stepOver());
expect(getNextStepSpy).not.toBeCalled();
- expect(isStepping(getState())).toBeTruthy();
+ expect(isStepping(getState(), "FakeThread")).toBeTruthy();
});
it("should step over when paused before an await", async () => {
@@ -211,18 +211,19 @@ describe("pause", () => {
await dispatch(actions.loadSourceText(source));
await dispatch(actions.paused(mockPauseInfo));
- expect(selectors.getFrames(getState())).toEqual([
+ expect(selectors.getFrames(getState(), "FakeThread")).toEqual([
{
generatedLocation: { column: 0, line: 1, sourceId: "foo" },
id: mockFrameId,
location: { column: 0, line: 1, sourceId: "foo" },
scope: {
bindings: { arguments: [{ a: {} }], variables: { b: {} } }
- }
+ },
+ thread: "FakeThread"
}
]);
- expect(selectors.getFrameScopes(getState())).toEqual({
+ expect(selectors.getFrameScopes(getState(), "FakeThread")).toEqual({
generated: {
"1": {
pending: false,
@@ -235,10 +236,9 @@ describe("pause", () => {
original: { "1": { pending: false, scope: null } }
});
- expect(selectors.getSelectedFrameBindings(getState())).toEqual([
- "b",
- "a"
- ]);
+ expect(
+ selectors.getSelectedFrameBindings(getState(), "FakeThread")
+ ).toEqual(["b", "a"]);
});
it("maps frame locations and names to original source", async () => {
@@ -256,6 +256,7 @@ describe("pause", () => {
const sourceMapsMock = {
getOriginalLocation: () => Promise.resolve(originalLocation),
+ getOriginalLocations: async items => items,
getOriginalSourceText: async () => ({
source: "\n\nfunction fooOriginal() {\n return -5;\n}",
contentType: "text/javascript"
@@ -276,13 +277,14 @@ describe("pause", () => {
await dispatch(actions.setSymbols("foo-original"));
await dispatch(actions.paused(mockPauseInfo));
- expect(selectors.getFrames(getState())).toEqual([
+ expect(selectors.getFrames(getState(), "FakeThread")).toEqual([
{
generatedLocation: { column: 0, line: 1, sourceId: "foo" },
id: mockFrameId,
location: { column: 0, line: 3, sourceId: "foo-original" },
originalDisplayName: "fooOriginal",
- scope: { bindings: { arguments: [], variables: {} } }
+ scope: { bindings: { arguments: [], variables: {} } },
+ thread: "FakeThread"
}
]);
});
@@ -307,17 +309,20 @@ describe("pause", () => {
const originStackFrames = [
{
- displayName: "fooBar"
+ displayName: "fooBar",
+ thread: "FakeThread"
},
{
displayName: "barZoo",
- location: originalLocation2
+ location: originalLocation2,
+ thread: "FakeThread"
}
];
const sourceMapsMock = {
getOriginalStackFrames: loc => Promise.resolve(originStackFrames),
getOriginalLocation: () => Promise.resolve(originalLocation),
+ getOriginalLocations: async items => items,
getOriginalSourceText: async () => ({
source: "fn fooBar() {}\nfn barZoo() { fooBar() }",
contentType: "text/rust"
@@ -338,7 +343,7 @@ describe("pause", () => {
await dispatch(actions.loadSourceText(originalSource));
await dispatch(actions.paused(mockPauseInfo));
- expect(selectors.getFrames(getState())).toEqual([
+ expect(selectors.getFrames(getState(), "FakeThread")).toEqual([
{
displayName: "fooBar",
generatedLocation: { column: 0, line: 1, sourceId: "foo-wasm" },
@@ -349,7 +354,7 @@ describe("pause", () => {
scope: { bindings: { arguments: [], variables: {} } },
source: null,
this: undefined,
- thread: undefined
+ thread: "FakeThread"
},
{
displayName: "barZoo",
@@ -361,7 +366,7 @@ describe("pause", () => {
scope: { bindings: { arguments: [], variables: {} } },
source: null,
this: undefined,
- thread: undefined
+ thread: "FakeThread"
}
]);
});
diff --git a/src/actions/preview.js b/src/actions/preview.js
index cbc9299851..99cfe6ec16 100644
--- a/src/actions/preview.js
+++ b/src/actions/preview.js
@@ -15,7 +15,8 @@ import {
isSelectedFrameVisible,
getSelectedSource,
getSelectedFrame,
- getSymbols
+ getSymbols,
+ getCurrentThread
} from "../selectors";
import { getMappedExpression } from "./expressions";
@@ -86,7 +87,8 @@ export function setPreview(
return;
}
- const selectedFrame = getSelectedFrame(getState());
+ const thread = getCurrentThread(getState());
+ const selectedFrame = getSelectedFrame(getState(), thread);
if (location && isOriginal(source)) {
const mapResult = await dispatch(getMappedExpression(expression));
@@ -101,7 +103,7 @@ export function setPreview(
const { result } = await client.evaluateInFrame(expression, {
frameId: selectedFrame.id,
- thread: selectedFrame.thread
+ thread
});
// Error case occurs for a token that follows an errored evaluation
diff --git a/src/actions/sources/loadSourceText.js b/src/actions/sources/loadSourceText.js
index f2f7f63ede..92083953f9 100644
--- a/src/actions/sources/loadSourceText.js
+++ b/src/actions/sources/loadSourceText.js
@@ -5,14 +5,19 @@
// @flow
import { PROMISE } from "../utils/middleware/promise";
-import { getGeneratedSource, getSource } from "../../selectors";
+import {
+ getSource,
+ getGeneratedSource,
+ getSourcesEpoch
+} from "../../selectors";
import { setBreakpointPositions } from "../breakpoints";
+import { prettyPrintSource } from "./prettyPrint";
+
import * as parser from "../../workers/parser";
-import { isLoaded, isOriginal } from "../../utils/source";
+import { isLoaded, isOriginal, isPretty } from "../../utils/source";
import { Telemetry } from "devtools-modules";
-import defer from "../../utils/defer";
import type { ThunkArgs } from "../types";
import type { Source } from "../../types";
@@ -23,80 +28,105 @@ const requests = new Map();
const loadSourceHistogram = "DEVTOOLS_DEBUGGER_LOAD_SOURCE_MS";
const telemetry = new Telemetry();
-async function loadSource(state, source: Source, { sourceMaps, client }) {
+async function loadSource(
+ state,
+ source: Source,
+ { sourceMaps, client }
+): Promise{
+ text: string,
+ contentType: string
+}> {
+ if (isPretty(source) && isOriginal(source)) {
+ const generatedSource = getGeneratedSource(state, source);
+ return prettyPrintSource(sourceMaps, source, generatedSource);
+ }
+
if (isOriginal(source)) {
- return sourceMaps.getOriginalSourceText(source);
+ const result = await sourceMaps.getOriginalSourceText(source);
+ if (!result) {
+ // The way we currently try to load and select a pending
+ // selected location, it is possible that we will try to fetch the
+ // original source text right after the source map has been cleared
+ // after a navigation event.
+ throw new Error("Original source text unavailable");
+ }
+ return result;
}
if (!source.actors.length) {
throw new Error("No source actor for loadSource");
}
+ telemetry.start(loadSourceHistogram, source);
const response = await client.sourceContents(source.actors[0]);
telemetry.finish(loadSourceHistogram, source);
return {
- id: source.id,
text: response.source,
contentType: response.contentType || "text/javascript"
};
}
-/**
- * @memberof actions/sources
- * @static
- */
-export function loadSourceText(source: ?Source) {
- return async ({ dispatch, getState, client, sourceMaps }: ThunkArgs) => {
- if (!source) {
- return;
- }
+async function loadSourceTextPromise(
+ source: Source,
+ epoch: number,
+ { dispatch, getState, client, sourceMaps }: ThunkArgs
+): Promise {
+ if (isLoaded(source)) {
+ return source;
+ }
- const id = source.id;
- // Fetch the source text only once.
- if (requests.has(id)) {
- return requests.get(id);
- }
+ await dispatch({
+ type: "LOAD_SOURCE_TEXT",
+ sourceId: source.id,
+ epoch,
+ [PROMISE]: loadSource(getState(), source, { sourceMaps, client })
+ });
- if (isLoaded(source)) {
- return Promise.resolve();
- }
+ const newSource = getSource(getState(), source.id);
+ if (!newSource) {
+ return;
+ }
- const deferred = defer();
- requests.set(id, deferred.promise);
-
- telemetry.start(loadSourceHistogram, source);
- try {
- await dispatch({
- type: "LOAD_SOURCE_TEXT",
- sourceId: source.id,
- [PROMISE]: loadSource(getState(), source, { sourceMaps, client })
- });
- } catch (e) {
- deferred.resolve();
- requests.delete(id);
- return;
- }
+ if (!newSource.isWasm && isLoaded(newSource)) {
+ parser.setSource(newSource);
+ await dispatch(setBreakpointPositions(newSource.id));
+ }
- const newSource = getSource(getState(), source.id);
- if (!newSource) {
- return;
- }
+ return newSource;
+}
- if (isOriginal(newSource) && !newSource.isWasm) {
- const generatedSource = getGeneratedSource(getState(), source);
- await dispatch(loadSourceText(generatedSource));
+/**
+ * @memberof actions/sources
+ * @static
+ */
+export function loadSourceText(inputSource: ?Source) {
+ return async (thunkArgs: ThunkArgs) => {
+ if (!inputSource) {
+ return;
}
-
- if (!newSource.isWasm && isLoaded(newSource)) {
- await parser.setSource(newSource);
- await dispatch(setBreakpointPositions(newSource.id));
+ // This ensures that the falsy check above is preserved into the IIFE
+ // below in a way that Flow is happy with.
+ const source = inputSource;
+
+ const epoch = getSourcesEpoch(thunkArgs.getState());
+
+ const id = `${epoch}:${source.id}`;
+ let promise = requests.get(id);
+ if (!promise) {
+ promise = (async () => {
+ try {
+ return await loadSourceTextPromise(source, epoch, thunkArgs);
+ } catch (e) {
+ // TODO: This swallows errors for now. Ideally we would get rid of
+ // this once we have a better handle on our async state management.
+ } finally {
+ requests.delete(id);
+ }
+ })();
+ requests.set(id, promise);
}
- // signal that the action is finished
- deferred.resolve();
- requests.delete(id);
-
- return source;
+ return promise;
};
}
diff --git a/src/actions/sources/newSources.js b/src/actions/sources/newSources.js
index 618def61d1..73dbb7bcc9 100644
--- a/src/actions/sources/newSources.js
+++ b/src/actions/sources/newSources.js
@@ -13,7 +13,7 @@ import { generatedToOriginalId } from "devtools-source-map";
import { flatten } from "lodash";
import { toggleBlackBox } from "./blackbox";
-import { syncBreakpoint } from "../breakpoints";
+import { syncBreakpoint, addBreakpoint } from "../breakpoints";
import { loadSourceText } from "./loadSourceText";
import { togglePrettyPrint } from "./prettyPrint";
import { selectLocation } from "../sources";
@@ -186,8 +186,19 @@ function checkPendingBreakpoints(sourceId: string) {
// load the source text if there is a pending breakpoint for it
await dispatch(loadSourceText(source));
+ // Matching pending breakpoints could have either the same generated or the
+ // same original source. We expect the generated source to appear first and
+ // will add a breakpoint at that location initially. If the original source
+ // appears later then we use syncBreakpoint to see if the generated location
+ // changed and we need to remove the breakpoint we added earlier.
await Promise.all(
- pendingBreakpoints.map(bp => dispatch(syncBreakpoint(sourceId, bp)))
+ pendingBreakpoints.map(bp => {
+ if (source.url == bp.location.sourceUrl) {
+ return dispatch(syncBreakpoint(sourceId, bp));
+ }
+ const { line, column } = bp.generatedLocation;
+ return dispatch(addBreakpoint({ sourceId, line, column }, bp.options));
+ })
);
};
}
diff --git a/src/actions/sources/prettyPrint.js b/src/actions/sources/prettyPrint.js
index c5d2ad4012..48d5332e1e 100644
--- a/src/actions/sources/prettyPrint.js
+++ b/src/actions/sources/prettyPrint.js
@@ -6,11 +6,10 @@
import assert from "../../utils/assert";
import { recordEvent } from "../../utils/telemetry";
-import { remapBreakpoints, setBreakpointPositions } from "../breakpoints";
+import { remapBreakpoints } from "../breakpoints";
import { setSymbols } from "../ast";
import { prettyPrint } from "../../workers/pretty-print";
-import { setSource } from "../../workers/parser";
import { getPrettySourceURL, isLoaded } from "../../utils/source";
import { loadSourceText } from "./loadSourceText";
import { mapFrames } from "../pause";
@@ -19,13 +18,38 @@ import { selectSpecificLocation } from "../sources";
import {
getSource,
getSourceFromId,
+ getSourceThreads,
getSourceByURL,
getSelectedLocation
} from "../../selectors";
import type { Action, ThunkArgs } from "../types";
import { selectSource } from "./select";
-import type { JsSource } from "../../types";
+import type { JsSource, Source } from "../../types";
+
+export async function prettyPrintSource(
+ sourceMaps: any,
+ prettySource: Source,
+ generatedSource: any
+) {
+ const url = getPrettySourceURL(generatedSource.url);
+ const { code, mappings } = await prettyPrint({
+ source: generatedSource,
+ url: url
+ });
+ await sourceMaps.applySourceMap(generatedSource.id, url, code, mappings);
+
+ // The source map URL service used by other devtools listens to changes to
+ // sources based on their actor IDs, so apply the mapping there too.
+ for (const sourceActor of generatedSource.actors) {
+ await sourceMaps.applySourceMap(sourceActor.actor, url, code, mappings);
+ }
+ return {
+ id: prettySource.id,
+ text: code,
+ contentType: "text/javascript"
+ };
+}
export function createPrettySource(sourceId: string) {
return async ({ dispatch, getState, sourceMaps }: ThunkArgs) => {
@@ -48,26 +72,7 @@ export function createPrettySource(sourceId: string) {
};
dispatch(({ type: "ADD_SOURCE", source: prettySource }: Action));
- dispatch(selectSource(prettySource.id));
-
- const { code, mappings } = await prettyPrint({ source, url });
- await sourceMaps.applySourceMap(source.id, url, code, mappings);
-
- // The source map URL service used by other devtools listens to changes to
- // sources based on their actor IDs, so apply the mapping there too.
- for (const sourceActor of source.actors) {
- await sourceMaps.applySourceMap(sourceActor.actor, url, code, mappings);
- }
-
- const loadedPrettySource: JsSource = {
- ...prettySource,
- text: code,
- loadedState: "loaded"
- };
-
- setSource(loadedPrettySource);
- dispatch(({ type: "UPDATE_SOURCE", source: loadedPrettySource }: Action));
- await dispatch(setBreakpointPositions(loadedPrettySource.id));
+ await dispatch(selectSource(prettySource.id));
return prettySource;
};
@@ -124,7 +129,10 @@ export function togglePrettyPrint(sourceId: string) {
const newPrettySource = await dispatch(createPrettySource(sourceId));
await dispatch(remapBreakpoints(sourceId));
- await dispatch(mapFrames());
+
+ const threads = getSourceThreads(getState(), source);
+ await Promise.all(threads.map(thread => dispatch(mapFrames(thread))));
+
await dispatch(setSymbols(newPrettySource.id));
dispatch(
diff --git a/src/actions/sources/tests/loadSource.spec.js b/src/actions/sources/tests/loadSource.spec.js
index 1bb67e9809..ff61d980a8 100644
--- a/src/actions/sources/tests/loadSource.spec.js
+++ b/src/actions/sources/tests/loadSource.spec.js
@@ -7,6 +7,7 @@
import {
actions,
selectors,
+ watchForState,
createStore,
makeSource
} from "../../../utils/test-head";
@@ -117,13 +118,20 @@ describe("loadSourceText", () => {
});
it("should indicate a loading source", async () => {
- const { dispatch, getState } = createStore(sourceThreadClient);
+ const store = createStore(sourceThreadClient);
+ const { dispatch } = store;
- // Don't block on this so we can check the loading state.
- const source = makeSource("foo1");
- dispatch(actions.loadSourceText(source));
- const fooSource = selectors.getSource(getState(), "foo1");
- expect(fooSource && fooSource.loadedState).toEqual("loading");
+ const source = makeSource("foo2");
+ await dispatch(actions.newSource(source));
+
+ const wasLoading = watchForState(store, state => {
+ const fooSource = selectors.getSource(state, "foo2");
+ return fooSource && fooSource.loadedState === "loading";
+ });
+
+ await dispatch(actions.loadSourceText(source));
+
+ expect(wasLoading()).toBe(true);
});
it("should indicate an errored source text", async () => {
diff --git a/src/actions/sources/tests/newSources.spec.js b/src/actions/sources/tests/newSources.spec.js
index d47ad02f1d..9bcd971c98 100644
--- a/src/actions/sources/tests/newSources.spec.js
+++ b/src/actions/sources/tests/newSources.spec.js
@@ -61,7 +61,8 @@ describe("sources - new sources", () => {
threadClient,
{},
{
- getOriginalURLs: async () => ["magic.js"]
+ getOriginalURLs: async () => ["magic.js"],
+ getOriginalLocations: async items => items
}
);
@@ -74,25 +75,27 @@ describe("sources - new sources", () => {
// eslint-disable-next-line
it("should not attempt to fetch original sources if it's missing a source map url", async () => {
const getOriginalURLs = jest.fn();
- const { dispatch } = createStore(threadClient, {}, { getOriginalURLs });
+ const { dispatch } = createStore(
+ threadClient,
+ {},
+ {
+ getOriginalURLs,
+ getOriginalLocations: async items => items
+ }
+ );
await dispatch(actions.newSource(makeSource("base.js")));
expect(getOriginalURLs).not.toHaveBeenCalled();
});
- it("should not fail if there isn't a source map service", async () => {
- const store = createStore(threadClient, {}, null);
- await store.dispatch(actions.newSource(makeSource("base.js")));
- expect(getSourceCount(store.getState())).toEqual(1);
- });
-
// eslint-disable-next-line
it("should process new sources immediately, without waiting for source maps to be fetched first", async () => {
const { dispatch, getState } = createStore(
threadClient,
{},
{
- getOriginalURLs: async () => new Promise(_ => {})
+ getOriginalURLs: async () => new Promise(_ => {}),
+ getOriginalLocations: async items => items
}
);
const baseSource = makeSource("base.js", { sourceMapURL: "base.js.map" });
@@ -116,6 +119,7 @@ describe("sources - new sources", () => {
return [source.id.replace(".js", ".cljs")];
},
+ getOriginalLocations: async items => items,
getGeneratedLocation: location => location
}
);
@@ -132,27 +136,4 @@ describe("sources - new sources", () => {
const bazzCljs = getSourceByURL(getState(), "bazz.cljs");
expect(bazzCljs && bazzCljs.url).toEqual("bazz.cljs");
});
-
- describe("sources - sources with querystrings", () => {
- it(`should find two sources when same source with
- querystring`, async () => {
- const { getSourcesUrlsInSources } = selectors;
- const { dispatch, getState } = createStore(threadClient);
- await dispatch(actions.newSource(makeSource("base.js?v=1")));
- await dispatch(actions.newSource(makeSource("base.js?v=2")));
- await dispatch(actions.newSource(makeSource("diff.js?v=1")));
-
- const base1 = "http://localhost:8000/examples/base.js?v=1";
- const diff1 = "http://localhost:8000/examples/diff.js?v=1";
- const diff2 = "http://localhost:8000/examples/diff.js?v=1";
-
- expect(getSourcesUrlsInSources(getState(), base1)).toHaveLength(2);
- expect(getSourcesUrlsInSources(getState(), base1)).toMatchSnapshot();
-
- expect(getSourcesUrlsInSources(getState(), diff1)).toHaveLength(1);
- await dispatch(actions.newSource(makeSource("diff.js?v=2")));
- expect(getSourcesUrlsInSources(getState(), diff2)).toHaveLength(2);
- expect(getSourcesUrlsInSources(getState(), diff1)).toHaveLength(2);
- });
- });
});
diff --git a/src/actions/sources/tests/querystrings.spec.js b/src/actions/sources/tests/querystrings.spec.js
new file mode 100644
index 0000000000..31290df14e
--- /dev/null
+++ b/src/actions/sources/tests/querystrings.spec.js
@@ -0,0 +1,38 @@
+/* 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 . */
+
+// @flow
+
+import {
+ actions,
+ selectors,
+ createStore,
+ makeSource
+} from "../../../utils/test-head";
+const { getSourcesUrlsInSources } = selectors;
+
+// eslint-disable-next-line max-len
+import { sourceThreadClient as threadClient } from "../../tests/helpers/threadClient.js";
+
+describe("sources - sources with querystrings", () => {
+ it("should find two sources when same source with querystring", async () => {
+ const { dispatch, getState } = createStore(threadClient);
+ await dispatch(actions.newSource(makeSource("base.js?v=1")));
+ await dispatch(actions.newSource(makeSource("base.js?v=2")));
+ await dispatch(actions.newSource(makeSource("diff.js?v=1")));
+
+ expect(
+ getSourcesUrlsInSources(
+ getState(),
+ "http://localhost:8000/examples/base.js?v=1"
+ )
+ ).toHaveLength(2);
+ expect(
+ getSourcesUrlsInSources(
+ getState(),
+ "http://localhost:8000/examples/diff.js?v=1"
+ )
+ ).toHaveLength(1);
+ });
+});
diff --git a/src/actions/sources/tests/select.spec.js b/src/actions/sources/tests/select.spec.js
index bb7b55d9f7..891bc271c4 100644
--- a/src/actions/sources/tests/select.spec.js
+++ b/src/actions/sources/tests/select.spec.js
@@ -229,6 +229,7 @@ describe("sources", () => {
{},
{
getOriginalLocation: async location => ({ ...location, line: 12 }),
+ getOriginalLocations: async items => items,
getGeneratedLocation: async location => ({ ...location, line: 12 }),
getOriginalSourceText: async () => ({ source: "" }),
getGeneratedRangesForOriginal: async () => []
@@ -257,6 +258,7 @@ describe("sources", () => {
{},
{
getOriginalLocation: async location => ({ ...location, line: 12 }),
+ getOriginalLocations: async items => items,
getGeneratedRangesForOriginal: async () => [],
getOriginalSourceText: async () => ({ source: "" })
}
diff --git a/src/actions/tests/ast.spec.js b/src/actions/tests/ast.spec.js
index dff0c36b69..c57c555b02 100644
--- a/src/actions/tests/ast.spec.js
+++ b/src/actions/tests/ast.spec.js
@@ -45,7 +45,8 @@ const sourceMaps = {
text: sourceTexts[id],
contentType: "text/javascript"
}),
- getGeneratedRangesForOriginal: async () => []
+ getGeneratedRangesForOriginal: async () => [],
+ getOriginalLocations: async items => items
};
const sourceTexts = {
@@ -176,10 +177,13 @@ describe("ast", () => {
await dispatch(actions.selectSource("base.js"));
const locations = getOutOfScopeLocations(getState());
- const lines = getInScopeLines(getState());
+ // const lines = getInScopeLines(getState());
expect(locations).toEqual(null);
- expect(lines).toEqual([1]);
+
+ // This check is disabled as locations that are in/out of scope may not
+ // have completed yet when the selectSource promise finishes.
+ // expect(lines).toEqual([1]);
});
});
});
diff --git a/src/actions/tests/pending-breakpoints.spec.js b/src/actions/tests/pending-breakpoints.spec.js
index 1b1b61d446..4df44ddfee 100644
--- a/src/actions/tests/pending-breakpoints.spec.js
+++ b/src/actions/tests/pending-breakpoints.spec.js
@@ -68,7 +68,8 @@ function mockSourceMaps() {
}),
getGeneratedRangesForOriginal: async () => [
{ start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
- ]
+ ],
+ getOriginalLocations: async items => items
};
}
@@ -379,7 +380,8 @@ describe("adding sources", () => {
getOriginalLocation: async location => location,
getGeneratedRangesForOriginal: async () => [
{ start: { line: 0, column: 0 }, end: { line: 10, column: 10 } }
- ]
+ ],
+ getOriginalLocations: async items => items
});
const { getState, dispatch } = store;
diff --git a/src/actions/tests/project-text-search.spec.js b/src/actions/tests/project-text-search.spec.js
index 3a31b1a195..7dcbc18a41 100644
--- a/src/actions/tests/project-text-search.spec.js
+++ b/src/actions/tests/project-text-search.spec.js
@@ -77,7 +77,8 @@ describe("project text search", () => {
contentType: "text/javascript"
}),
getOriginalURLs: async () => [source2.url],
- getGeneratedRangesForOriginal: async () => []
+ getGeneratedRangesForOriginal: async () => [],
+ getOriginalLocations: async items => items
};
const { dispatch, getState } = createStore(threadClient, {}, mockMaps);
diff --git a/src/actions/types/BreakpointAction.js b/src/actions/types/BreakpointAction.js
index 43e07f9ceb..ba064dd5c4 100644
--- a/src/actions/types/BreakpointAction.js
+++ b/src/actions/types/BreakpointAction.js
@@ -8,6 +8,7 @@ import type {
Breakpoint,
SourceLocation,
XHRBreakpoint,
+ Source,
BreakpointPositions
} from "../../types";
@@ -95,5 +96,5 @@ export type BreakpointAction =
| {|
type: "ADD_BREAKPOINT_POSITIONS",
positions: BreakpointPositions,
- sourceId: string
+ source: Source
|};
diff --git a/src/actions/types/PauseAction.js b/src/actions/types/PauseAction.js
index d1e3889028..9eb654f155 100644
--- a/src/actions/types/PauseAction.js
+++ b/src/actions/types/PauseAction.js
@@ -32,7 +32,6 @@ export type PauseAction =
|}
| {|
+type: "PAUSE_ON_EXCEPTIONS",
- +thread: string,
+shouldPauseOnExceptions: boolean,
+shouldPauseOnCaughtExceptions: boolean
|}
diff --git a/src/actions/types/SourceAction.js b/src/actions/types/SourceAction.js
index ff20f3c782..9704b68135 100644
--- a/src/actions/types/SourceAction.js
+++ b/src/actions/types/SourceAction.js
@@ -10,7 +10,8 @@ import type { PromiseAction } from "../utils/middleware/promise";
export type LoadSourceAction = PromiseAction<
{|
+type: "LOAD_SOURCE_TEXT",
- +sourceId: string
+ +sourceId: string,
+ +epoch: number
|},
Source
>;
diff --git a/src/actions/types/index.js b/src/actions/types/index.js
index 1285d332e9..7773b4bf0f 100644
--- a/src/actions/types/index.js
+++ b/src/actions/types/index.js
@@ -4,7 +4,7 @@
// @flow
-import type { Frame, Scope, Why, WorkerList, MainThread } from "../../types";
+import type { WorkerList, MainThread } from "../../types";
import type { State } from "../../reducers/types";
import type { MatchedLocations } from "../../reducers/file-search";
import type { TreeNode } from "../../utils/sources-tree/types";
@@ -64,25 +64,6 @@ type UpdateTabAction = {|
+sourceId?: string
|};
-type ReplayAction =
- | {|
- +type: "TRAVEL_TO",
- +data: {
- paused: {
- why: Why,
- scopes: Scope[],
- frames: Frame[],
- selectedFrameId: string,
- loadedObjects: Object
- },
- expressions?: Object[]
- },
- +position: number
- |}
- | {|
- +type: "CLEAR_HISTORY"
- |};
-
type NavigateAction =
| {| +type: "CONNECT", +mainThread: MainThread, +canRewind: boolean |}
| {| +type: "NAVIGATE", +mainThread: MainThread |};
@@ -176,5 +157,4 @@ export type Action =
| FileTextSearchAction
| ProjectTextSearchAction
| DebugeeAction
- | ReplayAction
| SourceTreeAction;
diff --git a/src/client/firefox/commands.js b/src/client/firefox/commands.js
index 19fe1d8bda..e975de06e9 100644
--- a/src/client/firefox/commands.js
+++ b/src/client/firefox/commands.js
@@ -309,17 +309,23 @@ async function getFrameScopes(frame: Frame): Promise<*> {
return sourceThreadClient.getEnvironment(frame.id);
}
-function pauseOnExceptions(
- thread: string,
+async function pauseOnExceptions(
shouldPauseOnExceptions: boolean,
shouldPauseOnCaughtExceptions: boolean
): Promise<*> {
- return lookupThreadClient(thread).pauseOnExceptions(
+ await threadClient.pauseOnExceptions(
shouldPauseOnExceptions,
// Providing opposite value because server
// uses "shouldIgnoreCaughtExceptions"
!shouldPauseOnCaughtExceptions
);
+
+ await forEachWorkerThread(thread =>
+ thread.pauseOnExceptions(
+ shouldPauseOnExceptions,
+ !shouldPauseOnCaughtExceptions
+ )
+ );
}
async function blackBox(
diff --git a/src/client/firefox/types.js b/src/client/firefox/types.js
index 9371c876dd..fe70366034 100644
--- a/src/client/firefox/types.js
+++ b/src/client/firefox/types.js
@@ -157,16 +157,12 @@ export type FramesResponse = {
export type TabPayload = {
actor: ActorId,
animationsActor: ActorId,
- callWatcherActor: ActorId,
- canvasActor: ActorId,
consoleActor: ActorId,
cssPropertiesActor: ActorId,
- cssUsageActor: ActorId,
directorManagerActor: ActorId,
emulationActor: ActorId,
eventLoopLagActor: ActorId,
framerateActor: ActorId,
- gcliActor: ActorId,
inspectorActor: ActorId,
memoryActor: ActorId,
monitorActor: ActorId,
@@ -182,9 +178,7 @@ export type TabPayload = {
timelineActor: ActorId,
title: string,
url: URL,
- webExtensionInspectedWindowActor: ActorId,
- webaudioActor: ActorId,
- webglActor: ActorId
+ webExtensionInspectedWindowActor: ActorId
};
/**
diff --git a/src/client/index.js b/src/client/index.js
index 743d28a3dd..c0aa5fe8b6 100644
--- a/src/client/index.js
+++ b/src/client/index.js
@@ -7,7 +7,7 @@
import * as firefox from "./firefox";
import * as chrome from "./chrome";
-import { prefs, asyncStore } from "../utils/prefs";
+import { asyncStore, verifyPrefSchema } from "../utils/prefs";
import { setupHelper } from "../utils/dbg";
import {
@@ -19,16 +19,6 @@ import { initialBreakpointsState } from "../reducers/breakpoints";
import type { Panel } from "./firefox/types";
-function loadFromPrefs(actions: Object) {
- const { pauseOnExceptions, pauseOnCaughtExceptions } = prefs;
- if (pauseOnExceptions || pauseOnCaughtExceptions) {
- return actions.pauseOnExceptions(
- pauseOnExceptions,
- pauseOnCaughtExceptions
- );
- }
-}
-
async function syncBreakpoints() {
const breakpoints = await asyncStore.pendingBreakpoints;
const breakpointValues = (Object.values(breakpoints): any);
@@ -77,6 +67,8 @@ export async function onConnect(
return;
}
+ verifyPrefSchema();
+
const client = getClient(connection);
const commands = client.clientCommands;
@@ -92,7 +84,6 @@ export async function onConnect(
const workers = bootstrapWorkers();
await client.onConnect(connection, actions);
- await loadFromPrefs(actions);
syncBreakpoints();
syncXHRBreakpoints();
setupHelper({
diff --git a/src/components/App.js b/src/components/App.js
index 9f47551271..822f8f2212 100644
--- a/src/components/App.js
+++ b/src/components/App.js
@@ -56,7 +56,6 @@ import Editor from "./Editor";
import SecondaryPanes from "./SecondaryPanes";
import WelcomeBox from "./WelcomeBox";
import EditorTabs from "./Editor/Tabs";
-import EditorFooter from "./Editor/Footer";
import QuickOpenModal from "./QuickOpenModal";
type Props = {
@@ -235,7 +234,6 @@ class App extends Component {
toggleShortcutsModal={() => this.toggleShortcutsModal()}
/>
) : null}
-
diff --git a/src/components/Editor/Breakpoints.css b/src/components/Editor/Breakpoints.css
new file mode 100644
index 0000000000..ee15faabb5
--- /dev/null
+++ b/src/components/Editor/Breakpoints.css
@@ -0,0 +1,154 @@
+/* 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 . */
+
+ .theme-light {
+ --gutter-hover-background-color: #dde1e4;
+
+ --breakpoint-fill: var(--blue-50);
+ --breakpoint-stroke: var(--blue-60);
+
+ --breakpoint-disabled-fill: var(--blue-55);
+ --breakpoint-disabled-stroke: var(--blue-55);
+}
+
+.theme-dark {
+ --gutter-hover-background-color: #414141;
+
+ --breakpoint-fill: var(--blue-55);
+ --breakpoint-stroke: var(--blue-40);
+
+ --breakpoint-disabled-fill: var(--blue-55);
+ --breakpoint-disabled-stroke: var(--blue-55);
+}
+
+.theme-light, .theme-dark {
+ --logpoint-fill: var(--theme-graphs-purple);
+ --logpoint-stroke: var(--purple-60);
+ --breakpoint-condition-fill: var(--theme-graphs-yellow);
+ --breakpoint-condition-stroke: var(--theme-graphs-orange);
+ --breakpoint-inactive-opacity: 0.3;
+ --breakpoint-disabled-opacity: 0.6;
+}
+
+/* Standard gutter breakpoints */
+.editor-wrapper .breakpoints {
+ position: absolute;
+ top: 0;
+ left: 0;
+}
+
+.new-breakpoint .CodeMirror-linenumber {
+ pointer-events: none;
+}
+
+:not(.empty-line):not(.new-breakpoint)
+ > .CodeMirror-gutter-wrapper:hover
+ > .CodeMirror-linenumber::after {
+ content: "";
+ position: absolute;
+ /* paint below the number */
+ z-index: -1;
+ top: 0;
+ left: 0;
+ right: -7px;
+ bottom: 0;
+ height: 15px;
+ background-color: var(--gutter-hover-background-color);
+ mask: url(/images/breakpoint.svg) no-repeat;
+ mask-size: auto 15px;
+ mask-position: right;
+}
+
+.editor.new-breakpoint svg {
+ fill: var(--breakpoint-fill);
+ stroke: var(--breakpoint-stroke);
+ width: 60px;
+ height: 15px;
+ position: absolute;
+ top: 0px;
+ right: -4px;
+}
+
+.editor .breakpoint {
+ position: absolute;
+ right: -2px;
+}
+
+.editor.new-breakpoint.folding-enabled svg {
+ right: -16px;
+}
+
+.new-breakpoint.has-condition .CodeMirror-gutter-wrapper svg {
+ fill: var(--breakpoint-condition-fill);
+ stroke: var(--breakpoint-condition-stroke);
+}
+
+.new-breakpoint.has-log .CodeMirror-gutter-wrapper svg {
+ fill: var(--logpoint-fill);
+ stroke: var(--logpoint-stroke);
+}
+
+.editor.new-breakpoint.breakpoint-disabled svg {
+ fill-opacity: var(--breakpoint-disabled-opacity);
+ stroke-opacity: var(--breakpoint-disabled-opacity);
+}
+
+/* Columnn breakpoints */
+.column-breakpoint {
+ display: inline;
+ padding-inline-start: 1px;
+ padding-inline-end: 1px;
+}
+
+.column-breakpoint:hover {
+ background-color: transparent;
+}
+
+.column-breakpoint svg {
+ display: inline-block;
+ cursor: pointer;
+ height: 13px;
+ width: 11px;
+ vertical-align: top;
+ fill: var(--breakpoint-fill);
+ stroke: var(--breakpoint-stroke);
+ fill-opacity: var(--breakpoint-inactive-opacity);
+ stroke-opacity: var(--breakpoint-inactive-opacity);
+}
+
+.column-breakpoint.active svg {
+ fill: var(--breakpoint-fill);
+ stroke: var(--breakpoint-stroke);
+ fill-opacity: 1;
+ stroke-opacity: 1;
+}
+
+.column-breakpoint.disabled svg {
+ fill-opacity: var(--breakpoint-disabled-opacity);
+ stroke-opacity: var(--breakpoint-disabled-opacity);
+}
+
+.column-breakpoint.has-log.disabled svg {
+ fill-opacity: 0.5;
+ stroke-opacity: 0.5;
+}
+
+.column-breakpoint.disabled:not(.has-condition):not(.has-log) svg {
+ fill: var(--breakpoint-disabled-fill);
+ stroke: var(--breakpoint-disabled-stroke);
+}
+
+.column-breakpoint.has-condition svg {
+ fill: var(--breakpoint-condition-fill);
+ stroke: var(--breakpoint-condition-stroke);
+}
+
+.column-breakpoint.has-log svg {
+ fill: var(--logpoint-fill);
+ stroke: var(--logpoint-stroke);
+}
+
+.img.column-marker {
+ background-image: url(/images/column-marker.svg);
+}
diff --git a/src/components/Editor/ColumnBreakpoint.js b/src/components/Editor/ColumnBreakpoint.js
index 7a38da3fb2..1d003e66a0 100644
--- a/src/components/Editor/ColumnBreakpoint.js
+++ b/src/components/Editor/ColumnBreakpoint.js
@@ -34,6 +34,7 @@ function makeBookmark({ breakpoint }, { onClick, onContextMenu }) {
const bp = breakpointImg.cloneNode(true);
const isActive = breakpoint && !breakpoint.disabled;
+ const isDisabled = breakpoint && breakpoint.disabled;
const condition = breakpoint && breakpoint.options.condition;
const logValue = breakpoint && breakpoint.options.logValue;
@@ -41,7 +42,7 @@ function makeBookmark({ breakpoint }, { onClick, onContextMenu }) {
"has-condition": condition,
"has-log": logValue,
active: isActive,
- disabled: !isActive
+ disabled: isDisabled
});
if (condition) {
diff --git a/src/components/Editor/ColumnBreakpoints.js b/src/components/Editor/ColumnBreakpoints.js
index 6a4d501825..f0762e9d61 100644
--- a/src/components/Editor/ColumnBreakpoints.js
+++ b/src/components/Editor/ColumnBreakpoints.js
@@ -7,7 +7,6 @@
import React, { Component } from "react";
import ColumnBreakpoint from "./ColumnBreakpoint";
-import "./ColumnBreakpoints.css";
import { getSelectedSource, visibleColumnBreakpoints } from "../../selectors";
import { connect } from "../../utils/connect";
diff --git a/src/components/Editor/DebugLine.js b/src/components/Editor/DebugLine.js
index 1ceeaca819..f8d263191f 100644
--- a/src/components/Editor/DebugLine.js
+++ b/src/components/Editor/DebugLine.js
@@ -18,7 +18,8 @@ import { connect } from "../../utils/connect";
import {
getVisibleSelectedFrame,
getPauseReason,
- getSourceFromId
+ getSourceFromId,
+ getCurrentThread
} from "../../selectors";
import type { Frame, Why, Source } from "../../types";
@@ -118,7 +119,7 @@ const mapStateToProps = state => {
return {
frame,
source: frame && getSourceFromId(state, frame.location.sourceId),
- why: getPauseReason(state)
+ why: getPauseReason(state, getCurrentThread(state))
};
};
diff --git a/src/components/Editor/Editor.css b/src/components/Editor/Editor.css
index 0487ae437c..08c5bbeda6 100644
--- a/src/components/Editor/Editor.css
+++ b/src/components/Editor/Editor.css
@@ -44,9 +44,9 @@
*/
.editor-wrapper {
position: absolute;
+ height: calc(100% - var(--editor-header-height));
width: calc(100% - 1px);
top: var(--editor-header-height);
- bottom: var(--editor-footer-height);
left: 0px;
}
@@ -54,18 +54,6 @@ html[dir="rtl"] .editor-mount {
direction: ltr;
}
-.theme-light {
- --gutter-hover-background-color: #dde1e4;
- --breakpoint-fill: var(--blue-50);
- --breakpoint-stroke: var(--blue-60);
-}
-
-.theme-dark {
- --gutter-hover-background-color: #414141;
- --breakpoint-fill: var(--blue-55);
- --breakpoint-stroke: var(--blue-40);
-}
-
.theme-light .cm-s-mozilla .empty-line .CodeMirror-linenumber {
color: var(--grey-40);
}
@@ -74,34 +62,6 @@ html[dir="rtl"] .editor-mount {
color: var(--grey-50);
}
-.new-breakpoint .CodeMirror-linenumber {
- pointer-events: none;
-}
-
-:not(.empty-line):not(.new-breakpoint)
- > .CodeMirror-gutter-wrapper:hover
- > .CodeMirror-linenumber::after {
- content: "";
- position: absolute;
- /* paint below the number */
- z-index: -1;
- top: 0;
- left: 0;
- right: -7px;
- bottom: 0;
- height: 15px;
- background-color: var(--gutter-hover-background-color);
- mask: url(/images/breakpoint.svg) no-repeat;
- mask-size: auto 15px;
- mask-position: right;
-}
-
-.editor-wrapper .breakpoints {
- position: absolute;
- top: 0;
- left: 0;
-}
-
.function-search {
max-height: 300px;
overflow: hidden;
@@ -119,50 +79,6 @@ html[dir="rtl"] .editor-mount {
background: var(--theme-selection-background-hover);
}
-.editor.new-breakpoint svg {
- fill: var(--breakpoint-fill);
- stroke: var(--breakpoint-stroke);
- width: 60px;
- height: 15px;
- position: absolute;
- top: 0px;
- right: -4px;
-}
-
-.inline-bp {
- background-color: #9ddfff;
- width: 20px;
- padding: 0px 5px;
- margin: 0px 4px;
- border-radius: 5px;
- border-color: blue;
- border: 1px solid #00b6ff;
-}
-
-.editor .breakpoint {
- position: absolute;
- right: -2px;
-}
-
-.editor.new-breakpoint.folding-enabled svg {
- right: -16px;
-}
-
-.new-breakpoint.has-condition .CodeMirror-gutter-wrapper svg {
- fill: var(--theme-graphs-yellow);
- stroke: var(--theme-graphs-orange);
-}
-
-.new-breakpoint.has-log .CodeMirror-gutter-wrapper svg {
- fill: var(--theme-graphs-purple);
- stroke: var(--purple-60);
-}
-
-.editor.new-breakpoint.breakpoint-disabled svg {
- fill-opacity: 0.7;
- stroke-opacity: 0.7;
-}
-
.CodeMirror {
width: 100%;
height: 100%;
@@ -264,8 +180,3 @@ debug-expression-error {
.download-anchor {
display: none;
}
-
-/* This is not used, but it is needed to copy over column-marker.svg */
-.column-marker {
- mask: url(/images/column-marker.svg) no-repeat;
-}
diff --git a/src/components/Editor/EditorMenu.js b/src/components/Editor/EditorMenu.js
index b9b55e4276..953e1c63f9 100644
--- a/src/components/Editor/EditorMenu.js
+++ b/src/components/Editor/EditorMenu.js
@@ -9,7 +9,11 @@ import { connect } from "../../utils/connect";
import { showMenu } from "devtools-contextmenu";
import { getSourceLocationFromMouseEvent } from "../../utils/editor";
-import { getPrettySource, getIsPaused } from "../../selectors";
+import {
+ getPrettySource,
+ getIsPaused,
+ getCurrentThread
+} from "../../selectors";
import { editorMenuItems, editorItemActions } from "./menus/editor";
@@ -74,7 +78,7 @@ class EditorMenu extends Component {
}
const mapStateToProps = (state, props) => ({
- isPaused: getIsPaused(state),
+ isPaused: getIsPaused(state, getCurrentThread(state)),
hasPrettySource: !!getPrettySource(state, props.selectedSource.id)
});
diff --git a/src/components/Editor/Footer.css b/src/components/Editor/Footer.css
index b8ecf16a1d..71639cefd9 100644
--- a/src/components/Editor/Footer.css
+++ b/src/components/Editor/Footer.css
@@ -16,12 +16,12 @@
user-select: none;
height: var(--editor-footer-height);
box-sizing: border-box;
+ justify-content: space-between;
}
.source-footer .commands {
display: flex;
align-items: center;
- justify-self: start;
}
.source-footer .commands * {
@@ -65,7 +65,6 @@
.source-footer .cursor-position {
color: var(--theme-body-color);
padding-right: 2.5px;
- margin-left: auto;
}
.source-footer .mapped-source {
diff --git a/src/components/Editor/Footer.js b/src/components/Editor/Footer.js
index 1575b0c3b4..e29dea035d 100644
--- a/src/components/Editor/Footer.js
+++ b/src/components/Editor/Footer.js
@@ -22,7 +22,7 @@ import {
shouldBlackbox
} from "../../utils/source";
import { getGeneratedSource } from "../../reducers/sources";
-import { shouldShowPrettyPrint } from "../../utils/editor";
+import { shouldShowFooter, shouldShowPrettyPrint } from "../../utils/editor";
import { PaneToggleButton } from "../shared/Button";
import AccessibleImage from "../shared/AccessibleImage";
@@ -40,6 +40,7 @@ type Props = {
selectedSource: Source,
mappedSource: Source,
endPanelCollapsed: boolean,
+ editor: Object,
horizontal: boolean,
togglePrettyPrint: typeof actions.togglePrettyPrint,
toggleBlackBox: typeof actions.toggleBlackBox,
@@ -55,40 +56,22 @@ class SourceFooter extends PureComponent {
constructor() {
super();
- this.state = { cursorPosition: { line: 0, column: 0 } };
+ this.state = { cursorPosition: { line: 1, column: 1 } };
}
- componentDidUpdate() {
- const eventDoc = document.querySelector(".editor-mount .CodeMirror");
- // querySelector can return null
- if (eventDoc) {
- this.toggleCodeMirror(eventDoc, true);
- }
+ componentDidMount() {
+ const { editor } = this.props;
+ editor.codeMirror.on("cursorActivity", this.onCursorChange);
}
componentWillUnmount() {
- const eventDoc = document.querySelector(".editor-mount .CodeMirror");
-
- if (eventDoc) {
- this.toggleCodeMirror(eventDoc, false);
- }
- }
-
- toggleCodeMirror(eventDoc: Object, toggle: boolean) {
- if (toggle === true) {
- eventDoc.CodeMirror.on("cursorActivity", this.onCursorChange);
- } else {
- eventDoc.CodeMirror.off("cursorActivity", this.onCursorChange);
- }
+ const { editor } = this.props;
+ editor.codeMirror.off("cursorActivity", this.onCursorChange);
}
prettyPrintButton() {
const { selectedSource, togglePrettyPrint } = this.props;
- if (!selectedSource) {
- return;
- }
-
if (isLoading(selectedSource) && selectedSource.isPrettyPrinted) {
return (
@@ -125,20 +108,13 @@ class SourceFooter extends PureComponent
{
const { selectedSource, toggleBlackBox } = this.props;
const sourceLoaded = selectedSource && isLoaded(selectedSource);
- if (!selectedSource) {
- return;
- }
-
if (!shouldBlackbox(selectedSource)) {
return;
}
const blackboxed = selectedSource.isBlackBoxed;
- const tooltip = blackboxed
- ? L10N.getStr("sourceFooter.unblackbox")
- : L10N.getStr("sourceFooter.blackbox");
-
+ const tooltip = L10N.getStr("sourceFooter.blackbox");
const type = "black-box";
return (
@@ -174,7 +150,7 @@ class SourceFooter extends PureComponent {
}
renderCommands() {
- const commands = [this.blackBoxButton(), this.prettyPrintButton()].filter(
+ const commands = [this.prettyPrintButton(), this.blackBoxButton()].filter(
Boolean
);
@@ -216,30 +192,32 @@ class SourceFooter extends PureComponent {
};
renderCursorPosition() {
- if (!this.props.selectedSource) {
- return null;
- }
-
- const { line, column } = this.state.cursorPosition;
+ const { cursorPosition } = this.state;
const text = L10N.getFormatStr(
"sourceFooter.currentCursorPosition",
- line + 1,
- column + 1
+ cursorPosition.line + 1,
+ cursorPosition.column + 1
);
const title = L10N.getFormatStr(
"sourceFooter.currentCursorPosition.tooltip",
- line + 1,
- column + 1
+ cursorPosition.line + 1,
+ cursorPosition.column + 1
);
return (
-
+
{text}
-
+
);
}
render() {
+ const { selectedSource, horizontal } = this.props;
+
+ if (!shouldShowFooter(selectedSource, horizontal)) {
+ return null;
+ }
+
return (
{this.renderCommands()}
diff --git a/src/components/Editor/HighlightLine.js b/src/components/Editor/HighlightLine.js
index 1aa904e20d..5872fca052 100644
--- a/src/components/Editor/HighlightLine.js
+++ b/src/components/Editor/HighlightLine.js
@@ -13,7 +13,8 @@ import {
getVisibleSelectedFrame,
getSelectedLocation,
getSelectedSource,
- getPauseCommand
+ getPauseCommand,
+ getCurrentThread
} from "../../selectors";
import type {
@@ -168,7 +169,7 @@ export class HighlightLine extends Component
{
}
export default connect(state => ({
- pauseCommand: getPauseCommand(state),
+ pauseCommand: getPauseCommand(state, getCurrentThread(state)),
selectedFrame: getVisibleSelectedFrame(state),
selectedLocation: getSelectedLocation(state),
selectedSource: getSelectedSource(state)
diff --git a/src/components/Editor/Preview/Popup.js b/src/components/Editor/Preview/Popup.js
index deb64bfdc7..cf2850f7b3 100644
--- a/src/components/Editor/Preview/Popup.js
+++ b/src/components/Editor/Preview/Popup.js
@@ -22,7 +22,10 @@ const {
} = utils;
import actions from "../../../actions";
-import { getAllPopupObjectProperties } from "../../../selectors";
+import {
+ getAllPopupObjectProperties,
+ getCurrentThread
+} from "../../../selectors";
import Popover from "../../shared/Popover";
import PreviewFunction from "../../shared/PreviewFunction";
@@ -318,7 +321,10 @@ export class Popup extends Component {
}
const mapStateToProps = state => ({
- popupObjectProperties: getAllPopupObjectProperties(state)
+ popupObjectProperties: getAllPopupObjectProperties(
+ state,
+ getCurrentThread(state)
+ )
});
const {
diff --git a/src/components/Editor/Preview/index.js b/src/components/Editor/Preview/index.js
index b155e06c4a..3fc9064f78 100644
--- a/src/components/Editor/Preview/index.js
+++ b/src/components/Editor/Preview/index.js
@@ -9,7 +9,12 @@ import { connect } from "../../../utils/connect";
import Popup from "./Popup";
-import { getPreview, getSelectedSource, getIsPaused } from "../../../selectors";
+import {
+ getPreview,
+ getSelectedSource,
+ getIsPaused,
+ getCurrentThread
+} from "../../../selectors";
import actions from "../../../actions";
import { toEditorRange } from "../../../utils/editor";
@@ -179,7 +184,7 @@ class Preview extends PureComponent {
const mapStateToProps = state => ({
preview: getPreview(state),
- isPaused: getIsPaused(state),
+ isPaused: getIsPaused(state, getCurrentThread(state)),
selectedSource: getSelectedSource(state)
});
diff --git a/src/components/Editor/SearchBar.css b/src/components/Editor/SearchBar.css
index ea23ac078c..0d94085328 100644
--- a/src/components/Editor/SearchBar.css
+++ b/src/components/Editor/SearchBar.css
@@ -19,7 +19,7 @@
left: 0;
right: 0;
bottom: -1px;
- border: solid 1px var(--blue-50);
+ border: solid 1px var(--blue-50);
pointer-events: none;
opacity: 0;
transition: opacity 150ms ease-out;
diff --git a/src/components/Editor/Tabs.js b/src/components/Editor/Tabs.js
index b95370f96c..a17a993c25 100644
--- a/src/components/Editor/Tabs.js
+++ b/src/components/Editor/Tabs.js
@@ -10,7 +10,8 @@ import { connect } from "../../utils/connect";
import {
getSelectedSource,
getSourcesForTabs,
- getIsPaused
+ getIsPaused,
+ getCurrentThread
} from "../../selectors";
import { isVisible } from "../../utils/ui";
@@ -227,7 +228,7 @@ class Tabs extends PureComponent {
const mapStateToProps = state => ({
selectedSource: getSelectedSource(state),
tabSources: getSourcesForTabs(state),
- isPaused: getIsPaused(state)
+ isPaused: getIsPaused(state, getCurrentThread(state))
});
export default connect(
diff --git a/src/components/Editor/index.js b/src/components/Editor/index.js
index 1cae91095e..0b6afc1e25 100644
--- a/src/components/Editor/index.js
+++ b/src/components/Editor/index.js
@@ -10,7 +10,7 @@ import { bindActionCreators } from "redux";
import ReactDOM from "react-dom";
import { connect } from "../../utils/connect";
import classnames from "classnames";
-import { debounce } from "lodash";
+import { throttle } from "lodash";
import { isLoaded } from "../../utils/source";
import { isFirefox } from "devtools-environment";
@@ -34,12 +34,14 @@ import {
getSelectedSource,
getConditionalPanelLocation,
getSymbols,
- getIsPaused
+ getIsPaused,
+ getCurrentThread
} from "../../selectors";
// Redux actions
import actions from "../../actions";
+import Footer from "./Footer";
import SearchBar from "./SearchBar";
import HighlightLines from "./HighlightLines";
import Preview from "./Preview";
@@ -56,6 +58,7 @@ import {
updateDocument,
showLoading,
showErrorMessage,
+ shouldShowFooter,
getEditor,
clearEditor,
getCursorLine,
@@ -74,6 +77,7 @@ import {
import { resizeToggleButton, resizeBreakpointGutter } from "../../utils/ui";
import "./Editor.css";
+import "./Breakpoints.css";
import "./Highlight.css";
import type SourceEditor from "../../utils/editor/source-editor";
@@ -81,13 +85,15 @@ import type { SymbolDeclarations } from "../../workers/parser";
import type { SourceLocation, Source } from "../../types";
const cssVars = {
- searchbarHeight: "var(--editor-searchbar-height)"
+ searchbarHeight: "var(--editor-searchbar-height)",
+ footerHeight: "var(--editor-footer-height)"
};
export type Props = {
selectedLocation: ?SourceLocation,
selectedSource: ?Source,
searchOn: boolean,
+ horizontal: boolean,
startPanelSize: number,
endPanelSize: number,
conditionalPanelLocation: SourceLocation,
@@ -201,7 +207,6 @@ class Editor extends PureComponent {
codeMirror.on("scroll", this.onEditorScroll);
this.onEditorScroll();
-
this.setState({ editor });
return editor;
}
@@ -216,11 +221,7 @@ class Editor extends PureComponent {
shortcuts.on(L10N.getStr("toggleBreakpoint.key"), this.onToggleBreakpoint);
shortcuts.on(
- L10N.getStr("toggleCondPanel.breakpoint.key"),
- this.onToggleConditionalPanel
- );
- shortcuts.on(
- L10N.getStr("toggleCondPanel.logPoint.key"),
+ L10N.getStr("toggleCondPanel.key"),
this.onToggleConditionalPanel
);
shortcuts.on(L10N.getStr("sourceTabs.closeTab.key"), this.onClosePress);
@@ -252,8 +253,7 @@ class Editor extends PureComponent {
const shortcuts = this.context.shortcuts;
shortcuts.off(L10N.getStr("sourceTabs.closeTab.key"));
shortcuts.off(L10N.getStr("toggleBreakpoint.key"));
- shortcuts.off(L10N.getStr("toggleCondPanel.breakpoint.key"));
- shortcuts.off(L10N.getStr("toggleCondPanel.logPoint.key"));
+ shortcuts.off(L10N.getStr("toggleCondPanel.key"));
shortcuts.off(searchAgainPrevKey);
shortcuts.off(searchAgainKey);
}
@@ -272,6 +272,10 @@ class Editor extends PureComponent {
this.setSize(this.props);
}
}
+
+ if (prevProps.selectedSource != selectedSource) {
+ this.props.updateViewport();
+ }
}
getCurrentLine() {
@@ -301,16 +305,14 @@ class Editor extends PureComponent {
e.stopPropagation();
e.preventDefault();
const line = this.getCurrentLine();
-
if (typeof line !== "number") {
return;
}
- const isLog = key === L10N.getStr("toggleCondPanel.logPoint.key");
- this.toggleConditionalPanel(line, isLog);
+ this.toggleConditionalPanel(line);
};
- onEditorScroll = debounce(this.props.updateViewport, 200);
+ onEditorScroll = throttle(this.props.updateViewport, 100);
onKeyDown(e: KeyboardEvent) {
const { codeMirror } = this.state.editor;
@@ -456,7 +458,7 @@ class Editor extends PureComponent {
}
}
- toggleConditionalPanel = (line, log: boolean = false) => {
+ toggleConditionalPanel = line => {
const {
conditionalPanelLocation,
closeConditionalPanel,
@@ -472,14 +474,11 @@ class Editor extends PureComponent {
return;
}
- return openConditionalPanel(
- {
- line: line,
- sourceId: selectedSource.id,
- sourceUrl: selectedSource.url
- },
- log
- );
+ return openConditionalPanel({
+ line: line,
+ sourceId: selectedSource.id,
+ sourceUrl: selectedSource.url
+ });
};
shouldScrollToLocation(nextProps) {
@@ -578,21 +577,28 @@ class Editor extends PureComponent {
}
getInlineEditorStyles() {
- const { searchOn } = this.props;
+ const { selectedSource, horizontal, searchOn } = this.props;
+
+ const subtractions = [];
+
+ if (shouldShowFooter(selectedSource, horizontal)) {
+ subtractions.push(cssVars.footerHeight);
+ }
if (searchOn) {
- return {
- height: `calc(100% - ${cssVars.searchbarHeight})`
- };
+ subtractions.push(cssVars.searchbarHeight);
}
return {
- height: "100%"
+ height:
+ subtractions.length === 0
+ ? "100%"
+ : `calc(100% - ${subtractions.join(" - ")})`
};
}
renderItems() {
- const { selectedSource, conditionalPanelLocation } = this.props;
+ const { horizontal, selectedSource, conditionalPanelLocation } = this.props;
const { editor, contextMenu } = this.state;
if (!selectedSource || !editor || !getDocument(selectedSource.id)) {
@@ -606,6 +612,7 @@ class Editor extends PureComponent {
+
{
{
renderSearchBar() {
const { editor } = this.state;
- if (!this.props.selectedSource) {
+ if (!editor) {
return null;
}
@@ -663,7 +670,7 @@ const mapStateToProps = state => {
searchOn: getActiveSearch(state) === "file",
conditionalPanelLocation: getConditionalPanelLocation(state),
symbols: getSymbols(state, selectedSource),
- isPaused: getIsPaused(state)
+ isPaused: getIsPaused(state, getCurrentThread(state))
};
};
diff --git a/src/components/Editor/menus/breakpoints.js b/src/components/Editor/menus/breakpoints.js
index 116d1dbf52..f798d9f14e 100644
--- a/src/components/Editor/menus/breakpoints.js
+++ b/src/components/Editor/menus/breakpoints.js
@@ -39,7 +39,7 @@ export const addConditionalBreakpointItem = (
) => ({
id: "node-menu-add-conditional-breakpoint",
label: L10N.getStr("editor.addConditionBreakpoint"),
- accelerator: L10N.getStr("toggleCondPanel.breakpoint.key"),
+ accelerator: L10N.getStr("toggleCondPanel.key"),
accesskey: L10N.getStr("editor.addConditionBreakpoint.accesskey"),
disabled: false,
click: () => breakpointActions.openConditionalPanel(location)
@@ -51,7 +51,7 @@ export const editConditionalBreakpointItem = (
) => ({
id: "node-menu-edit-conditional-breakpoint",
label: L10N.getStr("editor.editConditionBreakpoint"),
- accelerator: L10N.getStr("toggleCondPanel.breakpoint.key"),
+ accelerator: L10N.getStr("toggleCondPanel.key"),
accesskey: L10N.getStr("editor.addConditionBreakpoint.accesskey"),
disabled: false,
click: () => breakpointActions.openConditionalPanel(location)
@@ -79,7 +79,7 @@ export const addLogPointItem = (
accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
disabled: false,
click: () => breakpointActions.openConditionalPanel(location, true),
- accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
+ accelerator: L10N.getStr("toggleCondPanel.key")
});
export const editLogPointItem = (
@@ -91,7 +91,7 @@ export const editLogPointItem = (
accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
disabled: false,
click: () => breakpointActions.openConditionalPanel(location, true),
- accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
+ accelerator: L10N.getStr("toggleCondPanel.key")
});
export const logPointItem = (
diff --git a/src/components/Editor/tests/__snapshots__/Editor.spec.js.snap b/src/components/Editor/tests/__snapshots__/Editor.spec.js.snap
index 31aaa84b96..76a6d5b993 100644
--- a/src/components/Editor/tests/__snapshots__/Editor.spec.js.snap
+++ b/src/components/Editor/tests/__snapshots__/Editor.spec.js.snap
@@ -8,7 +8,7 @@ exports[`Editor When empty should render 1`] = `
className="editor-mount devtools-monospace"
style={
Object {
- "height": "100%",
+ "height": "calc(100% - var(--editor-footer-height))",
}
}
/>
diff --git a/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap b/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap
index a2ec2a6de4..88060f8c8b 100644
--- a/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap
+++ b/src/components/Editor/tests/__snapshots__/Footer.spec.js.snap
@@ -4,12 +4,12 @@ exports[`SourceFooter Component default case should render 1`] = `
-
- (1, 1)
-
+ (2, 2)
+
-
(6, 11)
-
+
{
return this.renderPane(
this.renderThreadHeader(),
- this.isEmpty() ? (
- this.renderEmptyElement(L10N.getStr("noSourcesText"))
- ) : (
-
- {this.renderTree()}
-
- )
+
+ {this.renderTree()}
+
);
}
}
diff --git a/src/components/PrimaryPanes/tests/SourcesTree.spec.js b/src/components/PrimaryPanes/tests/SourcesTree.spec.js
index f29f49d79a..9fa1480358 100644
--- a/src/components/PrimaryPanes/tests/SourcesTree.spec.js
+++ b/src/components/PrimaryPanes/tests/SourcesTree.spec.js
@@ -28,14 +28,6 @@ describe("SourcesTree", () => {
expect(component).toMatchSnapshot();
});
- it("Should show a 'No Sources' message if there are no sources", async () => {
- const { component, defaultState } = render();
- const sourceTree = defaultState.sourceTree;
- sourceTree.contents = [];
- component.setState({ sourceTree: sourceTree });
- expect(component).toMatchSnapshot();
- });
-
describe("When loading initial source", () => {
it("Shows the tree with one.js, two.js and three.js expanded", async () => {
const { component, props } = render();
diff --git a/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap b/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
index c93adeac15..f99bf83511 100644
--- a/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
+++ b/src/components/PrimaryPanes/tests/__snapshots__/SourcesTree.spec.js.snap
@@ -48,32 +48,6 @@ exports[`SourcesTree After changing expanded nodes Shows the tree with four.js,
`;
-exports[`SourcesTree Should show a 'No Sources' message if there are no sources 1`] = `
-
-
-
- This page has no sources.
-
-
-`;
-
exports[`SourcesTree Should show the tree with nothing expanded 1`] = `
{
const { selectedIndex, results } = this.state;
const resultCount = this.getResultCount();
const index = selectedIndex + direction;
- const nextIndex = (index + resultCount) % resultCount || 0;
+ const nextIndex = (index + resultCount) % resultCount;
this.setState({ selectedIndex: nextIndex });
diff --git a/src/components/SecondaryPanes/Breakpoints/Breakpoint.js b/src/components/SecondaryPanes/Breakpoints/Breakpoint.js
index a38b78200e..aa7e683ce5 100644
--- a/src/components/SecondaryPanes/Breakpoints/Breakpoint.js
+++ b/src/components/SecondaryPanes/Breakpoints/Breakpoint.js
@@ -37,7 +37,8 @@ type FormattedFrame = Frame & {
import {
getBreakpointsList,
getSelectedFrame,
- getSelectedSource
+ getSelectedSource,
+ getCurrentThread
} from "../../../selectors";
type Props = {
@@ -211,7 +212,7 @@ const getFormattedFrame = createSelector(
const mapStateToProps = state => ({
breakpoints: getBreakpointsList(state),
- frame: getFormattedFrame(state)
+ frame: getFormattedFrame(state, getCurrentThread(state))
});
export default connect(
diff --git a/src/components/SecondaryPanes/Breakpoints/Breakpoints.css b/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
index 1a4c64800e..e1d643f2bb 100644
--- a/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
+++ b/src/components/SecondaryPanes/Breakpoints/Breakpoints.css
@@ -6,24 +6,20 @@
margin: 2px 3px;
}
-.breakpoints-list * {
- user-select: none;
+.pane.breakpoints-list {
+ padding-bottom: 0.35em;
}
-.breakpoints-list {
- margin-top: 4px;
- margin-bottom: 4px;
+.breakpoints-list * {
+ user-select: none;
}
.breakpoints-list .breakpoint-heading {
text-overflow: ellipsis;
+ overflow: hidden;
+ display: flex;
width: 100%;
- font-size: 12px;
- line-height: 16px;
-}
-
-.breakpoint-heading:not(:first-child) {
- margin-top: 2px;
+ align-items: center;
}
.breakpoints-list .breakpoint-heading .filename {
@@ -38,8 +34,10 @@
.breakpoints-list .breakpoint-heading,
.breakpoints-list .breakpoint {
+ font-size: 12px;
color: var(--theme-content-color1);
position: relative;
+ transition: all 0.25s ease;
cursor: pointer;
}
@@ -47,56 +45,53 @@
.breakpoints-list .breakpoint,
.breakpoints-exceptions,
.breakpoints-exceptions-caught {
- display: flex;
- align-items: center;
- overflow: hidden;
- padding-top: 2px;
- padding-bottom: 2px;
- padding-inline-start: 16px;
- padding-inline-end: 12px;
+ padding: 0.25em 1em;
}
.breakpoints-exceptions {
- padding-bottom: 3px;
- padding-top: 3px;
+ padding-bottom: 0.5em;
+ padding-top: 0.5em;
user-select: none;
}
-.breakpoints-exceptions-caught {
- padding-bottom: 3px;
- padding-top: 3px;
- padding-inline-start: 36px;
+.breakpoints-list .breakpoint {
+ min-height: var(--breakpoint-expression-height);
+ overflow: hidden;
}
-.breakpoints-exceptions-options {
- padding-top: 4px;
- padding-bottom: 4px;
+.breakpoints-exceptions-caught {
+ padding: 0 1em 0.5em 3em;
+ margin-top: -0.25em;
}
-.xhr-breakpoints-pane .breakpoints-exceptions-options {
- border-bottom: 1px solid var(--theme-splitter-color);
+html[dir="rtl"] .breakpoints-exceptions-caught {
+ padding: 0 3em 0.5em 1em;
}
.breakpoints-exceptions-options:not(.empty) {
border-bottom: 1px solid var(--theme-splitter-color);
+ margin-bottom: 3px;
}
.breakpoints-exceptions input,
.breakpoints-exceptions-caught input {
padding-inline-start: 2px;
- margin-top: 0px;
- margin-bottom: 0px;
margin-inline-start: 0;
- margin-inline-end: 2px;
vertical-align: text-bottom;
}
.breakpoint-exceptions-label {
- line-height: 14px;
+ padding-top: 2px;
+ padding-inline-start: 2px;
padding-inline-end: 8px;
cursor: default;
- overflow: hidden;
- text-overflow: ellipsis;
+}
+
+.breakpoints-list .breakpoint,
+.breakpoints-exceptions,
+.breakpoints-exceptions-caught {
+ display: flex;
+ align-items: center;
}
html[dir="rtl"] .breakpoints-list .breakpoint,
@@ -133,20 +128,15 @@ html .breakpoints-list .breakpoint.paused {
background-color: var(--search-overlays-semitransparent);
}
-.breakpoint-line-close {
- margin-inline-start: 4px;
-}
-
.breakpoints-list .breakpoint .breakpoint-line {
font-size: 11px;
color: var(--theme-comment);
min-width: 16px;
text-align: end;
- padding-top: 1px;
- padding-bottom: 1px;
}
-.breakpoints-list .breakpoint:hover .breakpoint-line {
+.breakpoints-list .breakpoint:hover .breakpoint-line,
+.breakpoints-list .breakpoint-line-close:focus-within .breakpoint-line {
color: transparent;
}
@@ -155,24 +145,24 @@ html .breakpoints-list .breakpoint.paused {
}
.breakpoints-list .breakpoint-label {
+ max-width: calc(100% - var(--breakpoint-expression-right-clear-space));
display: inline-block;
+ padding-inline-end: 8px;
cursor: pointer;
flex-grow: 1;
text-overflow: ellipsis;
overflow: hidden;
+ padding-top: 2px;
font-size: 11px;
}
-.breakpoints-list .breakpoint-label span,
+.breakpoints-list .breakpoint-label,
.breakpoint-line-close {
- display: inline;
- line-height: 14px;
+ line-height: 1.4em;
}
.breakpoint-checkbox {
- margin-inline-start: 0px;
- margin-top: 0px;
- margin-bottom: 0px;
+ margin-inline-start: 0;
vertical-align: text-bottom;
}
@@ -191,17 +181,10 @@ html .breakpoints-list .breakpoint.paused {
}
.breakpoint .close-btn {
+ inset-inline-end: 15px;
+ inset-inline-start: auto;
position: absolute;
- /* hide button outside of row until hovered or focused */
- top: -100px;
-}
-
-[dir="ltr"] .breakpoint .close-btn {
- right: 12px;
-}
-
-[dir="rtl"] .breakpoint .close-btn {
- left: 12px;
+ top: -100px; /*For hiding button outside of row until hovered or focused*/
}
.breakpoint:hover .close-btn,
diff --git a/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js b/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js
index 7fc9544d51..dcd08d011c 100644
--- a/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js
+++ b/src/components/SecondaryPanes/Breakpoints/BreakpointsContextMenu.js
@@ -205,8 +205,7 @@ export default function showContextMenu(props: Props) {
click: () => {
selectSpecificLocation(selectedLocation);
openConditionalPanel(selectedLocation);
- },
- accelerator: L10N.getStr("toggleCondPanel.breakpoint.key")
+ }
};
const editConditionItem = {
@@ -216,8 +215,7 @@ export default function showContextMenu(props: Props) {
click: () => {
selectSpecificLocation(selectedLocation);
openConditionalPanel(selectedLocation);
- },
- accelerator: L10N.getStr("toggleCondPanel.breakpoint.key")
+ }
};
const addLogPointItem = {
@@ -226,7 +224,7 @@ export default function showContextMenu(props: Props) {
accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
disabled: false,
click: () => openConditionalPanel(selectedLocation, true),
- accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
+ accelerator: L10N.getStr("toggleCondPanel.key")
};
const editLogPointItem = {
@@ -235,7 +233,7 @@ export default function showContextMenu(props: Props) {
accesskey: L10N.getStr("editor.addLogPoint.accesskey"),
disabled: false,
click: () => openConditionalPanel(selectedLocation, true),
- accelerator: L10N.getStr("toggleCondPanel.logPoint.key")
+ accelerator: L10N.getStr("toggleCondPanel.key")
};
const removeLogPointItem = {
diff --git a/src/components/SecondaryPanes/Breakpoints/index.js b/src/components/SecondaryPanes/Breakpoints/index.js
index 66cbb8782e..0e4151e6fb 100644
--- a/src/components/SecondaryPanes/Breakpoints/index.js
+++ b/src/components/SecondaryPanes/Breakpoints/index.js
@@ -77,49 +77,43 @@ class Breakpoints extends Component
{
renderBreakpoints() {
const { breakpointSources, selectedSource } = this.props;
- if (!breakpointSources.length) {
- return null;
- }
-
const sources = [
...breakpointSources.map(({ source, breakpoints }) => source)
];
- return (
-
- {breakpointSources.map(({ source, breakpoints, i }) => {
- const path = getDisplayPath(source, sources);
- const sortedBreakpoints = sortSelectedBreakpoints(
- breakpoints,
- selectedSource
- );
-
- return [
- {
+ const path = getDisplayPath(source, sources);
+ const sortedBreakpoints = sortSelectedBreakpoints(
+ breakpoints,
+ selectedSource
+ );
+
+ return [
+ ,
+ ...sortedBreakpoints.map(breakpoint => (
+ ,
- ...sortedBreakpoints.map(breakpoint => (
-
- ))
- ];
- })}
-
- );
+ selectedSource={selectedSource}
+ key={makeBreakpointId(
+ getSelectedLocation(breakpoint, selectedSource)
+ )}
+ />
+ ))
+ ];
+ })
+ ];
}
render() {
return (
-
+
{this.renderExceptionsOptions()}
{this.renderBreakpoints()}
diff --git a/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/Breakpoint.spec.js.snap b/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/Breakpoint.spec.js.snap
index ff2b61ca24..03aef3ad6f 100644
--- a/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/Breakpoint.spec.js.snap
+++ b/src/components/SecondaryPanes/Breakpoints/tests/__snapshots__/Breakpoint.spec.js.snap
@@ -35,7 +35,7 @@ exports[`Breakpoint disabled 1`] = `
- 53
+ 53:73
- 53
+ 53:73
- 53
+ 53:73
- 5
+ 5:7
- 53
+ 53:73
({
- isPaused: getIsPaused(state),
- isWaitingOnBreak: getIsWaitingOnBreak(state),
+ isPaused: getIsPaused(state, getCurrentThread(state)),
+ isWaitingOnBreak: getIsWaitingOnBreak(state, getCurrentThread(state)),
canRewind: getCanRewind(state),
skipPausing: getSkipPausing(state)
});
diff --git a/src/components/SecondaryPanes/EventListeners.css b/src/components/SecondaryPanes/EventListeners.css
index 65f8114c03..27697ec4ad 100644
--- a/src/components/SecondaryPanes/EventListeners.css
+++ b/src/components/SecondaryPanes/EventListeners.css
@@ -3,56 +3,56 @@
* file, You can obtain one at . */
.event-listeners-content {
- padding-top: 4px;
- padding-bottom: 4px;
- padding-inline-start: 14px;
- padding-inline-end: 20px;
+ padding-top: 4px;
+ padding-bottom: 4px;
+ padding-inline-start: 14px;
+ padding-inline-end: 20px;
}
.event-listeners-content ul {
- padding: 0;
- list-style-type: none;
+ padding: 0;
+ list-style-type: none;
}
.event-listener-group {
- user-select: none;
+ user-select: none;
}
.event-listener-header {
- display: flex;
- align-items: center;
+ display: flex;
+ align-items: center;
}
.event-listener-expand {
- border: none;
- background: none;
- padding: 4px 5px;
- line-height: 12px;
+ border: none;
+ background: none;
+ padding: 4px 5px;
+ line-height: 12px;
}
.event-listener-expand:hover {
- background: transparent;
+ background: transparent;
}
.event-listener-group input[type="checkbox"] {
- margin: 0px;
- margin-inline-end: 4px;
+ margin: 0px;
+ margin-inline-end: 4px;
}
.event-listener-label {
- display: flex;
- align-items: center;
- padding-inline-start: 2px;
- padding-inline-end: 10px;
+ display: flex;
+ align-items: center;
+ padding-inline-start: 2px;
+ padding-inline-end: 10px;
}
.event-listener-category {
- padding: 3px 0px;
- line-height: 14px;
+ padding: 3px 0px;
+ line-height: 14px;
}
.event-listeners-content .arrow {
- margin-inline-end: 0;
+ margin-inline-end: 0;
}
html[dir="ltr"] .event-listeners-content .arrow.expanded {
@@ -64,19 +64,19 @@ html[dir="rtl"] .event-listeners-content .arrow.expanded {
}
.event-listener-event {
- display: flex;
- align-items: center;
- margin-inline-start: 30px;
+ display: flex;
+ align-items: center;
+ margin-inline-start: 30px;
}
.event-listener-name {
- line-height: 14px;
- padding: 3px 0px;
+ line-height: 14px;
+ padding: 3px 0px;
}
.event-listener-event input {
- margin-inline-end: 4px;
- margin-inline-start: 0px;
- margin-top: 0px;
- margin-bottom: 0px;
+ margin-inline-end: 4px;
+ margin-inline-start: 0px;
+ margin-top: 0px;
+ margin-bottom: 0px;
}
diff --git a/src/components/SecondaryPanes/Expressions.css b/src/components/SecondaryPanes/Expressions.css
index a586ad3ec9..5dfa3496ee 100644
--- a/src/components/SecondaryPanes/Expressions.css
+++ b/src/components/SecondaryPanes/Expressions.css
@@ -44,7 +44,7 @@
.expressions-list {
/* TODO: add normalize */
margin: 0;
- padding: 4px 0px;
+ padding: 0;
}
.expression-input-container {
@@ -72,7 +72,6 @@
background-color: var(--theme-body-background);
display: block;
position: relative;
- overflow: hidden;
min-height: var(--breakpoint-expression-height);
}
@@ -95,21 +94,8 @@
.expression-container__close-btn {
position: absolute;
- /* hiding button outside of row until hovered or focused */
- top: -100px;
-}
-
-.expression-container:hover .expression-container__close-btn,
-.expression-container__close-btn:focus-within {
- top: calc(50% - 8px);
-}
-
-[dir="ltr"] .expression-container__close-btn {
- right: 0;
-}
-
-[dir="rtl"] .expression-container__close-btn {
- left: 0;
+ inset-inline-end: 0px;
+ top: 1px;
}
.expression-content {
diff --git a/src/components/SecondaryPanes/Frames/index.js b/src/components/SecondaryPanes/Frames/index.js
index 5991d7b314..7243483afa 100644
--- a/src/components/SecondaryPanes/Frames/index.js
+++ b/src/components/SecondaryPanes/Frames/index.js
@@ -23,7 +23,8 @@ import {
getFrameworkGroupingState,
getSelectedFrame,
getCallStackFrames,
- getPauseReason
+ getPauseReason,
+ getCurrentThread
} from "../../../selectors";
import "./Frames.css";
@@ -216,9 +217,9 @@ Frames.contextTypes = { l10n: PropTypes.object };
const mapStateToProps = state => ({
frames: getCallStackFrames(state),
- why: getPauseReason(state),
+ why: getPauseReason(state, getCurrentThread(state)),
frameworkGroupingOn: getFrameworkGroupingState(state),
- selectedFrame: getSelectedFrame(state)
+ selectedFrame: getSelectedFrame(state, getCurrentThread(state))
});
export default connect(
diff --git a/src/components/SecondaryPanes/Scopes.css b/src/components/SecondaryPanes/Scopes.css
index 23d7b81255..24a90c27b8 100644
--- a/src/components/SecondaryPanes/Scopes.css
+++ b/src/components/SecondaryPanes/Scopes.css
@@ -2,40 +2,25 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at . */
-.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;
-}
+ .secondary-panes .map-scopes-header {
+ padding-inline-end: 3px;
+ }
-.scopes-content .toggle-map-scopes-label {
- padding-inline-start: 2px;
- padding-inline-end: 8px;
- cursor: default;
- flex-grow: 1;
- -moz-user-select: none;
+.secondary-panes .header-buttons .img.shortcuts {
+ width: 14px;
+ height: 14px;
+ /* Better vertical centering of the icon */
+ margin-top: -2px;
}
.scopes-content .toggle-map-scopes a.mdn {
- padding-inline-end: 10px;
+ padding-inline-start: 3px;
}
.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;
}
diff --git a/src/components/SecondaryPanes/Scopes.js b/src/components/SecondaryPanes/Scopes.js
index 2d83dd7932..5bbd317db4 100644
--- a/src/components/SecondaryPanes/Scopes.js
+++ b/src/components/SecondaryPanes/Scopes.js
@@ -4,9 +4,7 @@
// @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";
@@ -15,22 +13,20 @@ import {
getSelectedFrame,
getGeneratedFrameScope,
getOriginalFrameScope,
- isPaused as getIsPaused,
+ getIsPaused,
getPauseReason,
- getMapScopes
+ getMapScopes,
+ getCurrentThread
} 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 = {
@@ -109,30 +105,6 @@ class Scopes extends PureComponent {
this.props.toggleMapScopes();
};
- renderMapScopes() {
- const { selectedFrame, shouldMapScopes } = this.props;
-
- if (!features.mapScopes || isGeneratedId(selectedFrame.location.sourceId)) {
- return null;
- }
-
- return (
-
-
e.stopPropagation() && this.onToggleMapScopes()}
- />
-
- {L10N.getStr("scopes.mapScopes")}
-
-
-
-
-
- );
- }
-
renderScopesList() {
const {
isPaused,
@@ -160,20 +132,6 @@ class Scopes extends PureComponent {
onDOMNodeClick={grip => openElementInInspector(grip)}
onInspectIconClick={grip => openElementInInspector(grip)}
/>
- {originalScopes && shouldMapScopes ? (
-
-
-
- ) : null}
);
}
@@ -195,17 +153,13 @@ class Scopes extends PureComponent {
}
render() {
- return (
-
- {this.renderMapScopes()}
- {this.renderScopesList()}
-
- );
+ return {this.renderScopesList()}
;
}
}
const mapStateToProps = state => {
- const selectedFrame = getSelectedFrame(state);
+ const thread = getCurrentThread(state);
+ const selectedFrame = getSelectedFrame(state, thread);
const selectedSource = getSelectedSource(state);
const {
@@ -213,6 +167,7 @@ const mapStateToProps = state => {
pending: originalPending
} = getOriginalFrameScope(
state,
+ thread,
selectedSource && selectedSource.id,
selectedFrame && selectedFrame.id
) || { scope: null, pending: false };
@@ -220,7 +175,11 @@ const mapStateToProps = state => {
const {
scope: generatedFrameScopes,
pending: generatedPending
- } = getGeneratedFrameScope(state, selectedFrame && selectedFrame.id) || {
+ } = getGeneratedFrameScope(
+ state,
+ thread,
+ selectedFrame && selectedFrame.id
+ ) || {
scope: null,
pending: false
};
@@ -228,9 +187,9 @@ const mapStateToProps = state => {
return {
selectedFrame,
shouldMapScopes: getMapScopes(state),
- isPaused: getIsPaused(state),
+ isPaused: getIsPaused(state, thread),
isLoading: generatedPending || originalPending,
- why: getPauseReason(state),
+ why: getPauseReason(state, thread),
originalFrameScopes,
generatedFrameScopes
};
diff --git a/src/components/SecondaryPanes/SecondaryPanes.css b/src/components/SecondaryPanes/SecondaryPanes.css
index 89aadefc15..9686b11cd5 100644
--- a/src/components/SecondaryPanes/SecondaryPanes.css
+++ b/src/components/SecondaryPanes/SecondaryPanes.css
@@ -57,8 +57,3 @@
width: 20em;
overflow: auto;
}
-
-.secondary-panes input[type="checkbox"] {
- margin: 0;
- margin-inline-end: 4px;
-}
diff --git a/src/components/SecondaryPanes/Worker.js b/src/components/SecondaryPanes/Worker.js
index 36c8dc1fbe..f51245d0d8 100644
--- a/src/components/SecondaryPanes/Worker.js
+++ b/src/components/SecondaryPanes/Worker.js
@@ -9,7 +9,7 @@ import { connect } from "../../utils/connect";
import classnames from "classnames";
import actions from "../../actions";
-import { getCurrentThread, getThreadIsPaused } from "../../selectors";
+import { getCurrentThread, getIsPaused } from "../../selectors";
import { getDisplayName, isWorker } from "../../utils/workers";
import AccessibleImage from "../shared/AccessibleImage";
@@ -59,7 +59,7 @@ export class Worker extends Component {
const mapStateToProps = (state, props: Props) => ({
currentThread: getCurrentThread(state),
- isPaused: getThreadIsPaused(state, props.thread.actor)
+ isPaused: getIsPaused(state, props.thread.actor)
});
export default connect(
diff --git a/src/components/SecondaryPanes/XHRBreakpoints.css b/src/components/SecondaryPanes/XHRBreakpoints.css
index d53cb87faf..3e8f082933 100644
--- a/src/components/SecondaryPanes/XHRBreakpoints.css
+++ b/src/components/SecondaryPanes/XHRBreakpoints.css
@@ -3,7 +3,7 @@
* file, You can obtain one at . */
.xhr-input-container {
- display: flex;
+ display: block;
border: 1px solid transparent;
}
@@ -19,25 +19,27 @@
border: 1px solid red;
}
+.xhr-container label {
+ display: flex;
+}
+
.xhr-input-form {
display: inline-flex;
width: 100%;
- padding-inline-start: 20px;
- padding-inline-end: 12px;
+ padding: 0.5em 1em 0.5em 1em;
}
.xhr-checkbox {
margin-inline-start: 0;
- margin-inline-end: 4px;
}
.xhr-input-url {
border: 1px;
- padding: 3px 0px;
+ padding: 0em 0.6em 0em 0.6em;
flex-grow: 1;
background-color: var(--theme-sidebar-background);
- font-size: inherit;
- line-height: 14px;
+ font-size: 12px;
+ line-height: 18px;
color: var(--theme-body-color);
}
@@ -55,26 +57,26 @@
border-left: 4px solid transparent;
width: 100%;
color: var(--theme-body-color);
- padding-inline-start: 16px;
- padding-inline-end: 12px;
+ padding: 0.25em 1em;
background-color: var(--theme-body-background);
display: flex;
align-items: center;
position: relative;
+ min-height: var(--breakpoint-expression-height);
}
:root.theme-light .xhr-container:hover {
- background-color: var(--search-overlays-semitransparent);
+ background-color: var(--theme-selection-background-hover);
}
:root.theme-dark .xhr-container:hover {
- background-color: var(--search-overlays-semitransparent);
+ background-color: var(--theme-selection-background-hover);
}
.xhr-label-method {
- line-height: 14px;
+ padding: 0px 2px 0px 2px;
+ line-height: 15px;
display: inline-block;
- margin-inline-end: 2px;
}
.xhr-input-method {
@@ -94,18 +96,21 @@
text-overflow: ellipsis;
overflow: hidden;
padding: 0px 2px 0px 2px;
- line-height: 14px;
+ line-height: 15px;
+ font-size: 11px;
}
.xhr-container label {
flex-grow: 1;
display: flex;
+ padding-inline-end: 36px;
align-items: center;
overflow-x: hidden;
}
.xhr-container__close-btn {
- display: flex;
- padding-top: 2px;
- padding-bottom: 2px;
+ inset-inline-end: 12px;
+ inset-inline-start: auto;
+ position: absolute;
+ top: 8px;
}
diff --git a/src/components/SecondaryPanes/index.js b/src/components/SecondaryPanes/index.js
index 6f53e4eebc..772e77796b 100644
--- a/src/components/SecondaryPanes/index.js
+++ b/src/components/SecondaryPanes/index.js
@@ -5,6 +5,7 @@
// @flow
import React, { Component } from "react";
+import { isGeneratedId } from "devtools-source-map";
import { connect } from "../../utils/connect";
import { List } from "immutable";
@@ -16,9 +17,12 @@ import {
getBreakpointsLoading,
getExpressions,
getIsWaitingOnBreak,
+ getMapScopes,
+ getSelectedFrame,
getShouldPauseOnExceptions,
getShouldPauseOnCaughtExceptions,
- getWorkers
+ getWorkers,
+ getCurrentThread
} from "../../selectors";
import AccessibleImage from "../shared/AccessibleImage";
@@ -39,7 +43,7 @@ import Scopes from "./Scopes";
import "./SecondaryPanes.css";
-import type { Expression, WorkerList } from "../../types";
+import type { Expression, Frame, WorkerList } from "../../types";
type AccordionPaneItem = {
header: string,
@@ -73,19 +77,25 @@ type Props = {
hasFrames: boolean,
horizontal: boolean,
breakpoints: Object,
+ selectedFrame: ?Frame,
breakpointsDisabled: boolean,
breakpointsLoading: boolean,
isWaitingOnBreak: boolean,
+ shouldMapScopes: boolean,
shouldPauseOnExceptions: boolean,
shouldPauseOnCaughtExceptions: boolean,
workers: WorkerList,
toggleShortcutsModal: () => void,
toggleAllBreakpoints: typeof actions.toggleAllBreakpoints,
+ toggleMapScopes: typeof actions.toggleMapScopes,
evaluateExpressions: typeof actions.evaluateExpressions,
pauseOnExceptions: typeof actions.pauseOnExceptions,
breakOnNext: typeof actions.breakOnNext
};
+const mdnLink =
+ "https://developer.mozilla.org/docs/Tools/Debugger/Using_the_Debugger_map_scopes_feature?utm_source=devtools&utm_medium=debugger-map-scopes";
+
class SecondaryPanes extends Component {
constructor(props: Props) {
super(props);
@@ -207,12 +217,51 @@ class SecondaryPanes extends Component {
className: "scopes-pane",
component: ,
opened: prefs.scopesVisible,
+ buttons: this.getScopesButtons(),
onToggle: opened => {
prefs.scopesVisible = opened;
}
};
}
+ getScopesButtons() {
+ const { selectedFrame, shouldMapScopes } = this.props;
+
+ if (
+ !features.mapScopes ||
+ !selectedFrame ||
+ isGeneratedId(selectedFrame.location.sourceId)
+ ) {
+ return null;
+ }
+
+ return [
+
+ ];
+ }
+
getWatchItem(): AccordionPaneItem {
return {
header: L10N.getStr("watchExpressions.header"),
@@ -412,17 +461,23 @@ class SecondaryPanes extends Component {
}
}
-const mapStateToProps = state => ({
- expressions: getExpressions(state),
- hasFrames: !!getTopFrame(state),
- breakpoints: getBreakpointsList(state),
- breakpointsDisabled: getBreakpointsDisabled(state),
- breakpointsLoading: getBreakpointsLoading(state),
- isWaitingOnBreak: getIsWaitingOnBreak(state),
- shouldPauseOnExceptions: getShouldPauseOnExceptions(state),
- shouldPauseOnCaughtExceptions: getShouldPauseOnCaughtExceptions(state),
- workers: getWorkers(state)
-});
+const mapStateToProps = state => {
+ const thread = getCurrentThread(state);
+
+ return {
+ expressions: getExpressions(state),
+ hasFrames: !!getTopFrame(state, thread),
+ breakpoints: getBreakpointsList(state),
+ breakpointsDisabled: getBreakpointsDisabled(state),
+ breakpointsLoading: getBreakpointsLoading(state),
+ isWaitingOnBreak: getIsWaitingOnBreak(state, thread),
+ selectedFrame: getSelectedFrame(state, thread),
+ shouldMapScopes: getMapScopes(state),
+ shouldPauseOnExceptions: getShouldPauseOnExceptions(state),
+ shouldPauseOnCaughtExceptions: getShouldPauseOnCaughtExceptions(state),
+ workers: getWorkers(state)
+ };
+};
export default connect(
mapStateToProps,
@@ -430,6 +485,7 @@ export default connect(
toggleAllBreakpoints: actions.toggleAllBreakpoints,
evaluateExpressions: actions.evaluateExpressions,
pauseOnExceptions: actions.pauseOnExceptions,
+ toggleMapScopes: actions.toggleMapScopes,
breakOnNext: actions.breakOnNext
}
)(SecondaryPanes);
diff --git a/src/components/SecondaryPanes/tests/XHRBreakpoints.spec.js b/src/components/SecondaryPanes/tests/XHRBreakpoints.spec.js
index 110f8b76b5..8147ab6876 100644
--- a/src/components/SecondaryPanes/tests/XHRBreakpoints.spec.js
+++ b/src/components/SecondaryPanes/tests/XHRBreakpoints.spec.js
@@ -129,7 +129,7 @@ describe("XHR Breakpoints", function() {
const xhrBreakpointsComponent = renderXHRBreakpointsComponent();
xhrBreakpointsComponent.find(".xhr-input-url").simulate("focus");
- const xhrInputContainer = xhrBreakpointsComponent.find(
+ var xhrInputContainer = xhrBreakpointsComponent.find(
".xhr-input-container"
);
expect(xhrInputContainer.hasClass("focused")).toBeTruthy();
@@ -159,7 +159,7 @@ describe("XHR Breakpoints", function() {
propsOverride
);
xhrBreakpointsComponent.find(".xhr-input-url").simulate("focus");
- let xhrInputContainer = xhrBreakpointsComponent.find(
+ var xhrInputContainer = xhrBreakpointsComponent.find(
".xhr-input-container"
);
expect(xhrInputContainer.hasClass("focused")).toBeTruthy();
@@ -201,7 +201,7 @@ describe("XHR Breakpoints", function() {
expect(xhrBreakpointsComponent.state("clickedOnFormElement")).toBe(false);
xhrBreakpointsComponent.find(".xhr-input-method").simulate("click");
- const xhrInputContainer = xhrBreakpointsComponent.find(
+ var xhrInputContainer = xhrBreakpointsComponent.find(
".xhr-input-container"
);
expect(xhrInputContainer.hasClass("focused")).toBeTruthy();
diff --git a/src/components/ShortcutsModal.js b/src/components/ShortcutsModal.js
index 6ab67bf958..e130cbbefa 100644
--- a/src/components/ShortcutsModal.js
+++ b/src/components/ShortcutsModal.js
@@ -46,12 +46,8 @@ export class ShortcutsModal extends Component {
formatKeyShortcut(L10N.getStr("toggleBreakpoint.key"))
)}
{this.renderShorcutItem(
- L10N.getStr("shortcuts.toggleCondPanel.breakpoint"),
- formatKeyShortcut(L10N.getStr("toggleCondPanel.breakpoint.key"))
- )}
- {this.renderShorcutItem(
- L10N.getStr("shortcuts.toggleCondPanel.logPoint"),
- formatKeyShortcut(L10N.getStr("toggleCondPanel.logPoint.key"))
+ L10N.getStr("shortcuts.toggleCondPanel"),
+ formatKeyShortcut(L10N.getStr("toggleCondPanel.key"))
)}
);
diff --git a/src/components/WelcomeBox.css b/src/components/WelcomeBox.css
index 3aa065a8e3..f405d5952a 100644
--- a/src/components/WelcomeBox.css
+++ b/src/components/WelcomeBox.css
@@ -6,7 +6,7 @@
position: absolute;
top: var(--editor-header-height);
left: 0;
- bottom: var(--editor-footer-height);
+ bottom: 1px;
width: calc(100% - 1px);
padding: 50px 0 0 0;
text-align: center;
@@ -20,6 +20,14 @@
background-color: var(--theme-body-background);
}
+.welcomebox .command-bar-button {
+ position: absolute;
+ top: auto;
+ inset-inline-end: 0;
+ inset-inline-start: auto;
+ bottom: 0;
+}
+
.alignlabel {
display: flex;
white-space: nowrap;
@@ -81,3 +89,7 @@
padding: 10px 5px;
display: table-cell;
}
+
+html .welcomebox .toggle-button-end.collapsed {
+ bottom: 1px;
+}
diff --git a/src/components/WelcomeBox.js b/src/components/WelcomeBox.js
index 9d1b1fc4ec..6b642aabe5 100644
--- a/src/components/WelcomeBox.js
+++ b/src/components/WelcomeBox.js
@@ -11,6 +11,7 @@ import actions from "../actions";
import { getPaneCollapse } from "../selectors";
import { formatKeyShortcut } from "../utils/text";
+import { PaneToggleButton } from "./shared/Button";
import type { ActiveSearchType } from "../reducers/ui";
import "./WelcomeBox.css";
@@ -25,6 +26,22 @@ type Props = {
};
export class WelcomeBox extends Component {
+ renderToggleButton() {
+ const { horizontal, endPanelCollapsed, togglePaneCollapse } = this.props;
+ if (horizontal) {
+ return;
+ }
+
+ return (
+
+ );
+ }
+
render() {
const searchSourcesShortcut = formatKeyShortcut(
L10N.getStr("sources.search.key2")
@@ -76,6 +93,7 @@ export class WelcomeBox extends Component {
+ {this.renderToggleButton()}
);
}
diff --git a/src/components/shared/Accordion.css b/src/components/shared/Accordion.css
index 91d3498f16..d715f480a1 100644
--- a/src/components/shared/Accordion.css
+++ b/src/components/shared/Accordion.css
@@ -74,7 +74,7 @@
.accordion ._content {
border-bottom: 1px solid var(--theme-splitter-color);
- font-size: var(--theme-body-font-size);
+ font-size: 12px;
}
.accordion div:last-child ._content {
diff --git a/src/components/shared/SearchInput.css b/src/components/shared/SearchInput.css
index 1e38789d7c..1f59ede1b0 100644
--- a/src/components/shared/SearchInput.css
+++ b/src/components/shared/SearchInput.css
@@ -32,27 +32,18 @@
}
.search-field .img.search {
- --icon-mask-size: 12px;
- --icon-inset-inline-start: 6px;
position: absolute;
z-index: 1;
+ inset-inline-start: 6px;
top: calc(50% - 8px);
- mask-size: var(--icon-mask-size);
+ mask-size: 12px;
background-color: var(--theme-icon-dimmed-color);
pointer-events: none;
}
.search-field.big .img.search {
- --icon-mask-size: 16px;
- --icon-inset-inline-start: 12px;
-}
-
-[dir="ltr"] .search-field .img.search {
- left: var(--icon-inset-inline-start);
-}
-
-[dir="rtl"] .search-field .img.search {
- right: var(--icon-inset-inline-start);
+ inset-inline-start: 12px;
+ mask-size: 16px;
}
.search-field .img.loader {
diff --git a/src/components/test/QuickOpenModal.spec.js b/src/components/test/QuickOpenModal.spec.js
index b8baceb6e6..32a697a8d5 100644
--- a/src/components/test/QuickOpenModal.spec.js
+++ b/src/components/test/QuickOpenModal.spec.js
@@ -636,7 +636,7 @@ describe("QuickOpenModal", () => {
}));
wrapper.find("SearchInput").simulate("keydown", event);
expect(event.preventDefault).toHaveBeenCalled();
- expect(wrapper.state().selectedIndex).toEqual(0);
+ expect(wrapper.state().selectedIndex).toEqual(NaN);
expect(props.selectSpecificLocation).not.toHaveBeenCalledWith();
expect(props.highlightLineRange).not.toHaveBeenCalled();
});
diff --git a/src/components/test/__snapshots__/ShortcutsModal.spec.js.snap b/src/components/test/__snapshots__/ShortcutsModal.spec.js.snap
index 9986b2b3cc..944317324a 100644
--- a/src/components/test/__snapshots__/ShortcutsModal.spec.js.snap
+++ b/src/components/test/__snapshots__/ShortcutsModal.spec.js.snap
@@ -33,7 +33,7 @@ exports[`ShortcutsModal renders when enabled 1`] = `
- Edit Conditional Breakpoint
+ Toggle Conditional Panel
-
-
- Edit Log Point
-
-
-
- Ctrl+Shift+Y
-
-
-
+
`;
diff --git a/src/reducers/ast.js b/src/reducers/ast.js
index 4ec2b06d7f..4e63ae97aa 100644
--- a/src/reducers/ast.js
+++ b/src/reducers/ast.js
@@ -166,15 +166,6 @@ export function getPreview(state: OuterState) {
return state.ast.preview;
}
-export function isEmptyLineInSource(
- state: OuterState,
- line: number,
- selectedSourceId: string
-) {
- const emptyLines = getEmptyLines(state, selectedSourceId);
- return emptyLines && emptyLines.includes(line);
-}
-
const emptySourceMetaData = {};
export function getSourceMetaData(state: OuterState, sourceId: string) {
return state.ast.sourceMetaData[sourceId] || emptySourceMetaData;
@@ -193,12 +184,4 @@ export function isLineInScope(state: OuterState, line: number) {
return linesInScope && linesInScope.includes(line);
}
-export function getEmptyLines(state: OuterState, sourceId: string) {
- if (!sourceId) {
- return null;
- }
-
- return state.ast.emptyLines[sourceId];
-}
-
export default update;
diff --git a/src/reducers/breakpoints.js b/src/reducers/breakpoints.js
index 4f86c39f15..1eed06b82b 100644
--- a/src/reducers/breakpoints.js
+++ b/src/reducers/breakpoints.js
@@ -13,6 +13,10 @@ import { isGeneratedId, isOriginalId } from "devtools-source-map";
import { isEqual } from "lodash";
import { makeBreakpointId } from "../utils/breakpoint";
+import { findEmptyLines } from "../utils/empty-lines";
+
+// eslint-disable-next-line max-len
+import { getBreakpointsList as getBreakpointsListSelector } from "../selectors/breakpoints";
import type {
XHRBreakpoint,
@@ -31,7 +35,8 @@ export type BreakpointsState = {
breakpoints: BreakpointsMap,
breakpointPositions: BreakpointPositionsMap,
xhrBreakpoints: XHRBreakpointsList,
- breakpointsDisabled: boolean
+ breakpointsDisabled: boolean,
+ emptyLines: { [string]: number[] }
};
export function initialBreakpointsState(
@@ -41,7 +46,8 @@ export function initialBreakpointsState(
breakpoints: {},
xhrBreakpoints: xhrBreakpoints,
breakpointPositions: {},
- breakpointsDisabled: false
+ breakpointsDisabled: false,
+ emptyLines: {}
};
}
@@ -111,12 +117,18 @@ function update(
}
case "ADD_BREAKPOINT_POSITIONS": {
- const { sourceId, positions } = action;
+ const { source, positions } = action;
+ const emptyLines = findEmptyLines(source, positions);
+
return {
...state,
breakpointPositions: {
...state.breakpointPositions,
- [sourceId]: positions
+ [source.id]: positions
+ },
+ emptyLines: {
+ ...state.emptyLines,
+ [source.id]: emptyLines
}
};
}
@@ -284,7 +296,7 @@ export function getBreakpointsMap(state: OuterState): BreakpointsMap {
}
export function getBreakpointsList(state: OuterState): Breakpoint[] {
- return (Object.values(getBreakpointsMap(state)): any);
+ return getBreakpointsListSelector((state: any));
}
export function getBreakpointCount(state: OuterState): number {
@@ -383,4 +395,21 @@ export function getBreakpointPositionsForLine(
});
}
+export function isEmptyLineInSource(
+ state: OuterState,
+ line: number,
+ selectedSourceId: string
+) {
+ const emptyLines = getEmptyLines(state, selectedSourceId);
+ return emptyLines && emptyLines.includes(line);
+}
+
+export function getEmptyLines(state: OuterState, sourceId: string) {
+ if (!sourceId) {
+ return null;
+ }
+
+ return state.breakpoints.emptyLines[sourceId];
+}
+
export default update;
diff --git a/src/reducers/expressions.js b/src/reducers/expressions.js
index a791007c3e..7ca53e78f2 100644
--- a/src/reducers/expressions.js
+++ b/src/reducers/expressions.js
@@ -84,10 +84,6 @@ function update(
case "CLEAR_EXPRESSION_ERROR":
return state.set("expressionError", false);
- // respond to time travel
- case "TRAVEL_TO":
- return travelTo(state, action);
-
case "AUTOCOMPLETE":
const { matchProp, matches } = action.result;
@@ -104,22 +100,6 @@ function update(
return state;
}
-function travelTo(state, action) {
- const { expressions } = action.data;
- if (!expressions) {
- return state;
- }
- return expressions.reduce(
- (finalState, previousState) =>
- updateExpressionInList(finalState, previousState.input, {
- input: previousState.input,
- value: previousState.value,
- updating: false
- }),
- state
- );
-}
-
function restoreExpressions() {
const exprs = prefs.expressions;
if (exprs.length == 0) {
diff --git a/src/reducers/pause.js b/src/reducers/pause.js
index a1465dceb8..ce58981530 100644
--- a/src/reducers/pause.js
+++ b/src/reducers/pause.js
@@ -10,22 +10,22 @@
* @module reducers/pause
*/
-import { createSelector } from "reselect";
import { isGeneratedId } from "devtools-source-map";
import { prefs } from "../utils/prefs";
import { getSelectedSourceId } from "./sources";
+import { getSelectedFrame } from "../selectors/pause";
import type { OriginalScope } from "../utils/pause/mapScopes";
import type { Action } from "../actions/types";
-import type { Selector, State } from "./types";
+import type { State } from "./types";
import type {
Why,
Scope,
SourceId,
ChromeFrame,
- Frame,
FrameId,
- MappedLocation
+ MappedLocation,
+ ThreadId
} from "../types";
export type Command =
@@ -65,8 +65,6 @@ type ThreadPauseState = {
},
selectedFrameId: ?string,
loadedObjects: Object,
- shouldPauseOnExceptions: boolean,
- shouldPauseOnCaughtExceptions: boolean,
command: Command,
lastCommand: Command,
wasStepping: boolean,
@@ -75,11 +73,13 @@ type ThreadPauseState = {
// Pause state describing all threads.
export type PauseState = {
- currentThread: string,
+ currentThread: ThreadId,
canRewind: boolean,
- threads: { [string]: ThreadPauseState },
+ threads: { [ThreadId]: ThreadPauseState },
skipPausing: boolean,
- mapScopes: boolean
+ mapScopes: boolean,
+ shouldPauseOnExceptions: boolean,
+ shouldPauseOnCaughtExceptions: boolean
};
export const createPauseState = (): PauseState => ({
@@ -87,7 +87,9 @@ export const createPauseState = (): PauseState => ({
threads: {},
canRewind: false,
skipPausing: prefs.skipPausing,
- mapScopes: prefs.mapScopes
+ mapScopes: prefs.mapScopes,
+ shouldPauseOnExceptions: prefs.pauseOnExceptions,
+ shouldPauseOnCaughtExceptions: prefs.pauseOnCaughtExceptions
});
const resumedPauseState = {
@@ -105,15 +107,13 @@ const resumedPauseState = {
const createInitialPauseState = () => ({
...resumedPauseState,
isWaitingOnBreak: false,
- shouldPauseOnExceptions: prefs.pauseOnExceptions,
- shouldPauseOnCaughtExceptions: prefs.pauseOnCaughtExceptions,
canRewind: false,
command: null,
lastCommand: null,
previousLocation: null
});
-function getThreadPauseState(state: PauseState, thread: string) {
+function getThreadPauseState(state: PauseState, thread: ThreadId) {
// Thread state is lazily initialized so that we don't have to keep track of
// the current set of worker threads.
return state.threads[thread] || createInitialPauseState();
@@ -194,9 +194,6 @@ function update(
});
}
- case "TRAVEL_TO":
- return updateThreadState({ ...action.data.paused });
-
case "MAP_SCOPES": {
const { frame, status, value } = action;
const selectedFrameId = frame.id;
@@ -258,10 +255,11 @@ function update(
// Preserving for the old debugger
prefs.ignoreCaughtExceptions = !shouldPauseOnCaughtExceptions;
- return updateThreadState({
+ return {
+ ...state,
shouldPauseOnExceptions,
shouldPauseOnCaughtExceptions
- });
+ };
}
case "COMMAND":
@@ -350,77 +348,80 @@ function getPauseLocation(state, action) {
// (right now) to type those wrapped functions.
type OuterState = State;
-function getCurrentPauseState(state: OuterState): ThreadPauseState {
- return getThreadPauseState(state.pause, state.pause.currentThread);
+export function getAllPopupObjectProperties(
+ state: OuterState,
+ thread: ThreadId
+) {
+ return getThreadPauseState(state.pause, thread).loadedObjects;
}
-export const getAllPopupObjectProperties: Selector<{}> = createSelector(
- getCurrentPauseState,
- pauseWrapper => pauseWrapper.loadedObjects
-);
-
-export function getPauseReason(state: OuterState): ?Why {
- return getCurrentPauseState(state).why;
+export function getPauseReason(state: OuterState, thread: ThreadId): ?Why {
+ return getThreadPauseState(state.pause, thread).why;
}
-export function getPauseCommand(state: OuterState): Command {
- return getCurrentPauseState(state).command;
+export function getPauseCommand(state: OuterState, thread: ThreadId): Command {
+ return getThreadPauseState(state.pause, thread).command;
}
-export function wasStepping(state: OuterState): boolean {
- return getCurrentPauseState(state).wasStepping;
+export function wasStepping(state: OuterState, thread: ThreadId): boolean {
+ return getThreadPauseState(state.pause, thread).wasStepping;
}
-export function isStepping(state: OuterState) {
- return ["stepIn", "stepOver", "stepOut"].includes(getPauseCommand(state));
+export function isStepping(state: OuterState, thread: ThreadId) {
+ return ["stepIn", "stepOver", "stepOut"].includes(
+ getPauseCommand(state, thread)
+ );
}
export function getCurrentThread(state: OuterState) {
return state.pause.currentThread;
}
-export function getThreadIsPaused(state: OuterState, thread: string) {
+export function getIsPaused(state: OuterState, thread: ThreadId) {
return !!getThreadPauseState(state.pause, thread).frames;
}
-export function isPaused(state: OuterState) {
- return !!getFrames(state);
-}
-
-export function getIsPaused(state: OuterState) {
- return !!getFrames(state);
-}
-
-export function getPreviousPauseFrameLocation(state: OuterState) {
- return getCurrentPauseState(state).previousLocation;
+export function getPreviousPauseFrameLocation(
+ state: OuterState,
+ thread: ThreadId
+) {
+ return getThreadPauseState(state.pause, thread).previousLocation;
}
-export function isEvaluatingExpression(state: OuterState) {
- return getCurrentPauseState(state).command === "expression";
+export function isEvaluatingExpression(state: OuterState, thread: ThreadId) {
+ return getThreadPauseState(state.pause, thread).command === "expression";
}
-export function getPopupObjectProperties(state: OuterState, objectId: string) {
- return getAllPopupObjectProperties(state)[objectId];
+export function getPopupObjectProperties(
+ state: OuterState,
+ thread: ThreadId,
+ objectId: string
+) {
+ return getAllPopupObjectProperties(state, thread)[objectId];
}
-export function getIsWaitingOnBreak(state: OuterState) {
- return getCurrentPauseState(state).isWaitingOnBreak;
+export function getIsWaitingOnBreak(state: OuterState, thread: ThreadId) {
+ return getThreadPauseState(state.pause, thread).isWaitingOnBreak;
}
export function getShouldPauseOnExceptions(state: OuterState) {
- return getCurrentPauseState(state).shouldPauseOnExceptions;
+ return state.pause.shouldPauseOnExceptions;
}
export function getShouldPauseOnCaughtExceptions(state: OuterState) {
- return getCurrentPauseState(state).shouldPauseOnCaughtExceptions;
+ return state.pause.shouldPauseOnCaughtExceptions;
}
export function getCanRewind(state: OuterState) {
return state.pause.canRewind;
}
-export function getFrames(state: OuterState) {
- return getCurrentPauseState(state).frames;
+export function getFrames(state: OuterState, thread: ThreadId) {
+ return getThreadPauseState(state.pause, thread).frames;
+}
+
+export function getCurrentThreadFrames(state: OuterState) {
+ return getThreadPauseState(state.pause, getCurrentThread(state)).frames;
}
function getGeneratedFrameId(frameId: string): string {
@@ -431,16 +432,21 @@ function getGeneratedFrameId(frameId: string): string {
return frameId;
}
-export function getGeneratedFrameScope(state: OuterState, frameId: ?string) {
+export function getGeneratedFrameScope(
+ state: OuterState,
+ thread: ThreadId,
+ frameId: ?string
+) {
if (!frameId) {
return null;
}
- return getFrameScopes(state).generated[getGeneratedFrameId(frameId)];
+ return getFrameScopes(state, thread).generated[getGeneratedFrameId(frameId)];
}
export function getOriginalFrameScope(
state: OuterState,
+ thread: ThreadId,
sourceId: ?SourceId,
frameId: ?string
): ?{
@@ -452,7 +458,9 @@ export function getOriginalFrameScope(
}
const isGenerated = isGeneratedId(sourceId);
- const original = getFrameScopes(state).original[getGeneratedFrameId(frameId)];
+ const original = getFrameScopes(state, thread).original[
+ getGeneratedFrameId(frameId)
+ ];
if (!isGenerated && original && (original.pending || original.scope)) {
return original;
@@ -461,13 +469,13 @@ export function getOriginalFrameScope(
return null;
}
-export function getFrameScopes(state: OuterState) {
- return getCurrentPauseState(state).frameScopes;
+export function getFrameScopes(state: OuterState, thread: ThreadId) {
+ return getThreadPauseState(state.pause, thread).frameScopes;
}
-export function getSelectedFrameBindings(state: OuterState) {
- const scopes = getFrameScopes(state);
- const selectedFrameId = getSelectedFrameId(state);
+export function getSelectedFrameBindings(state: OuterState, thread: ThreadId) {
+ const scopes = getFrameScopes(state, thread);
+ const selectedFrameId = getSelectedFrameId(state, thread);
if (!scopes || !selectedFrameId) {
return null;
}
@@ -498,6 +506,7 @@ export function getSelectedFrameBindings(state: OuterState) {
export function getFrameScope(
state: OuterState,
+ thread: ThreadId,
sourceId: ?SourceId,
frameId: ?string
): ?{
@@ -505,16 +514,16 @@ export function getFrameScope(
+scope: OriginalScope | Scope
} {
return (
- getOriginalFrameScope(state, sourceId, frameId) ||
- getGeneratedFrameScope(state, frameId)
+ getOriginalFrameScope(state, thread, sourceId, frameId) ||
+ getGeneratedFrameScope(state, thread, frameId)
);
}
-export function getSelectedScope(state: OuterState) {
+export function getSelectedScope(state: OuterState, thread: ThreadId) {
const sourceId = getSelectedSourceId(state);
- const frameId = getSelectedFrameId(state);
+ const frameId = getSelectedFrameId(state, thread);
- const frameScope = getFrameScope(state, sourceId, frameId);
+ const frameScope = getFrameScope(state, thread, sourceId, frameId);
if (!frameScope) {
return null;
}
@@ -522,51 +531,40 @@ export function getSelectedScope(state: OuterState) {
return frameScope.scope || null;
}
-export function getSelectedOriginalScope(state: OuterState) {
+export function getSelectedOriginalScope(state: OuterState, thread: ThreadId) {
const sourceId = getSelectedSourceId(state);
- const frameId = getSelectedFrameId(state);
- return getOriginalFrameScope(state, sourceId, frameId);
+ const frameId = getSelectedFrameId(state, thread);
+ return getOriginalFrameScope(state, thread, sourceId, frameId);
}
-export function getSelectedGeneratedScope(state: OuterState) {
- const frameId = getSelectedFrameId(state);
- return getGeneratedFrameScope(state, frameId);
+export function getSelectedGeneratedScope(state: OuterState, thread: ThreadId) {
+ const frameId = getSelectedFrameId(state, thread);
+ return getGeneratedFrameScope(state, thread, frameId);
}
export function getSelectedScopeMappings(
- state: OuterState
+ state: OuterState,
+ thread: ThreadId
): {
[string]: string | null
} | null {
- const frameId = getSelectedFrameId(state);
+ const frameId = getSelectedFrameId(state, thread);
if (!frameId) {
return null;
}
- return getFrameScopes(state).mappings[frameId];
+ return getFrameScopes(state, thread).mappings[frameId];
}
-export function getSelectedFrameId(state: OuterState) {
- return getCurrentPauseState(state).selectedFrameId;
+export function getSelectedFrameId(state: OuterState, thread: ThreadId) {
+ return getThreadPauseState(state.pause, thread).selectedFrameId;
}
-export function getTopFrame(state: OuterState) {
- const frames = getFrames(state);
+export function getTopFrame(state: OuterState, thread: ThreadId) {
+ const frames = getFrames(state, thread);
return frames && frames[0];
}
-export const getSelectedFrame: Selector = createSelector(
- getSelectedFrameId,
- getFrames,
- (selectedFrameId, frames) => {
- if (!frames) {
- return null;
- }
-
- return frames.find(frame => frame.id == selectedFrameId);
- }
-);
-
export function getSkipPausing(state: OuterState) {
return state.pause.skipPausing;
}
@@ -576,8 +574,8 @@ export function getMapScopes(state: OuterState) {
}
// NOTE: currently only used for chrome
-export function getChromeScopes(state: OuterState) {
- const frame: ?ChromeFrame = (getSelectedFrame(state): any);
+export function getChromeScopes(state: OuterState, thread: ThreadId) {
+ const frame: ?ChromeFrame = (getSelectedFrame(state, thread): any);
return frame ? frame.scopeChain : undefined;
}
diff --git a/src/reducers/pending-breakpoints.js b/src/reducers/pending-breakpoints.js
index e3a4ac96a2..b9e505cba0 100644
--- a/src/reducers/pending-breakpoints.js
+++ b/src/reducers/pending-breakpoints.js
@@ -161,9 +161,12 @@ export function getPendingBreakpointsForSource(
return [];
}
- return getPendingBreakpointList(state).filter(
- pendingBreakpoint => pendingBreakpoint.location.sourceUrl === source.url
- );
+ return getPendingBreakpointList(state).filter(pendingBreakpoint => {
+ return (
+ pendingBreakpoint.location.sourceUrl === source.url ||
+ pendingBreakpoint.generatedLocation.sourceUrl == source.url
+ );
+ });
}
export default update;
diff --git a/src/reducers/sources.js b/src/reducers/sources.js
index d53ebf521e..7092d8ec80 100644
--- a/src/reducers/sources.js
+++ b/src/reducers/sources.js
@@ -16,8 +16,7 @@ import {
getRelativeUrl,
isGenerated,
isOriginal as isOriginalSource,
- isUrlExtension,
- getPlainUrl
+ isUrlExtension
} from "../utils/source";
import { originalToGeneratedId } from "devtools-source-map";
@@ -27,7 +26,7 @@ import type { Source, SourceId, SourceLocation, ThreadId } from "../types";
import type { PendingSelectedLocation, Selector } from "./types";
import type { Action, DonePromiseAction, FocusItem } from "../actions/types";
import type { LoadSourceAction } from "../actions/types/SourceAction";
-import { mapValues, uniqBy } from "lodash";
+import { mapValues, uniqBy, uniq } from "lodash";
export type SourcesMap = { [SourceId]: Source };
export type SourcesMapByThread = { [ThreadId]: SourcesMap };
@@ -35,9 +34,10 @@ export type SourcesMapByThread = { [ThreadId]: SourcesMap };
type UrlsMap = { [string]: SourceId[] };
type DisplayedSources = { [ThreadId]: { [SourceId]: boolean } };
type GetDisplayedSourcesSelector = OuterState => { [ThreadId]: SourcesMap };
-type PlainUrlsMap = { [string]: string[] };
export type SourcesState = {
+ epoch: number,
+
// All known sources.
sources: SourcesMap,
@@ -45,11 +45,6 @@ export type SourcesState = {
// sources can have the same URL.
urls: UrlsMap,
- // All full URLs belonging to a given plain (query string stripped) URL.
- // Query strings are only shown in the Sources tab if they are required for
- // disambiguation.
- plainUrls: PlainUrlsMap,
-
// For each thread, all sources in that thread that are under the project root
// and should be shown in the editor's sources pane.
displayed: DisplayedSources,
@@ -64,13 +59,13 @@ export type SourcesState = {
const emptySources = {
sources: {},
urls: {},
- plainUrls: {},
displayed: {}
};
export function initialSourcesState(): SourcesState {
return {
...emptySources,
+ epoch: 1,
selectedLocation: undefined,
pendingSelectedLocation: prefs.pendingSelectedLocation,
projectDirectoryRoot: prefs.projectDirectoryRoot,
@@ -172,7 +167,10 @@ function update(
return updateProjectDirectoryRoot(state, action.url);
case "NAVIGATE":
- return initialSourcesState();
+ return {
+ ...initialSourcesState(),
+ epoch: state.epoch + 1
+ };
case "SET_FOCUSED_SOURCE_ITEM":
return { ...state, focusedItem: action.item };
@@ -187,6 +185,15 @@ function update(
*/
function updateSource(state: SourcesState, source: Object) {
const existingSource = state.sources[source.id];
+
+ // 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) {
+ // TODO: We may want to consider throwing here once we have a better
+ // handle on async action flow control.
+ return state;
+ }
return {
...state,
sources: {
@@ -220,7 +227,6 @@ function addSources(state: SourcesState, sources: Source[]) {
...state,
sources: { ...state.sources },
urls: { ...state.urls },
- plainUrls: { ...state.plainUrls },
displayed: { ...state.displayed }
};
@@ -247,16 +253,7 @@ function addSources(state: SourcesState, sources: Source[]) {
state.urls[source.url] = [...existing, source.id];
}
- // 3. Update the plain url map
- if (source.url) {
- const plainUrl = getPlainUrl(source.url);
- const existingPlainUrls = state.plainUrls[plainUrl] || [];
- if (!existingPlainUrls.includes(source.url)) {
- state.plainUrls[plainUrl] = [...existingPlainUrls, source.url];
- }
- }
-
- // 4. Update the displayed actor map
+ // 3. Update the displayed actor map
if (
underRoot(source, state.projectDirectoryRoot) &&
(!source.isExtension ||
@@ -305,19 +302,24 @@ function updateProjectDirectoryRoot(state: SourcesState, root: string) {
* Update a source's loaded state fields
* i.e. loadedState, text, error
*/
-function updateLoadedState(state, action: LoadSourceAction): SourcesState {
+function updateLoadedState(
+ state: SourcesState,
+ action: LoadSourceAction
+): SourcesState {
const { sourceId } = action;
let source;
+ // 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;
+ }
+
if (action.status === "start") {
source = { id: sourceId, loadedState: "loading" };
} else if (action.status === "error") {
source = { id: sourceId, error: action.error, loadedState: "loaded" };
} else {
- if (!action.value) {
- return state;
- }
-
source = {
id: sourceId,
text: action.value.text,
@@ -369,6 +371,15 @@ function getSourceActors(state, source) {
return generatedSource ? generatedSource.actors : [];
}
+export function getSourceThreads(
+ state: OuterState,
+ source: Source
+): ThreadId[] {
+ return uniq(
+ getSourceActors(state.sources, source).map(actor => actor.thread)
+ );
+}
+
export function getSourceInSources(sources: SourcesMap, id: string): ?Source {
return sources[id];
}
@@ -497,14 +508,17 @@ export function hasPrettySource(state: OuterState, id: string) {
export function getSourcesUrlsInSources(
state: OuterState,
- url: ?string
+ url: string
): string[] {
- if (!url) {
+ const urls = getUrls(state);
+ if (!url || !urls[url]) {
return [];
}
+ const plainUrl = url.split("?")[0];
- const plainUrl = getPlainUrl(url);
- return getPlainUrls(state)[plainUrl] || [];
+ return Object.keys(urls)
+ .filter(Boolean)
+ .filter(sourceUrl => sourceUrl.split("?")[0] === plainUrl);
}
export function getHasSiblingOfSameName(state: OuterState, source: ?Source) {
@@ -519,12 +533,12 @@ export function getSources(state: OuterState) {
return state.sources.sources;
}
-export function getUrls(state: OuterState) {
- return state.sources.urls;
+export function getSourcesEpoch(state: OuterState) {
+ return state.sources.epoch;
}
-export function getPlainUrls(state: OuterState) {
- return state.sources.plainUrls;
+export function getUrls(state: OuterState) {
+ return state.sources.urls;
}
export function getSourceList(state: OuterState): Source[] {
diff --git a/src/selectors/breakpointSources.js b/src/selectors/breakpointSources.js
index f6354a22fb..227ce881eb 100644
--- a/src/selectors/breakpointSources.js
+++ b/src/selectors/breakpointSources.js
@@ -13,6 +13,7 @@ import {
} from "../selectors";
import { getFilename } from "../utils/source";
import { getSelectedLocation } from "../utils/source-maps";
+import { sortSelectedBreakpoints } from "../utils/breakpoint";
import type { Source, Breakpoint } from "../types";
import type { Selector, SourcesMap } from "../reducers/types";
@@ -27,12 +28,7 @@ function getBreakpointsForSource(
selectedSource: ?Source,
breakpoints: Breakpoint[]
) {
- return breakpoints
- .sort(
- (a, b) =>
- getSelectedLocation(a, selectedSource).line -
- getSelectedLocation(b, selectedSource).line
- )
+ return sortSelectedBreakpoints(breakpoints, selectedSource)
.filter(
bp =>
!bp.options.hidden &&
diff --git a/src/selectors/breakpoints.js b/src/selectors/breakpoints.js
index 985e14d252..f068dedd37 100644
--- a/src/selectors/breakpoints.js
+++ b/src/selectors/breakpoints.js
@@ -11,6 +11,7 @@ import type {
XHRBreakpointsList
} from "../reducers/breakpoints";
import type { Selector } from "../reducers/types";
+import type { Breakpoint } from "../types";
type OuterState = { breakpoints: BreakpointsState };
@@ -29,3 +30,8 @@ export const shouldPauseOnAnyXHR: Selector = createSelector(
return !emptyBp.disabled;
}
);
+
+export const getBreakpointsList: Selector = createSelector(
+ (state: OuterState) => state.breakpoints.breakpoints,
+ breakpoints => (Object.values(breakpoints): any)
+);
diff --git a/src/selectors/getCallStackFrames.js b/src/selectors/getCallStackFrames.js
index 6f1425ea9e..36e0e605ba 100644
--- a/src/selectors/getCallStackFrames.js
+++ b/src/selectors/getCallStackFrames.js
@@ -9,7 +9,7 @@ import {
getSelectedSource,
getSourceInSources
} from "../reducers/sources";
-import { getFrames } from "../reducers/pause";
+import { getCurrentThreadFrames } from "../reducers/pause";
import { annotateFrames } from "../utils/pause/frames";
import { isOriginal } from "../utils/source";
import { get } from "lodash";
@@ -65,7 +65,7 @@ export function formatCallStackFrames(
// eslint-disable-next-line
export const getCallStackFrames: State => Frame[] = (createSelector: any)(
- getFrames,
+ getCurrentThreadFrames,
getSources,
getSelectedSource,
formatCallStackFrames
diff --git a/src/selectors/inComponent.js b/src/selectors/inComponent.js
index 5cc9f5c7a0..99ef466874 100644
--- a/src/selectors/inComponent.js
+++ b/src/selectors/inComponent.js
@@ -4,14 +4,15 @@
// @flow
-import { getSymbols, getSource, getSelectedFrame } from ".";
+import { getSymbols, getSource, getSelectedFrame, getCurrentThread } from ".";
import { findClosestClass } from "../utils/ast";
import { getSourceMetaData } from "../reducers/ast";
import type { State } from "../reducers/types";
export function inComponent(state: State) {
- const selectedFrame = getSelectedFrame(state);
+ const thread = getCurrentThread(state);
+ const selectedFrame = getSelectedFrame(state, thread);
if (!selectedFrame) {
return;
}
diff --git a/src/selectors/index.js b/src/selectors/index.js
index b09e3a6ce8..411b675c52 100644
--- a/src/selectors/index.js
+++ b/src/selectors/index.js
@@ -35,10 +35,14 @@ export {
export { inComponent } from "./inComponent";
export { isSelectedFrameVisible } from "./isSelectedFrameVisible";
export { getCallStackFrames } from "./getCallStackFrames";
-export { getVisibleSelectedFrame } from "./visibleSelectedFrame";
export { getBreakpointSources } from "./breakpointSources";
export { getXHRBreakpoints, shouldPauseOnAnyXHR } from "./breakpoints";
export * from "./visibleColumnBreakpoints";
+export {
+ getSelectedFrame,
+ getSelectedFrames,
+ getVisibleSelectedFrame
+} from "./pause";
import { objectInspector } from "devtools-reps";
diff --git a/src/selectors/isSelectedFrameVisible.js b/src/selectors/isSelectedFrameVisible.js
index 7742623e8d..581ce78f73 100644
--- a/src/selectors/isSelectedFrameVisible.js
+++ b/src/selectors/isSelectedFrameVisible.js
@@ -5,8 +5,7 @@
// @flow
import { originalToGeneratedId, isOriginalId } from "devtools-source-map";
-import { getSelectedFrame } from "../reducers/pause";
-import { getSelectedLocation } from "../reducers/sources";
+import { getSelectedFrame, getSelectedLocation, getCurrentThread } from ".";
import type { State } from "../reducers/types";
function getGeneratedId(sourceId) {
@@ -22,8 +21,9 @@ function getGeneratedId(sourceId) {
* selected.
*/
export function isSelectedFrameVisible(state: State) {
+ const thread = getCurrentThread(state);
const selectedLocation = getSelectedLocation(state);
- const selectedFrame = getSelectedFrame(state);
+ const selectedFrame = getSelectedFrame(state, thread);
if (!selectedFrame || !selectedLocation) {
return false;
diff --git a/src/selectors/pause.js b/src/selectors/pause.js
new file mode 100644
index 0000000000..62644b8c04
--- /dev/null
+++ b/src/selectors/pause.js
@@ -0,0 +1,57 @@
+/* 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 . */
+
+import { getCurrentThread } from "../reducers/pause";
+import { getSelectedLocation } from "../reducers/sources";
+
+// eslint-disable-next-line
+import { getSelectedLocation as _getSelectedLocation } from "../utils/source-maps";
+import { createSelector } from "reselect";
+
+import type { Frame, SourceLocation } from "../types";
+import type { Selector, State } from "../reducers/types";
+
+export const getSelectedFrames: Selector<{ [string]: Frame }> = createSelector(
+ state => state.pause,
+ pauseState => {
+ const selectedFrames = {};
+ for (const thread in pauseState.threads) {
+ const pausedThread = pauseState.threads[thread];
+ const { selectedFrameId, frames } = pausedThread;
+ if (frames) {
+ selectedFrames[thread] = frames.find(
+ frame => frame.id == selectedFrameId
+ );
+ }
+ }
+ return selectedFrames;
+ }
+);
+
+export function getSelectedFrame(state: State, thread: ThreadId) {
+ const selectedFrames = getSelectedFrames(state);
+ return selectedFrames[thread];
+}
+
+export const getVisibleSelectedFrame: Selector{
+ id: string,
+ location: SourceLocation
+}> = createSelector(
+ getSelectedLocation,
+ getSelectedFrames,
+ getCurrentThread,
+ (selectedLocation, selectedFrames, thread) => {
+ const selectedFrame = selectedFrames[thread];
+ if (!selectedFrame) {
+ return null;
+ }
+
+ const { id } = selectedFrame;
+
+ return {
+ id,
+ location: _getSelectedLocation(selectedFrame, selectedLocation)
+ };
+ }
+);
diff --git a/src/selectors/visibleColumnBreakpoints.js b/src/selectors/visibleColumnBreakpoints.js
index c96df4c8fd..22740e3b5f 100644
--- a/src/selectors/visibleColumnBreakpoints.js
+++ b/src/selectors/visibleColumnBreakpoints.js
@@ -44,16 +44,13 @@ function contains(location: PartialPosition, range: Range) {
);
}
-function inViewport(viewport, location) {
- return viewport && contains(location, viewport);
-}
-
function groupBreakpoints(breakpoints, selectedSource) {
if (!breakpoints) {
return {};
}
+
const map: any = groupBy(
- breakpoints,
+ breakpoints.filter(breakpoint => !breakpoint.options.hidden),
breakpoint => getSelectedLocation(breakpoint, selectedSource).line
);
@@ -97,7 +94,7 @@ function filterByLineCount(positions, selectedSource) {
function filterVisible(positions, selectedSource, viewport) {
return positions.filter(columnBreakpoint => {
const location = getSelectedLocation(columnBreakpoint, selectedSource);
- return inViewport(viewport, location);
+ return viewport && contains(location, viewport);
});
}
@@ -177,19 +174,3 @@ export function getFirstBreakpointPosition(
position => getSelectedLocation(position, source).line == line
);
}
-
-export function getFirstVisibleBreakpointPosition(
- state: State,
- { line }: SourceLocation
-) {
- const positions = getVisibleBreakpointPositions(state);
- const selectedSource = getSelectedSource(state);
-
- if (!selectedSource || !positions) {
- return;
- }
-
- return positions.find(
- position => getSelectedLocation(position, selectedSource).line == line
- );
-}
diff --git a/src/utils/breakpoint/breakpointPositions.js b/src/utils/breakpoint/breakpointPositions.js
index b5046cb111..f4e6d5b6a5 100644
--- a/src/utils/breakpoint/breakpointPositions.js
+++ b/src/utils/breakpoint/breakpointPositions.js
@@ -5,19 +5,18 @@
* file, You can obtain one at . */
import { comparePosition } from "../location";
-import type {
- BreakpointPositions,
- SourceLocation,
- Position
-} from "../../types";
+import { getSelectedLocation } from "../source-maps";
+import type { BreakpointPositions, SourceLocation } from "../../types";
export function findPosition(
positions: ?BreakpointPositions,
- location: Position | SourceLocation
+ location: SourceLocation
) {
if (!positions) {
return null;
}
- return positions.find(pos => comparePosition(pos.location, location));
+ return positions.find(pos =>
+ comparePosition(getSelectedLocation(pos, location), location)
+ );
}
diff --git a/src/utils/breakpoint/index.js b/src/utils/breakpoint/index.js
index 44ea08d81d..128b7fa1ba 100644
--- a/src/utils/breakpoint/index.js
+++ b/src/utils/breakpoint/index.js
@@ -232,7 +232,7 @@ export function getSelectedText(
export function sortSelectedBreakpoints(
breakpoints: Breakpoint[],
- selectedSource: Source
+ selectedSource: ?Source
): Breakpoint[] {
return sortBy(breakpoints, [
// Priority: line number, undefined column, column number
diff --git a/src/utils/dbg.js b/src/utils/dbg.js
index bca5021996..5ca5f390db 100644
--- a/src/utils/dbg.js
+++ b/src/utils/dbg.js
@@ -46,12 +46,24 @@ function getCM() {
return cm && cm.CodeMirror;
}
-function _formatColumnBreapoints(dbg: Object) {
- console.log(
- dbg.selectors.formatColumnBreakpoints(
- dbg.selectors.visibleColumnBreakpoints()
- )
+function formatMappedLocation(mappedLocation) {
+ const { location, generatedLocation } = mappedLocation;
+ return {
+ original: `(${location.line}, ${location.column})`,
+ generated: `(${generatedLocation.line}, ${generatedLocation.column})`
+ };
+}
+
+function formatMappedLocations(locations) {
+ return console.table(locations.map(loc => formatMappedLocation(loc)));
+}
+
+function formatSelectedColumnBreakpoints(dbg) {
+ const positions = dbg.selectors.getBreakpointPositionsForSource(
+ dbg.selectors.getSelectedSource().id
);
+
+ return formatMappedLocations(positions);
}
export function setupHelper(obj: Object) {
@@ -73,7 +85,9 @@ export function setupHelper(obj: Object) {
dumpThread: () => sendPacketToThread(dbg, { type: "dumpThread" })
},
formatters: {
- visibleColumnBreakpoints: () => _formatColumnBreapoints(dbg)
+ mappedLocations: locations => formatMappedLocations(locations),
+ mappedLocation: location => formatMappedLocation(location),
+ selectedColumnBreakpoints: () => formatSelectedColumnBreakpoints(dbg)
},
_telemetry: {
events: {}
diff --git a/src/utils/editor/index.js b/src/utils/editor/index.js
index 6b699a5f89..84e3b3ee64 100644
--- a/src/utils/editor/index.js
+++ b/src/utils/editor/index.js
@@ -11,7 +11,7 @@ export * from "../ui";
export { onMouseOver } from "./token-events";
import { createEditor } from "./create-editor";
-import { shouldPrettyPrint } from "../source";
+import { shouldPrettyPrint, isOriginal } from "../source";
import { findNext, findPrev } from "./source-search";
import { isWasm, lineToWasmOffset, wasmOffsetToLine } from "../wasm";
@@ -62,6 +62,16 @@ export function shouldShowPrettyPrint(source: Source) {
return shouldPrettyPrint(source);
}
+export function shouldShowFooter(source: ?Source, horizontal: boolean) {
+ if (!horizontal) {
+ return true;
+ }
+ if (!source) {
+ return false;
+ }
+ return shouldShowPrettyPrint(source) || isOriginal(source);
+}
+
export function traverseResults(
e: Event,
ctx: any,
@@ -152,6 +162,12 @@ function isVisible(codeMirror: any, top: number, left: number) {
export function getLocationsInViewport({ codeMirror }: Object) {
// Get scroll position
+ if (!codeMirror) {
+ return {
+ start: { line: 0, column: 0 },
+ end: { line: 0, column: 0 }
+ };
+ }
const charWidth = codeMirror.defaultCharWidth();
const scrollArea = codeMirror.getScrollInfo();
const { scrollLeft } = codeMirror.doc;
diff --git a/src/utils/editor/tests/editor.spec.js b/src/utils/editor/tests/editor.spec.js
index 9be3cdb12c..5c10e1dac4 100644
--- a/src/utils/editor/tests/editor.spec.js
+++ b/src/utils/editor/tests/editor.spec.js
@@ -6,6 +6,7 @@
import {
shouldShowPrettyPrint,
+ shouldShowFooter,
traverseResults,
toEditorLine,
toEditorPosition,
@@ -24,6 +25,15 @@ import {
import { makeMockSource } from "../../test-mockup";
+function makeSource() {
+ return makeMockSource(
+ "http://example.com/index.js",
+ "test-id-123/originalSource",
+ "text/javascript",
+ "some text here"
+ );
+}
+
describe("shouldShowPrettyPrint", () => {
it("shows pretty print for a source", () => {
const source = makeMockSource(
@@ -36,6 +46,20 @@ describe("shouldShowPrettyPrint", () => {
});
});
+describe("shouldShowFooter", () => {
+ it("shows footer when not horizontal", () => {
+ expect(shouldShowFooter(makeSource(), false)).toEqual(true);
+ });
+
+ it("does not show footer when no source is selected", () => {
+ expect(shouldShowFooter(null, true)).toEqual(false);
+ });
+
+ it("shows if pretty print should show", () => {
+ expect(shouldShowFooter(makeSource(), true)).toEqual(true);
+ });
+});
+
describe("traverseResults", () => {
const e: any = { stopPropagation: jest.fn(), preventDefault: jest.fn() };
const ctx = {};
diff --git a/src/utils/empty-lines.js b/src/utils/empty-lines.js
new file mode 100644
index 0000000000..15c922316e
--- /dev/null
+++ b/src/utils/empty-lines.js
@@ -0,0 +1,30 @@
+/* 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 . */
+
+// @flow
+
+import { xor, range } from "lodash";
+import { getSelectedLocation } from "./source-maps";
+import type { BreakpointPositions, Source } from "../types";
+
+export function findEmptyLines(
+ source: Source,
+ breakpointPositions: BreakpointPositions
+): number[] {
+ if (!breakpointPositions || source.isWasm) {
+ return [];
+ }
+
+ const sourceText = source.text || "";
+ const lineCount = sourceText.split("\n").length;
+ const sourceLines = range(1, lineCount + 1);
+
+ const breakpointLines = breakpointPositions
+ .map(point => getSelectedLocation(point, source).line)
+ // NOTE: at the moment it is possible the location is an unmapped generated
+ // line which could be greater than the line count.
+ .filter(line => line <= lineCount);
+
+ return xor(sourceLines, breakpointLines);
+}
diff --git a/src/utils/prefs.js b/src/utils/prefs.js
index 7c8611fec2..2489355557 100644
--- a/src/utils/prefs.js
+++ b/src/utils/prefs.js
@@ -5,11 +5,11 @@
// @flow
import { PrefsHelper } from "devtools-modules";
-import { isDevelopment, isTesting } from "devtools-environment";
+import { isDevelopment } from "devtools-environment";
import Services from "devtools-services";
import { asyncStoreHelper } from "./asyncStoreHelper";
-const prefsSchemaVersion = "1.0.8";
+const prefsSchemaVersion = "1.0.9";
const pref = Services.pref;
if (isDevelopment()) {
@@ -54,7 +54,7 @@ if (isDevelopment()) {
pref("devtools.debugger.features.remove-command-bar-options", true);
pref("devtools.debugger.features.code-folding", false);
pref("devtools.debugger.features.outline", true);
- pref("devtools.debugger.features.column-breakpoints", false);
+ pref("devtools.debugger.features.column-breakpoints", true);
pref("devtools.debugger.features.skip-pausing", true);
pref("devtools.debugger.features.component-pane", false);
pref("devtools.debugger.features.autocomplete-expressions", false);
@@ -132,10 +132,12 @@ export const asyncStore = asyncStoreHelper("debugger", {
eventListenerBreakpoints: ["event-listener-breakpoints", []]
});
-if (!isTesting && prefs.debuggerPrefsSchemaVersion !== prefsSchemaVersion) {
- // clear pending Breakpoints
- asyncStore.pendingBreakpoints = {};
- asyncStore.tabs = [];
- asyncStore.xhrBreakpoints = [];
- prefs.debuggerPrefsSchemaVersion = prefsSchemaVersion;
+export function verifyPrefSchema() {
+ if (prefs.debuggerPrefsSchemaVersion !== prefsSchemaVersion) {
+ // clear pending Breakpoints
+ asyncStore.pendingBreakpoints = {};
+ asyncStore.tabs = [];
+ asyncStore.xhrBreakpoints = [];
+ prefs.debuggerPrefsSchemaVersion = prefsSchemaVersion;
+ }
}
diff --git a/src/utils/source-maps.js b/src/utils/source-maps.js
index 0090c74782..dd70b22528 100644
--- a/src/utils/source-maps.js
+++ b/src/utils/source-maps.js
@@ -6,7 +6,6 @@
import { isOriginalId } from "devtools-source-map";
import { getSource } from "../selectors";
-import { isGenerated } from "../utils/source";
import type { SourceLocation, MappedLocation, Source } from "../types";
import typeof SourceMaps from "../../packages/devtools-source-map/src";
@@ -104,9 +103,15 @@ export function isOriginalSource(source: ?Source) {
export function getSelectedLocation(
mappedLocation: MappedLocation,
- selectedSource: ?Source
+ context: ?(Source | SourceLocation)
): SourceLocation {
- return selectedSource && isGenerated(selectedSource)
- ? mappedLocation.generatedLocation
- : mappedLocation.location;
+ if (!context) {
+ return mappedLocation.location;
+ }
+
+ // $FlowIgnore
+ const sourceId = context.sourceId || context.id;
+ return isOriginalId(sourceId)
+ ? mappedLocation.location
+ : mappedLocation.generatedLocation;
}
diff --git a/src/utils/source.js b/src/utils/source.js
index 106ac5ce25..eb55bbdbd7 100644
--- a/src/utils/source.js
+++ b/src/utils/source.js
@@ -488,8 +488,3 @@ export function getSourceQueryString(source: ?Source) {
export function isUrlExtension(url: string) {
return /^(chrome|moz)-extension:\//.test(url);
}
-
-export function getPlainUrl(url: string): string {
- const queryStart = url.indexOf("?");
- return queryStart !== -1 ? url.slice(0, queryStart) : url;
-}
diff --git a/src/utils/test-head.js b/src/utils/test-head.js
index 3d71d8fc0d..6567adfbbc 100644
--- a/src/utils/test-head.js
+++ b/src/utils/test-head.js
@@ -69,6 +69,7 @@ function makeFrame({ id, sourceId }: Object, opts: Object = {}) {
id,
scope: { bindings: { variables: {}, arguments: [] } },
location: { sourceId, line: 4 },
+ thread: "FakeThread",
...opts
};
}
@@ -103,7 +104,8 @@ function makeSource(name: string, props: any = {}): Source {
function makeOriginalSource(name: string, props?: Object): Source {
const rv = {
...makeSourceRaw(name, props),
- id: `${name}/originalSource`
+ id: `${name}/originalSource`,
+ actors: []
};
return (rv: any);
}
@@ -157,6 +159,33 @@ function waitForState(store: any, predicate: any): Promise {
});
}
+function watchForState(store: any, predicate: any): () => boolean {
+ let sawState = false;
+ const checkState = function() {
+ if (!sawState && predicate(store.getState())) {
+ sawState = true;
+ }
+ return sawState;
+ };
+
+ let unsubscribe;
+ if (!checkState()) {
+ unsubscribe = store.subscribe(() => {
+ if (checkState()) {
+ unsubscribe();
+ }
+ });
+ }
+
+ return function read() {
+ if (unsubscribe) {
+ unsubscribe();
+ }
+
+ return sawState;
+ };
+}
+
function getTelemetryEvents(eventName: string) {
return window.dbg._telemetry.events[eventName] || [];
}
@@ -173,5 +202,6 @@ export {
makeOriginalSource,
makeSymbolDeclaration,
waitForState,
+ watchForState,
getHistory
};
diff --git a/src/utils/tests/empty-lines.spec.js b/src/utils/tests/empty-lines.spec.js
new file mode 100644
index 0000000000..9dccb9184e
--- /dev/null
+++ b/src/utils/tests/empty-lines.spec.js
@@ -0,0 +1,33 @@
+/* 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 . */
+
+// @flow
+
+import { findEmptyLines } from "../empty-lines";
+import { makeSource } from "../test-head";
+
+function ml(gLine) {
+ const generatedLocation = { line: gLine, column: 0, sourceId: "foo" };
+ return { generatedLocation, location: generatedLocation };
+}
+
+describe("emptyLines", () => {
+ it("no positions", () => {
+ const source = makeSource("foo", { text: "\n" });
+ const lines = findEmptyLines(source, []);
+ expect(lines).toEqual([1, 2]);
+ });
+
+ it("one position", () => {
+ const source = makeSource("foo", { text: "\n" });
+ const lines = findEmptyLines(source, [ml(1)]);
+ expect(lines).toEqual([2]);
+ });
+
+ it("outside positions are not included", () => {
+ const source = makeSource("foo", { text: "\n" });
+ const lines = findEmptyLines(source, [ml(10)]);
+ expect(lines).toEqual([1, 2]);
+ });
+});
diff --git a/src/workers/parser/getSymbols.js b/src/workers/parser/getSymbols.js
index 78ba366016..6618e85e27 100644
--- a/src/workers/parser/getSymbols.js
+++ b/src/workers/parser/getSymbols.js
@@ -100,9 +100,15 @@ function getUniqueIdentifiers(identifiers) {
}
/* eslint-disable complexity */
-function extractSymbol(path: SimplePath, symbols) {
+function extractSymbol(path: SimplePath, symbols, state) {
if (isFunction(path)) {
const name = getFunctionName(path.node, path.parent);
+
+ if (!state.fnCounts[name]) {
+ state.fnCounts[name] = 0;
+ }
+ const index = state.fnCounts[name]++;
+
symbols.functions.push({
name,
klass: inferClassName(path),
@@ -112,7 +118,7 @@ function extractSymbol(path: SimplePath, symbols) {
// indicates the occurence of the function in a file
// e.g { name: foo, ... index: 4 } is the 4th foo function
// in the file
- index: symbols.functions.filter(f => f.name === name).length
+ index
});
}
@@ -251,8 +257,7 @@ function extractSymbol(path: SimplePath, symbols) {
if (t.isVariableDeclarator(path)) {
const nodeId = path.node.id;
- const ids = getPatternIdentifiers(nodeId);
- symbols.identifiers = [...symbols.identifiers, ...ids];
+ symbols.identifiers.push(...getPatternIdentifiers(nodeId));
}
}
@@ -274,12 +279,16 @@ function extractSymbols(sourceId): SymbolDeclarations {
loading: false
};
+ const state = {
+ fnCounts: Object.create(null)
+ };
+
const ast = traverseAst(sourceId, {
enter(node: Node, ancestors: TraversalAncestors) {
try {
const path = createSimplePath(ancestors);
if (path) {
- extractSymbol(path, symbols);
+ extractSymbol(path, symbols, state);
}
} catch (e) {
console.error(e);
diff --git a/test/mochitest/browser.ini b/test/mochitest/browser.ini
index 42326569b5..8f1ee32076 100644
--- a/test/mochitest/browser.ini
+++ b/test/mochitest/browser.ini
@@ -593,6 +593,7 @@ support-files =
examples/doc-xhr.html
examples/doc-xhr-run-to-completion.html
examples/doc-scroll-run-to-completion.html
+ examples/pretty.js
examples/sum/sum.js
examples/sum/sum.min.js
examples/sum/sum.min.js.map
@@ -606,6 +607,7 @@ support-files =
examples/doc-async.html
examples/doc-asm.html
examples/doc-duplicate-functions.html
+ examples/doc-pretty.html
examples/doc-sourcemapped.html
examples/doc-content-script-sources.html
examples/doc-scripts.html
@@ -712,6 +714,7 @@ skip-if = ccov && os == 'win' # Bug 1443132
[browser_dbg-keyboard-shortcuts.js]
skip-if = os == "linux" # bug 1351952
[browser_dbg-layout-changes.js]
+[browser_dbg-log-points.js]
[browser_dbg-outline.js]
skip-if = verify
[browser_dbg-outline-pretty.js]
@@ -722,9 +725,10 @@ skip-if = !debug && (os == "win" && os_version == "6.1") # Bug 1456441
[browser_dbg-pause-ux.js]
skip-if = os == "win"
[browser_dbg-navigation.js]
-skip-if = (verify && debug && (os == 'mac'))
+skip-if = (verify && debug && (os == 'mac')) || (os == 'linux' && debug && bits == 64) || (os == 'mac' && debug) # Bug 1307249
[browser_dbg-minified.js]
[browser_dbg-pretty-print.js]
+[browser_dbg-pretty-print-breakpoints.js]
[browser_dbg-pretty-print-console.js]
[browser_dbg-pretty-print-paused.js]
[browser_dbg-preview.js]
diff --git a/test/mochitest/browser_dbg-breakpoints-actions.js b/test/mochitest/browser_dbg-breakpoints-actions.js
index 97f45151c7..bf94279a86 100644
--- a/test/mochitest/browser_dbg-breakpoints-actions.js
+++ b/test/mochitest/browser_dbg-breakpoints-actions.js
@@ -1,11 +1,11 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* 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 . */
-function openFirstBreakpointContextMenu(dbg){
- rightClickElement(dbg, "breakpointItem", 2);
+function openFirstBreakpointContextMenu(dbg) {
+ rightClickElement(dbg, "breakpointItem", 3);
}
-
// Tests to see if we can trigger a breakpoint action via the context menu
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html", "simple2");
@@ -14,11 +14,14 @@ add_task(async function() {
await addBreakpoint(dbg, "simple2", 3);
- openFirstBreakpointContextMenu(dbg)
+ openFirstBreakpointContextMenu(dbg);
// select "Remove breakpoint"
selectContextMenuItem(dbg, selectors.breakpointContextMenu.remove);
- await waitForState(dbg, state => dbg.selectors.getBreakpointCount(state) === 0);
+ await waitForState(
+ dbg,
+ state => dbg.selectors.getBreakpointCount(state) === 0
+ );
ok("successfully removed the breakpoint");
});
@@ -39,10 +42,11 @@ add_task(async function() {
// which promises get resolved. The problem seems to indicate a coverage gap
// in waitUntilService(). Workaround this by only waiting for one dispatch,
// though this is fragile and could break again in the future.
- let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /*2*/ 1);
+ let dispatched = waitForDispatch(dbg, "DISABLE_BREAKPOINT", /* 2*/ 1);
selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableOthers);
await waitForState(dbg, state =>
- dbg.selectors.getBreakpointsList(state)
+ dbg.selectors
+ .getBreakpointsList(state)
.every(bp => (bp.location.line !== 4) === bp.disabled)
);
await dispatched;
@@ -63,7 +67,8 @@ add_task(async function() {
dispatched = waitForDispatch(dbg, "ENABLE_BREAKPOINT", 2);
selectContextMenuItem(dbg, selectors.breakpointContextMenu.enableOthers);
await waitForState(dbg, state =>
- dbg.selectors.getBreakpointsList(state)
+ dbg.selectors
+ .getBreakpointsList(state)
.every(bp => (bp.location.line === 4) === bp.disabled)
);
await dispatched;
@@ -73,9 +78,11 @@ add_task(async function() {
// select "Remove Others"
dispatched = waitForDispatch(dbg, "REMOVE_BREAKPOINT", 2);
selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeOthers);
- await waitForState(dbg, state =>
- dbg.selectors.getBreakpointsList(state).length === 1 &&
- dbg.selectors.getBreakpointsList(state)[0].location.line === 4
+ await waitForState(
+ dbg,
+ state =>
+ dbg.selectors.getBreakpointsList(state).length === 1 &&
+ dbg.selectors.getBreakpointsList(state)[0].location.line === 4
);
await dispatched;
ok("remaining breakpoint should be on line 4");
diff --git a/test/mochitest/browser_dbg-breakpoints-cond.js b/test/mochitest/browser_dbg-breakpoints-cond.js
index e6da2bba8b..8030993790 100644
--- a/test/mochitest/browser_dbg-breakpoints-cond.js
+++ b/test/mochitest/browser_dbg-breakpoints-cond.js
@@ -144,7 +144,7 @@ add_task(async function() {
is(bp.options.condition, "1", "breakpoint is created with the condition");
assertEditorBreakpoint(dbg, 5, { hasCondition: true });
- rightClickElement(dbg, "breakpointItem", 2);
+ rightClickElement(dbg, "breakpointItem", 3);
info('select "remove condition"');
selectContextMenuItem(dbg, selectors.breakpointContextMenu.removeCondition);
await waitForBreakpointWithoutCondition(dbg, "simple2", 5);
diff --git a/test/mochitest/browser_dbg-breakpoints.js b/test/mochitest/browser_dbg-breakpoints.js
index c80e2a15d3..b7868d0d01 100644
--- a/test/mochitest/browser_dbg-breakpoints.js
+++ b/test/mochitest/browser_dbg-breakpoints.js
@@ -35,6 +35,20 @@ function enableBreakpoints(dbg, count) {
return enabled;
}
+function every(array, predicate) {
+ return !array.some(item => !predicate(item));
+}
+
+function subset(subArray, superArray) {
+ return every(subArray, subItem => superArray.includes(subItem));
+}
+
+function assertEmptyLines(dbg, lines) {
+ const sourceId = dbg.selectors.getSelectedSourceId(dbg.store.getState());
+ const emptyLines = dbg.selectors.getEmptyLines(dbg.store.getState(), sourceId);
+ ok(subset(lines, emptyLines), 'empty lines should match');
+}
+
// Test enabling and disabling a breakpoint using the check boxes
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html", "simple2");
@@ -65,7 +79,9 @@ add_task(async function() {
await addBreakpoint(dbg, "simple2", 3);
await addBreakpoint(dbg, "simple2", 5);
- rightClickElement(dbg, "breakpointItem", 2);
+ assertEmptyLines(dbg, [1,2]);
+
+ rightClickElement(dbg, "breakpointItem", 3);
const disableBreakpointDispatch = waitForDispatch(dbg, "DISABLE_BREAKPOINT");
selectContextMenuItem(dbg, selectors.breakpointContextMenu.disableSelf);
await disableBreakpointDispatch;
@@ -75,7 +91,7 @@ add_task(async function() {
is(bp1.disabled, true, "first breakpoint is disabled");
is(bp2.disabled, false, "second breakpoint is enabled");
- rightClickElement(dbg, "breakpointItem", 2);
+ rightClickElement(dbg, "breakpointItem", 3);
const enableBreakpointDispatch = waitForDispatch(dbg, "ENABLE_BREAKPOINT");
selectContextMenuItem(dbg, selectors.breakpointContextMenu.enableSelf);
await enableBreakpointDispatch;
diff --git a/test/mochitest/browser_dbg-call-stack.js b/test/mochitest/browser_dbg-call-stack.js
index ca5690abb2..9768a0627d 100644
--- a/test/mochitest/browser_dbg-call-stack.js
+++ b/test/mochitest/browser_dbg-call-stack.js
@@ -4,7 +4,13 @@
// checks to see if the frame is selected and the title is correct
function isFrameSelected(dbg, index, title) {
const $frame = findElement(dbg, "frame", index);
- const frame = dbg.selectors.getSelectedFrame(dbg.getState());
+
+ const {
+ selectors: { getSelectedFrame, getCurrentThread },
+ getState,
+ } = dbg;
+
+ const frame = getSelectedFrame(getState(), getCurrentThread(getState()));
const elSelected = $frame.classList.contains("selected");
const titleSelected = frame.displayName == title;
diff --git a/test/mochitest/browser_dbg-console-eval.js b/test/mochitest/browser_dbg-console-eval.js
index dad75d9ff2..40f552e761 100644
--- a/test/mochitest/browser_dbg-console-eval.js
+++ b/test/mochitest/browser_dbg-console-eval.js
@@ -16,20 +16,6 @@ function waitForConsolePanelChange(dbg) {
});
}
-function findMessages(win, query) {
- return Array.prototype.filter.call(
- win.document.querySelectorAll(".message"),
- e => e.innerText.includes(query)
- );
-}
-
-async function hasMessage(dbg, msg) {
- const webConsole = await dbg.toolbox.getPanel("webconsole");
- return waitFor(
- async () => findMessages(webConsole._frameWindow, msg).length > 0
- );
-}
-
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html", "simple2");
@@ -44,5 +30,5 @@ add_task(async function() {
selectContextMenuItem(dbg, "#node-menu-evaluate-in-console");
await waitForConsolePanelChange(dbg);
- await hasMessage(dbg, "undefined");
+ await hasConsoleMessage(dbg, "undefined");
});
diff --git a/test/mochitest/browser_dbg-console.js b/test/mochitest/browser_dbg-console.js
index cc5d2acd52..e400562dae 100644
--- a/test/mochitest/browser_dbg-console.js
+++ b/test/mochitest/browser_dbg-console.js
@@ -1,3 +1,7 @@
+/* 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 . */
+
add_task(async function() {
Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true);
const dbg = await initDebugger("doc-script-switching.html", "switching-01");
diff --git a/test/mochitest/browser_dbg-debug-line.js b/test/mochitest/browser_dbg-debug-line.js
index 641a364459..ad15917f2c 100644
--- a/test/mochitest/browser_dbg-debug-line.js
+++ b/test/mochitest/browser_dbg-debug-line.js
@@ -36,4 +36,4 @@ add_task(async function() {
0,
"Debug line no longer exists!"
);
-});
+});
\ No newline at end of file
diff --git a/test/mochitest/browser_dbg-editor-gutter.js b/test/mochitest/browser_dbg-editor-gutter.js
index 744ea5e628..5705f41c05 100644
--- a/test/mochitest/browser_dbg-editor-gutter.js
+++ b/test/mochitest/browser_dbg-editor-gutter.js
@@ -16,22 +16,6 @@ function clickGutter(dbg, line) {
clickElement(dbg, "gutter", line);
}
-function getLineEl(dbg, line) {
- const lines = dbg.win.document.querySelectorAll(".CodeMirror-code > div");
- return lines[line - 1];
-}
-
-function assertEditorBreakpoint(dbg, line, shouldExist) {
- const exists = !!getLineEl(dbg, line).querySelector(".new-breakpoint");
- ok(
- exists === shouldExist,
- "Breakpoint " +
- (shouldExist ? "exists" : "does not exist") +
- " on line " +
- line
- );
-}
-
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html", "simple1.js");
const {
diff --git a/test/mochitest/browser_dbg-expressions-focus.js b/test/mochitest/browser_dbg-expressions-focus.js
index b3129a4461..e61debf1c4 100644
--- a/test/mochitest/browser_dbg-expressions-focus.js
+++ b/test/mochitest/browser_dbg-expressions-focus.js
@@ -5,13 +5,17 @@
// Ensures the input is displayed and focused when "+" is clicked
add_task(async function() {
const dbg = await initDebugger("doc-script-switching.html");
- // Close the panel
+
+ info(">> Close the panel");
clickElementWithSelector(dbg, ".watch-expressions-pane ._header");
- // Click + to add the new expression
+
+ info(">> Click + to add the new expression");
clickElementWithSelector(dbg, ".watch-expressions-pane ._header .plus");
- // Ensure element gets focused
+
+ info(">> Ensure element gets focused");
await waitForElementWithSelector(dbg, ".expression-input-container.focused");
- // Ensure the element is focused
+
+ info(">> Ensure the element is focused");
is(
dbg.win.document.activeElement.classList.contains("input-expression"),
true
diff --git a/test/mochitest/browser_dbg-log-points.js b/test/mochitest/browser_dbg-log-points.js
new file mode 100644
index 0000000000..10e09adc3f
--- /dev/null
+++ b/test/mochitest/browser_dbg-log-points.js
@@ -0,0 +1,29 @@
+/* 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 . */
+
+/*
+ * Tests that log points are correctly logged to the console
+ */
+
+add_task(async function() {
+ Services.prefs.setBoolPref("devtools.toolbox.splitconsoleEnabled", true);
+ const dbg = await initDebugger("doc-script-switching.html", "switching-01");
+
+ const source = findSource(dbg, "switching-01")
+ await selectSource(dbg, "switching-01");
+
+ await getDebuggerSplitConsole(dbg);
+
+ await dbg.actions.addBreakpoint(
+ { line: 5, sourceId: source.id },
+ { logValue: "'a', 'b', 'c'" }
+ );
+ invokeInTab("firstCall");
+ await waitForPaused(dbg);
+
+ await hasConsoleMessage(dbg, "a b c");
+ const { link, value } = await findConsoleMessage(dbg, "a b c");
+ is(link, "script-switching-01.js:5:2", "logs should have the relevant link");
+ is(value, "a b c", "logs should have multiple values");
+});
diff --git a/test/mochitest/browser_dbg-navigation.js b/test/mochitest/browser_dbg-navigation.js
index 859b6f7300..c2e8c60266 100644
--- a/test/mochitest/browser_dbg-navigation.js
+++ b/test/mochitest/browser_dbg-navigation.js
@@ -21,7 +21,7 @@ const sources = [
add_task(async function() {
const dbg = await initDebugger("doc-script-switching.html");
const {
- selectors: { getSelectedSource, isPaused },
+ selectors: { getSelectedSource, getIsPaused, getCurrentThread },
getState
} = dbg;
@@ -41,7 +41,7 @@ add_task(async function() {
await navigate(dbg, "doc-scripts.html", ...sources);
is(countSources(dbg), 5, "5 sources are loaded.");
- ok(!isPaused(getState()), "Is not paused");
+ ok(!getIsPaused(getState(), getCurrentThread(getState())), "Is not paused");
await navigate(dbg, "doc-scripts.html", ...sources);
is(countSources(dbg), 5, "5 sources are loaded.");
diff --git a/test/mochitest/browser_dbg-pause-on-next.js b/test/mochitest/browser_dbg-pause-on-next.js
index 08db9a4ae8..5d64c545b7 100644
--- a/test/mochitest/browser_dbg-pause-on-next.js
+++ b/test/mochitest/browser_dbg-pause-on-next.js
@@ -5,9 +5,12 @@
add_task(async function() {
const dbg = await initDebugger("doc-scripts.html");
+ const {
+ selectors: { getIsWaitingOnBreak, getCurrentThread }
+ } = dbg;
clickElement(dbg, "pause");
- await waitForState(dbg, state => dbg.selectors.getIsWaitingOnBreak(state));
+ await waitForState(dbg, state => getIsWaitingOnBreak(state, getCurrentThread(state)));
invokeInTab("simple");
await waitForPaused(dbg, "simple3");
diff --git a/test/mochitest/browser_dbg-pause-points.js b/test/mochitest/browser_dbg-pause-points.js
index 7025d757b7..195ae83d17 100644
--- a/test/mochitest/browser_dbg-pause-points.js
+++ b/test/mochitest/browser_dbg-pause-points.js
@@ -18,8 +18,12 @@ async function testCase(dbg, { name, steps }) {
invokeInTab(name);
let locations = [];
+ const {
+ selectors: { getTopFrame, getCurrentThread }
+ } = dbg;
+
await stepOvers(dbg, steps.length, state => {
- const {line, column} = dbg.selectors.getTopFrame(state).location
+ const {line, column} = getTopFrame(state, getCurrentThread(state)).location
locations.push([line, column]);
});
diff --git a/test/mochitest/browser_dbg-pretty-print-breakpoints.js b/test/mochitest/browser_dbg-pretty-print-breakpoints.js
new file mode 100644
index 0000000000..0d87609471
--- /dev/null
+++ b/test/mochitest/browser_dbg-pretty-print-breakpoints.js
@@ -0,0 +1,21 @@
+/* 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 . */
+
+// Tests that breakpoints appear when you reload a page
+// with pretty-printed files.
+add_task(async function() {
+ const dbg = await initDebugger("doc-pretty.html", "pretty.js");
+
+ await selectSource(dbg, "pretty.js")
+ await prettyPrint(dbg);
+
+ await addBreakpoint(dbg, "pretty.js:formatted", 5);
+ await closeTab(dbg, "pretty.js:formatted");
+
+ await reload(dbg, "pretty.js");
+ invokeInTab("stuff");
+
+ await waitForPaused(dbg);
+ assertEditorBreakpoint(dbg, 4, true);
+});
diff --git a/test/mochitest/browser_dbg-react-app.js b/test/mochitest/browser_dbg-react-app.js
index 6621af9e87..2bc9aab4e7 100644
--- a/test/mochitest/browser_dbg-react-app.js
+++ b/test/mochitest/browser_dbg-react-app.js
@@ -7,9 +7,14 @@ add_task(async function() {
info('Test previewing an immutable Map inside of a react component')
invokeInTab("clickButton");
await waitForPaused(dbg);
+
+ const {
+ selectors: { getSelectedScopeMappings, getCurrentThread }
+ } = dbg;
+
await waitForState(
dbg,
- state => dbg.selectors.getSelectedScopeMappings(state)
+ state => getSelectedScopeMappings(state, getCurrentThread(state))
);
await assertPreviewTextValue(dbg, 10, 22, {
diff --git a/test/mochitest/browser_dbg-scroll-run-to-completion.js b/test/mochitest/browser_dbg-scroll-run-to-completion.js
index b2128c4890..d0882aea0e 100644
--- a/test/mochitest/browser_dbg-scroll-run-to-completion.js
+++ b/test/mochitest/browser_dbg-scroll-run-to-completion.js
@@ -8,8 +8,8 @@ add_task(async function() {
await waitForPaused(dbg);
assertPausedLocation(dbg);
- const threadClient = dbg.toolbox.threadClient;
- await checkEvaluateInTopFrame(threadClient, 'window.scrollBy(0, 10);', undefined);
+ const target = dbg.toolbox.target;
+ await checkEvaluateInTopFrame(target, 'window.scrollBy(0, 10);', undefined);
// checkEvaluateInTopFrame does an implicit resume for some reason.
await waitForPaused(dbg);
diff --git a/test/mochitest/browser_dbg-tabs-without-urls.js b/test/mochitest/browser_dbg-tabs-without-urls.js
index 697119c433..f240e99307 100644
--- a/test/mochitest/browser_dbg-tabs-without-urls.js
+++ b/test/mochitest/browser_dbg-tabs-without-urls.js
@@ -24,4 +24,11 @@ add_task(async function() {
// Test reloading the debugger
await reload(dbg, "simple1", "simple2");
is(countTabs(dbg), 2);
+
+ // TODO: This is here to make this test less flakey because otherwise the
+ // test will end while the files are still loading, which will stop all
+ // in-progress requests causing uncaught rejections when querying
+ // 'getBreakpointPositionsCompressed'.
+ await selectSource(dbg, "simple1");
+ await selectSource(dbg, "simple2");
});
diff --git a/test/mochitest/browser_dbg-windowless-workers.js b/test/mochitest/browser_dbg-windowless-workers.js
index 987e2fceec..3592506769 100644
--- a/test/mochitest/browser_dbg-windowless-workers.js
+++ b/test/mochitest/browser_dbg-windowless-workers.js
@@ -89,6 +89,18 @@ add_task(async function() {
info("Test pausing in both workers");
await addBreakpoint(dbg, "simple-worker", 10);
invokeInTab("sayHello");
+
+ // Wait for both workers to pause. When a thread pauses the current thread
+ // changes, and we don't want to get confused.
+ const {
+ selectors: { getIsPaused },
+ getState
+ } = dbg;
+ await waitFor(() => {
+ const state = getState();
+ return getIsPaused(state, worker1Thread) && getIsPaused(state, worker2Thread);
+ });
+
dbg.actions.selectThread(worker1Thread);
await waitForPaused(dbg);
diff --git a/test/mochitest/examples/doc-pretty.html b/test/mochitest/examples/doc-pretty.html
new file mode 100644
index 0000000000..99db330793
--- /dev/null
+++ b/test/mochitest/examples/doc-pretty.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Test a file that changes when pretty printed
+
+
+
+
+
+
diff --git a/test/mochitest/examples/pretty.js b/test/mochitest/examples/pretty.js
new file mode 100644
index 0000000000..2507e0ec01
--- /dev/null
+++ b/test/mochitest/examples/pretty.js
@@ -0,0 +1,7 @@
+function a() {}
+
+function stuff() {
+ a(); a();
+ a(); a();
+ debugger
+}
\ No newline at end of file
diff --git a/test/mochitest/helpers.js b/test/mochitest/helpers.js
index a05592b5de..e9f7f2d869 100644
--- a/test/mochitest/helpers.js
+++ b/test/mochitest/helpers.js
@@ -96,7 +96,7 @@ function _afterDispatchDone(store, type) {
function waitForDispatch(dbg, type, eventRepeat = 1) {
let count = 0;
- return Task.spawn(function* () {
+ return Task.spawn(function*() {
info(`Waiting for ${type} to dispatch ${eventRepeat} time(s)`);
while (count < eventRepeat) {
yield _afterDispatchDone(dbg.store, type);
@@ -402,10 +402,10 @@ function assertHighlightLocation(dbg, source, line) {
*/
function isPaused(dbg) {
const {
- selectors: { isPaused },
+ selectors: { getIsPaused, getCurrentThread },
getState
} = dbg;
- return !!isPaused(getState());
+ return getIsPaused(getState(), getCurrentThread(getState()));
}
// Make sure the debugger is paused at a certain source ID and line.
@@ -413,11 +413,11 @@ function assertPausedAtSourceAndLine(dbg, expectedSourceId, expectedLine) {
assertPaused(dbg);
const {
- selectors: { getWorkers, getFrames },
+ selectors: { getCurrentThreadFrames },
getState
} = dbg;
- const frames = getFrames(getState());
+ const frames = getCurrentThreadFrames(getState());
ok(frames.length >= 1, "Got at least one frame");
const { sourceId, line } = frames[0].location;
ok(sourceId == expectedSourceId, "Frame has correct source");
@@ -451,11 +451,11 @@ async function waitForLoadedScopes(dbg) {
* @static
*/
async function waitForPaused(dbg, url) {
- const { getSelectedScope } = dbg.selectors;
+ const { getSelectedScope, getCurrentThread } = dbg.selectors;
await waitForState(
dbg,
- state => isPaused(dbg) && !!getSelectedScope(state),
+ state => isPaused(dbg) && !!getSelectedScope(state, getCurrentThread(state)),
"paused"
);
@@ -863,6 +863,11 @@ async function invokeWithBreakpoint(
await invokeResult;
}
+function prettyPrint(dbg) {
+ const sourceId = dbg.selectors.getSelectedSourceId(dbg.store.getState());
+ return dbg.actions.togglePrettyPrint(sourceId);
+}
+
async function expandAllScopes(dbg) {
const scopes = await waitForElement(dbg, "scopes");
const scopeElements = scopes.querySelectorAll(
@@ -931,20 +936,17 @@ async function togglePauseOnExceptions(
pauseOnExceptions,
pauseOnCaughtExceptions
) {
- const command = dbg.actions.pauseOnExceptions(
+ return dbg.actions.pauseOnExceptions(
pauseOnExceptions,
pauseOnCaughtExceptions
);
-
- if (!isPaused(dbg)) {
- await waitForThreadEvents(dbg, "resumed");
- }
-
- return command;
}
function waitForActive(dbg) {
- return waitForState(dbg, state => !dbg.selectors.isPaused(state), "active");
+ const {
+ selectors: { getIsPaused, getCurrentThread },
+ } = dbg;
+ return waitForState(dbg, state => !getIsPaused(state, getCurrentThread(state)), "active");
}
// Helpers
@@ -962,11 +964,11 @@ function waitForActive(dbg) {
*/
function invokeInTab(fnc, ...args) {
info(`Invoking in tab: ${fnc}(${args.map(uneval).join(",")})`);
- return ContentTask.spawn(gBrowser.selectedBrowser, { fnc, args }, function* ({
+ return ContentTask.spawn(gBrowser.selectedBrowser, { fnc, args }, function*({
fnc,
args
}) {
- return content.wrappedJSObject[fnc](...args); // eslint-disable-line mozilla/no-cpows-in-tests, max-len
+ return content.wrappedJSObject[fnc](...args); // max-len
});
}
@@ -1091,6 +1093,22 @@ function isVisible(outerEl, innerEl) {
return visible;
}
+function getEditorLineEl(dbg, line) {
+ const lines = dbg.win.document.querySelectorAll(".CodeMirror-code > div");
+ return lines[line - 1];
+}
+
+function assertEditorBreakpoint(dbg, line, shouldExist) {
+ const exists = !!getEditorLineEl(dbg, line).querySelector(".new-breakpoint");
+ ok(
+ exists === shouldExist,
+ "Breakpoint " +
+ (shouldExist ? "exists" : "does not exist") +
+ " on line " +
+ line
+ );
+}
+
const selectors = {
callStackHeader: ".call-stack-pane ._header",
callStackBody: ".call-stack-pane .pane",
@@ -1601,27 +1619,42 @@ function hideConsoleContextMenu(hud) {
// Return a promise that resolves with the result of a thread evaluating a
// string in the topmost frame.
-async function evaluateInTopFrame(threadClient, text) {
+async function evaluateInTopFrame(target, text) {
+ const threadClient = target.threadClient;
+ const consoleFront = await target.getFront("console");
const { frames } = await threadClient.getFrames(0, 1);
ok(frames.length == 1, "Got one frame");
- const response = await threadClient.eval(frames[0].actor, text);
- ok(response.type == "resumed", "Got resume response from eval");
- let rval;
- await threadClient.addOneTimeListener("paused", function(event, packet) {
- ok(
- packet.type == "paused" &&
- packet.why.type == "clientEvaluated" &&
- "return" in packet.why.frameFinished,
- "Eval returned a value"
- );
- rval = packet.why.frameFinished.return;
- });
- return rval.type == "undefined" ? undefined : rval;
+ const options = { thread: threadClient.actor, frameActor: frames[0].actor };
+ const response = await consoleFront.evaluateJS(text, options);
+ return response.result.type == "undefined" ? undefined : response.result;
}
// Return a promise that resolves when a thread evaluates a string in the
// topmost frame, ensuring the result matches the expected value.
-async function checkEvaluateInTopFrame(threadClient, text, expected) {
- const rval = await evaluateInTopFrame(threadClient, text);
+async function checkEvaluateInTopFrame(target, text, expected) {
+ const rval = await evaluateInTopFrame(target, text);
ok(rval == expected, `Eval returned ${expected}`);
}
+
+async function findConsoleMessage(dbg, query) {
+ const [message,] = await findConsoleMessages(dbg, query);
+ const value = message.querySelector(".message-body").innerText;
+ const link = message.querySelector(".frame-link-source-inner").innerText;
+ return { value, link };
+}
+
+async function findConsoleMessages(dbg, query) {
+ const webConsole = await dbg.toolbox.getPanel("webconsole");
+ const win = webConsole._frameWindow;
+ return Array.prototype.filter.call(
+ win.document.querySelectorAll(".message"),
+ e => e.innerText.includes(query)
+ );
+}
+
+async function hasConsoleMessage(dbg, msg) {
+ return waitFor(async () => {
+ const messages = await findConsoleMessages(dbg, msg);
+ return messages.length > 0;
+ })
+}