mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-14 02:31:59 +00:00
Merge mozilla-central to mozilla-inbound
This commit is contained in:
commit
a035e94835
@ -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/**
|
||||
|
15
browser/config/mozconfigs/macosx64/beta
Normal file
15
browser/config/mozconfigs/macosx64/beta
Normal file
@ -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"
|
15
browser/config/mozconfigs/macosx64/common-opt
Normal file
15
browser/config/mozconfigs/macosx64/common-opt
Normal file
@ -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
|
@ -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"
|
||||
|
21
browser/config/mozconfigs/macosx64/release
Normal file
21
browser/config/mozconfigs/macosx64/release
Normal file
@ -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"
|
@ -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',
|
||||
|
@ -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();
|
||||
|
@ -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'}}
|
||||
|
||||
|
@ -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"
|
||||
|
@ -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)
|
||||
|
42
devtools/client/netmonitor/actions/batching.js
Normal file
42
devtools/client/netmonitor/actions/batching.js
Normal file
@ -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,
|
||||
};
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
);
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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,
|
||||
};
|
||||
|
67
devtools/client/netmonitor/actions/selection.js
Normal file
67
devtools/client/netmonitor/actions/selection.js
Normal file
@ -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,
|
||||
};
|
18
devtools/client/netmonitor/actions/sort.js
Normal file
18
devtools/client/netmonitor/actions/sort.js
Normal file
@ -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
|
||||
};
|
19
devtools/client/netmonitor/actions/timing-markers.js
Normal file
19
devtools/client/netmonitor/actions/timing-markers.js
Normal file
@ -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
|
||||
};
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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);
|
||||
|
@ -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',
|
||||
|
255
devtools/client/netmonitor/components/request-list-content.js
Normal file
255
devtools/client/netmonitor/components/request-list-content.js
Normal file
@ -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);
|
65
devtools/client/netmonitor/components/request-list-empty.js
Normal file
65
devtools/client/netmonitor/components/request-list-empty.js
Normal file
@ -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);
|
197
devtools/client/netmonitor/components/request-list-header.js
Normal file
197
devtools/client/netmonitor/components/request-list-header.js
Normal file
@ -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);
|
346
devtools/client/netmonitor/components/request-list-item.js
Normal file
346
devtools/client/netmonitor/components/request-list-item.js
Normal file
@ -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;
|
107
devtools/client/netmonitor/components/request-list-tooltip.js
Normal file
107
devtools/client/netmonitor/components/request-list-tooltip.js
Normal file
@ -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,
|
||||
};
|
34
devtools/client/netmonitor/components/request-list.js
Normal file
34
devtools/client/netmonitor/components/request-list.js
Normal file
@ -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);
|
7
devtools/client/netmonitor/components/shared/moz.build
Normal file
7
devtools/client/netmonitor/components/shared/moz.build
Normal file
@ -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',
|
||||
)
|
@ -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);
|
@ -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: () => {
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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}
|
||||
*/
|
||||
|
@ -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.
|
||||
*
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
132
devtools/client/netmonitor/middleware/batching.js
Normal file
132
devtools/client/netmonitor/middleware/batching.js
Normal file
@ -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;
|
7
devtools/client/netmonitor/middleware/moz.build
Normal file
7
devtools/client/netmonitor/middleware/moz.build
Normal file
@ -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',
|
||||
)
|
@ -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']
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
60
devtools/client/netmonitor/netmonitor.js
Normal file
60
devtools/client/netmonitor/netmonitor.js
Normal file
@ -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");
|
@ -12,8 +12,7 @@
|
||||
|
||||
<script type="application/javascript;version=1.8"
|
||||
src="chrome://devtools/content/shared/theme-switching.js"/>
|
||||
<script type="text/javascript" src="netmonitor-controller.js"/>
|
||||
<script type="text/javascript" src="netmonitor-view.js"/>
|
||||
<script type="text/javascript" src="netmonitor.js"/>
|
||||
|
||||
<deck id="body"
|
||||
class="theme-sidebar"
|
||||
@ -26,188 +25,10 @@
|
||||
<hbox id="network-table-and-sidebar"
|
||||
class="devtools-responsive-container"
|
||||
flex="1">
|
||||
<vbox id="network-table" flex="1" class="devtools-main-content">
|
||||
<toolbar id="requests-menu-toolbar"
|
||||
class="devtools-toolbar"
|
||||
align="center">
|
||||
<hbox id="toolbar-labels" flex="1">
|
||||
<hbox id="requests-menu-status-header-box"
|
||||
class="requests-menu-header requests-menu-status"
|
||||
align="center">
|
||||
<button id="requests-menu-status-button"
|
||||
class="requests-menu-header-button requests-menu-status"
|
||||
data-key="status"
|
||||
data-localization="label=netmonitor.toolbar.status3"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-method-header-box"
|
||||
class="requests-menu-header requests-menu-method"
|
||||
align="center">
|
||||
<button id="requests-menu-method-button"
|
||||
class="requests-menu-header-button requests-menu-method"
|
||||
data-key="method"
|
||||
data-localization="label=netmonitor.toolbar.method"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-icon-and-file-header-box"
|
||||
class="requests-menu-header requests-menu-icon-and-file"
|
||||
align="center">
|
||||
<button id="requests-menu-file-button"
|
||||
class="requests-menu-header-button requests-menu-file"
|
||||
data-key="file"
|
||||
data-localization="label=netmonitor.toolbar.file"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-domain-header-box"
|
||||
class="requests-menu-header requests-menu-security-and-domain"
|
||||
align="center">
|
||||
<button id="requests-menu-domain-button"
|
||||
class="requests-menu-header-button requests-menu-security-and-domain"
|
||||
data-key="domain"
|
||||
data-localization="label=netmonitor.toolbar.domain"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-cause-header-box"
|
||||
class="requests-menu-header requests-menu-cause"
|
||||
align="center">
|
||||
<button id="requests-menu-cause-button"
|
||||
class="requests-menu-header-button requests-menu-cause"
|
||||
data-key="cause"
|
||||
data-localization="label=netmonitor.toolbar.cause"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-type-header-box"
|
||||
class="requests-menu-header requests-menu-type"
|
||||
align="center">
|
||||
<button id="requests-menu-type-button"
|
||||
class="requests-menu-header-button requests-menu-type"
|
||||
data-key="type"
|
||||
data-localization="label=netmonitor.toolbar.type"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-transferred-header-box"
|
||||
class="requests-menu-header requests-menu-transferred"
|
||||
align="center">
|
||||
<button id="requests-menu-transferred-button"
|
||||
class="requests-menu-header-button requests-menu-transferred"
|
||||
data-key="transferred"
|
||||
data-localization="label=netmonitor.toolbar.transferred"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-size-header-box"
|
||||
class="requests-menu-header requests-menu-size"
|
||||
align="center">
|
||||
<button id="requests-menu-size-button"
|
||||
class="requests-menu-header-button requests-menu-size"
|
||||
data-key="size"
|
||||
data-localization="label=netmonitor.toolbar.size"
|
||||
crop="end"
|
||||
flex="1">
|
||||
</button>
|
||||
</hbox>
|
||||
<hbox id="requests-menu-waterfall-header-box"
|
||||
class="requests-menu-header requests-menu-waterfall"
|
||||
align="center"
|
||||
flex="1">
|
||||
<button id="requests-menu-waterfall-button"
|
||||
class="requests-menu-header-button requests-menu-waterfall"
|
||||
data-key="waterfall"
|
||||
pack="start"
|
||||
data-localization="label=netmonitor.toolbar.waterfall"
|
||||
flex="1">
|
||||
<image id="requests-menu-waterfall-image"/>
|
||||
<box id="requests-menu-waterfall-label-wrapper">
|
||||
<label id="requests-menu-waterfall-label"
|
||||
class="plain requests-menu-waterfall"
|
||||
data-localization="value=netmonitor.toolbar.waterfall"/>
|
||||
</box>
|
||||
</button>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</toolbar>
|
||||
|
||||
<vbox id="requests-menu-empty-notice"
|
||||
class="side-menu-widget-empty-text">
|
||||
<hbox id="notice-reload-message" align="center">
|
||||
<label data-localization="content=netmonitor.reloadNotice1"/>
|
||||
<button id="requests-menu-reload-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
data-localization="label=netmonitor.reloadNotice2"/>
|
||||
<label data-localization="content=netmonitor.reloadNotice3"/>
|
||||
</hbox>
|
||||
<hbox id="notice-perf-message" align="center">
|
||||
<label data-localization="content=netmonitor.perfNotice1"/>
|
||||
<button id="requests-menu-perf-notice-button"
|
||||
class="devtools-toolbarbutton"
|
||||
standalone="true"
|
||||
data-localization="tooltiptext=netmonitor.perfNotice3"/>
|
||||
<label data-localization="content=netmonitor.perfNotice2"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
|
||||
<vbox id="requests-menu-contents" flex="1">
|
||||
<hbox id="requests-menu-item-template" hidden="true">
|
||||
<hbox class="requests-menu-subitem requests-menu-status"
|
||||
align="center">
|
||||
<box class="requests-menu-status-icon"/>
|
||||
<label class="plain requests-menu-status-code"
|
||||
crop="end"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-method-box"
|
||||
align="center">
|
||||
<label class="plain requests-menu-method"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-icon-and-file"
|
||||
align="center">
|
||||
<image class="requests-menu-icon" hidden="true"/>
|
||||
<label class="plain requests-menu-file"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-security-and-domain"
|
||||
align="center">
|
||||
<image class="requests-security-state-icon" />
|
||||
<label class="plain requests-menu-domain"
|
||||
crop="end"
|
||||
flex="1"/>
|
||||
</hbox>
|
||||
<hbox class="requests-menu-subitem requests-menu-cause" align="center">
|
||||
<label class="requests-menu-cause-stack" value="JS" hidden="true"/>
|
||||
<label class="plain requests-menu-cause-label" flex="1" crop="end"/>
|
||||
</hbox>
|
||||
<label class="plain requests-menu-subitem requests-menu-type"
|
||||
crop="end"/>
|
||||
<label class="plain requests-menu-subitem requests-menu-transferred"
|
||||
crop="end"/>
|
||||
<label class="plain requests-menu-subitem requests-menu-size"
|
||||
crop="end"/>
|
||||
<hbox class="requests-menu-subitem requests-menu-waterfall"
|
||||
align="center"
|
||||
flex="1">
|
||||
<hbox class="requests-menu-timings"
|
||||
align="center">
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</hbox>
|
||||
</vbox>
|
||||
</vbox>
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="network-table"
|
||||
class="devtools-main-content">
|
||||
</html:div>
|
||||
|
||||
<splitter id="network-inspector-view-splitter"
|
||||
class="devtools-side-splitter"/>
|
||||
@ -441,56 +262,8 @@
|
||||
</tabpanel>
|
||||
<tabpanel id="timings-tabpanel"
|
||||
class="tabpanel-content">
|
||||
<vbox flex="1">
|
||||
<hbox id="timings-summary-blocked"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.blocked"/>
|
||||
<hbox class="requests-menu-timings-box blocked"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-dns"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.dns"/>
|
||||
<hbox class="requests-menu-timings-box dns"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-connect"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.connect"/>
|
||||
<hbox class="requests-menu-timings-box connect"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-send"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.send"/>
|
||||
<hbox class="requests-menu-timings-box send"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-wait"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.wait"/>
|
||||
<hbox class="requests-menu-timings-box wait"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
<hbox id="timings-summary-receive"
|
||||
class="tabpanel-summary-container"
|
||||
align="center">
|
||||
<label class="plain tabpanel-summary-label"
|
||||
data-localization="content=netmonitor.timings.receive"/>
|
||||
<hbox class="requests-menu-timings-box receive"/>
|
||||
<label class="plain requests-menu-timings-total"/>
|
||||
</hbox>
|
||||
</vbox>
|
||||
<html:div xmlns="http://www.w3.org/1999/xhtml"
|
||||
id="react-timings-tabpanel-hook"/>
|
||||
</tabpanel>
|
||||
<tabpanel id="security-tabpanel"
|
||||
class="tabpanel-content">
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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"]
|
||||
});
|
||||
|
25
devtools/client/netmonitor/reducers/batching.js
Normal file
25
devtools/client/netmonitor/reducers/batching.js
Normal file
@ -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;
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
);
|
||||
|
@ -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',
|
||||
)
|
||||
|
@ -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;
|
||||
|
33
devtools/client/netmonitor/reducers/sort.js
Normal file
33
devtools/client/netmonitor/reducers/sort.js
Normal file
@ -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;
|
54
devtools/client/netmonitor/reducers/timing-markers.js
Normal file
54
devtools/client/netmonitor/reducers/timing-markers.js
Normal file
@ -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;
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
}
|
||||
|
@ -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,
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
13
devtools/client/netmonitor/selectors/filters.js
Normal file
13
devtools/client/netmonitor/selectors/filters.js
Normal file
@ -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
|
||||
};
|
@ -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,
|
||||
};
|
||||
|
@ -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',
|
||||
)
|
||||
|
119
devtools/client/netmonitor/selectors/requests.js
Normal file
119
devtools/client/netmonitor/selectors/requests.js
Normal file
@ -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,
|
||||
};
|
32
devtools/client/netmonitor/selectors/ui.js
Normal file
32
devtools/client/netmonitor/selectors/ui.js
Normal file
@ -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,
|
||||
};
|
@ -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);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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]
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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++;
|
||||
}
|
||||
|
@ -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`);
|
||||
});
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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",
|
||||
|
@ -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}`,
|
||||
|
@ -25,7 +25,7 @@ add_task(function* () {
|
||||
|
||||
yield waitForClipboardPromise(function setup() {
|
||||
RequestsMenu.contextMenu.copyUrl();
|
||||
}, requestItem.attachment.url);
|
||||
}, requestItem.url);
|
||||
|
||||
yield teardown(monitor);
|
||||
});
|
||||
|
@ -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);
|
||||
|
@ -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") {
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
@ -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.");
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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"
|
||||
|
@ -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.");
|
||||
}
|
||||
});
|
||||
|
@ -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",
|
||||
|
@ -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 =>
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
});
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user