Bug 1833193 - Allow CFRs to use alt_anchor_id in more situations where anchor_id is not available. r=omc-reviewers,hanna_a

Differential Revision: https://phabricator.services.mozilla.com/D178099
This commit is contained in:
Shane Hughes 2023-07-19 20:25:15 +00:00
parent 7c063cf058
commit 9cfcbebf18
2 changed files with 141 additions and 51 deletions

View File

@ -803,29 +803,48 @@ class PageAction {
}
}
_getVisibleElement(id) {
const element = id && this.window.document.getElementById(id);
if (!element) {
return null; // element doesn't exist at all
}
const { visibility, display } = this.window.getComputedStyle(element);
if (
!this.window.isElementVisible(element) ||
visibility !== "visible" ||
display === "none"
) {
// CSS rules like visibility: hidden or display: none. these result in
// element being invisible and unclickable.
return null;
}
let widget = lazy.CustomizableUI.getWidget(id);
if (widget && (!widget.areaType || widget.areaType.includes("panel"))) {
// the element is a customizable widget (a toolbar item, e.g. the reload
// button or the downloads button). widgets can be in various areas, like
// the overflow panel (the chevron button that appears when your window is
// so small the toolbar overflows. they can also be in the customization
// palette, which is an element in the browser chrome, but it looks like a
// tab/page. it appears when you right click the toolbar and click Customize
// Toolbar. widgets in the palette are always present in the chrome's DOM.
// but they're totally invisible except in customize mode. so if a widget is
// in the palette, its areaType will be undefined.
return null;
}
return element;
}
async showPopup() {
const browser = this.window.gBrowser.selectedBrowser;
const message = RecommendationMap.get(browser);
const { content } = message;
let anchor;
// A hacky way of setting the popup anchor outside the usual url bar icon box
// See https://searchfox.org/mozilla-central/rev/847b64cc28b74b44c379f9bff4f415b97da1c6d7/toolkit/modules/PopupNotifications.jsm#42
//If the anchor has been moved to the overflow menu ('menu-panel') and an alt_anchor_id has been provided, we want to use the alt_anchor_id
if (
content.alt_anchor_id &&
lazy.CustomizableUI.getWidget(content.anchor_id).areaType.includes(
"panel"
)
) {
anchor = this.window.document.getElementById(content.alt_anchor_id);
} else {
anchor =
this.window.document.getElementById(content.anchor_id) ||
this.container;
}
browser.cfrpopupnotificationanchor = anchor;
// See https://searchfox.org/mozilla-central/rev/eb07633057d66ab25f9db4c5900eeb6913da7579/toolkit/modules/PopupNotifications.sys.mjs#44
browser.cfrpopupnotificationanchor =
this._getVisibleElement(content.anchor_id) ||
this._getVisibleElement(content.alt_anchor_id) ||
this.container;
await this._renderPopup(message, browser);
}
@ -836,7 +855,7 @@ class PageAction {
const { content } = message;
// A hacky way of setting the popup anchor outside the usual url bar icon box
// See https://searchfox.org/mozilla-central/rev/c5c002f81f08a73e04868e0c2bf0eb113f200b03/toolkit/modules/PopupNotifications.sys.mjs#40
// See https://searchfox.org/mozilla-central/rev/eb07633057d66ab25f9db4c5900eeb6913da7579/toolkit/modules/PopupNotifications.sys.mjs#44
browser.cfrpopupnotificationanchor =
this.window.document.getElementById(content.anchor_id) || this.container;

View File

@ -934,54 +934,125 @@ describe("CFRPageActions", () => {
let savedRec;
let pageAction;
let fakeAnchorId = "fake_anchor_id";
let sandboxShowPopup = sinon.createSandbox();
let fakePopUp = {
id: "fake_id",
template: "cfr_doorhanger",
content: {
skip_address_bar_notifier: true,
heading_text: "Fake Heading Text",
anchor_id: "fake_anchor_id",
},
};
let fakeAltAnchorId = "fake_alt_anchor_id";
let TEST_MESSAGE;
let getElmStub;
let getStyleStub;
let isElmVisibleStub;
let getWidgetStub;
beforeEach(() => {
const { id, content } = fakePopUp;
TEST_MESSAGE = {
id: "fake_id",
template: "cfr_doorhanger",
content: {
skip_address_bar_notifier: true,
heading_text: "Fake Heading Text",
anchor_id: fakeAnchorId,
},
};
getElmStub = sandbox
.stub(window.document, "getElementById")
.callsFake(id => ({ id }));
getStyleStub = sandbox
.stub(window, "getComputedStyle")
.returns({ display: "block", visibility: "visible" });
isElmVisibleStub = sandbox.stub().returns(true);
getWidgetStub = sandbox.stub();
globals.set({
CustomizableUI: { getWidget: getWidgetStub },
isElementVisible: isElmVisibleStub,
});
savedRec = {
id,
id: TEST_MESSAGE.id,
host: fakeHost,
content,
content: TEST_MESSAGE.content,
};
CFRPageActions.RecommendationMap.set(fakeBrowser, savedRec);
pageAction = new PageAction(window, dispatchStub);
sandboxShowPopup.stub(window.document, "getElementById");
sandboxShowPopup.stub(pageAction, "_renderPopup");
globals.set({
CustomizableUI: {
getWidget: sandboxShowPopup
.stub()
.withArgs(fakeAnchorId)
.returns({ areaType: "menu-panel" }),
},
});
sandbox.stub(pageAction, "_renderPopup");
});
afterEach(() => {
sandboxShowPopup.restore();
sandbox.restore();
globals.restore();
});
it("Should use default anchor_id if an alternate hasn't been provided", async () => {
it("should use anchor_id if element exists and is not a customizable widget", async () => {
await pageAction.showPopup();
assert.calledWith(window.document.getElementById, fakeAnchorId);
assert.equal(fakeBrowser.cfrpopupnotificationanchor.id, fakeAnchorId);
});
it("Should use alt_anchor_if if one has been provided AND the anchor_id has been removed", async () => {
let fakeAltAnchorId = "fake_alt_anchor_id";
fakePopUp.content.alt_anchor_id = fakeAltAnchorId;
it("should use anchor_id if element exists and is in the toolbar", async () => {
getWidgetStub.withArgs(fakeAnchorId).returns({ areaType: "toolbar" });
await pageAction.showPopup();
assert.calledWith(window.document.getElementById, fakeAltAnchorId);
assert.equal(fakeBrowser.cfrpopupnotificationanchor.id, fakeAnchorId);
});
it("should use default container if element exists but is in the widget overflow panel", async () => {
getWidgetStub
.withArgs(fakeAnchorId)
.returns({ areaType: "menu-panel" });
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
pageAction.container.id
);
});
it("should use default container if element exists but is in the customization palette", async () => {
getWidgetStub.withArgs(fakeAnchorId).returns({ areaType: null });
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
pageAction.container.id
);
});
it("should use alt_anchor_id if one has been provided and the anchor_id element cannot be found", async () => {
TEST_MESSAGE.content.alt_anchor_id = fakeAltAnchorId;
getElmStub.withArgs(fakeAnchorId).returns(null);
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
fakeAltAnchorId
);
});
it("should use alt_anchor_id if one has been provided and the anchor_id element is hidden by CSS", async () => {
TEST_MESSAGE.content.alt_anchor_id = fakeAltAnchorId;
getStyleStub
.withArgs(sandbox.match({ id: fakeAnchorId }))
.returns({ display: "none", visibility: "visible" });
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
fakeAltAnchorId
);
});
it("should use alt_anchor_id if one has been provided and the anchor_id element has no height/width", async () => {
TEST_MESSAGE.content.alt_anchor_id = fakeAltAnchorId;
isElmVisibleStub
.withArgs(sandbox.match({ id: fakeAnchorId }))
.returns(false);
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
fakeAltAnchorId
);
});
it("should use default container if the anchor_id and alt_anchor_id are both not visible", async () => {
TEST_MESSAGE.content.alt_anchor_id = fakeAltAnchorId;
getStyleStub
.withArgs(sandbox.match({ id: fakeAnchorId }))
.returns({ display: "none", visibility: "visible" });
getWidgetStub.withArgs(fakeAltAnchorId).returns({ areaType: null });
await pageAction.showPopup();
assert.equal(
fakeBrowser.cfrpopupnotificationanchor.id,
pageAction.container.id
);
});
});