зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1794651 - Adding custom position override to feature callout r=mviar
Differential Revision: https://phabricator.services.mozilla.com/D159556
This commit is contained in:
Родитель
5318c8b0a3
Коммит
e9b58109c1
|
@ -112,7 +112,7 @@ function _createContainer() {
|
|||
// Don't render the callout if the parent element is not present.
|
||||
// This means the message was misconfigured, mistargeted, or the
|
||||
// content of the parent page is not as expected.
|
||||
if (!parent) {
|
||||
if (!parent && !CURRENT_SCREEN?.content.callout_position_override) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -160,8 +160,11 @@ function _positionCallout() {
|
|||
overlap -= arrowWidth;
|
||||
// Is the document layout right to left?
|
||||
const RTL = document.dir === "rtl";
|
||||
const customPosition = CURRENT_SCREEN?.content.callout_position_override;
|
||||
|
||||
if (!container || !parentEl) {
|
||||
// Early exit if the container doesn't exist,
|
||||
// or if we're missing a parent element and don't have a custom callout position
|
||||
if (!container || (!parentEl && !customPosition)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -189,39 +192,96 @@ function _positionCallout() {
|
|||
});
|
||||
}
|
||||
|
||||
function addArrowPositionClassToContainer(finalArrowPosition) {
|
||||
let className;
|
||||
switch (finalArrowPosition) {
|
||||
case "bottom":
|
||||
className = "arrow-bottom";
|
||||
break;
|
||||
case "left":
|
||||
className = "arrow-inline-start";
|
||||
break;
|
||||
case "right":
|
||||
className = "arrow-inline-end";
|
||||
break;
|
||||
case "top-start":
|
||||
className = RTL ? "arrow-top-end" : "arrow-top-start";
|
||||
break;
|
||||
case "top-end":
|
||||
className = RTL ? "arrow-top-start" : "arrow-top-end";
|
||||
break;
|
||||
case "top":
|
||||
default:
|
||||
className = "arrow-top";
|
||||
break;
|
||||
}
|
||||
|
||||
container.classList.add(className);
|
||||
}
|
||||
|
||||
function overridePosition() {
|
||||
// We override _every_ positioner here, because we want to manually set all
|
||||
// container.style.positions in every positioner's "position" function
|
||||
// regardless of the actual arrow position
|
||||
for (const position in positioners) {
|
||||
positioners[position].position = function() {
|
||||
if (customPosition.top) {
|
||||
container.style.top = customPosition.top;
|
||||
}
|
||||
|
||||
if (customPosition.left) {
|
||||
container.style.left = customPosition.left;
|
||||
}
|
||||
|
||||
if (customPosition.right) {
|
||||
container.style.right = customPosition.right;
|
||||
}
|
||||
|
||||
if (customPosition.bottom) {
|
||||
container.style.bottom = customPosition.bottom;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
const positioners = {
|
||||
// availableSpace should be the space between the edge of the page in the assumed direction
|
||||
// and the edge of the parent (with the callout being intended to fit between those two edges)
|
||||
// while needed space should be the space necessary to fit the callout container
|
||||
top: {
|
||||
availableSpace:
|
||||
document.documentElement.clientHeight -
|
||||
getOffset(parentEl).top -
|
||||
parentEl.clientHeight,
|
||||
availableSpace() {
|
||||
return (
|
||||
document.documentElement.clientHeight -
|
||||
getOffset(parentEl).top -
|
||||
parentEl.clientHeight
|
||||
);
|
||||
},
|
||||
neededSpace: container.clientHeight - overlap,
|
||||
position() {
|
||||
// Point to an element above the callout
|
||||
let containerTop =
|
||||
getOffset(parentEl).top + parentEl.clientHeight - overlap;
|
||||
container.style.top = `${Math.max(0, containerTop)}px`;
|
||||
container.classList.add("arrow-top");
|
||||
centerHorizontally(container, parentEl);
|
||||
},
|
||||
},
|
||||
bottom: {
|
||||
availableSpace: getOffset(parentEl).top,
|
||||
availableSpace() {
|
||||
return getOffset(parentEl).top;
|
||||
},
|
||||
neededSpace: container.clientHeight - overlap,
|
||||
position() {
|
||||
// Point to an element below the callout
|
||||
let containerTop =
|
||||
getOffset(parentEl).top - container.clientHeight + overlap;
|
||||
container.style.top = `${Math.max(0, containerTop)}px`;
|
||||
container.classList.add("arrow-bottom");
|
||||
centerHorizontally(container, parentEl);
|
||||
},
|
||||
},
|
||||
right: {
|
||||
availableSpace: getOffset(parentEl).left,
|
||||
availableSpace() {
|
||||
return getOffset(parentEl).left;
|
||||
},
|
||||
neededSpace: container.clientWidth - overlap,
|
||||
position() {
|
||||
// Point to an element to the right of the callout
|
||||
|
@ -229,12 +289,12 @@ function _positionCallout() {
|
|||
getOffset(parentEl).left - container.clientWidth + overlap;
|
||||
container.style.left = `${Math.max(0, containerLeft)}px`;
|
||||
container.style.top = `${getOffset(parentEl).top}px`;
|
||||
container.classList.add("arrow-inline-end");
|
||||
},
|
||||
},
|
||||
left: {
|
||||
availableSpace:
|
||||
document.documentElement.clientWidth - getOffset(parentEl).right,
|
||||
availableSpace() {
|
||||
return document.documentElement.clientWidth - getOffset(parentEl).right;
|
||||
},
|
||||
neededSpace: container.clientWidth - overlap,
|
||||
position() {
|
||||
// Point to an element to the left of the callout
|
||||
|
@ -242,17 +302,24 @@ function _positionCallout() {
|
|||
getOffset(parentEl).left + parentEl.clientWidth - overlap;
|
||||
container.style.left = `${Math.max(0, containerLeft)}px`;
|
||||
container.style.top = `${getOffset(parentEl).top}px`;
|
||||
container.classList.add("arrow-inline-start");
|
||||
},
|
||||
},
|
||||
"top-end": {
|
||||
availableSpace() {
|
||||
document.documentElement.clientHeight -
|
||||
getOffset(parentEl).top -
|
||||
parentEl.clientHeight;
|
||||
},
|
||||
neededSpace: container.clientHeight - overlap,
|
||||
position() {
|
||||
// Point to an element above and at the end of the callout
|
||||
let containerTop =
|
||||
getOffset(parentEl).top + parentEl.clientHeight - overlap;
|
||||
container.style.top = `${Math.max(0, containerTop)}px`;
|
||||
container.style.top = `${Math.max(
|
||||
container.clientHeight - overlap,
|
||||
containerTop
|
||||
)}px`;
|
||||
alignEnd(container, parentEl);
|
||||
container.classList.add(RTL ? "arrow-top-start" : "arrow-top-end");
|
||||
},
|
||||
},
|
||||
};
|
||||
|
@ -265,7 +332,7 @@ function _positionCallout() {
|
|||
// not the alignment of the arrow along the edge of the callout
|
||||
let edgePosition = position.split("-")[0];
|
||||
return (
|
||||
positioners[edgePosition].availableSpace >
|
||||
positioners[edgePosition].availableSpace() >
|
||||
positioners[edgePosition].neededSpace
|
||||
);
|
||||
}
|
||||
|
@ -283,7 +350,8 @@ function _positionCallout() {
|
|||
// at an element to the right of itself, while in RTL layouts it is pointing to the left of itself
|
||||
position = RTL ^ (position === "start") ? "left" : "right";
|
||||
}
|
||||
if (calloutFits(position)) {
|
||||
// If we're overriding the position, we don't need to sort for available space
|
||||
if (customPosition || calloutFits(position)) {
|
||||
return position;
|
||||
}
|
||||
let sortedPositions = Object.keys(positioners)
|
||||
|
@ -291,8 +359,8 @@ function _positionCallout() {
|
|||
.filter(calloutFits)
|
||||
.sort((a, b) => {
|
||||
return (
|
||||
positioners[b].availableSpace - positioners[b].neededSpace >
|
||||
positioners[a].availableSpace - positioners[a].neededSpace
|
||||
positioners[b].availableSpace() - positioners[b].neededSpace >
|
||||
positioners[a].availableSpace() - positioners[a].neededSpace
|
||||
);
|
||||
});
|
||||
// If the callout doesn't fit in any position, use the configured one.
|
||||
|
@ -322,9 +390,16 @@ function _positionCallout() {
|
|||
|
||||
clearPosition(container);
|
||||
|
||||
if (customPosition) {
|
||||
// We override the position functions with new functions here,
|
||||
// but they don't actually get executed in the override function
|
||||
overridePosition();
|
||||
}
|
||||
|
||||
let finalPosition = choosePosition();
|
||||
if (finalPosition) {
|
||||
positioners[finalPosition].position();
|
||||
addArrowPositionClassToContainer(finalPosition);
|
||||
}
|
||||
|
||||
container.classList.remove("hidden");
|
||||
|
|
|
@ -297,3 +297,41 @@ add_task(
|
|||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
||||
add_task(
|
||||
async function feature_callout_custom_position_override_properties_are_applied() {
|
||||
const testMessage = {
|
||||
message: FeatureCalloutMessages.getMessages().find(
|
||||
m => m.id === "FIREFOX_VIEW_FEATURE_TOUR_1"
|
||||
),
|
||||
};
|
||||
testMessage.message.content.screens[0].content.callout_position_override = {
|
||||
top: "500px",
|
||||
left: "500px",
|
||||
};
|
||||
|
||||
const sandbox = sinon.createSandbox();
|
||||
const sendTriggerStub = sandbox.stub(ASRouter, "sendTriggerMessage");
|
||||
sendTriggerStub.resolves(testMessage);
|
||||
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
url: "about:firefoxview",
|
||||
},
|
||||
async browser => {
|
||||
const { document } = browser.contentWindow;
|
||||
await waitForCalloutScreen(document, 1);
|
||||
let container = document.querySelector(calloutSelector);
|
||||
let containerLeft = container.getBoundingClientRect().left;
|
||||
let containerTop = container.getBoundingClientRect().top;
|
||||
ok(
|
||||
containerLeft === 500 && containerTop === 500,
|
||||
"Feature callout container has a top position of 500, and left position of 500"
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
sandbox.restore();
|
||||
}
|
||||
);
|
||||
|
|
Загрузка…
Ссылка в новой задаче