Bug 859041 - Display timing interval divisions (ms ticks) in the timeline, r=rcampbell

This commit is contained in:
Victor Porof 2013-05-10 12:01:05 +03:00
Родитель 3d97bcc986
Коммит 7e66a760e4
8 изменённых файлов: 440 добавлений и 96 удалений

Просмотреть файл

@ -5,11 +5,19 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const HTML_NS = "http://www.w3.org/1999/xhtml";
const EPSILON = 0.001;
const RESIZE_REFRESH_RATE = 50; // ms
const REQUESTS_REFRESH_RATE = 50; // ms
const REQUESTS_HEADERS_SAFE_BOUNDS = 30; // px
const REQUESTS_WATERFALL_SAFE_BOUNDS = 100; // px
const REQUESTS_WATERFALL_BACKGROUND_PATTERN = [5, 250, 1000, 2000]; // ms
const REQUESTS_WATERFALL_SAFE_BOUNDS = 90; // px
const REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN = 60; // px
const REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE = 5; // ms
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES = 3;
const REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN = 10; // px
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN = 10; // byte
const REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD = 16; // byte
const DEFAULT_HTTP_VERSION = "HTTP/1.1";
const HEADERS_SIZE_DECIMALS = 3;
const CONTENT_SIZE_DECIMALS = 2;
@ -48,8 +56,6 @@ const GENERIC_VARIABLES_VIEW_SETTINGS = {
switch: () => {}
};
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
/**
* Object defining the network monitor view components.
*/
@ -356,8 +362,8 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
if (!this.lazyUpdate) {
return void this._flushRequests();
}
window.clearTimeout(this._updateTimeout);
this._updateTimeout = window.setTimeout(this._flushRequests, REQUESTS_REFRESH_RATE);
// Allow requests to settle down first.
drain("update-requests", REQUESTS_REFRESH_RATE, () => this._flushRequests());
},
/**
@ -584,38 +590,19 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
},
/**
* Rescales and redraws all the waterfalls in this container.
* Rescales and redraws all the waterfall views in this container.
*
* @param boolean aReset
* True if this container's width was changed.
*/
_flushWaterfallViews: function NVRM__flushWaterfallViews(aReset) {
// To avoid expensive operations like getBoundingClientRect(), the
// waterfalls width is cached. However, in certain scenarios like when
// the window is resized, this needs to be invalidated.
// To avoid expensive operations like getBoundingClientRect() and
// rebuilding the waterfall background each time a new request comes in,
// stuff is cached. However, in certain scenarios like when the window
// is resized, this needs to be invalidated.
if (aReset) {
this._cachedWaterfallWidth = 0;
let table = $("#network-table");
let toolbar = $("#requests-menu-toolbar");
let columns = [
[".requests-menu-waterfall", "waterfall-overflows"],
[".requests-menu-size", "size-overflows"],
[".requests-menu-type", "type-overflows"],
[".requests-menu-domain", "domain-overflows"]
];
// Flush headers.
columns.forEach(([, attribute]) => table.removeAttribute(attribute));
let availableWidth = toolbar.getBoundingClientRect().width;
// Hide overflowing columns.
columns.forEach(([className, attribute]) => {
let bounds = $(".requests-menu-header" + className).getBoundingClientRect();
if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) {
table.setAttribute(attribute, "");
}
});
this._hideOverflowingColumns();
}
// Determine the scaling to be applied to all the waterfalls so that
@ -624,6 +611,11 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
let longestWidth = this._lastRequestEndedMillis - this._firstRequestStartedMillis;
let scale = Math.min(Math.max(availableWidth / longestWidth, EPSILON), 1);
// Redraw and set the canvas background for each waterfall view.
this._showWaterfallDivisionLabels(scale);
this._drawWaterfallBackground(scale);
this._flushWaterfallBackgrounds();
// Apply CSS transforms to each waterfall in this container totalTime
// accurately translate and resize as needed.
for (let [, { target, attachment }] of this._cache) {
@ -651,6 +643,145 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
}
},
/**
* Creates the labels displayed on the waterfall header in this container.
*
* @param number aScale
* The current waterfall scale.
*/
_showWaterfallDivisionLabels: function NVRM__showWaterfallDivisionLabels(aScale) {
let container = $("#requests-menu-waterfall-header-box");
let availableWidth = this._waterfallWidth - REQUESTS_WATERFALL_SAFE_BOUNDS;
// Nuke all existing labels.
while (container.hasChildNodes()) {
container.firstChild.remove();
}
// Build new millisecond tick labels...
let timingStep = REQUESTS_WATERFALL_HEADER_TICKS_MULTIPLE;
let optimalTickIntervalFound = false;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
let scaledStep = aScale * timingStep;
if (scaledStep < REQUESTS_WATERFALL_HEADER_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
optimalTickIntervalFound = true;
// Insert one label for each division on the current scale.
let fragment = document.createDocumentFragment();
for (let x = 0; x < availableWidth; x += scaledStep) {
let divisionMS = (x / aScale).toFixed(0);
let translateX = "translateX(" + (x | 0) + "px)";
let node = document.createElement("label");
let text = L10N.getFormatStr("networkMenu.divisionMS", divisionMS);
node.className = "plain requests-menu-timings-division";
node.style.transform = translateX;
node.setAttribute("value", text);
fragment.appendChild(node);
}
container.appendChild(fragment);
}
},
/**
* Creates the background displayed on each waterfall view in this container.
*
* @param number aScale
* The current waterfall scale.
*/
_drawWaterfallBackground: function NVRM__drawWaterfallBackground(aScale) {
if (!this._canvas || !this._ctx) {
this._canvas = document.createElementNS(HTML_NS, "canvas");
this._ctx = this._canvas.getContext("2d");
}
let canvas = this._canvas;
let ctx = this._ctx;
// Nuke the context.
let canvasWidth = canvas.width = this._waterfallWidth;
let canvasHeight = canvas.height = 1; // Awww yeah, 1px, repeats on Y axis.
// Start over.
let imageData = ctx.createImageData(canvasWidth, canvasHeight);
let pixelArray = imageData.data;
let buf = new ArrayBuffer(pixelArray.length);
let buf8 = new Uint8ClampedArray(buf);
let data32 = new Uint32Array(buf);
// Build new millisecond tick lines...
let timingStep = REQUESTS_WATERFALL_BACKGROUND_TICKS_MULTIPLE;
let alphaComponent = REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_MIN;
let optimalTickIntervalFound = false;
while (!optimalTickIntervalFound) {
// Ignore any divisions that would end up being too close to each other.
let scaledStep = aScale * timingStep;
if (scaledStep < REQUESTS_WATERFALL_BACKGROUND_TICKS_SPACING_MIN) {
timingStep <<= 1;
continue;
}
optimalTickIntervalFound = true;
// Insert one pixel for each division on each scale.
for (let i = 1; i <= REQUESTS_WATERFALL_BACKGROUND_TICKS_SCALES; i++) {
let increment = scaledStep * Math.pow(2, i);
for (let x = 0; x < canvasWidth; x += increment) {
data32[x | 0] = (alphaComponent << 24) | (255 << 16) | (255 << 8) | 255;
}
alphaComponent += REQUESTS_WATERFALL_BACKGROUND_TICKS_OPACITY_ADD;
}
}
// Flush the image data and cache the waterfall background.
pixelArray.set(buf8);
ctx.putImageData(imageData, 0, 0);
this._cachedWaterfallBackground = "url(" + canvas.toDataURL() + ")";
},
/**
* Reapplies the current waterfall background on all request items.
*/
_flushWaterfallBackgrounds: function NVRM__flushWaterfallBackgrounds() {
for (let [, { target }] of this._cache) {
let waterfallNode = $(".requests-menu-waterfall", target);
waterfallNode.style.backgroundImage = this._cachedWaterfallBackground;
}
},
/**
* Hides the overflowing columns in the requests table.
*/
_hideOverflowingColumns: function NVRM__hideOverflowingColumns() {
let table = $("#network-table");
let toolbar = $("#requests-menu-toolbar");
let columns = [
["#requests-menu-waterfall-header-box", "waterfall-overflows"],
["#requests-menu-size-header-label", "size-overflows"],
["#requests-menu-type-header-label", "type-overflows"],
["#requests-menu-domain-header-label", "domain-overflows"]
];
// Flush headers.
columns.forEach(([, attribute]) => table.removeAttribute(attribute));
let availableWidth = toolbar.getBoundingClientRect().width;
// Hide the columns.
columns.forEach(([id, attribute]) => {
let bounds = $(id).getBoundingClientRect();
if (bounds.right > availableWidth - REQUESTS_HEADERS_SAFE_BOUNDS) {
table.setAttribute(attribute, "");
}
});
},
/**
* Function called each time a network request item is removed.
*
@ -685,7 +816,8 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
* The resize listener for this container's window.
*/
_onResize: function NVRM__onResize(e) {
this._flushWaterfallViews(true);
// Allow requests to settle down first.
drain("resize-events", RESIZE_REFRESH_RATE, () => this._flushWaterfallViews(true));
},
/**
@ -721,7 +853,7 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
get _waterfallWidth() {
if (this._cachedWaterfallWidth == 0) {
let container = $("#requests-menu-toolbar");
let waterfall = $("#requests-menu-waterfall-label");
let waterfall = $("#requests-menu-waterfall-header-box");
let containerBounds = container.getBoundingClientRect();
let waterfallBounds = waterfall.getBoundingClientRect();
this._cachedWaterfallWidth = containerBounds.width - waterfallBounds.left;
@ -730,11 +862,15 @@ create({ constructor: RequestsMenuView, proto: MenuContainer.prototype }, {
},
_cache: null,
_canvas: null,
_ctx: null,
_cachedWaterfallWidth: 0,
_cachedWaterfallBackground: null,
_firstRequestStartedMillis: -1,
_lastRequestEndedMillis: -1,
_updateQueue: [],
_updateTimeout: null
_updateTimeout: null,
_resizeTimeout: null
});
/**
@ -1213,6 +1349,22 @@ create({ constructor: NetworkDetailsView, proto: MenuContainer.prototype }, {
_responseCookies: ""
});
/**
* DOM query helper.
*/
function $(aSelector, aTarget = document) aTarget.querySelector(aSelector);
/**
* Helper for draining a rapid succession of events and invoking a callback
* once everything settles down.
*/
function drain(aId, aWait, aCallback) {
window.clearTimeout(drain.store.get(aId));
drain.store.set(aId, window.setTimeout(aCallback, aWait));
}
drain.store = new Map();
/**
* Preliminary setup for the NetMonitorView object.
*/

