// There are shutdown issues for which multiple rejections are left uncaught. // This bug should be fixed, but for the moment all tests in this directory // allow various classes of promise rejections. // // NOTE: Allowing rejections on an entire directory should be avoided. // Normally you should use "expectUncaughtRejection" to flag individual // failures. const { PromiseTestUtils } = ChromeUtils.importESModule( "resource://testing-common/PromiseTestUtils.sys.mjs"
);
PromiseTestUtils.allowMatchingRejectionsGlobally(
/Message manager disconnected/
);
PromiseTestUtils.allowMatchingRejectionsGlobally(/No matching message handler/);
PromiseTestUtils.allowMatchingRejectionsGlobally(
/Receiving end does not exist/
);
var { makeWidgetId, promisePopupShown, getPanelForNode, awaitBrowserLoaded } =
AppUiTestInternals;
// The extension tests can run a lot slower under ASAN. if (AppConstants.ASAN) {
requestLongerTimeout(5);
}
function loadTestSubscript(filePath) {
Services.scriptloader.loadSubScript(new URL(filePath, gTestPath).href, this);
}
// Ensure when we turn off topsites in the next few lines, // we don't hit any remote endpoints.
Services.prefs
.getDefaultBranch("browser.newtabpage.activity-stream.")
.setStringPref("discoverystream.endpointSpocsClear", ""); // Leaving Top Sites enabled during these tests would create site screenshots // and update pinned Top Sites unnecessarily.
Services.prefs
.getDefaultBranch("browser.newtabpage.activity-stream.")
.setBoolPref("feeds.topsites", false);
Services.prefs
.getDefaultBranch("browser.newtabpage.activity-stream.")
.setBoolPref("feeds.system.topsites", false);
{ // Touch the recipeParentPromise lazy getter so we don't get // `this._recipeManager is undefined` errors during tests. const { LoginManagerParent } = ChromeUtils.importESModule( "resource://gre/modules/LoginManagerParent.sys.mjs"
); void LoginManagerParent.recipeParentPromise;
}
// Bug 1239884: Our tests occasionally hit a long GC pause at unpredictable // times in debug builds, which results in intermittent timeouts. Until we have // a better solution, we force a GC after certain strategic tests, which tend to // accumulate a high number of unreaped windows. function forceGC() { if (AppConstants.DEBUG) {
Cu.forceGC();
}
}
var focusWindow = async function focusWindow(win) { if (Services.focus.activeWindow == win) { return;
}
let promise = new Promise(resolve => {
win.addEventListener( "focus", function () {
resolve();
},
{ capture: true, once: true }
);
});
win.focus();
await promise;
};
function imageBufferFromDataURI(encodedImageData) {
let decodedImageData = atob(encodedImageData); return Uint8Array.from(decodedImageData, byte => byte.charCodeAt(0)).buffer;
}
let img = "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg=="; var imageBuffer = imageBufferFromDataURI(img);
function getInlineOptionsBrowser(aboutAddonsBrowser) {
let { contentDocument } = aboutAddonsBrowser; return contentDocument.getElementById("addon-inline-options");
}
function _ensurePopupsInitialized(element) { // Ensure popups are initialized so that the elements are rendered and // getComputedStyle works. for (
let popup = element.closest("panel,menupopup");
popup;
popup = popup.parentElement?.closest("panel,menupopup")
) {
popup.ensureInitialized();
}
}
function getRawListStyleImage(button) {
_ensurePopupsInitialized(button); return button.documentGlobal.getComputedStyle(button).listStyleImage;
}
function getListStyleImage(button) {
let match = /url\("([^"]*)"\)/.exec(getRawListStyleImage(button)); return match && match[1];
}
function getRawMenuitemImage(menuitem) {
_ensurePopupsInitialized(menuitem); return menuitem.documentGlobal
.getComputedStyle(menuitem)
.getPropertyValue("--webextension-menuitem-image");
}
function getMenuitemImage(menuitem) {
let match = /url\("([^"]*)"\)/.exec(getRawMenuitemImage(menuitem)); return match && match[1];
}
function promiseAnimationFrame(win = window) { return AppUiTestInternals.promiseAnimationFrame(win);
}
async function promiseBrowserContentUnloaded(browser) { // Wait until the content has unloaded before resuming the test, to avoid // calling extension.getViews too early (and having intermittent failures). const MSG_WINDOW_DESTROYED = "Test:BrowserContentDestroyed";
let unloadPromise = new Promise(resolve => {
Services.ppmm.addMessageListener(MSG_WINDOW_DESTROYED, function listener() {
Services.ppmm.removeMessageListener(MSG_WINDOW_DESTROYED, listener);
resolve();
});
});
await SpecialPowers.spawn(
browser,
[MSG_WINDOW_DESTROYED],
MSG_WINDOW_DESTROYED => {
let innerWindowId = this.content.windowGlobalChild.innerWindowId;
let observer = subject => { if (
innerWindowId === subject.QueryInterface(Ci.nsISupportsPRUint64).data
) {
Services.obs.removeObserver(observer, "inner-window-destroyed");
// Use process message manager to ensure that the message is delivered // even after the <browser>'s message manager is disconnected.
Services.cpmm.sendAsyncMessage(MSG_WINDOW_DESTROYED);
}
}; // Observe inner-window-destroyed, like ExtensionPageChild, to ensure that // the ExtensionPageContextChild instance has been unloaded when we resolve // the unloadPromise.
Services.obs.addObserver(observer, "inner-window-destroyed");
}
);
// Return an object so that callers can use "await". return { unloadPromise };
}
function promisePossiblyInaccurateContentDimensions(browser) { return SpecialPowers.spawn(browser, [], async function () { function copyProps(obj, props) {
let res = {}; for (let prop of props) {
res[prop] = obj[prop];
} return res;
}
/** *Retrievethecontentdimensions(andwaituntilthecontentgetstothe. *sizeofthebrowserelementtheyareloadedinto,optionallytollerating *sizedifferencestopreventintermittentfailures). * *@param{BrowserElement}browser *Thebrowserelementwherethecontenthasbeenloaded. *@param{number}[tolleratedWidthSizeDiff] *widthsizedifferencetotollerateinpixels(defaultsto1). * *@returns{Promise<object>} *Anobjectwiththedimsretrievedfromthecontent.
*/
async function promiseContentDimensions(browser, tolleratedWidthSizeDiff = 1) { // For remote browsers, each resize operation requires an asynchronous // round-trip to resize the content window. Since there's a certain amount of // unpredictability in the timing, mainly due to the unpredictability of // reflows, we need to wait until the content window dimensions match the // <browser> dimensions before returning data. for (;;) {
let dims = await promisePossiblyInaccurateContentDimensions(browser);
info(`Got dimensions: ${JSON.stringify(dims)}`); if (
Math.abs(browser.clientWidth - dims.window.innerWidth) <=
tolleratedWidthSizeDiff &&
browser.clientHeight === Math.round(dims.window.innerHeight)
) { // We don't expect zoom or so on these documents, so these should stay // consistent, assuming there are no scrollbars. // If you want to test extension popup pinch zooming you probably need to // remove this assert.
is(
dims.visualViewport.scale, 1, "We expect no pinch zoom on these tests"
); if (!dims.window.scrollMaxY && !dims.window.scrollMaxX) {
isfuzzy(
dims.window.innerHeight,
dims.visualViewport.height, 1, "VisualViewport and window height are consistent"
);
isfuzzy(
dims.window.innerWidth,
dims.visualViewport.width, 1, "VisualViewport and window width are consistent"
);
} return dims;
} const diffWidth = Math.abs(browser.clientWidth - dims.window.innerWidth); const diffHeight = Math.abs(browser.clientHeight - dims.window.innerHeight);
info(
`Content dimension did not reached the expected size yet (diff: ${diffWidth}x${diffHeight}). Wait further.`
);
await delay(50);
}
}
async function toggleBookmarksToolbar(visible = true) {
let bookmarksToolbar = document.getElementById("PersonalToolbar"); // Third parameter is 'persist' and true is the default. // Fourth parameter is 'animated' and we want no animation.
setToolbarVisibility(bookmarksToolbar, visible, true, false); if (!visible) { return BrowserTestUtils.waitForMutationCondition(
bookmarksToolbar,
{ attributes: true },
() => bookmarksToolbar.collapsed
);
}
async function openContextMenuInPopup(
extension,
selector = "body",
win = window
) {
let doc = win.document;
let contentAreaContextMenu = doc.getElementById("contentAreaContextMenu");
let browser = await awaitExtensionPanel(extension, win);
// Ensure that the document layout has been flushed before triggering the mouse event // (See Bug 1519808 for a rationale).
await browser.documentGlobal.promiseDocumentFlushed(() => {});
let popupShownPromise = BrowserTestUtils.waitForEvent(
contentAreaContextMenu, "popupshown"
);
await BrowserTestUtils.synthesizeMouseAtCenter(
selector,
{ type: "mousedown", button: 2 },
browser
);
await BrowserTestUtils.synthesizeMouseAtCenter(
selector,
{ type: "contextmenu" },
browser
);
await popupShownPromise; return contentAreaContextMenu;
}
// Ensure each test leaves the sidebar in its initial state when it completes const initialSidebarState = { ...SidebarController.getUIState(), command: "" };
registerCleanupFunction(async function () { const { ObjectUtils } = ChromeUtils.importESModule( "resource://gre/modules/ObjectUtils.sys.mjs"
); if (
!ObjectUtils.deepEqual(SidebarController.getUIState(), initialSidebarState)
) {
info("Restoring to initial sidebar state");
await SidebarController.updateUIState(initialSidebarState);
}
});
async function openContextMenuInSidebar(selector = "body") {
let contentAreaContextMenu =
SidebarController.browser.contentDocument.getElementById( "contentAreaContextMenu"
);
let browser = SidebarController.browser.contentDocument.getElementById( "webext-panels-browser"
);
let popupShownPromise = BrowserTestUtils.waitForEvent(
contentAreaContextMenu, "popupshown"
);
// Wait for the layout to be flushed, otherwise this test may // fail intermittently if synthesizeMouseAtCenter is being called // while the sidebar is still opening and the browser window layout // being recomputed.
await SidebarController.browser.contentWindow.promiseDocumentFlushed(
() => {}
);
// `selector` should refer to the content in the frame. If invalid the test can // fail intermittently because the click could inadvertently be registered on // the upper-left corner of the frame (instead of inside the frame).
async function openContextMenuInFrame(selector = "body", frameIndex = 0) {
let contentAreaContextMenu = document.getElementById( "contentAreaContextMenu"
);
let popupShownPromise = BrowserTestUtils.waitForEvent(
contentAreaContextMenu, "popupshown"
);
await BrowserTestUtils.synthesizeMouseAtCenter(
selector,
{ type: "contextmenu" },
gBrowser.selectedBrowser.browsingContext.children[frameIndex]
);
await popupShownPromise; return contentAreaContextMenu;
}
function openTabContextMenu(tab = gBrowser.selectedTab) { // The TabContextMenu initializes its strings only on a focus or mouseover event. // Calls focus event on the TabContextMenu before opening.
tab.focus();
let indexOfTab = Array.prototype.indexOf.call(tab.parentNode.children, tab); return openChromeContextMenu( "tabContextMenu",
`.tabbrowser-tab:nth-child(${indexOfTab + 1})`,
tab.documentGlobal
);
}
// Shows the popup for the page action which for lists // all available page actions
async function showPageActionsPanel(win = window) { // See the comment at getPageActionButton
win.gURLBar.setPageProxyState("valid");
await promiseAnimationFrame(win);
let pageActionsPopup = win.document.getElementById("pageActionPanel");
async function triggerPageActionWithKeyboardInPanel(
extension,
modifiers = {},
win = window
) {
let pageActionsPopup = await showPageActionsPanel(win);
let pageActionId = BrowserPageActions.panelButtonNodeIDForActionID(
makeWidgetId(extension.id)
);
let popupHiddenPromise = promisePopupHidden(pageActionsPopup);
let widgetButton = win.document.getElementById(pageActionId); if (widgetButton.disabled) {
pageActionsPopup.hidePopup(); returnnew Promise(SimpleTest.executeSoon);
}
// Use key navigation so that the PanelMultiView doesn't ignore key events while (win.document.activeElement != widgetButton) {
EventUtils.synthesizeKey("KEY_ArrowDown");
ok(
pageActionsPopup.contains(win.document.activeElement), "Focus is inside of the panel"
);
}
EventUtils.synthesizeKey("KEY_Enter", modifiers);
await popupHiddenPromise;
returnnew Promise(SimpleTest.executeSoon);
}
function closePageAction(extension, win = window) { return AppUiTestDelegate.closePageAction(win, extension.id);
}
// This monitor extension runs with incognito: not_allowed, if it receives any // events with incognito data it fails.
async function startIncognitoMonitorExtension() { function background() { // Bug 1513220 - We're unable to get the tab during onRemoved, so we track // valid tabs in "seen" so we can at least validate tabs that we have "seen" // during onRemoved. This means that the monitor extension must be started // prior to creating any tabs that will be removed.
// Map<tabId -> tab>
let seenTabs = new Map(); function getTabById(tabId) { return seenTabs.has(tabId)
? seenTabs.get(tabId)
: browser.tabs.get(tabId);
}
async function getIncognitoWindow(url = "about:privatebrowsing") { // Since events will be limited based on incognito, we need a // spanning extension to get the tab id so we can test access failure.
function getCssAvailRect(screen) { const availLeft = {}; const availTop = {}; const availWidth = {}; const availHeight = {}; // GetAvailRectDisplayPix is in desktop pixels; divide by the screen's own // CSS-to-desktop scale to get the CSS pixels that window.screenX uses.
screen.GetAvailRectDisplayPix(availLeft, availTop, availWidth, availHeight); const cssToDesktop =
screen.defaultCSSScaleFactor / screen.contentsScaleFactor; const left = Math.floor(availLeft.value / cssToDesktop); const top = Math.floor(availTop.value / cssToDesktop); const width = Math.floor(availWidth.value / cssToDesktop); const height = Math.floor(availHeight.value / cssToDesktop); return {
left,
top,
width,
height,
right: left + width,
bottom: top + height,
};
}
function isRectContained(actualRect, maxRect) {
is(
`top=${actualRect.top >= maxRect.top},bottom=${
actualRect.bottom <= maxRect.bottom
},left=${actualRect.left >= maxRect.left},right=${
actualRect.right <= maxRect.right
}`, "top=true,bottom=true,left=true,right=true",
`Dimension must be inside, top:${actualRect.top}>=${maxRect.top}, bottom:${actualRect.bottom}<=${maxRect.bottom}, left:${actualRect.left}>=${maxRect.left}, right:${actualRect.right}<=${maxRect.right}`
);
}
function getToolboxBackgroundColor() {
let body = document.body; // Ignore any potentially ongoing transition.
body.style.transitionProperty = "none";
let color = window.getComputedStyle(body).backgroundColor;
body.style.transitionProperty = ""; return color;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.