Bug 1317649 - Implement Cookies Panel;r=Honza,jsnajdr,rickychien

use properties-view
show other Cookie properties

MozReview-Commit-ID: 7526Hm2ORbL

--HG--
extra : rebase_source : 2e2850fd8daf6646648cc3e63c2a53cd381e90f3
This commit is contained in:
Fred Lin 2016-11-24 17:05:31 +08:00
parent 5cc6eaefca
commit 61790522a3
11 changed files with 220 additions and 144 deletions

View File

@ -5,8 +5,8 @@
"use strict";
const {
TOGGLE_REQUEST_FILTER_TYPE,
ENABLE_REQUEST_FILTER_TYPE_ONLY,
TOGGLE_REQUEST_FILTER_TYPE,
SET_REQUEST_FILTER_TEXT,
} = require("../constants");
@ -40,7 +40,7 @@ function enableRequestFilterTypeOnly(filter) {
}
/**
* Set filter text.
* Set filter text in toolbar.
*
* @param {string} text - A filter text is going to be set
*/
@ -52,7 +52,7 @@ function setRequestFilterText(text) {
}
module.exports = {
toggleRequestFilterType,
enableRequestFilterTypeOnly,
toggleRequestFilterType,
setRequestFilterText,
};

View File

@ -8,18 +8,16 @@ 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);

View File

@ -10,7 +10,7 @@ const {
} = 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"));
@ -28,7 +28,7 @@ function Toolbar() {
),
span({ className: "devtools-toolbar-group" },
SummaryButton(),
SearchBox(),
ToolbarSearchBox(),
ToggleButton()
)
);

View File