Просмотреть файл

@ -22,30 +22,35 @@
<toolbar id="requests-menu-toolbar"
class="devtools-toolbar"
align="center">
<label id="requests-menu-status-and-method-label"
class="plain requests-menu-header requests-menu-status-and-method"
value="&netmonitorUI.toolbar.method;"
crop="end"/>
<label id="requests-menu-file-label"
class="plain requests-menu-header requests-menu-file"
value="&netmonitorUI.toolbar.file;"
crop="end"/>
<label id="requests-menu-domain-label"
class="plain requests-menu-header requests-menu-domain"
value="&netmonitorUI.toolbar.domain;"
crop="end"/>
<label id="requests-menu-type-label"
class="plain requests-menu-header requests-menu-type"
value="&netmonitorUI.toolbar.type;"
crop="end"/>
<label id="requests-menu-size-label"
class="plain requests-menu-header requests-menu-size"
value="&netmonitorUI.toolbar.size;"
crop="end"/>
<label id="requests-menu-waterfall-label"
class="plain requests-menu-header requests-menu-waterfall"
value="&netmonitorUI.toolbar.waterfall;"
crop="end"/>
<hbox id="toolbar-labels" flex="1">
<label id="requests-menu-status-and-method-header-label"
class="plain requests-menu-header requests-menu-status-and-method"
value="&netmonitorUI.toolbar.method;"
crop="end"/>
<label id="requests-menu-file-header-label"
class="plain requests-menu-header requests-menu-file"
value="&netmonitorUI.toolbar.file;"
crop="end"/>
<label id="requests-menu-domain-header-label"
class="plain requests-menu-header requests-menu-domain"
value="&netmonitorUI.toolbar.domain;"
crop="end"/>
<label id="requests-menu-type-header-label"
class="plain requests-menu-header requests-menu-type"
value="&netmonitorUI.toolbar.type;"
crop="end"/>
<label id="requests-menu-size-header-label"
class="plain requests-menu-header requests-menu-size"
value="&netmonitorUI.toolbar.size;"
crop="end"/>
<hbox id="requests-menu-waterfall-header-box"
class="plain requests-menu-header requests-menu-waterfall">
<label id="requests-menu-timeline-label"
class="plain requests-menu-timeline"
value="&netmonitorUI.toolbar.waterfall;"
crop="end"/>
</hbox>
</hbox>
<spacer id="toolbar-spacer" flex="1"/>
<toolbarbutton id="details-pane-toggle"
class="devtools-toolbarbutton"

