Merge mozilla-central to mozilla-inbound

This commit is contained in:
Carsten "Tomcat" Book 2016-12-09 14:00:26 +01:00
commit a035e94835
199 changed files with 4515 additions and 3533 deletions

View File

@ -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/**

View 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"

View 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

View File

@ -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"

View 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"

View File

@ -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',

View File

@ -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();

View File

@ -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'}}

View File

@ -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"

View File

@ -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)

View 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,
};

View File

@ -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,
};
}

View File

@ -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
);

View File

@ -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',
)

View File

@ -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,
};

View 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,
};

View 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
};

View 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
};
};

View File

@ -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,
};

View File

@ -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);

View File

@ -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',

View 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);

View 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);

View 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);

View 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;

View 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,
};

View 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);

View 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',
)

View File

@ -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);

View File

@ -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: () => {

View File

@ -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);

View File

@ -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);

View File

@ -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}
*/

View File

@ -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.
*

View File

@ -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));
}

View 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) {

View File

@ -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();

View 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;

View 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',
)

View File

@ -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']

View File

@ -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;

View File

@ -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;

View 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");

View File

@ -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">

View File

@ -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();

View File

@ -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) {

View File

@ -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"]
});

View 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;

View File

@ -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;
}

View File

@ -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,
})
);

View File

@ -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',
)

View File

@ -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;

View 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;

View 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;

View File

@ -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;
}

View File

@ -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
};
}

View File

@ -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

View 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
};

View File

@ -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,
};

View File

@ -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',
)

View 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,
};

View 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,
};

View File

@ -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);
},
/**

View File

@ -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;
}

View File

@ -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;

View File

@ -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]

View File

@ -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);
});

View File

@ -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);

View File

@ -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);

View File

@ -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));
}
});

View File

@ -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",

View File

@ -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++;
}

View File

@ -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`);
});

View File

@ -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) {

View File

@ -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",

View File

@ -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}`,

View File

@ -25,7 +25,7 @@ add_task(function* () {
yield waitForClipboardPromise(function setup() {
RequestsMenu.contextMenu.copyUrl();
}, requestItem.attachment.url);
}, requestItem.url);
yield teardown(monitor);
});

View File

@ -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);

View File

@ -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") {

View File

@ -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"

View File

@ -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"

View File

@ -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);
}
}
}
});

View File

@ -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);
}
}
}
}
});

View File

@ -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.");
}
});

View File

@ -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.");
}
});

View File

@ -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) {

View File

@ -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.");
}
});

View File

@ -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.");
}

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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",

View File

@ -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"

View File

@ -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.");
}
});

View File

@ -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",

View File

@ -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 =>

View File

@ -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");
}
}

View File

@ -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