Bug 859051 - Implement cache panel; r=Honza

--HG--
extra : histedit_source : 48d73a800ca6a9d09d70a5b80e7c86caf912c560
This commit is contained in:
David McCurry 2018-05-01 05:42:00 +03:00
parent 7521594a56
commit 771befd505
12 changed files with 403 additions and 4 deletions

View File

@ -595,6 +595,10 @@ netmonitor.tab.headers=Headers
# in the network details pane identifying the cookies tab.
netmonitor.tab.cookies=Cookies
# LOCALIZATION NOTE (netmonitor.tab.cache): This is the label displayed
# in the network details pane identifying the cache tab.
netmonitor.tab.cache=Cache
# LOCALIZATION NOTE (netmonitor.tab.params): This is the label displayed
# in the network details pane identifying the params tab.
netmonitor.tab.params=Params
@ -1086,3 +1090,39 @@ netmonitor.label.dropHarFiles = Drop HAR files here
# LOCALIZATION NOTE (netmonitor.label.har): This is a label used
# as a tooltip for toolbar drop-down button with HAR actions
netmonitor.label.har=HAR Export/Import
# LOCALIZATION NOTE (netmonitor.cache.cache): This is the label text for the parent
# node in the TreeView.
netmonitor.cache.cache=Cache
# LOCALIZATION NOTE (netmonitor.cache.empty): This is the text displayed when cache
# information is not available.
netmonitor.cache.empty=No cache information
# LOCALIZATION NOTE (netmonitor.cache.notAvailable): This is the text displayed under
# a node that has no information available.
netmonitor.cache.notAvailable=Not Available
# LOCALIZATION NOTE (netmonitor.cache.dataSize): This is the label text for
# the datasize of the cached object.
netmonitor.cache.dataSize=Data Size
# LOCALIZATION NOTE (netmonitor.cache.expires): This is the label text for the
# expires time of the cached object.
netmonitor.cache.expires=Expires
# LOCALIZATION NOTE (netmonitor.cache.fetchCount): This is the label text for the
# fetch count of the cached object.
netmonitor.cache.fetchCount=Fetch Count
# LOCALIZATION NOTE (netmonitor.cache.lastFetched): This is the label text for the
# last fetched date/time of the cached object.
netmonitor.cache.lastFetched=Last Fetched
# LOCALIZATION NOTE (netmonitor.cache.lastModified): This is the label text for the
# last modified date/time of the cached object.
netmonitor.cache.lastModified=Last Modified
# LOCALIZATION NOTE (netmonitor.cache.device): This is the label text for the device
# where a cached object was fetched from (e.g. "disk").
netmonitor.cache.device=Device

View File

