diff --git a/.eslintignore b/.eslintignore
index e67eceb534da..c327eb4e0146 100644
--- a/.eslintignore
+++ b/.eslintignore
@@ -125,14 +125,6 @@ devtools/server/tests/browser/**
!devtools/server/tests/browser/browser_webextension_inspected_window.js
devtools/server/tests/mochitest/**
devtools/server/tests/unit/**
-devtools/shared/*.js
-!devtools/shared/async-storage.js
-!devtools/shared/async-utils.js
-!devtools/shared/defer.js
-!devtools/shared/event-emitter.js
-!devtools/shared/indentation.js
-!devtools/shared/loader-plugin-raw.jsm
-!devtools/shared/task.js
devtools/shared/apps/**
devtools/shared/client/**
devtools/shared/discovery/**
diff --git a/browser/config/mozconfigs/macosx64/beta b/browser/config/mozconfigs/macosx64/beta
new file mode 100644
index 000000000000..64cf69d866f7
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/beta
@@ -0,0 +1,15 @@
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/macosx64/common-opt b/browser/config/mozconfigs/macosx64/common-opt
new file mode 100644
index 000000000000..413fb6dcca6e
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/common-opt
@@ -0,0 +1,15 @@
+# This file is sourced by the nightly, beta, and release mozconfigs.
+
+. $topsrcdir/build/macosx/mozconfig.common
+
+ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
+ac_add_options --with-google-api-keyfile=/builds/gapi.data
+ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
+
+# Needed to enable breakpad in application.ini
+export MOZILLA_OFFICIAL=1
+
+export MOZ_TELEMETRY_REPORTING=1
+
+# Package js shell.
+export MOZ_PACKAGE_JSSHELL=1
diff --git a/browser/config/mozconfigs/macosx64/nightly b/browser/config/mozconfigs/macosx64/nightly
index 12fec0474334..0a915da54660 100644
--- a/browser/config/mozconfigs/macosx64/nightly
+++ b/browser/config/mozconfigs/macosx64/nightly
@@ -1,20 +1,19 @@
-. $topsrcdir/build/macosx/mozconfig.common
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
+ac_add_options --disable-install-strip
ac_add_options --enable-verify-mar
+ac_add_options --enable-profiling
+ac_add_options --enable-instruments
-# Needed to enable breakpad in application.ini
-export MOZILLA_OFFICIAL=1
-
-# Enable Telemetry
-export MOZ_TELEMETRY_REPORTING=1
+# Cross-compiled builds fail when dtrace is enabled
+if test `uname -s` != Linux; then
+ ac_add_options --enable-dtrace
+fi
if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
ac_add_options --with-macbundlename-prefix=Firefox
fi
-# Package js shell.
-export MOZ_PACKAGE_JSSHELL=1
-
ac_add_options --with-branding=browser/branding/nightly
. "$topsrcdir/build/mozconfig.rust"
diff --git a/browser/config/mozconfigs/macosx64/release b/browser/config/mozconfigs/macosx64/release
new file mode 100644
index 000000000000..562a16b50ebe
--- /dev/null
+++ b/browser/config/mozconfigs/macosx64/release
@@ -0,0 +1,21 @@
+# This make file should be identical to the beta mozconfig, apart from the
+# safeguard below
+MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
+
+if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
+ MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
+ MOZ_AUTOMATION_UPDATE_PACKAGING=1
+fi
+
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
+
+ac_add_options --enable-official-branding
+ac_add_options --enable-verify-mar
+
+# safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
+# defines.sh during the beta cycle
+export BUILDING_RELEASE=1
+
+. "$topsrcdir/build/mozconfig.rust"
+. "$topsrcdir/build/mozconfig.common.override"
+. "$topsrcdir/build/mozconfig.cache"
diff --git a/browser/config/mozconfigs/whitelist b/browser/config/mozconfigs/whitelist
index 445629ef57ef..b18c0d3e145d 100644
--- a/browser/config/mozconfigs/whitelist
+++ b/browser/config/mozconfigs/whitelist
@@ -5,7 +5,7 @@ whitelist = {
'nightly': {},
}
-all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx-universal']
+all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx64']
for platform in all_platforms:
whitelist['nightly'][platform] = [
@@ -15,7 +15,7 @@ for platform in all_platforms:
'mk_add_options CLIENT_PY_ARGS="--hg-options=\'--verbose --time\' --hgtool=../tools/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print"'
]
-for platform in ['linux32', 'linux64', 'macosx-universal']:
+for platform in ['linux32', 'linux64', 'macosx64']:
whitelist['nightly'][platform] += [
'mk_add_options MOZ_MAKE_FLAGS="-j4"',
]
@@ -42,7 +42,7 @@ whitelist['nightly']['linux64'] += [
'. "$topsrcdir/build/mozconfig.cache"',
]
-whitelist['nightly']['macosx-universal'] += [
+whitelist['nightly']['macosx64'] += [
'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
'ac_add_options --with-macbundlename-prefix=Firefox',
'fi',
diff --git a/build/clang-plugin/clang-plugin.cpp b/build/clang-plugin/clang-plugin.cpp
index 7ca86c4c4247..595253d21409 100644
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -851,7 +851,8 @@ AST_MATCHER(BinaryOperator, isInSystemHeader) {
AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) {
const char* whitelist[] = {
"SkScalar.h",
- "json_writer.cpp"
+ "json_writer.cpp",
+ "State.cpp"
};
SourceLocation Loc = Node.getOperatorLoc();
diff --git a/build/compare-mozconfig/compare-mozconfigs-wrapper.py b/build/compare-mozconfig/compare-mozconfigs-wrapper.py
index 1c281a93f9a6..f737afe15086 100644
--- a/build/compare-mozconfig/compare-mozconfigs-wrapper.py
+++ b/build/compare-mozconfig/compare-mozconfigs-wrapper.py
@@ -19,8 +19,7 @@ log = logging.getLogger(__name__)
def determine_platform():
platform_mapping = {'WINNT': {'x86_64': 'win64',
'i686': 'win32'},
- 'Darwin': {'x86_64': 'macosx-universal',
- 'i386':'macosx-universal'},
+ 'Darwin': {'x86_64': 'macosx64'},
'Linux': {'x86_64': 'linux64',
'i686': 'linux32'}}
diff --git a/build/macosx/cross-mozconfig.common b/build/macosx/cross-mozconfig.common
index 8e56394d00bf..fc58da15d80b 100644
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -15,6 +15,12 @@ fi
mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/clang/lib"
CROSS_CCTOOLS_PATH=$topsrcdir/cctools
+# This SDK was copied from a local XCode install and uploaded to tooltool.
+# Generate the tarball by running this command with the proper SDK version:
+# sdk_path=$(xcrun --sdk macosx10.12 --show-sdk-path)
+# tar -C $(dirname ${sdk_path}) -cHjf /tmp/$(basename ${sdk_path}).tar.bz2 $(basename ${sdk_path})
+# Upload the resulting tarball from /tmp to tooltool, and change the entry in
+# `browser/config/tooltool-manifests/macosx64/cross-releng.manifest`.
CROSS_SYSROOT=$topsrcdir/MacOSX10.7.sdk
CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks
FLAGS="-target x86_64-apple-darwin10 -mlinker-version=136 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT"
diff --git a/devtools/client/jar.mn b/devtools/client/jar.mn
index bf39c549e552..6fb6ce096f79 100644
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -14,8 +14,7 @@ devtools.jar:
content/projecteditor/chrome/content/projecteditor-test.xul (projecteditor/chrome/content/projecteditor-test.xul)
content/projecteditor/chrome/content/projecteditor-loader.js (projecteditor/chrome/content/projecteditor-loader.js)
content/netmonitor/netmonitor.xul (netmonitor/netmonitor.xul)
- content/netmonitor/netmonitor-controller.js (netmonitor/netmonitor-controller.js)
- content/netmonitor/netmonitor-view.js (netmonitor/netmonitor-view.js)
+ content/netmonitor/netmonitor.js (netmonitor/netmonitor.js)
content/webconsole/webconsole.xul (webconsole/webconsole.xul)
* content/scratchpad/scratchpad.xul (scratchpad/scratchpad.xul)
content/scratchpad/scratchpad.js (scratchpad/scratchpad.js)
diff --git a/devtools/client/netmonitor/actions/batching.js b/devtools/client/netmonitor/actions/batching.js
new file mode 100644
index 000000000000..fd68db185a32
--- /dev/null
+++ b/devtools/client/netmonitor/actions/batching.js
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const {
+ BATCH_ACTIONS,
+ BATCH_ENABLE,
+ BATCH_RESET,
+} = require("../constants");
+
+/**
+ * Process multiple actions at once as part of one dispatch, and produce only one
+ * state update at the end. This action is not processed by any reducer, but by a
+ * special store enhancer.
+ */
+function batchActions(actions) {
+ return {
+ type: BATCH_ACTIONS,
+ actions
+ };
+}
+
+function batchEnable(enabled) {
+ return {
+ type: BATCH_ENABLE,
+ enabled
+ };
+}
+
+function batchReset() {
+ return {
+ type: BATCH_RESET,
+ };
+}
+
+module.exports = {
+ batchActions,
+ batchEnable,
+ batchReset,
+};
diff --git a/devtools/client/netmonitor/actions/filters.js b/devtools/client/netmonitor/actions/filters.js
index 0082c64dfdd9..b8d8726afd39 100644
--- a/devtools/client/netmonitor/actions/filters.js
+++ b/devtools/client/netmonitor/actions/filters.js
@@ -42,12 +42,12 @@ function enableFilterTypeOnly(filter) {
/**
* Set filter text.
*
- * @param {string} url - A filter text is going to be set
+ * @param {string} text - A filter text is going to be set
*/
-function setFilterText(url) {
+function setFilterText(text) {
return {
type: SET_FILTER_TEXT,
- url,
+ text,
};
}
diff --git a/devtools/client/netmonitor/actions/index.js b/devtools/client/netmonitor/actions/index.js
index 29ce28fd5d50..110f73ce76d8 100644
--- a/devtools/client/netmonitor/actions/index.js
+++ b/devtools/client/netmonitor/actions/index.js
@@ -4,8 +4,20 @@
"use strict";
+const batching = require("./batching");
const filters = require("./filters");
const requests = require("./requests");
+const selection = require("./selection");
+const sort = require("./sort");
+const timingMarkers = require("./timing-markers");
const ui = require("./ui");
-module.exports = Object.assign({}, filters, requests, ui);
+Object.assign(exports,
+ batching,
+ filters,
+ requests,
+ selection,
+ sort,
+ timingMarkers,
+ ui
+);
diff --git a/devtools/client/netmonitor/actions/moz.build b/devtools/client/netmonitor/actions/moz.build
index 9082b8bd1b88..e81d2714edfa 100644
--- a/devtools/client/netmonitor/actions/moz.build
+++ b/devtools/client/netmonitor/actions/moz.build
@@ -3,8 +3,12 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
+ 'batching.js',
'filters.js',
'index.js',
'requests.js',
+ 'selection.js',
+ 'sort.js',
+ 'timing-markers.js',
'ui.js',
)
diff --git a/devtools/client/netmonitor/actions/requests.js b/devtools/client/netmonitor/actions/requests.js
index ae794a437496..77871a0ff293 100644
--- a/devtools/client/netmonitor/actions/requests.js
+++ b/devtools/client/netmonitor/actions/requests.js
@@ -5,21 +5,61 @@
"use strict";
const {
- UPDATE_REQUESTS,
+ ADD_REQUEST,
+ UPDATE_REQUEST,
+ CLONE_SELECTED_REQUEST,
+ REMOVE_SELECTED_CUSTOM_REQUEST,
+ CLEAR_REQUESTS,
} = require("../constants");
-/**
- * Update request items
- *
- * @param {array} requests - visible request items
- */
-function updateRequests(items) {
+function addRequest(id, data, batch) {
return {
- type: UPDATE_REQUESTS,
- items,
+ type: ADD_REQUEST,
+ id,
+ data,
+ meta: { batch },
+ };
+}
+
+function updateRequest(id, data, batch) {
+ return {
+ type: UPDATE_REQUEST,
+ id,
+ data,
+ meta: { batch },
+ };
+}
+
+/**
+ * Clone the currently selected request, set the "isCustom" attribute.
+ * Used by the "Edit and Resend" feature.
+ */
+function cloneSelectedRequest() {
+ return {
+ type: CLONE_SELECTED_REQUEST
+ };
+}
+
+/**
+ * Remove a request from the list. Supports removing only cloned requests with a
+ * "isCustom" attribute. Other requests never need to be removed.
+ */
+function removeSelectedCustomRequest() {
+ return {
+ type: REMOVE_SELECTED_CUSTOM_REQUEST
+ };
+}
+
+function clearRequests() {
+ return {
+ type: CLEAR_REQUESTS
};
}
module.exports = {
- updateRequests,
+ addRequest,
+ updateRequest,
+ cloneSelectedRequest,
+ removeSelectedCustomRequest,
+ clearRequests,
};
diff --git a/devtools/client/netmonitor/actions/selection.js b/devtools/client/netmonitor/actions/selection.js
new file mode 100644
index 000000000000..dffc6d8cc000
--- /dev/null
+++ b/devtools/client/netmonitor/actions/selection.js
@@ -0,0 +1,67 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { getDisplayedRequests } = require("../selectors/index");
+const { SELECT_REQUEST, PRESELECT_REQUEST } = require("../constants");
+
+/**
+ * When a new request with a given id is added in future, select it immediately.
+ * Used by the "Edit and Resend" feature, where we know in advance the ID of the
+ * request, at a time when it wasn't sent yet.
+ */
+function preselectRequest(id) {
+ return {
+ type: PRESELECT_REQUEST,
+ id
+ };
+}
+
+/**
+ * Select request with a given id.
+ */
+function selectRequest(id) {
+ return {
+ type: SELECT_REQUEST,
+ id
+ };
+}
+
+const PAGE_SIZE_ITEM_COUNT_RATIO = 5;
+
+/**
+ * Move the selection up to down according to the "delta" parameter. Possible values:
+ * - Number: positive or negative, move up or down by specified distance
+ * - "PAGE_UP" | "PAGE_DOWN" (String): page up or page down
+ * - +Infinity | -Infinity: move to the start or end of the list
+ */
+function selectDelta(delta) {
+ return (dispatch, getState) => {
+ const state = getState();
+ const requests = getDisplayedRequests(state);
+
+ if (requests.isEmpty()) {
+ return;
+ }
+
+ const selIndex = requests.findIndex(r => r.id === state.requests.selectedId);
+
+ if (delta === "PAGE_DOWN") {
+ delta = Math.ceil(requests.size / PAGE_SIZE_ITEM_COUNT_RATIO);
+ } else if (delta === "PAGE_UP") {
+ delta = -Math.ceil(requests.size / PAGE_SIZE_ITEM_COUNT_RATIO);
+ }
+
+ const newIndex = Math.min(Math.max(0, selIndex + delta), requests.size - 1);
+ const newItem = requests.get(newIndex);
+ dispatch(selectRequest(newItem.id));
+ };
+}
+
+module.exports = {
+ preselectRequest,
+ selectRequest,
+ selectDelta,
+};
diff --git a/devtools/client/netmonitor/actions/sort.js b/devtools/client/netmonitor/actions/sort.js
new file mode 100644
index 000000000000..2dd02373ec50
--- /dev/null
+++ b/devtools/client/netmonitor/actions/sort.js
@@ -0,0 +1,18 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { SORT_BY } = require("../constants");
+
+function sortBy(sortType) {
+ return {
+ type: SORT_BY,
+ sortType
+ };
+}
+
+module.exports = {
+ sortBy
+};
diff --git a/devtools/client/netmonitor/actions/timing-markers.js b/devtools/client/netmonitor/actions/timing-markers.js
new file mode 100644
index 000000000000..4f1363a70637
--- /dev/null
+++ b/devtools/client/netmonitor/actions/timing-markers.js
@@ -0,0 +1,19 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+const { ADD_TIMING_MARKER, CLEAR_TIMING_MARKERS } = require("../constants");
+
+exports.addTimingMarker = (marker) => {
+ return {
+ type: ADD_TIMING_MARKER,
+ marker
+ };
+};
+
+exports.clearTimingMarkers = () => {
+ return {
+ type: CLEAR_TIMING_MARKERS
+ };
+};
diff --git a/devtools/client/netmonitor/actions/ui.js b/devtools/client/netmonitor/actions/ui.js
index 554921bc2781..31539518fb72 100644
--- a/devtools/client/netmonitor/actions/ui.js
+++ b/devtools/client/netmonitor/actions/ui.js
@@ -6,7 +6,7 @@
const {
OPEN_SIDEBAR,
- TOGGLE_SIDEBAR,
+ WATERFALL_RESIZE,
} = require("../constants");
/**
@@ -25,12 +25,21 @@ function openSidebar(open) {
* Toggle sidebar open state.
*/
function toggleSidebar() {
+ return (dispatch, getState) => dispatch(openSidebar(!getState().ui.sidebarOpen));
+}
+
+/**
+ * Waterfall width has changed (likely on window resize). Update the UI.
+ */
+function resizeWaterfall(width) {
return {
- type: TOGGLE_SIDEBAR,
+ type: WATERFALL_RESIZE,
+ width
};
}
module.exports = {
openSidebar,
toggleSidebar,
+ resizeWaterfall,
};
diff --git a/devtools/client/netmonitor/components/clear-button.js b/devtools/client/netmonitor/components/clear-button.js
index cccff81fb9dd..5ef1a73211fd 100644
--- a/devtools/client/netmonitor/components/clear-button.js
+++ b/devtools/client/netmonitor/components/clear-button.js
@@ -2,12 +2,12 @@
* 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/. */
-/* globals NetMonitorView */
-
"use strict";
const { DOM } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
const { L10N } = require("../l10n");
+const Actions = require("../actions/index");
const { button } = DOM;
@@ -15,15 +15,18 @@ const { button } = DOM;
* Clear button component
* A type of tool button is responsible for cleaning network requests.
*/
-function ClearButton() {
+function ClearButton({ onClick }) {
return button({
id: "requests-menu-clear-button",
className: "devtools-button devtools-clear-icon",
title: L10N.getStr("netmonitor.toolbar.clear"),
- onClick: () => {
- NetMonitorView.RequestsMenu.clear();
- },
+ onClick,
});
}
-module.exports = ClearButton;
+module.exports = connect(
+ undefined,
+ dispatch => ({
+ onClick: () => dispatch(Actions.clearRequests())
+ })
+)(ClearButton);
diff --git a/devtools/client/netmonitor/components/moz.build b/devtools/client/netmonitor/components/moz.build
index 02655b710f23..c4977531fdb0 100644
--- a/devtools/client/netmonitor/components/moz.build
+++ b/devtools/client/netmonitor/components/moz.build
@@ -2,9 +2,19 @@
# 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/.
+DIRS += [
+ 'shared',
+]
+
DevToolsModules(
'clear-button.js',
'filter-buttons.js',
+ 'request-list-content.js',
+ 'request-list-empty.js',
+ 'request-list-header.js',
+ 'request-list-item.js',
+ 'request-list-tooltip.js',
+ 'request-list.js',
'search-box.js',
'summary-button.js',
'toggle-button.js',
diff --git a/devtools/client/netmonitor/components/request-list-content.js b/devtools/client/netmonitor/components/request-list-content.js
new file mode 100644
index 000000000000..c5f029600d2b
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-content.js
@@ -0,0 +1,255 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals NetMonitorView */
+
+"use strict";
+
+const { Task } = require("devtools/shared/task");
+const { createClass, createFactory, DOM } = require("devtools/client/shared/vendor/react");
+const { div } = DOM;
+const Actions = require("../actions/index");
+const RequestListItem = createFactory(require("./request-list-item"));
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { setTooltipImageContent,
+ setTooltipStackTraceContent } = require("./request-list-tooltip");
+const { getDisplayedRequests,
+ getWaterfallScale } = require("../selectors/index");
+const { KeyCodes } = require("devtools/client/shared/keycodes");
+
+// tooltip show/hide delay in ms
+const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
+
+/**
+ * Renders the actual contents of the request list.
+ */
+const RequestListContent = createClass({
+ displayName: "RequestListContent",
+
+ componentDidMount() {
+ // Set the CSS variables for waterfall scaling
+ this.setScalingStyles();
+
+ // Install event handler for displaying a tooltip
+ this.props.tooltip.startTogglingOnHover(this.refs.contentEl, this.onHover, {
+ toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
+ interactive: true
+ });
+
+ // Install event handler to hide the tooltip on scroll
+ this.refs.contentEl.addEventListener("scroll", this.onScroll, true);
+ },
+
+ componentWillUpdate() {
+ // Check if the list is scrolled to bottom, before UI update
+ this.shouldScrollBottom = this.isScrolledToBottom();
+ },
+
+ componentDidUpdate(prevProps) {
+ // Update the CSS variables for waterfall scaling after props change
+ this.setScalingStyles();
+
+ // Keep the list scrolled to bottom if a new row was added
+ if (this.shouldScrollBottom) {
+ let node = this.refs.contentEl;
+ node.scrollTop = node.scrollHeight;
+ }
+ },
+
+ componentWillUnmount() {
+ this.refs.contentEl.removeEventListener("scroll", this.onScroll, true);
+
+ // Uninstall the tooltip event handler
+ this.props.tooltip.stopTogglingOnHover();
+ },
+
+ /**
+ * Set the CSS variables for waterfall scaling. If React supported setting CSS
+ * variables as part of the "style" property of a DOM element, we would use that.
+ *
+ * However, React doesn't support this, so we need to use a hack and update the
+ * DOM element directly: https://github.com/facebook/react/issues/6411
+ */
+ setScalingStyles(prevProps) {
+ const { scale } = this.props;
+ if (scale == this.currentScale) {
+ return;
+ }
+
+ this.currentScale = scale;
+
+ const { style } = this.refs.contentEl;
+ style.removeProperty("--timings-scale");
+ style.removeProperty("--timings-rev-scale");
+ style.setProperty("--timings-scale", scale);
+ style.setProperty("--timings-rev-scale", 1 / scale);
+ },
+
+ isScrolledToBottom() {
+ const { contentEl } = this.refs;
+ const lastChildEl = contentEl.lastElementChild;
+
+ if (!lastChildEl) {
+ return false;
+ }
+
+ let lastChildRect = lastChildEl.getBoundingClientRect();
+ let contentRect = contentEl.getBoundingClientRect();
+
+ return (lastChildRect.height + lastChildRect.top) <= contentRect.bottom;
+ },
+
+ /**
+ * The predicate used when deciding whether a popup should be shown
+ * over a request item or not.
+ *
+ * @param nsIDOMNode target
+ * The element node currently being hovered.
+ * @param object tooltip
+ * The current tooltip instance.
+ * @return {Promise}
+ */
+ onHover: Task.async(function* (target, tooltip) {
+ let itemEl = target.closest(".request-list-item");
+ if (!itemEl) {
+ return false;
+ }
+ let itemId = itemEl.dataset.id;
+ if (!itemId) {
+ return false;
+ }
+ let requestItem = this.props.displayedRequests.find(r => r.id == itemId);
+ if (!requestItem) {
+ return false;
+ }
+
+ if (requestItem.responseContent && target.closest(".requests-menu-icon-and-file")) {
+ return setTooltipImageContent(tooltip, itemEl, requestItem);
+ } else if (requestItem.cause && target.closest(".requests-menu-cause-stack")) {
+ return setTooltipStackTraceContent(tooltip, requestItem);
+ }
+
+ return false;
+ }),
+
+ /**
+ * Scroll listener for the requests menu view.
+ */
+ onScroll() {
+ this.props.tooltip.hide();
+ },
+
+ /**
+ * Handler for keyboard events. For arrow up/down, page up/down, home/end,
+ * move the selection up or down.
+ */
+ onKeyDown(e) {
+ let delta;
+
+ switch (e.keyCode) {
+ case KeyCodes.DOM_VK_UP:
+ case KeyCodes.DOM_VK_LEFT:
+ delta = -1;
+ break;
+ case KeyCodes.DOM_VK_DOWN:
+ case KeyCodes.DOM_VK_RIGHT:
+ delta = +1;
+ break;
+ case KeyCodes.DOM_VK_PAGE_UP:
+ delta = "PAGE_UP";
+ break;
+ case KeyCodes.DOM_VK_PAGE_DOWN:
+ delta = "PAGE_DOWN";
+ break;
+ case KeyCodes.DOM_VK_HOME:
+ delta = -Infinity;
+ break;
+ case KeyCodes.DOM_VK_END:
+ delta = +Infinity;
+ break;
+ }
+
+ if (delta) {
+ // Prevent scrolling when pressing navigation keys.
+ e.preventDefault();
+ e.stopPropagation();
+ this.props.onSelectDelta(delta);
+ }
+ },
+
+ /**
+ * If selection has just changed (by keyboard navigation), don't keep the list
+ * scrolled to bottom, but allow scrolling up with the selection.
+ */
+ onFocusedNodeChange() {
+ this.shouldScrollBottom = false;
+ },
+
+ /**
+ * If a focused item was unmounted, transfer the focus to the container element.
+ */
+ onFocusedNodeUnmount() {
+ if (this.refs.contentEl) {
+ this.refs.contentEl.focus();
+ }
+ },
+
+ render() {
+ const { selectedRequestId,
+ displayedRequests,
+ firstRequestStartedMillis,
+ onItemMouseDown,
+ onItemContextMenu,
+ onSecurityIconClick } = this.props;
+
+ return div(
+ {
+ ref: "contentEl",
+ className: "requests-menu-contents",
+ tabIndex: 0,
+ onKeyDown: this.onKeyDown,
+ },
+ displayedRequests.map((item, index) => RequestListItem({
+ key: item.id,
+ item,
+ index,
+ isSelected: item.id === selectedRequestId,
+ firstRequestStartedMillis,
+ onMouseDown: e => onItemMouseDown(e, item.id),
+ onContextMenu: e => onItemContextMenu(e, item.id),
+ onSecurityIconClick: e => onSecurityIconClick(e, item),
+ onFocusedNodeChange: this.onFocusedNodeChange,
+ onFocusedNodeUnmount: this.onFocusedNodeUnmount,
+ }))
+ );
+ },
+});
+
+module.exports = connect(
+ state => ({
+ displayedRequests: getDisplayedRequests(state),
+ selectedRequestId: state.requests.selectedId,
+ scale: getWaterfallScale(state),
+ firstRequestStartedMillis: state.requests.firstStartedMillis,
+ tooltip: NetMonitorView.RequestsMenu.tooltip,
+ }),
+ dispatch => ({
+ onItemMouseDown: (e, item) => dispatch(Actions.selectRequest(item)),
+ onItemContextMenu: (e, item) => {
+ e.preventDefault();
+ NetMonitorView.RequestsMenu.contextMenu.open(e);
+ },
+ onSelectDelta: (delta) => dispatch(Actions.selectDelta(delta)),
+ /**
+ * A handler that opens the security tab in the details view if secure or
+ * broken security indicator is clicked.
+ */
+ onSecurityIconClick: (e, item) => {
+ const { securityState } = item;
+ if (securityState && securityState !== "insecure") {
+ // Choose the security tab.
+ NetMonitorView.NetworkDetails.widget.selectedIndex = 5;
+ }
+ },
+ })
+)(RequestListContent);
diff --git a/devtools/client/netmonitor/components/request-list-empty.js b/devtools/client/netmonitor/components/request-list-empty.js
new file mode 100644
index 000000000000..f4fe56bc1c70
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-empty.js
@@ -0,0 +1,65 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals NetMonitorView */
+
+"use strict";
+
+const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
+const { L10N } = require("../l10n");
+const { div, span, button } = DOM;
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+
+/**
+ * UI displayed when the request list is empty. Contains instructions on reloading
+ * the page and on triggering performance analysis of the page.
+ */
+const RequestListEmptyNotice = createClass({
+ displayName: "RequestListEmptyNotice",
+
+ propTypes: {
+ onReloadClick: PropTypes.func.isRequired,
+ onPerfClick: PropTypes.func.isRequired,
+ },
+
+ render() {
+ return div(
+ {
+ id: "requests-menu-empty-notice",
+ className: "request-list-empty-notice",
+ },
+ div({ id: "notice-reload-message" },
+ span(null, L10N.getStr("netmonitor.reloadNotice1")),
+ button(
+ {
+ id: "requests-menu-reload-notice-button",
+ className: "devtools-toolbarbutton",
+ "data-standalone": true,
+ onClick: this.props.onReloadClick,
+ },
+ L10N.getStr("netmonitor.reloadNotice2")
+ ),
+ span(null, L10N.getStr("netmonitor.reloadNotice3"))
+ ),
+ div({ id: "notice-perf-message" },
+ span(null, L10N.getStr("netmonitor.perfNotice1")),
+ button({
+ id: "requests-menu-perf-notice-button",
+ title: L10N.getStr("netmonitor.perfNotice3"),
+ className: "devtools-button",
+ "data-standalone": true,
+ onClick: this.props.onPerfClick,
+ }),
+ span(null, L10N.getStr("netmonitor.perfNotice2"))
+ )
+ );
+ }
+});
+
+module.exports = connect(
+ undefined,
+ dispatch => ({
+ onPerfClick: e => NetMonitorView.toggleFrontendMode(),
+ onReloadClick: e => NetMonitorView.reloadPage(),
+ })
+)(RequestListEmptyNotice);
diff --git a/devtools/client/netmonitor/components/request-list-header.js b/devtools/client/netmonitor/components/request-list-header.js
new file mode 100644
index 000000000000..d28226eccf2b
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-header.js
@@ -0,0 +1,197 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals document */
+
+"use strict";
+
+const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
+const { div, button } = DOM;
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../l10n");
+const { getWaterfallScale } = require("../selectors/index");
+const Actions = require("../actions/index");
+const WaterfallBackground = require("../waterfall-background");
+
+// ms
+const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
+// px
+const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
+
+const REQUEST_TIME_DECIMALS = 2;
+
+const HEADERS = [
+ { name: "status", label: "status3" },
+ { name: "method" },
+ { name: "file", boxName: "icon-and-file" },
+ { name: "domain", boxName: "security-and-domain" },
+ { name: "cause" },
+ { name: "type" },
+ { name: "transferred" },
+ { name: "size" },
+ { name: "waterfall" }
+];
+
+/**
+ * Render the request list header with sorting arrows for columns.
+ * Displays tick marks in the waterfall column header.
+ * Also draws the waterfall background canvas and updates it when needed.
+ */
+const RequestListHeader = createClass({
+ displayName: "RequestListHeader",
+
+ propTypes: {
+ sort: PropTypes.object,
+ scale: PropTypes.number,
+ waterfallWidth: PropTypes.number,
+ onHeaderClick: PropTypes.func.isRequired,
+ },
+
+ componentDidMount() {
+ this.background = new WaterfallBackground(document);
+ this.background.draw(this.props);
+ },
+
+ componentDidUpdate() {
+ this.background.draw(this.props);
+ },
+
+ componentWillUnmount() {
+ this.background.destroy();
+ this.background = null;
+ },
+
+ render() {
+ const { sort, scale, waterfallWidth, onHeaderClick } = this.props;
+
+ return div(
+ { id: "requests-menu-toolbar", className: "devtools-toolbar" },
+ div({ id: "toolbar-labels" },
+ HEADERS.map(header => {
+ const name = header.name;
+ const boxName = header.boxName || name;
+ const label = L10N.getStr(`netmonitor.toolbar.${header.label || name}`);
+
+ let sorted, sortedTitle;
+ const active = sort.type == name ? true : undefined;
+ if (active) {
+ sorted = sort.ascending ? "ascending" : "descending";
+ sortedTitle = L10N.getStr(sort.ascending
+ ? "networkMenu.sortedAsc"
+ : "networkMenu.sortedDesc");
+ }
+
+ return div(
+ {
+ id: `requests-menu-${boxName}-header-box`,
+ key: name,
+ className: `requests-menu-header requests-menu-${boxName}`,
+ // Used to style the next column.
+ "data-active": active,
+ },
+ button(
+ {
+ id: `requests-menu-${name}-button`,
+ className: `requests-menu-header-button requests-menu-${name}`,
+ "data-sorted": sorted,
+ title: sortedTitle,
+ onClick: () => onHeaderClick(name),
+ },
+ name == "waterfall" ? WaterfallLabel(waterfallWidth, scale, label)
+ : div({ className: "button-text" }, label),
+ div({ className: "button-icon" })
+ )
+ );
+ })
+ )
+ );
+ }
+});
+
+/**
+ * Build the waterfall header - timing tick marks with the right spacing
+ */
+function waterfallDivisionLabels(waterfallWidth, scale) {
+ let labels = [];
+
+ // Build new millisecond tick labels...
+ let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
+ let scaledStep = scale * timingStep;
+
+ // Ignore any divisions that would end up being too close to each other.
+ while (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
+ scaledStep *= 2;
+ }
+
+ // Insert one label for each division on the current scale.
+ for (let x = 0; x < waterfallWidth; x += scaledStep) {
+ let millisecondTime = x / scale;
+
+ let normalizedTime = millisecondTime;
+ let divisionScale = "millisecond";
+
+ // If the division is greater than 1 minute.
+ if (normalizedTime > 60000) {
+ normalizedTime /= 60000;
+ divisionScale = "minute";
+ } else if (normalizedTime > 1000) {
+ // If the division is greater than 1 second.
+ normalizedTime /= 1000;
+ divisionScale = "second";
+ }
+
+ // Showing too many decimals is bad UX.
+ if (divisionScale == "millisecond") {
+ normalizedTime |= 0;
+ } else {
+ normalizedTime = L10N.numberWithDecimals(normalizedTime, REQUEST_TIME_DECIMALS);
+ }
+
+ let width = (x + scaledStep | 0) - (x | 0);
+ // Adjust the first marker for the borders
+ if (x == 0) {
+ width -= 2;
+ }
+ // Last marker doesn't need a width specified at all
+ if (x + scaledStep >= waterfallWidth) {
+ width = undefined;
+ }
+
+ labels.push(div(
+ {
+ key: labels.length,
+ className: "requests-menu-timings-division",
+ "data-division-scale": divisionScale,
+ style: { width }
+ },
+ L10N.getFormatStr("networkMenu." + divisionScale, normalizedTime)
+ ));
+ }
+
+ return labels;
+}
+
+function WaterfallLabel(waterfallWidth, scale, label) {
+ let className = "button-text requests-menu-waterfall-label-wrapper";
+
+ if (scale != null) {
+ label = waterfallDivisionLabels(waterfallWidth, scale);
+ className += " requests-menu-waterfall-visible";
+ }
+
+ return div({ className }, label);
+}
+
+module.exports = connect(
+ state => ({
+ sort: state.sort,
+ scale: getWaterfallScale(state),
+ waterfallWidth: state.ui.waterfallWidth,
+ firstRequestStartedMillis: state.requests.firstStartedMillis,
+ timingMarkers: state.timingMarkers,
+ }),
+ dispatch => ({
+ onHeaderClick: type => dispatch(Actions.sortBy(type)),
+ })
+)(RequestListHeader);
diff --git a/devtools/client/netmonitor/components/request-list-item.js b/devtools/client/netmonitor/components/request-list-item.js
new file mode 100644
index 000000000000..a7976ea2ae29
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-item.js
@@ -0,0 +1,346 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createClass, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
+const { div, span, img } = DOM;
+const { L10N } = require("../l10n");
+const { getFormattedSize } = require("../utils/format-utils");
+const { getAbbreviatedMimeType } = require("../request-utils");
+
+/**
+ * Render one row in the request list.
+ */
+const RequestListItem = createClass({
+ displayName: "RequestListItem",
+
+ propTypes: {
+ item: PropTypes.object.isRequired,
+ index: PropTypes.number.isRequired,
+ isSelected: PropTypes.bool.isRequired,
+ firstRequestStartedMillis: PropTypes.number.isRequired,
+ onContextMenu: PropTypes.func.isRequired,
+ onMouseDown: PropTypes.func.isRequired,
+ onSecurityIconClick: PropTypes.func.isRequired,
+ },
+
+ componentDidMount() {
+ if (this.props.isSelected) {
+ this.refs.el.focus();
+ }
+ },
+
+ shouldComponentUpdate(nextProps) {
+ return !relevantPropsEqual(this.props.item, nextProps.item)
+ || this.props.index !== nextProps.index
+ || this.props.isSelected !== nextProps.isSelected
+ || this.props.firstRequestStartedMillis !== nextProps.firstRequestStartedMillis;
+ },
+
+ componentDidUpdate(prevProps) {
+ if (!prevProps.isSelected && this.props.isSelected) {
+ this.refs.el.focus();
+ if (this.props.onFocusedNodeChange) {
+ this.props.onFocusedNodeChange();
+ }
+ }
+ },
+
+ componentWillUnmount() {
+ // If this node is being destroyed and has focus, transfer the focus manually
+ // to the parent tree component. Otherwise, the focus will get lost and keyboard
+ // navigation in the tree will stop working. This is a workaround for a XUL bug.
+ // See bugs 1259228 and 1152441 for details.
+ // DE-XUL: Remove this hack once all usages are only in HTML documents.
+ if (this.props.isSelected) {
+ this.refs.el.blur();
+ if (this.props.onFocusedNodeUnmount) {
+ this.props.onFocusedNodeUnmount();
+ }
+ }
+ },
+
+ render() {
+ const {
+ item,
+ index,
+ isSelected,
+ firstRequestStartedMillis,
+ onContextMenu,
+ onMouseDown,
+ onSecurityIconClick
+ } = this.props;
+
+ let classList = [ "request-list-item" ];
+ if (isSelected) {
+ classList.push("selected");
+ }
+ classList.push(index % 2 ? "odd" : "even");
+
+ return div(
+ {
+ ref: "el",
+ className: classList.join(" "),
+ "data-id": item.id,
+ tabIndex: 0,
+ onContextMenu,
+ onMouseDown,
+ },
+ StatusColumn(item),
+ MethodColumn(item),
+ FileColumn(item),
+ DomainColumn(item, onSecurityIconClick),
+ CauseColumn(item),
+ TypeColumn(item),
+ TransferredSizeColumn(item),
+ ContentSizeColumn(item),
+ WaterfallColumn(item, firstRequestStartedMillis)
+ );
+ }
+});
+
+/**
+ * Used by shouldComponentUpdate: compare two items, and compare only properties
+ * relevant for rendering the RequestListItem. Other properties (like request and
+ * response headers, cookies, bodies) are ignored. These are very useful for the
+ * sidebar details, but not here.
+ */
+const RELEVANT_ITEM_PROPS = [
+ "status",
+ "statusText",
+ "fromCache",
+ "fromServiceWorker",
+ "method",
+ "url",
+ "responseContentDataUri",
+ "remoteAddress",
+ "securityState",
+ "cause",
+ "mimeType",
+ "contentSize",
+ "transferredSize",
+ "startedMillis",
+ "totalTime",
+ "eventTimings",
+];
+
+function relevantPropsEqual(item1, item2) {
+ return item1 === item2 || RELEVANT_ITEM_PROPS.every(p => item1[p] === item2[p]);
+}
+
+function StatusColumn(item) {
+ const { status, statusText, fromCache, fromServiceWorker } = item;
+
+ let code, title;
+
+ if (status) {
+ if (fromCache) {
+ code = "cached";
+ } else if (fromServiceWorker) {
+ code = "service worker";
+ } else {
+ code = status;
+ }
+
+ if (statusText) {
+ title = `${status} ${statusText}`;
+ if (fromCache) {
+ title += " (cached)";
+ }
+ if (fromServiceWorker) {
+ title += " (service worker)";
+ }
+ }
+ }
+
+ return div({ className: "requests-menu-subitem requests-menu-status", title },
+ div({ className: "requests-menu-status-icon", "data-code": code }),
+ span({ className: "subitem-label requests-menu-status-code" }, status)
+ );
+}
+
+function MethodColumn(item) {
+ const { method } = item;
+ return div({ className: "requests-menu-subitem requests-menu-method-box" },
+ span({ className: "subitem-label requests-menu-method" }, method)
+ );
+}
+
+function FileColumn(item) {
+ const { urlDetails, responseContentDataUri } = item;
+
+ return div({ className: "requests-menu-subitem requests-menu-icon-and-file" },
+ img({
+ className: "requests-menu-icon",
+ src: responseContentDataUri,
+ hidden: !responseContentDataUri,
+ "data-type": responseContentDataUri ? "thumbnail" : undefined
+ }),
+ div(
+ {
+ className: "subitem-label requests-menu-file",
+ title: urlDetails.unicodeUrl
+ },
+ urlDetails.baseNameWithQuery
+ )
+ );
+}
+
+function DomainColumn(item, onSecurityIconClick) {
+ const { urlDetails, remoteAddress, securityState } = item;
+
+ let iconClassList = [ "requests-security-state-icon" ];
+ let iconTitle;
+ if (urlDetails.isLocal) {
+ iconClassList.push("security-state-local");
+ iconTitle = L10N.getStr("netmonitor.security.state.secure");
+ } else if (securityState) {
+ iconClassList.push(`security-state-${securityState}`);
+ iconTitle = L10N.getStr(`netmonitor.security.state.${securityState}`);
+ }
+
+ let title = urlDetails.host + (remoteAddress ? ` (${remoteAddress})` : "");
+
+ return div(
+ { className: "requests-menu-subitem requests-menu-security-and-domain" },
+ div({
+ className: iconClassList.join(" "),
+ title: iconTitle,
+ onClick: onSecurityIconClick,
+ }),
+ span({ className: "subitem-label requests-menu-domain", title }, urlDetails.host)
+ );
+}
+
+function CauseColumn(item) {
+ const { cause } = item;
+
+ let causeType = "";
+ let causeUri = undefined;
+ let causeHasStack = false;
+
+ if (cause) {
+ causeType = cause.type;
+ causeUri = cause.loadingDocumentUri;
+ causeHasStack = cause.stacktrace && cause.stacktrace.length > 0;
+ }
+
+ return div(
+ { className: "requests-menu-subitem requests-menu-cause", title: causeUri },
+ span({ className: "requests-menu-cause-stack", hidden: !causeHasStack }, "JS"),
+ span({ className: "subitem-label" }, causeType)
+ );
+}
+
+const CONTENT_MIME_TYPE_ABBREVIATIONS = {
+ "ecmascript": "js",
+ "javascript": "js",
+ "x-javascript": "js"
+};
+
+function TypeColumn(item) {
+ const { mimeType } = item;
+ let abbrevType;
+ if (mimeType) {
+ abbrevType = getAbbreviatedMimeType(mimeType);
+ abbrevType = CONTENT_MIME_TYPE_ABBREVIATIONS[abbrevType] || abbrevType;
+ }
+
+ return div(
+ { className: "requests-menu-subitem requests-menu-type", title: mimeType },
+ span({ className: "subitem-label" }, abbrevType)
+ );
+}
+
+function TransferredSizeColumn(item) {
+ const { transferredSize, fromCache, fromServiceWorker } = item;
+
+ let text;
+ let className = "subitem-label";
+ if (fromCache) {
+ text = L10N.getStr("networkMenu.sizeCached");
+ className += " theme-comment";
+ } else if (fromServiceWorker) {
+ text = L10N.getStr("networkMenu.sizeServiceWorker");
+ className += " theme-comment";
+ } else if (typeof transferredSize == "number") {
+ text = getFormattedSize(transferredSize);
+ } else if (transferredSize === null) {
+ text = L10N.getStr("networkMenu.sizeUnavailable");
+ }
+
+ return div(
+ { className: "requests-menu-subitem requests-menu-transferred", title: text },
+ span({ className }, text)
+ );
+}
+
+function ContentSizeColumn(item) {
+ const { contentSize } = item;
+
+ let text;
+ if (typeof contentSize == "number") {
+ text = getFormattedSize(contentSize);
+ }
+
+ return div(
+ { className: "requests-menu-subitem subitem-label requests-menu-size", title: text },
+ span({ className: "subitem-label" }, text)
+ );
+}
+
+// List of properties of the timing info we want to create boxes for
+const TIMING_KEYS = ["blocked", "dns", "connect", "send", "wait", "receive"];
+
+function timingBoxes(item) {
+ const { eventTimings, totalTime, fromCache, fromServiceWorker } = item;
+ let boxes = [];
+
+ if (fromCache || fromServiceWorker) {
+ return boxes;
+ }
+
+ if (eventTimings) {
+ // Add a set of boxes representing timing information.
+ for (let key of TIMING_KEYS) {
+ let width = eventTimings.timings[key];
+
+ // Don't render anything if it surely won't be visible.
+ // One millisecond == one unscaled pixel.
+ if (width > 0) {
+ boxes.push(div({
+ key,
+ className: "requests-menu-timings-box " + key,
+ style: { width }
+ }));
+ }
+ }
+ }
+
+ if (typeof totalTime == "number") {
+ let text = L10N.getFormatStr("networkMenu.totalMS", totalTime);
+ boxes.push(div({
+ key: "total",
+ className: "requests-menu-timings-total",
+ title: text
+ }, text));
+ }
+
+ return boxes;
+}
+
+function WaterfallColumn(item, firstRequestStartedMillis) {
+ const startedDeltaMillis = item.startedMillis - firstRequestStartedMillis;
+ const paddingInlineStart = `${startedDeltaMillis}px`;
+
+ return div({ className: "requests-menu-subitem requests-menu-waterfall" },
+ div(
+ { className: "requests-menu-timings", style: { paddingInlineStart } },
+ timingBoxes(item)
+ )
+ );
+}
+
+module.exports = RequestListItem;
diff --git a/devtools/client/netmonitor/components/request-list-tooltip.js b/devtools/client/netmonitor/components/request-list-tooltip.js
new file mode 100644
index 000000000000..2bea502cf9e3
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list-tooltip.js
@@ -0,0 +1,107 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals gNetwork, NetMonitorController */
+
+"use strict";
+
+const { Task } = require("devtools/shared/task");
+const { formDataURI } = require("../request-utils");
+const { WEBCONSOLE_L10N } = require("../l10n");
+const { setImageTooltip,
+ getImageDimensions } = require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
+
+// px
+const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
+// px
+const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+
+const setTooltipImageContent = Task.async(function* (tooltip, itemEl, requestItem) {
+ let { mimeType, text, encoding } = requestItem.responseContent.content;
+
+ if (!mimeType || !mimeType.includes("image/")) {
+ return false;
+ }
+
+ let string = yield gNetwork.getString(text);
+ let src = formDataURI(mimeType, encoding, string);
+ let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
+ let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src);
+ let options = { maxDim, naturalWidth, naturalHeight };
+ setImageTooltip(tooltip, tooltip.doc, src, options);
+
+ return itemEl.querySelector(".requests-menu-icon");
+});
+
+const setTooltipStackTraceContent = Task.async(function* (tooltip, requestItem) {
+ let {stacktrace} = requestItem.cause;
+
+ if (!stacktrace || stacktrace.length == 0) {
+ return false;
+ }
+
+ let doc = tooltip.doc;
+ let el = doc.createElementNS(HTML_NS, "div");
+ el.className = "stack-trace-tooltip devtools-monospace";
+
+ for (let f of stacktrace) {
+ let { functionName, filename, lineNumber, columnNumber, asyncCause } = f;
+
+ if (asyncCause) {
+ // if there is asyncCause, append a "divider" row into the trace
+ let asyncFrameEl = doc.createElementNS(HTML_NS, "div");
+ asyncFrameEl.className = "stack-frame stack-frame-async";
+ asyncFrameEl.textContent =
+ WEBCONSOLE_L10N.getFormatStr("stacktrace.asyncStack", asyncCause);
+ el.appendChild(asyncFrameEl);
+ }
+
+ // Parse a source name in format "url -> url"
+ let sourceUrl = filename.split(" -> ").pop();
+
+ let frameEl = doc.createElementNS(HTML_NS, "div");
+ frameEl.className = "stack-frame stack-frame-call";
+
+ let funcEl = doc.createElementNS(HTML_NS, "span");
+ funcEl.className = "stack-frame-function-name";
+ funcEl.textContent =
+ functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction");
+ frameEl.appendChild(funcEl);
+
+ let sourceEl = doc.createElementNS(HTML_NS, "span");
+ sourceEl.className = "stack-frame-source-name";
+ frameEl.appendChild(sourceEl);
+
+ let sourceInnerEl = doc.createElementNS(HTML_NS, "span");
+ sourceInnerEl.className = "stack-frame-source-name-inner";
+ sourceEl.appendChild(sourceInnerEl);
+
+ sourceInnerEl.textContent = sourceUrl;
+ sourceInnerEl.title = sourceUrl;
+
+ let lineEl = doc.createElementNS(HTML_NS, "span");
+ lineEl.className = "stack-frame-line";
+ lineEl.textContent = `:${lineNumber}:${columnNumber}`;
+ sourceInnerEl.appendChild(lineEl);
+
+ frameEl.addEventListener("click", () => {
+ // hide the tooltip immediately, not after delay
+ tooltip.hide();
+ NetMonitorController.viewSourceInDebugger(filename, lineNumber);
+ }, false);
+
+ el.appendChild(frameEl);
+ }
+
+ tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
+
+ return true;
+});
+
+module.exports = {
+ setTooltipImageContent,
+ setTooltipStackTraceContent,
+};
diff --git a/devtools/client/netmonitor/components/request-list.js b/devtools/client/netmonitor/components/request-list.js
new file mode 100644
index 000000000000..7048fdb4b629
--- /dev/null
+++ b/devtools/client/netmonitor/components/request-list.js
@@ -0,0 +1,34 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createFactory, PropTypes, DOM } = require("devtools/client/shared/vendor/react");
+const { div } = DOM;
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const RequestListHeader = createFactory(require("./request-list-header"));
+const RequestListEmptyNotice = createFactory(require("./request-list-empty"));
+const RequestListContent = createFactory(require("./request-list-content"));
+
+/**
+ * Renders the request list - header, empty text, the actual content with rows
+ */
+const RequestList = function ({ isEmpty }) {
+ return div({ className: "request-list-container" },
+ RequestListHeader(),
+ isEmpty ? RequestListEmptyNotice() : RequestListContent()
+ );
+};
+
+RequestList.displayName = "RequestList";
+
+RequestList.propTypes = {
+ isEmpty: PropTypes.bool.isRequired,
+};
+
+module.exports = connect(
+ state => ({
+ isEmpty: state.requests.requests.isEmpty()
+ })
+)(RequestList);
diff --git a/devtools/client/netmonitor/components/shared/moz.build b/devtools/client/netmonitor/components/shared/moz.build
new file mode 100644
index 000000000000..ce44aaec6a88
--- /dev/null
+++ b/devtools/client/netmonitor/components/shared/moz.build
@@ -0,0 +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 http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'timings-panel.js',
+)
diff --git a/devtools/client/netmonitor/components/shared/timings-panel.js b/devtools/client/netmonitor/components/shared/timings-panel.js
new file mode 100644
index 000000000000..49924701891f
--- /dev/null
+++ b/devtools/client/netmonitor/components/shared/timings-panel.js
@@ -0,0 +1,85 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const { getSelectedRequest } = require("../../selectors/index");
+
+const { div, span } = DOM;
+const types = ["blocked", "dns", "connect", "send", "wait", "receive"];
+const TIMINGS_END_PADDING = "80px";
+
+/*
+ * Timings panel component
+ * Display timeline bars that shows the total wait time for various stages
+ */
+function TimingsPanel({
+ timings = {},
+ totalTime = 0,
+}) {
+ const timelines = types.map((type, idx) => {
+ // Determine the relative offset for each timings box. For example, the
+ // offset of third timings box will be 0 + blocked offset + dns offset
+ const offset = types
+ .slice(0, idx)
+ .reduce((acc, cur) => (acc + timings[cur] || 0), 0);
+ const offsetScale = offset / totalTime || 0;
+ const timelineScale = timings[type] / totalTime || 0;
+
+ return div({
+ key: type,
+ id: `timings-summary-${type}`,
+ className: "tabpanel-summary-container",
+ },
+ span({ className: "tabpanel-summary-label" },
+ L10N.getStr(`netmonitor.timings.${type}`)
+ ),
+ div({ className: "requests-menu-timings-container" },
+ span({
+ className: "requests-menu-timings-offset",
+ style: {
+ width: `calc(${offsetScale} * (100% - ${TIMINGS_END_PADDING})`,
+ },
+ }),
+ span({
+ className: `requests-menu-timings-box ${type}`,
+ style: {
+ width: `calc(${timelineScale} * (100% - ${TIMINGS_END_PADDING}))`,
+ },
+ }),
+ span({ className: "requests-menu-timings-total" },
+ L10N.getFormatStr("networkMenu.totalMS", timings[type])
+ )
+ ),
+ );
+ });
+
+ return div({}, timelines);
+}
+
+TimingsPanel.displayName = "TimingsPanel";
+
+TimingsPanel.propTypes = {
+ timings: PropTypes.object,
+ totalTime: PropTypes.number,
+};
+
+module.exports = connect(
+ (state) => {
+ const selectedRequest = getSelectedRequest(state);
+
+ if (selectedRequest && selectedRequest.eventTimings) {
+ const { timings, totalTime } = selectedRequest.eventTimings;
+ return {
+ timings,
+ totalTime,
+ };
+ }
+
+ return {};
+ }
+)(TimingsPanel);
diff --git a/devtools/client/netmonitor/components/summary-button.js b/devtools/client/netmonitor/components/summary-button.js
index 9465d3147b6c..7b8980d4ec5c 100644
--- a/devtools/client/netmonitor/components/summary-button.js
+++ b/devtools/client/netmonitor/components/summary-button.js
@@ -14,7 +14,7 @@ const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { PluralForm } = require("devtools/shared/plural-form");
const { L10N } = require("../l10n");
-const { getSummary } = require("../selectors/index");
+const { getDisplayedRequestsSummary } = require("../selectors/index");
const { button, span } = DOM;
@@ -22,14 +22,12 @@ function SummaryButton({
summary,
triggerSummary,
}) {
- let { count, totalBytes, totalMillis } = summary;
+ let { count, bytes, millis } = summary;
const text = (count === 0) ? L10N.getStr("networkMenu.empty") :
PluralForm.get(count, L10N.getStr("networkMenu.summary"))
.replace("#1", count)
- .replace("#2", L10N.numberWithDecimals(totalBytes / 1024,
- CONTENT_SIZE_DECIMALS))
- .replace("#3", L10N.numberWithDecimals(totalMillis / 1000,
- REQUEST_TIME_DECIMALS));
+ .replace("#2", L10N.numberWithDecimals(bytes / 1024, CONTENT_SIZE_DECIMALS))
+ .replace("#3", L10N.numberWithDecimals(millis / 1000, REQUEST_TIME_DECIMALS));
return button({
id: "requests-menu-network-summary-button",
@@ -47,7 +45,7 @@ SummaryButton.propTypes = {
module.exports = connect(
(state) => ({
- summary: getSummary(state),
+ summary: getDisplayedRequestsSummary(state),
}),
(dispatch) => ({
triggerSummary: () => {
diff --git a/devtools/client/netmonitor/components/toggle-button.js b/devtools/client/netmonitor/components/toggle-button.js
index 2a3cea027529..57d7f3a924e1 100644
--- a/devtools/client/netmonitor/components/toggle-button.js
+++ b/devtools/client/netmonitor/components/toggle-button.js
@@ -2,21 +2,20 @@
* 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/. */
-/* globals NetMonitorView */
-
"use strict";
const { DOM, PropTypes } = require("devtools/client/shared/vendor/react");
const { connect } = require("devtools/client/shared/vendor/react-redux");
const { L10N } = require("../l10n");
const Actions = require("../actions/index");
+const { isSidebarToggleButtonDisabled } = require("../selectors/index");
const { button } = DOM;
function ToggleButton({
disabled,
open,
- triggerSidebar,
+ onToggle,
}) {
let className = ["devtools-button"];
if (!open) {
@@ -32,34 +31,21 @@ function ToggleButton({
title,
disabled,
tabIndex: "0",
- onMouseDown: triggerSidebar,
+ onMouseDown: onToggle,
});
}
ToggleButton.propTypes = {
disabled: PropTypes.bool.isRequired,
- triggerSidebar: PropTypes.func.isRequired,
+ onToggle: PropTypes.func.isRequired,
};
module.exports = connect(
(state) => ({
- disabled: state.requests.items.length === 0,
- open: state.ui.sidebar.open,
+ disabled: isSidebarToggleButtonDisabled(state),
+ open: state.ui.sidebarOpen,
}),
(dispatch) => ({
- triggerSidebar: () => {
- dispatch(Actions.toggleSidebar());
-
- let requestsMenu = NetMonitorView.RequestsMenu;
- let selectedIndex = requestsMenu.selectedIndex;
-
- // Make sure there's a selection if the button is pressed, to avoid
- // showing an empty network details pane.
- if (selectedIndex == -1 && requestsMenu.itemCount) {
- requestsMenu.selectedIndex = 0;
- } else {
- requestsMenu.selectedIndex = -1;
- }
- },
+ onToggle: () => dispatch(Actions.toggleSidebar())
})
)(ToggleButton);
diff --git a/devtools/client/netmonitor/constants.js b/devtools/client/netmonitor/constants.js
index 48f520e64401..de98cf7c29d0 100644
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -11,12 +11,40 @@ const general = {
};
const actionTypes = {
+ BATCH_ACTIONS: "BATCH_ACTIONS",
+ BATCH_ENABLE: "BATCH_ENABLE",
+ ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
+ CLEAR_TIMING_MARKERS: "CLEAR_TIMING_MARKERS",
+ ADD_REQUEST: "ADD_REQUEST",
+ UPDATE_REQUEST: "UPDATE_REQUEST",
+ CLEAR_REQUESTS: "CLEAR_REQUESTS",
+ CLONE_SELECTED_REQUEST: "CLONE_SELECTED_REQUEST",
+ REMOVE_SELECTED_CUSTOM_REQUEST: "REMOVE_SELECTED_CUSTOM_REQUEST",
+ SELECT_REQUEST: "SELECT_REQUEST",
+ PRESELECT_REQUEST: "PRESELECT_REQUEST",
+ SORT_BY: "SORT_BY",
TOGGLE_FILTER_TYPE: "TOGGLE_FILTER_TYPE",
ENABLE_FILTER_TYPE_ONLY: "ENABLE_FILTER_TYPE_ONLY",
SET_FILTER_TEXT: "SET_FILTER_TEXT",
OPEN_SIDEBAR: "OPEN_SIDEBAR",
- TOGGLE_SIDEBAR: "TOGGLE_SIDEBAR",
- UPDATE_REQUESTS: "UPDATE_REQUESTS",
+ WATERFALL_RESIZE: "WATERFALL_RESIZE",
};
-module.exports = Object.assign({}, general, actionTypes);
+// Descriptions for what this frontend is currently doing.
+const ACTIVITY_TYPE = {
+ // Standing by and handling requests normally.
+ NONE: 0,
+
+ // Forcing the target to reload with cache enabled or disabled.
+ RELOAD: {
+ WITH_CACHE_ENABLED: 1,
+ WITH_CACHE_DISABLED: 2,
+ WITH_CACHE_DEFAULT: 3
+ },
+
+ // Enabling or disabling the cache without triggering a reload.
+ ENABLE_CACHE: 3,
+ DISABLE_CACHE: 4
+};
+
+module.exports = Object.assign({ ACTIVITY_TYPE }, general, actionTypes);
diff --git a/devtools/client/netmonitor/custom-request-view.js b/devtools/client/netmonitor/custom-request-view.js
index 06871a61f483..f7ebf27e3d13 100644
--- a/devtools/client/netmonitor/custom-request-view.js
+++ b/devtools/client/netmonitor/custom-request-view.js
@@ -7,12 +7,11 @@
"use strict";
const { Task } = require("devtools/shared/task");
-const {
- writeHeaderText,
- getKeyWithEvent,
- getUrlQuery,
- parseQueryString,
-} = require("./request-utils");
+const { writeHeaderText,
+ getKeyWithEvent,
+ getUrlQuery,
+ parseQueryString } = require("./request-utils");
+const Actions = require("./actions/index");
/**
* Functions handling the custom request view.
@@ -76,37 +75,41 @@ CustomRequestView.prototype = {
*/
onUpdate: function (field) {
let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
+ let store = NetMonitorView.RequestsMenu.store;
let value;
switch (field) {
case "method":
value = $("#custom-method-value").value.trim();
- selectedItem.attachment.method = value;
+ store.dispatch(Actions.updateRequest(selectedItem.id, { method: value }));
break;
case "url":
value = $("#custom-url-value").value;
this.updateCustomQuery(value);
- selectedItem.attachment.url = value;
+ store.dispatch(Actions.updateRequest(selectedItem.id, { url: value }));
break;
case "query":
let query = $("#custom-query-value").value;
this.updateCustomUrl(query);
- field = "url";
value = $("#custom-url-value").value;
- selectedItem.attachment.url = value;
+ store.dispatch(Actions.updateRequest(selectedItem.id, { url: value }));
break;
case "body":
value = $("#custom-postdata-value").value;
- selectedItem.attachment.requestPostData = { postData: { text: value } };
+ store.dispatch(Actions.updateRequest(selectedItem.id, {
+ requestPostData: {
+ postData: { text: value }
+ }
+ }));
break;
case "headers":
let headersText = $("#custom-headers-value").value;
value = parseHeadersText(headersText);
- selectedItem.attachment.requestHeaders = { headers: value };
+ store.dispatch(Actions.updateRequest(selectedItem.id, {
+ requestHeaders: { headers: value }
+ }));
break;
}
-
- NetMonitorView.RequestsMenu.updateMenuView(selectedItem, field, value);
},
/**
@@ -161,7 +164,7 @@ function parseHeadersText(text) {
* Parse readable text list of a query string.
*
* @param string text
- * Text of query string represetation
+ * Text of query string representation
* @return array
* Array of query params {name, value}
*/
diff --git a/devtools/client/netmonitor/details-view.js b/devtools/client/netmonitor/details-view.js
index 97687323a131..9d531f11a781 100644
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -2,14 +2,14 @@
* 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/. */
-/* import-globals-from ./netmonitor-controller.js */
/* eslint-disable mozilla/reject-some-requires */
-/* globals dumpn, $, NetMonitorView, gNetwork */
+/* globals window, dumpn, $, NetMonitorView, gNetwork */
"use strict";
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
+const Editor = require("devtools/client/sourceeditor/editor");
const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
const { Task } = require("devtools/shared/task");
const { ToolSidebar } = require("devtools/client/framework/sidebar");
@@ -27,6 +27,10 @@ const {
getUrlHost,
parseQueryString,
} = require("./request-utils");
+const { createFactory } = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const Provider = createFactory(require("devtools/client/shared/vendor/react-redux").Provider);
+const TimingsPanel = createFactory(require("./components/shared/timings-panel"));
// 100 KB in bytes
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
@@ -86,9 +90,16 @@ DetailsView.prototype = {
/**
* Initialization function, called when the network monitor is started.
*/
- initialize: function () {
+ initialize: function (store) {
dumpn("Initializing the DetailsView");
+ this._timingsPanelNode = $("#react-timings-tabpanel-hook");
+
+ ReactDOM.render(Provider(
+ { store },
+ TimingsPanel()
+ ), this._timingsPanelNode);
+
this.widget = $("#event-details-pane");
this.sidebar = new ToolSidebar(this.widget, this, "netmonitor", {
disableTelemetry: true,
@@ -134,6 +145,7 @@ DetailsView.prototype = {
*/
destroy: function () {
dumpn("Destroying the DetailsView");
+ ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
this.sidebar.destroy();
$("tabpanels", this.widget).removeEventListener("select",
this._onTabSelect);
@@ -243,10 +255,6 @@ DetailsView.prototype = {
case 3:
yield view._setResponseBody(src.url, src.responseContent);
break;
- // "Timings"
- case 4:
- yield view._setTimingsInformation(src.eventTimings);
- break;
// "Security"
case 5:
yield view._setSecurityInfo(src.securityInfo, src.url);
@@ -267,10 +275,6 @@ DetailsView.prototype = {
// Tab is selected but not dirty. We're done here.
populated[tab] = true;
window.emit(EVENTS.TAB_UPDATED);
-
- if (NetMonitorController.isConnected()) {
- NetMonitorView.RequestsMenu.ensureSelectedItemIsVisible();
- }
}
} else if (viewState.dirty[tab]) {
// Tab is dirty but no longer selected. Don't refresh it now, it'll be
@@ -328,7 +332,7 @@ DetailsView.prototype = {
} else {
code = data.status;
}
- $("#headers-summary-status-circle").setAttribute("code", code);
+ $("#headers-summary-status-circle").setAttribute("data-code", code);
$("#headers-summary-status-value").setAttribute("value",
data.status + " " + data.statusText);
$("#headers-summary-status").removeAttribute("hidden");
@@ -689,87 +693,6 @@ DetailsView.prototype = {
window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
}),
- /**
- * Sets the timings information shown in this view.
- *
- * @param object response
- * The message received from the server.
- */
- _setTimingsInformation: function (response) {
- if (!response) {
- return;
- }
- let { blocked, dns, connect, send, wait, receive } = response.timings;
-
- let tabboxWidth = $("#details-pane").getAttribute("width");
-
- // Other nodes also take some space.
- let availableWidth = tabboxWidth / 2;
- let scale = (response.totalTime > 0 ?
- Math.max(availableWidth / response.totalTime, 0) :
- 0);
-
- $("#timings-summary-blocked .requests-menu-timings-box")
- .setAttribute("width", blocked * scale);
- $("#timings-summary-blocked .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", blocked));
-
- $("#timings-summary-dns .requests-menu-timings-box")
- .setAttribute("width", dns * scale);
- $("#timings-summary-dns .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", dns));
-
- $("#timings-summary-connect .requests-menu-timings-box")
- .setAttribute("width", connect * scale);
- $("#timings-summary-connect .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", connect));
-
- $("#timings-summary-send .requests-menu-timings-box")
- .setAttribute("width", send * scale);
- $("#timings-summary-send .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", send));
-
- $("#timings-summary-wait .requests-menu-timings-box")
- .setAttribute("width", wait * scale);
- $("#timings-summary-wait .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", wait));
-
- $("#timings-summary-receive .requests-menu-timings-box")
- .setAttribute("width", receive * scale);
- $("#timings-summary-receive .requests-menu-timings-total")
- .setAttribute("value", L10N.getFormatStr("networkMenu.totalMS", receive));
-
- $("#timings-summary-dns .requests-menu-timings-box")
- .style.transform = "translateX(" + (scale * blocked) + "px)";
- $("#timings-summary-connect .requests-menu-timings-box")
- .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
- $("#timings-summary-send .requests-menu-timings-box")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect)) + "px)";
- $("#timings-summary-wait .requests-menu-timings-box")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
- $("#timings-summary-receive .requests-menu-timings-box")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
- "px)";
-
- $("#timings-summary-dns .requests-menu-timings-total")
- .style.transform = "translateX(" + (scale * blocked) + "px)";
- $("#timings-summary-connect .requests-menu-timings-total")
- .style.transform = "translateX(" + (scale * (blocked + dns)) + "px)";
- $("#timings-summary-send .requests-menu-timings-total")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect)) + "px)";
- $("#timings-summary-wait .requests-menu-timings-total")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect + send)) + "px)";
- $("#timings-summary-receive .requests-menu-timings-total")
- .style.transform =
- "translateX(" + (scale * (blocked + dns + connect + send + wait)) +
- "px)";
- },
-
/**
* Sets the preview for HTML responses shown in this view.
*
diff --git a/devtools/client/netmonitor/har/har-builder.js b/devtools/client/netmonitor/har/har-builder.js
index 565a4be0973c..698be8dcfd01 100644
--- a/devtools/client/netmonitor/har/har-builder.js
+++ b/devtools/client/netmonitor/har/har-builder.js
@@ -63,9 +63,7 @@ HarBuilder.prototype = {
let log = this.buildLog();
// Build entries.
- let items = this._options.items;
- for (let i = 0; i < items.length; i++) {
- let file = items[i].attachment;
+ for (let file of this._options.items) {
log.entries.push(this.buildEntry(log, file));
}
diff --git a/devtools/client/netmonitor/har/har-collector.js b/devtools/client/netmonitor/har/har-collector.js
index a55326a51e16..315066bf7572 100644
--- a/devtools/client/netmonitor/har/har-collector.js
+++ b/devtools/client/netmonitor/har/har-collector.js
@@ -201,9 +201,7 @@ HarCollector.prototype = {
this.files.set(actor, file);
// Mimic the Net panel data structure
- this.items.push({
- attachment: file
- });
+ this.items.push(file);
},
onNetworkEventUpdate: function (type, packet) {
diff --git a/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
index 882088cd19bd..564eea991f99 100644
--- a/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
@@ -16,7 +16,7 @@ function* throttleUploadTest(actuallyThrottle) {
info("Starting test... (actuallyThrottle = " + actuallyThrottle + ")");
- let { NetMonitorView } = monitor.panelWin;
+ let { NetMonitorController, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
const size = 4096;
@@ -32,7 +32,7 @@ function* throttleUploadTest(actuallyThrottle) {
uploadBPSMax: uploadSize,
},
};
- let client = monitor._controller.webConsoleClient;
+ let client = NetMonitorController.webConsoleClient;
info("sending throttle request");
let deferred = promise.defer();
diff --git a/devtools/client/netmonitor/middleware/batching.js b/devtools/client/netmonitor/middleware/batching.js
new file mode 100644
index 000000000000..bf53d787bb0c
--- /dev/null
+++ b/devtools/client/netmonitor/middleware/batching.js
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { BATCH_ACTIONS, BATCH_ENABLE, BATCH_RESET } = require("../constants");
+
+// ms
+const REQUESTS_REFRESH_RATE = 50;
+
+/**
+ * Middleware that watches for actions with a "batch = true" value in their meta field.
+ * These actions are queued and dispatched as one batch after a timeout.
+ * Special actions that are handled by this middleware:
+ * - BATCH_ENABLE can be used to enable and disable the batching.
+ * - BATCH_RESET discards the actions that are currently in the queue.
+ */
+function batchingMiddleware(store) {
+ return next => {
+ let queuedActions = [];
+ let enabled = true;
+ let flushTask = null;
+
+ return action => {
+ if (action.type === BATCH_ENABLE) {
+ return setEnabled(action.enabled);
+ }
+
+ if (action.type === BATCH_RESET) {
+ return resetQueue();
+ }
+
+ if (action.meta && action.meta.batch) {
+ if (!enabled) {
+ next(action);
+ return Promise.resolve();
+ }
+
+ queuedActions.push(action);
+
+ if (!flushTask) {
+ flushTask = new DelayedTask(flushActions, REQUESTS_REFRESH_RATE);
+ }
+
+ return flushTask.promise;
+ }
+
+ return next(action);
+ };
+
+ function setEnabled(value) {
+ enabled = value;
+
+ // If disabling the batching, flush the actions that have been queued so far
+ if (!enabled && flushTask) {
+ flushTask.runNow();
+ }
+ }
+
+ function resetQueue() {
+ queuedActions = [];
+
+ if (flushTask) {
+ flushTask.cancel();
+ flushTask = null;
+ }
+ }
+
+ function flushActions() {
+ const actions = queuedActions;
+ queuedActions = [];
+
+ next({
+ type: BATCH_ACTIONS,
+ actions,
+ });
+
+ flushTask = null;
+ }
+ };
+}
+
+/**
+ * Create a delayed task that calls the specified task function after a delay.
+ */
+function DelayedTask(taskFn, delay) {
+ this._promise = new Promise((resolve, reject) => {
+ this.runTask = (cancel) => {
+ if (cancel) {
+ reject("Task cancelled");
+ } else {
+ taskFn();
+ resolve();
+ }
+ this.runTask = null;
+ };
+ this.timeout = setTimeout(this.runTask, delay);
+ }).catch(console.error);
+}
+
+DelayedTask.prototype = {
+ /**
+ * Return a promise that is resolved after the task is performed or canceled.
+ */
+ get promise() {
+ return this._promise;
+ },
+
+ /**
+ * Cancel the execution of the task.
+ */
+ cancel() {
+ clearTimeout(this.timeout);
+ if (this.runTask) {
+ this.runTask(true);
+ }
+ },
+
+ /**
+ * Execute the scheduled task immediately, without waiting for the timeout.
+ * Resolves the promise correctly.
+ */
+ runNow() {
+ clearTimeout(this.timeout);
+ if (this.runTask) {
+ this.runTask();
+ }
+ }
+};
+
+module.exports = batchingMiddleware;
diff --git a/devtools/client/netmonitor/middleware/moz.build b/devtools/client/netmonitor/middleware/moz.build
new file mode 100644
index 000000000000..19c8f8b69de3
--- /dev/null
+++ b/devtools/client/netmonitor/middleware/moz.build
@@ -0,0 +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 http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'batching.js',
+)
diff --git a/devtools/client/netmonitor/moz.build b/devtools/client/netmonitor/moz.build
index b80c8726a458..107316ebe851 100644
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -6,8 +6,10 @@ DIRS += [
'actions',
'components',
'har',
+ 'middleware',
'reducers',
- 'selectors'
+ 'selectors',
+ 'utils',
]
DevToolsModules(
@@ -17,6 +19,8 @@ DevToolsModules(
'events.js',
'filter-predicates.js',
'l10n.js',
+ 'netmonitor-controller.js',
+ 'netmonitor-view.js',
'panel.js',
'performance-statistics-view.js',
'prefs.js',
@@ -27,6 +31,7 @@ DevToolsModules(
'sort-predicates.js',
'store.js',
'toolbar-view.js',
+ 'waterfall-background.js',
)
BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
diff --git a/devtools/client/netmonitor/netmonitor-controller.js b/devtools/client/netmonitor/netmonitor-controller.js
index 2cd595a30d9d..5017898c9493 100644
--- a/devtools/client/netmonitor/netmonitor-controller.js
+++ b/devtools/client/netmonitor/netmonitor-controller.js
@@ -3,37 +3,10 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable mozilla/reject-some-requires */
-/* globals window, document, NetMonitorView, gStore, Actions */
-/* exported loader */
+/* globals window, NetMonitorView, gStore, dumpn */
"use strict";
-var { utils: Cu } = Components;
-
-// Descriptions for what this frontend is currently doing.
-const ACTIVITY_TYPE = {
- // Standing by and handling requests normally.
- NONE: 0,
-
- // Forcing the target to reload with cache enabled or disabled.
- RELOAD: {
- WITH_CACHE_ENABLED: 1,
- WITH_CACHE_DISABLED: 2,
- WITH_CACHE_DEFAULT: 3
- },
-
- // Enabling or disabling the cache without triggering a reload.
- ENABLE_CACHE: 3,
- DISABLE_CACHE: 4
-};
-
-var BrowserLoaderModule = {};
-Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
-var { loader, require } = BrowserLoaderModule.BrowserLoader({
- baseURI: "resource://devtools/client/netmonitor/",
- window
-});
-
const promise = require("promise");
const Services = require("Services");
const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
@@ -41,20 +14,22 @@ const EventEmitter = require("devtools/shared/event-emitter");
const Editor = require("devtools/client/sourceeditor/editor");
const {TimelineFront} = require("devtools/shared/fronts/timeline");
const {Task} = require("devtools/shared/task");
-const {Prefs} = require("./prefs");
-const {EVENTS} = require("./events");
+const { ACTIVITY_TYPE } = require("./constants");
+const { EVENTS } = require("./events");
+const { configureStore } = require("./store");
const Actions = require("./actions/index");
+const { getDisplayedRequestById } = require("./selectors/index");
+const { Prefs } = require("./prefs");
-XPCOMUtils.defineConstant(this, "EVENTS", EVENTS);
-XPCOMUtils.defineConstant(this, "ACTIVITY_TYPE", ACTIVITY_TYPE);
-XPCOMUtils.defineConstant(this, "Editor", Editor);
-XPCOMUtils.defineConstant(this, "Prefs", Prefs);
-
-XPCOMUtils.defineLazyModuleGetter(this, "Chart",
+XPCOMUtils.defineConstant(window, "EVENTS", EVENTS);
+XPCOMUtils.defineConstant(window, "ACTIVITY_TYPE", ACTIVITY_TYPE);
+XPCOMUtils.defineConstant(window, "Editor", Editor);
+XPCOMUtils.defineConstant(window, "Prefs", Prefs);
+XPCOMUtils.defineLazyModuleGetter(window, "Chart",
"resource://devtools/client/shared/widgets/Chart.jsm");
-XPCOMUtils.defineLazyServiceGetter(this, "clipboardHelper",
- "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
+// Initialize the global Redux store
+window.gStore = configureStore();
/**
* Object defining the network monitor controller components.
@@ -91,6 +66,7 @@ var NetMonitorController = {
}
this._shutdown = promise.defer();
{
+ gStore.dispatch(Actions.batchReset());
NetMonitorView.destroy();
this.TargetEventsHandler.disconnect();
this.NetworkEventsHandler.disconnect();
@@ -287,19 +263,18 @@ var NetMonitorController = {
let deferred = promise.defer();
let request = null;
let inspector = function () {
- let predicate = i => i.value === requestId;
- request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
+ request = getDisplayedRequestById(gStore.getState(), requestId);
if (!request) {
// Reset filters so that the request is visible.
gStore.dispatch(Actions.toggleFilterType("all"));
- request = NetMonitorView.RequestsMenu.getItemForPredicate(predicate);
+ request = getDisplayedRequestById(gStore.getState(), requestId);
}
// If the request was found, select it. Otherwise this function will be
// called again once new requests arrive.
if (request) {
window.off(EVENTS.REQUEST_ADDED, inspector);
- NetMonitorView.RequestsMenu.selectedItem = request;
+ gStore.dispatch(Actions.selectRequest(request.id));
deferred.resolve();
}
};
@@ -398,14 +373,14 @@ TargetEventsHandler.prototype = {
// Reset UI.
if (!Services.prefs.getBoolPref("devtools.webconsole.persistlog")) {
NetMonitorView.RequestsMenu.reset();
- NetMonitorView.Sidebar.toggle(false);
+ } else {
+ // If the log is persistent, just clear all accumulated timing markers.
+ gStore.dispatch(Actions.clearTimingMarkers());
}
// Switch to the default network traffic inspector view.
if (NetMonitorController.getCurrentActivity() == ACTIVITY_TYPE.NONE) {
NetMonitorView.showNetworkInspectorView();
}
- // Clear any accumulated markers.
- NetMonitorController.NetworkEventsHandler.clearMarkers();
window.emit(EVENTS.TARGET_WILL_NAVIGATE);
break;
@@ -429,8 +404,6 @@ TargetEventsHandler.prototype = {
* Functions handling target network events.
*/
function NetworkEventsHandler() {
- this._markers = [];
-
this._onNetworkEvent = this._onNetworkEvent.bind(this);
this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this);
@@ -456,19 +429,6 @@ NetworkEventsHandler.prototype = {
return NetMonitorController.timelineFront;
},
- get firstDocumentDOMContentLoadedTimestamp() {
- let marker = this._markers.filter(e => {
- return e.name == "document::DOMContentLoaded";
- })[0];
-
- return marker ? marker.unixTime / 1000 : -1;
- },
-
- get firstDocumentLoadTimestamp() {
- let marker = this._markers.filter(e => e.name == "document::Load")[0];
- return marker ? marker.unixTime / 1000 : -1;
- },
-
/**
* Connect to the current target client.
*/
@@ -525,7 +485,7 @@ NetworkEventsHandler.prototype = {
*/
_onDocLoadingMarker: function (marker) {
window.emit(EVENTS.TIMELINE_EVENT, marker);
- this._markers.push(marker);
+ gStore.dispatch(Actions.addTimingMarker(marker));
},
/**
@@ -547,8 +507,7 @@ NetworkEventsHandler.prototype = {
} = networkInfo;
NetMonitorView.RequestsMenu.addRequest(
- actor, startedDateTime, method, url, isXHR, cause, fromCache,
- fromServiceWorker
+ actor, {startedDateTime, method, url, isXHR, cause, fromCache, fromServiceWorker}
);
window.emit(EVENTS.NETWORK_EVENT, actor);
},
@@ -637,7 +596,7 @@ NetworkEventsHandler.prototype = {
_onRequestHeaders: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
requestHeaders: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_REQUEST_HEADERS, response.from);
});
},
@@ -651,7 +610,7 @@ NetworkEventsHandler.prototype = {
_onRequestCookies: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
requestCookies: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_REQUEST_COOKIES, response.from);
});
},
@@ -665,7 +624,7 @@ NetworkEventsHandler.prototype = {
_onRequestPostData: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
requestPostData: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_REQUEST_POST_DATA, response.from);
});
},
@@ -679,7 +638,7 @@ NetworkEventsHandler.prototype = {
_onSecurityInfo: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
securityInfo: response.securityInfo
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_SECURITY_INFO, response.from);
});
},
@@ -693,7 +652,7 @@ NetworkEventsHandler.prototype = {
_onResponseHeaders: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
responseHeaders: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_RESPONSE_HEADERS, response.from);
});
},
@@ -707,7 +666,7 @@ NetworkEventsHandler.prototype = {
_onResponseCookies: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
responseCookies: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_RESPONSE_COOKIES, response.from);
});
},
@@ -721,7 +680,7 @@ NetworkEventsHandler.prototype = {
_onResponseContent: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
responseContent: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_RESPONSE_CONTENT, response.from);
});
},
@@ -735,18 +694,11 @@ NetworkEventsHandler.prototype = {
_onEventTimings: function (response) {
NetMonitorView.RequestsMenu.updateRequest(response.from, {
eventTimings: response
- }, () => {
+ }).then(() => {
window.emit(EVENTS.RECEIVED_EVENT_TIMINGS, response.from);
});
},
- /**
- * Clears all accumulated markers.
- */
- clearMarkers: function () {
- this._markers.length = 0;
- },
-
/**
* Fetches the full text of a LongString.
*
@@ -763,19 +715,10 @@ NetworkEventsHandler.prototype = {
}
};
-/**
- * Returns true if this is document is in RTL mode.
- * @return boolean
- */
-XPCOMUtils.defineLazyGetter(window, "isRTL", function () {
- return window.getComputedStyle(document.documentElement, null)
- .direction == "rtl";
-});
-
/**
* Convenient way of emitting events from the panel window.
*/
-EventEmitter.decorate(this);
+EventEmitter.decorate(window);
/**
* Preliminary setup for the NetMonitorController object.
@@ -795,14 +738,4 @@ Object.defineProperties(window, {
}
});
-/**
- * Helper method for debugging.
- * @param string
- */
-function dumpn(str) {
- if (wantLogging) {
- dump("NET-FRONTEND: " + str + "\n");
- }
-}
-
-var wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
+exports.NetMonitorController = NetMonitorController;
diff --git a/devtools/client/netmonitor/netmonitor-view.js b/devtools/client/netmonitor/netmonitor-view.js
index b13b3f91df14..84461e406021 100644
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -2,25 +2,25 @@
* 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/. */
-/* import-globals-from ./netmonitor-controller.js */
/* eslint-disable mozilla/reject-some-requires */
-/* globals Prefs, setInterval, setTimeout, clearInterval, clearTimeout, btoa */
-/* exported $, $all */
+/* globals $, gStore, NetMonitorController, dumpn */
"use strict";
const { testing: isTesting } = require("devtools/shared/flags");
+const promise = require("promise");
+const Editor = require("devtools/client/sourceeditor/editor");
+const { Task } = require("devtools/shared/task");
const { ViewHelpers } = require("devtools/client/shared/widgets/view-helpers");
-const { configureStore } = require("./store");
const { RequestsMenuView } = require("./requests-menu-view");
const { CustomRequestView } = require("./custom-request-view");
const { ToolbarView } = require("./toolbar-view");
const { SidebarView } = require("./sidebar-view");
const { DetailsView } = require("./details-view");
const { PerformanceStatisticsView } = require("./performance-statistics-view");
-
-// Initialize the global redux variables
-var gStore = configureStore();
+const { ACTIVITY_TYPE } = require("./constants");
+const Actions = require("./actions/index");
+const { Prefs } = require("./prefs");
// ms
const WDA_DEFAULT_VERIFY_INTERVAL = 50;
@@ -50,7 +50,7 @@ var NetMonitorView = {
this.Toolbar.initialize(gStore);
this.RequestsMenu.initialize(gStore);
- this.NetworkDetails.initialize();
+ this.NetworkDetails.initialize(gStore);
this.CustomRequest.initialize();
this.PerformanceStatistics.initialize(gStore);
},
@@ -80,12 +80,6 @@ var NetMonitorView = {
this._detailsPane.setAttribute("width", Prefs.networkDetailsWidth);
this._detailsPane.setAttribute("height", Prefs.networkDetailsHeight);
this.toggleDetailsPane({ visible: false });
-
- // Disable the performance statistics mode.
- if (!Prefs.statistics) {
- $("#request-menu-context-perf").hidden = true;
- $("#notice-perf-message").hidden = true;
- }
},
/**
@@ -169,7 +163,6 @@ var NetMonitorView = {
*/
showNetworkInspectorView: function () {
this._body.selectedPanel = $("#network-inspector-view");
- this.RequestsMenu._flushWaterfallViews(true);
},
/**
@@ -192,7 +185,7 @@ var NetMonitorView = {
// • The response content size and request total time are necessary for
// populating the statistics view.
// • The response mime type is used for categorization.
- yield whenDataAvailable(requestsView, [
+ yield whenDataAvailable(requestsView.store, [
"responseHeaders", "status", "contentSize", "mimeType", "totalTime"
]);
} catch (ex) {
@@ -200,8 +193,9 @@ var NetMonitorView = {
console.error(ex);
}
- statisticsView.createPrimedCacheChart(requestsView.items);
- statisticsView.createEmptyCacheChart(requestsView.items);
+ const requests = requestsView.store.getState().requests.requests;
+ statisticsView.createPrimedCacheChart(requests);
+ statisticsView.createEmptyCacheChart(requests);
});
},
@@ -241,44 +235,39 @@ var NetMonitorView = {
_editorPromises: new Map()
};
-/**
- * DOM query helper.
- * TODO: Move it into "dom-utils.js" module and "require" it when needed.
- */
-var $ = (selector, target = document) => target.querySelector(selector);
-var $all = (selector, target = document) => target.querySelectorAll(selector);
-
/**
* Makes sure certain properties are available on all objects in a data store.
*
- * @param array dataStore
- * The request view object from which to fetch the item list.
+ * @param Store dataStore
+ * A Redux store for which to check the availability of properties.
* @param array mandatoryFields
* A list of strings representing properties of objects in dataStore.
* @return object
* A promise resolved when all objects in dataStore contain the
* properties defined in mandatoryFields.
*/
-function whenDataAvailable(requestsView, mandatoryFields) {
- let deferred = promise.defer();
+function whenDataAvailable(dataStore, mandatoryFields) {
+ return new Promise((resolve, reject) => {
+ let interval = setInterval(() => {
+ const { requests } = dataStore.getState().requests;
+ const allFieldsPresent = !requests.isEmpty() && requests.every(
+ item => mandatoryFields.every(
+ field => item.get(field) !== undefined
+ )
+ );
- let interval = setInterval(() => {
- const { attachments } = requestsView;
- if (attachments.length > 0 && attachments.every(item => {
- return mandatoryFields.every(field => field in item);
- })) {
+ if (allFieldsPresent) {
+ clearInterval(interval);
+ clearTimeout(timer);
+ resolve();
+ }
+ }, WDA_DEFAULT_VERIFY_INTERVAL);
+
+ let timer = setTimeout(() => {
clearInterval(interval);
- clearTimeout(timer);
- deferred.resolve();
- }
- }, WDA_DEFAULT_VERIFY_INTERVAL);
-
- let timer = setTimeout(() => {
- clearInterval(interval);
- deferred.reject(new Error("Timed out while waiting for data"));
- }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
-
- return deferred.promise;
+ reject(new Error("Timed out while waiting for data"));
+ }, WDA_DEFAULT_GIVE_UP_TIMEOUT);
+ });
}
/**
@@ -290,3 +279,5 @@ NetMonitorView.NetworkDetails = new DetailsView();
NetMonitorView.RequestsMenu = new RequestsMenuView();
NetMonitorView.CustomRequest = new CustomRequestView();
NetMonitorView.PerformanceStatistics = new PerformanceStatisticsView();
+
+exports.NetMonitorView = NetMonitorView;
diff --git a/devtools/client/netmonitor/netmonitor.js b/devtools/client/netmonitor/netmonitor.js
new file mode 100644
index 000000000000..5c23b5c52657
--- /dev/null
+++ b/devtools/client/netmonitor/netmonitor.js
@@ -0,0 +1,60 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals window, document, NetMonitorController, NetMonitorView */
+/* exported Netmonitor, NetMonitorController, NetMonitorView, $, $all, dumpn */
+
+"use strict";
+
+const Cu = Components.utils;
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+const { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+
+function Netmonitor(toolbox) {
+ const { require } = BrowserLoader({
+ baseURI: "resource://devtools/client/netmonitor/",
+ window,
+ commonLibRequire: toolbox.browserRequire,
+ });
+
+ window.windowRequire = require;
+
+ const { NetMonitorController } = require("./netmonitor-controller.js");
+ const { NetMonitorView } = require("./netmonitor-view.js");
+
+ window.NetMonitorController = NetMonitorController;
+ window.NetMonitorView = NetMonitorView;
+
+ NetMonitorController._toolbox = toolbox;
+ NetMonitorController._target = toolbox.target;
+}
+
+Netmonitor.prototype = {
+ init() {
+ return window.NetMonitorController.startupNetMonitor();
+ },
+
+ destroy() {
+ return window.NetMonitorController.shutdownNetMonitor();
+ }
+};
+
+/**
+ * DOM query helper.
+ * TODO: Move it into "dom-utils.js" module and "require" it when needed.
+ */
+var $ = (selector, target = document) => target.querySelector(selector);
+var $all = (selector, target = document) => target.querySelectorAll(selector);
+
+/**
+ * Helper method for debugging.
+ * @param string
+ */
+function dumpn(str) {
+ if (wantLogging) {
+ dump("NET-FRONTEND: " + str + "\n");
+ }
+}
+
+var wantLogging = Services.prefs.getBoolPref("devtools.debugger.log");
diff --git a/devtools/client/netmonitor/netmonitor.xul b/devtools/client/netmonitor/netmonitor.xul
index 69e2e1d3100d..9b4e1f86bdae 100644
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -12,8 +12,7 @@
-
-
+
-
-
-
-
-
-
-
+
+
@@ -441,56 +262,8 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
diff --git a/devtools/client/netmonitor/panel.js b/devtools/client/netmonitor/panel.js
index aa71debdec6a..3f2b9fc400ef 100644
--- a/devtools/client/netmonitor/panel.js
+++ b/devtools/client/netmonitor/panel.js
@@ -14,10 +14,7 @@ function NetMonitorPanel(iframeWindow, toolbox) {
this.panelDoc = iframeWindow.document;
this._toolbox = toolbox;
- this._view = this.panelWin.NetMonitorView;
- this._controller = this.panelWin.NetMonitorController;
- this._controller._target = this.target;
- this._controller._toolbox = this._toolbox;
+ this._netmonitor = new iframeWindow.Netmonitor(toolbox);
EventEmitter.decorate(this);
}
@@ -46,7 +43,8 @@ NetMonitorPanel.prototype = {
yield this.target.makeRemote();
}
- yield this._controller.startupNetMonitor();
+ yield this._netmonitor.init();
+
this.isReady = true;
this.emit("ready");
@@ -67,7 +65,7 @@ NetMonitorPanel.prototype = {
let deferred = promise.defer();
this._destroying = deferred.promise;
- yield this._controller.shutdownNetMonitor();
+ yield this._netmonitor.destroy();
this.emit("destroyed");
deferred.resolve();
diff --git a/devtools/client/netmonitor/performance-statistics-view.js b/devtools/client/netmonitor/performance-statistics-view.js
index a1f52b461b83..be27548d1fca 100644
--- a/devtools/client/netmonitor/performance-statistics-view.js
+++ b/devtools/client/netmonitor/performance-statistics-view.js
@@ -2,16 +2,21 @@
* 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/. */
-/* import-globals-from ./netmonitor-controller.js */
-/* globals $ */
+/* eslint-disable mozilla/reject-some-requires */
+/* globals $, window, document, NetMonitorView */
"use strict";
+const {XPCOMUtils} = require("resource://gre/modules/XPCOMUtils.jsm");
const {PluralForm} = require("devtools/shared/plural-form");
const {Filters} = require("./filter-predicates");
const {L10N} = require("./l10n");
+const {EVENTS} = require("./events");
const Actions = require("./actions/index");
+XPCOMUtils.defineLazyModuleGetter(this, "Chart",
+ "resource://devtools/client/shared/widgets/Chart.jsm");
+
const REQUEST_TIME_DECIMALS = 2;
const CONTENT_SIZE_DECIMALS = 2;
@@ -172,7 +177,7 @@ PerformanceStatisticsView.prototype = {
}));
for (let requestItem of items) {
- let details = requestItem.attachment;
+ let details = requestItem;
let type;
if (Filters.html(details)) {
@@ -237,11 +242,8 @@ function responseIsFresh({ responseHeaders, status }) {
}
let list = responseHeaders.headers;
- let cacheControl = list.filter(e => {
- return e.name.toLowerCase() == "cache-control";
- })[0];
-
- let expires = list.filter(e => e.name.toLowerCase() == "expires")[0];
+ let cacheControl = list.find(e => e.name.toLowerCase() == "cache-control");
+ let expires = list.find(e => e.name.toLowerCase() == "expires");
// Check the "Cache-Control" header for a maximum age value.
if (cacheControl) {
diff --git a/devtools/client/netmonitor/prefs.js b/devtools/client/netmonitor/prefs.js
index 9fcb4f9e8b27..d95b85993bb1 100644
--- a/devtools/client/netmonitor/prefs.js
+++ b/devtools/client/netmonitor/prefs.js
@@ -13,6 +13,5 @@ const {PrefsHelper} = require("devtools/client/shared/prefs");
exports.Prefs = new PrefsHelper("devtools.netmonitor", {
networkDetailsWidth: ["Int", "panes-network-details-width"],
networkDetailsHeight: ["Int", "panes-network-details-height"],
- statistics: ["Bool", "statistics"],
filters: ["Json", "filters"]
});
diff --git a/devtools/client/netmonitor/reducers/batching.js b/devtools/client/netmonitor/reducers/batching.js
new file mode 100644
index 000000000000..613aff1e2ffa
--- /dev/null
+++ b/devtools/client/netmonitor/reducers/batching.js
@@ -0,0 +1,25 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { BATCH_ACTIONS } = require("../constants");
+
+/**
+ * A reducer to handle batched actions. For each action in the BATCH_ACTIONS array,
+ * the reducer is called successively on the array of batched actions, resulting in
+ * only one state update.
+ */
+function batchingReducer(nextReducer) {
+ return function reducer(state, action) {
+ switch (action.type) {
+ case BATCH_ACTIONS:
+ return action.actions.reduce(reducer, state);
+ default:
+ return nextReducer(state, action);
+ }
+ };
+}
+
+module.exports = batchingReducer;
diff --git a/devtools/client/netmonitor/reducers/filters.js b/devtools/client/netmonitor/reducers/filters.js
index 7b264fd0c769..d04afb31ddae 100644
--- a/devtools/client/netmonitor/reducers/filters.js
+++ b/devtools/client/netmonitor/reducers/filters.js
@@ -27,7 +27,7 @@ const FilterTypes = I.Record({
const Filters = I.Record({
types: new FilterTypes({ all: true }),
- url: "",
+ text: "",
});
function toggleFilterType(state, action) {
@@ -72,7 +72,7 @@ function filters(state = new Filters(), action) {
case ENABLE_FILTER_TYPE_ONLY:
return state.set("types", enableFilterTypeOnly(state.types, action));
case SET_FILTER_TEXT:
- return state.set("url", action.url);
+ return state.set("text", action.text);
default:
return state;
}
diff --git a/devtools/client/netmonitor/reducers/index.js b/devtools/client/netmonitor/reducers/index.js
index 4cab57ac1c61..479490c3c9e3 100644
--- a/devtools/client/netmonitor/reducers/index.js
+++ b/devtools/client/netmonitor/reducers/index.js
@@ -5,12 +5,19 @@
"use strict";
const { combineReducers } = require("devtools/client/shared/vendor/redux");
-const filters = require("./filters");
+const batchingReducer = require("./batching");
const requests = require("./requests");
+const sort = require("./sort");
+const filters = require("./filters");
+const timingMarkers = require("./timing-markers");
const ui = require("./ui");
-module.exports = combineReducers({
- filters,
- requests,
- ui,
-});
+module.exports = batchingReducer(
+ combineReducers({
+ requests,
+ sort,
+ filters,
+ timingMarkers,
+ ui,
+ })
+);
diff --git a/devtools/client/netmonitor/reducers/moz.build b/devtools/client/netmonitor/reducers/moz.build
index 9082b8bd1b88..535ee7453b4b 100644
--- a/devtools/client/netmonitor/reducers/moz.build
+++ b/devtools/client/netmonitor/reducers/moz.build
@@ -3,8 +3,11 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
+ 'batching.js',
'filters.js',
'index.js',
'requests.js',
+ 'sort.js',
+ 'timing-markers.js',
'ui.js',
)
diff --git a/devtools/client/netmonitor/reducers/requests.js b/devtools/client/netmonitor/reducers/requests.js
index 87d7ae692e0b..4d982e016e44 100644
--- a/devtools/client/netmonitor/reducers/requests.js
+++ b/devtools/client/netmonitor/reducers/requests.js
@@ -5,25 +5,242 @@
"use strict";
const I = require("devtools/client/shared/vendor/immutable");
+const { getUrlDetails } = require("../request-utils");
const {
- UPDATE_REQUESTS,
+ ADD_REQUEST,
+ UPDATE_REQUEST,
+ CLEAR_REQUESTS,
+ SELECT_REQUEST,
+ PRESELECT_REQUEST,
+ CLONE_SELECTED_REQUEST,
+ REMOVE_SELECTED_CUSTOM_REQUEST,
+ OPEN_SIDEBAR
} = require("../constants");
-const Requests = I.Record({
- items: [],
+const Request = I.Record({
+ id: null,
+ // Set to true in case of a request that's being edited as part of "edit and resend"
+ isCustom: false,
+ // Request properties - at the beginning, they are unknown and are gradually filled in
+ startedMillis: undefined,
+ method: undefined,
+ url: undefined,
+ urlDetails: undefined,
+ remotePort: undefined,
+ remoteAddress: undefined,
+ isXHR: undefined,
+ cause: undefined,
+ fromCache: undefined,
+ fromServiceWorker: undefined,
+ status: undefined,
+ statusText: undefined,
+ httpVersion: undefined,
+ securityState: undefined,
+ securityInfo: undefined,
+ mimeType: undefined,
+ contentSize: undefined,
+ transferredSize: undefined,
+ totalTime: undefined,
+ eventTimings: undefined,
+ headersSize: undefined,
+ requestHeaders: undefined,
+ requestHeadersFromUploadStream: undefined,
+ requestCookies: undefined,
+ requestPostData: undefined,
+ responseHeaders: undefined,
+ responseCookies: undefined,
+ responseContent: undefined,
+ responseContentDataUri: undefined,
});
-function updateRequests(state, action) {
- return state.set("items", action.items || state.items);
-}
+const Requests = I.Record({
+ // The request list
+ requests: I.List(),
+ // Selection state
+ selectedId: null,
+ preselectedId: null,
+ // Auxiliary fields to hold requests stats
+ firstStartedMillis: +Infinity,
+ lastEndedMillis: -Infinity,
+});
-function requests(state = new Requests(), action) {
+const UPDATE_PROPS = [
+ "method",
+ "url",
+ "remotePort",
+ "remoteAddress",
+ "status",
+ "statusText",
+ "httpVersion",
+ "securityState",
+ "securityInfo",
+ "mimeType",
+ "contentSize",
+ "transferredSize",
+ "totalTime",
+ "eventTimings",
+ "headersSize",
+ "requestHeaders",
+ "requestHeadersFromUploadStream",
+ "requestCookies",
+ "requestPostData",
+ "responseHeaders",
+ "responseCookies",
+ "responseContent",
+ "responseContentDataUri"
+];
+
+function requestsReducer(state = new Requests(), action) {
switch (action.type) {
- case UPDATE_REQUESTS:
- return updateRequests(state, action);
+ case ADD_REQUEST: {
+ return state.withMutations(st => {
+ let newRequest = new Request(Object.assign(
+ { id: action.id },
+ action.data,
+ { urlDetails: getUrlDetails(action.data.url) }
+ ));
+ st.requests = st.requests.push(newRequest);
+
+ // Update the started/ended timestamps
+ let { startedMillis } = action.data;
+ if (startedMillis < st.firstStartedMillis) {
+ st.firstStartedMillis = startedMillis;
+ }
+ if (startedMillis > st.lastEndedMillis) {
+ st.lastEndedMillis = startedMillis;
+ }
+
+ // Select the request if it was preselected and there is no other selection
+ if (st.preselectedId && st.preselectedId === action.id) {
+ st.selectedId = st.selectedId || st.preselectedId;
+ st.preselectedId = null;
+ }
+ });
+ }
+
+ case UPDATE_REQUEST: {
+ let { requests, lastEndedMillis } = state;
+
+ let updateIdx = requests.findIndex(r => r.id === action.id);
+ if (updateIdx === -1) {
+ return state;
+ }
+
+ requests = requests.update(updateIdx, r => r.withMutations(request => {
+ for (let [key, value] of Object.entries(action.data)) {
+ if (!UPDATE_PROPS.includes(key)) {
+ continue;
+ }
+
+ request[key] = value;
+
+ switch (key) {
+ case "url":
+ // Compute the additional URL details
+ request.urlDetails = getUrlDetails(value);
+ break;
+ case "responseContent":
+ // If there's no mime type available when the response content
+ // is received, assume text/plain as a fallback.
+ if (!request.mimeType) {
+ request.mimeType = "text/plain";
+ }
+ break;
+ case "totalTime":
+ const endedMillis = request.startedMillis + value;
+ lastEndedMillis = Math.max(lastEndedMillis, endedMillis);
+ break;
+ case "requestPostData":
+ request.requestHeadersFromUploadStream = {
+ headers: [],
+ headersSize: 0,
+ };
+ break;
+ }
+ }
+ }));
+
+ return state.withMutations(st => {
+ st.requests = requests;
+ st.lastEndedMillis = lastEndedMillis;
+ });
+ }
+ case CLEAR_REQUESTS: {
+ return new Requests();
+ }
+ case SELECT_REQUEST: {
+ return state.set("selectedId", action.id);
+ }
+ case PRESELECT_REQUEST: {
+ return state.set("preselectedId", action.id);
+ }
+ case CLONE_SELECTED_REQUEST: {
+ let { requests, selectedId } = state;
+
+ if (!selectedId) {
+ return state;
+ }
+
+ let clonedIdx = requests.findIndex(r => r.id === selectedId);
+ if (clonedIdx === -1) {
+ return state;
+ }
+
+ let clonedRequest = requests.get(clonedIdx);
+ let newRequest = new Request({
+ id: clonedRequest.id + "-clone",
+ method: clonedRequest.method,
+ url: clonedRequest.url,
+ urlDetails: clonedRequest.urlDetails,
+ requestHeaders: clonedRequest.requestHeaders,
+ requestPostData: clonedRequest.requestPostData,
+ isCustom: true
+ });
+
+ // Insert the clone right after the original. This ensures that the requests
+ // are always sorted next to each other, even when multiple requests are
+ // equal according to the sorting criteria.
+ requests = requests.insert(clonedIdx + 1, newRequest);
+
+ return state.withMutations(st => {
+ st.requests = requests;
+ st.selectedId = newRequest.id;
+ });
+ }
+ case REMOVE_SELECTED_CUSTOM_REQUEST: {
+ let { requests, selectedId } = state;
+
+ if (!selectedId) {
+ return state;
+ }
+
+ let removedRequest = requests.find(r => r.id === selectedId);
+
+ // Only custom requests can be removed
+ if (!removedRequest || !removedRequest.isCustom) {
+ return state;
+ }
+
+ return state.withMutations(st => {
+ st.requests = requests.filter(r => r !== removedRequest);
+ st.selectedId = null;
+ });
+ }
+ case OPEN_SIDEBAR: {
+ if (!action.open) {
+ return state.set("selectedId", null);
+ }
+
+ if (!state.selectedId && !state.requests.isEmpty()) {
+ return state.set("selectedId", state.requests.get(0).id);
+ }
+
+ return state;
+ }
+
default:
return state;
}
}
-module.exports = requests;
+module.exports = requestsReducer;
diff --git a/devtools/client/netmonitor/reducers/sort.js b/devtools/client/netmonitor/reducers/sort.js
new file mode 100644
index 000000000000..33cbb64c5e2b
--- /dev/null
+++ b/devtools/client/netmonitor/reducers/sort.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 http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const I = require("devtools/client/shared/vendor/immutable");
+const { SORT_BY } = require("../constants");
+
+const Sort = I.Record({
+ // null means: sort by "waterfall", but don't highlight the table header
+ type: null,
+ ascending: true,
+});
+
+function sortReducer(state = new Sort(), action) {
+ switch (action.type) {
+ case SORT_BY: {
+ return state.withMutations(st => {
+ if (action.sortType == st.type) {
+ st.ascending = !st.ascending;
+ } else {
+ st.type = action.sortType;
+ st.ascending = true;
+ }
+ });
+ }
+ default:
+ return state;
+ }
+}
+
+module.exports = sortReducer;
diff --git a/devtools/client/netmonitor/reducers/timing-markers.js b/devtools/client/netmonitor/reducers/timing-markers.js
new file mode 100644
index 000000000000..4b0e92bcfd96
--- /dev/null
+++ b/devtools/client/netmonitor/reducers/timing-markers.js
@@ -0,0 +1,54 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const I = require("devtools/client/shared/vendor/immutable");
+const { ADD_TIMING_MARKER,
+ CLEAR_TIMING_MARKERS,
+ CLEAR_REQUESTS } = require("../constants");
+
+const TimingMarkers = I.Record({
+ firstDocumentDOMContentLoadedTimestamp: -1,
+ firstDocumentLoadTimestamp: -1,
+});
+
+function addTimingMarker(state, action) {
+ if (action.marker.name == "document::DOMContentLoaded" &&
+ state.firstDocumentDOMContentLoadedTimestamp == -1) {
+ return state.set("firstDocumentDOMContentLoadedTimestamp",
+ action.marker.unixTime / 1000);
+ }
+
+ if (action.marker.name == "document::Load" &&
+ state.firstDocumentLoadTimestamp == -1) {
+ return state.set("firstDocumentLoadTimestamp",
+ action.marker.unixTime / 1000);
+ }
+
+ return state;
+}
+
+function clearTimingMarkers(state) {
+ return state.withMutations(st => {
+ st.remove("firstDocumentDOMContentLoadedTimestamp");
+ st.remove("firstDocumentLoadTimestamp");
+ });
+}
+
+function timingMarkers(state = new TimingMarkers(), action) {
+ switch (action.type) {
+ case ADD_TIMING_MARKER:
+ return addTimingMarker(state, action);
+
+ case CLEAR_REQUESTS:
+ case CLEAR_TIMING_MARKERS:
+ return clearTimingMarkers(state);
+
+ default:
+ return state;
+ }
+}
+
+module.exports = timingMarkers;
diff --git a/devtools/client/netmonitor/reducers/ui.js b/devtools/client/netmonitor/reducers/ui.js
index 033b944f354e..fa6e0c0a7a8d 100644
--- a/devtools/client/netmonitor/reducers/ui.js
+++ b/devtools/client/netmonitor/reducers/ui.js
@@ -7,31 +7,31 @@
const I = require("devtools/client/shared/vendor/immutable");
const {
OPEN_SIDEBAR,
- TOGGLE_SIDEBAR,
+ WATERFALL_RESIZE,
} = require("../constants");
-const Sidebar = I.Record({
- open: false,
-});
-
const UI = I.Record({
- sidebar: new Sidebar(),
+ sidebarOpen: false,
+ waterfallWidth: 300,
});
function openSidebar(state, action) {
- return state.setIn(["sidebar", "open"], action.open);
+ return state.set("sidebarOpen", action.open);
}
-function toggleSidebar(state, action) {
- return state.setIn(["sidebar", "open"], !state.sidebar.open);
+// Safe bounds for waterfall width (px)
+const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
+
+function resizeWaterfall(state, action) {
+ return state.set("waterfallWidth", action.width - REQUESTS_WATERFALL_SAFE_BOUNDS);
}
function ui(state = new UI(), action) {
switch (action.type) {
case OPEN_SIDEBAR:
return openSidebar(state, action);
- case TOGGLE_SIDEBAR:
- return toggleSidebar(state, action);
+ case WATERFALL_RESIZE:
+ return resizeWaterfall(state, action);
default:
return state;
}
diff --git a/devtools/client/netmonitor/request-list-context-menu.js b/devtools/client/netmonitor/request-list-context-menu.js
index 1045ebb15d3c..30cdacc7ad34 100644
--- a/devtools/client/netmonitor/request-list-context-menu.js
+++ b/devtools/client/netmonitor/request-list-context-menu.js
@@ -58,7 +58,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-copy-url-params",
label: L10N.getStr("netmonitor.context.copyUrlParams"),
accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
- visible: !!(selectedItem && getUrlQuery(selectedItem.attachment.url)),
+ visible: !!(selectedItem && getUrlQuery(selectedItem.url)),
click: () => this.copyUrlParams(),
}));
@@ -66,7 +66,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-copy-post-data",
label: L10N.getStr("netmonitor.context.copyPostData"),
accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
- visible: !!(selectedItem && selectedItem.attachment.requestPostData),
+ visible: !!(selectedItem && selectedItem.requestPostData),
click: () => this.copyPostData(),
}));
@@ -74,7 +74,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-copy-as-curl",
label: L10N.getStr("netmonitor.context.copyAsCurl"),
accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
- visible: !!(selectedItem && selectedItem.attachment),
+ visible: !!selectedItem,
click: () => this.copyAsCurl(),
}));
@@ -87,7 +87,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-copy-request-headers",
label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
- visible: !!(selectedItem && selectedItem.attachment.requestHeaders),
+ visible: !!(selectedItem && selectedItem.requestHeaders),
click: () => this.copyRequestHeaders(),
}));
@@ -95,7 +95,7 @@ RequestListContextMenu.prototype = {
id: "response-menu-context-copy-response-headers",
label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
- visible: !!(selectedItem && selectedItem.attachment.responseHeaders),
+ visible: !!(selectedItem && selectedItem.responseHeaders),
click: () => this.copyResponseHeaders(),
}));
@@ -104,9 +104,9 @@ RequestListContextMenu.prototype = {
label: L10N.getStr("netmonitor.context.copyResponse"),
accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
visible: !!(selectedItem &&
- selectedItem.attachment.responseContent &&
- selectedItem.attachment.responseContent.content.text &&
- selectedItem.attachment.responseContent.content.text.length !== 0),
+ selectedItem.responseContent &&
+ selectedItem.responseContent.content.text &&
+ selectedItem.responseContent.content.text.length !== 0),
click: () => this.copyResponse(),
}));
@@ -115,9 +115,8 @@ RequestListContextMenu.prototype = {
label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
visible: !!(selectedItem &&
- selectedItem.attachment.responseContent &&
- selectedItem.attachment.responseContent.content
- .mimeType.includes("image/")),
+ selectedItem.responseContent &&
+ selectedItem.responseContent.content.mimeType.includes("image/")),
click: () => this.copyImageAsDataUri(),
}));
@@ -130,7 +129,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-copy-all-as-har",
label: L10N.getStr("netmonitor.context.copyAllAsHar"),
accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
- visible: !!this.items.length,
+ visible: this.items.size > 0,
click: () => this.copyAllAsHar(),
}));
@@ -138,7 +137,7 @@ RequestListContextMenu.prototype = {
id: "request-menu-context-save-all-as-har",
label: L10N.getStr("netmonitor.context.saveAllAsHar"),
accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
- visible: !!this.items.length,
+ visible: this.items.size > 0,
click: () => this.saveAllAsHar(),
}));
@@ -152,8 +151,7 @@ RequestListContextMenu.prototype = {
label: L10N.getStr("netmonitor.context.editAndResend"),
accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
visible: !!(NetMonitorController.supportsCustomRequest &&
- selectedItem &&
- !selectedItem.attachment.isCustom),
+ selectedItem && !selectedItem.isCustom),
click: () => NetMonitorView.RequestsMenu.cloneSelectedRequest(),
}));
@@ -187,15 +185,14 @@ RequestListContextMenu.prototype = {
*/
openRequestInTab() {
let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
- let { url } = this.selectedItem.attachment;
- win.openUILinkIn(url, "tab", { relatedToCurrent: true });
+ win.openUILinkIn(this.selectedItem.url, "tab", { relatedToCurrent: true });
},
/**
* Copy the request url from the currently selected item.
*/
copyUrl() {
- clipboardHelper.copyString(this.selectedItem.attachment.url);
+ clipboardHelper.copyString(this.selectedItem.url);
},
/**
@@ -203,7 +200,7 @@ RequestListContextMenu.prototype = {
* selected item.
*/
copyUrlParams() {
- let { url } = this.selectedItem.attachment;
+ let { url } = this.selectedItem;
let params = getUrlQuery(url).split("&");
let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
clipboardHelper.copyString(string);
@@ -214,7 +211,7 @@ RequestListContextMenu.prototype = {
* the currently selected item.
*/
copyPostData: Task.async(function* () {
- let selected = this.selectedItem.attachment;
+ let selected = this.selectedItem;
// Try to extract any form data parameters.
let formDataSections = yield getFormDataSections(
@@ -251,7 +248,7 @@ RequestListContextMenu.prototype = {
* Copy a cURL command from the currently selected item.
*/
copyAsCurl: Task.async(function* () {
- let selected = this.selectedItem.attachment;
+ let selected = this.selectedItem;
// Create a sanitized object for the Curl command generator.
let data = {
@@ -281,8 +278,7 @@ RequestListContextMenu.prototype = {
* Copy the raw request headers from the currently selected item.
*/
copyRequestHeaders() {
- let selected = this.selectedItem.attachment;
- let rawHeaders = selected.requestHeaders.rawHeaders.trim();
+ let rawHeaders = this.selectedItem.requestHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
@@ -293,8 +289,7 @@ RequestListContextMenu.prototype = {
* Copy the raw response headers from the currently selected item.
*/
copyResponseHeaders() {
- let selected = this.selectedItem.attachment;
- let rawHeaders = selected.responseHeaders.rawHeaders.trim();
+ let rawHeaders = this.selectedItem.responseHeaders.rawHeaders.trim();
if (Services.appinfo.OS !== "WINNT") {
rawHeaders = rawHeaders.replace(/\r/g, "");
}
@@ -305,8 +300,7 @@ RequestListContextMenu.prototype = {
* Copy image as data uri.
*/
copyImageAsDataUri() {
- let selected = this.selectedItem.attachment;
- let { mimeType, text, encoding } = selected.responseContent.content;
+ const { mimeType, text, encoding } = this.selectedItem.responseContent.content;
gNetwork.getString(text).then(string => {
let data = formDataURI(mimeType, encoding, string);
@@ -318,8 +312,7 @@ RequestListContextMenu.prototype = {
* Copy response data as a string.
*/
copyResponse() {
- let selected = this.selectedItem.attachment;
- let text = selected.responseContent.content.text;
+ const { text } = this.selectedItem.responseContent.content;
gNetwork.getString(text).then(string => {
clipboardHelper.copyString(string);
@@ -348,8 +341,7 @@ RequestListContextMenu.prototype = {
return {
getString: gNetwork.getString.bind(gNetwork),
- view: NetMonitorView.RequestsMenu,
- items: NetMonitorView.RequestsMenu.items,
+ items: this.items,
title: title
};
}
diff --git a/devtools/client/netmonitor/request-utils.js b/devtools/client/netmonitor/request-utils.js
index 01ef43de45b0..4a22cdbc2b2c 100644
--- a/devtools/client/netmonitor/request-utils.js
+++ b/devtools/client/netmonitor/request-utils.js
@@ -50,8 +50,8 @@ const getFormDataSections = Task.async(function* (headers, uploadHeaders, postDa
getString) {
let formDataSections = [];
- let { headers: requestHeaders } = headers;
- let { headers: payloadHeaders } = uploadHeaders;
+ let requestHeaders = headers.headers;
+ let payloadHeaders = uploadHeaders ? uploadHeaders.headers : [];
let allHeaders = [...payloadHeaders, ...requestHeaders];
let contentTypeHeader = allHeaders.find(e => {
@@ -188,6 +188,37 @@ function getUrlHost(url) {
return decodeUnicodeUrl((new URL(url)).host);
}
+/**
+ * Extract several details fields from a URL at once.
+ */
+function getUrlDetails(url) {
+ let baseNameWithQuery = getUrlBaseNameWithQuery(url);
+ let host = getUrlHost(url);
+ let hostname = getUrlHostName(url);
+ let unicodeUrl = decodeUnicodeUrl(url);
+
+ // Mark local hosts specially, where "local" is as defined in the W3C
+ // spec for secure contexts.
+ // http://www.w3.org/TR/powerful-features/
+ //
+ // * If the name falls under 'localhost'
+ // * If the name is an IPv4 address within 127.0.0.0/8
+ // * If the name is an IPv6 address within ::1/128
+ //
+ // IPv6 parsing is a little sloppy; it assumes that the address has
+ // been validated before it gets here.
+ let isLocal = hostname.match(/(.+\.)?localhost$/) ||
+ hostname.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}/) ||
+ hostname.match(/\[[0:]+1\]/);
+
+ return {
+ baseNameWithQuery,
+ host,
+ unicodeUrl,
+ isLocal
+ };
+}
+
/**
* Parse a url's query string into its components
*
@@ -253,6 +284,7 @@ module.exports = {
getUrlBaseNameWithQuery,
getUrlHostName,
getUrlHost,
+ getUrlDetails,
parseQueryString,
loadCauseString,
};
diff --git a/devtools/client/netmonitor/requests-menu-view.js b/devtools/client/netmonitor/requests-menu-view.js
index 2aaf440131fe..aa37d4704f61 100644
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -2,84 +2,40 @@
* 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/. */
-/* eslint-disable mozilla/reject-some-requires */
-/* globals document, window, dumpn, $, gNetwork, EVENTS, Prefs,
- NetMonitorController, NetMonitorView */
+/* globals window, dumpn, $, gNetwork, NetMonitorController, NetMonitorView */
"use strict";
-const { Cu } = require("chrome");
-const {Task} = require("devtools/shared/task");
-const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
-const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
-const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
-const {setImageTooltip, getImageDimensions} =
- require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
-const {Heritage, WidgetMethods, setNamedTimeout} =
- require("devtools/client/shared/widgets/view-helpers");
-const {CurlUtils} = require("devtools/client/shared/curl");
-const {Filters, isFreetextMatch} = require("./filter-predicates");
-const {Sorters} = require("./sort-predicates");
-const {L10N, WEBCONSOLE_L10N} = require("./l10n");
-const {formDataURI,
- writeHeaderText,
- decodeUnicodeUrl,
- getKeyWithEvent,
- getAbbreviatedMimeType,
- getUrlBaseNameWithQuery,
- getUrlHost,
- getUrlHostName,
- loadCauseString} = require("./request-utils");
-const Actions = require("./actions/index");
+const { Task } = require("devtools/shared/task");
+const { HTMLTooltip } = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
+const { setNamedTimeout } = require("devtools/client/shared/widgets/view-helpers");
+const { CurlUtils } = require("devtools/client/shared/curl");
+const { L10N } = require("./l10n");
+const { EVENTS } = require("./events");
+const { createElement, createFactory } = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { Provider } = require("devtools/client/shared/vendor/react-redux");
+const RequestList = createFactory(require("./components/request-list"));
const RequestListContextMenu = require("./request-list-context-menu");
+const Actions = require("./actions/index");
+const { Prefs } = require("./prefs");
+
+const {
+ formDataURI,
+ writeHeaderText,
+ loadCauseString
+} = require("./request-utils");
+
+const {
+ getActiveFilters,
+ getSortedRequests,
+ getDisplayedRequests,
+ getRequestById,
+ getSelectedRequest
+} = require("./selectors/index");
-const HTML_NS = "http://www.w3.org/1999/xhtml";
-const EPSILON = 0.001;
// ms
const RESIZE_REFRESH_RATE = 50;
-// ms
-const REQUESTS_REFRESH_RATE = 50;
-// tooltip show/hide delay in ms
-const REQUESTS_TOOLTIP_TOGGLE_DELAY = 500;
-// px
-const REQUESTS_TOOLTIP_IMAGE_MAX_DIM = 400;
-// px
-const REQUESTS_TOOLTIP_STACK_TRACE_WIDTH = 600;
-// px
-const REQUESTS_WATERFALL_SAFE_BOUNDS = 90;
-// ms
-const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5;
-// px
-const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60;
-// ms
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
-// px
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
-// byte
-const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
-const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [255, 0, 0, 128];
-const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [0, 0, 255, 128];
-
-// Constants for formatting bytes.
-const BYTES_IN_KB = 1024;
-const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
-const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
-const MAX_BYTES_SIZE = 1000;
-const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
-const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
-
-// TODO: duplicated from netmonitor-view.js. Move to a format-utils.js module.
-const REQUEST_TIME_DECIMALS = 2;
-const CONTENT_SIZE_DECIMALS = 2;
-
-const CONTENT_MIME_TYPE_ABBREVIATIONS = {
- "ecmascript": "js",
- "javascript": "js",
- "x-javascript": "js"
-};
// A smart store watcher to notify store changes as necessary
function storeWatcher(initialValue, reduceValue, onChange) {
@@ -98,21 +54,13 @@ function storeWatcher(initialValue, reduceValue, onChange) {
/**
* Functions handling the requests menu (containing details about each request,
* like status, method, file, domain, as well as a waterfall representing
- * timing imformation).
+ * timing information).
*/
function RequestsMenuView() {
dumpn("RequestsMenuView was instantiated");
-
- this._flushRequests = this._flushRequests.bind(this);
- this._onHover = this._onHover.bind(this);
- this._onSelect = this._onSelect.bind(this);
- this._onSwap = this._onSwap.bind(this);
- this._onResize = this._onResize.bind(this);
- this._onScroll = this._onScroll.bind(this);
- this._onSecurityIconClick = this._onSecurityIconClick.bind(this);
}
-RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
+RequestsMenuView.prototype = {
/**
* Initialization function, called when the network monitor is started.
*/
@@ -123,239 +71,295 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
this.contextMenu = new RequestListContextMenu();
- let widgetParentEl = $("#requests-menu-contents");
- this.widget = new SideMenuWidget(widgetParentEl);
- this._splitter = $("#network-inspector-view-splitter");
+ Prefs.filters.forEach(type => store.dispatch(Actions.toggleFilterType(type)));
- // Create a tooltip for the newly appended network request item.
- this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
- this.tooltip.startTogglingOnHover(widgetParentEl, this._onHover, {
- toggleDelay: REQUESTS_TOOLTIP_TOGGLE_DELAY,
- interactive: true
- });
+ // Watch selection changes
+ this.store.subscribe(storeWatcher(
+ null,
+ () => getSelectedRequest(this.store.getState()),
+ (newSelected, oldSelected) => this.onSelectionUpdate(newSelected, oldSelected)
+ ));
- this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
+ // Watch the sidebar status and resize the waterfall column on change
+ this.store.subscribe(storeWatcher(
+ false,
+ () => this.store.getState().ui.sidebarOpen,
+ () => this.onResize()
+ ));
- this.allowFocusOnRightClick = true;
- this.maintainSelectionVisible = true;
-
- this.widget.addEventListener("select", this._onSelect, false);
- this.widget.addEventListener("swap", this._onSwap, false);
- this._splitter.addEventListener("mousemove", this._onResize, false);
- window.addEventListener("resize", this._onResize, false);
-
- this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
- this.requestsMenuSortKeyboardEvent = getKeyWithEvent(this.sortBy.bind(this), true);
- this._onContextMenu = this._onContextMenu.bind(this);
this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
- this._onReloadCommand = () => NetMonitorView.reloadPage();
- this._flushRequestsTask = new DeferredTask(this._flushRequests,
- REQUESTS_REFRESH_RATE);
this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
this.toggleRawHeadersEvent = this.toggleRawHeaders.bind(this);
- this.reFilterRequests = this.reFilterRequests.bind(this);
+ $("#toggle-raw-headers")
+ .addEventListener("click", this.toggleRawHeadersEvent, false);
- $("#toolbar-labels").addEventListener("click",
- this.requestsMenuSortEvent, false);
- $("#toolbar-labels").addEventListener("keydown",
- this.requestsMenuSortKeyboardEvent, false);
- $("#toggle-raw-headers").addEventListener("click",
- this.toggleRawHeadersEvent, false);
- $("#requests-menu-contents").addEventListener("scroll", this._onScroll, true);
- $("#requests-menu-contents").addEventListener("contextmenu", this._onContextMenu);
+ this._summary = $("#requests-menu-network-summary-button");
+ this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
- this.unsubscribeStore = store.subscribe(storeWatcher(
- null,
- () => store.getState().filters,
- (newFilters) => {
- this._activeFilters = newFilters.types
- .toSeq()
- .filter((checked, key) => checked)
- .keySeq()
- .toArray();
- this._currentFreetextFilter = newFilters.url;
- this.reFilterRequests();
- }
- ));
+ this.onResize = this.onResize.bind(this);
+ this._splitter = $("#network-inspector-view-splitter");
+ this._splitter.addEventListener("mouseup", this.onResize, false);
+ window.addEventListener("resize", this.onResize, false);
- Prefs.filters.forEach(type =>
- store.dispatch(Actions.toggleFilterType(type)));
+ this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
+
+ this.mountPoint = $("#network-table");
+ ReactDOM.render(createElement(Provider,
+ { store: this.store },
+ RequestList()
+ ), this.mountPoint);
window.once("connected", this._onConnect.bind(this));
},
- _onConnect: function () {
- $("#requests-menu-reload-notice-button").addEventListener("command",
- this._onReloadCommand, false);
-
+ _onConnect() {
if (NetMonitorController.supportsCustomRequest) {
- $("#custom-request-send-button").addEventListener("click",
- this.sendCustomRequestEvent, false);
- $("#custom-request-close-button").addEventListener("click",
- this.closeCustomRequestEvent, false);
- $("#headers-summary-resend").addEventListener("click",
- this.cloneSelectedRequestEvent, false);
+ $("#custom-request-send-button")
+ .addEventListener("click", this.sendCustomRequestEvent, false);
+ $("#custom-request-close-button")
+ .addEventListener("click", this.closeCustomRequestEvent, false);
+ $("#headers-summary-resend")
+ .addEventListener("click", this.cloneSelectedRequestEvent, false);
} else {
$("#headers-summary-resend").hidden = true;
}
- if (NetMonitorController.supportsPerfStats) {
- $("#requests-menu-perf-notice-button").addEventListener("command",
- this._onContextPerfCommand, false);
- $("#network-statistics-back-button").addEventListener("command",
- this._onContextPerfCommand, false);
- } else {
- $("#notice-perf-message").hidden = true;
- }
-
- if (!NetMonitorController.supportsTransferredResponseSize) {
- $("#requests-menu-transferred-header-box").hidden = true;
- $("#requests-menu-item-template .requests-menu-transferred")
- .hidden = true;
- }
+ $("#network-statistics-back-button")
+ .addEventListener("command", this._onContextPerfCommand, false);
},
/**
* Destruction function, called when the network monitor is closed.
*/
- destroy: function () {
+ destroy() {
dumpn("Destroying the RequestsMenuView");
- Prefs.filters = this._activeFilters;
+ Prefs.filters = getActiveFilters(this.store.getState());
+
+ // this.flushRequestsTask.disarm();
+
+ $("#network-statistics-back-button")
+ .removeEventListener("command", this._onContextPerfCommand, false);
+ $("#custom-request-send-button")
+ .removeEventListener("click", this.sendCustomRequestEvent, false);
+ $("#custom-request-close-button")
+ .removeEventListener("click", this.closeCustomRequestEvent, false);
+ $("#headers-summary-resend")
+ .removeEventListener("click", this.cloneSelectedRequestEvent, false);
+ $("#toggle-raw-headers")
+ .removeEventListener("click", this.toggleRawHeadersEvent, false);
+
+ this._splitter.removeEventListener("mouseup", this.onResize, false);
+ window.removeEventListener("resize", this.onResize, false);
- /* Destroy the tooltip */
- this.tooltip.stopTogglingOnHover();
this.tooltip.destroy();
- $("#requests-menu-contents").removeEventListener("scroll", this._onScroll, true);
- $("#requests-menu-contents").removeEventListener("contextmenu", this._onContextMenu);
- this.widget.removeEventListener("select", this._onSelect, false);
- this.widget.removeEventListener("swap", this._onSwap, false);
- this._splitter.removeEventListener("mousemove", this._onResize, false);
- window.removeEventListener("resize", this._onResize, false);
-
- $("#toolbar-labels").removeEventListener("click",
- this.requestsMenuSortEvent, false);
- $("#toolbar-labels").removeEventListener("keydown",
- this.requestsMenuSortKeyboardEvent, false);
-
- this._flushRequestsTask.disarm();
-
- $("#requests-menu-reload-notice-button").removeEventListener("command",
- this._onReloadCommand, false);
- $("#requests-menu-perf-notice-button").removeEventListener("command",
- this._onContextPerfCommand, false);
- $("#network-statistics-back-button").removeEventListener("command",
- this._onContextPerfCommand, false);
-
- $("#custom-request-send-button").removeEventListener("click",
- this.sendCustomRequestEvent, false);
- $("#custom-request-close-button").removeEventListener("click",
- this.closeCustomRequestEvent, false);
- $("#headers-summary-resend").removeEventListener("click",
- this.cloneSelectedRequestEvent, false);
- $("#toggle-raw-headers").removeEventListener("click",
- this.toggleRawHeadersEvent, false);
-
- this.unsubscribeStore();
+ ReactDOM.unmountComponentAtNode(this.mountPoint);
},
/**
* Resets this container (removes all the networking information).
*/
- reset: function () {
- this.empty();
- this._addQueue = [];
- this._updateQueue = [];
- this._firstRequestStartedMillis = -1;
- this._lastRequestEndedMillis = -1;
+ reset() {
+ this.store.dispatch(Actions.batchReset());
+ this.store.dispatch(Actions.clearRequests());
},
/**
- * Specifies if this view may be updated lazily.
+ * Removes all network requests and closes the sidebar if open.
*/
- _lazyUpdate: true,
-
- get lazyUpdate() {
- return this._lazyUpdate;
+ clear() {
+ this.store.dispatch(Actions.clearRequests());
},
+ addRequest(id, data) {
+ let { method, url, isXHR, cause, startedDateTime, fromCache,
+ fromServiceWorker } = data;
+
+ // Convert the received date/time string to a unix timestamp.
+ let startedMillis = Date.parse(startedDateTime);
+
+ // Convert the cause from a Ci.nsIContentPolicy constant to a string
+ if (cause) {
+ let type = loadCauseString(cause.type);
+ cause = Object.assign({}, cause, { type });
+ }
+
+ const action = Actions.addRequest(
+ id,
+ {
+ startedMillis,
+ method,
+ url,
+ isXHR,
+ cause,
+ fromCache,
+ fromServiceWorker
+ },
+ true
+ );
+
+ this.store.dispatch(action).then(() => window.emit(EVENTS.REQUEST_ADDED, action.id));
+ },
+
+ updateRequest: Task.async(function* (id, data) {
+ const action = Actions.updateRequest(id, data, true);
+ yield this.store.dispatch(action);
+
+ const { responseContent, requestPostData } = action.data;
+
+ // Fetch response data if the response is an image (to display thumbnail)
+ if (responseContent && responseContent.content) {
+ let request = getRequestById(this.store.getState(), action.id);
+ if (request) {
+ let { mimeType } = request;
+ if (mimeType.includes("image/")) {
+ let { text, encoding } = responseContent.content;
+ let responseBody = yield gNetwork.getString(text);
+ const dataUri = formDataURI(mimeType, encoding, responseBody);
+ yield this.store.dispatch(Actions.updateRequest(
+ action.id,
+ { responseContentDataUri: dataUri },
+ true
+ ));
+ window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
+ }
+ }
+ }
+
+ // Search the POST data upload stream for request headers and add
+ // them as a separate property, different from the classic headers.
+ if (requestPostData && requestPostData.postData) {
+ let { text } = requestPostData.postData;
+ let postData = yield gNetwork.getString(text);
+ const headers = CurlUtils.getHeadersFromMultipartText(postData);
+ const headersSize = headers.reduce((acc, { name, value }) => {
+ return acc + name.length + value.length + 2;
+ }, 0);
+ yield this.store.dispatch(Actions.updateRequest(action.id, {
+ requestHeadersFromUploadStream: { headers, headersSize }
+ }, true));
+ }
+ }),
+
+ /**
+ * Disable batched updates. Used by tests.
+ */
set lazyUpdate(value) {
- this._lazyUpdate = value;
- if (!value) {
- this._flushRequests();
+ this.store.dispatch(Actions.batchEnable(value));
+ },
+
+ get items() {
+ return getSortedRequests(this.store.getState());
+ },
+
+ get visibleItems() {
+ return getDisplayedRequests(this.store.getState());
+ },
+
+ get itemCount() {
+ return this.store.getState().requests.requests.size;
+ },
+
+ getItemAtIndex(index) {
+ return getSortedRequests(this.store.getState()).get(index);
+ },
+
+ get selectedIndex() {
+ const state = this.store.getState();
+ if (!state.requests.selectedId) {
+ return -1;
+ }
+ return getSortedRequests(state).findIndex(r => r.id === state.requests.selectedId);
+ },
+
+ set selectedIndex(index) {
+ const requests = getSortedRequests(this.store.getState());
+ let itemId = null;
+ if (index >= 0 && index < requests.size) {
+ itemId = requests.get(index).id;
+ }
+ this.store.dispatch(Actions.selectRequest(itemId));
+ },
+
+ get selectedItem() {
+ return getSelectedRequest(this.store.getState());
+ },
+
+ set selectedItem(item) {
+ this.store.dispatch(Actions.selectRequest(item ? item.id : null));
+ },
+
+ /**
+ * Updates the sidebar status when something about the selection changes
+ */
+ onSelectionUpdate(newSelected, oldSelected) {
+ if (newSelected && oldSelected && newSelected.id === oldSelected.id) {
+ // The same item is still selected, its data only got updated
+ NetMonitorView.NetworkDetails.populate(newSelected);
+ } else if (newSelected) {
+ // Another item just got selected
+ NetMonitorView.Sidebar.populate(newSelected);
+ NetMonitorView.Sidebar.toggle(true);
+ } else {
+ // Selection just got empty
+ NetMonitorView.Sidebar.toggle(false);
}
},
/**
- * Adds a network request to this container.
- *
- * @param string id
- * An identifier coming from the network monitor controller.
- * @param string startedDateTime
- * A string representation of when the request was started, which
- * can be parsed by Date (for example "2012-09-17T19:50:03.699Z").
- * @param string method
- * Specifies the request method (e.g. "GET", "POST", etc.)
- * @param string url
- * Specifies the request's url.
- * @param boolean isXHR
- * True if this request was initiated via XHR.
- * @param object cause
- * Specifies the request's cause. Has the following properties:
- * - type: nsContentPolicyType constant
- * - loadingDocumentUri: URI of the request origin
- * - stacktrace: JS stacktrace of the request
- * @param boolean fromCache
- * Indicates if the result came from the browser cache
- * @param boolean fromServiceWorker
- * Indicates if the request has been intercepted by a Service Worker
+ * The resize listener for this container's window.
*/
- addRequest: function (id, startedDateTime, method, url, isXHR, cause,
- fromCache, fromServiceWorker) {
- this._addQueue.push([id, startedDateTime, method, url, isXHR, cause,
- fromCache, fromServiceWorker]);
-
- // Lazy updating is disabled in some tests.
- if (!this.lazyUpdate) {
- return void this._flushRequests();
- }
-
- this._flushRequestsTask.arm();
- return undefined;
+ onResize() {
+ // Allow requests to settle down first.
+ setNamedTimeout("resize-events", RESIZE_REFRESH_RATE, () => {
+ const waterfallHeaderEl = $("#requests-menu-waterfall-header-box");
+ if (waterfallHeaderEl) {
+ const { width } = waterfallHeaderEl.getBoundingClientRect();
+ this.store.dispatch(Actions.resizeWaterfall(width));
+ }
+ });
},
/**
* Create a new custom request form populated with the data from
* the currently selected request.
*/
- cloneSelectedRequest: function () {
- let selected = this.selectedItem.attachment;
+ cloneSelectedRequest() {
+ this.store.dispatch(Actions.cloneSelectedRequest());
+ },
- // Create the element node for the network request item.
- let menuView = this._createMenuView(selected.method, selected.url,
- selected.cause);
+ /**
+ * Shows raw request/response headers in textboxes.
+ */
+ toggleRawHeaders: function () {
+ let requestTextarea = $("#raw-request-headers-textarea");
+ let responseTextarea = $("#raw-response-headers-textarea");
+ let rawHeadersHidden = $("#raw-headers").getAttribute("hidden");
- // Append a network request item to this container.
- let newItem = this.push([menuView], {
- attachment: Object.create(selected, {
- isCustom: { value: true }
- })
- });
-
- // Immediately switch to new request pane.
- this.selectedItem = newItem;
+ if (rawHeadersHidden) {
+ let selected = getSelectedRequest(this.store.getState());
+ let selectedRequestHeaders = selected.requestHeaders.headers;
+ let selectedResponseHeaders = selected.responseHeaders.headers;
+ requestTextarea.value = writeHeaderText(selectedRequestHeaders);
+ responseTextarea.value = writeHeaderText(selectedResponseHeaders);
+ $("#raw-headers").hidden = false;
+ } else {
+ requestTextarea.value = null;
+ responseTextarea.value = null;
+ $("#raw-headers").hidden = true;
+ }
},
/**
* Send a new HTTP request using the data in the custom request form.
*/
sendCustomRequest: function () {
- let selected = this.selectedItem.attachment;
+ let selected = getSelectedRequest(this.store.getState());
let data = {
url: selected.url,
@@ -371,7 +375,7 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
NetMonitorController.webConsoleClient.sendHTTPRequest(data, response => {
let id = response.eventActor.actor;
- this._preferredItemId = id;
+ this.store.dispatch(Actions.preselectRequest(id));
});
this.closeCustomRequest();
@@ -380,1175 +384,9 @@ RequestsMenuView.prototype = Heritage.extend(WidgetMethods, {
/**
* Remove the currently selected custom request.
*/
- closeCustomRequest: function () {
- this.remove(this.selectedItem);
- NetMonitorView.Sidebar.toggle(false);
+ closeCustomRequest() {
+ this.store.dispatch(Actions.removeSelectedCustomRequest());
},
-
- /**
- * Shows raw request/response headers in textboxes.
- */
- toggleRawHeaders: function () {
- let requestTextarea = $("#raw-request-headers-textarea");
- let responseTextare = $("#raw-response-headers-textarea");
- let rawHeadersHidden = $("#raw-headers").getAttribute("hidden");
-
- if (rawHeadersHidden) {
- let selected = this.selectedItem.attachment;
- let selectedRequestHeaders = selected.requestHeaders.headers;
- let selectedResponseHeaders = selected.responseHeaders.headers;
- requestTextarea.value = writeHeaderText(selectedRequestHeaders);
- responseTextare.value = writeHeaderText(selectedResponseHeaders);
- $("#raw-headers").hidden = false;
- } else {
- requestTextarea.value = null;
- responseTextare.value = null;
- $("#raw-headers").hidden = true;
- }
- },
-
- /**
- * Refreshes the view contents with the newly selected filters
- */
- reFilterRequests: function () {
- this.filterContents(this._filterPredicate);
- this.updateRequests();
- this.refreshZebra();
- },
-
- /**
- * Returns a predicate that can be used to test if a request matches any of
- * the active filters.
- */
- get _filterPredicate() {
- let currentFreetextFilter = this._currentFreetextFilter;
-
- return requestItem => {
- const { attachment } = requestItem;
- return this._activeFilters.some(filterName => Filters[filterName](attachment)) &&
- isFreetextMatch(attachment, currentFreetextFilter);
- };
- },
-
- /**
- * Sorts all network requests in this container by a specified detail.
- *
- * @param string type
- * Either "status", "method", "file", "domain", "type", "transferred",
- * "size" or "waterfall".
- */
- sortBy: function (type = "waterfall") {
- let target = $("#requests-menu-" + type + "-button");
- let headers = document.querySelectorAll(".requests-menu-header-button");
-
- for (let header of headers) {
- if (header != target) {
- header.removeAttribute("sorted");
- header.removeAttribute("tooltiptext");
- header.parentNode.removeAttribute("active");
- }
- }
-
- let direction = "";
- if (target) {
- if (target.getAttribute("sorted") == "ascending") {
- target.setAttribute("sorted", direction = "descending");
- target.setAttribute("tooltiptext",
- L10N.getStr("networkMenu.sortedDesc"));
- } else {
- target.setAttribute("sorted", direction = "ascending");
- target.setAttribute("tooltiptext",
- L10N.getStr("networkMenu.sortedAsc"));
- }
- // Used to style the next column.
- target.parentNode.setAttribute("active", "true");
- }
-
- // Sort by whatever was requested.
- switch (type) {
- case "status":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.status(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.status(a.attachment, b.attachment));
- }
- break;
- case "method":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.method(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.method(a.attachment, b.attachment));
- }
- break;
- case "file":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.file(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.file(a.attachment, b.attachment));
- }
- break;
- case "domain":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.domain(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.domain(a.attachment, b.attachment));
- }
- break;
- case "cause":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.cause(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.cause(a.attachment, b.attachment));
- }
- break;
- case "type":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.type(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.type(a.attachment, b.attachment));
- }
- break;
- case "transferred":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.transferred(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.transferred(a.attachment, b.attachment));
- }
- break;
- case "size":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.size(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.size(a.attachment, b.attachment));
- }
- break;
- case "waterfall":
- if (direction == "ascending") {
- this.sortContents((a, b) => Sorters.waterfall(a.attachment, b.attachment));
- } else {
- this.sortContents((a, b) => -Sorters.waterfall(a.attachment, b.attachment));
- }
- break;
- }
-
- this.updateRequests();
- this.refreshZebra();
- },
-
- /**
- * Removes all network requests and closes the sidebar if open.
- */
- clear: function () {
- NetMonitorController.NetworkEventsHandler.clearMarkers();
- NetMonitorView.Sidebar.toggle(false);
-
- $("#requests-menu-empty-notice").hidden = false;
-
- this.empty();
- this.updateRequests();
- },
-
- /**
- * Update store request itmes and trigger related UI update
- */
- updateRequests: function () {
- this.store.dispatch(Actions.updateRequests(this.visibleItems));
- },
-
- /**
- * Adds odd/even attributes to all the visible items in this container.
- */
- refreshZebra: function () {
- let visibleItems = this.visibleItems;
-
- for (let i = 0, len = visibleItems.length; i < len; i++) {
- let requestItem = visibleItems[i];
- let requestTarget = requestItem.target;
-
- if (i % 2 == 0) {
- requestTarget.setAttribute("even", "");
- requestTarget.removeAttribute("odd");
- } else {
- requestTarget.setAttribute("odd", "");
- requestTarget.removeAttribute("even");
- }
- }
- },
-
- /**
- * Attaches security icon click listener for the given request menu item.
- *
- * @param object item
- * The network request item to attach the listener to.
- */
- attachSecurityIconClickListener: function ({ target }) {
- let icon = $(".requests-security-state-icon", target);
- icon.addEventListener("click", this._onSecurityIconClick);
- },
-
- /**
- * Schedules adding additional information to a network request.
- *
- * @param string id
- * An identifier coming from the network monitor controller.
- * @param object data
- * An object containing several { key: value } tuples of network info.
- * Supported keys are "httpVersion", "status", "statusText" etc.
- * @param function callback
- * A function to call once the request has been updated in the view.
- */
- updateRequest: function (id, data, callback) {
- this._updateQueue.push([id, data, callback]);
-
- // Lazy updating is disabled in some tests.
- if (!this.lazyUpdate) {
- return void this._flushRequests();
- }
-
- this._flushRequestsTask.arm();
- return undefined;
- },
-
- /**
- * Starts adding all queued additional information about network requests.
- */
- _flushRequests: function () {
- // Prevent displaying any updates received after the target closed.
- if (NetMonitorView._isDestroyed) {
- return;
- }
-
- let widget = NetMonitorView.RequestsMenu.widget;
- let isScrolledToBottom = widget.isScrolledToBottom();
-
- for (let [id, startedDateTime, method, url, isXHR, cause, fromCache,
- fromServiceWorker] of this._addQueue) {
- // Convert the received date/time string to a unix timestamp.
- let unixTime = Date.parse(startedDateTime);
-
- // Create the element node for the network request item.
- let menuView = this._createMenuView(method, url, cause);
-
- // Remember the first and last event boundaries.
- this._registerFirstRequestStart(unixTime);
- this._registerLastRequestEnd(unixTime);
-
- // Append a network request item to this container.
- let requestItem = this.push([menuView, id], {
- attachment: {
- startedDeltaMillis: unixTime - this._firstRequestStartedMillis,
- startedMillis: unixTime,
- method: method,
- url: url,
- isXHR: isXHR,
- cause: cause,
- fromCache: fromCache,
- fromServiceWorker: fromServiceWorker
- }
- });
-
- if (id == this._preferredItemId) {
- this.selectedItem = requestItem;
- }
-
- window.emit(EVENTS.REQUEST_ADDED, id);
- }
-
- if (isScrolledToBottom && this._addQueue.length) {
- widget.scrollToBottom();
- }
-
- // For each queued additional information packet, get the corresponding
- // request item in the view and update it based on the specified data.
- for (let [id, data, callback] of this._updateQueue) {
- let requestItem = this.getItemByValue(id);
- if (!requestItem) {
- // Packet corresponds to a dead request item, target navigated.
- continue;
- }
-
- // Each information packet may contain several { key: value } tuples of
- // network info, so update the view based on each one.
- for (let key in data) {
- let val = data[key];
- if (val === undefined) {
- // The information in the packet is empty, it can be safely ignored.
- continue;
- }
-
- switch (key) {
- case "requestHeaders":
- requestItem.attachment.requestHeaders = val;
- break;
- case "requestCookies":
- requestItem.attachment.requestCookies = val;
- break;
- case "requestPostData":
- // Search the POST data upload stream for request headers and add
- // them to a separate store, different from the classic headers.
- // XXX: Be really careful here! We're creating a function inside
- // a loop, so remember the actual request item we want to modify.
- let currentItem = requestItem;
- let currentStore = { headers: [], headersSize: 0 };
-
- Task.spawn(function* () {
- let postData = yield gNetwork.getString(val.postData.text);
- let payloadHeaders = CurlUtils.getHeadersFromMultipartText(
- postData);
-
- currentStore.headers = payloadHeaders;
- currentStore.headersSize = payloadHeaders.reduce(
- (acc, { name, value }) =>
- acc + name.length + value.length + 2, 0);
-
- // The `getString` promise is async, so we need to refresh the
- // information displayed in the network details pane again here.
- refreshNetworkDetailsPaneIfNecessary(currentItem);
- });
-
- requestItem.attachment.requestPostData = val;
- requestItem.attachment.requestHeadersFromUploadStream =
- currentStore;
- break;
- case "securityState":
- requestItem.attachment.securityState = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "securityInfo":
- requestItem.attachment.securityInfo = val;
- break;
- case "responseHeaders":
- requestItem.attachment.responseHeaders = val;
- break;
- case "responseCookies":
- requestItem.attachment.responseCookies = val;
- break;
- case "httpVersion":
- requestItem.attachment.httpVersion = val;
- break;
- case "remoteAddress":
- requestItem.attachment.remoteAddress = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "remotePort":
- requestItem.attachment.remotePort = val;
- break;
- case "status":
- requestItem.attachment.status = val;
- this.updateMenuView(requestItem, key, {
- status: val,
- cached: requestItem.attachment.fromCache,
- serviceWorker: requestItem.attachment.fromServiceWorker
- });
- break;
- case "statusText":
- requestItem.attachment.statusText = val;
- let text = (requestItem.attachment.status + " " +
- requestItem.attachment.statusText);
- if (requestItem.attachment.fromCache) {
- text += " (cached)";
- } else if (requestItem.attachment.fromServiceWorker) {
- text += " (service worker)";
- }
-
- this.updateMenuView(requestItem, key, text);
- break;
- case "headersSize":
- requestItem.attachment.headersSize = val;
- break;
- case "contentSize":
- requestItem.attachment.contentSize = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "transferredSize":
- if (requestItem.attachment.fromCache) {
- requestItem.attachment.transferredSize = 0;
- this.updateMenuView(requestItem, key, "cached");
- } else if (requestItem.attachment.fromServiceWorker) {
- requestItem.attachment.transferredSize = 0;
- this.updateMenuView(requestItem, key, "service worker");
- } else {
- requestItem.attachment.transferredSize = val;
- this.updateMenuView(requestItem, key, val);
- }
- break;
- case "mimeType":
- requestItem.attachment.mimeType = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "responseContent":
- // If there's no mime type available when the response content
- // is received, assume text/plain as a fallback.
- if (!requestItem.attachment.mimeType) {
- requestItem.attachment.mimeType = "text/plain";
- this.updateMenuView(requestItem, "mimeType", "text/plain");
- }
- requestItem.attachment.responseContent = val;
- this.updateMenuView(requestItem, key, val);
- break;
- case "totalTime":
- requestItem.attachment.totalTime = val;
- requestItem.attachment.endedMillis =
- requestItem.attachment.startedMillis + val;
-
- this.updateMenuView(requestItem, key, val);
- this._registerLastRequestEnd(requestItem.attachment.endedMillis);
- break;
- case "eventTimings":
- requestItem.attachment.eventTimings = val;
- this._createWaterfallView(
- requestItem, val.timings,
- requestItem.attachment.fromCache ||
- requestItem.attachment.fromServiceWorker
- );
- break;
- }
- }
- refreshNetworkDetailsPaneIfNecessary(requestItem);
-
- if (callback) {
- callback();
- }
- }
-
- /**
- * Refreshes the information displayed in the sidebar, in case this update
- * may have additional information about a request which isn't shown yet
- * in the network details pane.
- *
- * @param object requestItem
- * The item to repopulate the sidebar with in case it's selected in
- * this requests menu.
- */
- function refreshNetworkDetailsPaneIfNecessary(requestItem) {
- let selectedItem = NetMonitorView.RequestsMenu.selectedItem;
- if (selectedItem == requestItem) {
- NetMonitorView.NetworkDetails.populate(selectedItem.attachment);
- }
- }
-
- // We're done flushing all the requests, clear the update queue.
- this._updateQueue = [];
- this._addQueue = [];
-
- $("#requests-menu-empty-notice").hidden = !!this.itemCount;
-
- // Make sure all the requests are sorted and filtered.
- // Freshly added requests may not yet contain all the information required
- // for sorting and filtering predicates, so this is done each time the
- // network requests table is flushed (don't worry, events are drained first
- // so this doesn't happen once per network event update).
- this.sortContents();
- this.filterContents();
- this.updateRequests();
- this.refreshZebra();
-
- // Rescale all the waterfalls so that everything is visible at once.
- this._flushWaterfallViews();
- },
-
- /**
- * Customization function for creating an item's UI.
- *
- * @param string method
- * Specifies the request method (e.g. "GET", "POST", etc.)
- * @param string url
- * Specifies the request's url.
- * @param object cause
- * Specifies the request's cause. Has two properties:
- * - type: nsContentPolicyType constant
- * - uri: URI of the request origin
- * @return nsIDOMNode
- * The network request view.
- */
- _createMenuView: function (method, url, cause) {
- let template = $("#requests-menu-item-template");
- let fragment = document.createDocumentFragment();
-
- // Flatten the DOM by removing one redundant box (the template container).
- for (let node of template.childNodes) {
- fragment.appendChild(node.cloneNode(true));
- }
-
- this.updateMenuView(fragment, "method", method);
- this.updateMenuView(fragment, "url", url);
- this.updateMenuView(fragment, "cause", cause);
-
- return fragment;
- },
-
- /**
- * Get a human-readable string from a number of bytes, with the B, KB, MB, or
- * GB value. Note that the transition between abbreviations is by 1000 rather
- * than 1024 in order to keep the displayed digits smaller as "1016 KB" is
- * more awkward than 0.99 MB"
- */
- getFormattedSize(bytes) {
- if (bytes < MAX_BYTES_SIZE) {
- return L10N.getFormatStr("networkMenu.sizeB", bytes);
- } else if (bytes < MAX_KB_SIZE) {
- let kb = bytes / BYTES_IN_KB;
- let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeKB", size);
- } else if (bytes < MAX_MB_SIZE) {
- let mb = bytes / BYTES_IN_MB;
- let size = L10N.numberWithDecimals(mb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeMB", size);
- }
- let gb = bytes / BYTES_IN_GB;
- let size = L10N.numberWithDecimals(gb, CONTENT_SIZE_DECIMALS);
- return L10N.getFormatStr("networkMenu.sizeGB", size);
- },
-
- /**
- * Updates the information displayed in a network request item view.
- *
- * @param object item
- * The network request item in this container.
- * @param string key
- * The type of information that is to be updated.
- * @param any value
- * The new value to be shown.
- * @return object
- * A promise that is resolved once the information is displayed.
- */
- updateMenuView: Task.async(function* (item, key, value) {
- let target = item.target || item;
-
- switch (key) {
- case "method": {
- let node = $(".requests-menu-method", target);
- node.setAttribute("value", value);
- break;
- }
- case "url": {
- let nameWithQuery = getUrlBaseNameWithQuery(value);
- let hostPort = getUrlHost(value);
- let host = getUrlHostName(value);
- let unicodeUrl = decodeUnicodeUrl(value);
-
- let file = $(".requests-menu-file", target);
- file.setAttribute("value", nameWithQuery);
- file.setAttribute("tooltiptext", unicodeUrl);
-
- let domain = $(".requests-menu-domain", target);
- domain.setAttribute("value", hostPort);
- domain.setAttribute("tooltiptext", hostPort);
-
- // Mark local hosts specially, where "local" is as defined in the W3C
- // spec for secure contexts.
- // http://www.w3.org/TR/powerful-features/
- //
- // * If the name falls under 'localhost'
- // * If the name is an IPv4 address within 127.0.0.0/8
- // * If the name is an IPv6 address within ::1/128
- //
- // IPv6 parsing is a little sloppy; it assumes that the address has
- // been validated before it gets here.
- let icon = $(".requests-security-state-icon", target);
- icon.classList.remove("security-state-local");
- if (host.match(/(.+\.)?localhost$/) ||
- host.match(/^127\.\d{1,3}\.\d{1,3}\.\d{1,3}/) ||
- host.match(/\[[0:]+1\]/)) {
- let tooltip = L10N.getStr("netmonitor.security.state.secure");
- icon.classList.add("security-state-local");
- icon.setAttribute("tooltiptext", tooltip);
- }
-
- break;
- }
- case "remoteAddress":
- let domain = $(".requests-menu-domain", target);
- let tooltip = (domain.getAttribute("value") +
- (value ? " (" + value + ")" : ""));
- domain.setAttribute("tooltiptext", tooltip);
- break;
- case "securityState": {
- let icon = $(".requests-security-state-icon", target);
- this.attachSecurityIconClickListener(item);
-
- // Security icon for local hosts is set in the "url" branch
- if (icon.classList.contains("security-state-local")) {
- break;
- }
-
- let tooltip2 = L10N.getStr("netmonitor.security.state." + value);
- icon.classList.add("security-state-" + value);
- icon.setAttribute("tooltiptext", tooltip2);
- break;
- }
- case "status": {
- let node = $(".requests-menu-status-icon", target);
- // "code" attribute is only used by css to determine the icon color
- let code;
- if (value.cached) {
- code = "cached";
- } else if (value.serviceWorker) {
- code = "service worker";
- } else {
- code = value.status;
- }
- node.setAttribute("code", code);
- let codeNode = $(".requests-menu-status-code", target);
- codeNode.setAttribute("value", value.status);
- break;
- }
- case "statusText": {
- let node = $(".requests-menu-status", target);
- node.setAttribute("tooltiptext", value);
- break;
- }
- case "cause": {
- let labelNode = $(".requests-menu-cause-label", target);
- labelNode.setAttribute("value", loadCauseString(value.type));
- if (value.loadingDocumentUri) {
- labelNode.setAttribute("tooltiptext", value.loadingDocumentUri);
- }
-
- let stackNode = $(".requests-menu-cause-stack", target);
- if (value.stacktrace && value.stacktrace.length > 0) {
- stackNode.removeAttribute("hidden");
- }
- break;
- }
- case "contentSize": {
- let node = $(".requests-menu-size", target);
-
- let text = this.getFormattedSize(value);
-
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- case "transferredSize": {
- let node = $(".requests-menu-transferred", target);
-
- let text;
- if (value === null) {
- text = L10N.getStr("networkMenu.sizeUnavailable");
- } else if (value === "cached") {
- text = L10N.getStr("networkMenu.sizeCached");
- node.classList.add("theme-comment");
- } else if (value === "service worker") {
- text = L10N.getStr("networkMenu.sizeServiceWorker");
- node.classList.add("theme-comment");
- } else {
- text = this.getFormattedSize(value);
- }
-
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- case "mimeType": {
- let type = getAbbreviatedMimeType(value);
- let node = $(".requests-menu-type", target);
- let text = CONTENT_MIME_TYPE_ABBREVIATIONS[type] || type;
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", value);
- break;
- }
- case "responseContent": {
- let { mimeType } = item.attachment;
-
- if (mimeType.includes("image/")) {
- let { text, encoding } = value.content;
- let responseBody = yield gNetwork.getString(text);
- let node = $(".requests-menu-icon", item.target);
- node.src = formDataURI(mimeType, encoding, responseBody);
- node.setAttribute("type", "thumbnail");
- node.removeAttribute("hidden");
-
- window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
- }
- break;
- }
- case "totalTime": {
- let node = $(".requests-menu-timings-total", target);
-
- // integer
- let text = L10N.getFormatStr("networkMenu.totalMS", value);
- node.setAttribute("value", text);
- node.setAttribute("tooltiptext", text);
- break;
- }
- }
- }),
-
- /**
- * Creates a waterfall representing timing information in a network
- * request item view.
- *
- * @param object item
- * The network request item in this container.
- * @param object timings
- * An object containing timing information.
- * @param boolean fromCache
- * Indicates if the result came from the browser cache or
- * a service worker
- */
- _createWaterfallView: function (item, timings, fromCache) {
- let { target } = item;
- let sections = ["blocked", "dns", "connect", "send", "wait", "receive"];
- // Skipping "blocked" because it doesn't work yet.
-
- let timingsNode = $(".requests-menu-timings", target);
- let timingsTotal = $(".requests-menu-timings-total", timingsNode);
-
- if (fromCache) {
- timingsTotal.style.display = "none";
- return;
- }
-
- // Add a set of boxes representing timing information.
- for (let key of sections) {
- let width = timings[key];
-
- // Don't render anything if it surely won't be visible.
- // One millisecond == one unscaled pixel.
- if (width > 0) {
- let timingBox = document.createElement("hbox");
- timingBox.className = "requests-menu-timings-box " + key;
- timingBox.setAttribute("width", width);
- timingsNode.insertBefore(timingBox, timingsTotal);
- }
- }
- },
-
- /**
- * Rescales and redraws all the waterfall views in this container.
- *
- * @param boolean reset
- * True if this container's width was changed.
- */
- _flushWaterfallViews: function (reset) {
- // Don't paint things while the waterfall view isn't even visible,
- // or there are no items added to this container.
- if (NetMonitorView.currentFrontendMode !=
- "network-inspector-view" || !this.itemCount) {
- return;
- }
-
- // To avoid expensive operations like getBoundingClientRect() and
- // rebuilding the waterfall background each time a new request comes in,
- // stuff is cached. However, in certain scenarios like when the window
- // is resized, this needs to be invalidated.
- if (reset) {
- this._cachedWaterfallWidth = 0;
- }
-
- // Determine the scaling to be applied to all the waterfalls so that
- // everything is visible at once. One millisecond == one unscaled pixel.
- let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
- let longestWidth = this._lastRequestEndedMillis -
- this._firstRequestStartedMillis;
- let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1);
-
- // Redraw and set the canvas background for each waterfall view.
- this._showWaterfallDivisionLabels(scale);
- this._drawWaterfallBackground(scale);
-
- // Apply CSS transforms to each waterfall in this container totalTime
- // accurately translate and resize as needed.
- for (let { target, attachment } of this) {
- let timingsNode = $(".requests-menu-timings", target);
- let totalNode = $(".requests-menu-timings-total", target);
- let direction = window.isRTL ? -1 : 1;
-
- // Render the timing information at a specific horizontal translation
- // based on the delta to the first monitored event network.
- let translateX = "translateX(" + (direction *
- attachment.startedDeltaMillis) + "px)";
-
- // Based on the total time passed until the last request, rescale
- // all the waterfalls to a reasonable size.
- let scaleX = "scaleX(" + scale + ")";
-
- // Certain nodes should not be scaled, even if they're children of
- // another scaled node. In this case, apply a reversed transformation.
- let revScaleX = "scaleX(" + (1 / scale) + ")";
-
- timingsNode.style.transform = scaleX + " " + translateX;
- totalNode.style.transform = revScaleX;
- }
- },
-
- /**
- * Creates the labels displayed on the waterfall header in this container.
- *
- * @param number scale
- * The current waterfall scale.
- */
- _showWaterfallDivisionLabels: function (scale) {
- let container = $("#requests-menu-waterfall-label-wrapper");
- let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
-
- // Nuke all existing labels.
- while (container.hasChildNodes()) {
- container.firstChild.remove();
- }
-
- // Build new millisecond tick labels...
- let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
- let optimalTickIntervalFound = false;
-
- while (!optimalTickIntervalFound) {
- // Ignore any divisions that would end up being too close to each other.
- let scaledStep = scale * timingStep;
- if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
- timingStep <<= 1;
- continue;
- }
- optimalTickIntervalFound = true;
-
- // Insert one label for each division on the current scale.
- let fragment = document.createDocumentFragment();
- let direction = window.isRTL ? -1 : 1;
-
- for (let x = 0; x < availableWidth; x += scaledStep) {
- let translateX = "translateX(" + ((direction * x) | 0) + "px)";
- let millisecondTime = x / scale;
-
- let normalizedTime = millisecondTime;
- let divisionScale = "millisecond";
-
- // If the division is greater than 1 minute.
- if (normalizedTime > 60000) {
- normalizedTime /= 60000;
- divisionScale = "minute";
- } else if (normalizedTime > 1000) {
- // If the division is greater than 1 second.
- normalizedTime /= 1000;
- divisionScale = "second";
- }
-
- // Showing too many decimals is bad UX.
- if (divisionScale == "millisecond") {
- normalizedTime |= 0;
- } else {
- normalizedTime = L10N.numberWithDecimals(normalizedTime,
- REQUEST_TIME_DECIMALS);
- }
-
- let node = document.createElement("label");
- let text = L10N.getFormatStr("networkMenu." +
- divisionScale, normalizedTime);
- node.className = "plain requests-menu-timings-division";
- node.setAttribute("division-scale", divisionScale);
- node.style.transform = translateX;
-
- node.setAttribute("value", text);
- fragment.appendChild(node);
- }
- container.appendChild(fragment);
-
- container.className = "requests-menu-waterfall-visible";
- }
- },
-
- /**
- * Creates the background displayed on each waterfall view in this container.
- *
- * @param number scale
- * The current waterfall scale.
- */
- _drawWaterfallBackground: function (scale) {
- if (!this._canvas || !this._ctx) {
- this._canvas = document.createElementNS(HTML_NS, "canvas");
- this._ctx = this._canvas.getContext("2d");
- }
- let canvas = this._canvas;
- let ctx = this._ctx;
-
- // Nuke the context.
- let canvasWidth = canvas.width = this._waterfallWidth;
- // Awww yeah, 1px, repeats on Y axis.
- let canvasHeight = canvas.height = 1;
-
- // Start over.
- let imageData = ctx.createImageData(canvasWidth, canvasHeight);
- let pixelArray = imageData.data;
-
- let buf = new ArrayBuffer(pixelArray.length);
- let view8bit = new Uint8ClampedArray(buf);
- let view32bit = new Uint32Array(buf);
-
- // Build new millisecond tick lines...
- let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
- let [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
- let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
- let optimalTickIntervalFound = false;
-
- while (!optimalTickIntervalFound) {
- // Ignore any divisions that would end up being too close to each other.
- let scaledStep = scale * timingStep;
- if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
- timingStep <<= 1;
- continue;
- }
- optimalTickIntervalFound = true;
-
- // Insert one pixel for each division on each scale.
- for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
- let increment = scaledStep * Math.pow(2, i);
- for (let x = 0; x < canvasWidth; x += increment) {
- let position = (window.isRTL ? canvasWidth - x : x) | 0;
- view32bit[position] =
- (alphaComponent << 24) | (b << 16) | (g << 8) | r;
- }
- alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
- }
- }
-
- {
- let t = NetMonitorController.NetworkEventsHandler
- .firstDocumentDOMContentLoadedTimestamp;
-
- let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
- let [r1, g1, b1, a1] =
- REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA;
- view32bit[delta] = (a1 << 24) | (r1 << 16) | (g1 << 8) | b1;
- }
- {
- let t = NetMonitorController.NetworkEventsHandler
- .firstDocumentLoadTimestamp;
-
- let delta = Math.floor((t - this._firstRequestStartedMillis) * scale);
- let [r2, g2, b2, a2] = REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA;
- view32bit[delta] = (a2 << 24) | (r2 << 16) | (g2 << 8) | b2;
- }
-
- // Flush the image data and cache the waterfall background.
- pixelArray.set(view8bit);
- ctx.putImageData(imageData, 0, 0);
- document.mozSetImageElement("waterfall-background", canvas);
- },
-
- /**
- * The selection listener for this container.
- */
- _onSelect: function ({ detail: item }) {
- if (item) {
- NetMonitorView.Sidebar.populate(item.attachment);
- NetMonitorView.Sidebar.toggle(true);
- } else {
- NetMonitorView.Sidebar.toggle(false);
- }
- },
-
- /**
- * The swap listener for this container.
- * Called when two items switch places, when the contents are sorted.
- */
- _onSwap: function ({ detail: [firstItem, secondItem] }) {
- // Reattach click listener to the security icons
- this.attachSecurityIconClickListener(firstItem);
- this.attachSecurityIconClickListener(secondItem);
- },
-
- /**
- * The predicate used when deciding whether a popup should be shown
- * over a request item or not.
- *
- * @param nsIDOMNode target
- * The element node currently being hovered.
- * @param object tooltip
- * The current tooltip instance.
- * @return {Promise}
- */
- _onHover: Task.async(function* (target, tooltip) {
- let requestItem = this.getItemForElement(target);
- if (!requestItem) {
- return false;
- }
-
- let hovered = requestItem.attachment;
- if (hovered.responseContent && target.closest(".requests-menu-icon-and-file")) {
- return this._setTooltipImageContent(tooltip, requestItem);
- } else if (hovered.cause && target.closest(".requests-menu-cause-stack")) {
- return this._setTooltipStackTraceContent(tooltip, requestItem);
- }
-
- return false;
- }),
-
- _setTooltipImageContent: Task.async(function* (tooltip, requestItem) {
- let { mimeType, text, encoding } = requestItem.attachment.responseContent.content;
-
- if (!mimeType || !mimeType.includes("image/")) {
- return false;
- }
-
- let string = yield gNetwork.getString(text);
- let src = formDataURI(mimeType, encoding, string);
- let maxDim = REQUESTS_TOOLTIP_IMAGE_MAX_DIM;
- let { naturalWidth, naturalHeight } = yield getImageDimensions(tooltip.doc, src);
- let options = { maxDim, naturalWidth, naturalHeight };
- setImageTooltip(tooltip, tooltip.doc, src, options);
-
- return $(".requests-menu-icon", requestItem.target);
- }),
-
- _setTooltipStackTraceContent: Task.async(function* (tooltip, requestItem) {
- let {stacktrace} = requestItem.attachment.cause;
-
- if (!stacktrace || stacktrace.length == 0) {
- return false;
- }
-
- let doc = tooltip.doc;
- let el = doc.createElementNS(HTML_NS, "div");
- el.className = "stack-trace-tooltip devtools-monospace";
-
- for (let f of stacktrace) {
- let { functionName, filename, lineNumber, columnNumber, asyncCause } = f;
-
- if (asyncCause) {
- // if there is asyncCause, append a "divider" row into the trace
- let asyncFrameEl = doc.createElementNS(HTML_NS, "div");
- asyncFrameEl.className = "stack-frame stack-frame-async";
- asyncFrameEl.textContent =
- WEBCONSOLE_L10N.getFormatStr("stacktrace.asyncStack", asyncCause);
- el.appendChild(asyncFrameEl);
- }
-
- // Parse a source name in format "url -> url"
- let sourceUrl = filename.split(" -> ").pop();
-
- let frameEl = doc.createElementNS(HTML_NS, "div");
- frameEl.className = "stack-frame stack-frame-call";
-
- let funcEl = doc.createElementNS(HTML_NS, "span");
- funcEl.className = "stack-frame-function-name";
- funcEl.textContent =
- functionName || WEBCONSOLE_L10N.getStr("stacktrace.anonymousFunction");
- frameEl.appendChild(funcEl);
-
- let sourceEl = doc.createElementNS(HTML_NS, "span");
- sourceEl.className = "stack-frame-source-name";
- frameEl.appendChild(sourceEl);
-
- let sourceInnerEl = doc.createElementNS(HTML_NS, "span");
- sourceInnerEl.className = "stack-frame-source-name-inner";
- sourceEl.appendChild(sourceInnerEl);
-
- sourceInnerEl.textContent = sourceUrl;
- sourceInnerEl.title = sourceUrl;
-
- let lineEl = doc.createElementNS(HTML_NS, "span");
- lineEl.className = "stack-frame-line";
- lineEl.textContent = `:${lineNumber}:${columnNumber}`;
- sourceInnerEl.appendChild(lineEl);
-
- frameEl.addEventListener("click", () => {
- // hide the tooltip immediately, not after delay
- tooltip.hide();
- NetMonitorController.viewSourceInDebugger(filename, lineNumber);
- }, false);
-
- el.appendChild(frameEl);
- }
-
- tooltip.setContent(el, {width: REQUESTS_TOOLTIP_STACK_TRACE_WIDTH});
-
- return true;
- }),
-
- /**
- * A handler that opens the security tab in the details view if secure or
- * broken security indicator is clicked.
- */
- _onSecurityIconClick: function (e) {
- let state = this.selectedItem.attachment.securityState;
- if (state !== "insecure") {
- // Choose the security tab.
- NetMonitorView.NetworkDetails.widget.selectedIndex = 5;
- }
- },
-
- /**
- * The resize listener for this container's window.
- */
- _onResize: function (e) {
- // Allow requests to settle down first.
- setNamedTimeout("resize-events",
- RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
- },
-
- /**
- * Scroll listener for the requests menu view.
- */
- _onScroll: function () {
- this.tooltip.hide();
- },
-
- /**
- * Open context menu
- */
- _onContextMenu: function (e) {
- e.preventDefault();
- this.contextMenu.open(e);
- },
-
- /**
- * Checks if the specified unix time is the first one to be known of,
- * and saves it if so.
- *
- * @param number unixTime
- * The milliseconds to check and save.
- */
- _registerFirstRequestStart: function (unixTime) {
- if (this._firstRequestStartedMillis == -1) {
- this._firstRequestStartedMillis = unixTime;
- }
- },
-
- /**
- * Checks if the specified unix time is the last one to be known of,
- * and saves it if so.
- *
- * @param number unixTime
- * The milliseconds to check and save.
- */
- _registerLastRequestEnd: function (unixTime) {
- if (this._lastRequestEndedMillis < unixTime) {
- this._lastRequestEndedMillis = unixTime;
- }
- },
-
- /**
- * Gets the available waterfall width in this container.
- * @return number
- */
- get _waterfallWidth() {
- if (this._cachedWaterfallWidth == 0) {
- let container = $("#requests-menu-toolbar");
- let waterfall = $("#requests-menu-waterfall-header-box");
- let containerBounds = container.getBoundingClientRect();
- let waterfallBounds = waterfall.getBoundingClientRect();
- if (!window.isRTL) {
- this._cachedWaterfallWidth = containerBounds.width -
- waterfallBounds.left;
- } else {
- this._cachedWaterfallWidth = waterfallBounds.right;
- }
- }
- return this._cachedWaterfallWidth;
- },
-
- _splitter: null,
- _summary: null,
- _canvas: null,
- _ctx: null,
- _cachedWaterfallWidth: 0,
- _firstRequestStartedMillis: -1,
- _lastRequestEndedMillis: -1,
- _updateQueue: [],
- _addQueue: [],
- _updateTimeout: null,
- _resizeTimeout: null,
- _activeFilters: ["all"],
- _currentFreetextFilter: ""
-});
+};
exports.RequestsMenuView = RequestsMenuView;
diff --git a/devtools/client/netmonitor/selectors/filters.js b/devtools/client/netmonitor/selectors/filters.js
new file mode 100644
index 000000000000..277c47d9f9c8
--- /dev/null
+++ b/devtools/client/netmonitor/selectors/filters.js
@@ -0,0 +1,13 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+function getActiveFilters(state) {
+ return state.filters.types.toSeq().filter(checked => checked).keySeq().toArray();
+}
+
+module.exports = {
+ getActiveFilters
+};
diff --git a/devtools/client/netmonitor/selectors/index.js b/devtools/client/netmonitor/selectors/index.js
index 8cd6b6ffc2e7..c25c724767d5 100644
--- a/devtools/client/netmonitor/selectors/index.js
+++ b/devtools/client/netmonitor/selectors/index.js
@@ -4,60 +4,12 @@
"use strict";
-const { createSelector } = require("devtools/client/shared/vendor/reselect");
+const filters = require("./filters");
+const requests = require("./requests");
+const ui = require("./ui");
-/**
- * Gets the total number of bytes representing the cumulated content size of
- * a set of requests. Returns 0 for an empty set.
- *
- * @param {array} items - an array of request items
- * @return {number} total bytes of requests
- */
-function getTotalBytesOfRequests(items) {
- if (!items.length) {
- return 0;
- }
-
- let result = 0;
- items.forEach((item) => {
- let size = item.attachment.contentSize;
- result += (typeof size == "number") ? size : 0;
- });
-
- return result;
-}
-
-/**
- * Gets the total milliseconds for all requests. Returns null for an
- * empty set.
- *
- * @param {array} items - an array of request items
- * @return {object} total milliseconds for all requests
- */
-function getTotalMillisOfRequests(items) {
- if (!items.length) {
- return null;
- }
-
- const oldest = items.reduce((prev, curr) =>
- prev.attachment.startedMillis < curr.attachment.startedMillis ?
- prev : curr);
- const newest = items.reduce((prev, curr) =>
- prev.attachment.startedMillis > curr.attachment.startedMillis ?
- prev : curr);
-
- return newest.attachment.endedMillis - oldest.attachment.startedMillis;
-}
-
-const getSummary = createSelector(
- (state) => state.requests.items,
- (requests) => ({
- count: requests.length,
- totalBytes: getTotalBytesOfRequests(requests),
- totalMillis: getTotalMillisOfRequests(requests),
- })
+Object.assign(exports,
+ filters,
+ requests,
+ ui
);
-
-module.exports = {
- getSummary,
-};
diff --git a/devtools/client/netmonitor/selectors/moz.build b/devtools/client/netmonitor/selectors/moz.build
index c4db22a9f908..9082b8bd1b88 100644
--- a/devtools/client/netmonitor/selectors/moz.build
+++ b/devtools/client/netmonitor/selectors/moz.build
@@ -3,5 +3,8 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
- 'index.js'
+ 'filters.js',
+ 'index.js',
+ 'requests.js',
+ 'ui.js',
)
diff --git a/devtools/client/netmonitor/selectors/requests.js b/devtools/client/netmonitor/selectors/requests.js
new file mode 100644
index 000000000000..ee44f6cd420c
--- /dev/null
+++ b/devtools/client/netmonitor/selectors/requests.js
@@ -0,0 +1,119 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { createSelector } = require("devtools/client/shared/vendor/reselect");
+const { Filters, isFreetextMatch } = require("../filter-predicates");
+const { Sorters } = require("../sort-predicates");
+
+/**
+ * Check if the given requests is a clone, find and return the original request if it is.
+ * Cloned requests are sorted by comparing the original ones.
+ */
+function getOrigRequest(requests, req) {
+ if (!req.id.endsWith("-clone")) {
+ return req;
+ }
+
+ const origId = req.id.replace(/-clone$/, "");
+ return requests.find(r => r.id === origId);
+}
+
+const getFilterFn = createSelector(
+ state => state.filters,
+ filters => r => {
+ const matchesType = filters.types.some((enabled, filter) => {
+ return enabled && Filters[filter] && Filters[filter](r);
+ });
+ return matchesType && isFreetextMatch(r, filters.text);
+ }
+);
+
+const getSortFn = createSelector(
+ state => state.requests.requests,
+ state => state.sort,
+ (requests, sort) => {
+ let dataSorter = Sorters[sort.type || "waterfall"];
+
+ function sortWithClones(a, b) {
+ // If one request is a clone of the other, sort them next to each other
+ if (a.id == b.id + "-clone") {
+ return +1;
+ } else if (a.id + "-clone" == b.id) {
+ return -1;
+ }
+
+ // Otherwise, get the original requests and compare them
+ return dataSorter(
+ getOrigRequest(requests, a),
+ getOrigRequest(requests, b)
+ );
+ }
+
+ const ascending = sort.ascending ? +1 : -1;
+ return (a, b) => ascending * sortWithClones(a, b, dataSorter);
+ }
+);
+
+const getSortedRequests = createSelector(
+ state => state.requests.requests,
+ getSortFn,
+ (requests, sortFn) => requests.sort(sortFn)
+);
+
+const getDisplayedRequests = createSelector(
+ state => state.requests.requests,
+ getFilterFn,
+ getSortFn,
+ (requests, filterFn, sortFn) => requests.filter(filterFn).sort(sortFn)
+);
+
+const getDisplayedRequestsSummary = createSelector(
+ getDisplayedRequests,
+ state => state.requests.lastEndedMillis - state.requests.firstStartedMillis,
+ (requests, totalMillis) => {
+ if (requests.size == 0) {
+ return { count: 0, bytes: 0, millis: 0 };
+ }
+
+ const totalBytes = requests.reduce((total, item) => {
+ if (typeof item.contentSize == "number") {
+ total += item.contentSize;
+ }
+ return total;
+ }, 0);
+
+ return {
+ count: requests.size,
+ bytes: totalBytes,
+ millis: totalMillis,
+ };
+ }
+);
+
+function getRequestById(state, id) {
+ return state.requests.requests.find(r => r.id === id);
+}
+
+function getDisplayedRequestById(state, id) {
+ return getDisplayedRequests(state).find(r => r.id === id);
+}
+
+function getSelectedRequest(state) {
+ if (!state.requests.selectedId) {
+ return null;
+ }
+
+ return getRequestById(state, state.requests.selectedId);
+}
+
+module.exports = {
+ getSortedRequests,
+ getDisplayedRequests,
+ getDisplayedRequestsSummary,
+ getRequestById,
+ getDisplayedRequestById,
+ getSelectedRequest,
+};
diff --git a/devtools/client/netmonitor/selectors/ui.js b/devtools/client/netmonitor/selectors/ui.js
new file mode 100644
index 000000000000..35f202144b3e
--- /dev/null
+++ b/devtools/client/netmonitor/selectors/ui.js
@@ -0,0 +1,32 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { getDisplayedRequests } = require("./requests");
+
+function isSidebarToggleButtonDisabled(state) {
+ return getDisplayedRequests(state).isEmpty();
+}
+
+const EPSILON = 0.001;
+
+function getWaterfallScale(state) {
+ const { requests, timingMarkers, ui } = state;
+
+ if (requests.firstStartedMillis == +Infinity) {
+ return null;
+ }
+
+ const lastEventMillis = Math.max(requests.lastEndedMillis,
+ timingMarkers.firstDocumentDOMContentLoadedTimestamp,
+ timingMarkers.firstDocumentLoadTimestamp);
+ const longestWidth = lastEventMillis - requests.firstStartedMillis;
+ return Math.min(Math.max(ui.waterfallWidth / longestWidth, EPSILON), 1);
+}
+
+module.exports = {
+ isSidebarToggleButtonDisabled,
+ getWaterfallScale,
+};
diff --git a/devtools/client/netmonitor/sidebar-view.js b/devtools/client/netmonitor/sidebar-view.js
index 027dfaf08446..d1c4473fc646 100644
--- a/devtools/client/netmonitor/sidebar-view.js
+++ b/devtools/client/netmonitor/sidebar-view.js
@@ -2,8 +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/. */
-/* import-globals-from ./netmonitor-controller.js */
-/* globals dumpn, $, NetMonitorView */
+/* globals window, dumpn, $, NetMonitorView */
"use strict";
@@ -26,7 +25,6 @@ SidebarView.prototype = {
*/
toggle: function (visibleFlag) {
NetMonitorView.toggleDetailsPane({ visible: visibleFlag });
- NetMonitorView.RequestsMenu._flushWaterfallViews(true);
},
/**
diff --git a/devtools/client/netmonitor/sort-predicates.js b/devtools/client/netmonitor/sort-predicates.js
index 18290a51ce13..935ce540bfb8 100644
--- a/devtools/client/netmonitor/sort-predicates.js
+++ b/devtools/client/netmonitor/sort-predicates.js
@@ -8,7 +8,6 @@ const {
getAbbreviatedMimeType,
getUrlBaseNameWithQuery,
getUrlHost,
- loadCauseString,
} = require("./request-utils");
/**
@@ -60,8 +59,8 @@ function domain(first, second) {
}
function cause(first, second) {
- let firstCause = loadCauseString(first.cause.type);
- let secondCause = loadCauseString(second.cause.type);
+ let firstCause = first.cause.type;
+ let secondCause = second.cause.type;
if (firstCause == secondCause) {
return first.startedMillis - second.startedMillis;
}
diff --git a/devtools/client/netmonitor/store.js b/devtools/client/netmonitor/store.js
index 9e01657f9427..63f32f235686 100644
--- a/devtools/client/netmonitor/store.js
+++ b/devtools/client/netmonitor/store.js
@@ -4,11 +4,19 @@
"use strict";
-const createStore = require("devtools/client/shared/redux/create-store");
-const reducers = require("./reducers/index");
+const { createStore, applyMiddleware } = require("devtools/client/shared/vendor/redux");
+const { thunk } = require("devtools/client/shared/redux/middleware/thunk");
+const batching = require("./middleware/batching");
+const rootReducer = require("./reducers/index");
function configureStore() {
- return createStore()(reducers);
+ return createStore(
+ rootReducer,
+ applyMiddleware(
+ thunk,
+ batching
+ )
+ );
}
exports.configureStore = configureStore;
diff --git a/devtools/client/netmonitor/test/browser.ini b/devtools/client/netmonitor/test/browser.ini
index 0c8b861a6e6e..1ab7be4fbb45 100644
--- a/devtools/client/netmonitor/test/browser.ini
+++ b/devtools/client/netmonitor/test/browser.ini
@@ -54,7 +54,6 @@ support-files =
skip-if = (toolkit == "cocoa" && e10s) # bug 1252254
[browser_net_api-calls.js]
[browser_net_autoscroll.js]
-skip-if = true # Bug 1309191 - replace with rewritten version in React
[browser_net_cached-status.js]
[browser_net_cause.js]
[browser_net_cause_redirect.js]
@@ -89,9 +88,11 @@ subsuite = clipboard
[browser_net_cyrillic-01.js]
[browser_net_cyrillic-02.js]
[browser_net_details-no-duplicated-content.js]
-skip-if = (os == 'linux' && e10s && debug) # Bug 1242204
+skip-if = true # Test broken in React version, is too low-level
[browser_net_frame.js]
+skip-if = (os == 'linux' && debug && bits == 32) # Bug 1321434
[browser_net_filter-01.js]
+skip-if = (os == 'linux' && debug && bits == 32) # Bug 1303439
[browser_net_filter-02.js]
[browser_net_filter-03.js]
[browser_net_filter-04.js]
@@ -140,6 +141,7 @@ skip-if = true # Bug 1258809
skip-if = true # Bug 1258809
[browser_net_simple-request.js]
[browser_net_sort-01.js]
+skip-if = true # Redundant for React/Redux version
[browser_net_sort-02.js]
[browser_net_sort-03.js]
[browser_net_statistics-01.js]
@@ -149,5 +151,6 @@ skip-if = true # Bug 1258809
[browser_net_streaming-response.js]
[browser_net_throttle.js]
[browser_net_timeline_ticks.js]
+skip-if = true # TODO: fix the test
[browser_net_timing-division.js]
[browser_net_persistent_logs.js]
diff --git a/devtools/client/netmonitor/test/browser_net_accessibility-01.js b/devtools/client/netmonitor/test/browser_net_accessibility-01.js
index c0832064f4c6..44ecedb3b0d2 100644
--- a/devtools/client/netmonitor/test/browser_net_accessibility-01.js
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-01.js
@@ -14,11 +14,13 @@ add_task(function* () {
// It seems that this test may be slow on Ubuntu builds running on ec2.
requestLongerTimeout(2);
- let { NetMonitorView } = monitor.panelWin;
+ let { NetMonitorView, gStore, windowRequire } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
+ let Actions = windowRequire("devtools/client/netmonitor/actions/index");
+
let count = 0;
function check(selectedIndex, paneVisibility) {
info("Performing check " + (count++) + ".");
@@ -37,24 +39,19 @@ add_task(function* () {
check(-1, false);
- RequestsMenu.focusLastVisibleItem();
+ gStore.dispatch(Actions.selectDelta(+Infinity));
check(1, true);
- RequestsMenu.focusFirstVisibleItem();
+ gStore.dispatch(Actions.selectDelta(-Infinity));
check(0, true);
- RequestsMenu.focusNextItem();
+ gStore.dispatch(Actions.selectDelta(+1));
check(1, true);
- RequestsMenu.focusPrevItem();
+ gStore.dispatch(Actions.selectDelta(-1));
check(0, true);
- RequestsMenu.focusItemAtDelta(+1);
+ gStore.dispatch(Actions.selectDelta(+10));
check(1, true);
- RequestsMenu.focusItemAtDelta(-1);
- check(0, true);
-
- RequestsMenu.focusItemAtDelta(+10);
- check(1, true);
- RequestsMenu.focusItemAtDelta(-10);
+ gStore.dispatch(Actions.selectDelta(-10));
check(0, true);
wait = waitForNetworkEvents(monitor, 18);
@@ -63,25 +60,25 @@ add_task(function* () {
});
yield wait;
- RequestsMenu.focusLastVisibleItem();
+ gStore.dispatch(Actions.selectDelta(+Infinity));
check(19, true);
- RequestsMenu.focusFirstVisibleItem();
+ gStore.dispatch(Actions.selectDelta(-Infinity));
check(0, true);
- RequestsMenu.focusNextItem();
+ gStore.dispatch(Actions.selectDelta(+1));
check(1, true);
- RequestsMenu.focusPrevItem();
+ gStore.dispatch(Actions.selectDelta(-1));
check(0, true);
- RequestsMenu.focusItemAtDelta(+10);
+ gStore.dispatch(Actions.selectDelta(+10));
check(10, true);
- RequestsMenu.focusItemAtDelta(-10);
+ gStore.dispatch(Actions.selectDelta(-10));
check(0, true);
- RequestsMenu.focusItemAtDelta(+100);
+ gStore.dispatch(Actions.selectDelta(+100));
check(19, true);
- RequestsMenu.focusItemAtDelta(-100);
+ gStore.dispatch(Actions.selectDelta(-100));
check(0, true);
- yield teardown(monitor);
+ return teardown(monitor);
});
diff --git a/devtools/client/netmonitor/test/browser_net_accessibility-02.js b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
index 33420a4401d2..0e17263ff589 100644
--- a/devtools/client/netmonitor/test/browser_net_accessibility-02.js
+++ b/devtools/client/netmonitor/test/browser_net_accessibility-02.js
@@ -35,6 +35,8 @@ add_task(function* () {
});
yield wait;
+ $(".requests-menu-contents").focus();
+
check(-1, false);
EventUtils.sendKey("DOWN", window);
@@ -123,7 +125,7 @@ add_task(function* () {
EventUtils.sendMouseEvent({ type: "mousedown" }, $("#details-pane-toggle"));
check(-1, false);
- EventUtils.sendMouseEvent({ type: "mousedown" }, $(".side-menu-widget-item"));
+ EventUtils.sendMouseEvent({ type: "mousedown" }, $(".request-list-item"));
check(0, true);
yield teardown(monitor);
diff --git a/devtools/client/netmonitor/test/browser_net_api-calls.js b/devtools/client/netmonitor/test/browser_net_api-calls.js
index 994dc03549c1..7fd87afe9619 100644
--- a/devtools/client/netmonitor/test/browser_net_api-calls.js
+++ b/devtools/client/netmonitor/test/browser_net_api-calls.js
@@ -32,7 +32,7 @@ add_task(function* () {
yield wait;
REQUEST_URIS.forEach(function (uri, index) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(index), "GET", uri);
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(index), "GET", uri);
});
yield teardown(monitor);
diff --git a/devtools/client/netmonitor/test/browser_net_autoscroll.js b/devtools/client/netmonitor/test/browser_net_autoscroll.js
index 9abb3fd17ce5..86f9300d8852 100644
--- a/devtools/client/netmonitor/test/browser_net_autoscroll.js
+++ b/devtools/client/netmonitor/test/browser_net_autoscroll.js
@@ -10,22 +10,25 @@ add_task(function* () {
requestLongerTimeout(2);
let { monitor } = yield initNetMonitor(INFINITE_GET_URL);
- let win = monitor.panelWin;
- let topNode = win.document.getElementById("requests-menu-contents");
- let requestsContainer = topNode.getElementsByTagName("scrollbox")[0];
- ok(!!requestsContainer, "Container element exists as expected.");
+ let { $ } = monitor.panelWin;
+
+ // Wait until the first request makes the empty notice disappear
+ yield waitForRequestListToAppear();
+
+ let requestsContainer = $(".requests-menu-contents");
+ ok(requestsContainer, "Container element exists as expected.");
// (1) Check that the scroll position is maintained at the bottom
// when the requests overflow the vertical size of the container.
yield waitForRequestsToOverflowContainer();
yield waitForScroll();
- ok(scrolledToBottom(requestsContainer), "Scrolled to bottom on overflow.");
+ ok(true, "Scrolled to bottom on overflow.");
- // (2) Now set the scroll position somewhere in the middle and check
+ // (2) Now set the scroll position to the first item and check
// that additional requests do not change the scroll position.
- let children = requestsContainer.childNodes;
- let middleNode = children.item(children.length / 2);
- middleNode.scrollIntoView();
+ let firstNode = requestsContainer.firstChild;
+ firstNode.scrollIntoView();
+ yield waitSomeTime();
ok(!scrolledToBottom(requestsContainer), "Not scrolled to bottom.");
// save for comparison later
let scrollTop = requestsContainer.scrollTop;
@@ -39,7 +42,7 @@ add_task(function* () {
ok(scrolledToBottom(requestsContainer), "Set scroll position to bottom.");
yield waitForNetworkEvents(monitor, 8);
yield waitForScroll();
- ok(scrolledToBottom(requestsContainer), "Still scrolled to bottom.");
+ ok(true, "Still scrolled to bottom.");
// (4) Now select an item in the list and check that additional requests
// do not change the scroll position.
@@ -49,12 +52,20 @@ add_task(function* () {
is(requestsContainer.scrollTop, 0, "Did not scroll.");
// Done: clean up.
- yield teardown(monitor);
+ return teardown(monitor);
+
+ function waitForRequestListToAppear() {
+ info("Waiting until the empty notice disappears and is replaced with the list");
+ return waitUntil(() => !!$(".requests-menu-contents"));
+ }
function* waitForRequestsToOverflowContainer() {
+ info("Waiting for enough requests to overflow the container");
while (true) {
+ info("Waiting for one network request");
yield waitForNetworkEvents(monitor, 1);
if (requestsContainer.scrollHeight > requestsContainer.clientHeight) {
+ info("The list is long enough, returning");
return;
}
}
@@ -70,6 +81,7 @@ add_task(function* () {
}
function waitForScroll() {
- return monitor._view.RequestsMenu.widget.once("scroll-to-bottom");
+ info("Waiting for the list to scroll to bottom");
+ return waitUntil(() => scrolledToBottom(requestsContainer));
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_brotli.js b/devtools/client/netmonitor/test/browser_net_brotli.js
index cc6908d68aed..2318e2937904 100644
--- a/devtools/client/netmonitor/test/browser_net_brotli.js
+++ b/devtools/client/netmonitor/test/browser_net_brotli.js
@@ -27,7 +27,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", HTTPS_CONTENT_TYPE_SJS + "?fmt=br", {
status: 200,
statusText: "Connected",
diff --git a/devtools/client/netmonitor/test/browser_net_cached-status.js b/devtools/client/netmonitor/test/browser_net_cached-status.js
index bc97f76774f0..b81a4acbd33f 100644
--- a/devtools/client/netmonitor/test/browser_net_cached-status.js
+++ b/devtools/client/netmonitor/test/browser_net_cached-status.js
@@ -93,7 +93,8 @@ add_task(function* () {
let item = RequestsMenu.getItemAtIndex(index);
info("Verifying request #" + index);
- yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+ yield verifyRequestItemTarget(RequestsMenu, item,
+ request.method, request.uri, request.details);
index++;
}
diff --git a/devtools/client/netmonitor/test/browser_net_cause.js b/devtools/client/netmonitor/test/browser_net_cause.js
index 2e73965d0d6f..469f779f1606 100644
--- a/devtools/client/netmonitor/test/browser_net_cause.js
+++ b/devtools/client/netmonitor/test/browser_net_cause.js
@@ -15,7 +15,7 @@ const EXPECTED_REQUESTS = [
method: "GET",
url: CAUSE_URL,
causeType: "document",
- causeUri: "",
+ causeUri: null,
// The document load has internal privileged JS code on the stack
stack: true
},
@@ -103,11 +103,11 @@ add_task(function* () {
let { method, url, causeType, causeUri, stack } = spec;
let requestItem = RequestsMenu.getItemAtIndex(i);
- verifyRequestItemTarget(requestItem,
+ verifyRequestItemTarget(RequestsMenu, requestItem,
method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
);
- let { stacktrace } = requestItem.attachment.cause;
+ let { stacktrace } = requestItem.cause;
let stackLen = stacktrace ? stacktrace.length : 0;
if (stack) {
@@ -137,9 +137,7 @@ add_task(function* () {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-cause-button"));
let expectedOrder = EXPECTED_REQUESTS.map(r => r.causeType).sort();
expectedOrder.forEach((expectedCause, i) => {
- let { target } = RequestsMenu.getItemAtIndex(i);
- let causeLabel = target.querySelector(".requests-menu-cause-label");
- let cause = causeLabel.getAttribute("value");
+ const cause = RequestsMenu.getItemAtIndex(i).cause.type;
is(cause, expectedCause, `The request #${i} has the expected cause after sorting`);
});
diff --git a/devtools/client/netmonitor/test/browser_net_cause_redirect.js b/devtools/client/netmonitor/test/browser_net_cause_redirect.js
index ace6390abbc6..b16e17682551 100644
--- a/devtools/client/netmonitor/test/browser_net_cause_redirect.js
+++ b/devtools/client/netmonitor/test/browser_net_cause_redirect.js
@@ -27,11 +27,11 @@ add_task(function* () {
yield wait;
EXPECTED_REQUESTS.forEach(({status, hasStack}, i) => {
- let { attachment } = RequestsMenu.getItemAtIndex(i);
+ let item = RequestsMenu.getItemAtIndex(i);
- is(attachment.status, status, `Request #${i} has the expected status`);
+ is(item.status, status, `Request #${i} has the expected status`);
- let { stacktrace } = attachment.cause;
+ let { stacktrace } = item.cause;
let stackLen = stacktrace ? stacktrace.length : 0;
if (hasStack) {
diff --git a/devtools/client/netmonitor/test/browser_net_content-type.js b/devtools/client/netmonitor/test/browser_net_content-type.js
index 1951bc69d579..321598cbd5cc 100644
--- a/devtools/client/netmonitor/test/browser_net_content-type.js
+++ b/devtools/client/netmonitor/test/browser_net_content-type.js
@@ -24,7 +24,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=xml", {
status: 200,
statusText: "OK",
@@ -33,7 +33,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 42),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
"GET", CONTENT_TYPE_SJS + "?fmt=css", {
status: 200,
statusText: "OK",
@@ -42,7 +42,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(2),
"GET", CONTENT_TYPE_SJS + "?fmt=js", {
status: 200,
statusText: "OK",
@@ -51,7 +51,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 34),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(3),
"GET", CONTENT_TYPE_SJS + "?fmt=json", {
status: 200,
statusText: "OK",
@@ -60,7 +60,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(4),
"GET", CONTENT_TYPE_SJS + "?fmt=bogus", {
status: 404,
statusText: "Not Found",
@@ -69,7 +69,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 24),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(5),
"GET", TEST_IMAGE, {
fuzzyUrl: true,
status: 200,
@@ -79,7 +79,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 580),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(6),
"GET", CONTENT_TYPE_SJS + "?fmt=gzip", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_copy_headers.js b/devtools/client/netmonitor/test/browser_net_copy_headers.js
index 36ce2fb34789..58a3737d3c39 100644
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -23,7 +23,7 @@ add_task(function* () {
let requestItem = RequestsMenu.getItemAtIndex(0);
RequestsMenu.selectedItem = requestItem;
- let { method, httpVersion, status, statusText } = requestItem.attachment;
+ let { method, httpVersion, status, statusText } = requestItem;
const EXPECTED_REQUEST_HEADERS = [
`${method} ${SIMPLE_URL} ${httpVersion}`,
diff --git a/devtools/client/netmonitor/test/browser_net_copy_url.js b/devtools/client/netmonitor/test/browser_net_copy_url.js
index 660f5fe7994b..a4dc37ceb6f2 100644
--- a/devtools/client/netmonitor/test/browser_net_copy_url.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_url.js
@@ -25,7 +25,7 @@ add_task(function* () {
yield waitForClipboardPromise(function setup() {
RequestsMenu.contextMenu.copyUrl();
- }, requestItem.attachment.url);
+ }, requestItem.url);
yield teardown(monitor);
});
diff --git a/devtools/client/netmonitor/test/browser_net_cors_requests.js b/devtools/client/netmonitor/test/browser_net_cors_requests.js
index 4a9f84335e20..7ce4930bb31d 100644
--- a/devtools/client/netmonitor/test/browser_net_cors_requests.js
+++ b/devtools/client/netmonitor/test/browser_net_cors_requests.js
@@ -25,7 +25,8 @@ add_task(function* () {
info("Checking the preflight and flight methods");
["OPTIONS", "POST"].forEach((method, i) => {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i), method, requestUrl);
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
+ method, requestUrl);
});
yield teardown(monitor);
diff --git a/devtools/client/netmonitor/test/browser_net_curl-utils.js b/devtools/client/netmonitor/test/browser_net_curl-utils.js
index 7a5fc7926478..71389a26eec2 100644
--- a/devtools/client/netmonitor/test/browser_net_curl-utils.js
+++ b/devtools/client/netmonitor/test/browser_net_curl-utils.js
@@ -31,19 +31,19 @@ add_task(function* () {
multipartForm: RequestsMenu.getItemAtIndex(3)
};
- let data = yield createCurlData(requests.get.attachment, gNetwork);
+ let data = yield createCurlData(requests.get, gNetwork);
testFindHeader(data);
- data = yield createCurlData(requests.post.attachment, gNetwork);
+ data = yield createCurlData(requests.post, gNetwork);
testIsUrlEncodedRequest(data);
testWritePostDataTextParams(data);
- data = yield createCurlData(requests.multipart.attachment, gNetwork);
+ data = yield createCurlData(requests.multipart, gNetwork);
testIsMultipartRequest(data);
testGetMultipartBoundary(data);
testRemoveBinaryDataFromMultipartText(data);
- data = yield createCurlData(requests.multipartForm.attachment, gNetwork);
+ data = yield createCurlData(requests.multipartForm, gNetwork);
testGetHeadersFromMultipartText(data);
if (Services.appinfo.OS != "WINNT") {
diff --git a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
index 43d6f522e979..4807881efc25 100644
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-01.js
@@ -22,7 +22,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=txt", {
status: 200,
statusText: "DA DA DA"
diff --git a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
index cd6b2000e339..c22f2d231643 100644
--- a/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
+++ b/devtools/client/netmonitor/test/browser_net_cyrillic-02.js
@@ -21,7 +21,7 @@ add_task(function* () {
tab.linkedBrowser.reload();
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CYRILLIC_URL, {
status: 200,
statusText: "OK"
diff --git a/devtools/client/netmonitor/test/browser_net_filter-01.js b/devtools/client/netmonitor/test/browser_net_filter-01.js
index b0d76c6293e0..f8e01e3ba663 100644
--- a/devtools/client/netmonitor/test/browser_net_filter-01.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-01.js
@@ -28,8 +28,109 @@ const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.conca
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
]);
+const EXPECTED_REQUESTS = [
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=html",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=css",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=js",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=font",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "woff",
+ fullMimeType: "font/woff"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=image",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=audio",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "ogg",
+ fullMimeType: "audio/ogg"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=video",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "webm",
+ fullMimeType: "video/webm"
+ },
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=flash",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "x-shockwave-flash",
+ fullMimeType: "application/x-shockwave-flash"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=ws",
+ data: {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Switching Protocols",
+ }
+ }
+];
+
add_task(function* () {
let Actions = require("devtools/client/netmonitor/actions/index");
+
let { monitor } = yield initNetMonitor(FILTERING_URL);
let { gStore } = monitor.panelWin;
@@ -180,85 +281,25 @@ add_task(function* () {
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should still be visible after filtering.");
- is(RequestsMenu.items.length, visibility.length,
+ const items = RequestsMenu.items;
+ const visibleItems = RequestsMenu.visibleItems;
+
+ is(items.size, visibility.length,
"There should be a specific amount of items in the requests menu.");
- is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
- "There should be a specific amount of visbile items in the requests menu.");
+ is(visibleItems.size, visibility.filter(e => e).length,
+ "There should be a specific amount of visible items in the requests menu.");
for (let i = 0; i < visibility.length; i++) {
- is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
- "The item at index " + i + " doesn't have the correct hidden state.");
- }
+ let itemId = items.get(i).id;
+ let shouldBeVisible = !!visibility[i];
+ let isThere = visibleItems.some(r => r.id == itemId);
+ is(isThere, shouldBeVisible,
+ `The item at index ${i} has visibility=${shouldBeVisible}`);
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
- "GET", CONTENT_TYPE_SJS + "?fmt=html", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "html",
- fullMimeType: "text/html; charset=utf-8"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
- "GET", CONTENT_TYPE_SJS + "?fmt=css", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "css",
- fullMimeType: "text/css; charset=utf-8"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(2),
- "GET", CONTENT_TYPE_SJS + "?fmt=js", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "js",
- fullMimeType: "application/javascript; charset=utf-8"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(3),
- "GET", CONTENT_TYPE_SJS + "?fmt=font", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "woff",
- fullMimeType: "font/woff"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(4),
- "GET", CONTENT_TYPE_SJS + "?fmt=image", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "png",
- fullMimeType: "image/png"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(5),
- "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "ogg",
- fullMimeType: "audio/ogg"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(6),
- "GET", CONTENT_TYPE_SJS + "?fmt=video", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "webm",
- fullMimeType: "video/webm"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(7),
- "GET", CONTENT_TYPE_SJS + "?fmt=flash", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "x-shockwave-flash",
- fullMimeType: "application/x-shockwave-flash"
- });
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(8),
- "GET", CONTENT_TYPE_SJS + "?fmt=ws", {
- fuzzyUrl: true,
- status: 101,
- statusText: "Switching Protocols",
- });
+ if (shouldBeVisible) {
+ let { method, url, data } = EXPECTED_REQUESTS[i];
+ verifyRequestItemTarget(RequestsMenu, items.get(i), method, url, data);
+ }
+ }
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-02.js b/devtools/client/netmonitor/test/browser_net_filter-02.js
index 70a051b6df2a..fc5b6cfd49d5 100644
--- a/devtools/client/netmonitor/test/browser_net_filter-02.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-02.js
@@ -29,6 +29,106 @@ const REQUESTS_WITH_MEDIA_AND_FLASH_AND_WS = REQUESTS_WITH_MEDIA_AND_FLASH.conca
{ url: "sjs_content-type-test-server.sjs?fmt=ws" },
]);
+const EXPECTED_REQUESTS = [
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=html",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "html",
+ fullMimeType: "text/html; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=css",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "css",
+ fullMimeType: "text/css; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=js",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "js",
+ fullMimeType: "application/javascript; charset=utf-8"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=font",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "woff",
+ fullMimeType: "font/woff"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=image",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "png",
+ fullMimeType: "image/png"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=audio",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "ogg",
+ fullMimeType: "audio/ogg"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=video",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "webm",
+ fullMimeType: "video/webm"
+ },
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=flash",
+ data: {
+ fuzzyUrl: true,
+ status: 200,
+ statusText: "OK",
+ type: "x-shockwave-flash",
+ fullMimeType: "application/x-shockwave-flash"
+ }
+ },
+ {
+ method: "GET",
+ url: CONTENT_TYPE_SJS + "?fmt=ws",
+ data: {
+ fuzzyUrl: true,
+ status: 101,
+ statusText: "Switching Protocols",
+ }
+ }
+];
+
add_task(function* () {
let { monitor } = yield initNetMonitor(FILTERING_URL);
info("Starting test... ");
@@ -98,103 +198,29 @@ add_task(function* () {
is(NetMonitorView.detailsPaneHidden, false,
"The details pane should still be visible after filtering.");
- is(RequestsMenu.items.length, visibility.length,
+ const items = RequestsMenu.items;
+ const visibleItems = RequestsMenu.visibleItems;
+
+ is(items.size, visibility.length,
"There should be a specific amount of items in the requests menu.");
- is(RequestsMenu.visibleItems.length, visibility.filter(e => e).length,
- "There should be a specific amount of visbile items in the requests menu.");
+ is(visibleItems.size, visibility.filter(e => e).length,
+ "There should be a specific amount of visible items in the requests menu.");
for (let i = 0; i < visibility.length; i++) {
- is(RequestsMenu.getItemAtIndex(i).target.hidden, !visibility[i],
- "The item at index " + i + " doesn't have the correct hidden state.");
+ let itemId = items.get(i).id;
+ let shouldBeVisible = !!visibility[i];
+ let isThere = visibleItems.some(r => r.id == itemId);
+ is(isThere, shouldBeVisible,
+ `The item at index ${i} has visibility=${shouldBeVisible}`);
}
- for (let i = 0; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=html", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "html",
- fullMimeType: "text/html; charset=utf-8"
- });
- }
- for (let i = 1; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=css", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "css",
- fullMimeType: "text/css; charset=utf-8"
- });
- }
- for (let i = 2; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=js", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "js",
- fullMimeType: "application/javascript; charset=utf-8"
- });
- }
- for (let i = 3; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=font", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "woff",
- fullMimeType: "font/woff"
- });
- }
- for (let i = 4; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=image", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "png",
- fullMimeType: "image/png"
- });
- }
- for (let i = 5; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "ogg",
- fullMimeType: "audio/ogg"
- });
- }
- for (let i = 6; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=video", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "webm",
- fullMimeType: "video/webm"
- });
- }
- for (let i = 7; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=flash", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "x-shockwave-flash",
- fullMimeType: "application/x-shockwave-flash"
- });
- }
- for (let i = 8; i < visibility.length; i += 9) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
- "GET", CONTENT_TYPE_SJS + "?fmt=ws", {
- fuzzyUrl: true,
- status: 101,
- statusText: "Switching Protocols"
- });
+ for (let i = 0; i < EXPECTED_REQUESTS.length; i++) {
+ let { method, url, data } = EXPECTED_REQUESTS[i];
+ for (let j = i; j < visibility.length; j += EXPECTED_REQUESTS.length) {
+ if (visibility[j]) {
+ verifyRequestItemTarget(RequestsMenu, items.get(j), method, url, data);
+ }
+ }
}
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_filter-03.js b/devtools/client/netmonitor/test/browser_net_filter-03.js
index 2babdaab393b..1c863a8402dc 100644
--- a/devtools/client/netmonitor/test/browser_net_filter-03.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-03.js
@@ -104,82 +104,6 @@ add_task(function* () {
is(RequestsMenu.items.length, order.length,
"There should be a specific amount of items in the requests menu.");
is(RequestsMenu.visibleItems.length, visible,
- "There should be a specific amount of visbile items in the requests menu.");
-
- for (let i = 0; i < order.length; i++) {
- is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
- "The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
- }
-
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i]),
- "GET", CONTENT_TYPE_SJS + "?fmt=html", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "html",
- fullMimeType: "text/html; charset=utf-8"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len]),
- "GET", CONTENT_TYPE_SJS + "?fmt=css", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "css",
- fullMimeType: "text/css; charset=utf-8"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 2]),
- "GET", CONTENT_TYPE_SJS + "?fmt=js", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "js",
- fullMimeType: "application/javascript; charset=utf-8"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 3]),
- "GET", CONTENT_TYPE_SJS + "?fmt=font", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "woff",
- fullMimeType: "font/woff"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 4]),
- "GET", CONTENT_TYPE_SJS + "?fmt=image", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "png",
- fullMimeType: "image/png"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 5]),
- "GET", CONTENT_TYPE_SJS + "?fmt=audio", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "ogg",
- fullMimeType: "audio/ogg"
- });
- }
- for (let i = 0, len = order.length / 7; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 6]),
- "GET", CONTENT_TYPE_SJS + "?fmt=video", {
- fuzzyUrl: true,
- status: 200,
- statusText: "OK",
- type: "webm",
- fullMimeType: "video/webm"
- });
- }
+ "There should be a specific amount of visible items in the requests menu.");
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_footer-summary.js b/devtools/client/netmonitor/test/browser_net_footer-summary.js
index ac0a5b05ba23..cca08eff1fad 100644
--- a/devtools/client/netmonitor/test/browser_net_footer-summary.js
+++ b/devtools/client/netmonitor/test/browser_net_footer-summary.js
@@ -4,8 +4,7 @@
"use strict";
/**
- * Test if the summary text displayed in the network requests menu footer
- * is correct.
+ * Test if the summary text displayed in the network requests menu footer is correct.
*/
add_task(function* () {
@@ -14,13 +13,13 @@ add_task(function* () {
let { tab, monitor } = yield initNetMonitor(FILTERING_URL);
info("Starting test... ");
- let { $, NetMonitorView, gStore } = monitor.panelWin;
+ let { $, NetMonitorView, gStore, windowRequire } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
- let winRequire = monitor.panelWin.require;
- let { getSummary } = winRequire("devtools/client/netmonitor/selectors/index");
- let { L10N } = winRequire("devtools/client/netmonitor/l10n");
- let { PluralForm } = winRequire("devtools/shared/plural-form");
+ let { getDisplayedRequestsSummary } =
+ windowRequire("devtools/client/netmonitor/selectors/index");
+ let { L10N } = windowRequire("devtools/client/netmonitor/l10n");
+ let { PluralForm } = windowRequire("devtools/shared/plural-form");
RequestsMenu.lazyUpdate = false;
testStatus();
@@ -46,26 +45,27 @@ add_task(function* () {
yield teardown(monitor);
function testStatus() {
- const { count, totalBytes, totalMillis } = getSummary(gStore.getState());
let value = $("#requests-menu-network-summary-button").textContent;
info("Current summary: " + value);
- let totalRequestsCount = RequestsMenu.itemCount;
- info("Current requests: " + count + " of " + totalRequestsCount + ".");
+ let state = gStore.getState();
+ let totalRequestsCount = state.requests.requests.size;
+ let requestsSummary = getDisplayedRequestsSummary(state);
+ info(`Current requests: ${requestsSummary.count} of ${totalRequestsCount}.`);
- if (!totalRequestsCount || !count) {
+ if (!totalRequestsCount || !requestsSummary.count) {
is(value, L10N.getStr("networkMenu.empty"),
"The current summary text is incorrect, expected an 'empty' label.");
return;
}
- info("Computed total bytes: " + totalBytes);
- info("Computed total millis: " + totalMillis);
+ info(`Computed total bytes: ${requestsSummary.bytes}`);
+ info(`Computed total millis: ${requestsSummary.millis}`);
- is(value, PluralForm.get(count, L10N.getStr("networkMenu.summary"))
- .replace("#1", count)
- .replace("#2", L10N.numberWithDecimals((totalBytes || 0) / 1024, 2))
- .replace("#3", L10N.numberWithDecimals((totalMillis || 0) / 1000, 2))
- , "The current summary text is incorrect.");
+ is(value, PluralForm.get(requestsSummary.count, L10N.getStr("networkMenu.summary"))
+ .replace("#1", requestsSummary.count)
+ .replace("#2", L10N.numberWithDecimals(requestsSummary.bytes / 1024, 2))
+ .replace("#3", L10N.numberWithDecimals(requestsSummary.millis / 1000, 2))
+ , "The current summary text is correct.");
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_frame.js b/devtools/client/netmonitor/test/browser_net_frame.js
index eeded652ba72..d26958c6e4b0 100644
--- a/devtools/client/netmonitor/test/browser_net_frame.js
+++ b/devtools/client/netmonitor/test/browser_net_frame.js
@@ -17,7 +17,7 @@ const EXPECTED_REQUESTS_TOP = [
method: "GET",
url: TOP_URL,
causeType: "document",
- causeUri: "",
+ causeUri: null,
stack: true
},
{
@@ -176,9 +176,8 @@ add_task(function* () {
for (let i = 0; i < REQUEST_COUNT; i++) {
let requestItem = RequestsMenu.getItemAtIndex(i);
- let itemUrl = requestItem.attachment.url;
- let itemCauseUri = requestItem.target.querySelector(".requests-menu-cause-label")
- .getAttribute("tooltiptext");
+ let itemUrl = requestItem.url;
+ let itemCauseUri = requestItem.cause.loadingDocumentUri;
let spec;
if (itemUrl == SUB_URL || itemCauseUri == SUB_URL) {
spec = EXPECTED_REQUESTS_SUB[currentSub++];
@@ -187,11 +186,11 @@ add_task(function* () {
}
let { method, url, causeType, causeUri, stack } = spec;
- verifyRequestItemTarget(requestItem,
+ verifyRequestItemTarget(RequestsMenu, requestItem,
method, url, { cause: { type: causeType, loadingDocumentUri: causeUri } }
);
- let { stacktrace } = requestItem.attachment.cause;
+ let { stacktrace } = requestItem.cause;
let stackLen = stacktrace ? stacktrace.length : 0;
if (stack) {
diff --git a/devtools/client/netmonitor/test/browser_net_icon-preview.js b/devtools/client/netmonitor/test/browser_net_icon-preview.js
index e3c5bde4e072..00a491bf5a2b 100644
--- a/devtools/client/netmonitor/test/browser_net_icon-preview.js
+++ b/devtools/client/netmonitor/test/browser_net_icon-preview.js
@@ -24,7 +24,7 @@ add_task(function* () {
info("Checking the image thumbnail when all items are shown.");
checkImageThumbnail();
- RequestsMenu.sortBy("size");
+ gStore.dispatch(Actions.sortBy("size"));
info("Checking the image thumbnail when all items are sorted.");
checkImageThumbnail();
@@ -61,11 +61,11 @@ add_task(function* () {
}
function checkImageThumbnail() {
- is($all(".requests-menu-icon[type=thumbnail]").length, 1,
+ is($all(".requests-menu-icon[data-type=thumbnail]").length, 1,
"There should be only one image request with a thumbnail displayed.");
- is($(".requests-menu-icon[type=thumbnail]").src, TEST_IMAGE_DATA_URI,
+ is($(".requests-menu-icon[data-type=thumbnail]").src, TEST_IMAGE_DATA_URI,
"The image requests-menu-icon thumbnail is displayed correctly.");
- is($(".requests-menu-icon[type=thumbnail]").hidden, false,
+ is($(".requests-menu-icon[data-type=thumbnail]").hidden, false,
"The image requests-menu-icon thumbnail should not be hidden.");
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_image-tooltip.js b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
index 04cd26959928..aa9fe3f27628 100644
--- a/devtools/client/netmonitor/test/browser_net_image-tooltip.js
+++ b/devtools/client/netmonitor/test/browser_net_image-tooltip.js
@@ -26,12 +26,12 @@ add_task(function* test() {
yield onThumbnail;
info("Checking the image thumbnail after a few requests were made...");
- yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
+ yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(0));
// Hide tooltip before next test, to avoid the situation that tooltip covers
// the icon for the request of the next test.
info("Checking the image thumbnail gets hidden...");
- yield hideTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[0]);
+ yield hideTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(0));
// +1 extra document reload
onEvents = waitForNetworkEvents(monitor, IMAGE_TOOLTIP_REQUESTS + 1);
@@ -44,10 +44,10 @@ add_task(function* test() {
yield onThumbnail;
info("Checking the image thumbnail after a reload.");
- yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.items[1]);
+ yield showTooltipAndVerify(RequestsMenu.tooltip, RequestsMenu.getItemAtIndex(1));
info("Checking if the image thumbnail is hidden when mouse leaves the menu widget");
- let requestsMenuEl = $("#requests-menu-contents");
+ let requestsMenuEl = $(".requests-menu-contents");
let onHidden = RequestsMenu.tooltip.once("hidden");
EventUtils.synthesizeMouse(requestsMenuEl, 0, 0, {type: "mouseout"}, monitor.panelWin);
yield onHidden;
@@ -65,7 +65,7 @@ add_task(function* test() {
* with the expected content.
*/
function* showTooltipAndVerify(tooltip, requestItem) {
- let anchor = $(".requests-menu-file", requestItem.target);
+ let anchor = $(".requests-menu-file", getItemTarget(RequestsMenu, requestItem));
yield showTooltipOn(tooltip, anchor);
info("Tooltip was successfully opened for the image request.");
@@ -88,13 +88,13 @@ add_task(function* test() {
* Hide a tooltip on the {requestItem} and verify that it was closed.
*/
function* hideTooltipAndVerify(tooltip, requestItem) {
- // Hovering method hides tooltip.
- let anchor = $(".requests-menu-method", requestItem.target);
+ // Hovering over the "method" column hides the tooltip.
+ let anchor = $(".requests-menu-method", getItemTarget(RequestsMenu, requestItem));
- let onHidden = tooltip.once("hidden");
+ let onTooltipHidden = tooltip.once("hidden");
let win = anchor.ownerDocument.defaultView;
EventUtils.synthesizeMouseAtCenter(anchor, {type: "mousemove"}, win);
- yield onHidden;
+ yield onTooltipHidden;
info("Tooltip was successfully closed.");
}
diff --git a/devtools/client/netmonitor/test/browser_net_json-long.js b/devtools/client/netmonitor/test/browser_net_json-long.js
index 2347d26c4318..a0cedaa0073e 100644
--- a/devtools/client/netmonitor/test/browser_net_json-long.js
+++ b/devtools/client/netmonitor/test/browser_net_json-long.js
@@ -28,7 +28,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_json-malformed.js b/devtools/client/netmonitor/test/browser_net_json-malformed.js
index 6bed60480078..72a5bafb3bf5 100644
--- a/devtools/client/netmonitor/test/browser_net_json-malformed.js
+++ b/devtools/client/netmonitor/test/browser_net_json-malformed.js
@@ -22,7 +22,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=json-malformed", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
index 210ffbbe8b82..b478d250ad77 100644
--- a/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_custom_mime.js
@@ -24,7 +24,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=json-custom-mime", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_json_text_mime.js b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
index edc98a5c9657..5cabf7b49e9e 100644
--- a/devtools/client/netmonitor/test/browser_net_json_text_mime.js
+++ b/devtools/client/netmonitor/test/browser_net_json_text_mime.js
@@ -24,7 +24,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=json-text-mime", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_jsonp.js b/devtools/client/netmonitor/test/browser_net_jsonp.js
index 3007d8c4dcdc..2567a4da74eb 100644
--- a/devtools/client/netmonitor/test/browser_net_jsonp.js
+++ b/devtools/client/netmonitor/test/browser_net_jsonp.js
@@ -25,7 +25,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=jsonp&jsonp=$_0123Fun", {
status: 200,
statusText: "OK",
@@ -34,7 +34,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 41),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
"GET", CONTENT_TYPE_SJS + "?fmt=jsonp2&jsonp=$_4567Sad", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_large-response.js b/devtools/client/netmonitor/test/browser_net_large-response.js
index 98d67b46d17e..6e435239fb7b 100644
--- a/devtools/client/netmonitor/test/browser_net_large-response.js
+++ b/devtools/client/netmonitor/test/browser_net_large-response.js
@@ -28,7 +28,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"GET", CONTENT_TYPE_SJS + "?fmt=html-long", {
status: 200,
statusText: "OK"
diff --git a/devtools/client/netmonitor/test/browser_net_page-nav.js b/devtools/client/netmonitor/test/browser_net_page-nav.js
index 6ac18297c4a5..4e99b8e40b2f 100644
--- a/devtools/client/netmonitor/test/browser_net_page-nav.js
+++ b/devtools/client/netmonitor/test/browser_net_page-nav.js
@@ -59,11 +59,11 @@ add_task(function* () {
removeTab(tab);
yield onDestroyed;
- ok(!monitor._controller.client,
+ ok(!monitor.panelWin.NetMonitorController.client,
"There shouldn't be a client available after destruction.");
- ok(!monitor._controller.tabClient,
+ ok(!monitor.panelWin.NetMonitorController.tabClient,
"There shouldn't be a tabClient available after destruction.");
- ok(!monitor._controller.webConsoleClient,
+ ok(!monitor.panelWin.NetMonitorController.webConsoleClient,
"There shouldn't be a webConsoleClient available after destruction.");
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_post-data-01.js b/devtools/client/netmonitor/test/browser_net_post-data-01.js
index 6d5f8dc1b461..0fb3718e3def 100644
--- a/devtools/client/netmonitor/test/browser_net_post-data-01.js
+++ b/devtools/client/netmonitor/test/browser_net_post-data-01.js
@@ -25,7 +25,7 @@ add_task(function* () {
});
yield wait;
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(0),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(0),
"POST", SIMPLE_SJS + "?foo=bar&baz=42&type=urlencoded", {
status: 200,
statusText: "Och Aye",
@@ -34,7 +34,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(1),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(1),
"POST", SIMPLE_SJS + "?foo=bar&baz=42&type=multipart", {
status: 200,
statusText: "Och Aye",
diff --git a/devtools/client/netmonitor/test/browser_net_prefs-reload.js b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
index ee56ee446179..17094bd6baee 100644
--- a/devtools/client/netmonitor/test/browser_net_prefs-reload.js
+++ b/devtools/client/netmonitor/test/browser_net_prefs-reload.js
@@ -9,6 +9,8 @@
add_task(function* () {
let Actions = require("devtools/client/netmonitor/actions/index");
+ let { getActiveFilters } = require("devtools/client/netmonitor/selectors/index");
+
let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
@@ -19,8 +21,8 @@ add_task(function* () {
// Use these getters instead of caching instances inside the panel win,
// since the tool is reopened a bunch of times during this test
// and the instances will differ.
- let getView = () => monitor.panelWin.NetMonitorView;
let getStore = () => monitor.panelWin.gStore;
+ let getState = () => getStore().getState();
let prefsToCheck = {
filters: {
@@ -28,7 +30,7 @@ add_task(function* () {
newValue: ["html", "css"],
// Getter used to retrieve the current value from the frontend, in order
// to verify that the pref was applied properly.
- validateValue: ($) => getView().RequestsMenu._activeFilters,
+ validateValue: ($) => getActiveFilters(getState()),
// Predicate used to modify the frontend when setting the new pref value,
// before trying to validate the changes.
modifyFrontend: ($, value) => value.forEach(e =>
diff --git a/devtools/client/netmonitor/test/browser_net_raw_headers.js b/devtools/client/netmonitor/test/browser_net_raw_headers.js
index 2cb734745c0b..2b4b8b9b50c3 100644
--- a/devtools/client/netmonitor/test/browser_net_raw_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_raw_headers.js
@@ -31,7 +31,7 @@ add_task(function* () {
EventUtils.sendMouseEvent({ type: "click" },
document.getElementById("toggle-raw-headers"));
- testShowRawHeaders(origItem.attachment);
+ testShowRawHeaders(origItem);
EventUtils.sendMouseEvent({ type: "click" },
document.getElementById("toggle-raw-headers"));
@@ -46,12 +46,12 @@ add_task(function* () {
function testShowRawHeaders(data) {
let requestHeaders = document.getElementById("raw-request-headers-textarea").value;
for (let header of data.requestHeaders.headers) {
- ok(requestHeaders.indexOf(header.name + ": " + header.value) >= 0,
+ ok(requestHeaders.includes(header.name + ": " + header.value),
"textarea contains request headers");
}
let responseHeaders = document.getElementById("raw-response-headers-textarea").value;
for (let header of data.responseHeaders.headers) {
- ok(responseHeaders.indexOf(header.name + ": " + header.value) >= 0,
+ ok(responseHeaders.includes(header.name + ": " + header.value),
"textarea contains response headers");
}
}
diff --git a/devtools/client/netmonitor/test/browser_net_reload-button.js b/devtools/client/netmonitor/test/browser_net_reload-button.js
index e91de8302a1c..02cd4d406623 100644
--- a/devtools/client/netmonitor/test/browser_net_reload-button.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-button.js
@@ -8,18 +8,18 @@
*/
add_task(function* () {
- let { monitor } = yield initNetMonitor(SINGLE_GET_URL);
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { document, NetMonitorView } = monitor.panelWin;
let { RequestsMenu } = NetMonitorView;
- let wait = waitForNetworkEvents(monitor, 2);
+ let wait = waitForNetworkEvents(monitor, 1);
let button = document.querySelector("#requests-menu-reload-notice-button");
button.click();
yield wait;
- is(RequestsMenu.itemCount, 2, "The request menu should have two items after reloading");
+ is(RequestsMenu.itemCount, 1, "The request menu should have one item after reloading");
return teardown(monitor);
});
diff --git a/devtools/client/netmonitor/test/browser_net_reload-markers.js b/devtools/client/netmonitor/test/browser_net_reload-markers.js
index 26866830ff65..cdba1e672ba4 100644
--- a/devtools/client/netmonitor/test/browser_net_reload-markers.js
+++ b/devtools/client/netmonitor/test/browser_net_reload-markers.js
@@ -8,7 +8,7 @@
*/
add_task(function* () {
- let { monitor } = yield initNetMonitor(SINGLE_GET_URL);
+ let { monitor } = yield initNetMonitor(SIMPLE_URL);
info("Starting test... ");
let { document, EVENTS } = monitor.panelWin;
@@ -21,7 +21,7 @@ add_task(function* () {
markers.push(marker);
});
- yield waitForNetworkEvents(monitor, 2);
+ yield waitForNetworkEvents(monitor, 1);
yield waitUntil(() => markers.length == 2);
ok(true, "Reloading finished");
diff --git a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
index 71a9135019ab..1b4f1ae5cfb2 100644
--- a/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
+++ b/devtools/client/netmonitor/test/browser_net_req-resp-bodies.js
@@ -54,7 +54,7 @@ add_task(function* () {
return teardown(monitor);
function verifyRequest(offset) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(offset),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(offset),
"GET", CONTENT_TYPE_SJS + "?fmt=json-long", {
status: 200,
statusText: "OK",
diff --git a/devtools/client/netmonitor/test/browser_net_resend.js b/devtools/client/netmonitor/test/browser_net_resend.js
index 8e42ef59ca41..1a1665e3efd9 100644
--- a/devtools/client/netmonitor/test/browser_net_resend.js
+++ b/devtools/client/netmonitor/test/browser_net_resend.js
@@ -39,13 +39,15 @@ add_task(function* () {
RequestsMenu.cloneSelectedRequest();
yield onPopulated;
- testCustomForm(origItem.attachment);
+ testCustomForm(origItem);
let customItem = RequestsMenu.selectedItem;
testCustomItem(customItem, origItem);
// edit the custom request
yield editCustomForm();
+ // FIXME: reread the customItem, it's been replaced by a new object (immutable!)
+ customItem = RequestsMenu.selectedItem;
testCustomItemChanged(customItem, origItem);
// send the new request
@@ -54,30 +56,20 @@ add_task(function* () {
yield wait;
let sentItem = RequestsMenu.selectedItem;
- testSentRequest(sentItem.attachment, origItem.attachment);
+ testSentRequest(sentItem, origItem);
return teardown(monitor);
function testCustomItem(item, orig) {
- let method = item.target.querySelector(".requests-menu-method").value;
- let origMethod = orig.target.querySelector(".requests-menu-method").value;
- is(method, origMethod, "menu item is showing the same method as original request");
-
- let file = item.target.querySelector(".requests-menu-file").value;
- let origFile = orig.target.querySelector(".requests-menu-file").value;
- is(file, origFile, "menu item is showing the same file name as original request");
-
- let domain = item.target.querySelector(".requests-menu-domain").value;
- let origDomain = orig.target.querySelector(".requests-menu-domain").value;
- is(domain, origDomain, "menu item is showing the same domain as original request");
+ is(item.method, orig.method, "item is showing the same method as original request");
+ is(item.url, orig.url, "item is showing the same URL as original request");
}
function testCustomItemChanged(item, orig) {
- let file = item.target.querySelector(".requests-menu-file").value;
- let expectedFile = orig.target.querySelector(".requests-menu-file").value +
- "&" + ADD_QUERY;
+ let url = item.url;
+ let expectedUrl = orig.url + "&" + ADD_QUERY;
- is(file, expectedFile, "menu item is updated to reflect url entered in form");
+ is(url, expectedUrl, "menu item is updated to reflect url entered in form");
}
/*
diff --git a/devtools/client/netmonitor/test/browser_net_resend_cors.js b/devtools/client/netmonitor/test/browser_net_resend_cors.js
index d63c3b54e585..bd7386895e7d 100644
--- a/devtools/client/netmonitor/test/browser_net_resend_cors.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_cors.js
@@ -30,9 +30,9 @@ add_task(function* () {
// Check the requests that were sent
for (let [i, method] of METHODS.entries()) {
- let { attachment } = RequestsMenu.getItemAtIndex(i);
- is(attachment.method, method, `The ${method} request has the right method`);
- is(attachment.url, requestUrl, `The ${method} request has the right URL`);
+ let item = RequestsMenu.getItemAtIndex(i);
+ is(item.method, method, `The ${method} request has the right method`);
+ is(item.url, requestUrl, `The ${method} request has the right URL`);
}
// Resend both requests without modification. Wait for resent OPTIONS, then POST.
@@ -61,7 +61,7 @@ add_task(function* () {
// Check the resent requests
for (let [i, method] of METHODS.entries()) {
let index = i + 2;
- let item = RequestsMenu.getItemAtIndex(index).attachment;
+ let item = RequestsMenu.getItemAtIndex(index);
is(item.method, method, `The ${method} request has the right method`);
is(item.url, requestUrl, `The ${method} request has the right URL`);
is(item.status, 200, `The ${method} response has the right status`);
diff --git a/devtools/client/netmonitor/test/browser_net_resend_headers.js b/devtools/client/netmonitor/test/browser_net_resend_headers.js
index 0503817e35fb..9aa46a9c9496 100644
--- a/devtools/client/netmonitor/test/browser_net_resend_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_resend_headers.js
@@ -35,21 +35,21 @@ add_task(function* () {
});
yield wait;
- let { attachment } = RequestsMenu.getItemAtIndex(0);
- is(attachment.method, "POST", "The request has the right method");
- is(attachment.url, requestUrl, "The request has the right URL");
+ let item = RequestsMenu.getItemAtIndex(0);
+ is(item.method, "POST", "The request has the right method");
+ is(item.url, requestUrl, "The request has the right URL");
- for (let { name, value } of attachment.requestHeaders.headers) {
+ for (let { name, value } of item.requestHeaders.headers) {
info(`Request header: ${name}: ${value}`);
}
function hasRequestHeader(name, value) {
- let { headers } = attachment.requestHeaders;
+ let { headers } = item.requestHeaders;
return headers.some(h => h.name === name && h.value === value);
}
function hasNotRequestHeader(name) {
- let { headers } = attachment.requestHeaders;
+ let { headers } = item.requestHeaders;
return headers.every(h => h.name !== name);
}
diff --git a/devtools/client/netmonitor/test/browser_net_security-icon-click.js b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
index a7822e5720f2..d8266be7886b 100644
--- a/devtools/client/netmonitor/test/browser_net_security-icon-click.js
+++ b/devtools/client/netmonitor/test/browser_net_security-icon-click.js
@@ -42,13 +42,13 @@ add_task(function* () {
}
function* clickAndTestSecurityIcon() {
- let item = RequestsMenu.items[0];
- let icon = $(".requests-security-state-icon", item.target);
+ let item = RequestsMenu.getItemAtIndex(0);
+ let target = getItemTarget(RequestsMenu, item);
+ let icon = $(".requests-security-state-icon", target);
- info("Clicking security icon of the first request and waiting for the " +
- "panel to update.");
+ info("Clicking security icon of the first request and waiting for panel update.");
+ EventUtils.synthesizeMouseAtCenter(icon, {}, monitor.panelWin);
- icon.click();
yield monitor.panelWin.once(EVENTS.TAB_UPDATED);
is(NetworkDetails.widget.selectedPanel, $("#security-tabpanel"),
diff --git a/devtools/client/netmonitor/test/browser_net_security-redirect.js b/devtools/client/netmonitor/test/browser_net_security-redirect.js
index 157eb84ec876..11280223308a 100644
--- a/devtools/client/netmonitor/test/browser_net_security-redirect.js
+++ b/devtools/client/netmonitor/test/browser_net_security-redirect.js
@@ -22,11 +22,13 @@ add_task(function* () {
is(RequestsMenu.itemCount, 2, "There were two requests due to redirect.");
- let initial = RequestsMenu.items[0];
- let redirect = RequestsMenu.items[1];
+ let initial = RequestsMenu.getItemAtIndex(0);
+ let redirect = RequestsMenu.getItemAtIndex(1);
- let initialSecurityIcon = $(".requests-security-state-icon", initial.target);
- let redirectSecurityIcon = $(".requests-security-state-icon", redirect.target);
+ let initialSecurityIcon =
+ $(".requests-security-state-icon", getItemTarget(RequestsMenu, initial));
+ let redirectSecurityIcon =
+ $(".requests-security-state-icon", getItemTarget(RequestsMenu, redirect));
ok(initialSecurityIcon.classList.contains("security-state-insecure"),
"Initial request was marked insecure.");
diff --git a/devtools/client/netmonitor/test/browser_net_security-state.js b/devtools/client/netmonitor/test/browser_net_security-state.js
index a9d14f688bad..9a91617b21df 100644
--- a/devtools/client/netmonitor/test/browser_net_security-state.js
+++ b/devtools/client/netmonitor/test/browser_net_security-state.js
@@ -24,12 +24,13 @@ add_task(function* () {
yield performRequests();
for (let item of RequestsMenu.items) {
- let domain = $(".requests-menu-domain", item.target).value;
+ let target = getItemTarget(RequestsMenu, item);
+ let domain = $(".requests-menu-domain", target).textContent;
info("Found a request to " + domain);
ok(domain in EXPECTED_SECURITY_STATES, "Domain " + domain + " was expected.");
- let classes = $(".requests-security-state-icon", item.target).classList;
+ let classes = $(".requests-security-state-icon", target).classList;
let expectedClass = EXPECTED_SECURITY_STATES[domain];
info("Classes of security state icon are: " + classes);
diff --git a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
index ebfc8449e548..1ce8d397b77c 100644
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -58,7 +58,7 @@ add_task(function* () {
info("Selecting the request.");
RequestsMenu.selectedIndex = 0;
- is(RequestsMenu.selectedItem.attachment.securityState, undefined,
+ is(RequestsMenu.selectedItem.securityState, undefined,
"Security state has not yet arrived.");
is(tabEl.hidden, !testcase.visibleOnNewEvent,
"Security tab is " +
@@ -70,7 +70,7 @@ add_task(function* () {
info("Waiting for security information to arrive.");
yield onSecurityInfo;
- ok(RequestsMenu.selectedItem.attachment.securityState,
+ ok(RequestsMenu.selectedItem.securityState,
"Security state arrived.");
is(tabEl.hidden, !testcase.visibleOnSecurityInfo,
"Security tab is " +
diff --git a/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js b/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
index b425ad5ca930..5e566c134078 100644
--- a/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon-other-tab.js
@@ -26,8 +26,8 @@ add_task(function* () {
is(RequestsMenu.itemCount, 1, "Only the reload should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
- is(request.attachment.method, "GET", "The method is correct.");
- is(request.attachment.status, "200", "The status is correct.");
+ is(request.method, "GET", "The method is correct.");
+ is(request.status, "200", "The status is correct.");
yield removeTab(beaconTab);
return teardown(monitor);
diff --git a/devtools/client/netmonitor/test/browser_net_send-beacon.js b/devtools/client/netmonitor/test/browser_net_send-beacon.js
index bdc30a960646..348a8f0ae7c6 100644
--- a/devtools/client/netmonitor/test/browser_net_send-beacon.js
+++ b/devtools/client/netmonitor/test/browser_net_send-beacon.js
@@ -23,9 +23,9 @@ add_task(function* () {
is(RequestsMenu.itemCount, 1, "The beacon should be recorded.");
let request = RequestsMenu.getItemAtIndex(0);
- is(request.attachment.method, "POST", "The method is correct.");
- ok(request.attachment.url.endsWith("beacon_request"), "The URL is correct.");
- is(request.attachment.status, "404", "The status is correct.");
+ is(request.method, "POST", "The method is correct.");
+ ok(request.url.endsWith("beacon_request"), "The URL is correct.");
+ is(request.status, "404", "The status is correct.");
return teardown(monitor);
});
diff --git a/devtools/client/netmonitor/test/browser_net_service-worker-status.js b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
index 146d090805d8..213e4ba9487e 100644
--- a/devtools/client/netmonitor/test/browser_net_service-worker-status.js
+++ b/devtools/client/netmonitor/test/browser_net_service-worker-status.js
@@ -51,9 +51,10 @@ add_task(function* () {
let item = RequestsMenu.getItemAtIndex(index);
info(`Verifying request #${index}`);
- yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+ yield verifyRequestItemTarget(RequestsMenu, item,
+ request.method, request.uri, request.details);
- let { stacktrace } = item.attachment.cause;
+ let { stacktrace } = item.cause;
let stackLen = stacktrace ? stacktrace.length : 0;
ok(stacktrace, `Request #${index} has a stacktrace`);
diff --git a/devtools/client/netmonitor/test/browser_net_simple-init.js b/devtools/client/netmonitor/test/browser_net_simple-init.js
index 19d05811c3dc..ea25aa104217 100644
--- a/devtools/client/netmonitor/test/browser_net_simple-init.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-init.js
@@ -22,50 +22,50 @@ function test() {
function checkIfInitialized(tag) {
info(`Checking if initialization is ok (${tag}).`);
- ok(monitor._view,
+ ok(monitor.panelWin.NetMonitorView,
`The network monitor view object exists (${tag}).`);
- ok(monitor._controller,
+ ok(monitor.panelWin.NetMonitorController,
`The network monitor controller object exists (${tag}).`);
- ok(monitor._controller._startup,
+ ok(monitor.panelWin.NetMonitorController._startup,
`The network monitor controller object exists and is initialized (${tag}).`);
ok(monitor.isReady,
`The network monitor panel appears to be ready (${tag}).`);
- ok(monitor._controller.tabClient,
+ ok(monitor.panelWin.NetMonitorController.tabClient,
`There should be a tabClient available at this point (${tag}).`);
- ok(monitor._controller.webConsoleClient,
+ ok(monitor.panelWin.NetMonitorController.webConsoleClient,
`There should be a webConsoleClient available at this point (${tag}).`);
- ok(monitor._controller.timelineFront,
+ ok(monitor.panelWin.NetMonitorController.timelineFront,
`There should be a timelineFront available at this point (${tag}).`);
}
function checkIfDestroyed(tag) {
gInfo("Checking if destruction is ok.");
- gOk(monitor._view,
+ gOk(monitor.panelWin.NetMonitorView,
`The network monitor view object still exists (${tag}).`);
- gOk(monitor._controller,
+ gOk(monitor.panelWin.NetMonitorController,
`The network monitor controller object still exists (${tag}).`);
- gOk(monitor._controller._shutdown,
+ gOk(monitor.panelWin.NetMonitorController._shutdown,
`The network monitor controller object still exists and is destroyed (${tag}).`);
- gOk(!monitor._controller.tabClient,
+ gOk(!monitor.panelWin.NetMonitorController.tabClient,
`There shouldn't be a tabClient available after destruction (${tag}).`);
- gOk(!monitor._controller.webConsoleClient,
+ gOk(!monitor.panelWin.NetMonitorController.webConsoleClient,
`There shouldn't be a webConsoleClient available after destruction (${tag}).`);
- gOk(!monitor._controller.timelineFront,
+ gOk(!monitor.panelWin.NetMonitorController.timelineFront,
`There shouldn't be a timelineFront available after destruction (${tag}).`);
}
executeSoon(() => {
checkIfInitialized(1);
- monitor._controller.startupNetMonitor()
+ monitor.panelWin.NetMonitorController.startupNetMonitor()
.then(() => {
info("Starting up again shouldn't do anything special.");
checkIfInitialized(2);
- return monitor._controller.connect();
+ return monitor.panelWin.NetMonitorController.connect();
})
.then(() => {
info("Connecting again shouldn't do anything special.");
@@ -78,11 +78,11 @@ function test() {
registerCleanupFunction(() => {
checkIfDestroyed(1);
- monitor._controller.shutdownNetMonitor()
+ monitor.panelWin.NetMonitorController.shutdownNetMonitor()
.then(() => {
gInfo("Shutting down again shouldn't do anything special.");
checkIfDestroyed(2);
- return monitor._controller.disconnect();
+ return monitor.panelWin.NetMonitorController.disconnect();
})
.then(() => {
gInfo("Disconnecting again shouldn't do anything special.");
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-data.js b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
index 1b952bd7198f..f262e02812bb 100644
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -32,69 +32,63 @@ function test() {
let requestItem = RequestsMenu.getItemAtIndex(0);
- is(typeof requestItem.value, "string",
+ is(typeof requestItem.id, "string",
"The attached request id is incorrect.");
- isnot(requestItem.value, "",
+ isnot(requestItem.id, "",
"The attached request id should not be empty.");
- is(typeof requestItem.attachment.startedDeltaMillis, "number",
- "The attached startedDeltaMillis is incorrect.");
- is(requestItem.attachment.startedDeltaMillis, 0,
- "The attached startedDeltaMillis should be zero.");
-
- is(typeof requestItem.attachment.startedMillis, "number",
+ is(typeof requestItem.startedMillis, "number",
"The attached startedMillis is incorrect.");
- isnot(requestItem.attachment.startedMillis, 0,
+ isnot(requestItem.startedMillis, 0,
"The attached startedMillis should not be zero.");
- is(requestItem.attachment.requestHeaders, undefined,
+ is(requestItem.requestHeaders, undefined,
"The requestHeaders should not yet be set.");
- is(requestItem.attachment.requestCookies, undefined,
+ is(requestItem.requestCookies, undefined,
"The requestCookies should not yet be set.");
- is(requestItem.attachment.requestPostData, undefined,
+ is(requestItem.requestPostData, undefined,
"The requestPostData should not yet be set.");
- is(requestItem.attachment.responseHeaders, undefined,
+ is(requestItem.responseHeaders, undefined,
"The responseHeaders should not yet be set.");
- is(requestItem.attachment.responseCookies, undefined,
+ is(requestItem.responseCookies, undefined,
"The responseCookies should not yet be set.");
- is(requestItem.attachment.httpVersion, undefined,
+ is(requestItem.httpVersion, undefined,
"The httpVersion should not yet be set.");
- is(requestItem.attachment.status, undefined,
+ is(requestItem.status, undefined,
"The status should not yet be set.");
- is(requestItem.attachment.statusText, undefined,
+ is(requestItem.statusText, undefined,
"The statusText should not yet be set.");
- is(requestItem.attachment.headersSize, undefined,
+ is(requestItem.headersSize, undefined,
"The headersSize should not yet be set.");
- is(requestItem.attachment.transferredSize, undefined,
+ is(requestItem.transferredSize, undefined,
"The transferredSize should not yet be set.");
- is(requestItem.attachment.contentSize, undefined,
+ is(requestItem.contentSize, undefined,
"The contentSize should not yet be set.");
- is(requestItem.attachment.mimeType, undefined,
+ is(requestItem.mimeType, undefined,
"The mimeType should not yet be set.");
- is(requestItem.attachment.responseContent, undefined,
+ is(requestItem.responseContent, undefined,
"The responseContent should not yet be set.");
- is(requestItem.attachment.totalTime, undefined,
+ is(requestItem.totalTime, undefined,
"The totalTime should not yet be set.");
- is(requestItem.attachment.eventTimings, undefined,
+ is(requestItem.eventTimings, undefined,
"The eventTimings should not yet be set.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
});
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_HEADERS, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
-
- ok(requestItem.attachment.requestHeaders,
- "There should be a requestHeaders attachment available.");
- is(requestItem.attachment.requestHeaders.headers.length, 9,
- "The requestHeaders attachment has an incorrect |headers| property.");
- isnot(requestItem.attachment.requestHeaders.headersSize, 0,
- "The requestHeaders attachment has an incorrect |headersSize| property.");
+ ok(requestItem.requestHeaders,
+ "There should be a requestHeaders data available.");
+ is(requestItem.requestHeaders.headers.length, 10,
+ "The requestHeaders data has an incorrect |headers| property.");
+ isnot(requestItem.requestHeaders.headersSize, 0,
+ "The requestHeaders data has an incorrect |headersSize| property.");
// Can't test for the exact request headers size because the value may
// vary across platforms ("User-Agent" header differs).
@@ -104,12 +98,12 @@ function test() {
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_COOKIES, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- ok(requestItem.attachment.requestCookies,
- "There should be a requestCookies attachment available.");
- is(requestItem.attachment.requestCookies.cookies.length, 2,
- "The requestCookies attachment has an incorrect |cookies| property.");
+ ok(requestItem.requestCookies,
+ "There should be a requestCookies data available.");
+ is(requestItem.requestCookies.cookies.length, 2,
+ "The requestCookies data has an incorrect |cookies| property.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
});
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
@@ -119,40 +113,40 @@ function test() {
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_HEADERS, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- ok(requestItem.attachment.responseHeaders,
- "There should be a responseHeaders attachment available.");
- is(requestItem.attachment.responseHeaders.headers.length, 10,
- "The responseHeaders attachment has an incorrect |headers| property.");
- is(requestItem.attachment.responseHeaders.headersSize, 330,
- "The responseHeaders attachment has an incorrect |headersSize| property.");
+ ok(requestItem.responseHeaders,
+ "There should be a responseHeaders data available.");
+ is(requestItem.responseHeaders.headers.length, 10,
+ "The responseHeaders data has an incorrect |headers| property.");
+ is(requestItem.responseHeaders.headersSize, 330,
+ "The responseHeaders data has an incorrect |headersSize| property.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
});
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_COOKIES, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- ok(requestItem.attachment.responseCookies,
- "There should be a responseCookies attachment available.");
- is(requestItem.attachment.responseCookies.cookies.length, 2,
- "The responseCookies attachment has an incorrect |cookies| property.");
+ ok(requestItem.responseCookies,
+ "There should be a responseCookies data available.");
+ is(requestItem.responseCookies.cookies.length, 2,
+ "The responseCookies data has an incorrect |cookies| property.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS);
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS);
});
monitor.panelWin.once(monitor.panelWin.EVENTS.STARTED_RECEIVING_RESPONSE, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- is(requestItem.attachment.httpVersion, "HTTP/1.1",
- "The httpVersion attachment has an incorrect value.");
- is(requestItem.attachment.status, "200",
- "The status attachment has an incorrect value.");
- is(requestItem.attachment.statusText, "Och Aye",
- "The statusText attachment has an incorrect value.");
- is(requestItem.attachment.headersSize, 330,
- "The headersSize attachment has an incorrect value.");
+ is(requestItem.httpVersion, "HTTP/1.1",
+ "The httpVersion data has an incorrect value.");
+ is(requestItem.status, "200",
+ "The status data has an incorrect value.");
+ is(requestItem.statusText, "Och Aye",
+ "The statusText data has an incorrect value.");
+ is(requestItem.headersSize, 330,
+ "The headersSize data has an incorrect value.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
status: "200",
statusText: "Och Aye"
});
@@ -161,58 +155,53 @@ function test() {
monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_RESPONSE_CONTENT, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- is(requestItem.attachment.transferredSize, "12",
- "The transferredSize attachment has an incorrect value.");
- is(requestItem.attachment.contentSize, "12",
- "The contentSize attachment has an incorrect value.");
- is(requestItem.attachment.mimeType, "text/plain; charset=utf-8",
- "The mimeType attachment has an incorrect value.");
+ is(requestItem.transferredSize, "12",
+ "The transferredSize data has an incorrect value.");
+ is(requestItem.contentSize, "12",
+ "The contentSize data has an incorrect value.");
+ is(requestItem.mimeType, "text/plain; charset=utf-8",
+ "The mimeType data has an incorrect value.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
- transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
- size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
});
});
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- ok(requestItem.attachment.responseContent,
- "There should be a responseContent attachment available.");
- is(requestItem.attachment.responseContent.content.mimeType,
+ ok(requestItem.responseContent,
+ "There should be a responseContent data available.");
+ is(requestItem.responseContent.content.mimeType,
"text/plain; charset=utf-8",
- "The responseContent attachment has an incorrect |content.mimeType| property.");
- is(requestItem.attachment.responseContent.content.text,
+ "The responseContent data has an incorrect |content.mimeType| property.");
+ is(requestItem.responseContent.content.text,
"Hello world!",
- "The responseContent attachment has an incorrect |content.text| property.");
- is(requestItem.attachment.responseContent.content.size,
+ "The responseContent data has an incorrect |content.text| property.");
+ is(requestItem.responseContent.content.size,
12,
- "The responseContent attachment has an incorrect |content.size| property.");
+ "The responseContent data has an incorrect |content.size| property.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
type: "plain",
fullMimeType: "text/plain; charset=utf-8",
- transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
- size: L10N.getFormatStrWithNumbers("networkMenu.sizeKB", 0.01),
+ transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
+ size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
});
});
monitor.panelWin.once(monitor.panelWin.EVENTS.UPDATING_EVENT_TIMINGS, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- is(typeof requestItem.attachment.totalTime, "number",
+ is(typeof requestItem.totalTime, "number",
"The attached totalTime is incorrect.");
- ok(requestItem.attachment.totalTime >= 0,
+ ok(requestItem.totalTime >= 0,
"The attached totalTime should be positive.");
- is(typeof requestItem.attachment.endedMillis, "number",
- "The attached endedMillis is incorrect.");
- ok(requestItem.attachment.endedMillis >= 0,
- "The attached endedMillis should be positive.");
-
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
time: true
});
});
@@ -220,24 +209,24 @@ function test() {
monitor.panelWin.once(monitor.panelWin.EVENTS.RECEIVED_EVENT_TIMINGS, () => {
let requestItem = RequestsMenu.getItemAtIndex(0);
- ok(requestItem.attachment.eventTimings,
- "There should be a eventTimings attachment available.");
- is(typeof requestItem.attachment.eventTimings.timings.blocked, "number",
- "The eventTimings attachment has an incorrect |timings.blocked| property.");
- is(typeof requestItem.attachment.eventTimings.timings.dns, "number",
- "The eventTimings attachment has an incorrect |timings.dns| property.");
- is(typeof requestItem.attachment.eventTimings.timings.connect, "number",
- "The eventTimings attachment has an incorrect |timings.connect| property.");
- is(typeof requestItem.attachment.eventTimings.timings.send, "number",
- "The eventTimings attachment has an incorrect |timings.send| property.");
- is(typeof requestItem.attachment.eventTimings.timings.wait, "number",
- "The eventTimings attachment has an incorrect |timings.wait| property.");
- is(typeof requestItem.attachment.eventTimings.timings.receive, "number",
- "The eventTimings attachment has an incorrect |timings.receive| property.");
- is(typeof requestItem.attachment.eventTimings.totalTime, "number",
- "The eventTimings attachment has an incorrect |totalTime| property.");
+ ok(requestItem.eventTimings,
+ "There should be a eventTimings data available.");
+ is(typeof requestItem.eventTimings.timings.blocked, "number",
+ "The eventTimings data has an incorrect |timings.blocked| property.");
+ is(typeof requestItem.eventTimings.timings.dns, "number",
+ "The eventTimings data has an incorrect |timings.dns| property.");
+ is(typeof requestItem.eventTimings.timings.connect, "number",
+ "The eventTimings data has an incorrect |timings.connect| property.");
+ is(typeof requestItem.eventTimings.timings.send, "number",
+ "The eventTimings data has an incorrect |timings.send| property.");
+ is(typeof requestItem.eventTimings.timings.wait, "number",
+ "The eventTimings data has an incorrect |timings.wait| property.");
+ is(typeof requestItem.eventTimings.timings.receive, "number",
+ "The eventTimings data has an incorrect |timings.receive| property.");
+ is(typeof requestItem.eventTimings.totalTime, "number",
+ "The eventTimings data has an incorrect |totalTime| property.");
- verifyRequestItemTarget(requestItem, "GET", SIMPLE_SJS, {
+ verifyRequestItemTarget(RequestsMenu, requestItem, "GET", SIMPLE_SJS, {
time: true
});
});
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request-details.js b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
index 6be634e68400..0019e6e5ec67 100644
--- a/devtools/client/netmonitor/test/browser_net_simple-request-details.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-details.js
@@ -62,7 +62,7 @@ add_task(function* () {
"GET", "The method summary value is incorrect.");
is(tabpanel.querySelector("#headers-summary-address-value").getAttribute("value"),
"127.0.0.1:8888", "The remote address summary value is incorrect.");
- is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
+ is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("data-code"),
"200", "The status summary code is incorrect.");
is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
"200 Och Aye", "The status summary value is incorrect.");
diff --git a/devtools/client/netmonitor/test/browser_net_simple-request.js b/devtools/client/netmonitor/test/browser_net_simple-request.js
index 237ef76d7745..8a37f1f9c9fa 100644
--- a/devtools/client/netmonitor/test/browser_net_simple-request.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -23,7 +23,7 @@ add_task(function* () {
is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), true,
"The pane toggle button should be disabled when the frontend is opened.");
- is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), false,
+ ok(document.querySelector("#requests-menu-empty-notice"),
"An empty notice should be displayed when the frontend is opened.");
is(RequestsMenu.itemCount, 0,
"The requests menu should be empty when the frontend is opened.");
@@ -34,7 +34,7 @@ add_task(function* () {
is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), false,
"The pane toggle button should be enabled after the first request.");
- is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), true,
+ ok(!document.querySelector("#requests-menu-empty-notice"),
"The empty notice should be hidden after the first request.");
is(RequestsMenu.itemCount, 1,
"The requests menu should not be empty after the first request.");
@@ -45,7 +45,7 @@ add_task(function* () {
is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), false,
"The pane toggle button should be still be enabled after a reload.");
- is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), true,
+ ok(!document.querySelector("#requests-menu-empty-notice"),
"The empty notice should be still hidden after a reload.");
is(RequestsMenu.itemCount, 1,
"The requests menu should not be empty after a reload.");
@@ -56,7 +56,7 @@ add_task(function* () {
is(document.querySelector("#details-pane-toggle").hasAttribute("disabled"), true,
"The pane toggle button should be disabled when after clear.");
- is(document.querySelector("#requests-menu-empty-notice").hasAttribute("hidden"), false,
+ ok(document.querySelector("#requests-menu-empty-notice"),
"An empty notice should be displayed again after clear.");
is(RequestsMenu.itemCount, 0,
"The requests menu should be empty after clear.");
diff --git a/devtools/client/netmonitor/test/browser_net_sort-01.js b/devtools/client/netmonitor/test/browser_net_sort-01.js
index c738be57eb79..30d9b88a6c41 100644
--- a/devtools/client/netmonitor/test/browser_net_sort-01.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-01.js
@@ -162,21 +162,10 @@ add_task(function* () {
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
"There should be a total of 5 visbile items in the requests menu.");
- is($all(".side-menu-widget-item").length, 5,
+ is($all(".request-list-item").length, 5,
"The visible items in the requests menu are, in fact, visible!");
- is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
- "The requests menu items aren't ordered correctly. First item is misplaced.");
- is(RequestsMenu.getItemAtIndex(1), RequestsMenu.items[1],
- "The requests menu items aren't ordered correctly. Second item is misplaced.");
- is(RequestsMenu.getItemAtIndex(2), RequestsMenu.items[2],
- "The requests menu items aren't ordered correctly. Third item is misplaced.");
- is(RequestsMenu.getItemAtIndex(3), RequestsMenu.items[3],
- "The requests menu items aren't ordered correctly. Fourth item is misplaced.");
- is(RequestsMenu.getItemAtIndex(4), RequestsMenu.items[4],
- "The requests menu items aren't ordered correctly. Fifth item is misplaced.");
-
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(a),
"GET", STATUS_CODES_SJS + "?sts=100", {
status: 101,
statusText: "Switching Protocols",
@@ -186,7 +175,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(b),
"GET", STATUS_CODES_SJS + "?sts=200", {
status: 202,
statusText: "Created",
@@ -196,7 +185,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(c),
"GET", STATUS_CODES_SJS + "?sts=300", {
status: 303,
statusText: "See Other",
@@ -206,7 +195,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(d),
"GET", STATUS_CODES_SJS + "?sts=400", {
status: 404,
statusText: "Not Found",
@@ -216,7 +205,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 22),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(e),
"GET", STATUS_CODES_SJS + "?sts=500", {
status: 501,
statusText: "Not Implemented",
diff --git a/devtools/client/netmonitor/test/browser_net_sort-02.js b/devtools/client/netmonitor/test/browser_net_sort-02.js
index ce8c69e45511..65e9a367340d 100644
--- a/devtools/client/netmonitor/test/browser_net_sort-02.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-02.js
@@ -172,17 +172,17 @@ add_task(function* () {
for (let header of headers) {
if (header != target) {
- is(header.hasAttribute("sorted"), false,
- "The " + header.id + " header should not have a 'sorted' attribute.");
- is(header.hasAttribute("tooltiptext"), false,
- "The " + header.id + " header should not have a 'tooltiptext' attribute.");
+ ok(!header.hasAttribute("data-sorted"),
+ "The " + header.id + " header does not have a 'data-sorted' attribute.");
+ ok(!header.getAttribute("title"),
+ "The " + header.id + " header does not have a 'title' attribute.");
} else {
- is(header.getAttribute("sorted"), direction,
- "The " + header.id + " header has an incorrect 'sorted' attribute.");
- is(header.getAttribute("tooltiptext"), direction == "ascending"
+ is(header.getAttribute("data-sorted"), direction,
+ "The " + header.id + " header has a correct 'data-sorted' attribute.");
+ is(header.getAttribute("title"), direction == "ascending"
? L10N.getStr("networkMenu.sortedAsc")
: L10N.getStr("networkMenu.sortedDesc"),
- "The " + header.id + " has an incorrect 'tooltiptext' attribute.");
+ "The " + header.id + " header has a correct 'title' attribute.");
}
}
}
@@ -198,22 +198,11 @@ add_task(function* () {
is(RequestsMenu.items.length, 5,
"There should be a total of 5 items in the requests menu.");
is(RequestsMenu.visibleItems.length, 5,
- "There should be a total of 5 visbile items in the requests menu.");
- is($all(".side-menu-widget-item").length, 5,
+ "There should be a total of 5 visible items in the requests menu.");
+ is($all(".request-list-item").length, 5,
"The visible items in the requests menu are, in fact, visible!");
- is(RequestsMenu.getItemAtIndex(0), RequestsMenu.items[0],
- "The requests menu items aren't ordered correctly. First item is misplaced.");
- is(RequestsMenu.getItemAtIndex(1), RequestsMenu.items[1],
- "The requests menu items aren't ordered correctly. Second item is misplaced.");
- is(RequestsMenu.getItemAtIndex(2), RequestsMenu.items[2],
- "The requests menu items aren't ordered correctly. Third item is misplaced.");
- is(RequestsMenu.getItemAtIndex(3), RequestsMenu.items[3],
- "The requests menu items aren't ordered correctly. Fourth item is misplaced.");
- is(RequestsMenu.getItemAtIndex(4), RequestsMenu.items[4],
- "The requests menu items aren't ordered correctly. Fifth item is misplaced.");
-
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(a),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(a),
"GET1", SORTING_SJS + "?index=1", {
fuzzyUrl: true,
status: 101,
@@ -224,7 +213,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 0),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(b),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(b),
"GET2", SORTING_SJS + "?index=2", {
fuzzyUrl: true,
status: 200,
@@ -235,7 +224,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 19),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(c),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(c),
"GET3", SORTING_SJS + "?index=3", {
fuzzyUrl: true,
status: 300,
@@ -246,7 +235,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 29),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(d),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(d),
"GET4", SORTING_SJS + "?index=4", {
fuzzyUrl: true,
status: 400,
@@ -257,7 +246,7 @@ add_task(function* () {
size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 39),
time: true
});
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(e),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(e),
"GET5", SORTING_SJS + "?index=5", {
fuzzyUrl: true,
status: 500,
diff --git a/devtools/client/netmonitor/test/browser_net_sort-03.js b/devtools/client/netmonitor/test/browser_net_sort-03.js
index ada0872a8ff3..d7f1792ebc4f 100644
--- a/devtools/client/netmonitor/test/browser_net_sort-03.js
+++ b/devtools/client/netmonitor/test/browser_net_sort-03.js
@@ -105,17 +105,17 @@ add_task(function* () {
for (let header of headers) {
if (header != target) {
- is(header.hasAttribute("sorted"), false,
- "The " + header.id + " header should not have a 'sorted' attribute.");
- is(header.hasAttribute("tooltiptext"), false,
- "The " + header.id + " header should not have a 'tooltiptext' attribute.");
+ ok(!header.hasAttribute("data-sorted"),
+ "The " + header.id + " header does not have a 'data-sorted' attribute.");
+ ok(!header.getAttribute("title"),
+ "The " + header.id + " header does not have a 'title' attribute.");
} else {
- is(header.getAttribute("sorted"), direction,
- "The " + header.id + " header has an incorrect 'sorted' attribute.");
- is(header.getAttribute("tooltiptext"), direction == "ascending"
+ is(header.getAttribute("data-sorted"), direction,
+ "The " + header.id + " header has a correct 'data-sorted' attribute.");
+ is(header.getAttribute("title"), direction == "ascending"
? L10N.getStr("networkMenu.sortedAsc")
: L10N.getStr("networkMenu.sortedDesc"),
- "The " + header.id + " has an incorrect 'tooltiptext' attribute.");
+ "The " + header.id + " header has a correct 'title' attribute.");
}
}
}
@@ -132,16 +132,12 @@ add_task(function* () {
"There should be a specific number of items in the requests menu.");
is(RequestsMenu.visibleItems.length, order.length,
"There should be a specific number of visbile items in the requests menu.");
- is($all(".side-menu-widget-item").length, order.length,
+ is($all(".request-list-item").length, order.length,
"The visible items in the requests menu are, in fact, visible!");
- for (let i = 0; i < order.length; i++) {
- is(RequestsMenu.getItemAtIndex(i), RequestsMenu.items[i],
- "The requests menu items aren't ordered correctly. Misplaced item " + i + ".");
- }
-
for (let i = 0, len = order.length / 5; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i]),
+ verifyRequestItemTarget(RequestsMenu,
+ RequestsMenu.getItemAtIndex(order[i]),
"GET1", SORTING_SJS + "?index=1", {
fuzzyUrl: true,
status: 101,
@@ -154,7 +150,8 @@ add_task(function* () {
});
}
for (let i = 0, len = order.length / 5; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len]),
+ verifyRequestItemTarget(RequestsMenu,
+ RequestsMenu.getItemAtIndex(order[i + len]),
"GET2", SORTING_SJS + "?index=2", {
fuzzyUrl: true,
status: 200,
@@ -167,7 +164,8 @@ add_task(function* () {
});
}
for (let i = 0, len = order.length / 5; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 2]),
+ verifyRequestItemTarget(RequestsMenu,
+ RequestsMenu.getItemAtIndex(order[i + len * 2]),
"GET3", SORTING_SJS + "?index=3", {
fuzzyUrl: true,
status: 300,
@@ -180,7 +178,8 @@ add_task(function* () {
});
}
for (let i = 0, len = order.length / 5; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 3]),
+ verifyRequestItemTarget(RequestsMenu,
+ RequestsMenu.getItemAtIndex(order[i + len * 3]),
"GET4", SORTING_SJS + "?index=4", {
fuzzyUrl: true,
status: 400,
@@ -193,7 +192,8 @@ add_task(function* () {
});
}
for (let i = 0, len = order.length / 5; i < len; i++) {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(order[i + len * 4]),
+ verifyRequestItemTarget(RequestsMenu,
+ RequestsMenu.getItemAtIndex(order[i + len * 4]),
"GET5", SORTING_SJS + "?index=5", {
fuzzyUrl: true,
status: 500,
diff --git a/devtools/client/netmonitor/test/browser_net_statistics-03.js b/devtools/client/netmonitor/test/browser_net_statistics-03.js
index f3c6bf691364..f835fd45b373 100644
--- a/devtools/client/netmonitor/test/browser_net_statistics-03.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-03.js
@@ -20,7 +20,7 @@ add_task(function* () {
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-js-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-ws-button"));
EventUtils.sendMouseEvent({ type: "click" }, $("#requests-menu-filter-other-button"));
- testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1]);
+ testFilterButtonsCustom(monitor, [0, 1, 1, 1, 0, 0, 0, 0, 0, 1, 1]);
info("The correct filtering predicates are used before entering perf. analysis mode.");
let onEvents = promise.all([
diff --git a/devtools/client/netmonitor/test/browser_net_status-codes.js b/devtools/client/netmonitor/test/browser_net_status-codes.js
index df07c6c4fce8..1ec066c166c6 100644
--- a/devtools/client/netmonitor/test/browser_net_status-codes.js
+++ b/devtools/client/netmonitor/test/browser_net_status-codes.js
@@ -114,7 +114,8 @@ add_task(function* () {
requestItems[index] = item;
info("Verifying request #" + index);
- yield verifyRequestItemTarget(item, request.method, request.uri, request.details);
+ yield verifyRequestItemTarget(RequestsMenu, item,
+ request.method, request.uri, request.details);
index++;
}
@@ -159,7 +160,7 @@ add_task(function* () {
uri, "The url summary value is incorrect.");
is(tabpanel.querySelector("#headers-summary-method-value").getAttribute("value"),
method, "The method summary value is incorrect.");
- is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("code"),
+ is(tabpanel.querySelector("#headers-summary-status-circle").getAttribute("data-code"),
status, "The status summary code is incorrect.");
is(tabpanel.querySelector("#headers-summary-status-value").getAttribute("value"),
status + " " + statusText, "The status summary value is incorrect.");
@@ -207,7 +208,8 @@ add_task(function* () {
*/
function chooseRequest(index) {
let onTabUpdated = monitor.panelWin.once(EVENTS.TAB_UPDATED);
- EventUtils.sendMouseEvent({ type: "mousedown" }, requestItems[index].target);
+ let target = getItemTarget(RequestsMenu, requestItems[index]);
+ EventUtils.sendMouseEvent({ type: "mousedown" }, target);
return onTabUpdated;
}
});
diff --git a/devtools/client/netmonitor/test/browser_net_streaming-response.js b/devtools/client/netmonitor/test/browser_net_streaming-response.js
index 49a75ec3267b..a19ac8da8e45 100644
--- a/devtools/client/netmonitor/test/browser_net_streaming-response.js
+++ b/devtools/client/netmonitor/test/browser_net_streaming-response.js
@@ -33,7 +33,7 @@ add_task(function* () {
yield wait;
REQUESTS.forEach(([ fmt ], i) => {
- verifyRequestItemTarget(RequestsMenu.getItemAtIndex(i),
+ verifyRequestItemTarget(RequestsMenu, RequestsMenu.getItemAtIndex(i),
"GET", CONTENT_TYPE_SJS + "?fmt=" + fmt, {
status: 200,
statusText: "OK"
diff --git a/devtools/client/netmonitor/test/browser_net_throttle.js b/devtools/client/netmonitor/test/browser_net_throttle.js
index 8634305dc802..2db7ecc6a549 100644
--- a/devtools/client/netmonitor/test/browser_net_throttle.js
+++ b/devtools/client/netmonitor/test/browser_net_throttle.js
@@ -32,7 +32,7 @@ function* throttleTest(actuallyThrottle) {
uploadBPSMax: 10000,
},
};
- let client = monitor._controller.webConsoleClient;
+ let client = NetMonitorController.webConsoleClient;
info("sending throttle request");
let deferred = promise.defer();
@@ -46,7 +46,7 @@ function* throttleTest(actuallyThrottle) {
yield eventPromise;
let requestItem = NetMonitorView.RequestsMenu.getItemAtIndex(0);
- const reportedOneSecond = requestItem.attachment.eventTimings.timings.receive > 1000;
+ const reportedOneSecond = requestItem.eventTimings.timings.receive > 1000;
if (actuallyThrottle) {
ok(reportedOneSecond, "download reported as taking more than one second");
} else {
diff --git a/devtools/client/netmonitor/test/browser_net_timeline_ticks.js b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
index 2aafcb98d061..555c747cfe13 100644
--- a/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
+++ b/devtools/client/netmonitor/test/browser_net_timeline_ticks.js
@@ -19,8 +19,8 @@ add_task(function* () {
// Disable transferred size column support for this test.
// Without this, the waterfall only has enough room for one division, which
// would remove most of the value of this test.
- $("#requests-menu-transferred-header-box").hidden = true;
- $("#requests-menu-item-template .requests-menu-transferred").hidden = true;
+ // $("#requests-menu-transferred-header-box").hidden = true;
+ // $("#requests-menu-item-template .requests-menu-transferred").hidden = true;
RequestsMenu.lazyUpdate = false;
@@ -46,22 +46,17 @@ add_task(function* () {
ok($all(".requests-menu-timings-division").length >= 3,
"There should be at least 3 tick labels in the network requests header.");
- is($all(".requests-menu-timings-division")[0].getAttribute("value"),
- L10N.getFormatStr("networkMenu.millisecond", 0),
- "The first tick label has an incorrect value");
- is($all(".requests-menu-timings-division")[1].getAttribute("value"),
- L10N.getFormatStr("networkMenu.millisecond", 80),
- "The second tick label has an incorrect value");
- is($all(".requests-menu-timings-division")[2].getAttribute("value"),
- L10N.getFormatStr("networkMenu.millisecond", 160),
- "The third tick label has an incorrect value");
+ let timingDivisionEls = $all(".requests-menu-timings-division");
+ is(timingDivisionEls[0].textContent, L10N.getFormatStr("networkMenu.millisecond", 0),
+ "The first tick label has correct value");
+ is(timingDivisionEls[1].textContent, L10N.getFormatStr("networkMenu.millisecond", 80),
+ "The second tick label has correct value");
+ is(timingDivisionEls[2].textContent, L10N.getFormatStr("networkMenu.millisecond", 160),
+ "The third tick label has correct value");
- is($all(".requests-menu-timings-division")[0].style.transform, "translateX(0px)",
- "The first tick label has an incorrect translation");
- is($all(".requests-menu-timings-division")[1].style.transform, "translateX(80px)",
- "The second tick label has an incorrect translation");
- is($all(".requests-menu-timings-division")[2].style.transform, "translateX(160px)",
- "The third tick label has an incorrect translation");
+ is(timingDivisionEls[0].style.width, "78px", "The first tick label has correct width");
+ is(timingDivisionEls[1].style.width, "80px", "The second tick label has correct width");
+ is(timingDivisionEls[2].style.width, "80px", "The third tick label has correct width");
ok(RequestsMenu._canvas, "A canvas should be created after the first request.");
ok(RequestsMenu._ctx, "A 2d context should be created after the first request.");
diff --git a/devtools/client/netmonitor/test/browser_net_timing-division.js b/devtools/client/netmonitor/test/browser_net_timing-division.js
index 0114ba235500..684752d02de8 100644
--- a/devtools/client/netmonitor/test/browser_net_timing-division.js
+++ b/devtools/client/netmonitor/test/browser_net_timing-division.js
@@ -23,38 +23,31 @@ add_task(function* () {
});
yield wait;
- let milDivs = $all(".requests-menu-timings-division[division-scale=millisecond]");
- let secDivs = $all(".requests-menu-timings-division[division-scale=second]");
- let minDivs = $all(".requests-menu-timings-division[division-scale=minute]");
+ let milDivs = $all(".requests-menu-timings-division[data-division-scale=millisecond]");
+ let secDivs = $all(".requests-menu-timings-division[data-division-scale=second]");
+ let minDivs = $all(".requests-menu-timings-division[data-division-scale=minute]");
info("Number of millisecond divisions: " + milDivs.length);
info("Number of second divisions: " + secDivs.length);
info("Number of minute divisions: " + minDivs.length);
- for (let div of milDivs) {
- info("Millisecond division: " + div.getAttribute("value"));
- }
- for (let div of secDivs) {
- info("Second division: " + div.getAttribute("value"));
- }
- for (let div of minDivs) {
- info("Minute division: " + div.getAttribute("value"));
- }
+ milDivs.forEach(div => info(`Millisecond division: ${div.textContent}`));
+ secDivs.forEach(div => info(`Second division: ${div.textContent}`));
+ minDivs.forEach(div => info(`Minute division: ${div.textContent}`));
- is(RequestsMenu.itemCount, 2,
- "There should be only two requests made.");
+ is(RequestsMenu.itemCount, 2, "There should be only two requests made.");
let firstRequest = RequestsMenu.getItemAtIndex(0);
let lastRequest = RequestsMenu.getItemAtIndex(1);
info("First request happened at: " +
- firstRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
+ firstRequest.responseHeaders.headers.find(e => e.name == "Date").value);
info("Last request happened at: " +
- lastRequest.attachment.responseHeaders.headers.find(e => e.name == "Date").value);
+ lastRequest.responseHeaders.headers.find(e => e.name == "Date").value);
ok(secDivs.length,
"There should be at least one division on the seconds time scale.");
- ok(secDivs[0].getAttribute("value").match(/\d+\.\d{2}\s\w+/),
+ ok(secDivs[0].textContent.match(/\d+\.\d{2}\s\w+/),
"The division on the seconds time scale looks legit.");
return teardown(monitor);
diff --git a/devtools/client/netmonitor/test/head.js b/devtools/client/netmonitor/test/head.js
index 746e5e2db8fb..63e45d765a9b 100644
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -250,62 +250,70 @@ function waitForNetworkEvents(aMonitor, aGetRequests, aPostRequests = 0) {
return deferred.promise;
}
-function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
+/**
+ * Convert a store record (model) to the rendered element. Tests that need to use
+ * this should be rewritten - test the rendered markup at unit level, integration
+ * mochitest should check only the store state.
+ */
+function getItemTarget(requestList, requestItem) {
+ const items = requestList.mountPoint.querySelectorAll(".request-list-item");
+ return [...items].find(el => el.dataset.id == requestItem.id);
+}
+
+function verifyRequestItemTarget(requestList, requestItem, aMethod, aUrl, aData = {}) {
info("> Verifying: " + aMethod + " " + aUrl + " " + aData.toSource());
// This bloats log sizes significantly in automation (bug 992485)
- // info("> Request: " + aRequestItem.attachment.toSource());
+ // info("> Request: " + requestItem.toSource());
- let requestsMenu = aRequestItem.ownerView;
- let widgetIndex = requestsMenu.indexOfItem(aRequestItem);
- let visibleIndex = requestsMenu.visibleItems.indexOf(aRequestItem);
+ let visibleIndex = requestList.visibleItems.indexOf(requestItem);
- info("Widget index of item: " + widgetIndex);
info("Visible index of item: " + visibleIndex);
let { fuzzyUrl, status, statusText, cause, type, fullMimeType,
transferred, size, time, displayedStatus } = aData;
- let { attachment, target } = aRequestItem;
+
+ let target = getItemTarget(requestList, requestItem);
let unicodeUrl = decodeUnicodeUrl(aUrl);
let name = getUrlBaseName(aUrl);
let query = getUrlQuery(aUrl);
let hostPort = getUrlHost(aUrl);
- let remoteAddress = attachment.remoteAddress;
+ let remoteAddress = requestItem.remoteAddress;
if (fuzzyUrl) {
- ok(attachment.method.startsWith(aMethod), "The attached method is correct.");
- ok(attachment.url.startsWith(aUrl), "The attached url is correct.");
+ ok(requestItem.method.startsWith(aMethod), "The attached method is correct.");
+ ok(requestItem.url.startsWith(aUrl), "The attached url is correct.");
} else {
- is(attachment.method, aMethod, "The attached method is correct.");
- is(attachment.url, aUrl, "The attached url is correct.");
+ is(requestItem.method, aMethod, "The attached method is correct.");
+ is(requestItem.url, aUrl, "The attached url is correct.");
}
- is(target.querySelector(".requests-menu-method").getAttribute("value"),
+ is(target.querySelector(".requests-menu-method").textContent,
aMethod, "The displayed method is correct.");
if (fuzzyUrl) {
- ok(target.querySelector(".requests-menu-file").getAttribute("value").startsWith(
+ ok(target.querySelector(".requests-menu-file").textContent.startsWith(
name + (query ? "?" + query : "")), "The displayed file is correct.");
- ok(target.querySelector(".requests-menu-file").getAttribute("tooltiptext").startsWith(unicodeUrl),
+ ok(target.querySelector(".requests-menu-file").getAttribute("title").startsWith(unicodeUrl),
"The tooltip file is correct.");
} else {
- is(target.querySelector(".requests-menu-file").getAttribute("value"),
+ is(target.querySelector(".requests-menu-file").textContent,
name + (query ? "?" + query : ""), "The displayed file is correct.");
- is(target.querySelector(".requests-menu-file").getAttribute("tooltiptext"),
+ is(target.querySelector(".requests-menu-file").getAttribute("title"),
unicodeUrl, "The tooltip file is correct.");
}
- is(target.querySelector(".requests-menu-domain").getAttribute("value"),
+ is(target.querySelector(".requests-menu-domain").textContent,
hostPort, "The displayed domain is correct.");
let domainTooltip = hostPort + (remoteAddress ? " (" + remoteAddress + ")" : "");
- is(target.querySelector(".requests-menu-domain").getAttribute("tooltiptext"),
+ is(target.querySelector(".requests-menu-domain").getAttribute("title"),
domainTooltip, "The tooltip domain is correct.");
if (status !== undefined) {
- let value = target.querySelector(".requests-menu-status-icon").getAttribute("code");
- let codeValue = target.querySelector(".requests-menu-status-code").getAttribute("value");
- let tooltip = target.querySelector(".requests-menu-status").getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-status-icon").getAttribute("data-code");
+ let codeValue = target.querySelector(".requests-menu-status-code").textContent;
+ let tooltip = target.querySelector(".requests-menu-status").getAttribute("title");
info("Displayed status: " + value);
info("Displayed code: " + codeValue);
info("Tooltip status: " + tooltip);
@@ -314,41 +322,40 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
is(tooltip, status + " " + statusText, "The tooltip status is correct.");
}
if (cause !== undefined) {
- let causeLabel = target.querySelector(".requests-menu-cause-label");
- let value = causeLabel.getAttribute("value");
- let tooltip = causeLabel.getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-cause > .subitem-label").textContent;
+ let tooltip = target.querySelector(".requests-menu-cause").getAttribute("title");
info("Displayed cause: " + value);
info("Tooltip cause: " + tooltip);
is(value, cause.type, "The displayed cause is correct.");
is(tooltip, cause.loadingDocumentUri, "The tooltip cause is correct.")
}
if (type !== undefined) {
- let value = target.querySelector(".requests-menu-type").getAttribute("value");
- let tooltip = target.querySelector(".requests-menu-type").getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-type").textContent;
+ let tooltip = target.querySelector(".requests-menu-type").getAttribute("title");
info("Displayed type: " + value);
info("Tooltip type: " + tooltip);
is(value, type, "The displayed type is correct.");
is(tooltip, fullMimeType, "The tooltip type is correct.");
}
if (transferred !== undefined) {
- let value = target.querySelector(".requests-menu-transferred").getAttribute("value");
- let tooltip = target.querySelector(".requests-menu-transferred").getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-transferred").textContent;
+ let tooltip = target.querySelector(".requests-menu-transferred").getAttribute("title");
info("Displayed transferred size: " + value);
info("Tooltip transferred size: " + tooltip);
is(value, transferred, "The displayed transferred size is correct.");
is(tooltip, transferred, "The tooltip transferred size is correct.");
}
if (size !== undefined) {
- let value = target.querySelector(".requests-menu-size").getAttribute("value");
- let tooltip = target.querySelector(".requests-menu-size").getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-size").textContent;
+ let tooltip = target.querySelector(".requests-menu-size").getAttribute("title");
info("Displayed size: " + value);
info("Tooltip size: " + tooltip);
is(value, size, "The displayed size is correct.");
is(tooltip, size, "The tooltip size is correct.");
}
if (time !== undefined) {
- let value = target.querySelector(".requests-menu-timings-total").getAttribute("value");
- let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("tooltiptext");
+ let value = target.querySelector(".requests-menu-timings-total").textContent;
+ let tooltip = target.querySelector(".requests-menu-timings-total").getAttribute("title");
info("Displayed time: " + value);
info("Tooltip time: " + tooltip);
ok(~~(value.match(/[0-9]+/)) >= 0, "The displayed time is correct.");
@@ -357,15 +364,11 @@ function verifyRequestItemTarget(aRequestItem, aMethod, aUrl, aData = {}) {
if (visibleIndex != -1) {
if (visibleIndex % 2 == 0) {
- ok(aRequestItem.target.hasAttribute("even"),
- aRequestItem.value + " should have 'even' attribute.");
- ok(!aRequestItem.target.hasAttribute("odd"),
- aRequestItem.value + " shouldn't have 'odd' attribute.");
+ ok(target.classList.contains("even"), "Item should have 'even' class.");
+ ok(!target.classList.contains("odd"), "Item shouldn't have 'odd' class.");
} else {
- ok(!aRequestItem.target.hasAttribute("even"),
- aRequestItem.value + " shouldn't have 'even' attribute.");
- ok(aRequestItem.target.hasAttribute("odd"),
- aRequestItem.value + " should have 'odd' attribute.");
+ ok(!target.classList.contains("even"), "Item shouldn't have 'even' class.");
+ ok(target.classList.contains("odd"), "Item should have 'odd' class.");
}
}
}
diff --git a/devtools/client/netmonitor/utils/format-utils.js b/devtools/client/netmonitor/utils/format-utils.js
new file mode 100644
index 000000000000..5bdd50bd3e6d
--- /dev/null
+++ b/devtools/client/netmonitor/utils/format-utils.js
@@ -0,0 +1,44 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const { L10N } = require("../l10n");
+
+// Constants for formatting bytes.
+const BYTES_IN_KB = 1024;
+const BYTES_IN_MB = Math.pow(BYTES_IN_KB, 2);
+const BYTES_IN_GB = Math.pow(BYTES_IN_KB, 3);
+const MAX_BYTES_SIZE = 1000;
+const MAX_KB_SIZE = 1000 * BYTES_IN_KB;
+const MAX_MB_SIZE = 1000 * BYTES_IN_MB;
+
+const CONTENT_SIZE_DECIMALS = 2;
+
+/**
+ * Get a human-readable string from a number of bytes, with the B, KB, MB, or
+ * GB value. Note that the transition between abbreviations is by 1000 rather
+ * than 1024 in order to keep the displayed digits smaller as "1016 KB" is
+ * more awkward than 0.99 MB"
+ */
+function getFormattedSize(bytes) {
+ if (bytes < MAX_BYTES_SIZE) {
+ return L10N.getFormatStr("networkMenu.sizeB", bytes);
+ } else if (bytes < MAX_KB_SIZE) {
+ let kb = bytes / BYTES_IN_KB;
+ let size = L10N.numberWithDecimals(kb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeKB", size);
+ } else if (bytes < MAX_MB_SIZE) {
+ let mb = bytes / BYTES_IN_MB;
+ let size = L10N.numberWithDecimals(mb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeMB", size);
+ }
+ let gb = bytes / BYTES_IN_GB;
+ let size = L10N.numberWithDecimals(gb, CONTENT_SIZE_DECIMALS);
+ return L10N.getFormatStr("networkMenu.sizeGB", size);
+}
+
+module.exports = {
+ getFormattedSize
+};
diff --git a/devtools/client/netmonitor/utils/moz.build b/devtools/client/netmonitor/utils/moz.build
new file mode 100644
index 000000000000..59a5f0507211
--- /dev/null
+++ b/devtools/client/netmonitor/utils/moz.build
@@ -0,0 +1,8 @@
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+DevToolsModules(
+ 'format-utils.js'
+)
diff --git a/devtools/client/netmonitor/waterfall-background.js b/devtools/client/netmonitor/waterfall-background.js
new file mode 100644
index 000000000000..a79107d29fc6
--- /dev/null
+++ b/devtools/client/netmonitor/waterfall-background.js
@@ -0,0 +1,135 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+const HTML_NS = "http://www.w3.org/1999/xhtml";
+// ms
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
+// px
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB = [128, 136, 144];
+// 8-bit value of the alpha component of the tick color
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 32;
+const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 32;
+// RGBA colors for the timing markers
+const REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA = [0, 0, 255, 128];
+const REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA = [255, 0, 0, 128];
+
+const STATE_KEYS = [
+ "scale",
+ "waterfallWidth",
+ "firstRequestStartedMillis",
+ "timingMarkers",
+];
+
+/**
+ * Creates the background displayed on each waterfall view in this container.
+ */
+function WaterfallBackground(document) {
+ this.document = document;
+ this.canvas = document.createElementNS(HTML_NS, "canvas");
+ this.ctx = this.canvas.getContext("2d");
+ this.prevState = {};
+}
+
+WaterfallBackground.prototype = {
+ draw(state) {
+ // Do a shallow compare of the previous and the new state
+ const shouldUpdate = STATE_KEYS.some(key => this.prevState[key] !== state[key]);
+ if (!shouldUpdate) {
+ return;
+ }
+
+ this.prevState = state;
+
+ if (state.scale == null) {
+ this.document.mozSetImageElement("waterfall-background", null);
+ return;
+ }
+
+ // Nuke the context.
+ let canvasWidth = this.canvas.width = state.waterfallWidth;
+ // Awww yeah, 1px, repeats on Y axis.
+ let canvasHeight = this.canvas.height = 1;
+
+ // Start over.
+ let imageData = this.ctx.createImageData(canvasWidth, canvasHeight);
+ let pixelArray = imageData.data;
+
+ let buf = new ArrayBuffer(pixelArray.length);
+ let view8bit = new Uint8ClampedArray(buf);
+ let view32bit = new Uint32Array(buf);
+
+ // Build new millisecond tick lines...
+ let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
+ let optimalTickIntervalFound = false;
+ let scaledStep;
+
+ while (!optimalTickIntervalFound) {
+ // Ignore any divisions that would end up being too close to each other.
+ scaledStep = state.scale * timingStep;
+ if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
+ timingStep <<= 1;
+ continue;
+ }
+ optimalTickIntervalFound = true;
+ }
+
+ const isRTL = isDocumentRTL(this.document);
+ const [r, g, b] = REQUESTS_WATERFALL_BACKGROUND_TICKS_COLOR_RGB;
+ let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
+
+ function drawPixelAt(offset, color) {
+ let position = (isRTL ? canvasWidth - offset : offset - 1) | 0;
+ let [rc, gc, bc, ac] = color;
+ view32bit[position] = (ac << 24) | (bc << 16) | (gc << 8) | rc;
+ }
+
+ // Insert one pixel for each division on each scale.
+ for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
+ let increment = scaledStep * Math.pow(2, i);
+ for (let x = 0; x < canvasWidth; x += increment) {
+ drawPixelAt(x, [r, g, b, alphaComponent]);
+ }
+ alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
+ }
+
+ function drawTimestamp(timestamp, color) {
+ if (timestamp == -1) {
+ return;
+ }
+
+ let delta = Math.floor((timestamp - state.firstRequestStartedMillis) * state.scale);
+ drawPixelAt(delta, color);
+ }
+
+ drawTimestamp(state.timingMarkers.firstDocumentDOMContentLoadedTimestamp,
+ REQUESTS_WATERFALL_DOMCONTENTLOADED_TICKS_COLOR_RGBA);
+
+ drawTimestamp(state.timingMarkers.firstDocumentLoadTimestamp,
+ REQUESTS_WATERFALL_LOAD_TICKS_COLOR_RGBA);
+
+ // Flush the image data and cache the waterfall background.
+ pixelArray.set(view8bit);
+ this.ctx.putImageData(imageData, 0, 0);
+
+ this.document.mozSetImageElement("waterfall-background", this.canvas);
+ },
+
+ destroy() {
+ this.document.mozSetImageElement("waterfall-background", null);
+ }
+};
+
+/**
+ * Returns true if this is document is in RTL mode.
+ * @return boolean
+ */
+function isDocumentRTL(doc) {
+ return doc.defaultView.getComputedStyle(doc.documentElement).direction === "rtl";
+}
+
+module.exports = WaterfallBackground;
diff --git a/devtools/client/preferences/devtools.js b/devtools/client/preferences/devtools.js
index bb273515be73..b17362ff8693 100644
--- a/devtools/client/preferences/devtools.js
+++ b/devtools/client/preferences/devtools.js
@@ -171,7 +171,6 @@ pref("devtools.netmonitor.enabled", true);
// The default Network Monitor UI settings
pref("devtools.netmonitor.panes-network-details-width", 550);
pref("devtools.netmonitor.panes-network-details-height", 450);
-pref("devtools.netmonitor.statistics", true);
pref("devtools.netmonitor.filters", "[\"all\"]");
// The default Network monitor HAR export setting
diff --git a/devtools/client/shared/browser-loader.js b/devtools/client/shared/browser-loader.js
index f5cac31e7b8a..ab7505beee9b 100644
--- a/devtools/client/shared/browser-loader.js
+++ b/devtools/client/shared/browser-loader.js
@@ -18,6 +18,10 @@ const BROWSER_BASED_DIRS = [
"resource://devtools/client/shared/redux",
];
+const COMMON_LIBRARY_DIRS = [
+ "resource://devtools/client/shared/vendor",
+];
+
// Any directory that matches the following regular expression
// is also considered as browser based module directory.
// ('resource://devtools/client/.*/components/')
@@ -81,8 +85,12 @@ function BrowserLoader(options) {
* @param Boolean useOnlyShared
* If true, ignores `baseURI` and only loads the shared
* BROWSER_BASED_DIRS via BrowserLoader.
+ * @param Function commonLibRequire
+ * Require function that should be used to load common libraries, like React.
+ * Allows for sharing common modules between tools, instead of loading a new
+ * instance into each tool. For example, pass "toolbox.browserRequire" here.
*/
-function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
+function BrowserLoaderBuilder({ baseURI, window, useOnlyShared, commonLibRequire }) {
assert(!!baseURI !== !!useOnlyShared,
"Cannot use both `baseURI` and `useOnlyShared`.");
@@ -109,15 +117,15 @@ function BrowserLoaderBuilder({ baseURI, window, useOnlyShared }) {
}
const uri = require.resolve(id);
- let isBrowserDir = BROWSER_BASED_DIRS.filter(dir => {
- return uri.startsWith(dir);
- }).length > 0;
- // If the URI doesn't match hardcoded paths try the regexp.
- if (!isBrowserDir) {
- isBrowserDir = uri.match(browserBasedDirsRegExp) != null;
+ if (commonLibRequire && COMMON_LIBRARY_DIRS.some(dir => uri.startsWith(dir))) {
+ return commonLibRequire(uri);
}
+ // Check if the URI matches one of hardcoded paths or a regexp.
+ let isBrowserDir = BROWSER_BASED_DIRS.some(dir => uri.startsWith(dir)) ||
+ uri.match(browserBasedDirsRegExp) != null;
+
if ((useOnlyShared || !uri.startsWith(baseURI)) && !isBrowserDir) {
return devtools.require(uri);
}
diff --git a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
index 7aeb24d69e4c..f05ee6bad40f 100644
--- a/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
+++ b/devtools/client/styleeditor/test/browser_styleeditor_fetch-from-cache.js
@@ -14,7 +14,8 @@ add_task(function* () {
let target = TargetFactory.forTab(tab);
let toolbox = yield gDevTools.showToolbox(target, "netmonitor");
let netmonitor = toolbox.getPanel("netmonitor");
- netmonitor._view.RequestsMenu.lazyUpdate = false;
+ let { RequestsMenu } = netmonitor.panelWin.NetMonitorView;
+ RequestsMenu.lazyUpdate = false;
info("Navigating to test page");
yield navigateTo(TEST_URL);
@@ -26,15 +27,15 @@ add_task(function* () {
yield styleeditor.UI.editors[0].getSourceEditor();
info("Checking Netmonitor contents.");
- let attachments = [];
- for (let item of netmonitor._view.RequestsMenu) {
- if (item.attachment.url.endsWith("doc_uncached.css")) {
- attachments.push(item.attachment);
+ let items = [];
+ for (let item of RequestsMenu.items) {
+ if (item.url.endsWith("doc_uncached.css")) {
+ items.push(item);
}
}
- is(attachments.length, 2,
+ is(items.length, 2,
"Got two requests for doc_uncached.css after Style Editor was loaded.");
- ok(attachments[1].fromCache,
+ ok(items[1].fromCache,
"Second request was loaded from browser cache");
});
diff --git a/devtools/client/themes/netmonitor.css b/devtools/client/themes/netmonitor.css
index bdced8995e8f..a3bf7c5171c9 100644
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -5,6 +5,8 @@
#toolbar-labels {
overflow: hidden;
+ display: flex;
+ flex: auto;
}
.devtools-toolbar-container {
@@ -48,17 +50,6 @@
text-overflow: ellipsis;
}
-/* Responsive sidebar */
-@media (max-width: 700px) {
- #toolbar-spacer,
- #details-pane-toggle,
- #details-pane.pane-collapsed,
- .requests-menu-waterfall,
- #requests-menu-network-summary-button > .summary-info-text {
- display: none;
- }
-}
-
:root.theme-dark {
--table-splitter-color: rgba(255,255,255,0.15);
--table-zebra-background: rgba(255,255,255,0.05);
@@ -104,7 +95,20 @@
--sort-descending-image: url(chrome://devtools/skin/images/firebug/arrow-down.svg);
}
-#requests-menu-empty-notice {
+#network-table {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+ overflow: hidden;
+}
+
+.request-list-container {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+}
+
+.request-list-empty-notice {
margin: 0;
padding: 12px;
font-size: 120%;
@@ -117,30 +121,27 @@
#requests-menu-perf-notice-button {
min-width: 30px;
min-height: 26px;
- margin: 0;
- list-style-image: url(images/profiler-stopwatch.svg);
+ margin: 0 5px;
+ vertical-align: middle;
}
-/* Make sure the icon is visible on Linux (to overwrite a rule
- in xul.css that hides the icon if there is no label.
- See also bug 1278050. */
-#requests-menu-perf-notice-button .button-icon {
- display: block;
-}
-
-#requests-menu-perf-notice-button .button-text {
- display: none;
+#requests-menu-perf-notice-button::before {
+ background-image: url(images/profiler-stopwatch.svg);
}
#requests-menu-reload-notice-button {
+ font-size: inherit;
min-height: 26px;
- margin: 0;
+ padding-left: 10px;
+ padding-right: 10px;
+ margin: 0 5px;
background-color: var(--theme-toolbar-background);
}
/* Network requests table */
#requests-menu-toolbar {
+ display: flex;
padding: 0;
}
@@ -150,14 +151,43 @@
}
.theme-firebug #requests-menu-toolbar {
- height: 16px !important;
+ height: 19px !important;
+}
+
+.requests-menu-contents {
+ display: -moz-box;
+ -moz-box-orient: vertical;
+ -moz-box-flex: 1;
+ overflow-x: hidden;
+ overflow-y: auto;
+
+ --timings-scale: 1;
+ --timings-rev-scale: 1;
}
.requests-menu-subitem {
+ display: flex;
+ flex: none;
+ box-sizing: border-box;
+ align-items: center;
padding: 3px;
}
+.subitem-label {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.requests-menu-header {
+ display: flex;
+ flex: none;
+}
+
.requests-menu-header-button {
+ display: flex;
+ align-items: center;
+ flex: auto;
-moz-appearance: none;
background-color: transparent;
border-image: linear-gradient(transparent 15%,
@@ -170,14 +200,20 @@
min-width: 1px;
min-height: 24px;
margin: 0;
- padding-bottom: 2px;
- padding-inline-start: 13px;
padding-top: 2px;
+ padding-bottom: 2px;
+ padding-inline-start: 16px;
+ padding-inline-end: 0;
text-align: center;
color: inherit;
font-weight: inherit !important;
}
+.requests-menu-header-button::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
.requests-menu-header:first-child .requests-menu-header-button {
border-width: 0;
}
@@ -186,38 +222,41 @@
background-color: rgba(0, 0, 0, 0.1);
}
-.requests-menu-header-button > .button-box > .button-icon,
-#requests-menu-waterfall-image {
- display: -moz-box;
+.requests-menu-header-button > .button-text {
+ flex: auto;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.requests-menu-header-button > .button-icon {
+ flex: none;
height: 4px;
+ margin-inline-start: 3px;
margin-inline-end: 6px;
- -moz-box-ordinal-group: 2;
width: 7px;
}
-.requests-menu-header-button[sorted=ascending] > .button-box > .button-icon,
-.requests-menu-header-button[sorted=ascending] #requests-menu-waterfall-image {
- list-style-image: var(--sort-ascending-image);
+.requests-menu-header-button[data-sorted=ascending] > .button-icon {
+ background-image: var(--sort-ascending-image);
}
-.requests-menu-header-button[sorted=descending] > .button-box > .button-icon,
-.requests-menu-header-button[sorted=descending] #requests-menu-waterfall-image {
- list-style-image: var(--sort-descending-image);
+.requests-menu-header-button[data-sorted=descending] > .button-icon {
+ background-image: var(--sort-descending-image);
}
-.requests-menu-header-button > .button-box > .button-text,
-#requests-menu-waterfall-label-wrapper {
- -moz-box-flex: 1;
+.requests-menu-waterfall-label-wrapper {
+ display: flex;
}
-.requests-menu-header-button[sorted],
-.requests-menu-header-button[sorted]:hover {
+.requests-menu-header-button[data-sorted],
+.requests-menu-header-button[data-sorted]:hover {
background-color: var(--theme-selection-background);
color: var(--theme-selection-color);
}
-.requests-menu-header-button[sorted],
-.requests-menu-header[active] + .requests-menu-header .requests-menu-header-button {
+.requests-menu-header-button[data-sorted],
+.requests-menu-header[data-active] + .requests-menu-header .requests-menu-header-button {
border-image: linear-gradient(var(--theme-splitter-color), var(--theme-splitter-color)) 1 1;
}
@@ -235,11 +274,15 @@
min-height: 17px;
}
-.theme-firebug .requests-header-menu-button[sorted] {
+.theme-firebug .requests-menu-header-button > .button-icon {
+ height: 7px;
+}
+
+.theme-firebug .requests-menu-header-button[data-sorted] {
background-color: #AAC3DC;
}
-.theme-firebug .requests-header-menu:hover:active {
+.theme-firebug .requests-menu-header:hover:active {
background-image: linear-gradient(rgba(0, 0, 0, 0.1),
transparent);
}
@@ -265,10 +308,9 @@
}
.requests-menu-icon {
- background: #fff;
- width: calc(1em + 4px);
- height: calc(1em + 4px);
- margin: -4px 0px;
+ background: transparent;
+ width: 15px;
+ height: 15px;
margin-inline-end: 4px;
}
@@ -281,40 +323,47 @@
}
.requests-security-state-icon {
+ flex: none;
width: 16px;
height: 16px;
margin-inline-end: 4px;
}
-.side-menu-widget-item.selected .requests-security-state-icon {
+.request-list-item.selected .requests-security-state-icon {
filter: brightness(1.3);
}
.security-state-insecure {
- list-style-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
+ background-image: url(chrome://devtools/skin/images/security-state-insecure.svg);
}
.security-state-secure {
- list-style-image: url(chrome://devtools/skin/images/security-state-secure.svg);
+ background-image: url(chrome://devtools/skin/images/security-state-secure.svg);
}
.security-state-weak {
- list-style-image: url(chrome://devtools/skin/images/security-state-weak.svg);
+ background-image: url(chrome://devtools/skin/images/security-state-weak.svg);
}
.security-state-broken {
- list-style-image: url(chrome://devtools/skin/images/security-state-broken.svg);
+ background-image: url(chrome://devtools/skin/images/security-state-broken.svg);
}
.security-state-local {
- list-style-image: url(chrome://devtools/skin/images/globe.svg);
+ background-image: url(chrome://devtools/skin/images/globe.svg);
}
.requests-menu-type,
.requests-menu-size {
max-width: 6em;
- text-align: center;
width: 8vw;
+ justify-content: center;
+}
+
+.requests-menu-transferred {
+ max-width: 8em;
+ width: 8vw;
+ justify-content: center;
}
.requests-menu-cause {
@@ -335,13 +384,7 @@
-moz-user-select: none;
}
-.requests-menu-transferred {
- max-width: 8em;
- text-align: center;
- width: 8vw;
-}
-
-.side-menu-widget-item.selected .requests-menu-transferred.theme-comment {
+.request-list-item.selected .requests-menu-transferred.theme-comment {
color: var(--theme-selection-color);
}
@@ -361,31 +404,32 @@
margin-inline-end: 5px;
border-radius: 10px;
transition: box-shadow 0.5s ease-in-out;
+ box-sizing: border-box;
}
-.side-menu-widget-item.selected .requests-menu-status-icon {
+.request-list-item.selected .requests-menu-status-icon {
filter: brightness(1.3);
}
-.requests-menu-status-icon:not([code]) {
+.requests-menu-status-icon:not([data-code]) {
background-color: var(--theme-content-color2);
}
-.requests-menu-status-icon[code="cached"] {
+.requests-menu-status-icon[data-code="cached"] {
border: 2px solid var(--theme-content-color2);
background-color: transparent;
}
-.requests-menu-status-icon[code^="1"] {
+.requests-menu-status-icon[data-code^="1"] {
background-color: var(--theme-highlight-blue);
}
-.requests-menu-status-icon[code^="2"] {
+.requests-menu-status-icon[data-code^="2"] {
background-color: var(--theme-highlight-green);
}
/* 3xx are triangles */
-.requests-menu-status-icon[code^="3"] {
+.requests-menu-status-icon[data-code^="3"] {
background-color: transparent;
width: 0;
height: 0;
@@ -396,12 +440,12 @@
}
/* 4xx and 5xx are squares - error codes */
-.requests-menu-status-icon[code^="4"] {
+.requests-menu-status-icon[data-code^="4"] {
background-color: var(--theme-highlight-red);
border-radius: 0; /* squares */
}
-.requests-menu-status-icon[code^="5"] {
+.requests-menu-status-icon[data-code^="5"] {
background-color: var(--theme-highlight-pink);
border-radius: 0;
transform: rotate(45deg);
@@ -410,30 +454,28 @@
/* Network requests table: waterfall header */
.requests-menu-waterfall {
+ flex: auto;
padding-inline-start: 0;
}
-#requests-menu-waterfall-label:not(.requests-menu-waterfall-visible) {
- padding-inline-start: 13px;
+.requests-menu-waterfall-label-wrapper:not(.requests-menu-waterfall-visible) {
+ padding-inline-start: 16px;
}
.requests-menu-timings-division {
- width: 100px;
padding-top: 2px;
padding-inline-start: 4px;
font-size: 75%;
pointer-events: none;
box-sizing: border-box;
text-align: start;
-}
-
-.requests-menu-timings-division:first-child {
- width: 98px; /* Substract 2px for borders */
+ /* Allow the timing label to shrink if the container gets too narrow.
+ * The container width then is not limited by the content. */
+ flex: initial;
}
.requests-menu-timings-division:not(:first-child) {
border-inline-start: 1px dashed;
- margin-inline-start: -100px !important; /* Don't affect layout. */
}
.requests-menu-timings-division:-moz-locale-dir(ltr) {
@@ -452,27 +494,34 @@
border-inline-start-color: #585959 !important;
}
-.requests-menu-timings-division[division-scale=second],
-.requests-menu-timings-division[division-scale=minute] {
+.requests-menu-timings-division[data-division-scale=second],
+.requests-menu-timings-division[data-division-scale=minute] {
font-weight: 600;
}
/* Network requests table: waterfall items */
.requests-menu-subitem.requests-menu-waterfall {
- padding-inline-start: 0px;
+ padding-inline-start: 0;
padding-inline-end: 4px;
/* Background created on a