Просмотреть файл

@ -25,6 +25,7 @@ MOCHITEST_BROWSER_TESTS = \
browser_net_post-data.js \
browser_net_jsonp.js \
browser_net_json-long.js \
browser_net_timeline_ticks.js \
head.js \
$(NULL)

Просмотреть файл

@ -0,0 +1,137 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
/**
* Tests if timeline correctly displays interval divisions.
*/
function test() {
initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
info("Starting test... ");
let { document, L10N, NetMonitorView } = aMonitor.panelWin;
let { RequestsMenu } = NetMonitorView;
RequestsMenu.lazyUpdate = false;
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), false,
"An timeline label should be displayed when the frontend is opened.");
ok(document.querySelectorAll(".requests-menu-timings-division").length == 0,
"No tick labels should be displayed when the frontend is opened.");
ok(!RequestsMenu._canvas,
"No canvas should be created when the frontend is opened.");
ok(!RequestsMenu._ctx,
"No 2d context should be created when the frontend is opened.");
waitForNetworkEvents(aMonitor, 1).then(() => {
is(document.querySelector(".requests-menu-empty-notice")
.hasAttribute("hidden"), true,
"The timeline label should be hidden after the first request.");
ok(document.querySelectorAll(".requests-menu-timings-division").length >= 3,
"There should be at least 3 tick labels in the network requests header.");
is(document.querySelectorAll(".requests-menu-timings-division")[0]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 0),
"The first tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[1]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 80),
"The second tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[2]
.getAttribute("value"), L10N.getFormatStr("networkMenu.divisionMS", 160),
"The third tick label has an incorrect value");
is(document.querySelectorAll(".requests-menu-timings-division")[0]
.style.transform, "translateX(0px)",
"The first tick label has an incorrect translation");
is(document.querySelectorAll(".requests-menu-timings-division")[1]
.style.transform, "translateX(80px)",
"The second tick label has an incorrect translation");
is(document.querySelectorAll(".requests-menu-timings-division")[2]
.style.transform, "translateX(160px)",
"The third tick label has an incorrect translation");
ok(RequestsMenu._canvas,
"A canvas should be created after the first request.");
ok(RequestsMenu._ctx,
"A 2d context should be created after the first request.");
let imageData = RequestsMenu._ctx.getImageData(0, 0, 161, 1);
ok(imageData, "The image data should have been created.");
let data = imageData.data;
ok(data, "The image data should contain a pixel array.");
ok( hasPixelAt(0), "The tick at 0 is should not be empty.");
ok(!hasPixelAt(1), "The tick at 1 is should be empty.");
ok(!hasPixelAt(19), "The tick at 19 is should be empty.");
ok( hasPixelAt(20), "The tick at 20 is should not be empty.");
ok(!hasPixelAt(21), "The tick at 21 is should be empty.");
ok(!hasPixelAt(39), "The tick at 39 is should be empty.");
ok( hasPixelAt(40), "The tick at 40 is should not be empty.");
ok(!hasPixelAt(41), "The tick at 41 is should be empty.");
ok(!hasPixelAt(59), "The tick at 59 is should be empty.");
ok( hasPixelAt(60), "The tick at 60 is should not be empty.");
ok(!hasPixelAt(61), "The tick at 61 is should be empty.");
ok(!hasPixelAt(79), "The tick at 79 is should be empty.");
ok( hasPixelAt(80), "The tick at 80 is should not be empty.");
ok(!hasPixelAt(81), "The tick at 81 is should be empty.");
ok(!hasPixelAt(159), "The tick at 159 is should be empty.");
ok( hasPixelAt(160), "The tick at 160 is should not be empty.");
ok(!hasPixelAt(161), "The tick at 161 is should be empty.");
ok(isPixelBrighterAtThan(0, 20),
"The tick at 0 should be brighter than the one at 20");
ok(isPixelBrighterAtThan(40, 20),
"The tick at 40 should be brighter than the one at 20");
ok(isPixelBrighterAtThan(40, 60),
"The tick at 40 should be brighter than the one at 60");
ok(isPixelBrighterAtThan(80, 60),
"The tick at 80 should be brighter than the one at 60");
ok(isPixelBrighterAtThan(80, 100),
"The tick at 80 should be brighter than the one at 100");
ok(isPixelBrighterAtThan(120, 100),
"The tick at 120 should be brighter than the one at 100");
ok(isPixelBrighterAtThan(120, 140),
"The tick at 120 should be brighter than the one at 140");
ok(isPixelBrighterAtThan(160, 140),
"The tick at 160 should be brighter than the one at 140");
ok(isPixelEquallyBright(20, 60),
"The tick at 20 should be equally bright to the one at 60");
ok(isPixelEquallyBright(100, 140),
"The tick at 100 should be equally bright to the one at 140");
ok(isPixelEquallyBright(40, 120),
"The tick at 40 should be equally bright to the one at 120");
ok(isPixelEquallyBright(0, 80),
"The tick at 80 should be equally bright to the one at 160");
ok(isPixelEquallyBright(80, 160),
"The tick at 80 should be equally bright to the one at 160");
function hasPixelAt(x) {
let i = (x | 0) * 4;
return data[i] && data[i + 1] && data[i + 2] && data[i + 3];
}
function isPixelBrighterAtThan(x1, x2) {
let i = (x1 | 0) * 4;
let j = (x2 | 0) * 4;
return data[i + 3] > data [j + 3];
}
function isPixelEquallyBright(x1, x2) {
let i = (x1 | 0) * 4;
let j = (x2 | 0) * 4;
return data[i + 3] == data [j + 3];
}
teardown(aMonitor).then(finish);
});
aDebuggee.location.reload();
});
}