@ -0,0 +1,131 @@
/* 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 { Component, createFactory } = require("devtools/client/shared/vendor/react");
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
const dom = require("devtools/client/shared/vendor/react-dom-factories");
const { L10N } = require("../utils/l10n");
const { fetchNetworkUpdatePacket } = require("../utils/request-utils");
// Components
const TreeViewClass = require("devtools/client/shared/components/tree/TreeView");
const PropertiesView = createFactory(require("./PropertiesView"));
const { div, input } = dom;
const CACHE = L10N.getStr("netmonitor.cache.cache");
const DATA_SIZE = L10N.getStr("netmonitor.cache.dataSize");
const EXPIRES = L10N.getStr("netmonitor.cache.expires");
const FETCH_COUNT = L10N.getStr("netmonitor.cache.fetchCount");
const LAST_FETCHED = L10N.getStr("netmonitor.cache.lastFetched");
const LAST_MODIFIED = L10N.getStr("netmonitor.cache.lastModified");
const DEVICE = L10N.getStr("netmonitor.cache.device");
const NOT_AVAILABLE = L10N.getStr("netmonitor.cache.notAvailable");
const EMPTY = L10N.getStr("netmonitor.cache.empty");
/**
* Cache panel component
* This tab lists full details of any cache information of the response.
*/
class CachePanel extends Component {
static get propTypes() {
return {
connector: PropTypes.object.isRequired,
openLink: PropTypes.func,
request: PropTypes.object.isRequired,
};
}
componentDidMount() {
let { connector, request } = this.props;
fetchNetworkUpdatePacket(connector.requestData, request, ["responseCache"]);
}
componentWillReceiveProps(nextProps) {
let { connector, request } = nextProps;
fetchNetworkUpdatePacket(connector.requestData, request, ["responseCache"]);
}
renderSummary(label, value) {
return (
div({ className: "tabpanel-summary-container cache-summary"},
div({
className: "tabpanel-summary-label cache-summary-label",
}, label + ":"),
input({
className: "tabpanel-summary-value textbox-input devtools-monospace",
readOnly: true,
value
}),
)
);
}
getProperties(responseCache) {
let responseCacheObj;
let cacheObj;
try {
responseCacheObj = Object.assign({}, responseCache);
responseCacheObj = responseCacheObj.cache;
} catch (e) {
return null;
}
try {
cacheObj = Object.assign({}, responseCacheObj);
} catch (e) {
return null;
}
return cacheObj;
}
getDate(timestamp) {
if (!timestamp) {
return null;
}
let d = new Date(parseInt(timestamp, 10) * 1000);
return d.toLocaleDateString() + " " + d.toLocaleTimeString();
}
render() {
let {
request,
openLink,
} = this.props;
let { responseCache } = request;
let object;
let cache = this.getProperties(responseCache);
if (cache.lastFetched || cache.fetchCount || cache.dataSize
|| cache.lastModified | cache.expires || cache.device) {
object = {
[CACHE]: {
[LAST_FETCHED]: this.getDate(cache.lastFetched) || NOT_AVAILABLE,
[FETCH_COUNT]: cache.fetchCount || NOT_AVAILABLE,
[DATA_SIZE]: cache.dataSize || NOT_AVAILABLE,
[LAST_MODIFIED]: this.getDate(cache.lastModified) || NOT_AVAILABLE,
[EXPIRES]: this.getDate(cache.expires) || NOT_AVAILABLE,
[DEVICE]: cache.device || NOT_AVAILABLE
}
};
} else {
return div({ className: "empty-notice" },
EMPTY
);
}
return div({ className: "panel-container security-panel" },
PropertiesView({
object,
enableFilter: false,
expandedNodes: TreeViewClass.getExpandedNodes(object),
openLink,
})
);
}
}
module.exports = CachePanel;

View File

