зеркало из https://github.com/mozilla/gecko-dev.git
Bug 859041 - Display timing interval divisions (ms ticks) in the timeline, r=rcampbell
This commit is contained in:
Родитель
3d97bcc986
Коммит
7e66a760e4
|
@ -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 {
|
||||
|
|
Загрузка…
Ссылка в новой задаче