@ -3,22 +3,20 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/* eslint-disable mozilla/reject-some-requires */
/* globals window, dumpn, $, gNetwork */
/* globals window, dumpn, $ */
"use strict";
const promise = require("promise");
const EventEmitter = require("devtools/shared/event-emitter");
const { Heritage } = require("devtools/client/shared/widgets/view-helpers");
const { Task } = require("devtools/shared/task");
const { ToolSidebar } = require("devtools/client/framework/sidebar");
const { VariablesView } = require("resource://devtools/client/shared/widgets/VariablesView.jsm");
const { EVENTS } = require("./events");
const { L10N } = require("./l10n");
const { Filters } = require("./filter-predicates");
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 HeadersPanel = createFactory(require("./shared/components/headers-panel"));
const ParamsPanel = createFactory(require("./shared/components/params-panel"));
const PreviewPanel = createFactory(require("./shared/components/preview-panel"));
@ -26,18 +24,6 @@ const ResponsePanel = createFactory(require("./shared/components/response-panel"
const SecurityPanel = createFactory(require("./shared/components/security-panel"));
const TimingsPanel = createFactory(require("./shared/components/timings-panel"));
const GENERIC_VARIABLES_VIEW_SETTINGS = {
lazyEmpty: true,
// ms
lazyEmptyDelay: 10,
searchEnabled: true,
editableValueTooltip: "",
editableNameTooltip: "",
preventDisableOnChange: true,
preventDescriptorModifiers: true,
eval: () => {}
};
/**
* Functions handling the requests details view.
*/
@ -70,6 +56,13 @@ DetailsView.prototype = {
initialize: function (store) {
dumpn("Initializing the DetailsView");
this._cookiesPanelNode = $("#react-cookies-tabpanel-hook");
ReactDOM.render(Provider(
{ store },
CookiesPanel()
), this._cookiesPanelNode);
this._headersPanelNode = $("#react-headers-tabpanel-hook");
ReactDOM.render(Provider(
@ -117,16 +110,6 @@ DetailsView.prototype = {
disableTelemetry: true,
showAllTabsMenu: true
});
this._cookies = new VariablesView($("#all-cookies"),
Heritage.extend(GENERIC_VARIABLES_VIEW_SETTINGS, {
emptyText: L10N.getStr("cookiesEmptyText"),
searchPlaceholder: L10N.getStr("cookiesFilterText")
}));
this._requestCookies = L10N.getStr("requestCookies");
this._responseCookies = L10N.getStr("responseCookies");
$("tabpanels", this.widget).addEventListener("select", this._onTabSelect);
},
@ -135,8 +118,9 @@ DetailsView.prototype = {
*/
destroy: function () {
dumpn("Destroying the DetailsView");
ReactDOM.unmountComponentAtNode(this._paramsPanelNode);
ReactDOM.unmountComponentAtNode(this._cookiesPanelNode);
ReactDOM.unmountComponentAtNode(this._headersPanelNode);
ReactDOM.unmountComponentAtNode(this._paramsPanelNode);
ReactDOM.unmountComponentAtNode(this._previewPanelNode);
ReactDOM.unmountComponentAtNode(this._responsePanelNode);
ReactDOM.unmountComponentAtNode(this._securityPanelNode);
@ -177,8 +161,6 @@ DetailsView.prototype = {
this.widget.selectedIndex = 0;
}
this._cookies.empty();
this._dataSrc = { src: data, populated: [] };
this._onTabSelect();
window.emit(EVENTS.NETWORKDETAILSVIEW_POPULATED);
@ -211,14 +193,6 @@ DetailsView.prototype = {
}
Task.spawn(function* () {
viewState.updating[tab] = true;
switch (tab) {
// "Cookies"
case 1:
yield view._setResponseCookies(src.responseCookies);
yield view._setRequestCookies(src.requestCookies);
break;
}
viewState.updating[tab] = false;
}).then(() => {
if (tab == this.widget.selectedIndex) {
@ -239,78 +213,7 @@ DetailsView.prototype = {
}, e => console.error(e));
},
/**
* 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;
}
}),
_dataSrc: null,
_cookies: null,
_requestCookies: "",
_responseCookies: ""
};
exports.DetailsView = DetailsView;

View File

@ -129,7 +129,8 @@
<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"

View File

@ -6,8 +6,8 @@
const I = require("devtools/client/shared/vendor/immutable");
const {
TOGGLE_REQUEST_FILTER_TYPE,
ENABLE_REQUEST_FILTER_TYPE_ONLY,
TOGGLE_REQUEST_FILTER_TYPE,
SET_REQUEST_FILTER_TEXT,
} = require("../constants");
@ -67,12 +67,12 @@ function enableRequestFilterTypeOnly(state, action) {
function filters(state = new Filters(), action) {
switch (action.type) {
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 TOGGLE_REQUEST_FILTER_TYPE:
return state.set("requestFilterTypes",
toggleRequestFilterType(state.requestFilterTypes, action));
case SET_REQUEST_FILTER_TEXT:
return state.set("requestFilterText", action.text);
default:

View File

@ -28,10 +28,10 @@ const {
const {
getActiveFilters,
getSortedRequests,
getDisplayedRequests,
getRequestById,
getSelectedRequest,
getSortedRequests,
} = require("./selectors/index");
// ms
@ -224,7 +224,7 @@ RequestsMenuView.prototype = {
isXHR,
cause,
fromCache,
fromServiceWorker
fromServiceWorker,
},
true
);
@ -235,13 +235,15 @@ RequestsMenuView.prototype = {
updateRequest: Task.async(function* (id, data) {
const action = Actions.updateRequest(id, data, true);
yield this.store.dispatch(action);
let {
responseContent,
responseCookies,
responseHeaders,
requestCookies,
requestHeaders,
requestPostData,
responseContent,
responseHeaders,
} = action.data;
let request = getRequestById(this.store.getState(), action.id);
if (requestHeaders && requestHeaders.headers && requestHeaders.headers.length) {
let headers = yield fetchHeaders(
@ -267,26 +269,23 @@ RequestsMenuView.prototype = {
}
}
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);
}
responseContent.content.text = response;
payload.responseContent = responseContent;
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);
}
}
@ -306,6 +305,51 @@ RequestsMenuView.prototype = {
yield this.store.dispatch(Actions.updateRequest(action.id, payload, true));
}
// Fetch request and 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.
if (requestCookies) {
let reqCookies = [];
// request store cookies in requestCookies or requestCookies.cookies
let cookies = requestCookies.cookies ?
requestCookies.cookies : requestCookies;
// make sure cookies is iterable
if (typeof cookies[Symbol.iterator] === "function") {
for (let cookie of cookies) {
reqCookies.push(Object.assign({}, cookie, {
value: yield gNetwork.getString(cookie.value),
}));
}
if (reqCookies.length) {
yield this.store.dispatch(Actions.updateRequest(
action.id,
{ requestCookies: reqCookies },
true));
}
}
}
if (responseCookies) {
let resCookies = [];
// response store cookies in responseCookies or responseCookies.cookies
let cookies = responseCookies.cookies ?
responseCookies.cookies : responseCookies;
// make sure cookies is iterable
if (typeof cookies[Symbol.iterator] === "function") {
for (let cookie of cookies) {
resCookies.push(Object.assign({}, cookie, {
value: yield gNetwork.getString(cookie.value),
}));
}
if (resCookies.length) {
yield this.store.dispatch(Actions.updateRequest(
action.id,
{ responseCookies: resCookies },
true));
}
}
}
}),
/**

View File

@ -97,6 +97,32 @@ const getSelectedRequest = createSelector(
({ selectedId, requests }) => selectedId ? requests.get(selectedId) : null
);
const getSelectedRequestCookies = createSelector(
getSelectedRequest,
selectedRequest => {
// request store cookies in requestCookies or requestCookies.cookies
if (selectedRequest && selectedRequest.requestCookies) {
return selectedRequest.requestCookies.cookies ?
selectedRequest.requestCookies.cookies : selectedRequest.requestCookies;
}
return [];
}
);
const getSelectedResponseCookies = createSelector(
getSelectedRequest,
selectedRequest => {
// response store cookies in responseCookies or responseCookies.cookies
if (selectedRequest && selectedRequest.responseCookies) {
return selectedRequest.responseCookies.cookies ?
selectedRequest.responseCookies.cookies : selectedRequest.responseCookies;
}
return [];
}
);
function getRequestById(state, id) {
return state.requests.requests.get(id);
}
@ -111,5 +137,7 @@ module.exports = {
getDisplayedRequestsSummary,
getRequestById,
getSelectedRequest,
getSelectedRequestCookies,
getSelectedResponseCookies,
getSortedRequests,
};

View File

@ -0,0 +1,99 @@
/* 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 {
getSelectedRequestCookies,
getSelectedResponseCookies,
} = require("../../selectors/index");
// Component
const PropertiesView = createFactory(require("./properties-view"));
const { div } = DOM;
const COOKIES_EMPTY_TEXT = L10N.getStr("cookiesEmptyText");
const COOKIES_FILTER_TEXT = L10N.getStr("cookiesFilterText");
const REQUEST_COOKIES = L10N.getStr("requestCookies");
const RESPONSE_COOKIES = L10N.getStr("responseCookies");
const SECTION_NAMES = [
RESPONSE_COOKIES,
REQUEST_COOKIES,
];
/*
* Cookies panel component
* This tab lists full details of any cookies sent with the request or response
*/
function CookiesPanel({
request,
response,
}) {
if (!response.length && !request.length) {
return div({ className: "empty-notice" },
COOKIES_EMPTY_TEXT
);
}
let object = {};
if (response.length) {
object[RESPONSE_COOKIES] = getProperties(response);
}
if (request.length) {
object[REQUEST_COOKIES] = getProperties(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 getProperties(arr) {
return arr.reduce((map, obj) => {
// Generally cookies object contains only name and value properties and can
// be rendered as name: value pair.
// When there are more properties in cookies object such as extra or path,
// We will pass the object to display these extra information
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 => ({
request: getSelectedRequestCookies(state),
response: getSelectedResponseCookies(state),
})
)(CookiesPanel);

View File

@ -3,6 +3,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'cookies-panel.js',
'editor.js',
'headers-panel.js',
'params-panel.js',

View File

@ -1272,6 +1272,7 @@
* 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-headers-tabpanel-hook,
#react-params-tabpanel-hook,
#react-preview-tabpanel-hook,
@ -1286,6 +1287,7 @@
}
/* For vbox */
#react-cookies-tabpanel-hook,
#react-headers-tabpanel-hook,
#react-params-tabpanel-hook,
#react-preview-tabpanel-hook,