Просмотреть файл

@ -97,10 +97,14 @@ jsonScopeName=JSON
# in the response tab of the network details pane for a JSONP scope.
jsonpScopeName=JSONP → callback %S()
# LOCALIZATION NOTE (networkMenu.size): This is the label displayed
# LOCALIZATION NOTE (networkMenu.sizeKB): This is the label displayed
# in the network menu specifying the size of a request (in kilobytes).
networkMenu.sizeKB=%S KB
# LOCALIZATION NOTE (networkMenu.total): This is the label displayed
# LOCALIZATION NOTE (networkMenu.totalMS): This is the label displayed
# in the network menu specifying the time for a request to finish (in milliseconds).
networkMenu.totalMS=→ %S ms
# LOCALIZATION NOTE (networkMenu.divisionMS): This is the label displayed
# in the network menu specifying timing interval divisions (in milliseconds).
networkMenu.divisionMS=%S ms

Просмотреть файл

@ -25,10 +25,10 @@
}
.requests-menu-subitem {
padding: 0 4px;
padding: 4px;
}
.requests-menu-header:not(:last-of-type),
.requests-menu-header:not(:last-child),
.requests-menu-subitem:not(:last-child) {
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
@ -112,24 +112,35 @@
width: 6em;
}
.requests-menu-header.requests-menu-waterfall {
/* Network requests table: waterfall header */
#requests-menu-timeline-label {
-moz-padding-start: 8px;
-moz-padding-end: 8px;
text-align: center;
}
/* Network request waterfall */
.requests-menu-timings-division {
width: 100px;
padding-top: 2px;
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #999;
font-size: 75%;
text-align: left;
pointer-events: none;
}
.requests-menu-timings-division:not(:first-child) {
-moz-margin-start: -100px !important; /* Don't affect layout. */
}
/* Network requests table: waterfall items */
.requests-menu-subitem.requests-menu-waterfall {
-moz-padding-start: 4px;
-moz-padding-end: 4px;
background-size: 5px;
background-image:
-moz-linear-gradient(left,
transparent 25%,
rgba(255,255,255,0.02) 25%,
rgba(255,255,255,0.02) 75%,
transparent 75%);
background-repeat: repeat-y; /* Background created on a <canvas> in js. */
margin-top: -1px; /* Compensate borders. */
margin-bottom: -1px;
}
.requests-menu-timings {
@ -213,6 +224,10 @@
background: rgba(255,255,255,0.05);
}
.side-menu-widget-item-contents {
padding: 0px 4px;
}
/* Network request details */
#details-pane {

