Bug 1628423 - Distinguish favicon loads from lazy loading images. r=Honza

Note that it seems unpredictable when we receive the favicon request in the
network monitor, for example, on a Linux machine, the favicon request for a
static link element after the stylesheet link element in
html_cause-test-page.html it happens after the XHR request was received. So
instead of using a static link favicon element, we add a favicon link
dynamically in tests.  Unfortunately there is no handy way to tell a favicon
request has done in contents, so we use LinkHandlerParent utility here.

Also note that the favicon request is triggered in FaviconLoader.jsm, which
means the call stack should NOT be shown (bug 1280266).  For now, we
intentionally specify the stack and use todo for the case.

Differential Revision: https://phabricator.services.mozilla.com/D70572
This commit is contained in:
Hiroyuki Ikezoe 2020-04-21 03:25:09 +00:00
Родитель 10353690c9
Коммит 9616df4a8b
4 изменённых файлов: 116 добавлений и 32 удалений

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

@ -93,6 +93,22 @@ const EXPECTED_REQUESTS = [
}, },
], ],
}, },
{
method: "GET",
url: EXAMPLE_URL + "favicon_request",
causeType: "img",
causeUri: INITIATOR_URL,
// the favicon request is triggered in FaviconLoader.jsm module, it should
// NOT be shown in the stack (bug 1280266). For now we intentionally
// specify the file and the line number to be properly sorted.
// NOTE: The line number can be an arbitrary number greater than 0.
stack: [
{
file: "resource:///modules/FaviconLoader.jsm",
line: Number.MAX_SAFE_INTEGER,
},
],
},
{ {
method: "GET", method: "GET",
url: EXAMPLE_URL + "lazy_img_request", url: EXAMPLE_URL + "lazy_img_request",
@ -112,7 +128,7 @@ const EXPECTED_REQUESTS = [
url: EXAMPLE_URL + "beacon_request", url: EXAMPLE_URL + "beacon_request",
causeType: "beacon", causeType: "beacon",
causeUri: INITIATOR_URL, causeUri: INITIATOR_URL,
stack: [{ fn: "performBeaconRequest", file: INITIATOR_URL, line: 70 }], stack: [{ fn: "performBeaconRequest", file: INITIATOR_URL, line: 80 }],
}, },
]; ];
@ -142,6 +158,9 @@ add_task(async function() {
const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length); const wait = waitForNetworkEvents(monitor, EXPECTED_REQUESTS.length);
BrowserTestUtils.loadURI(tab.linkedBrowser, INITIATOR_URL); BrowserTestUtils.loadURI(tab.linkedBrowser, INITIATOR_URL);
registerFaviconNotifier(tab.linkedBrowser);
await wait; await wait;
// For all expected requests // For all expected requests
@ -180,6 +199,7 @@ add_task(async function() {
const expectedOrder = EXPECTED_REQUESTS.sort(initiatorSortPredicate).map( const expectedOrder = EXPECTED_REQUESTS.sort(initiatorSortPredicate).map(
r => { r => {
let isChromeFrames = false;
const lastFrameExists = !!r.stack; const lastFrameExists = !!r.stack;
let initiator = ""; let initiator = "";
let lineNumber = ""; let lineNumber = "";
@ -187,16 +207,24 @@ add_task(async function() {
const { file, line: _lineNumber } = r.stack[0]; const { file, line: _lineNumber } = r.stack[0];
initiator = getUrlBaseName(file); initiator = getUrlBaseName(file);
lineNumber = ":" + _lineNumber; lineNumber = ":" + _lineNumber;
isChromeFrames = file.startsWith("resource:///");
} }
const causeStr = lastFrameExists ? " (" + r.causeType + ")" : r.causeType; const causeStr = lastFrameExists ? " (" + r.causeType + ")" : r.causeType;
return initiator + lineNumber + causeStr; return {
initiatorStr: initiator + lineNumber + causeStr,
isChromeFrames,
};
} }
); );
expectedOrder.forEach((expectedInitiator, i) => { expectedOrder.forEach((expectedInitiator, i) => {
const request = getSortedRequests(store.getState())[i]; const request = getSortedRequests(store.getState())[i];
let initiator; let initiator;
if (request.cause.stacktraceAvailable) { // In cases of chrome frames, we shouldn't have stack.
if (
request.cause.stacktraceAvailable &&
!expectedInitiator.isChromeFrames
) {
const { filename, lineNumber } = request.cause.lastFrame; const { filename, lineNumber } = request.cause.lastFrame;
initiator = initiator =
getUrlBaseName(filename) + getUrlBaseName(filename) +
@ -208,11 +236,20 @@ add_task(async function() {
} else { } else {
initiator = request.cause.type; initiator = request.cause.type;
} }
is(
initiator, if (expectedInitiator.isChromeFrames) {
expectedInitiator, todo_is(
`The request #${i} has the expected initiator after sorting` initiator,
); expectedInitiator.initiatorStr,
`The request #${i} has the expected initiator after sorting`
);
} else {
is(
initiator,
expectedInitiator.initiatorStr,
`The request #${i} has the expected initiator after sorting`
);
}
}); });
await teardown(monitor); await teardown(monitor);

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

@ -6,7 +6,7 @@
verifyRequestItemTarget, waitFor, waitForDispatch, testFilterButtons, verifyRequestItemTarget, waitFor, waitForDispatch, testFilterButtons,
performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor, performRequestsInContent, waitForNetworkEvents, selectIndexAndWaitForSourceEditor,
testColumnsAlignment, hideColumn, showColumn, performRequests, waitForRequestData, testColumnsAlignment, hideColumn, showColumn, performRequests, waitForRequestData,
toggleBlockedUrl */ toggleBlockedUrl, registerFaviconNotifier */
"use strict"; "use strict";
@ -16,6 +16,10 @@ Services.scriptloader.loadSubScript(
this this
); );
const { LinkHandlerParent } = ChromeUtils.import(
"resource:///actors/LinkHandlerParent.jsm"
);
const { const {
getFormattedIPAndPort, getFormattedIPAndPort,
getFormattedTime, getFormattedTime,
@ -1131,26 +1135,33 @@ function validateRequests(requests, monitor) {
// if "stack" is array, check the details about the top stack frames // if "stack" is array, check the details about the top stack frames
if (Array.isArray(stack)) { if (Array.isArray(stack)) {
stack.forEach((frame, j) => { stack.forEach((frame, j) => {
is( // If the `fn` is "*", it means the request is triggered from chrome
stacktrace[j].functionName, // resources, e.g. `resource:///modules/XX.jsm`, so we skip checking
frame.fn, // the function name for now (bug 1280266).
`Request #${i} has the correct function on JS stack frame #${j}` if (frame.file.startsWith("resource:///")) {
); todo(false, "Requests from chrome resource should not be included");
is( } else {
stacktrace[j].filename.split("/").pop(), is(
frame.file.split("/").pop(), stacktrace[j].functionName,
`Request #${i} has the correct file on JS stack frame #${j}` frame.fn,
); `Request #${i} has the correct function on JS stack frame #${j}`
is( );
stacktrace[j].lineNumber, is(
frame.line, stacktrace[j].filename.split("/").pop(),
`Request #${i} has the correct line number on JS stack frame #${j}` frame.file.split("/").pop(),
); `Request #${i} has the correct file on JS stack frame #${j}`
is( );
stacktrace[j].asyncCause, is(
frame.asyncCause, stacktrace[j].lineNumber,
`Request #${i} has the correct async cause on JS stack frame #${j}` frame.line,
); `Request #${i} has the correct line number on JS stack frame #${j}`
);
is(
stacktrace[j].asyncCause,
frame.asyncCause,
`Request #${i} has the correct async cause on JS stack frame #${j}`
);
}
}); });
} }
} else { } else {
@ -1225,6 +1236,27 @@ function clickElement(element, monitor) {
EventUtils.synthesizeMouseAtCenter(element, {}, monitor.panelWin); EventUtils.synthesizeMouseAtCenter(element, {}, monitor.panelWin);
} }
/**
* Register a listener to be notified when a favicon finished loading and
* dispatch a "devtools:test:favicon" event to the favicon's link element.
*
* @param {Browser} browser
* Target browser to observe the favicon load.
*/
function registerFaviconNotifier(browser) {
const listener = async (name, data) => {
if (name == "SetIcon" || name == "SetFailedIcon") {
await SpecialPowers.spawn(browser, [], async () => {
content.document
.querySelector("link[rel='icon']")
.dispatchEvent(new content.CustomEvent("devtools:test:favicon"));
});
LinkHandlerParent.removeListenerForTests(listener);
}
};
LinkHandlerParent.addListenerForTests(listener);
}
/** /**
* Predicates used when sorting items. * Predicates used when sorting items.
* *

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

@ -50,6 +50,16 @@
}); });
} }
function performFavicon() {
return new Promise(function (resolve) {
const link = document.createElement("link");
link.rel = "icon";
link.href = "favicon_request";
document.querySelector("head").appendChild(link);
link.addEventListener("devtools:test:favicon", resolve);
});
}
function performLazyLoadingImage() { function performLazyLoadingImage() {
return new Promise(function (resolve) { return new Promise(function (resolve) {
const lazyImgs = document.querySelectorAll("img[loading='lazy']"); const lazyImgs = document.querySelectorAll("img[loading='lazy']");
@ -75,6 +85,7 @@
await performFetchRequest(); await performFetchRequest();
await performPromiseFetchRequest(); await performPromiseFetchRequest();
await performTimeoutFetchRequest(); await performTimeoutFetchRequest();
await performFavicon();
await performLazyLoadingImage(); await performLazyLoadingImage();
// Finally, send a beacon request // Finally, send a beacon request

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

@ -708,7 +708,11 @@ NetworkObserver.prototype = {
} }
event.cause = { event.cause = {
type: causeTypeToString(causeType, channel.loadFlags), type: causeTypeToString(
causeType,
channel.loadFlags,
channel.loadInfo.internalContentPolicyType
),
loadingDocumentUri: causeUri, loadingDocumentUri: causeUri,
stacktrace, stacktrace,
}; };
@ -1501,11 +1505,11 @@ const LOAD_CAUSE_STRINGS = {
[Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest", [Ci.nsIContentPolicy.TYPE_WEB_MANIFEST]: "webManifest",
}; };
function causeTypeToString(causeType, loadFlags) { function causeTypeToString(causeType, loadFlags, internalContentPolicyType) {
let prefix = ""; let prefix = "";
if ( if (
(causeType == Ci.nsIContentPolicy.TYPE_IMAGESET || (causeType == Ci.nsIContentPolicy.TYPE_IMAGESET ||
causeType == Ci.nsIContentPolicy.TYPE_IMAGE) && internalContentPolicyType == Ci.nsIContentPolicy.TYPE_INTERNAL_IMAGE) &&
loadFlags & Ci.nsIRequest.LOAD_BACKGROUND loadFlags & Ci.nsIRequest.LOAD_BACKGROUND
) { ) {
prefix = "lazy-"; prefix = "lazy-";