@ -15,12 +15,14 @@ const TabPanel = createFactory(require("devtools/client/shared/components/tabs/T
const CookiesPanel = createFactory(require("./CookiesPanel"));
const HeadersPanel = createFactory(require("./HeadersPanel"));
const ParamsPanel = createFactory(require("./ParamsPanel"));
const CachePanel = createFactory(require("./CachePanel"));
const ResponsePanel = createFactory(require("./ResponsePanel"));
const SecurityPanel = createFactory(require("./SecurityPanel"));
const StackTracePanel = createFactory(require("./StackTracePanel"));
const TimingsPanel = createFactory(require("./TimingsPanel"));
const COLLAPSE_DETAILS_PANE = L10N.getStr("collapseDetailsPane");
const CACHE_TITLE = L10N.getStr("netmonitor.tab.cache");
const COOKIES_TITLE = L10N.getStr("netmonitor.tab.cookies");
const HEADERS_TITLE = L10N.getStr("netmonitor.tab.headers");
const PARAMS_TITLE = L10N.getStr("netmonitor.tab.params");
@ -94,6 +96,13 @@ function TabboxPanel({
},
ResponsePanel({ request, openLink, connector }),
),
(request.fromCache || request.status == "304") &&
TabPanel({
id: PANELS.CACHE,
title: CACHE_TITLE,
},
CachePanel({ request, openLink, connector }),
),
TabPanel({
id: PANELS.TIMINGS,
title: TIMINGS_TITLE,

View File

@ -4,6 +4,7 @@
DevToolsModules(
'App.js',
'CachePanel.js',
'CookiesPanel.js',
'CustomRequestPanel.js',
'DropHarHandler.js',

View File

@ -109,6 +109,7 @@ class FirefoxDataProvider {
requestCookies,
requestHeaders,
requestPostData,
responseCache,
} = data;
// fetch request detail contents in parallel
@ -119,6 +120,7 @@ class FirefoxDataProvider {
postDataObj,
requestCookiesObj,
responseCookiesObj,
responseCacheObj,
] = await Promise.all([
this.fetchResponseContent(responseContent),
this.fetchRequestHeaders(requestHeaders),
@ -126,6 +128,7 @@ class FirefoxDataProvider {
this.fetchPostData(requestPostData),
this.fetchRequestCookies(requestCookies),
this.fetchResponseCookies(responseCookies),
this.fetchResponseCache(responseCache),
]);
let payload = Object.assign({},
@ -135,7 +138,8 @@ class FirefoxDataProvider {
responseHeadersObj,
postDataObj,
requestCookiesObj,
responseCookiesObj
responseCookiesObj,
responseCacheObj,
);
if (this.actionsEnabled && this.actions.updateRequest) {
@ -244,6 +248,15 @@ class FirefoxDataProvider {
return payload;
}
async fetchResponseCache(responseCache) {
let payload = {};
if (responseCache) {
payload.responseCache = await responseCache;
payload.responseCacheAvailable = false;
}
return payload;
}
/**
* Public API used by the Toolbox: Tells if there is still any pending request.
*
@ -577,6 +590,18 @@ class FirefoxDataProvider {
return payload.responseCookies;
}
/**
* Handles additional information received for a "responseCache" packet.
* @param {object} response the message received from the server.
*/
async onResponseCache(response) {
let payload = await this.updateRequest(response.from, {
responseCache: response
});
this.emit(EVENTS.RECEIVED_RESPONSE_CACHE, response.from);
return payload.responseCache;
}
/**
* Handles additional information received via "getResponseContent" request.
*

View File

@ -97,6 +97,9 @@ const EVENTS = {
// When stack-trace finishes receiving.
RECEIVED_EVENT_STACKTRACE: "NetMonitor:NetworkEventUpdated:StackTrace",
UPDATING_RESPONSE_CACHE: "NetMonitor:NetworkEventUpdating:ResponseCache",
RECEIVED_RESPONSE_CACHE: "NetMonitor:NetworkEventUpdated:ResponseCache",
// Fired once the connection is established
CONNECTED: "connected",
@ -136,6 +139,8 @@ const UPDATE_PROPS = [
"responseCookiesAvailable",
"responseContent",
"responseContentAvailable",
"responseCache",
"responseCacheAvailable",
"formDataSections",
"stacktrace",
];
@ -145,6 +150,7 @@ const PANELS = {
HEADERS: "headers",
PARAMS: "params",
RESPONSE: "response",
CACHE: "cache",
SECURITY: "security",
STACK_TRACE: "stack-trace",
TIMINGS: "timings",

View File

@ -335,8 +335,15 @@ NewConsoleOutputWrapper.prototype = {
// that networkInfo.updates has all we need.
// Note that 'requestPostData' is sent only for POST requests, so we need
// to count with that.
// 'fetchCacheDescriptor' will also cause a network update and increment
// the number of networkInfo.updates
const NUMBER_OF_NETWORK_UPDATE = 8;
let expectedLength = NUMBER_OF_NETWORK_UPDATE;
if (this.hud.proxy.webConsoleClient.traits.fetchCacheDescriptor
&& res.networkInfo.updates.includes("responseCache")) {
expectedLength++;
}
if (res.networkInfo.updates.includes("requestPostData")) {
expectedLength++;
}

View File

@ -90,6 +90,7 @@ function WebConsoleActor(connection, parentActor) {
evaluateJSAsync: true,
transferredResponseSize: true,
selectedObjectActor: true, // 44+
fetchCacheDescriptor: true,
};
}
@ -2096,6 +2097,19 @@ NetworkEventActor.prototype =
};
},
/**
* The "getResponseCache" packet type handler.
*
* @return object
* The cache packet - network cache information.
*/
onGetResponseCache: function() {
return {
from: this.actorID,
cache: this._response.responseCache,
};
},
/**
* The "getResponseCookies" packet type handler.
*
@ -2352,6 +2366,16 @@ NetworkEventActor.prototype =
this.conn.send(packet);
},
addResponseCache: function(content) {
this._response.responseCache = content.responseCache;
let packet = {
from: this.actorID,
type: "networkEventUpdate",
updateType: "responseCache",
};
this.conn.send(packet);
},
/**
* Add network event timing information.
*
@ -2400,6 +2424,7 @@ NetworkEventActor.prototype.requestTypes =
"getRequestPostData": NetworkEventActor.prototype.onGetRequestPostData,
"getResponseHeaders": NetworkEventActor.prototype.onGetResponseHeaders,
"getResponseCookies": NetworkEventActor.prototype.onGetResponseCookies,
"getResponseCache": NetworkEventActor.prototype.onGetResponseCache,
"getResponseContent": NetworkEventActor.prototype.onGetResponseContent,
"getEventTimings": NetworkEventActor.prototype.onGetEventTimings,
"getSecurityInfo": NetworkEventActor.prototype.onGetSecurityInfo,

View File

@ -0,0 +1,117 @@
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set ft= javascript ts=2 et sw=2 tw=80: */
/* 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 Services = require("Services");
const {Ci} = require("chrome");
loader.lazyRequireGetter(this, "NetworkHelper",
"devtools/shared/webconsole/network-helper");
/**
* Module to fetch cache objects from CacheStorageService
* and return them as an object.
*/
exports.CacheEntry = {
/**
* Flag for cache session being initialized.
*/
isCacheSessionInitialized: false,
/**
* Cache session object.
*/
cacheSession: null,
/**
* Initializes our cache session / cache storage session.
*/
initializeCacheSession: function(request) {
try {
let cacheService = Services.cache2;
if (cacheService) {
let loadContext = NetworkHelper.getRequestLoadContext(request);
if (!loadContext) { // Get default load context if we can't fetch.
loadContext = Services.loadContextInfo.default;
}
this.cacheSession =
cacheService.diskCacheStorage(loadContext, false);
this.isCacheSessionInitialized = true;
}
} catch (e) {
this.isCacheSessionInitialized = false;
}
},
/**
* Parses a cache descriptor returned from the backend into a
* usable object.
*
* @param Object descriptor The descriptor from the backend.
*/
parseCacheDescriptor: function(descriptor) {
let descriptorObj = {};
try {
if (descriptor.storageDataSize) {
descriptorObj.dataSize = descriptor.storageDataSize;
}
} catch (e) {
// We just need to handle this in case it's a js file of 0B.
}
if (descriptor.expirationTime) {
descriptorObj.expires = descriptor.expirationTime;
}
if (descriptor.fetchCount) {
descriptorObj.fetchCount = descriptor.fetchCount;
}
if (descriptor.lastFetched) {
descriptorObj.lastFetched = descriptor.lastFetched;
}
if (descriptor.lastModified) {
descriptorObj.lastModified = descriptor.lastModified;
}
if (descriptor.deviceID) {
descriptorObj.device = descriptor.deviceID;
}
return descriptorObj;
},
/**
* Does the fetch for the cache descriptor from the session.
*
* @param string request
* The request object.
* @param Function onCacheDescriptorAvailable
* callback function.
*/
getCacheEntry: function(request, onCacheDescriptorAvailable) {
if (!this.isCacheSessionInitialized) {
this.initializeCacheSession(request);
}
if (this.cacheSession) {
let uri = NetworkHelper.nsIURL(request.URI.spec);
this.cacheSession.asyncOpenURI(
uri,
"",
Ci.nsICacheStorage.OPEN_SECRETLY,
{
onCacheEntryCheck: (entry, appcache) => {
return Ci.nsICacheEntryOpenCallback.ENTRY_WANTED;
},
onCacheEntryAvailable: (descriptor, isnew, appcache, status) => {
if (descriptor) {
let descriptorObj = this.parseCacheDescriptor(descriptor);
onCacheDescriptorAvailable(descriptorObj);
} else {
onCacheDescriptorAvailable(null);
}
}
}
);
} else {
onCacheDescriptorAvailable(null);
}
}
};

View File

@ -5,6 +5,7 @@
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
DevToolsModules(
'cache-entry.js',
'clipboard.js',
'stack.js',
)

View File

@ -167,6 +167,9 @@ WebConsoleClient.prototype = {
case "securityInfo":
networkInfo.securityInfo = packet.state;
break;
case "responseCache":
networkInfo.response.responseCache = packet.responseCache;
break;
}
this.emit("networkEventUpdate", {
@ -546,6 +549,24 @@ WebConsoleClient.prototype = {
return this._client.request(packet, onResponse);
},
/**
* Retrieve the response cache from the given NetworkEventActor
*
* @param string actor
* The NetworkEventActor ID.
* @param function onResponse
* The function invoked when the response is received.
* @return request
* Request object that implements both Promise and EventEmitter interfaces.
*/
getResponseCache: function(actor, onResponse) {
let packet = {
to: actor,
type: "getResponseCache",
};
return this._client.request(packet, onResponse);
},
/**
* Retrieve the timing information for the given NetworkEventActor.
*

View File

@ -21,7 +21,7 @@ loader.lazyServiceGetter(this, "gActivityDistributor",
"@mozilla.org/network/http-activity-distributor;1",
"nsIHttpActivityDistributor");
const {NetworkThrottleManager} = require("devtools/shared/webconsole/throttle");
const {CacheEntry} = require("devtools/shared/platform/cache-entry");
// Network logging
// The maximum uint32 value.
@ -518,6 +518,19 @@ NetworkResponseListener.prototype = {
this.httpActivity.owner.addSecurityInfo(info);
}),
/**
* Fetches cache information from CacheEntry
* @private
*/
_fetchCacheInformation: function() {
let httpActivity = this.httpActivity;
CacheEntry.getCacheEntry(this.request, (descriptor) => {
httpActivity.owner.addResponseCache({
responseCache: descriptor
});
});
},
/**
* Handle the onStopRequest by closing the sink output stream.
*
@ -571,7 +584,6 @@ NetworkResponseListener.prototype = {
return;
}
this._foundOpenResponse = true;
this.owner.openResponses.delete(channel);
this.httpActivity.owner.addResponseHeaders(openResponse.headers);
@ -592,6 +604,9 @@ NetworkResponseListener.prototype = {
this.setAsyncListener(this.sink.inputStream, null);
this._findOpenResponse();
if (this.request.fromCache || this.httpActivity.responseStatus == 304) {
this._fetchCacheInformation();
}
if (!this.httpActivity.discardResponseBody && this.receivedData.length) {
this._onComplete(this.receivedData);
@ -1901,7 +1916,8 @@ NetworkEventActorProxy.prototype = {
// Listeners for new network event data coming from the NetworkMonitor.
let methods = ["addRequestHeaders", "addRequestCookies", "addRequestPostData",
"addResponseStart", "addSecurityInfo", "addResponseHeaders",
"addResponseCookies", "addResponseContent", "addEventTimings"];
"addResponseCookies", "addResponseContent", "addResponseCache",
"addEventTimings"];
let factory = NetworkEventActorProxy.methodFactory;
for (let method of methods) {
NetworkEventActorProxy.prototype[method] = factory(method);