Просмотреть файл

@ -25,10 +25,10 @@
}
.requests-menu-subitem {
padding: 0 4px;
padding: 4px;
}
.requests-menu-header:not(:last-of-type),
.requests-menu-header:not(:last-child),
.requests-menu-subitem:not(:last-child) {
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
@ -112,24 +112,35 @@
width: 8em;
}
.requests-menu-header.requests-menu-waterfall {
/* Network requests table: waterfall header */
#requests-menu-timeline-label {
-moz-padding-start: 8px;
-moz-padding-end: 8px;
text-align: center;
}
/* Network request waterfall */
.requests-menu-timings-division {
width: 100px;
padding-top: 2px;
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #999;
font-size: 75%;
text-align: left;
pointer-events: none;
}
.requests-menu-timings-division:not(:first-child) {
-moz-margin-start: -100px !important; /* Don't affect layout. */
}
/* Network requests table: waterfall items */
.requests-menu-subitem.requests-menu-waterfall {
-moz-padding-start: 4px;
-moz-padding-end: 4px;
background-size: 5px;
background-image:
-moz-linear-gradient(left,
transparent 25%,
rgba(255,255,255,0.02) 25%,
rgba(255,255,255,0.02) 75%,
transparent 75%);
background-repeat: repeat-y; /* Background created on a <canvas> in js. */
margin-top: -1px; /* Compensate borders. */
margin-bottom: -1px;
}
.requests-menu-timings {
@ -213,6 +224,10 @@
background: rgba(255,255,255,0.05);
}
.side-menu-widget-item-contents {
padding: 0px 4px;
}
/* Network request details */
#details-pane {

