--- a/devtools/client/netmonitor/actions/filters.js
+++ b/devtools/client/netmonitor/actions/filters.js
@@ -1,17 +1,17 @@
/* 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 {
+ ENABLE_REQUEST_FILTER_TYPE_ONLY,
TOGGLE_REQUEST_FILTER_TYPE,
- ENABLE_REQUEST_FILTER_TYPE_ONLY,
SET_REQUEST_FILTER_TEXT,
} = require("../constants");
/**
* Toggle an existing filter type state.
* If type 'all' is specified, all the other filter types are set to false.
* Available filter types are defined in filters reducer.
*
@@ -35,24 +35,24 @@ function toggleRequestFilterType(filter)
function enableRequestFilterTypeOnly(filter) {
return {
type: ENABLE_REQUEST_FILTER_TYPE_ONLY,
filter,
};
}
/**
- * Set filter text.
+ * Set filter text in toolbar.
*
* @param {string} text - A filter text is going to be set
*/
function setRequestFilterText(text) {
return {
type: SET_REQUEST_FILTER_TEXT,
text,
};
}
module.exports = {
+ enableRequestFilterTypeOnly,
toggleRequestFilterType,
- enableRequestFilterTypeOnly,
setRequestFilterText,
};
--- a/devtools/client/netmonitor/components/search-box.js
+++ b/devtools/client/netmonitor/components/search-box.js
@@ -3,23 +3,21 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { connect } = require("devtools/client/shared/vendor/react-redux");
const SearchBox = require("devtools/client/shared/components/search-box");
const { L10N } = require("../l10n");
const Actions = require("../actions/index");
-const { FREETEXT_FILTER_SEARCH_DELAY } = require("../constants");
+const { FILTER_SEARCH_DELAY } = require("../constants");
module.exports = connect(
(state) => ({
- delay: FREETEXT_FILTER_SEARCH_DELAY,
+ delay: FILTER_SEARCH_DELAY,
keyShortcut: L10N.getStr("netmonitor.toolbar.filterFreetext.key"),
placeholder: L10N.getStr("netmonitor.toolbar.filterFreetext.label"),
type: "filter",
}),
(dispatch) => ({
- onChange: (url) => {
- dispatch(Actions.setRequestFilterText(url));
- },
+ onChange: text => dispatch(Actions.setRequestFilterText(text)),
})
)(SearchBox);
--- a/devtools/client/netmonitor/components/toolbar.js
+++ b/devtools/client/netmonitor/components/toolbar.js
@@ -5,17 +5,17 @@
"use strict";
const {
createFactory,
DOM,
} = require("devtools/client/shared/vendor/react");
const ClearButton = createFactory(require("./clear-button"));
const FilterButtons = createFactory(require("./filter-buttons"));
-const SearchBox = createFactory(require("./search-box"));
+const ToolbarSearchBox = createFactory(require("./search-box"));
const SummaryButton = createFactory(require("./summary-button"));
const ToggleButton = createFactory(require("./toggle-button"));
const { span } = DOM;
/*
* Network monitor toolbar component
* Toolbar contains a set of useful tools to control network requests
@@ -23,15 +23,15 @@ const { span } = DOM;
function Toolbar() {
return span({ className: "devtools-toolbar devtools-toolbar-container" },
span({ className: "devtools-toolbar-group" },
ClearButton(),
FilterButtons()
),
span({ className: "devtools-toolbar-group" },
SummaryButton(),
- SearchBox(),
+ ToolbarSearchBox(),
ToggleButton()
)
);
}
module.exports = Toolbar;
--- a/devtools/client/netmonitor/constants.js
+++ b/devtools/client/netmonitor/constants.js
@@ -1,17 +1,17 @@
/* 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 general = {
CONTENT_SIZE_DECIMALS: 2,
- FREETEXT_FILTER_SEARCH_DELAY: 200,
+ FILTER_SEARCH_DELAY: 200,
REQUEST_TIME_DECIMALS: 2,
};
const actionTypes = {
ADD_REQUEST: "ADD_REQUEST",
ADD_TIMING_MARKER: "ADD_TIMING_MARKER",
BATCH_ACTIONS: "BATCH_ACTIONS",
BATCH_ENABLE: "BATCH_ENABLE",
--- a/devtools/client/netmonitor/details-view.js
+++ b/devtools/client/netmonitor/details-view.js
@@ -24,16 +24,17 @@ const {
getFormDataSections,
getUrlBaseName,
getUrlQuery,
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 CookiesPanel = createFactory(require("./shared/components/cookies-panel"));
const PreviewPanel = createFactory(require("./shared/components/preview-panel"));
const SecurityPanel = createFactory(require("./shared/components/security-panel"));
const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
// 100 KB in bytes
const SOURCE_SYNTAX_HIGHLIGHT_MAX_FILE_SIZE = 102400;
const HEADERS_SIZE_DECIMALS = 3;
const CONTENT_MIME_TYPE_MAPPINGS = {
@@ -89,16 +90,23 @@ DetailsView.prototype = {
},
/**
* Initialization function, called when the network monitor is started.
*/
initialize: function (store) {
dumpn("Initializing the DetailsView");
+ this._cookiesPanelNode = $("#react-cookies-tabpanel-hook");
+
+ ReactDOM.render(Provider(
+ { store },
+ CookiesPanel()
+ ), this._cookiesPanelNode);
+
this._previewPanelNode = $("#react-preview-tabpanel-hook");
ReactDOM.render(Provider(
{ store },
PreviewPanel()
), this._previewPanelNode);
this._securityPanelNode = $("#react-security-tabpanel-hook");
@@ -121,21 +129,16 @@ DetailsView.prototype = {
showAllTabsMenu: true
});
this._headers = new VariablesView($("#all-headers"),
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
emptyText: L10N.getStr("headersEmptyText"),
searchPlaceholder: L10N.getStr("headersFilterText")
}));
- this._cookies = new VariablesView($("#all-cookies"),
- Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
- emptyText: L10N.getStr("cookiesEmptyText"),
- searchPlaceholder: L10N.getStr("cookiesFilterText")
- }));
this._params = new VariablesView($("#request-params"),
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
emptyText: L10N.getStr("paramsEmptyText"),
searchPlaceholder: L10N.getStr("paramsFilterText")
}));
this._json = new VariablesView($("#response-content-json"),
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
onlyEnumVisible: true,
@@ -144,27 +147,26 @@ DetailsView.prototype = {
VariablesViewController.attach(this._json);
this._paramsQueryString = L10N.getStr("paramsQueryString");
this._paramsFormData = L10N.getStr("paramsFormData");
this._paramsPostPayload = L10N.getStr("paramsPostPayload");
this._requestHeaders = L10N.getStr("requestHeaders");
this._requestHeadersFromUpload = L10N.getStr("requestHeadersFromUpload");
this._responseHeaders = L10N.getStr("responseHeaders");
- this._requestCookies = L10N.getStr("requestCookies");
- this._responseCookies = L10N.getStr("responseCookies");
$("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
},
/**
* Destruction function, called when the network monitor is closed.
*/
destroy: function () {
dumpn("Destroying the DetailsView");
+ ReactDOM.unmountComponentAtNode(this._cookiesPanelNode);
ReactDOM.unmountComponentAtNode(this._previewPanelNode);
ReactDOM.unmountComponentAtNode(this._securityPanelNode);
ReactDOM.unmountComponentAtNode(this._timingsPanelNode);
this.sidebar.destroy();
$("tabpanels", this.widget).removeEventListener("select",
this._onTabSelect);
},
@@ -204,17 +206,16 @@ DetailsView.prototype = {
if (!isHtml && this.widget.selectedPanel === $("#preview-tabpanel") ||
!hasSecurityInfo && this.widget.selectedPanel ===
$("#security-tabpanel")) {
this.widget.selectedIndex = 0;
}
this._headers.empty();
- this._cookies.empty();
this._params.empty();
this._json.empty();
this._dataSrc = { src: data, populated: [] };
this._onTabSelect();
window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
return promise.resolve();
@@ -250,21 +251,16 @@ DetailsView.prototype = {
// "Headers"
case 0:
yield view._setSummary(src);
yield view._setResponseHeaders(src.responseHeaders);
yield view._setRequestHeaders(
src.requestHeaders,
src.requestHeadersFromUploadStream);
break;
- // "Cookies"
- case 1:
- yield view._setResponseCookies(src.responseCookies);
- yield view._setRequestCookies(src.requestCookies);
- break;
// "Params"
case 2:
yield view._setRequestGetParams(src.url);
yield view._setRequestPostParams(
src.requestHeaders,
src.requestHeadersFromUploadStream,
src.requestPostData);
break;
@@ -413,84 +409,16 @@ DetailsView.prototype = {
for (let header of response.headers) {
let headerVar = headersScope.addItem(header.name, {}, {relaxed: true});
let headerValue = yield gNetwork.getString(header.value);
headerVar.setGrip(headerValue);
}
}),
/**
- * Sets the network request cookies shown in this view.
- *
- * @param object response
- * The message received from the server.
- * @return object
- * A promise that is resolved when the request cookies are set.
- */
- _setRequestCookies: Task.async(function* (response) {
- if (response && response.cookies.length) {
- response.cookies.sort((a, b) => a.name > b.name);
- yield this._addCookies(this._requestCookies, response);
- }
- }),
-
- /**
- * Sets the network response cookies shown in this view.
- *
- * @param object response
- * The message received from the server.
- * @return object
- * A promise that is resolved when the response cookies are set.
- */
- _setResponseCookies: Task.async(function* (response) {
- if (response && response.cookies.length) {
- yield this._addCookies(this._responseCookies, response);
- }
- }),
-
- /**
- * Populates the cookies container in this view with the specified data.
- *
- * @param string name
- * The type of cookies to populate (request or response).
- * @param object response
- * The message received from the server.
- * @return object
- * Returns a promise that resolves upon the adding of cookies.
- */
- _addCookies: Task.async(function* (name, response) {
- let cookiesScope = this._cookies.addScope(name);
- cookiesScope.expanded = true;
-
- for (let cookie of response.cookies) {
- let cookieVar = cookiesScope.addItem(cookie.name, {}, {relaxed: true});
- let cookieValue = yield gNetwork.getString(cookie.value);
- cookieVar.setGrip(cookieValue);
-
- // By default the cookie name and value are shown. If this is the only
- // information available, then nothing else is to be displayed.
- let cookieProps = Object.keys(cookie);
- if (cookieProps.length == 2) {
- continue;
- }
-
- // Display any other information other than the cookie name and value
- // which may be available.
- let rawObject = Object.create(null);
- let otherProps = cookieProps.filter(e => e != "name" && e != "value");
- for (let prop of otherProps) {
- rawObject[prop] = cookie[prop];
- }
- cookieVar.populate(rawObject);
- cookieVar.twisty = true;
- cookieVar.expanded = true;
- }
- }),
-
- /**
* Sets the network request get params shown in this view.
*
* @param string url
* The request's url.
*/
_setRequestGetParams: function (url) {
let query = getUrlQuery(url);
if (query) {
@@ -699,21 +627,18 @@ DetailsView.prototype = {
}
}
window.emit(EVENTS.RESPONSE_BODY_DISPLAYED);
}),
_dataSrc: null,
_headers: null,
- _cookies: null,
_params: null,
_json: null,
_paramsQueryString: "",
_paramsFormData: "",
_paramsPostPayload: "",
_requestHeaders: "",
- _responseHeaders: "",
- _requestCookies: "",
- _responseCookies: ""
+ _responseHeaders: ""
};
exports.DetailsView = DetailsView;
--- a/devtools/client/netmonitor/netmonitor.xul
+++ b/devtools/client/netmonitor/netmonitor.xul
@@ -202,17 +202,18 @@
</vbox>
</hbox>
<vbox id="all-headers" flex="1"/>
</vbox>
</tabpanel>
<tabpanel id="cookies-tabpanel"
class="tabpanel-content">
<vbox flex="1">
- <vbox id="all-cookies" flex="1"/>
+ <html:div xmlns="http://www.w3.org/1999/xhtml"
+ id="react-cookies-tabpanel-hook"/>
</vbox>
</tabpanel>
<tabpanel id="params-tabpanel"
class="tabpanel-content">
<vbox flex="1">
<vbox id="request-params-box" flex="1" hidden="true">
<vbox id="request-params" flex="1"/>
</vbox>
--- a/devtools/client/netmonitor/reducers/filters.js
+++ b/devtools/client/netmonitor/reducers/filters.js
@@ -1,18 +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 I = require("devtools/client/shared/vendor/immutable");
const {
+ ENABLE_REQUEST_FILTER_TYPE_ONLY,
TOGGLE_REQUEST_FILTER_TYPE,
- ENABLE_REQUEST_FILTER_TYPE_ONLY,
SET_REQUEST_FILTER_TEXT,
} = require("../constants");
const FilterTypes = I.Record({
all: false,
html: false,
css: false,
js: false,
@@ -23,16 +23,17 @@ const FilterTypes = I.Record({
flash: false,
ws: false,
other: false,
});
const Filters = I.Record({
requestFilterTypes: new FilterTypes({ all: true }),
requestFilterText: "",
+ cookiesFilterText: "",
});
function toggleRequestFilterType(state, action) {
let { filter } = action;
let newState;
// Ignore unknown filter type
if (!state.has(filter)) {
@@ -62,22 +63,22 @@ function enableRequestFilterTypeOnly(sta
return state;
}
return new FilterTypes({ [filter]: true });
}
function filters(state = new Filters(), action) {
switch (action.type) {
+ case ENABLE_REQUEST_FILTER_TYPE_ONLY:
+ return state.set("requestFilterTypes",
+ enableRequestFilterTypeOnly(state.requestFilterTypes, action));
case TOGGLE_REQUEST_FILTER_TYPE:
return state.set("requestFilterTypes",
toggleRequestFilterType(state.requestFilterTypes, action));
- case ENABLE_REQUEST_FILTER_TYPE_ONLY:
- return state.set("requestFilterTypes",
- enableRequestFilterTypeOnly(state.requestFilterTypes, action));
case SET_REQUEST_FILTER_TEXT:
return state.set("requestFilterText", action.text);
default:
return state;
}
}
module.exports = filters;
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -17,26 +17,26 @@ const ReactDOM = require("devtools/clien
const { Provider } = require("devtools/client/shared/vendor/react-redux");
const RequestList = createFactory(require("./components/request-list"));
const RequestListContextMenu = require("./request-list-context-menu");
const Actions = require("./actions/index");
const { Prefs } = require("./prefs");
const {
formDataURI,
+ loadCauseString,
writeHeaderText,
- loadCauseString
} = require("./request-utils");
const {
getActiveFilters,
- getSortedRequests,
getDisplayedRequests,
getRequestById,
- getSelectedRequest
+ getSelectedRequest,
+ getSortedRequests,
} = require("./selectors/index");
// ms
const RESIZE_REFRESH_RATE = 50;
// A smart store watcher to notify store changes as necessary
function storeWatcher(initialValue, reduceValue, onChange) {
let currentValue = initialValue;
@@ -187,52 +187,90 @@ RequestsMenuView.prototype = {
id,
{
startedMillis,
method,
url,
isXHR,
cause,
fromCache,
- fromServiceWorker
+ fromServiceWorker,
},
true
);
this.store.dispatch(action).then(() => window.emit(EVENTS.REQUEST_ADDED, action.id));
},
+ // Fetch response cookies long value.
+ // Actor does not provide full sized cookie value when the value is too long
+ // To display values correctly, we need fetch them in each request.
+ fetchResponseCookiesLongValue: function* (request) {
+ let { responseCookies } = request;
+ if (!responseCookies || !responseCookies.cookies) {
+ return null;
+ }
+
+ let rspCookies = [];
+ // response store cookies in responseCookies or responseCookies.cookies
+ let cookies = responseCookies.cookies ? responseCookies.cookies : responseCookies;
+ for (let cookie of cookies) {
+ rspCookies.push(Object.assign({}, cookie, {
+ value: yield gNetwork.getString(cookie.value),
+ }));
+ }
+ return rspCookies.length ? { responseCookies: rspCookies } : null;
+ },
+
+ // Fetch request cookies long value.
+ // Actor does not provide full sized cookie value when the value is too long
+ // To display values correctly, we need fetch them in each request.
+ fetchRequestCookiesLongValue: function* (request) {
+ let { requestCookies } = request;
+ if (!requestCookies || !requestCookies.cookies) {
+ return null;
+ }
+
+ let reqCookies = [];
+ // request store cookies in requestCookies or requestCookies.cookies
+ let cookies = requestCookies.cookies ? requestCookies.cookies : requestCookies;
+ for (let cookie of cookies) {
+ reqCookies.push(Object.assign({}, cookie, {
+ value: yield gNetwork.getString(cookie.value),
+ }));
+ }
+ return reqCookies.length ? { requestCookies: reqCookies } : null;
+ },
+
updateRequest: Task.async(function* (id, data) {
const action = Actions.updateRequest(id, data, true);
yield this.store.dispatch(action);
let { responseContent, requestPostData } = action.data;
+ let request = getRequestById(this.store.getState(), action.id);
- if (responseContent && responseContent.content) {
- let request = getRequestById(this.store.getState(), action.id);
- if (request) {
- let { mimeType } = request;
- let { text, encoding } = responseContent.content;
- let response = yield gNetwork.getString(text);
- let payload = {};
+ if (request && responseContent && responseContent.content) {
+ let { mimeType } = request;
+ let { text, encoding } = responseContent.content;
+ let response = yield gNetwork.getString(text);
+ let payload = {};
- if (mimeType.includes("image/")) {
- payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
- }
+ if (mimeType.includes("image/")) {
+ payload.responseContentDataUri = formDataURI(mimeType, encoding, response);
+ }
- if (mimeType.includes("text/")) {
- responseContent.content.text = response;
- payload.responseContent = responseContent;
- }
+ if (mimeType.includes("text/")) {
+ responseContent.content.text = response;
+ payload.responseContent = responseContent;
+ }
- yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
+ yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
- if (mimeType.includes("image/")) {
- window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
- }
+ if (mimeType.includes("image/")) {
+ window.emit(EVENTS.RESPONSE_IMAGE_THUMBNAIL_DISPLAYED);
}
}
// Search the POST data upload stream for request headers and add
// them as a separate property, different from the classic headers.
if (requestPostData && requestPostData.postData) {
let { text } = requestPostData.postData;
let postData = yield gNetwork.getString(text);
@@ -242,16 +280,31 @@ RequestsMenuView.prototype = {
}, 0);
let payload = {};
requestPostData.postData.text = postData;
payload.requestPostData = Object.assign({}, requestPostData);
payload.requestHeadersFromUploadStream = { headers, headersSize };
yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
}
+
+ if (request) {
+ // Fetch responseCookies long value when any response cookie is exist
+ let responseObj = yield this.fetchResponseCookiesLongValue(request);
+ if (responseObj) {
+ yield this.store.dispatch(Actions.updateRequest(
+ action.id, responseObj, true));
+ }
+ // Fetch requestCookies long value when any request cookie is exist
+ let requestObj = yield this.fetchRequestCookiesLongValue(request);
+ if (requestObj) {
+ yield this.store.dispatch(Actions.updateRequest(
+ action.id, requestObj, true));
+ }
+ }
}),
/**
* Disable batched updates. Used by tests.
*/
set lazyUpdate(value) {
this.store.dispatch(Actions.batchEnable(value));
},
--- a/devtools/client/netmonitor/selectors/requests.js
+++ b/devtools/client/netmonitor/selectors/requests.js
@@ -99,24 +99,60 @@ const getSelectedRequest = createSelecto
if (!requests.selectedId) {
return null;
}
return requests.requests.find(r => r.id === requests.selectedId);
}
);
+const getSelectedResponseCookies = createSelector(
+ getSelectedRequest,
+ selectedRequest => {
+ if (!selectedRequest) {
+ return [];
+ }
+
+ // response store cookies in responseCookies or responseCookies.cookies
+ if (selectedRequest.responseCookies) {
+ return selectedRequest.responseCookies.cookies ?
+ selectedRequest.responseCookies.cookies : selectedRequest.responseCookies;
+ }
+
+ return [];
+ }
+);
+
+const getSelectedRequestCookies = createSelector(
+ getSelectedRequest,
+ selectedRequest => {
+ if (!selectedRequest) {
+ return [];
+ }
+
+ // request store cookies in requestCookies or requestCookies.cookies
+ if (selectedRequest.requestCookies) {
+ return selectedRequest.requestCookies.cookies ?
+ selectedRequest.requestCookies.cookies : selectedRequest.requestCookies;
+ }
+
+ return [];
+ }
+);
+
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);
}
module.exports = {
getDisplayedRequestById,
getDisplayedRequests,
getDisplayedRequestsSummary,
getRequestById,
getSelectedRequest,
+ getSelectedResponseCookies,
+ getSelectedRequestCookies,
getSortedRequests,
};
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/shared/components/cookies-panel.js
@@ -0,0 +1,93 @@
+/* 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,
+ DOM,
+ PropTypes,
+} = require("devtools/client/shared/vendor/react");
+const { connect } = require("devtools/client/shared/vendor/react-redux");
+const { L10N } = require("../../l10n");
+const { getSelectedResponseCookies, getSelectedRequestCookies } = require("../../selectors/index");
+
+const { div, input } = DOM;
+const PropertiesView = createFactory(require("./properties-view"));
+
+const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
+const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
+const COOKIES_RESPONSE_COOKIES_TEXT = L10N.getStr("responseCookies");
+const COOKIES_REQUEST_COOKIES_TEXT = L10N.getStr("requestCookies");
+const SECTION_NAMES = [
+ COOKIES_RESPONSE_COOKIES_TEXT,
+ COOKIES_REQUEST_COOKIES_TEXT,
+];
+
+/**
+ * This template represents 'Cookies' panel displayed when the user
+ * tap the request menu item in the network panel. It's responsible for rendering
+ * sent and received cookies.
+ */
+function CookiesPanel({
+ request,
+ response,
+}) {
+ if (response.length === 0 && request.length === 0) {
+ return div({ className: "empty-notice" },
+ COOKIES_EMPTY_TEXT
+ );
+ }
+
+ let object = {};
+ if (response.length) {
+ object[COOKIES_RESPONSE_COOKIES_TEXT] = arrayToDict(response);
+ }
+ if (request.length) {
+ object[COOKIES_REQUEST_COOKIES_TEXT] = arrayToDict(request);
+ }
+
+ return (
+ PropertiesView({
+ object,
+ filterPlaceHolder: COOKIES_FILTER_TEXT,
+ sectionNames: SECTION_NAMES,
+ })
+ );
+}
+
+CookiesPanel.displayName = "CookiesPanel";
+
+CookiesPanel.propTypes = {
+ request: PropTypes.array.isRequired,
+ response: PropTypes.array.isRequired,
+};
+
+/**
+ * Mapping array to dict for TreeView usage.
+ * Since TreeView only support Object(dict) format.
+ *
+ * @param {Object[]} arr - key-value pair array like cookies or params
+ * @returns {Object}
+ */
+function arrayToDict(arr) {
+ return arr.reduce(function (map, obj) {
+ // Display any other information other than the cookie name and value
+ // which may be available.
+ if (Object.keys(obj).length > 2) {
+ map[obj.name] = Object.assign({}, obj);
+ delete map[obj.name].name;
+ } else {
+ map[obj.name] = obj.value;
+ }
+ return map;
+ }, {});
+}
+
+module.exports = connect(
+ state => ({
+ response: getSelectedResponseCookies(state),
+ request: getSelectedRequestCookies(state),
+ })
+)(CookiesPanel);
--- a/devtools/client/netmonitor/shared/components/moz.build
+++ b/devtools/client/netmonitor/shared/components/moz.build
@@ -1,11 +1,12 @@
# 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(
+ 'cookies-panel.js',
'editor.js',
'preview-panel.js',
'properties-view.js',
'security-panel.js',
'timings-panel.js',
)
--- a/devtools/client/themes/netmonitor.css
+++ b/devtools/client/themes/netmonitor.css
@@ -1127,18 +1127,31 @@
display: flex;
flex-direction: column;
}
.properties-view .searchbox-section {
flex: 0 1 auto;
}
-.properties-view .devtools-searchbox {
- padding: 0;
+.theme-light .treeTable .treeValueCell .textbox-input {
+ color: var(--theme-highlight-purple)
+}
+
+.theme-dark .treeTable .treeValueCell .textbox-input {
+ color: var(--theme-highlight-gray);
+}
+
+.theme-light .treeTable .treeValueCell .textbox-input:focus,
+.theme-dark .treeTable .treeValueCell .textbox-input:focus {
+ color: inherit;
+}
+
+.theme-firebug .treeTable .treeLabel {
+ color: var(--theme-body-color);
}
.properties-view .devtools-searchbox input {
margin: 1px 3px;
}
.tree-container {
position: relative;
@@ -1184,31 +1197,75 @@
.editor-container,
.editor-mount,
.editor-mount iframe {
border: none;
width: 100%;
height: 100%;
}
+.detailsTreeView .treeTable {
+ display: block;
+ overflow-y: auto;
+ /* Minus 72px * 3 for toolbox height + tabpanel height + searchbox height */
+ max-height: calc(100vh - 72px);
+}
+
+.detailsTreeView .devtools-searchbox input {
+ margin-inline-end: 6px;
+}
+
+.detailsTreeView .devtools-searchbox,
+.detailsTreeView .treeTable .treeSection {
+ width: 100%;
+ background-color: var(--theme-toolbar-background);
+}
+
+.detailsTreeView .treeTable .treeSection {
+ height: 22px;
+}
+
+.detailsTreeView .devtools-searchbox,
+.detailsTreeView .treeTable tr:not(:last-child) td:not(:empty) {
+ border-bottom: 1px solid var(--theme-splitter-color);
+}
+
+.detailsTreeView .treeTable .treeSection > * {
+ vertical-align: middle;
+}
+
+.detailsTreeView .treeTable .treeRow.treeSection > .treeLabelCell > .treeLabel,
+.detailsTreeView .treeTable .treeRow.treeSection > .treeLabelCell > .treeLabel:hover {
+ font-weight: 400;
+ color: var(--theme-body-color-alt);
+}
+
+.empty-notice {
+ color: var(--theme-body-color-alt);
+ padding: 3px 8px;
+}
+
/*
* FIXME: normal html block element cannot fill outer XUL element
* This workaround should be removed after netmonitor is migrated to react
*/
+#react-cookies-tabpanel-hook,
#react-preview-tabpanel-hook,
#react-security-tabpanel-hook,
#react-timings-tabpanel-hook,
#network-statistics-charts,
#primed-cache-chart,
#empty-cache-chart {
- display: -moz-box;
-moz-box-flex: 1;
+ display: flex;
+ flex-flow: column;
}
/* For vbox */
+#react-cookies-tabpanel-hook,
#react-preview-tabpanel-hook,
#react-security-tabpanel-hook,
#react-timings-tabpanel-hook,
#primed-cache-chart,
#empty-cache-chart {
-moz-box-orient: vertical;
}