// 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 getRawListStyleImage(button) { // Ensure popups are initialized so that the elements are rendered and // getComputedStyle works. for (
let popup = button.closest("panel,menupopup");
popup;
popup = popup.parentElement?.closest("panel,menupopup")
) {
popup.ensureInitialized();
}
function getListStyleImage(button) {
let match = /url\("([^"]*)"\)/.exec(getRawListStyleImage(button)); 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 ContentTask.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 };
}
/** * Wait for the given PopupNotification to display * * @param {string} name * The name of the notification to wait for. * @param {Window} [win] * The chrome window in which to wait for the notification. * * @returns {Promise} * Resolves with the notification window.
*/ function promisePopupNotificationShown(name, win = window) { returnnew Promise(resolve => { function popupshown() {
let notification = win.PopupNotifications.getNotification(name); if (!notification) { return;
}
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;
}
/** * Retrieve the content dimensions (and wait until the content gets to the. * size of the browser element they are loaded into, optionally tollerating * size differences to prevent intermittent failures). * * @param {BrowserElement} browser * The browser element where the content has been loaded. * @param {number} [tolleratedWidthSizeDiff] * width size difference to tollerate in pixels (defaults to 1). * * @returns {Promise<object>} * An object with the dims retrieved from the content.
*/
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.ownerGlobal.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;
}
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.ownerGlobal
);
}
// 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);
}
function promisePrefChangeObserved(pref) { returnnew Promise(resolve =>
Preferences.observe(pref, function prefObserver() {
Preferences.ignore(pref, prefObserver);
resolve();
})
);
}
// 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.
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.