Просмотреть файл

@ -25,10 +25,10 @@
}
.requests-menu-subitem {
padding: 0 4px;
padding: 4px;
}
.requests-menu-header:not(:last-of-type),
.requests-menu-header:not(:last-child),
.requests-menu-subitem:not(:last-child) {
-moz-border-end: 1px solid hsla(210,8%,5%,.25);
box-shadow: 1px 0 0 hsla(210,16%,76%,.1);
@ -112,24 +112,35 @@
width: 8em;
}
.requests-menu-header.requests-menu-waterfall {
/* Network requests table: waterfall header */
#requests-menu-timeline-label {
-moz-padding-start: 8px;
-moz-padding-end: 8px;
text-align: center;
}
/* Network request waterfall */
.requests-menu-timings-division {
width: 100px;
padding-top: 1px;
-moz-padding-start: 4px;
-moz-border-start: 1px dotted #999;
font-size: 90%;
text-align: left;
pointer-events: none;
}
.requests-menu-timings-division:not(:first-child) {
-moz-margin-start: -100px !important; /* Don't affect layout. */
}
/* Network requests table: waterfall items */
.requests-menu-subitem.requests-menu-waterfall {
-moz-padding-start: 4px;
-moz-padding-end: 4px;
background-size: 5px;
background-image:
-moz-linear-gradient(left,
transparent 25%,
rgba(255,255,255,0.02) 25%,
rgba(255,255,255,0.02) 75%,
transparent 75%);
background-repeat: repeat-y; /* Background created on a <canvas> in js. */
margin-top: -1px; /* Compensate borders. */
margin-bottom: -1px;
}
.requests-menu-timings {
@ -213,6 +224,10 @@
background: rgba(255,255,255,0.05);
}
.side-menu-widget-item-contents {
padding: 0px 4px;
}
/* Network request details */
#details-pane {